From 2a16f2f53b9d3d5f93378511fbda14980634caec Mon Sep 17 00:00:00 2001 From: wehub-resource-sync Date: Mon, 13 Jul 2026 12:32:53 +0800 Subject: [PATCH] chore: import upstream snapshot with attribution --- .codecov.yml | 11 + .gitattributes | 2 + .github/CONTRIBUTING.rst | 105 + .github/dependabot.yml | 11 + .github/workflows/coverage.yml | 45 + .github/workflows/docs.yml | 34 + .github/workflows/linter.yml | 15 + .github/workflows/publish.yml | 35 + .github/workflows/test.yml | 64 + .gitignore | 74 + .readthedocs.yaml | 20 + CHANGES | 1341 ++++ CITATION.bib | 7 + LICENSE | 27 + README.rst | 192 + README.wehub.md | 7 + conftest.py | 35 + demo/mandelbrot.py | 33 + demo/manydigits.py | 106 + demo/pidigits.py | 82 + demo/plotting.py | 35 + demo/sofa.py | 63 + demo/taylor.py | 71 + docs/basics.rst | 253 + docs/calculus/approximation.rst | 23 + docs/calculus/differentiation.rst | 19 + docs/calculus/index.rst | 14 + docs/calculus/integration.rst | 36 + docs/calculus/inverselaplace.rst | 71 + docs/calculus/odes.rst | 7 + docs/calculus/optimization.rst | 25 + docs/calculus/polynomials.rst | 15 + docs/calculus/sums_limits.rst | 67 + docs/cli.rst | 8 + docs/conf.py | 52 + docs/contexts.rst | 370 + docs/functions/bessel.rst | 108 + docs/functions/constants.rst | 45 + docs/functions/elliptic.rst | 65 + docs/functions/expintegrals.rst | 70 + docs/functions/gamma.rst | 79 + docs/functions/hyperbolic.rst | 23 + docs/functions/hypergeometric.rst | 74 + docs/functions/index.rst | 22 + docs/functions/numtheory.rst | 58 + docs/functions/orthogonal.rst | 77 + docs/functions/powers.rst | 45 + docs/functions/qfunctions.rst | 20 + docs/functions/signals.rst | 34 + docs/functions/trigonometric.rst | 62 + docs/functions/zeta.rst | 60 + docs/general.rst | 240 + docs/genindex.rst | 2 + docs/identification.rst | 31 + docs/index.rst | 55 + docs/matrices.rst | 569 ++ docs/plots/ai.png | Bin 0 -> 26596 bytes docs/plots/ai.py | 5 + docs/plots/ai_c.png | Bin 0 -> 61953 bytes docs/plots/ai_c.py | 2 + docs/plots/ber.png | Bin 0 -> 19451 bytes docs/plots/ber.py | 6 + docs/plots/besseli.png | Bin 0 -> 19101 bytes docs/plots/besseli.py | 6 + docs/plots/besseli_c.png | Bin 0 -> 35095 bytes docs/plots/besseli_c.py | 2 + docs/plots/besselj.png | Bin 0 -> 23549 bytes docs/plots/besselj.py | 6 + docs/plots/besselj_c.png | Bin 0 -> 37422 bytes docs/plots/besselj_c.py | 2 + docs/plots/besselk.png | Bin 0 -> 15252 bytes docs/plots/besselk.py | 6 + docs/plots/besselk_c.png | Bin 0 -> 30483 bytes docs/plots/besselk_c.py | 2 + docs/plots/bessely.png | Bin 0 -> 19658 bytes docs/plots/bessely.py | 6 + docs/plots/bessely_c.png | Bin 0 -> 40491 bytes docs/plots/bessely_c.py | 2 + docs/plots/bi.png | Bin 0 -> 25211 bytes docs/plots/bi.py | 5 + docs/plots/bi_c.png | Bin 0 -> 67342 bytes docs/plots/bi_c.py | 2 + docs/plots/buildplots.py | 22 + docs/plots/chebyt.png | Bin 0 -> 33025 bytes docs/plots/chebyt.py | 7 + docs/plots/chebyu.png | Bin 0 -> 22495 bytes docs/plots/chebyu.py | 7 + docs/plots/coulombf.png | Bin 0 -> 34810 bytes docs/plots/coulombf.py | 7 + docs/plots/coulombf_c.png | Bin 0 -> 39897 bytes docs/plots/coulombf_c.py | 2 + docs/plots/coulombg.png | Bin 0 -> 34197 bytes docs/plots/coulombg.py | 7 + docs/plots/coulombg_c.png | Bin 0 -> 46092 bytes docs/plots/coulombg_c.py | 2 + docs/plots/ellipe.png | Bin 0 -> 24597 bytes docs/plots/ellipe.py | 7 + docs/plots/ellipf.png | Bin 0 -> 22313 bytes docs/plots/ellipf.py | 7 + docs/plots/ellipk.png | Bin 0 -> 12183 bytes docs/plots/ellipk.py | 2 + docs/plots/ellippi.png | Bin 0 -> 21917 bytes docs/plots/ellippi.py | 7 + docs/plots/gi.png | Bin 0 -> 18186 bytes docs/plots/gi.py | 2 + docs/plots/gi_c.png | Bin 0 -> 57729 bytes docs/plots/gi_c.py | 2 + docs/plots/hankel1.png | Bin 0 -> 26786 bytes docs/plots/hankel1.py | 6 + docs/plots/hankel1_c.png | Bin 0 -> 36130 bytes docs/plots/hankel1_c.py | 2 + docs/plots/hankel2.png | Bin 0 -> 27100 bytes docs/plots/hankel2.py | 6 + docs/plots/hankel2_c.png | Bin 0 -> 35840 bytes docs/plots/hankel2_c.py | 2 + docs/plots/hermite.png | Bin 0 -> 25846 bytes docs/plots/hermite.py | 7 + docs/plots/hi.png | Bin 0 -> 10748 bytes docs/plots/hi.py | 2 + docs/plots/hi_c.png | Bin 0 -> 47358 bytes docs/plots/hi_c.py | 2 + docs/plots/ker.png | Bin 0 -> 13848 bytes docs/plots/ker.py | 6 + docs/plots/kleinj.png | Bin 0 -> 77369 bytes docs/plots/kleinj.py | 2 + docs/plots/kleinj2.png | Bin 0 -> 61723 bytes docs/plots/kleinj2.py | 2 + docs/plots/laguerre.png | Bin 0 -> 19345 bytes docs/plots/laguerre.py | 7 + docs/plots/lambertw.png | Bin 0 -> 15860 bytes docs/plots/lambertw.py | 2 + docs/plots/lambertw_c.png | Bin 0 -> 28683 bytes docs/plots/lambertw_c.py | 2 + docs/plots/legendre.png | Bin 0 -> 28034 bytes docs/plots/legendre.py | 7 + docs/plots/lommels1.png | Bin 0 -> 26654 bytes docs/plots/lommels1.py | 6 + docs/plots/lommels2.png | Bin 0 -> 17657 bytes docs/plots/lommels2.py | 6 + docs/plots/pcfd.png | Bin 0 -> 28296 bytes docs/plots/pcfd.py | 7 + docs/plots/spherharm40.png | Bin 0 -> 23638 bytes docs/plots/spherharm40.py | 15 + docs/plots/spherharm41.png | Bin 0 -> 37882 bytes docs/plots/spherharm41.py | 11 + docs/plots/spherharm42.png | Bin 0 -> 51314 bytes docs/plots/spherharm42.py | 11 + docs/plots/spherharm43.png | Bin 0 -> 48141 bytes docs/plots/spherharm43.py | 11 + docs/plots/spherharm44.png | Bin 0 -> 41468 bytes docs/plots/spherharm44.py | 14 + docs/plotting.rst | 40 + docs/references.rst | 213 + docs/setup.rst | 160 + docs/technical.rst | 158 + mpmath/__init__.py | 469 ++ mpmath/__main__.py | 186 + mpmath/_interactive.py | 90 + mpmath/calculus/__init__.py | 6 + mpmath/calculus/approximation.py | 252 + mpmath/calculus/calculus.py | 6 + mpmath/calculus/differentiation.py | 656 ++ mpmath/calculus/extrapolation.py | 2107 +++++ mpmath/calculus/inverselaplace.py | 947 +++ mpmath/calculus/odes.py | 285 + mpmath/calculus/optimization.py | 1311 +++ mpmath/calculus/polynomials.py | 223 + mpmath/calculus/quadrature.py | 1110 +++ mpmath/ctx_base.py | 517 ++ mpmath/ctx_fp.py | 283 + mpmath/ctx_iv.py | 545 ++ mpmath/ctx_mp.py | 1338 ++++ mpmath/ctx_mp_python.py | 1306 +++ mpmath/function_docs.py | 10636 +++++++++++++++++++++++++ mpmath/functions/__init__.py | 14 + mpmath/functions/bessel.py | 1281 +++ mpmath/functions/elliptic.py | 1977 +++++ mpmath/functions/expintegrals.py | 437 + mpmath/functions/factorials.py | 186 + mpmath/functions/functions.py | 691 ++ mpmath/functions/hypergeometric.py | 1495 ++++ mpmath/functions/orthogonal.py | 508 ++ mpmath/functions/qfunctions.py | 285 + mpmath/functions/rszeta.py | 1403 ++++ mpmath/functions/signals.py | 32 + mpmath/functions/theta.py | 552 ++ mpmath/functions/zeta.py | 1204 +++ mpmath/functions/zetazeros.py | 1018 +++ mpmath/identification.py | 844 ++ mpmath/libfp.py | 523 ++ mpmath/libmp/__init__.py | 18 + mpmath/libmp/backend.py | 46 + mpmath/libmp/gammazeta.py | 2153 +++++ mpmath/libmp/libelefun.py | 1437 ++++ mpmath/libmp/libhyper.py | 1118 +++ mpmath/libmp/libintmath.py | 523 ++ mpmath/libmp/libmpc.py | 852 ++ mpmath/libmp/libmpf.py | 1805 +++++ mpmath/libmp/libmpi.py | 924 +++ mpmath/matrices/__init__.py | 2 + mpmath/matrices/calculus.py | 537 ++ mpmath/matrices/eigen.py | 876 ++ mpmath/matrices/eigen_symmetric.py | 1802 +++++ mpmath/matrices/linalg.py | 964 +++ mpmath/matrices/matrices.py | 1064 +++ mpmath/tests/test_basic_ops.py | 785 ++ mpmath/tests/test_bitwise.py | 183 + mpmath/tests/test_calculus.py | 278 + mpmath/tests/test_cli.py | 138 + mpmath/tests/test_compatibility.py | 77 + mpmath/tests/test_convert.py | 291 + mpmath/tests/test_demos.py | 140 + mpmath/tests/test_diff.py | 59 + mpmath/tests/test_division.py | 139 + mpmath/tests/test_eigen.py | 185 + mpmath/tests/test_eigen_symmetric.py | 352 + mpmath/tests/test_elliptic.py | 1160 +++ mpmath/tests/test_extra_gamma.py | 163 + mpmath/tests/test_extra_zeta.py | 28 + mpmath/tests/test_format.py | 914 +++ mpmath/tests/test_fp.py | 1813 +++++ mpmath/tests/test_functions.py | 1106 +++ mpmath/tests/test_functions2.py | 2549 ++++++ mpmath/tests/test_gammazeta.py | 707 ++ mpmath/tests/test_hp.py | 288 + mpmath/tests/test_identify.py | 22 + mpmath/tests/test_interval.py | 453 ++ mpmath/tests/test_levin.py | 149 + mpmath/tests/test_linalg.py | 380 + mpmath/tests/test_matrices.py | 306 + mpmath/tests/test_module.py | 18 + mpmath/tests/test_mpmath.py | 7 + mpmath/tests/test_ode.py | 71 + mpmath/tests/test_pickle.py | 12 + mpmath/tests/test_power.py | 155 + mpmath/tests/test_quad.py | 98 + mpmath/tests/test_rootfinding.py | 176 + mpmath/tests/test_special.py | 115 + mpmath/tests/test_str.py | 52 + mpmath/tests/test_summation.py | 50 + mpmath/tests/test_torture.py | 181 + mpmath/tests/test_trig.py | 131 + mpmath/tests/test_version_frozen.sh | 51 + mpmath/tests/test_visualization.py | 70 + mpmath/usertools.py | 95 + mpmath/visualization.py | 321 + pyproject.toml | 84 + 247 files changed, 69150 insertions(+) create mode 100644 .codecov.yml create mode 100644 .gitattributes create mode 100644 .github/CONTRIBUTING.rst create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/coverage.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/linter.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 .readthedocs.yaml create mode 100644 CHANGES create mode 100644 CITATION.bib create mode 100644 LICENSE create mode 100644 README.rst create mode 100644 README.wehub.md create mode 100644 conftest.py create mode 100644 demo/mandelbrot.py create mode 100644 demo/manydigits.py create mode 100644 demo/pidigits.py create mode 100644 demo/plotting.py create mode 100644 demo/sofa.py create mode 100644 demo/taylor.py create mode 100644 docs/basics.rst create mode 100644 docs/calculus/approximation.rst create mode 100644 docs/calculus/differentiation.rst create mode 100644 docs/calculus/index.rst create mode 100644 docs/calculus/integration.rst create mode 100644 docs/calculus/inverselaplace.rst create mode 100644 docs/calculus/odes.rst create mode 100644 docs/calculus/optimization.rst create mode 100644 docs/calculus/polynomials.rst create mode 100644 docs/calculus/sums_limits.rst create mode 100644 docs/cli.rst create mode 100644 docs/conf.py create mode 100644 docs/contexts.rst create mode 100644 docs/functions/bessel.rst create mode 100644 docs/functions/constants.rst create mode 100644 docs/functions/elliptic.rst create mode 100644 docs/functions/expintegrals.rst create mode 100644 docs/functions/gamma.rst create mode 100644 docs/functions/hyperbolic.rst create mode 100644 docs/functions/hypergeometric.rst create mode 100644 docs/functions/index.rst create mode 100644 docs/functions/numtheory.rst create mode 100644 docs/functions/orthogonal.rst create mode 100644 docs/functions/powers.rst create mode 100644 docs/functions/qfunctions.rst create mode 100644 docs/functions/signals.rst create mode 100644 docs/functions/trigonometric.rst create mode 100644 docs/functions/zeta.rst create mode 100644 docs/general.rst create mode 100644 docs/genindex.rst create mode 100644 docs/identification.rst create mode 100644 docs/index.rst create mode 100644 docs/matrices.rst create mode 100644 docs/plots/ai.png create mode 100644 docs/plots/ai.py create mode 100644 docs/plots/ai_c.png create mode 100644 docs/plots/ai_c.py create mode 100644 docs/plots/ber.png create mode 100644 docs/plots/ber.py create mode 100644 docs/plots/besseli.png create mode 100644 docs/plots/besseli.py create mode 100644 docs/plots/besseli_c.png create mode 100644 docs/plots/besseli_c.py create mode 100644 docs/plots/besselj.png create mode 100644 docs/plots/besselj.py create mode 100644 docs/plots/besselj_c.png create mode 100644 docs/plots/besselj_c.py create mode 100644 docs/plots/besselk.png create mode 100644 docs/plots/besselk.py create mode 100644 docs/plots/besselk_c.png create mode 100644 docs/plots/besselk_c.py create mode 100644 docs/plots/bessely.png create mode 100644 docs/plots/bessely.py create mode 100644 docs/plots/bessely_c.png create mode 100644 docs/plots/bessely_c.py create mode 100644 docs/plots/bi.png create mode 100644 docs/plots/bi.py create mode 100644 docs/plots/bi_c.png create mode 100644 docs/plots/bi_c.py create mode 100644 docs/plots/buildplots.py create mode 100644 docs/plots/chebyt.png create mode 100644 docs/plots/chebyt.py create mode 100644 docs/plots/chebyu.png create mode 100644 docs/plots/chebyu.py create mode 100644 docs/plots/coulombf.png create mode 100644 docs/plots/coulombf.py create mode 100644 docs/plots/coulombf_c.png create mode 100644 docs/plots/coulombf_c.py create mode 100644 docs/plots/coulombg.png create mode 100644 docs/plots/coulombg.py create mode 100644 docs/plots/coulombg_c.png create mode 100644 docs/plots/coulombg_c.py create mode 100644 docs/plots/ellipe.png create mode 100644 docs/plots/ellipe.py create mode 100644 docs/plots/ellipf.png create mode 100644 docs/plots/ellipf.py create mode 100644 docs/plots/ellipk.png create mode 100644 docs/plots/ellipk.py create mode 100644 docs/plots/ellippi.png create mode 100644 docs/plots/ellippi.py create mode 100644 docs/plots/gi.png create mode 100644 docs/plots/gi.py create mode 100644 docs/plots/gi_c.png create mode 100644 docs/plots/gi_c.py create mode 100644 docs/plots/hankel1.png create mode 100644 docs/plots/hankel1.py create mode 100644 docs/plots/hankel1_c.png create mode 100644 docs/plots/hankel1_c.py create mode 100644 docs/plots/hankel2.png create mode 100644 docs/plots/hankel2.py create mode 100644 docs/plots/hankel2_c.png create mode 100644 docs/plots/hankel2_c.py create mode 100644 docs/plots/hermite.png create mode 100644 docs/plots/hermite.py create mode 100644 docs/plots/hi.png create mode 100644 docs/plots/hi.py create mode 100644 docs/plots/hi_c.png create mode 100644 docs/plots/hi_c.py create mode 100644 docs/plots/ker.png create mode 100644 docs/plots/ker.py create mode 100644 docs/plots/kleinj.png create mode 100644 docs/plots/kleinj.py create mode 100644 docs/plots/kleinj2.png create mode 100644 docs/plots/kleinj2.py create mode 100644 docs/plots/laguerre.png create mode 100644 docs/plots/laguerre.py create mode 100644 docs/plots/lambertw.png create mode 100644 docs/plots/lambertw.py create mode 100644 docs/plots/lambertw_c.png create mode 100644 docs/plots/lambertw_c.py create mode 100644 docs/plots/legendre.png create mode 100644 docs/plots/legendre.py create mode 100644 docs/plots/lommels1.png create mode 100644 docs/plots/lommels1.py create mode 100644 docs/plots/lommels2.png create mode 100644 docs/plots/lommels2.py create mode 100644 docs/plots/pcfd.png create mode 100644 docs/plots/pcfd.py create mode 100644 docs/plots/spherharm40.png create mode 100644 docs/plots/spherharm40.py create mode 100644 docs/plots/spherharm41.png create mode 100644 docs/plots/spherharm41.py create mode 100644 docs/plots/spherharm42.png create mode 100644 docs/plots/spherharm42.py create mode 100644 docs/plots/spherharm43.png create mode 100644 docs/plots/spherharm43.py create mode 100644 docs/plots/spherharm44.png create mode 100644 docs/plots/spherharm44.py create mode 100644 docs/plotting.rst create mode 100644 docs/references.rst create mode 100644 docs/setup.rst create mode 100644 docs/technical.rst create mode 100644 mpmath/__init__.py create mode 100644 mpmath/__main__.py create mode 100644 mpmath/_interactive.py create mode 100644 mpmath/calculus/__init__.py create mode 100644 mpmath/calculus/approximation.py create mode 100644 mpmath/calculus/calculus.py create mode 100644 mpmath/calculus/differentiation.py create mode 100644 mpmath/calculus/extrapolation.py create mode 100644 mpmath/calculus/inverselaplace.py create mode 100644 mpmath/calculus/odes.py create mode 100644 mpmath/calculus/optimization.py create mode 100644 mpmath/calculus/polynomials.py create mode 100644 mpmath/calculus/quadrature.py create mode 100644 mpmath/ctx_base.py create mode 100644 mpmath/ctx_fp.py create mode 100644 mpmath/ctx_iv.py create mode 100644 mpmath/ctx_mp.py create mode 100644 mpmath/ctx_mp_python.py create mode 100644 mpmath/function_docs.py create mode 100644 mpmath/functions/__init__.py create mode 100644 mpmath/functions/bessel.py create mode 100644 mpmath/functions/elliptic.py create mode 100644 mpmath/functions/expintegrals.py create mode 100644 mpmath/functions/factorials.py create mode 100644 mpmath/functions/functions.py create mode 100644 mpmath/functions/hypergeometric.py create mode 100644 mpmath/functions/orthogonal.py create mode 100644 mpmath/functions/qfunctions.py create mode 100644 mpmath/functions/rszeta.py create mode 100644 mpmath/functions/signals.py create mode 100644 mpmath/functions/theta.py create mode 100644 mpmath/functions/zeta.py create mode 100644 mpmath/functions/zetazeros.py create mode 100644 mpmath/identification.py create mode 100644 mpmath/libfp.py create mode 100644 mpmath/libmp/__init__.py create mode 100644 mpmath/libmp/backend.py create mode 100644 mpmath/libmp/gammazeta.py create mode 100644 mpmath/libmp/libelefun.py create mode 100644 mpmath/libmp/libhyper.py create mode 100644 mpmath/libmp/libintmath.py create mode 100644 mpmath/libmp/libmpc.py create mode 100644 mpmath/libmp/libmpf.py create mode 100644 mpmath/libmp/libmpi.py create mode 100644 mpmath/matrices/__init__.py create mode 100644 mpmath/matrices/calculus.py create mode 100644 mpmath/matrices/eigen.py create mode 100644 mpmath/matrices/eigen_symmetric.py create mode 100644 mpmath/matrices/linalg.py create mode 100644 mpmath/matrices/matrices.py create mode 100644 mpmath/tests/test_basic_ops.py create mode 100644 mpmath/tests/test_bitwise.py create mode 100644 mpmath/tests/test_calculus.py create mode 100644 mpmath/tests/test_cli.py create mode 100644 mpmath/tests/test_compatibility.py create mode 100644 mpmath/tests/test_convert.py create mode 100644 mpmath/tests/test_demos.py create mode 100644 mpmath/tests/test_diff.py create mode 100644 mpmath/tests/test_division.py create mode 100644 mpmath/tests/test_eigen.py create mode 100644 mpmath/tests/test_eigen_symmetric.py create mode 100644 mpmath/tests/test_elliptic.py create mode 100644 mpmath/tests/test_extra_gamma.py create mode 100644 mpmath/tests/test_extra_zeta.py create mode 100644 mpmath/tests/test_format.py create mode 100644 mpmath/tests/test_fp.py create mode 100644 mpmath/tests/test_functions.py create mode 100644 mpmath/tests/test_functions2.py create mode 100644 mpmath/tests/test_gammazeta.py create mode 100644 mpmath/tests/test_hp.py create mode 100644 mpmath/tests/test_identify.py create mode 100644 mpmath/tests/test_interval.py create mode 100644 mpmath/tests/test_levin.py create mode 100644 mpmath/tests/test_linalg.py create mode 100644 mpmath/tests/test_matrices.py create mode 100644 mpmath/tests/test_module.py create mode 100644 mpmath/tests/test_mpmath.py create mode 100644 mpmath/tests/test_ode.py create mode 100644 mpmath/tests/test_pickle.py create mode 100644 mpmath/tests/test_power.py create mode 100644 mpmath/tests/test_quad.py create mode 100644 mpmath/tests/test_rootfinding.py create mode 100644 mpmath/tests/test_special.py create mode 100644 mpmath/tests/test_str.py create mode 100644 mpmath/tests/test_summation.py create mode 100644 mpmath/tests/test_torture.py create mode 100644 mpmath/tests/test_trig.py create mode 100755 mpmath/tests/test_version_frozen.sh create mode 100644 mpmath/tests/test_visualization.py create mode 100644 mpmath/usertools.py create mode 100644 mpmath/visualization.py create mode 100644 pyproject.toml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..831d5b5 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,11 @@ +coverage: + precision: 0 + round: down + status: + patch: + default: + target: 97 + project: + default: + threshold: 1% +comment: false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9e8cc9d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto +*.py diff=python text diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst new file mode 100644 index 0000000..b3edb81 --- /dev/null +++ b/.github/CONTRIBUTING.rst @@ -0,0 +1,105 @@ +Reporting Issues +================ + +When opening a new issue, please take the following steps: + +1. Please search `GitHub issues`_ to avoid duplicate reports. + +2. If possible, try updating to master and reproducing your issue. + +3. Try to include a minimal code example that demonstrates the problem. + +4. Include any relevant details of your local setup (mpmath version, Python + version, installed libraries). + +Please avoid changing your messages on the GitHub, unless you want fix a typo +and so on. Just expand your comment or add a new one. + + +Contributing Code +================= + +All work should be submitted via `Pull Requests (PR)`_. + +1. PR can be submitted as soon as there is code worth discussing. + Please make a draft PR, if one is not intended to be merged + in its present shape even if all checks pass. + +2. Please put your work on the branch of your fork, not in the + master branch. PR should generally be made against master. + +3. One logical change per commit. Make good commit messages: short + (<= 78 characters) one-line summary, then newline followed by + verbose description of your changes. Please `mention closed + issues`_ with commit message. + +4. Please conform to `PEP 8`_; run:: + + flake518 + + to check formatting. + +5. PR should include tests: + + 1. Bugfixes should include regression tests (named as ``test_issue_123``). + 2. All new functionality should be tested, every new line + should be covered by tests. Please use in tests only + public interfaces. Regression tests are not accounted in + the coverage statistics. + 3. Optionally, provide doctests to illustrate usage. But keep in + mind, doctests are not tests. Think of them as examples that + happen to be tested. + +6. It's good idea to be sure that **all** existing tests + pass and you don't break anything, so please run:: + + pytest + +7. If your change affects documentation, please build it by:: + + sphinx-build -W -b html docs build/sphinx/html + + and check that it looks as expected. + + +AI Generated Code and Communication Policy +========================================== + +The person submitting an issue or PR is responsible for its content, regardless +of whether AI tools were used in its creation. Generative AI tools can produce +output quickly, but discretion, good judgment, and critical thinking are the +foundation of all good contributions. + +You must understand and explain the code you submit as well as the existing +related code. It is not acceptable to submit a patch that you cannot +understand and explain yourself. In explaining your contribution, do not use +AI to automatically generate descriptions, as AI rarely communicates such +information correctly and concisely. + +Disclosure +---------- + +If you substantially make use of AI to assist in the development of your patch, +you must disclose how it was used and what code in the patch is AI generated. +Pull request without such disclosure may be rejected. + +Code Quality +------------ + +Code generated by AI is very often of low quality. Contributors are expected +to submit code that meets our standards (see above). We will reject pull +requests that we deem being "AI slop". Do not waste developers time by +submitting code that is fully or mostly generated by AI. + +Communication +------------- + +When interacting in communication among developers (email list, discussions, +issues, pull requests, etc) do not use AI to speak for you, other than for +translation or grammar editing. + + +.. _GitHub issues: https://github.com/mpmath/mpmath/issues +.. _Pull Requests (PR): https://github.com/mpmath/mpmath/pulls +.. _PEP 8: https://www.python.org/dev/peps/pep-0008/ +.. _mention closed issues: https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a3e4fa0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: "monthly" + rebase-strategy: "disabled" + groups: + actions-deps: + patterns: + - "*" diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..dd51d7f --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,45 @@ +name: Run coverage tests +on: [workflow_dispatch, workflow_call] +jobs: + coverage: + runs-on: ubuntu-24.04 + strategy: + fail-fast: true + env: + PYTEST_ADDOPTS: --cov mpmath --cov-append -n auto + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - uses: actions/setup-python@v6 + with: + python-version: "3.x" + - name: Install dependencies + run: | + pip install --upgrade setuptools pip + pip install --upgrade .[develop,gmpy2,gmp,ci] + - name: Run coverage tests + run: | + pytest + pip uninstall -y ipython + pytest mpmath/tests/test_cli.py + pip uninstall -y gmpy2 + pytest mpmath/tests/test_basic_ops.py mpmath/tests/test_convert.py \ + mpmath/tests/test_functions.py mpmath/tests/test_gammazeta.py \ + mpmath/tests/test_bitwise.py + pip uninstall -y python-gmp + pytest mpmath/tests/test_basic_ops.py mpmath/tests/test_convert.py \ + mpmath/tests/test_functions.py mpmath/tests/test_gammazeta.py \ + mpmath/tests/test_bitwise.py + - name: Generate coverage reports + run: | + coverage xml + coverage html + diff-cover coverage.xml --fail-under=100 \ + --compare-branch=origin/master + - uses: actions/upload-artifact@v7 + with: + name: coverage + path: | + coverage.xml + build/coverage/html/ diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..1bb36ee --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,34 @@ +name: Build & test docs +on: [workflow_dispatch, workflow_call] +jobs: + docs: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - uses: actions/setup-python@v6 + with: + python-version: "3.x" + - name: Install libs + run: | + sudo apt update + sudo apt install latexmk texlive-xetex + - name: Install dependencies + run: | + pip install --upgrade setuptools pip + pip install --upgrade .[docs] + - name: Building docs + run: | + sphinx-build --color -W --keep-going -b html docs build/sphinx/html + sphinx-build --color -W --keep-going -b latex docs build/sphinx/latex + make -C build/sphinx/latex all-pdf + env: + NO_COLOR: 1 # workaround for sphinx-contrib/autoprogram#76 + COLUMNS: 80 # also enable line wrapping for argparse + - uses: actions/upload-artifact@v7 + with: + name: docs + path: | + build/sphinx/html/ + build/sphinx/latex/mpmath.pdf diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 0000000..e4bf930 --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,15 @@ +name: Linting with flake8, etc +on: [workflow_dispatch, workflow_call] +jobs: + linter: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - uses: actions/setup-python@v6 + with: + python-version: "3.x" + - run: pip install --upgrade .[develop] + - run: python -We:invalid -m compileall -f mpmath -q + - run: flake518 mpmath diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..e3b51bd --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,35 @@ +name: Publish on PyPI +on: [push, workflow_dispatch, workflow_call] +jobs: + build: + name: Build distributions + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - uses: actions/setup-python@v6 + with: + python-version: "3.x" + - run: pip install build + - run: python -m build + - uses: actions/upload-artifact@v7 + with: + name: build + path: dist/ + publish-to-pypi: + name: Publish distributions to PyPI + if: startsWith(github.ref, 'refs/tags/') + needs: + - build + runs-on: ubuntu-24.04 + steps: + - uses: actions/download-artifact@v8 + with: + pattern: build + path: dist/ + merge-multiple: true + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..e95139a --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,64 @@ +name: test +on: + push: + pull_request: + workflow_dispatch: + schedule: + - cron: '0 0 * * 2' +jobs: + linter: + uses: ./.github/workflows/linter.yml + coverage: + uses: ./.github/workflows/coverage.yml + docs: + uses: ./.github/workflows/docs.yml + frozen-version: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - uses: actions/setup-python@v6 + with: + python-version: "3.x" + - name: Run frozen version test + run: ./mpmath/tests/test_version_frozen.sh + tests: + needs: + - linter + - coverage + - frozen-version + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + python-version: ['3.10', 3.11, 3.12, 3.13, 3.14, 3.14t, 3.15, 3.15t] + nogmpy: [false] + purepy: [false] + include: + - python-version: "3.x" + nogmpy: true + - python-version: "3.x" + purepy: true + - python-version: pypy3.11 + purepy: true + env: + PYTEST_ADDOPTS: -n auto --durations=20 + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + allow-prereleases: true + - name: Install dependencies + run: | + pip install --upgrade setuptools pip + pip install --upgrade .[develop,gmpy2,gmp,ci] + - run: pip uninstall -y gmpy2 + if: matrix.nogmpy + - run: pip uninstall -y gmpy2 python-gmp + if: matrix.purepy + - run: pytest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..32835e1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,74 @@ +# This file tells git what files to ignore (e.g., you won't see them as +# untracked with "git status"). Add anything to it that can be cleared +# without any worry (e.g., by "git clean -Xdf"), because it can be +# regenerated. Lines beginning with # are comments. You can also ignore +# files on a per-repository basis by modifying the core.excludesfile +# configuration option (see "git help config"). If you need to make git +# track a file that is ignored for some reason, you have to use +# "git add -f". See "git help gitignore" for more information. + + +# Regular Python bytecode file +*.pyc +__pycache__/ + +# Optimized Python bytecode file +*.pyo + +# Vim's swap files +*.sw[op] + +# Generated files from Jython +*$py.class + +# File generated by setup.py using MANIFEST.in +MANIFEST + +# Generated by ctags (used to improve autocompletion in vim) +tags + +my/ + +# Files generated by setup.py +dist/ +build/ + +# Generated by setuptools_scm +mpmath/_version.py + +# Tox files +.tox/ + +# Mac OS X Junk +.DS_Store + +# Backup files +*~ + +.cache/ +.coverage +doc/source/_build/ + +# Pytest cache +.pytest_cache/ + +# Files generated by setupegg.py +mpmath.egg-info/ +.eggs + +# Coverage-related files +.coverage +htmlcov/ + +# Visual Studio files +.vs/ + +# PyCharm files +.idea/ + +# Ignore files cached by Hypothesis (including examples directory so far) +.hypothesis/ + +# per-project directories for virtual environments +venv/ +.venv/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..36a7c82 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,20 @@ +version: 2 +formats: + - htmlzip + - pdf +build: + os: ubuntu-22.04 + tools: + python: "3" + jobs: + post_checkout: + - git fetch --unshallow +python: + install: + - method: pip + path: . + extra_requirements: + - docs +sphinx: + fail_on_warning: true + configuration: docs/conf.py diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..93457c1 --- /dev/null +++ b/CHANGES @@ -0,0 +1,1341 @@ +--1.5.0-- +Released TBD + +Features: + +* Support special numbers in mpf_frexp() like math.frexp(), + see #1081 (Sergey B Kirpichev) +* Add ModAB rootfinding algorithm, see #1093 (Ayush Baranwal) +* Add Brent root-finding algorithm, see #1103 (Ayush Baranwal) +* Correct integral path of the lerchphi() to use Laplace transform + integral, see #1109 (Sergey B Kirpichev) + +Compatibility: + +* Drop support for CPython 3.9, see #1058 (Sergey B Kirpichev) +* Remove deprecated math2 and rational modules, see #1057 (Sergey B Kirpichev) +* Remove deprecated mp.mpnumeric alias, see #1057 (Sergey B Kirpichev) +* Remove deprecated bitcount(), fp.is_special() and to/from_pickable() + functions, see #1057 (Sergey B Kirpichev) +* Drop DeprecationWarning for force_type kwarg for matrix(), + see #1057 (Sergey B Kirpichev) +* Use signed=True per default in to_man_exp(), see #1057 (Sergey B Kirpichev) +* Use asc=True per default for polynomial functions, see + #1057 (Sergey B Kirpichev) +* Restrict libmp exports to public API, see #1089 (Sergey B Kirpichev) + +Bug fixes: + +* Fix test_hexadecimal_with_libc_bulk(), see #1049 (Doug Torrance) +* Keep available deprecated aliases for mpc/mpf_log() (Sergey B Kirpichev) +* Use version_file option of setuptools-scm to keep version info, see #1048 + (Sergey B Kirpichev) +* Add workaround for test on s390x, see #1061 (Sergey B Kirpichev) +* Fix signature of root(), see #1072 (Sergey B Kirpichev) +* Speedup removal trailing zeros in _normalize/from_man_exp(), see #1074 + (Fredrik Johansson and Sergey B Kirpichev) +* Improve documentation about rounding in the mp context, + see #1079 (Sergey B Kirpichev) +* Correct to_float() conversion for double-rounding cases (e.g. subnormals), + see #1082 (Sergey B Kirpichev) +* Fix qr_solve() failure on well-conditioned matrices with zero pivot, see + #1083 (Jam Balaya) +* Clarify to_float() docstring, see #1087 (Sergey B Kirpichev) +* Add extra precision for summation in mpf_hypot(), see + #1088 (Sergey B Kirpichev) +* Fix typo and function names for sin/cospi(), see #1091 (Sergey B Kirpichev) +* Raise ValueError when same sign at interval boundaries in bisection + rootfinding algorithm, see #1092 (Ayush Baranwal) +* Correct interval update for Ridder's method, see #1096 (Sergey B Kirpichev) +* Set dynamic maxsteps value for the bisect method, see + #1096 (Sergey B Kirpichev) +* Implement direct series for lerchphi() base case with |z| < 1, + see #1100 (Sergey B Kirpichev) +* Correct exception message for THETA_Q_LIM, see #1102 + (Sergey B Kirpichev and Jam Balaya) +* Fix gegenbauer() failing to converge for odd integer n at z=0, + see #1101 (Vincent Gao) +* Reorganize fixed-precision computations for theta3 to avoid + severe cancellation, see #1107 (Sergey B Kirpichev) + +Maintenance: + +* Add bash script to test package version in a frozen application version + and a separate CI job to run it, see #1055 (flurin4) +* Revert "Add backport action", see #1063 (Sergey B Kirpichev) +* Test on CPython 3.15, see #1071, #1106 and #1110 (Sergey B Kirpichev) +* Add AI-related policy, see #1098 (Sergey B Kirpichev) +* Fix test_sn_cn_dn_identities(), see #1106 (Sergey B Kirpichev) +* Fix test_compatibility(): avoid using of private numpy + API (Sergey B Kirpichev) + + +--1.4.1-- +Released March 15, 2026 + +Bug fixes: + +* Fix test_hexadecimal_with_libc_bulk(), see #1049 (Doug Torrance) +* Keep available deprecated aliases for mpc/mpf_log() (Sergey B Kirpichev) +* Use version_file option of setuptools-scm to keep version info, see #1048 + (Sergey B Kirpichev) +* Add workaround for test on s390x, see #1061 (Sergey B Kirpichev) + + +--1.4.0-- +Released February 23, 2026 + +Features: + +* Support underscores as digit separators per PEP 515, see #661 (Sergey B + Kirpichev) +* Add rationals converter for mpf's, see #666 (Sergey B Kirpichev) +* Rewrite bernpoly/eulerpoly to avoid dependency on bernoulli(1) convention, + see #700 (Sergey B Kirpichev) +* Support base kwarg for from_str(), see #703 (Jonathan Warner, Sergey B + Kirpichev) +* Support randmatrix() for mp.iv and mp contexts, see #527 (Maximilian + Gaukler) +* Added rank() function for matrices, see #610 (Jan-Philipp Hoffmann) +* Add plus flag to select the B_1 sign convention for bernoulli/bernfrac, see + #724 (Jeremy Tan Jie Rui, Sergey B Kirpichev) +* Add mpf.as_integer_ratio() method, support construction of mpf from Decimal + objects, see #731 (Sergey B Kirpichev) +* Expose lower/upper_gamma functions, see #740 (Sergey B Kirpichev) +* Support mpc initialization from string, see #743 (Sergey B Kirpichev) +* Support asinh/acosh/atanh in the fp context, see #750 (Sergey B Kirpichev) +* Support binary/octal/hexadecimal string output, see #711 (Jonathan Warner, + Sergey B Kirpichev) +* Support pickling for matrices and mpi, see #761 (Sergey B Kirpichev) +* Support matrix.__array__() dunder method, see #767 (Sergey B Kirpichev) +* Support more number syntaxes, see #778 (Sergey B Kirpichev) +* Run mpmath as a module for interactive work, see #773, #923, #931, #936, + #939 and #954 (Sergey B Kirpichev) +* Add signed option to to_man_exp(), see #783 (Sergey B Kirpichev) +* Add fp.hypot, see #798 (Sergey B Kirpichev) +* Support inf/nan's in ctx.almosteq(), #802 (Sergey B Kirpichev) +* Implement mpf.__format__(), see #819, #831, #850, #859, #857, #862, #881, + #944 and #966 (Javier Garcia, Sergey B Kirpichev) +* Support conversion from scalar ndarray's, see #821 (Sergey B Kirpichev) +* Support rounding modes in mpf.__format__, see #823, #831, #834 + and #969 (Javier Garcia, Sergey B Kirpichev) +* Support '%' presentation type for mpf, see #847 (Sergey B Kirpichev) +* Support gmpy2-like rounding modes in to_str(), see #830 (Javier Garcia) +* Implement 'a'/'A' formating types for mpf.__format__, see #841 and #870 + (Sergey B Kirpichev) +* Add mpc.__format__(), see #855 (Sergey B Kirpichev) +* Now mpf.__round__() returns mpf, see #826 and #966 (Sergey B Kirpichev) +* Support 'b' (binary) format type for mpf/mpc, see #867 (Sergey B Kirpichev) +* Implement mpf.__floordiv__() and mpf.__divmod__(), see #873 (Sergey B + Kirpichev) +* Add parameters for MPContext constructor, see #876 and #963 (Sergey B Kirpichev) +* Add MPFR-compatible aliases for rounding modes, see #892 (Sergey B + Kirpichev) +* Support negative indexes in matrix, see #897 (Riccardo Orsi) +* Better introspection support for decorated functions, see #900 (Sergey B + Kirpichev) +* Add moving sofa demo, see #924 (Sergey B Kirpichev) +* Support spherical Bessel functions (jn/yn), #935 (Sergey B Kirpichev) +* Add pretty_dps context property to control number of printed digits, see + #933 (Sergey B Kirpichev) +* Support thousands separators for formatting of fractional part, see #925 and + #936 (Sergey B Kirpichev) +* Use PyREPL, as fallback (no IPython), see #941 (Sergey B Kirpichev) +* Add exp2() and log2(), see #948 (Sergey B Kirpichev) +* Support rounding property for the mp context, see #963 (Sergey B Kirpichev) +* Add Fox H-function with rational A/B parameters (foxh()), see #982 (Hongren Zheng) +* Provide experimental support for free-threading builds, see #993 (Sergey B Kirpichev) + +Compatibility: + +* Drop Python 2 support, see #629 (Fangchen Li) +* Drop support for Python versions < 3.9, see #675 and #911 (Sergey B Kirpichev) +* Drop private mpq class, use Rational's, provided by backend, see #691 and + #769 (Sergey B Kirpichev) +* Drop to_pickable()/from_pickable() helpers, see #667 and #769 (Sergey B + Kirpichev) +* Direct access to _mpf_ tuples now deprecated, please use from/to_man_exp() + functions and special constants (finf, fninf and fnan), see + #783 (Sergey B Kirpichev) +* Removed sage backend, see #732 (Sergey B Kirpichev) +* Drop MPMATH_STRICT environment variable, see #759 (Sergey B Kirpichev) +* Deprecate current (descending) order of coefficients in polyval(), etc, see + #779, #844 and #845 (Sergey B Kirpichev, Warren Weckesser) +* Deprecate mpmath.math2, see #769 (Sergey B Kirpichev) +* Importing from the mpmath.libmp submodules is deprecated, use instead ``from + mpmath.libmp import foo``, see + issue https://github.com/mpmath/mpmath/issues/704#issuecomment-2953536980 + for available functions (Sergey B Kirpichev) +* Deprecate bitcount function, see #721 and #955 (Sergey B Kirpichev) +* Deprecate mpf/mpc_log, see #989 (Sergey B Kirpichev) +* Deprecate fp.is_special(), see #1042 (Sergey B Kirpichev) + +Bug fixes: + +* sum_accurately(), betainc() and power() fixes, see #664 (Sergey B Kirpichev) +* Warn users about Python's true division, see #670 (Sergey B Kirpichev) +* Propagate nan's in ei_asympt(), see #672 (Sergey B Kirpichev) +* Fix matrix.__eq__, fix string parsing with underscores, see #679 (Sergey B + Kirpichev) +* Raise IndexError if matrix index out of bounds, see #689 (Sergey B + Kirpichev) +* Fix nan handling in fp.mag() and hyper(), see #688 (Sergey B Kirpichev) +* Optimize sparse matrix dot product, see #450 (Tyler Chen) +* Correct pow() for mpf's to be consistent with mpfr/float's, see #690 (Sergey + B Kirpichev) +* Improve hypsum non-convergence behaviour, see #703 (Benjamin Fischer) +* Fixed TypeError in LU_comp, see #610 (Jan-Philipp Hoffmann) +* Fix disagreement fp.mod vs mp.mod, see #710 (Sergey B Kirpichev) +* Skip eigenvectors if left=right=False for one-dimentional matrix, see #713 + (Sergey B Kirpichev) +* Fix mpc() constructor to be compatible with complex(), fix disagreement + fp.pow vs mp.pow, see #731 (Sergey B Kirpichev) +* Add derivative keyword argument for besselk(), see #735 (Sergey B Kirpichev) +* Fix quadosc(), fast case in _hyp2f1(), correct fp.gamma() and fp.isnprint(), + see #740 (Sergey B Kirpichev) +* Pevent erroneous setting of dps/prec on mpmath module, see #678 + (Colin B. Macdonald) +* Update splot() for recent matplotlib, see #747 (Sergey B Kirpichev) +* acos_asin(): don't try to normalize special numbers, fix repr(mp.eps), see + #750 (Sergey B Kirpichev) +* Fix choleksy_solve for complex matrix, see #755 (Qiming Sun) +* Fix hang in polylog_general(), fix demos, use cholesky_solve() for + overdetermined complex linear systems, fix several issues with empty + matrices, see #759 +* sqrt(z): special case for infinite z.imag, see #777 (Sergey B Kirpichev) +* Correct atan2(±inf, ±inf), see #775 (Sergey B Kirpichev) +* Handle infinite arguments in tan/tanh, see #785 (Sergey B Kirpichev) +* Drop mpf.__complex__(), workaround 1/z division for acot/asec/acsc/acoth, + see #797 (Sergey B Kirpichev) +* Handle more special cases in besselk and hyp1f1, see #792, #800 and #801 + (Sergey B Kirpichev) +* Correct asin/acos for infinite arguments, see #795 (Sergey B Kirpichev) +* Reduce memory usage for QuadratureRule cache, see #812 (Sergey B Kirpichev) +* Drop sign from nstr(mpf('inf')) output, see #828 (Sergey B Kirpichev) +* Improve accuracy of log1p(), see #803 and #854 (Tim Peters, Sergey B + Kirpichev) +* Normalize mpf in mp.npconvert(), fix special cases in bernpoly(), see #839 + (Sergey B Kirpichev) +* Fix mpf_div() for prec=0, see #849 (Sergey B Kirpichev) +* Raise ValueError for complex infininity condition in zeta(s, a), see #864 + (Sergey B Kirpichev) +* Enable trap_complex for MDNewton, see #870 (Sergey B Kirpichev) +* Use correct mixed-mode functions in fsub/fdiv, add special cases for + infinities in mpc_*div(), see #873 (Sergey B Kirpichev) +* Revert "fix ellippi to return an inf instead of raising an exception", see + #875 (Sergey B Kirpichev) +* Reject invalid strings in from_str(), see #886 (Sergey B Kirpichev) +* Use parity formula for besseli, see #889 (Sergey B Kirpichev) +* Special case in ctx.hypsum for infinite z, see #902 (Sergey B Kirpichev) +* Raise an exception if iv's comparison can't be decided, see #903 (Sergey B + Kirpichev) +* Add special case for ±inf in polylog_continuation(), see #904, #1034 + and #1037 (Sergey B Kirpichev, Colin B. Macdonald) +* Increase working precision in polylog_general() for negative s, see #898 + (Sergey B Kirpichev) +* Correct case for integer n in besselj/besseli, see #909 (Sergey B Kirpichev) +* Use sum_accurately() in hankel1/2(), see #926 (Sergey B Kirpichev) +* Ensure mpf_bernoulli() returns normalized answer, see #939 (Sergey B + Kirpichev) +* Use mpf_log1p in acos_asin() helper (implementing Hull et al algorithm), see + #948 and #1036 (Sergey B Kirpichev) +* Fix kwargs passing in the nstr() for mpc, see #964 (David Walker) +* Fix exception type for int(inf), see #966 (Sergey B Kirpichev) +* Ensure exp, sin, tan, etc have a correct __name__ attribute, see #997 + (Warren Weckesser) +* Matrix raise ValueError in case of negative dimensions, see #1004 (Ayush + Baranwal) +* Support lists in sinm() and cosm(), see #1003 (Ayush Baranwal) +* Properly handle nan's elliprj(), see #1038 (Sergey B Kirpichev) +* Raise ValueError for logm(0), see #1017 (Ayush Baranwal) +* Fix erf(z) with re(z) of large magnitude, see #1039 (Sergey B Kirpichev) +* Return nan's for polylog(s, nan) or polylog(s, nan+nanj), + see #1041 (Sergey B Kirpichev) +* Fix fp.isnormal() for subnormals, see #1042 (Sergey B Kirpichev) + +Maintenance: + +* Use codecov/coverage-action, see #674 (Sergey B Kirpichev) +* Run CI tests with pytest-xdist, see #685 (Sergey B Kirpichev) +* Add pyproject.toml, depend on flake518, see #684 (Sergey B Kirpichev) +* Port torture.py/extratest_zeta.py/extratest_gamma.py to the pytest + framework, see #687 (Sergey B Kirpichev) +* Create CITATION.bib, see #681 (Devharsh Trivedi) +* Avoid using star imports in tests and documentation, see #698 (Sergey B + Kirpichev) +* Fix some py2's remnants and remove old gmpy workarounds, see #699 (Sergey B + Kirpichev) +* Use math.isqrt in isqrt_python calculations, see #695 (Daiki Takahashi) +* Use gcd() and other bigint's functions from the backend, see #697 (Sergey B + Kirpichev) +* Drop legacy and redundant code, see #701 (Sergey B Kirpichev) +* Change FPContext to use more functions from the stdlib, see #692 (Sergey B + Kirpichev) +* Avoid dynamic method creation in _mpf, see #702 (Sergey B Kirpichev) +* Enable testing on CPython 3.12-dev, see #706 (Sergey B Kirpichev) +* Use bit_length() method instead of bitcount(), see #721 (Sergey B Kirpichev) +* Use lru_cache() in ifib() and eulernum(), isprime() alternatives from + backends, see #722 (Sergey B Kirpichev) +* Use setuptools_scm to update __version__, see #694 (Sergey B Kirpichev) +* Run tests on 3.13, see #759 (Sergey B Kirpichev) +* Do not build depend on pip and wheel, see #758 (Gonzalo Tornaría) +* Add CONTRIBUTING.rst, see #763 (Sergey B Kirpichev) +* Simplify ctx_mp_python.py, see #806 (Sergey B Kirpichev) +* Update gmpy2 deps, see #808 and #813 (Sergey B Kirpichev) +* Enable testing on 3.14, see #851 (Sergey B Kirpichev) +* Refactor Github Actions, see #905 (Sergey B Kirpichev) +* Build and publish wheel, see #913 (David Hotham) +* Use the intended setuptools_scm integration pattern, see #940 (Ronny + Pfannschmidt) +* Add backport action, see #1042 (Sergey B Kirpichev) + +See the release milestone (1.4) for a complete list of issues and pull requests +involved in this release. + + +--1.3.0-- +Released March 7, 2023 + +Security issues: + +* Fixed ReDOS vulnerability in mpmathify() (CVE-2021-29063) (Vinzent Steinberg) + +Features: + +* Added quadsubdiv() for numerical integration with adaptive path splitting + (Fredrik Johansson) +* Added the Cohen algorithm for inverse Laplace transforms + (Guillermo Navas-Palencia) +* Some speedup of matrix multiplication (Fredrik Johansson) +* Optimizations to Carlson elliptic integrals (Paul Masson) +* Added signal functions (squarew(), trianglew(), sawtoothw(), unit_triangle() + sigmoidw()) (Nike Dattani, Deyan Mihaylov, Tina Yu) + +Bug fixes: + +* Correct mpf initialization from tuple for finf and fninf (Sergey B Kirpichev) +* Support QR decomposition for matrices of width 0 and 1 (Clemens Hofreither) +* Fixed some cases where elliprj() gave inaccurate results (Fredrik Johansson) +* Fixed cases where digamma() hangs for complex input (Fredrik Johansson) +* Fixed cases of polylog() with integer-valued parameter with complex type + (Fredrik Johansson) +* Fixed fp.nsum() with Euler-Maclaurin algorithm (Fredrik Johansson) + +Maintenance: + +* Dropped support for Python 3.4 (Sergey B Kirpichev) +* Documentation cleanup (Sergey B Kirpichev) +* Removed obsolete files (Sergey B Kirpichev) +* Added options to runtests.py to skip tests and exit on failure + (Jonathan Warner) + + +--1.2.0-- +Released February 1, 2021 + +Features and optimizations: + +* Support @ operator for matrix multiplication (Max Gaukler) +* Add eta() implementing the Dedekind eta function +* Optimized the python_trailing function (adhoc-king) +* Implement unary plus for matrices (Max Gaukler) +* Improved calculation of gram_index (p15-git-acc) + +Compatibility: + +* Enable sage backend by default only if SAGE_ROOT is set (Pauli Virtanen) +* Fix syntax warnings on CPython 3.8 (Sergey B Kirpichev) +* Changed version requirements to Python 2.7 and 3.4 or later + (Sergey B Kirpichev) +* Improvements to the setup and test code (Sergey B Kirpichev) +* Fix sys.version comparisons for compatibility with Python 3.10 (Jakub Wilk) +* Fixes to Python2/3 compatibility for printing (Christian Clauss) + +Bug fixes: + +* Fix a possible division by zero in shanks() (Pascal Hebbeker) +* Fixed indexing errors in deHoog, Knight & Stokes inverse laplace + transform algorithm (Kris Kuhlman) +* Corrected branch cuts of the elliprj() function in some cases +* Fix initialization of iv.matrix from non-interval matrix (Max Gaukler) +* Preserve function signatures in PrecisionManager (Viet Tran) +* Implemented float and complex conversions for ivmpf + (Jonathan Warner) +* Fixed issue with scalar-matrix multiplication for interval matrices + (Jonathan Warner) +* Fix estimation of quadrature error with multiple subintervals (Tom Minka) +* Fixed a problem with the defun decorators (Sergey B Kirpichev) +* Fix eigenvalue sorting by absolute value (Georg Ostrovski) + +Cleanup: + +* Documentation corrections (Paul Masson, S.Y. Lee) +* Remove inaccessible logic in fsum/fdot (Sergey B Kirpichev) +* Remove broken force_type option for matrix constructor (Max Gaukler) +* Fix text of the BSD license in LICENSE (Sergey B Kirpichev) +* Minor code cleanup (Frédéric Chapoton) +* Removed old, unused code + + +--1.1.0-- +Released December 11, 2018 + +Bugs: +* Fixed severe bug in householder() for complex matrices + (Michael Kagalenko) +* Fixed frequently-reported bug where findroot() mysteriously raised + UnboundLocalError (Sergey B Kirpichev) +* Corrected rounding in binary-to-decimal conversion above 500 digits +* Fixed minor loss of accuracy affecting rf(), ff(), binomial(), beta() +* Fixed incorrect computation of the Hurwitz zeta function in some cases +* Fixed accuracy of digamma function near 0 +* Fixed RuntimeError in qfac() in Python 3.7 caused by raising + StopIteration (Zbigniew Jędrzejewski-Szmek) +* Fix to allow NumPy arrays in fdot() (Nico Schlömer) + +Features and improvements: +* Added more automatic conversions from Fraction, Decimal, NumPy types + (Jonathan Warner) +* Support creating mpf from a long literal (ylemkimon) +* Implemented log1p() +* Slight speedup of eig() +* Implement polylog() for general complex s and z by using Hurwitz zeta + algorithm as a fallback + +Library: +* Test more CPython and PyPy versions (Sergey B Kirpichev, Aaron Meurer) +* Drop support for Python 2.6 and 3.2 (Sergey B Kirpichev) +* Use py.test for test code; lots of code cleanup (Sergey B Kirpichev) +* Corrections to the documentation (Paul Masson, Connor Behan, + Warren Weckesser, Aaron Meurer) + +--1.0.0-- +Released September 27, 2017 + +* Bumped to major version number for 10 year anniversary +* Added module for inverse Laplace transforms, including the top level + function invertlaplace() as well as several different algorithms + (Talbot, Gaver-Stehfest and de Hoog) implemented in + mpmath.calculus.inverselaplace (Kris Kuhlman) +* Fixed bugs in elliprg() giving incorrect values for certain input +* Fixed wrong degree 1 nodes for Gaussian quadrature +* Made make acot(0) and acoth(0) return a finite result +* Fixed sieved zeta sum not being used in Python 3, and added cutoff + for sieved zeta sum on 32-bit systems when too much memory would be used +* Fixed zeta(0,0.5) to return correct value instead of raising + NoConvergence exception +* Added detection of exact zeros in gammainc(), in particular fixing + NoConvergence error for gammainc(3,-1+1j) +* Fixed wrong values from besseli() due to improper internal precision +* Fixed bessely(0,1j) to return complex nan instead of raising NameError + (Paul Masson) +* Changed float() and complex() applied to an mpf or mpc to use rounding + to nearest (or the context rounding mode) instead truncating +* Fix imaginary part of gammainc(n,x), n negative odd int, x < 0 +* Added alternative "phase" color scheme to cplot() +* Better error message for int(inf) or int(nan) (Aaron Meurer) +* Fixed polyroots() with error=True +* Added support to pass optional initial values to polyroots() + (Michael Kagalenko) +* Rewrote the Python major version selection to make it work if something + else has redefined xrange (Arne Brys) +* Switched documentation formula rendering to MathJax (Sergey B Kirpichev) +* Fixed documentation TeX build (Sergey B Kirpichev) +* Added PEP8 conformity testing (Sergey B Kirpichev) +* Various fixes for the test code and test infrastructure on different + platforms and Python versions (Sergey B Kirpichev) +* Fixed module paths in setup.py (Aaron Meurer) +* Documented more options for methods such as nstr() and hyper() +* Miscellaneous corrections to the documentation (various) + +--0.19-- +Released June 10, 2014 + +* Moved issue tracking to github and the main website to mpmath.org. + Several URLs and issue numbers were updated in the documentation + (Sergey B Kirpichev) +* Enabled automatic testing with Travis CI (Sergey B Kirpichev) +* Fixed many doctest issues (Sergey B Kirpichev) +* Converted line endings to LF (Ondrej Certik) +* Made polyroots() more robust (Ondrej Certik) + +--0.18-- +Released December 31, 2013 + +Linear algebra: +* added qr() for matrix QR factorization (contributed by Ken Allen) +* added functions eigsy(), eighe(), eig() to compute matrix + eigenvalues (contributed by Timo Hartmann) +* added functions svd(), svd_r(), svd_c() for singular value + decomposition of matrices (contributed by Timo Hartmann) +* added calculation of Gaussian quadrature rules for various weight + functions (contributed by Timo Hartmann) +* improved precision selection in exp_pade() (contributed by + Mario Pernici) + +Special functions: +* fixed ellippi() to return an inf instead of raising an exception +* fixed a crash in zeta() with huge arguments +* added functions for computing Stirling numbers + (stirling1(), stirling2()) +* improved the computation of zeros of zeta at high precision + (contributed by Juan Arias de Reyna) +* fixed zeta(-x) raising an exception for tiny x +* recognize when lerchphi() can call zeta() or polylog(), + handling those cases faster + +Compatibility: +* fixed gmpy2 compatibility issues (contributed by Case Van Horsen) +* better solutions for python 2/3 compatibility, + using Benjamin Peterson's six.py +* fixes to allow mpmath to run in non-sage mode when sage is available +* support abstract base classes (contributed by Stefan Krastanov) +* use new-style classes to improve pypy performance + +Other: +* added Levin, Sidi-S and Cohen/Villegas/Zagier series + transformations (contributed by Timo Hartmann) +* added isfinite() utility function +* fixed a problem with bisection root-finding +* fixed several documentation errors +* corrected number of coefficients returned by diffs() with + method='quad' +* fixed repr(constant) being slow at high precision +* made intervals hashable + +--0.17-- +Released February 1, 2011 + +Compatibility: + +* Python 3 is now supported +* Dropped Python 2.4 compatibility +* Fixed Python 2.5 compatibility in matrix slicing code +* Implemented Python 3.2-compatible hashing, making mpmath numbers + hash compatible with extremely large integers and with fractions + in Python versions >= 3.2 (contributed by Case Vanhorsen) + +Special functions: + +* Implemented the von Mangoldt function (mangoldt()) +* Implemented the "secondary zeta function" (secondzeta()) (contributed + by Juan Arias de Reyna). +* Implemented zeta zero counting (nzeros()) and the Backlund S function + (backlunds()) (contributed by Juan Arias de Reyna) +* Implemented derivatives of order 1-4 for siegelz() and siegeltheta() + (contributed by Juan Arias de Reyna) +* Improved Euler-Maclaurin summation for zeta() to give more accurate + results in the right half-plane when the reflection formula + cannot be used +* Implemented the Lerch transcendent (lerchphi()) +* Fixed polygamma function to return a complex NaN at complex + infinity or NaN, instead of raising an unrelated exception. + + +--0.16-- +Released September 24, 2010 + +Backends and distribution: + +* Added Sage hooks for Cython versions of exp, ln, cos, sin, + hypergeometric series, and some related functions +* Fixed imports for gmpy2 compatibility (contributed by Case Van Horsen) +* Removed documentation from main mpmath package to save space (a separate + tar.gz file is now provided for the documentation sources) +* Fixed matplotlib version detection +* Converted files to Unix line endings + +Special functions: + +* Started adding plots of special functions to the documentation +* Added Anger and Weber functions (angerj(), webere()) +* Added Lommel functions (lommels1(), lommels2()) +* Added interval versions of gamma(), loggamma(), rgamma() and + factorial() +* Rewritten Airy functions to improve speed and accuracy +* Support for arbitrary-order derivatives of airyai(), airybi() +* Added Airy function zeros (airyaizero(), airybizero()) +* Added Scorer functions (scorergi(), scorerhi()) +* Added computation of Bessel function zeros and Bessel function + derivative zeros (besseljzero(), besselyzero()) +* Fixed besselj(mpc(n), z) +* Rewritten lambertw() to fix various subtle bugs and robustly handle + numerical difficulties near branch cuts and branch points. +* Fixed fp.lambertw() to behave the same on branch cuts on systems with + and without signed-zero floats +* Added Carlson symmetric incomplete elliptic integrals + (elliprf(), elliprc(), elliprj(), elliprd(), elliprg()) +* Added Legendre incomplete elliptic integrals (ellipf(), ellippi(), + ellipe() with two arguments) +* Implemented Parabolic cylinder functions (pcfd(), pcfu(), pcfv(), + pcfw()) +* Implemented Euler-Maclaurin summation for hypergeometric functions + of order (p,p-1) to support evaluation with z close to 1 in remaining cases +* Fixed a bug in hypergeometric series summation, causing occasional + inaccurate results and incorrect detection of zeros +* Fixed qfrom(m=...) + +Calculus: + +* Implemented generators diffs_exp(), diffs_prod() for composing + derivatives +* Implemented Abel-Plana summation for infinite series (sumap()) + +Basic arithmetic and functions: + +* Implemented matrix slice indexing, supporting submatrix + extraction and assignment (contributed by Ioannis Tziakos) +* Added missing constant fp.glaisher +* Fixed a bug preventing internal rational numbers from being + hashable +* Fixed bug in isnpint() +* Fixed a bug in cos_sin() for pure imaginary argument +* Slightly improved performance for elementary functions of pure + real or pure imaginary mpc inputs +* Fixed plot() with real-valued mpc instances +* Fixed cplot() to work with endpoints of other type than float/int + + +--0.15-- +Released June 6, 2010 + +Basic transcendental functions: + +* Reimplemented all elementary functions except log, reducing + overhead and giving asymptotic speedups at high precision +* Reimplemented gamma() and loggamma(), improving speed and + fixing accuracy in corner cases +* Added rgamma() (reciprocal gamma function) +* Added a stress test suite for the gamma function +* Provided top-level functions cos_sin() and cospi_sinpi() for fast + simultaneous computation + +Riemann zeta function: + +* New zetazeros() implementation, supporting arbitrarily large indices + (contributed by Juan Arias de Reyna) +* Tuned algorithm selection in zeta() for complex arguments + (contributed by Juan Arias de Reyna) +* Accelerated computation of zeta function series using sieving + +Special functions: + +* Added qfrom(), qbarfrom(), mfrom(), kfrom(), taufrom() for elliptic + argument conversion +* Merged jsn(), jcn(), jdn() -> ellipfun() and generalized it to compute + all 12 Jacobi elliptic functions +* Implemented the Klein j-invariant (kleinj()) +* Implemented the q-Pochhammer symbol (qp()) +* Implemented q-factorial (qfac()) and q--gamma (qgamma()) +* Implemented q-hypergeometric series (qhyper()) +* Implemented bilateral hypergeometric series (bihyper()) +* Implemented Appell 2D hypergeometric series F2-F4 (appellf2()-appellf4()) +* Implemented generalized 2D hypergeometric series (hyper2d()) +* Fixed gammainc() for integer-valued complex argument (contributed by + Juan Arias de Reyna) +* Fixed asymptotic expansion of hyp1f1() (contributed by Juan Arias de Reyna) + +Numerical calculus: + +* Added support for multidimensional series in nsum() +* Made nprod() faster by default by extrapolating directly instead of + calling nsum() +* Changed some options for diff()/diffs() +* Made taylor() chop tiny coefficients by default +* Added support for partial derivatives in diff() + +Interval arithmetic: + +* All interval arithmetic functionality moved to a separate context + namespace (iv) +* Preliminary support for complex intervals (iv.mpc) +* Fixed interval cos/sin to support intervals overlapping zeros/extreme points +* Implemented interval atan2 +* Implemented exp/log/cos/sin for complex intervals +* Some other interface changes to interval code + +Utility functions: + +* Made chop() use relative rather than absolute tolerance for + real/imaginary parts +* Optimized floor(), ceil(), isinf(), isnan(), isint() +* Implemented nint(), frac(), isnormal() +* Fixed and documented semantics for isinf(), isin(), isnan() +* Added utility functions autoprec(), maxcalls(), memoize() + +Miscellaneous tweaks and fixes: + +* Support complex conjugation in fdot() +* Added support for Cholesky decomposition of complex matrices +* Fixed a small precision bug in linear algebra functions +* Suppress NoConvergence exception when plotting +* Removed some dirty code to improve PyPy compatibility +* Fixed plotting to work with mpmath numbers in the interval specification +* Fixed fp arithmetic on systems where math.log and math.sqrt return NaN + instead of raising an exception +* Fixed fp.conj for Python 2.4 and 2.5 +* Fixed quadrature to work with reversed infinite intervals such as [0,-inf] +* Renamed modf() -> fmod() for consistency + +--0.14-- +Released February 5, 2010 + +General changes: + +* Fully separated the code into "low-level" and "high-level", permitting the + use of alternative contexts (the mpmath.mp object provides the default + implementation) +* Implemented a context for fast double-precision arithmetic using Python + types (mpmath.fp) +* Implemented hooks for importing a faster version of mp arithmetic from Sage +* Implemented optimized fp versions of certain functions (including erf, erfc, + gamma, digamma, ei, e1) +* Renamed and reorganized various internal modules and methods (including + merging low-level modules into mpmath.libmp). This should not affect most + external code using top-level imports. + +Plotting: + +* Implemented splot() for 3D surface plots (contributed by Jorn Baayen) +* Permit calling plot functions with custom axes (contributed by Jorn Baayen) + +Matrices: + +* Fixed lu_solve for overdetermined systems (contributed by Vinzent Steinberg) +* Added conjugate matrix transpose (contributed by Vinzent Steinberg) +* Implemented matrix functions (expm, cosm, sinm, sqrtm, logm, powm) + +Miscellaneous: + +* Prettier printing of numbers with leading zeros at small precisions +* Made nstr pass on kwargs, permitting more formatting options +* Fixed wrong directed rounding of addition of numbers with large magnitude + differences +* Fixed several docstring typos (contributed by Chris Smith) +* Fixed a bug that prevented caching of quadrature nodes to work optimally. + +Special functions: + +* Implemented fast evaluation for large imaginary heights of the Riemann zeta + function, Z function and derived functions using the Riemann-Siegel + (contributed by Juan Arias de Reyna) +* Unified the zeta() and hurwitz() functions, automatically selecting a fast + algorithm +* Improved altzeta() to fall back to zeta() for large arguments +* Fixed accuracy of zeta(s) for s ~= 1 +* Implemented exact evaluation of Euler numbers (contributed by Juan Arias + de Reyna) +* Implemented numerical evaluation of Euler numbers and Euler polynomials + (eulernum(), eulerpoly()) +* Fixed bernpoly() and eulerpoly() to compute accurate values for large + parameters +* Fixed accuracy problems for hypergeometric functions with large parameters +* Faster evaluation of hypergeometric series using on-the-fly code generation +* Optimized hypercomb to detect certain zero terms symbolically +* Removed the djtheta function (jtheta() accepts a derivative parameter) +* Implemented li(x, offset=True) to compute the offset logarithmic integral +* Fixed wrong branch in Lambert W function for certain complex inputs +* Implemented the reflection formula for the Barnes G-function, + superfactorials, hyperfactorials, permitting large arguments in the left + half-plane +* Implemented analytic continuation to |z| >= 1 for hypergeometric functions + pFq with p=q+1; added hyp3f2() +* Implemented Borel summation of divergent pFq functions with p > q+1 +* Implemented automatic degree reduction of hypergeometric functions with + repeated parameters +* Added convenience functions expj(), expjpi() +* Use Mathematica's convention for the continuation of the Meijer G-function +* Added phase(), polar(), rect() functions for compatibility with the + Python 2.6 cmath module +* Implemented spherical harmonics (spherharm()) +* Optimized ci(), si(), chi(), shi() for complex arguments by evaluating + them in terms of ei() +* Optimized hyp2f1 for z ~= -1 + +--0.13-- +Released August 13, 2009 + +New special functions: + +* The generalized exponential integral E_n (expint(), e1() for E_1) +* The generalized incomplete beta function (betainc()) +* Whittaker functions (whitm(), whitw()) +* Struve functions (struveh(), struvel()) +* Kelvin functions (ber(), bei(), ker(), kei()) +* Cyclotomic polynomials (cyclotomic()) +* The Meijer G-function (meijerg()) +* Clausen functions (clsin(), clcos()) +* The Appell F1 hypergeometric function of two variables (appellf1()) +* The Hurwitz zeta function, with nth order derivatives (hurwitz()) +* Dirichlet L-series (dirichlet()) +* Coulomb wave functions (coulombf(), coulombg(), coulombc()) +* Associated Legendre functions of 1st and 2nd kind (legenp(), legenq()) +* Hermite polynomials (hermite()) +* Gegenbauer polynomials (gegenbauer()) +* Associated Laguerre polynomials (laguerre()) +* Hypergeometric functions hyp1f2(), hyp2f2(), hyp2f3(), hyp2f0(), hyperu() + +Evaluation of hypergeometric functions: + +* Added the function hypercomb() for evaluating expressions containing + hypergeometric series, with automatic handling of limits +* The available hypergeometric series (of orders up to and including 2F3) + implement asymptotic expansions with respect to the last argument z, allowing + fast and accurate evaluation anywhere in the complex plane. A massive number + of functions, including Bessel functions, error functions, etc., have been + updated to take advantage of this to support fast and accurate evaluation + anywhere in the complex plane. +* Fixed hyp2f1 to handle z close to and on the unit circle (supporting + evaluation anywhere in the complex plane) +* hyper() handles the 0F0 and 1F0 cases exactly +* hyper() eventually raises NoConvergence instead of getting stuck in + an infinite loop if given a divergent or extremely slowly convergent series + +Other improvements and bug fixes to special functions: + +* gammainc is much faster for large arguments and avoids catastrophic + cancellation +* Implemented specialized code for ei(x), e1(x), expint(n,x) and gammainc(n,x) + for small integers n, making evaluation much faster +* Extended the domain of polylog +* Fixed accuracy for asin(x) near x = 1 +* Fast evaluation of Bernoulli polynomials for large z +* Fixed Jacobi polynomials to handle some poles +* Some Bessel functions support computing nth order derivatives +* A set of "torture tests" for special functions is available as + tests/torture.py + +Other: +* Implemented the differint() function for fractional differentiaton / iterated + integration +* Added functions fadd, fsub, fneg, fmul, fdiv for high-level arithmetic with + controllable precision and rounding +* Added the function mag() for quick order-of-magnitude estimates of numbers +* Implemented powm1() for accurate calculation of x^y-1 +* Improved speed and accuracy for raising a pure imaginary number to + an integer power +* nthroot() renamed to root(); root() optionally computes any of + the non-principal roots of a number +* Implemented unitroots() for generating all (primitive) roots of unity +* Added the mp.pretty option for nicer repr output + +--0.12-- +Released June 9, 2009 + +General +* It is now possible to create multiple context objects and use context-local + methods instead of global state/functions (e.g. mp2=mp.clone(); mp2.dps=50; + mp2.cos(3)). Not all functions have been converted to context methods, and + there are some bugs, so this feature is currently experimental. +* If mpmath is installed in Sage 4.0 or later, mpmath will now use sage.Integer + instead of Python long internally. +* Removed instances of old-style integer division from the codebase. +* runtests.py can be run with -coverage to generate coverage statistics. + +Types and basic arithmetic + +* Fixed comparison with -inf. +* Changed repr format of the mpi interval type to make eval(repr(x)) == x. +* Improved printing of intervals, with configurable output format (contributed + by Vinzent Steinberg based on code by Don Peterson). +* Intervals supported by mpmathify() and nstr() (contributed by Vinzent + Steinberg). +* mpc is now hashable. +* Added more formatting options to the internal function to_str. +* Faster pure-Python square root. +* Fix trailing whitespace giving wrong values in str->mpf conversion. + +Calculus + +* Fixed nsum() with Euler-Maclaurin summation which would previously + ignore the starting index and sum from n=1. +* Implemented Newton's method for findroot() (contributed by Vinzent + Steinberg). + +Linear algebra + +* Fixed LU_decomp() to recognize singular matrices (contributed by Jorn Baayen). +* The various norm functions were replaced by the generic vector norm + function norm(x,p) and the generic matrix norm function mnorm(x,p). + +Special functions: + +* Some internal caches were changed to always slightly overallocate + precision. This fixes worst-case behavior where previously the cached + value had to be recomputed on every function call. +* Fixed log(tiny number) returning nonsense at high precision. +* Fixed gamma() and derivative functions such as binomial() returning + wrong results at integer inputs being divisible by a large power of 2. +* Fixed asin() not to raise an exception at high precision (contributed + by Vinzent Steinberg). +* Optimized the AGM code for the natural logarithm, making the previously + used Newton method at intermediate precisions obsolete. +* The arithmetic-geometric mean function agm() is now an order of magnitude + faster at low precision. +* Faster implementations of ellipk() and ellipe(). +* Analytic continuation of ellipe() to |x| >= 1 implemented. +* Implemented the log gamma function (loggamma()) with correct branch + cuts (slow, placeholder implementation). +* Fixed branch cuts of hyperfac(). +* Implemented the Riemann-Siegel Z-function (siegelz()). +* Implemented the Riemann-Siegel theta function (siegeltheta()). +* Implemented calculation of Gram points (grampoint()). +* Implemented calculation of Riemann zeta function zeros (zetazero()). +* Implemented the prime counting function: a slow, exact version (primepi()). + and a fast approximate version (primepi2()) that gives a bounding interval. +* Implemented the Riemann R prime counting function (riemannr()). +* Implemented Bell numbers and polynomials (bell()). +* Implemented the expm1() function. +* Implemented the 'polyexponential function' (polyexp()). +* Implemented the twin prime constant (twinprime) and Mertens' constant + (mertens). +* Implemented the prime zeta function (primezeta()). + + +--0.11-- +Released January 26, 2009 + +General: + +* Most of the documentation is now generated from docstrings + using Sphinx' autodoc feature, and proper LaTeX is used + for mathematical formulas. A large amount of new documentation + has been written. +* Improved gmpy backend. Using gmpy-1.04 gives a ~30% unit tests + speedup over 1.03, with speedups in the range of 2-3x for + specific operations (contributed by Case van Horsen and + Mario Pernici). +* Mpmath imports slightly faster due to not trying to + load the 'random' library + +Numerical calculus, etc: + +* Implemented a fast high-precision ODE solver (to replace the slow + and low-accuracy RK4 algorithm) (odefun()) +* Created an intelligent function nsum() to replace sumrich/sumsh +* Implemented nprod() for computing infinite products +* Rewrote limit() to use the same adaptive extrapolation algorithm + as nsum() +* Multidimensional nonlinear solving with Newton's method + implemented in findroot() (contributed by Vinzent Steinberg) +* Simplified the implementation and interface of sumem() +* Reimplemented Shanks transformation for nsum using Wynn's epsilon + algorithm (shanks()) +* Reimplemented Richardson extrapolation slightly more simply and + efficiently (richardson()) +* Prevent shanks() from exiting prematurely by adding + random noise to zeros +* Removed the obsolete secant() function (see findroot()) +* Implemented high-order derivatives (diff(), diffs()) +* Implemented calculation of Taylor series (taylor()) +* Implemented calculation of Fourier series (fourier(), fourierval()) +* Implemented Pade approximation (pade()) (contributed by + Mario Pernici) +* Better cancel condition for findroot() (contributed by + Vinzent Steinberg) +* Some refactoring of numerical integration code +* Fix erroneous nodes for 0-th order Gauss-Legendre quadrature, + which was causing unnecessary slowness +* Quadrature nodes are cached for arbitrary intervals, giving a + 30% speedup for repeated integrations +* Unified interface and added more options for identify(), pslq(), findpoly() + +New special functions: + +* Implemented polylogarithms (polylog()) +* Implemented Bernoulli polynomials (bernpoly()) +* Implemented the Barnes G-function (barnesg()) +* Implemented double factorials (fac2()) +* Implemented superfactorials (superfac()) +* Implemented hyperfactorials (hyperfac()) +* Replaced lower_gamma and upper_gamma with a more versatile + function gammainc() for computing the generalized (and optionally + regularized) incomplete gamma function +* Implemented sinc() and sincpi() +* Implemented Fibonacci numbers (fib()) +* Implemented the Dirichlet eta function (altzeta()) +* Implemented the inverse error function (erfinv()) +* Jacobi theta functions and elliptic functions were essentially + rewritten from scratch, making them much faster and more + general. Renamed Jacobi theta functions: jacobi_theta -> jtheta, + etc. (contributed by Mario Pernici) +* Implemented derivatives of jtheta (djtheta) (contributed by + Mario Pernici) +* Implemented Bessel Y, I, K functions (bessely, besseli, + besselk; Bessel J functions were also renamed to besselj) + also renamed) +* Generalized Stieltjes constants can now be computed, + with stieltjes(n,a) +* Implemented Hankel functions (hankel1, hankel2) + +Speed improvements and bugfixes to special functions: +* Fast logarithm at very high precision using the formula by + Sasaki and Kanada (contributed by Mario Pernici) +* Slightly faster logarithm at low precision (contributed by + Mario Pernici) +* Faster exponential function at high precision, using + Newton's method (contributed by Mario Pernici) +* Faster computation of ln2 and ln10 by means of binary splitting + (contributed by Mario Pernici) +* Fixed accuracy problems in sinpi() and cospi() +* Correct evaluation of beta() at limits +* Much faster evaluation of stieltjes(n), using an improved integral + formula +* Fixed bernoulli() being inaccurate for large n and low precision, + and being needlessly slow for small n and huge precision +* Fixed accuracy of zeta(s) for large negative re(s) +* Fixed accuracy problems for asinh, atanh and tanh +* Fixed accuracy of airyai() for large x.real +* Fixed bug in nthroot() for very large arguments (contributed by + Mario Pernici) +* Fixed accuracy of log(x) for complex |x| ~= 1 +* Fixed accuracy of exp(n), n a huge integer and prec >= 600 +* Slightly faster hypergeometric functions with rational parameters + (contributed by Mario Pernici) +* Faster and more accurate calculation of ci(x), si(x) +* Faster and more accurate calculation of ei(x) for large x, + using an asymptotic expansion (contributed by Mario Pernici) +* Fixed accuracy bugs in theta functions (contributed by + Mario Pernici) +* The Lambert W function returns more appropriate values at infinities + +Arithmetic and basic interface: +* Square roots are now rounded correctly +* Made float(huge) -> inf and float(1/huge) -> 0 instead of + raising OverflowError +* Renamed convert_lossless -> mpmathify +* mpmathify() accepts strings representing fractions or complex + numbers (contributed by Vinzent Steinberg) +* Fixed a bug in interval multiplication giving wrong signs +* Added monitor() to monitor function evaluation +* Implemented a chop() utility function for deletion of numerical noise +* Added re(), im(), conj(), fabs(), mpf.conjugate() +* Fixed the != operator for intervals +* Added functions fsum, fprod, fdot for efficient computation of + sums, products and dot products of lists of mpf:s or mpc:s + +Matrices: + +* Generation of Hilbert matrices (hilbert()) (contributed by + Vinzent Steinberg) +* Added lu_solve_mat() to solve a*x=b where a and b are matrices (contributed + by Mario Pernici) +* Implemented computation of matrix exponentials (exp_pade()) (contributed + by Mario Pernici) +* Prettier repr of complex matrices (contributed by Vinzent Steinberg) +* Speedups by using fdot and fsum (contributed by Vinzent Steinberg) + +--0.10-- +Released October 15, 2008 + +Interface / general: +* Mpmath now works with Python 2.6 +* Implemented function plotting via 'plot' and 'cplot' (requires + matplotlib) +* Removed global rounding mode (always rounding to nearest by default) +* Instead added 'prec', 'dps', 'rounding' keyword arguments to + standard functions, for optional fine-grained control over precision + and rounding +* Implemented isinf, isnan, isint, utility functions +* A large number of internal functions were moved and/or renamed to + improve consistency. This particularly affects low-level mpf + functions (lib.fadd -> libmpf.mpf_add, etc). +* Syntax for some operations was changed (see details below) +* The test runner (runtests.py) was updated to support running + isolated tests and to allow a local import of mpmath +* Unit tests can now be run with import mpmath; mpmath.runtests() +* Implicit imports are no longer used internally in the main codebase. + (This will hopefully make the source easier to read, and can catch + installation problems more cleanly.) + +Added linear algebra functions (contributed by Vinzent Steinberg): +* Provided a matrix class +* Computation of powers, inverses, determinants +* Linear system solving using LU, QR and Cholesky +* Vector and matrix norms +* Calculation of condition numbers + +Improvements to interval arithmetic: +* Fixed rounding direction for negative numbers and related + spurious bugs +* Fix interval exponentiation (all cases should work now) +* Basic interval arithmetic is up to 10x faster +* sqrt, exp, log, sin, cos and a few other functions + accept interval arguments +* Intervals supported in matrices + +Changes to root-finding code: +* secant renamed to findroot +* findroot was made more general; many useful alternative root-finding + algorithms were implemented (contributed by Vinzent Steinberg) + +Improvements to special functions: +* Implemented polygamma functions +* Implemented harmonic numbers +* Implemented Stieltjes constants +* Made gamma more accurate for huge arguments and/or precision +* Made zeta more accurate in various cases +* Made zeta typically 2-5x faster +* Much more efficient computation of zeta for huge s (zeta(s) ~= 1) +* Optimized numerical calculation of Bernoulli numbers +* Fast exact calculation of huge Bernoulli numbers via zeta and the + von Staudt-Clausen theorem +* Using AGM to compute ellipk, which is much faster and works + in the entire complex plane +* Allow single-argument form agm(x) = agm(1,x) +* Faster and more accurate computation of erf +* Added fast and accurate implementation of erfc +* Normal probability functions npdf, ncdf +* Fixed directed rounding in corner cases for various functions + +Improvements to numerical integration: +* Changed syntax for integration (quad(f, [a, b], options)) +* Implemented Gauss-Legendre quadrature +* Direct support for triple integrals (quad(f, X, Y, Z)) +* Interval can be a list of several points, to split integration + into subintervals +* Oscillatory quadrature uses Gauss-Legendre instead of tanh-sinh, + since this is typically faster +* Fixed minor rounding bug in tanh-sinh quadrature not giving + complete symmetry in the nodes +* Implemented quadrature rules in classes for improved extensibility + +Various speed improvements: +* Up to 3x faster computation of log(x) at low precision, due to using + Taylor series with argument reduction and partial caching +* About 2x faster log(x) at very high precision due to more efficient + computation of exp in the Newton iteration +* Up to 10x faster computation of atan(x) at low to medium precision, + due to using Taylor series with argument reduction and partial caching +* Faster pickling due to using hex() instead of long() +* Optimized code for Khinchin's constant (2.5x faster at 1000 digits) +* Optimized code for Glaisher's constant (1.5x faster at 1000 digits) +* Faster algorithm for Euler's constant (10x faster at 10000 digits) +* Rewrote PSLQ to use fixed-point arithmetic, giving a ~7x speedup + in pslq, findpoly and identify + +Miscellaneous bugfixes: +* Fixed nthroot for n = -1, 0, 1 +* Fixed inf/2**n and nan/2**n returning zero + +--0.9-- +Released August 23, 2008 + +* gmpy mpzs are used instead of python ints when available, for + huge speedups at high precision (contributed by Case Van Horsen) +* using binary splitting to compute pi and e near-optimally +* mpmath includes __version__ information +* arange behaves more like range (contributed by Vinzent Steinberg) +* asymptotically faster trigonometric functions via Brent's trick + (contributed by Mario Pernici) +* asymptotically faster inverse trigonometric functions via Newton's + method (contributed by Mario Pernici) +* added Jacobi elliptic functions 1-4, sn, cn, dn (contributed by Mike + Taschuk) +* polyval, polyroots and related functions now use the same order + for coefficients as scipy and matlab (i.e. the reverse order of what + was used previously in mpmath) +* fixed polyroots for degree-0 polynomials (contributed by Nimish Telang) +* added convenience functions (log10, degrees, radians, frexp, modf, ln, + arg, sign) +* added fast cbrt and nthroot functions for computing nth roots + (contributed by (Mario Pernici) +* added various exponential integrals (ei, li, si, ci, shi, chi, erfi) +* added airy functions (airyai, airybi) +* more __op__ and __rop__ methods return NotImplemented where + appropriate +* external classes can define a special method _mpmath_ to interact + with mpmath numbers and functions +* fixed some corner cases in atan2 +* faster rand() + +--0.8-- +Released April 20, 2008 + +New features: +* the full set of reciprocal trigonometric and hyperbolic functions + and their inverses (cotangent, secant, etc) is available +* oscillatory quadrature algorithm +* the PSLQ algorithm and constant recognition functions +* Richardson and Shanks transformations for computing limits and series +* Euler-Maclaurin summation of series +* basic ODE solvers (contributed by Ondrej Certik) +* the Lambert W function +* arithmetic-geometric mean function +* generic hypergeometric series and some special hypergeometric + functions (elliptic integrals, orthogonal polynomials) +* several more mathematical constants +* fast sequential computation of integer logarithms and Bernoulli + numbers +* Bessel function jv works for complex arguments and noninteger v +* support for trapping complex results +* using Sphinx to generate HTML documentation + +Bugfixes, speed enhancements, and other improvements: +* compatibility tests should now pass on systems where Python is + compiled to use 80-bit registers for floating-point operations +* fixed mpmath to work with some versions of Python 2.4 where a + list indexing bug is present in the Python core +* better algorithms for various complex elementary functions + (tan and tanh by Fredrik; sqrt, acos, asin, acosh and asinh + improved by Mario Pernici) +* multiplication and integer powers for complex numbers is faster + and more accurate +* miscellaneous speed improvements to complex arithmetic (contributed + by Mario Pernici) +* faster computation of cos and sin when only one of them is needed + (contributed by Mario Pernici) +* slightly faster square roots at low precision (contributed by + Mario Pernici) +* fixed computation of exp(n) for negative integers and x**y for + negative half integers y +* mpf ** complex now works +* faster generation of quadrature nodes (contributed by Mario Pernici) +* faster change of variables for quadrature +* comparisons and conversions have been optimized slightly. + comparisons with float(nan) also work as intended. +* str() is several times faster at very high precision +* implementations of most elementary functions moved to the lib + and libmpc modules for cleaner separation of functionality +* the rounding argument for lib and libmpc functions, and for + some functions also the the prec argument, are now optional, + resulting in cleaner and slightly faster lib code +* gamma and factorial are about 2x faster +* polyroots returns nicer results +* pickling now works for mpf and mpc instances + +--0.7-- +Released March 12, 2008 + +* the interface for switching precision and rounding modes has been + changed. instead of changing mpf.prec, there is a new object mp + which holds the precision as mp.prec. this change improves + flexibility in the implementation. it will unfortunately break + any existing code written for mpmath, but the broken code should + be trivial to update. +* the functions workprec, workdps, extraprec, extradps have been + introduced for switching precision in a more safe manner, + ensuring that the precision gets reset when finished. they can + be used with the 'with' statement available in python 2.5 +* improved documentation (manual.html) +* the round-half-down and round-half-up modes have been deprecated, + since they added complexity without being particularly useful. + round-half-even has been renamed to round-nearest. +* implemented the functions nstr, nprint for printing numbers with + a small or custom number of digits +* accuracy of cos and sin near roots has been fixed. computing + sin(x) or cos(x) where x is huge is also much faster +* implemented a magical constant eps that gives the "machine" + epsilon +* additional mathematical functions: implemented Catalan's constant + and Bessel functions J_n(x) for integer n and real x +* new functions diff and diffc for numerical differentiation +* implemented the ldexp function for fast multiplication by 2**n +* mpf uses rich comparison methods (contributed by Pearu Peterson) +* epydoc-friendly docstrings (contributed by Pearu Peterson) +* support creating mpf from float nan or inf +* fixed printing of complex numbers with negative imaginary part +* flattened package structure to simplify inclusion of mpmath in other + packages +* external classes can interoperate with mpf and mpc instances + by defining _mpf_ or _mpc_ properties +* renamed lib.fpow -> lib.fpowi and implemented lib.fpow for general + real powers. ** should now be slightly faster and more + robust +* the internal number representation has been changed to include + an explicit sign bit. as a result of this change and other small + tweaks, arithmetic is up to 20% faster and the total running + time for mpmath's unit tests has dropped 10%. +* miscellaneous speed improvements: + * ** is 2-3 times faster (contributed by Mario Pernici) + * * and * is roughly twice as fast + * / is roughly twice as fast (contributed by Mario + Pernici) + * exp and log are about 10% faster + * fast computation of e and exp(n) when precision is extremely high + +--0.6-- +Released January 13, 2008 + +* added the mpi type for interval arithmetic +* powers with integer exponents are computed with directed rounding + all the way through to preserve interval bounds more robustly + (however, the code is not fully tested and may have some bugs) +* string input to mpf.__new__() can now be unicode +* mpf.__eq__ now works in pypy +* infs and nans are now implemented in mpmath.lib, resulting in + considerable simplification of the mpf class implementation +* partial support for infs and nans in functions, e.g. + exp(inf) -> inf and exp(-inf) -> 0 now work. +* renamed several files. created mpmath.apps and moved tests into + the main mpmath directory +* wrote script to permit running unit tests without py.test available +* improved bit counting code in fadd, fmul and fdiv, plus other + small performance tweaks, resulting in a 20-30% speedup for + arithmetic + +--0.5-- +Released November 24, 2007 + +* added the quad module for arbitrary-precision numerical integration +* floor and ceil functions available +* implemented __mod__ and __rmod__ for the mpf class +* partial support for the special numbers +inf, -inf and nan +* faster multiplication and division (up to 40% faster with psyco) +* simplified syntax for conversion function (from_int instead of + from_int_exact, etc) +* renamed cgamma to euler +* more documentation strings + +--0.4-- +Released November 3, 2007 + +* new string conversion code (much faster; unlimited exponents) +* fixed bug in factorial (it gave the wrong value, though gamma worked) +* division now uses a rigorous algorithm for directed rounding + (mpmath previously used a heuristic that got the last bit wrong + in 1/10000 of cases) +* misc. performance improvements (arithmetic is 15% faster) +* refactored parts of the code; added many more docstrings and tests +* added a function rand() for generating full-precision random numbers +* rewrote the benchmark script to compare against Decimal and to + automatically generate timings with psyco both disabled and enabled +* rewrote unit tests to use py.test + +--0.3-- +Released October 5, 2007 + +* fixed high-precision accuracy problem in complex sqrt +* fixed high-precision accuracy problem in atan and complex log +* fixed directed rounding for sqrt (always rounded to nearest) +* implemented all hyperbolic and inverse functions (there are some + accuracy issues left to sort out) +* included gamma, factorial, erf, zeta incomplete gamma functions +* made sin and tan more accurate for complex input very close to 0 +* more docstrings +* many more tests +* including a benchmark script + +-- 0.2 -- +Released October 2, 2007 + +* 50% faster exponential function +* faster mpf <-> int ops +* fixed import error in pidigits.py; added demos to source distribution +* __rmul__ was missing on mpf +* fixed bitcount bug also for -(2**n-1) +* more docstrings +* more tests; tests included in source distribution +* approximate equality testing (.ae method) supported +* implemented atan and atan2 functions +* tan works for both mpfs and mpcs +* complex logarithms and complex powers supported +* more details in README + +-- 0.1 -- +Released September 27, 2007 + +* imported code from SymPy's numerics module +* renamed functions and restructured various parts of the code +* fixed erroneous bitcount for 2**n-1 mantissa with directed rounding +* various small speed improvements and bug fixes diff --git a/CITATION.bib b/CITATION.bib new file mode 100644 index 0000000..648d7c1 --- /dev/null +++ b/CITATION.bib @@ -0,0 +1,7 @@ +@manual{mpmath, + key = {mpmath}, + author = {The mpmath development team}, + title = {mpmath: a {P}ython library for arbitrary-precision floating-point arithmetic (version 1.4.0)}, + note = {{\tt https://mpmath.org/}}, + year = {2026}, +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..98f8d7c --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2005-2026 Fredrik Johansson and mpmath contributors + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + a. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + b. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + c. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..559d5de --- /dev/null +++ b/README.rst @@ -0,0 +1,192 @@ +mpmath +====== + +|pypi version| |Build status| |Zenodo Badge| + +.. |pypi version| image:: https://img.shields.io/pypi/v/mpmath.svg + :target: https://pypi.python.org/pypi/mpmath +.. |Build status| image:: https://github.com/mpmath/mpmath/workflows/test/badge.svg + :target: https://github.com/mpmath/mpmath/actions?workflow=test +.. |Zenodo Badge| image:: https://zenodo.org/badge/2934512.svg + :target: https://zenodo.org/badge/latestdoi/2934512 + +A Python library for arbitrary-precision floating-point arithmetic. + +Website: https://mpmath.org/ +Main author: Fredrik Johansson + +Mpmath is free software released under the New BSD License (see the +LICENSE file for details). + +0. History and credits +---------------------- + +The following people (among others) have contributed major patches +or new features to mpmath: + +* Pearu Peterson +* Mario Pernici +* Ondrej Certik +* Vinzent Steinberg +* Nimish Telang +* Mike Taschuk +* Case Van Horsen +* Jorn Baayen +* Chris Smith +* Juan Arias de Reyna +* Ioannis Tziakos +* Aaron Meurer +* Stefan Krastanov +* Ken Allen +* Timo Hartmann +* Sergey B Kirpichev +* Kris Kuhlman +* Paul Masson +* Michael Kagalenko +* Jonathan Warner +* Max Gaukler +* Guillermo Navas-Palencia +* Nike Dattani +* Tim Peters +* Javier Garcia + +Numerous other people have contributed by reporting bugs, +requesting new features, or suggesting improvements to the +documentation. + +For a detailed changelog, including individual contributions, +see the CHANGES file. + +Fredrik's work on mpmath during summer 2008 was sponsored by Google +as part of the Google Summer of Code program. + +Fredrik's work on mpmath during summer 2009 was sponsored by the +American Institute of Mathematics under the support of the National Science +Foundation Grant No. 0757627 (FRG: L-functions and Modular Forms). + +Any opinions, findings, and conclusions or recommendations expressed in this +material are those of the author(s) and do not necessarily reflect the +views of the sponsors. + +Credit also goes to: + +* The authors of the GMP library and the Python wrapper + gmpy, enabling mpmath to become much faster at + high precision +* The authors of MPFR, pari/gp, MPFUN, and other arbitrary- + precision libraries, whose documentation has been helpful + for implementing many of the algorithms in mpmath +* Wikipedia contributors; Abramowitz & Stegun; Gradshteyn & Ryzhik; + Wolfram Research for MathWorld and the Wolfram Functions site. + These are the main references used for special functions + implementations. +* George Brandl for developing the Sphinx documentation tool + used to build mpmath's documentation + +Release history: + +* Version 1.4.1 released on March 15, 2026 +* Version 1.4.0 released on February 23, 2026 +* Version 1.3.0 released on March 7, 2023 +* Version 1.2.1 released on February 9, 2021 +* Version 1.2.0 released on February 1, 2021 +* Version 1.1.0 released on December 11, 2018 +* Version 1.0.0 released on September 27, 2017 +* Version 0.19 released on June 10, 2014 +* Version 0.18 released on December 31, 2013 +* Version 0.17 released on February 1, 2011 +* Version 0.16 released on September 24, 2010 +* Version 0.15 released on June 6, 2010 +* Version 0.14 released on February 5, 2010 +* Version 0.13 released on August 13, 2009 +* Version 0.12 released on June 9, 2009 +* Version 0.11 released on January 26, 2009 +* Version 0.10 released on October 15, 2008 +* Version 0.9 released on August 23, 2008 +* Version 0.8 released on April 20, 2008 +* Version 0.7 released on March 12, 2008 +* Version 0.6 released on January 13, 2008 +* Version 0.5 released on November 24, 2007 +* Version 0.4 released on November 3, 2007 +* Version 0.3 released on October 5, 2007 +* Version 0.2 released on October 2, 2007 +* Version 0.1 released on September 27, 2007 + +1. Download & installation +-------------------------- + +Mpmath requires Python 3.10 or later versions. It has been tested with CPython +3.10 through 3.15 and for PyPy 3.11. + +The latest release of mpmath can be downloaded from the mpmath +website and from https://github.com/mpmath/mpmath/releases + +It should also be available in the Python Package Index at +https://pypi.python.org/pypi/mpmath + +To install latest release of Mpmath with pip, simply run + +``pip install mpmath`` + +or from the source tree + +``pip install .`` + +The latest development code is available from +https://github.com/mpmath/mpmath + +See the main documentation for more detailed instructions. + +2. Documentation +---------------- + +Documentation in reStructuredText format is available in the +docs directory included with the source package. These files +are human-readable, but can be compiled to prettier HTML using +`Sphinx `_. + +The most recent documentation is also available in HTML format: + +https://mpmath.readthedocs.io/ + +3. Running tests +---------------- + +The unit tests in mpmath/tests/ can be run with `pytest +`_, see the main documentation. + +You may also want to check out the demo scripts in the demo +directory. + +The master branch is automatically tested on the Github Actions. + +4. Known problems +----------------- + +Mpmath is a work in progress. Major issues include: + +* Some functions may return incorrect values when given extremely + large arguments or arguments very close to singularities. + +* Directed rounding works for arithmetic operations. It is implemented + heuristically for other operations, and their results may be off by one + or two units in the last place (even if otherwise accurate). + +* Some IEEE 754 features are not available. Inifinities and NaN are + partially supported, there is no signed zero; denormal rounding is + not available at all. + +* The interface for switching precision and rounding is not finalized. + The current method is not threadsafe. + +5. Help and bug reports +----------------------- + +General questions and comments can be `sent `_ +to the `mpmath mailinglist `_. + +You can also report bugs and send patches to the mpmath issue tracker, +https://github.com/mpmath/mpmath/issues + +See also our `contributing guidelines +`_. diff --git a/README.wehub.md b/README.wehub.md new file mode 100644 index 0000000..e0dbd9b --- /dev/null +++ b/README.wehub.md @@ -0,0 +1,7 @@ +# WeHub 来源说明 + +- 原始项目:`mpmath/mpmath` +- 原始仓库:https://github.com/mpmath/mpmath +- 导入方式:上游默认分支的最新快照 +- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准 +- 本文件仅用于记录来源,不代表 WeHub 是原项目作者 diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..8816adb --- /dev/null +++ b/conftest.py @@ -0,0 +1,35 @@ +import sys + +import pytest + +import mpmath + + +def pytest_report_header(config): + print("mpmath backend: %s" % mpmath.libmp.backend.BACKEND) + print("mpmath mp class: %s" % repr(mpmath.mp)) + print("mpmath version: %s" % mpmath.__version__) + print("Python version: %s" % sys.version) + + +def pytest_configure(config): + config.addinivalue_line('markers', 'slow: marks tests as slow') + + if "no:hypothesispytest" not in config.getoption("-p"): + from hypothesis import settings + + default = settings.get_profile("default") + settings.register_profile("default", + settings(default, max_examples=1000)) + ci = settings.get_profile("ci") + settings.register_profile("ci", settings(ci, max_examples=10000)) + + +@pytest.fixture(autouse=True) +def reset_mp_globals(): + mpmath.mp.prec = sys.float_info.mant_dig + mpmath.mp.pretty = False + mpmath.mp.rounding = 'n' + mpmath.mp.pretty_dps = "str" + mpmath.iv.prec = mpmath.mp.prec + mpmath.iv.pretty = False diff --git a/demo/mandelbrot.py b/demo/mandelbrot.py new file mode 100644 index 0000000..0f50619 --- /dev/null +++ b/demo/mandelbrot.py @@ -0,0 +1,33 @@ +""" +This script uses the cplot function in mpmath to plot the Mandelbrot set. +By default, the fp context is used for speed. The mp context could be used +to improve accuracy at extremely high zoom levels. +""" + +import mpmath + +ctx = mpmath.fp +# ctx = mpmath.mp + +ITERATIONS = 50 +POINTS = 100000 +ESCAPE_RADIUS = 8 + +# Full plot +RE = [-2.5, 1.5] +IM = [-1.5, 1.5] + +# A pretty subplot +#RE = [-0.96, -0.80] +#IM = [-0.35, -0.2] + +def mandelbrot(z): + c = z + for i in range(ITERATIONS): + zprev = z + z = z*z + c + if abs(z) > ESCAPE_RADIUS: + return ctx.exp(1j*(i + 1 - ctx.log(ctx.log(abs(z)))/ctx.log(2))) + return 0 + +ctx.cplot(mandelbrot, RE, IM, points=POINTS, verbose=1) diff --git a/demo/manydigits.py b/demo/manydigits.py new file mode 100644 index 0000000..215c9b6 --- /dev/null +++ b/demo/manydigits.py @@ -0,0 +1,106 @@ +""" +This script calculates solutions to some of the problems from the +"Many Digits" competition: +http://www.cs.ru.nl/~milad/manydigits/problems.php + +Run with: + + python manydigits.py + +""" +from mpmath import (asin, asinh, atan, atanh, catalan, cos, e, exp, findroot, + mp, mpf, pi, quadts, sin, sqrt, tan, tanh, zeta) +from mpmath.libmp.libintmath import bin_to_radix +from mpmath.libmp.libmpf import to_fixed + + +dps = 100 +mp.dps = dps + 10 + +def pr(x): + """Return the first dps digits after the decimal point""" + x = x._mpf_ + p = int(dps*3.33 + 10) + t = to_fixed(x, p) + d = bin_to_radix(t, p, 10, dps) + s = str(d).zfill(dps)[-dps:] + return s[:dps//2] + "\n" + s[dps//2:] + +print(""" +This script prints answers to a selection of the "Many Digits" +competition problems: http://www.cs.ru.nl/~milad/manydigits/problems.php + +The output for each problem is the first 100 digits after the +decimal point in the result. +""") + +print("C01: sin(tan(cos(1)))") +print(pr(sin(tan(cos(1))))) +print() + +print("C02: sqrt(e/pi)") +print(pr(sqrt(e/pi))) +print() + +print("C03: sin((e+1)^3)") +print(pr(sin((e+1)**3))) +print() + +print("C04: exp(pi*sqrt(2011))") +mp.dps += 65 +print(pr(exp(pi*sqrt(2011)))) +mp.dps -= 65 +print() + +print("C05: exp(exp(exp(1/2)))") +print(pr(exp(exp(exp(0.5))))) +print() + +print("C06: arctanh(1-arctanh(1-arctanh(1-arctanh(1/pi))))") +print(pr(atanh(1-atanh(1-atanh(1-atanh(1/pi)))))) +print() + +print("C07: pi^1000") +mp.dps += 505 +print(pr(pi**1000)) +mp.dps -= 505 +print() + +print("C08: sin(6^(6^6))") +print(pr(sin(6**(6**6)))) +print() + +print("C09: sin(10*arctan(tanh(pi*(2011^(1/2))/3)))") +mp.dps += 150 +print(pr(sin(10*atan(tanh(pi*sqrt(2011)/3))))) +mp.dps -= 150 +print() + +print("C10: (7+2^(1/5)-5*(8^(1/5)))^(1/3) + 4^(1/5)-2^(1/5)") +a = mpf(1)/5 +print(pr(((7 + 2**a - 5*(8**a))**(mpf(1)/3) + 4**a - 2**a))) +print() + +print("C11: tan(2^(1/2))+arctanh(sin(1))") +print(pr((tan(sqrt(2)) + atanh(sin(1))))) +print() + +print("C12: arcsin(1/e^2) + arcsinh(e^2)") +print(pr(asin(1/exp(2)) + asinh(exp(2)))) +print() + +print("C17: S= -4*Zeta(2) - 2*Zeta(3) + 4*Zeta(2)*Zeta(3) + 2*Zeta(5)") +print(pr(-4*zeta(2) - 2*zeta(3) + 4*zeta(2)*zeta(3) + 2*zeta(5))) +print() + +print(r"C18: Catalan G = Sum{i=0}{\infty}(-1)^i/(2i+1)^2") +print(pr(catalan)) +print() + +print("C21: Equation exp(cos(x)) = x") +print(pr(findroot(lambda x: exp(cos(x))-x, 1))) +print() + +print("C22: J = integral(sin(sin(sin(x)))), x=0..1") +print(pr(quadts(lambda x: sin(sin(sin(x))), [0, 1]))) +print() diff --git a/demo/pidigits.py b/demo/pidigits.py new file mode 100644 index 0000000..0f89785 --- /dev/null +++ b/demo/pidigits.py @@ -0,0 +1,82 @@ +""" +Calculate digits of pi. This module can be run interactively with + + python pidigits.py + +""" + +import math +import sys +from time import perf_counter + +from mpmath.libmp.libelefun import pi_fixed +from mpmath.libmp.libintmath import bin_to_radix, numeral + + +def display_fraction(digits, skip=0, colwidth=10, columns=5): + perline = colwidth * columns + printed = 0 + for linecount in range((len(digits)-skip) // (colwidth * columns)): + line = digits[skip+linecount*perline:skip+(linecount+1)*perline] + for i in range(columns): + print(line[i*colwidth : (i+1)*colwidth], end=' ') + print(":", (linecount+1)*perline) + if (linecount+1) % 10 == 0: + print() + printed += colwidth*columns + rem = (len(digits)-skip) % (colwidth * columns) + if rem: + buf = digits[-rem:] + s = "" + for i in range(columns): + s += buf[:colwidth].ljust(colwidth+1, " ") + buf = buf[colwidth:] + print(s + ":", printed + colwidth*columns) + +def calculateit(base, n, tofile): + intpart = numeral(3, base) + skip = 1 + if base <= 3: + skip = 2 + + prec = int(n*math.log(base,2))+10 + + print("Step 1 of 2: calculating binary value...") + t = perf_counter() + a = pi_fixed(prec, verbose=True, verbose_base=base) + step1_time = perf_counter() - t + + print("Step 2 of 2: converting to specified base...") + t = perf_counter() + d = bin_to_radix(a, prec, base, n) + d = numeral(d, base, n) + step2_time = perf_counter() - t + + print("\nWriting output...\n") + + if tofile: + out_ = sys.stdout + sys.stdout = tofile + print("%i base-%i digits of pi:\n" % (n, base)) + print(intpart, ".\n") + + display_fraction(d, skip, colwidth=10, columns=5) + if tofile: + sys.stdout = out_ + print("\nFinished in %f seconds (%f calc, %f convert)" % \ + ((step1_time + step2_time), step1_time, step2_time)) + +def interactive(): + print("Compute digits of pi with mpmath\n") + base = input("Which base? (2-36, 10 for decimal) \n> ") + digits = input("How many digits? (enter a big number, say, 10000)\n> ") + tofile = input("Output to file? (enter a filename, or just press " \ + "enter\nto print directly to the screen) \n> ") + if tofile: + tofile = open(tofile, "w") + + calculateit(int(base), int(digits), tofile) + input("\nPress enter to close this script.") + +if __name__ == "__main__": + interactive() diff --git a/demo/plotting.py b/demo/plotting.py new file mode 100644 index 0000000..3ec47c3 --- /dev/null +++ b/demo/plotting.py @@ -0,0 +1,35 @@ +""" +Function plotting demo. +""" +from mpmath import * + +def main(): + print(""" + Simple function plotting. You can enter one or several + formulas, in ordinary Python syntax and using the mpmath + function library. The variable is 'x'. So for example + the input "sin(x/2)" (without quotation marks) defines + a valid function. + """) + functions = [] + for i in range(10): + if i == 0: + s = input('Enter a function: ') + else: + s = input('Enter another function (optional): ') + if not s: + print() + break + f = eval("lambda x: " + s) + functions.append(f) + print("Added f(x) = " + s) + print() + xlim = input('Enter xmin, xmax (optional): ') + if xlim: + xlim = eval(xlim) + else: + xlim = [-5, 5] + print("Plotting...") + plot(functions, xlim=xlim) + +main() diff --git a/demo/sofa.py b/demo/sofa.py new file mode 100644 index 0000000..00a7c61 --- /dev/null +++ b/demo/sofa.py @@ -0,0 +1,63 @@ +''' +This script calculates the constant in Gerver's solution to the moving sofa +problem. + +See Finch, S. R. "Moving Sofa Constant." §8.12 in Mathematical Constants. +Cambridge, England: Cambridge University Press, pp. 519-523, 2003. +''' + +from mpmath import cos, sin, pi, quad, findroot, mp + +mp.prec = 113 + +eqs = [lambda A, B, φ, θ: (A*(cos(θ) - cos(φ)) - 2*B*sin(φ) + + (θ - φ - 1)*cos(θ) - sin(θ) + cos(φ) + sin(φ)), + lambda A, B, φ, θ: (A*(3*sin(θ) + sin(φ)) - 2*B*cos(φ) + + 3*(θ - φ - 1)*sin(θ) + 3*cos(θ) - sin(φ) + cos(φ)), + lambda A, B, φ, θ: A*cos(φ) - (sin(φ) + 0.5 - 0.5*cos(φ) + B*sin(φ)), + lambda A, B, φ, θ: ((A + pi/2 - φ - θ) - (B - (θ - φ)*(1 + A)/2 + - 0.25*(θ - φ)**2))] +A, B, φ, θ = findroot(eqs, (0, 0, 0, 0)) + +def r(α): + if 0 <= α < φ: + return 0.5 + if φ <= α < θ: + return (1 + A + α - φ)/2 + if θ <= α < pi/2 - θ: + return A + α - φ + return B - (pi/2 - α - φ)*(1 + A)/2 - (pi/2 - α - φ)**2/4 + +s = lambda α: 1 - r(α) + +def u(α): + if φ <= α < θ: + return B - (α - φ)*(1 + A)/2 - (α - φ)**2/4 + return A + pi/2 - φ - α + +def du(α): + if φ <= α < θ: + return -(1 + A)/2 - (α - φ)/2 + return -1 + +def y(α, f): + if α > pi/2 - θ: + i = [0, φ, θ, pi/2 - θ, α] + elif α > θ: + i = [0, φ, θ, α] + elif α > φ: + i = [0, φ, α] + else: + i = i = [0, α] + return 1 - quad(lambda x: f(x)*sin(x), i) + +y1 = lambda α: y(α, r) +y2 = lambda α: y(α, s) +y3 = lambda α: y2(α) - u(α)*sin(α) + +S1 = quad(lambda x: y1(x)*r(x)*cos(x), [0, φ, θ, pi/2 - θ, pi/2 - φ]) +S2 = quad(lambda x: y2(x)*s(x)*cos(x), [0, φ, θ]) +S3 = quad(lambda x: y3(x)*(u(x)*sin(x) - du(x)*cos(x) - s(x)*cos(x)), + [φ, θ, pi/4]) + +print(2*(S1 + S2 + S3)) diff --git a/demo/taylor.py b/demo/taylor.py new file mode 100644 index 0000000..172b44a --- /dev/null +++ b/demo/taylor.py @@ -0,0 +1,71 @@ +""" +Interval arithmetic demo: estimating error of numerical Taylor series. + +This module can be run interactively with + + python taylor.py + +""" +from mpmath import mpi, exp, factorial, mpf + +def taylor(x, n): + print("-"*75) + t = x = mpi(x) + s = 1 + print("adding 1") + print(s, "\n") + s += t + print("adding x") + print(s, "\n") + for k in range(2, n+1): + t = (t * x) / k + s += t + print("adding x^%i / %i! ~= %s" % (k, k, t.mid)) + print(s, "\n") + print("-"*75) + return s + +# Note: this should really be computed using interval arithmetic too! +def remainder(x, n): + xi = max(0, x) + r = exp(xi) / factorial(n+1) + r = r * x**(n+1) + return abs(r) + +def exponential(x, n): + """ + Compute exp(x) using n terms of the Taylor series for exp using + intervals, and print detailed error analysis. + """ + t = taylor(x, n) + r = remainder(x, n) + expx = exp(x) + print("Correct value of exp(x): ", expx) + print() + print("Computed interval: ") + print(t) + print() + print("Computed value (midpoint): ", t.mid) + print() + print("Estimated rounding error: ", t.delta) + print("Estimated truncation error: ", r) + print("Estimated total error: ", t.delta + r) + print("Actual error ", abs(expx - t.mid)) + print() + u = t + mpi(-r, r) + print("Interval with est. truncation error added:") + print(u) + print() + print("Correct value contained in computed interval:", t.a <= expx <= t.b) + print("When accounting for truncation error:", u.a <= expx <= u.b) + +if __name__ == "__main__": + print("Interval arithmetic demo") + print() + print("This script sums the Taylor series for exp(x) using interval arithmetic,") + print("and then compares the numerical errors due to rounding and truncation.") + print() + x = mpf(input("Enter the value of x (e.g. 3.5): ")) + n = int(input("Enter the number of terms n (e.g. 10): ")) + print() + exponential(x, n) diff --git a/docs/basics.rst b/docs/basics.rst new file mode 100644 index 0000000..3e635f1 --- /dev/null +++ b/docs/basics.rst @@ -0,0 +1,253 @@ +Basic usage +=========================== + +To avoid inadvertently overriding other functions or objects, explicitly import +only the needed objects, or use the ``mpmath.`` or ``mp.`` namespaces:: + + >>> from mpmath import sin + >>> sin(1) + mpf('0.8414709848078965') + + >>> import mpmath + >>> mpmath.sin(1) + mpf('0.8414709848078965') + + >>> from mpmath import mp # mp context object -- to be explained + >>> mp.sin(1) + mpf('0.8414709848078965') + +.. note:: + + Importing everything with ``from mpmath import *`` can be convenient, + especially when using mpmath interactively, but is best to avoid such + import statements in production code, as they make it unclear which + names are present in the namespace and wildcard-imported names may + conflict with other modules or variable names. + +Number types +------------ + +Mpmath provides the following numerical types: + + +------------+----------------+ + | Class | Description | + +============+================+ + | ``mpf`` | Real float | + +------------+----------------+ + | ``mpc`` | Complex float | + +------------+----------------+ + | ``matrix`` | Matrix | + +------------+----------------+ + +The following section will provide a very short introduction to the types ``mpf`` and ``mpc``. Intervals and matrices are described further in the documentation chapters on interval arithmetic and matrices / linear algebra. + +The ``mpf`` type is analogous to Python's built-in ``float``. It holds a real number or one of the special values ``inf`` (positive infinity), ``-inf`` (negative infinity) and ``nan`` (not-a-number, indicating an indeterminate result). You can create ``mpf`` instances from strings, integers, floats, and other ``mpf`` instances: + + >>> from mpmath import mpf, mpc, mp + >>> mpf(4) + mpf('4.0') + >>> mpf(2.5) + mpf('2.5') + >>> mpf("1.25e6") + mpf('1250000.0') + >>> mpf(mpf(2)) + mpf('2.0') + >>> mpf("inf") + mpf('inf') + +The ``mpc`` type represents a complex number in rectangular form as a pair of ``mpf`` instances. It can be constructed from a Python ``complex``, a real number, or a pair of real numbers: + + >>> mpc(2,3) + mpc(real='2.0', imag='3.0') + >>> mpc(complex(2,3)).imag + mpf('3.0') + +You can mix ``mpf`` and ``mpc`` instances with each other and with Python numbers: + + >>> mpf(3) + 2*mpf('2.5') + 1.0 + mpf('9.0') + >>> mp.dps = 15 # Set precision (see below) + >>> mpc(1j)**0.5 + mpc(real='0.70710678118654757', imag='0.70710678118654757') + + +Setting the precision +--------------------- + +Mpmath uses a global working precision; it does not keep track of the precision or accuracy of individual numbers. Performing an arithmetic operation or calling ``mpf()`` rounds the result to the current working precision. The working precision is controlled by a context object called ``mp``, which has the following default state: + + >>> print(mp) + Mpmath settings: + mp.prec = 53 [default: 53] + mp.dps = 15 [default: 15] + mp.rounding = 'n' [default: 'n'] + mp.trap_complex = False [default: False] + +The term **prec** denotes the binary precision (measured in bits) while **dps** (short for *decimal places*) is the decimal precision. Binary and decimal precision are related roughly according to the formula ``prec = 3.33*dps``. For example, it takes a precision of roughly 333 bits to hold an approximation of pi that is accurate to 100 decimal places (actually slightly more than 333 bits is used). + +Changing either precision property of the ``mp`` object automatically updates the other; usually you just want to change the ``dps`` value: + + >>> mp.dps = 100 + >>> mp.dps + 100 + >>> mp.prec + 336 + +When the precision has been set, all ``mpf`` operations are carried out at that precision:: + + >>> mp.dps = 50 + >>> mpf(1) / 6 + mpf('0.1666666666666666666666666666666666666666666666666666') + >>> mp.dps = 25 + >>> mpf(2) ** mpf('0.5') + mpf('1.41421356237309504880168871') + +The precision of complex arithmetic is also controlled by the ``mp`` object: + + >>> mp.dps = 10 + >>> mpc(1,2) / 3 + mpc(real='0.3333333333321', imag='0.6666666666642') + +There is no restriction on the magnitude of numbers. An ``mpf`` can for example hold an approximation of a large Mersenne prime: + + >>> mp.dps = 15 + >>> print(mpf(2)**32582657 - 1) + 1.24575026015369e+9808357 + +Or why not 1 googolplex: + + >>> print(mpf(10) ** (10**100)) + 1.0e+100000000000000000000000000000000000000000000000000... + +The (binary) exponent is stored exactly and is independent of the precision. + +The ``rounding`` property control default rounding mode for the context: + + >>> mp.rounding # round to nearest is the default + 'n' + >>> sin(1) + mpf('0.8414709848078965') + >>> mp.rounding = 'u' # round up + >>> sin(1) + mpf('0.84147098480789662') + >>> mp.rounding = 'n' + +Temporarily changing the precision +.................................. + +It is often useful to change the precision during only part of a calculation. A way to temporarily increase the precision and then restore it is as follows: + + >>> mp.prec += 2 + >>> # do_something() + >>> mp.prec -= 2 + +The ``with`` statement along with the mpmath functions ``workprec``, ``workdps``, ``extraprec`` and ``extradps`` can be used to temporarily change precision in a more safe manner: + + >>> from mpmath import extradps, workdps + >>> with workdps(20): + ... print(mpf(1)/7) + ... with extradps(10): + ... print(mpf(1)/7) + ... + 0.14285714285714285714 + 0.142857142857142857142857142857 + >>> mp.dps + 15 + +The ``with`` statement ensures that the precision gets reset when exiting the block, even in the case that an exception is raised. + +The ``workprec`` family of functions can also be used as function decorators: + + >>> @workdps(6) + ... def f(): + ... return mpf(1)/3 + ... + >>> f() + mpf('0.33333331346511841') + + +Some functions accept the ``prec`` and ``dps`` keyword arguments and this will override the global working precision. Note that this will not affect the precision at which the result is printed, so to get all digits, you must either use increase precision afterward when printing or use ``nstr``/``nprint``: + + >>> from mpmath import exp, nprint + >>> mp.dps = 15 + >>> print(exp(1)) + 2.71828182845905 + >>> print(exp(1, dps=50)) # Extra digits won't be printed + 2.71828182845905 + >>> nprint(exp(1, dps=50), 50) + 2.7182818284590452353602874713526624977572470937 + +Finally, instead of using the global context object ``mp``, you can create custom contexts and work with methods of those instances instead of global functions. The working precision will be local to each context object: + + >>> mp2 = mp.clone() + >>> mp.dps = 10 + >>> mp2.dps = 20 + >>> print(mp.mpf(1) / 3) + 0.3333333333 + >>> print(mp2.mpf(1) / 3) + 0.33333333333333333333 + +**Note**: the ability to create multiple contexts is a new feature that is only partially implemented. Not all mpmath functions are yet available as context-local methods. In the present version, you are likely to encounter bugs if you try mixing different contexts. + +Providing correct input +----------------------- + +Note that when creating a new ``mpf``, the value will at most be as accurate as the input. *Be careful when mixing mpmath numbers with Python floats*. When working at high precision, fractional ``mpf`` values should be created from strings or integers: + + >>> mp.dps = 30 + >>> mpf(10.9) # bad + mpf('10.9000000000000003552713678800501') + >>> mpf(1090/100) # bad, beware Python's true division produces floats + mpf('10.9000000000000003552713678800501') + >>> mpf('10.9') # good + mpf('10.8999999999999999999999999999997') + >>> mpf(109) / mpf(10) # also good + mpf('10.8999999999999999999999999999997') + >>> mp.dps = 15 + +(Binary fractions such as 0.5, 1.5, 0.75, 0.125, etc, are generally safe as input, however, since those can be represented exactly by Python floats.) + +Printing +-------- + +By default, the ``repr()`` of a number includes its type signature. This way ``eval`` can be used to recreate a number from its string representation: + + >>> eval(repr(mpf(2.5))) + mpf('2.5') + +Prettier output can be obtained by using ``str()`` or ``print``, which hide the ``mpf`` and ``mpc`` signatures and also suppress rounding artifacts in the last few digits: + + >>> mpf("3.14159") + mpf('3.1415899999999999') + >>> print(mpf("3.14159")) + 3.14159 + >>> print(mpc(1j)**0.5) + (0.707106781186548 + 0.707106781186548j) + +Setting the ``mp.pretty`` option will use the ``str()``-style output for ``repr()`` as well: + + >>> mp.pretty = True + >>> mpf(0.6) + 0.6 + >>> mp.pretty = False + >>> mpf(0.6) + mpf('0.59999999999999998') + +To use enough digits to be able recreate value exactly, set ``mp.pretty_dps`` +to ``"repr"`` (default value is ``"str"``). Same option is used to control +default number of digits in the new-style string formatting *without format +specifier*, i.e. ``format(exp(mpf(1)))``. + +The number of digits with which numbers are printed by default is determined by +the working precision. To specify the number of digits to show without +changing the working precision, use :func:`format syntax support +` or functions :func:`mpmath.nstr` and +:func:`mpmath.nprint`: + + >>> a = mpf(1) / 6 + >>> a + mpf('0.16666666666666666') + >>> f'{a:.8}' + '0.16666667' + >>> f'{a:.50}' + '0.16666666666666665741480812812369549646973609924316' diff --git a/docs/calculus/approximation.rst b/docs/calculus/approximation.rst new file mode 100644 index 0000000..c6b5309 --- /dev/null +++ b/docs/calculus/approximation.rst @@ -0,0 +1,23 @@ +Function approximation +---------------------- + +Taylor series (``taylor``) +.......................... + +.. autofunction:: mpmath.taylor + +Pade approximation (``pade``) +............................. + +.. autofunction:: mpmath.pade + +Chebyshev approximation (``chebyfit``) +...................................... + +.. autofunction:: mpmath.chebyfit + +Fourier series (``fourier``, ``fourierval``) +............................................ + +.. autofunction:: mpmath.fourier +.. autofunction:: mpmath.fourierval diff --git a/docs/calculus/differentiation.rst b/docs/calculus/differentiation.rst new file mode 100644 index 0000000..5660cf1 --- /dev/null +++ b/docs/calculus/differentiation.rst @@ -0,0 +1,19 @@ +Differentiation +--------------- + +Numerical derivatives (``diff``, ``diffs``) +........................................... + +.. autofunction:: mpmath.diff +.. autofunction:: mpmath.diffs + +Composition of derivatives (``diffs_prod``, ``diffs_exp``) +.......................................................... + +.. autofunction:: mpmath.diffs_prod +.. autofunction:: mpmath.diffs_exp + +Fractional derivatives / differintegration (``differint``) +............................................................ + +.. autofunction:: mpmath.differint diff --git a/docs/calculus/index.rst b/docs/calculus/index.rst new file mode 100644 index 0000000..a6d7668 --- /dev/null +++ b/docs/calculus/index.rst @@ -0,0 +1,14 @@ +Numerical calculus +================== + +.. toctree:: + :maxdepth: 2 + + polynomials + optimization + sums_limits + differentiation + integration + odes + approximation + inverselaplace diff --git a/docs/calculus/integration.rst b/docs/calculus/integration.rst new file mode 100644 index 0000000..e5c9148 --- /dev/null +++ b/docs/calculus/integration.rst @@ -0,0 +1,36 @@ +Numerical integration (quadrature) +---------------------------------- + +Standard quadrature (``quad``) +.............................. + +.. autofunction:: mpmath.quad + +Quadrature with subdivision (``quadsubdiv``) +............................................ + +.. autofunction:: mpmath.quadsubdiv + +Oscillatory quadrature (``quadosc``) +.................................... + +.. autofunction:: mpmath.quadosc + +Quadrature rules +................ + +.. autoclass:: mpmath.calculus.quadrature.QuadratureRule + :members: + +Tanh-sinh rule +~~~~~~~~~~~~~~ + +.. autoclass:: mpmath.calculus.quadrature.TanhSinh + :members: + + +Gauss-Legendre rule +~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: mpmath.calculus.quadrature.GaussLegendre + :members: diff --git a/docs/calculus/inverselaplace.rst b/docs/calculus/inverselaplace.rst new file mode 100644 index 0000000..ff033cd --- /dev/null +++ b/docs/calculus/inverselaplace.rst @@ -0,0 +1,71 @@ +Numerical inverse Laplace transform +----------------------------------- + +One-step algorithm (``invertlaplace``) +...................................... + +.. autofunction:: mpmath.invertlaplace + +Specific algorithms +................... + +Fixed Talbot algorithm +~~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: mpmath.calculus.inverselaplace.FixedTalbot + :members: + +Gaver-Stehfest algorithm +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: mpmath.calculus.inverselaplace.Stehfest + :members: + +de Hoog, Knight & Stokes algorithm +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: mpmath.calculus.inverselaplace.deHoog + :members: + +Cohen acceleration algorithm +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: mpmath.calculus.inverselaplace.Cohen + :members: + +Manual approach +............... + +It is possible and sometimes beneficial to re-create some of the +functionality in ``invertlaplace``. This could be used to compute the +Laplace-space function evaluations in a different way. For example, +the Laplace-space function evaluations could be the result of a +quadrature or sum, solution to a system of ordinary differential +equations, or possibly computed in parallel from some external library +or function call. + +A trivial example showing the process (which could be implemented +using the existing interface): + +>>> from mpmath import calculus, convert, exp, mp +>>> myTalbot = calculus.inverselaplace.FixedTalbot(mp) +>>> t = convert(0.25) +>>> myTalbot.calc_laplace_parameter(t) +>>> fp = lambda p: 1/(p + 1) - 1/(p + 1000) +>>> ft = lambda t: exp(-t) - exp(-1000*t) +>>> fpvec = [fp(p) for p in myTalbot.p] +>>> ft(t)-myTalbot.calc_time_domain_solution(fpvec,t,manual_prec=True) +mpf('1.92830017952889006175687218e-21') + +This manual approach is also useful to look at the Laplace parameter, +order, or working precision which were computed. + +>>> myTalbot.degree +34 + +Credit +...... + +The numerical inverse Laplace transform functionality was contributed +to mpmath by Kristopher L. Kuhlman in 2017. The Cohen method was contributed +to mpmath by Guillermo Navas-Palencia in 2022. diff --git a/docs/calculus/odes.rst b/docs/calculus/odes.rst new file mode 100644 index 0000000..7c2ae83 --- /dev/null +++ b/docs/calculus/odes.rst @@ -0,0 +1,7 @@ +Ordinary differential equations +------------------------------- + +Solving the ODE initial value problem (``odefun``) +.................................................. + +.. autofunction:: mpmath.odefun diff --git a/docs/calculus/optimization.rst b/docs/calculus/optimization.rst new file mode 100644 index 0000000..b683753 --- /dev/null +++ b/docs/calculus/optimization.rst @@ -0,0 +1,25 @@ +Root-finding and optimization +----------------------------- + +Root-finding (``findroot``) +........................... + +.. autofunction:: mpmath.findroot(f, x0, solver=Secant, tol=None, verbose=False, verify=True, **kwargs) + +Solvers +^^^^^^^ + +.. autoclass:: mpmath.calculus.optimization.Secant +.. autoclass:: mpmath.calculus.optimization.Newton +.. autoclass:: mpmath.calculus.optimization.MNewton +.. autoclass:: mpmath.calculus.optimization.Halley +.. autoclass:: mpmath.calculus.optimization.Muller +.. autoclass:: mpmath.calculus.optimization.Bisection +.. autoclass:: mpmath.calculus.optimization.Illinois +.. autoclass:: mpmath.calculus.optimization.Pegasus +.. autoclass:: mpmath.calculus.optimization.Anderson +.. autoclass:: mpmath.calculus.optimization.Ridder +.. autoclass:: mpmath.calculus.optimization.ANewton +.. autoclass:: mpmath.calculus.optimization.MDNewton +.. autoclass:: mpmath.calculus.optimization.ModAB +.. autoclass:: mpmath.calculus.optimization.Brent diff --git a/docs/calculus/polynomials.rst b/docs/calculus/polynomials.rst new file mode 100644 index 0000000..d11210c --- /dev/null +++ b/docs/calculus/polynomials.rst @@ -0,0 +1,15 @@ +Polynomials +----------- + +See also :func:`~mpmath.taylor` and :func:`~mpmath.chebyfit` for +approximation of functions by polynomials. + +Polynomial evaluation (``polyval``) +................................... + +.. autofunction:: mpmath.polyval + +Polynomial roots (``polyroots``) +................................ + +.. autofunction:: mpmath.polyroots diff --git a/docs/calculus/sums_limits.rst b/docs/calculus/sums_limits.rst new file mode 100644 index 0000000..6107966 --- /dev/null +++ b/docs/calculus/sums_limits.rst @@ -0,0 +1,67 @@ +Sums, products, limits and extrapolation +---------------------------------------- + +The functions listed here permit approximation of infinite +sums, products, and other sequence limits. +Use :func:`mpmath.fsum` and :func:`mpmath.fprod` +for summation and multiplication of finite sequences. + +Summation +.......................................... + +:func:`~mpmath.nsum` +^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.nsum + +:func:`~mpmath.sumem` +^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.sumem + +:func:`~mpmath.sumap` +^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.sumap + +Products +............................... + +:func:`~mpmath.nprod` +^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.nprod + +Limits (``limit``) +.................. + +:func:`~mpmath.limit` +^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.limit + +Extrapolation +.......................................... + +The following functions provide a direct interface to +extrapolation algorithms. :func:`~mpmath.nsum` and :func:`~mpmath.limit` +essentially work by calling the following functions with an increasing +number of terms until the extrapolated limit is accurate enough. + +The following functions may be useful to call directly if the +precise number of terms needed to achieve a desired accuracy is +known in advance, or if one wishes to study the convergence +properties of the algorithms. + + +:func:`~mpmath.richardson` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.richardson + +:func:`~mpmath.shanks` +^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.shanks + +:func:`~mpmath.levin` +^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.levin + +:func:`~mpmath.cohen_alt` +^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.cohen_alt + diff --git a/docs/cli.rst b/docs/cli.rst new file mode 100644 index 0000000..9c162bf --- /dev/null +++ b/docs/cli.rst @@ -0,0 +1,8 @@ +.. _cli: + +Command-Line Usage +================== + +When called as a program from the command line, the following form is used: + +.. autoprogram:: mpmath.__main__:parser diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..baf9f20 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,52 @@ +""" +Mpmath documentation build configuration file. + +This file is execfile()d with the current directory set to its +containing dir. + +The contents of this file are pickled, so don't put values in the +namespace that aren't pickleable (module imports are okay, they're +removed automatically). +""" + +import mpmath + + +# Add any Sphinx extension module names here, as strings. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.mathjax', + 'sphinx.ext.intersphinx', 'sphinxcontrib.autoprogram', + 'matplotlib.sphinxext.plot_directive'] + +# Sphinx will warn about all references where the target cannot be found. +nitpicky = True + +# Project information. +project = mpmath.__name__ +copyright = '2007-2026, Fredrik Johansson and mpmath developers' +release = version = mpmath.__version__ + +# Define how the current time is formatted using time.strftime(). +today_fmt = '%B %d, %Y' + +# The "theme" that the HTML output should use. +html_theme = 'classic' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, document class [howto/manual]). +latex_documents = [('index', 'mpmath.tex', 'mpmath documentation', + r'Fredrik Johansson \and mpmath contributors', 'manual')] + +# The name of default reST role, that is, for text marked up `like this`. +default_role = 'math' + +# Contains mapping the locations and names of other projects that +# should be linked to in this documentation. +intersphinx_mapping = { + 'python': ('https://docs.python.org/3/', None), + 'sympy': ('https://docs.sympy.org/latest/', None), +} + +plot_include_source = True +plot_formats = [('png', 96), 'pdf'] +plot_html_show_formats = False +plot_html_show_source_link = False diff --git a/docs/contexts.rst b/docs/contexts.rst new file mode 100644 index 0000000..4faaf1e --- /dev/null +++ b/docs/contexts.rst @@ -0,0 +1,370 @@ +Contexts +======== + +High-level code in mpmath is implemented as methods on a "context object". The context implements arithmetic, type conversions and other fundamental operations. The context also holds settings such as precision, and stores cache data. A few different contexts (with a mostly compatible interface) are provided so that the high-level algorithms can be used with different implementations of the underlying arithmetic, allowing different features and speed-accuracy tradeoffs. Currently, mpmath provides the following contexts: + + * Arbitrary-precision arithmetic (``mp``) + * Arbitrary-precision interval arithmetic (``iv``) + * Double-precision arithmetic using Python's builtin ``float`` and ``complex`` types (``fp``) + +.. note:: + + Using global context is not thread-safe, create instead + local contexts with e.g. :class:`~mpmath.MPContext`. + +Most global functions in the global mpmath namespace are actually methods of the ``mp`` +context. This fact is usually transparent to the user, but sometimes shows up in the +form of an initial parameter called "ctx" visible in the help for the function:: + + >>> import mpmath + >>> help(mpmath.fsum) + Help on method fsum in module mpmath.ctx_mp_python: + + fsum(terms, absolute=False, squared=False) method of mpmath.ctx_mp.MPContext instance + Calculates a sum containing a finite number of terms (for infinite + series, see :func:`~mpmath.nsum`). The terms will be converted to + ... + +The following operations are equivalent:: + + >>> mpmath.fsum([1,2,3]) + mpf('6.0') + >>> mpmath.mp.fsum([1,2,3]) + mpf('6.0') + +The corresponding operation using the ``fp`` context:: + + >>> mpmath.fp.fsum([1,2,3]) + 6.0 + +Common interface +---------------- + +``ctx.mpf`` creates a real number:: + + >>> from mpmath import mp, fp + >>> mp.mpf(3) + mpf('3.0') + >>> fp.mpf(3) + 3.0 + +``ctx.mpc`` creates a complex number:: + + >>> mp.mpc(2,3) + mpc(real='2.0', imag='3.0') + >>> fp.mpc(2,3) + (2+3j) + +``ctx.matrix`` creates a matrix:: + + >>> mp.matrix([[1,0],[0,1]]) + matrix( + [['1.0', '0.0'], + ['0.0', '1.0']]) + >>> _[0,0] + mpf('1.0') + >>> fp.matrix([[1,0],[0,1]]) + matrix( + [['1.0', '0.0'], + ['0.0', '1.0']]) + >>> _[0,0] + 1.0 + +``ctx.prec`` holds the current precision (in bits):: + + >>> mp.prec + 53 + >>> fp.prec + 53 + +``ctx.dps`` holds the current precision (in digits):: + + >>> mp.dps + 15 + >>> fp.dps + 15 + +``ctx.pretty`` controls whether objects should be pretty-printed automatically by :func:`repr`. Pretty-printing for ``mp`` numbers is disabled by default so that they can clearly be distinguished from Python numbers and so that ``eval(repr(x)) == x`` works:: + + >>> mp.mpf(3) + mpf('3.0') + >>> mpf = mp.mpf + >>> eval(repr(mp.mpf(3))) + mpf('3.0') + >>> mp.pretty = True + >>> mp.mpf(3) + 3.0 + >>> fp.matrix([[1,0],[0,1]]) + matrix( + [['1.0', '0.0'], + ['0.0', '1.0']]) + >>> fp.pretty = True + >>> fp.matrix([[1,0],[0,1]]) + [1.0 0.0] + [0.0 1.0] + >>> fp.pretty = False + + +Arbitrary-precision floating-point (``mp``) +--------------------------------------------- + +The ``mp`` context is what most users probably want to use most of the time, as it supports the most functions, is most well-tested, and is implemented with a high level of optimization. Nearly all examples in this documentation use ``mp`` functions. + +See :doc:`basics` for a description of basic usage. + +.. autoclass:: mpmath.MPContext + +Local contexts, created on demand, could be used just as the global ``mp``: + + >>> from mpmath import MPContext + >>> ctx = MPContext() + >>> ctx.sin(1) + mpf('0.8414709848078965') + >>> ctx.prec = 113 + >>> ctx.sin(1) + mpf('0.841470984807896506652502321630298954') + +Arbitrary-precision interval arithmetic (``iv``) +------------------------------------------------ + +The ``iv.mpf`` type represents a closed interval `[a,b]`; that is, the set `\{x : a \le x \le b\}`, where `a` and `b` are arbitrary-precision floating-point values, possibly `\pm \infty`. The ``iv.mpc`` type represents a rectangular complex interval `[a,b] + [c,d]i`; that is, the set `\{z = x+iy : a \le x \le b \land c \le y \le d\}`. + +Interval arithmetic provides rigorous error tracking. If `f` is a mathematical function and `\hat f` is its interval arithmetic version, then the basic guarantee of interval arithmetic is that `f(v) \subseteq \hat f(v)` for any input interval `v`. Put differently, if an interval represents the known uncertainty for a fixed number, any sequence of interval operations will produce an interval that contains what would be the result of applying the same sequence of operations to the exact number. The principal drawbacks of interval arithmetic are speed (``iv`` arithmetic is typically at least two times slower than ``mp`` arithmetic) and that it sometimes provides far too pessimistic bounds. + +.. note :: + + The support for interval arithmetic in mpmath is still experimental, and many functions + do not yet properly support intervals. Please use this feature with caution. + +Intervals can be created from single numbers (treated as zero-width intervals) or pairs of endpoint numbers. Strings are treated as exact decimal numbers. Note that a Python float like ``0.1`` generally does not represent the same number as its literal; use ``'0.1'`` instead:: + + >>> from mpmath import iv + >>> iv.mpf(3) + mpi('3.0', '3.0') + >>> print(iv.mpf(3)) + [3.0, 3.0] + >>> iv.pretty = True + >>> iv.mpf([2,3]) + [2.0, 3.0] + >>> iv.mpf(0.1) # probably not intended + [0.10000000000000000555, 0.10000000000000000555] + >>> iv.mpf('0.1') # good, gives a containing interval + [0.099999999999999991673, 0.10000000000000000555] + >>> iv.mpf(['0.1', '0.2']) + [0.099999999999999991673, 0.2000000000000000111] + +The fact that ``'0.1'`` results in an interval of nonzero width indicates that 1/10 cannot be represented using binary floating-point numbers at this precision level (in fact, it cannot be represented exactly at any precision). + +Intervals may be infinite or half-infinite:: + + >>> print(1 / iv.mpf([2, 'inf'])) + [0.0, 0.5] + +The equality testing operators ``==`` and ``!=`` check whether their operands +are identical as intervals; that is, have the same endpoints. The ordering +operators ``< <= > >=`` permit inequality testing using triple-valued logic: a +guaranteed inequality returns ``True`` or ``False`` while an indeterminate +inequality raises :exc:`ValueError`:: + + >>> iv.mpf([1,2]) == iv.mpf([1,2]) + True + >>> iv.mpf([1,2]) != iv.mpf([1,2]) + False + >>> iv.mpf([1,2]) <= 2 + True + >>> iv.mpf([1,2]) > 0 + True + >>> iv.mpf([1,2]) < 1 + False + >>> iv.mpf([1,2]) < 2 + Traceback (most recent call last): + ... + ValueError + >>> iv.mpf([2,2]) < 2 + False + >>> iv.mpf([1,2]) <= iv.mpf([2,3]) + True + >>> iv.mpf([1,2]) < iv.mpf([2,3]) + Traceback (most recent call last): + ... + ValueError + >>> iv.mpf([1,2]) < iv.mpf([-1,0]) + False + +The ``in`` operator tests whether a number or interval is contained in another interval:: + + >>> iv.mpf([0,2]) in iv.mpf([0,10]) + True + >>> 3 in iv.mpf(['-inf', 0]) + False + +Intervals have the properties ``.a``, ``.b`` (endpoints), ``.mid``, and ``.delta`` (width):: + + >>> x = iv.mpf([2, 5]) + >>> x.a + [2.0, 2.0] + >>> x.b + [5.0, 5.0] + >>> x.mid + [3.5, 3.5] + >>> x.delta + [3.0, 3.0] + +Some transcendental functions are supported:: + + >>> iv.dps = 15 + >>> mp.dps = 15 + >>> iv.mpf([0.5,1.5]) ** iv.mpf([0.5, 1.5]) + [0.35355339059327373086, 1.837117307087383633] + >>> iv.exp(0) + [1.0, 1.0] + >>> iv.exp(['-inf','inf']) + [0.0, inf] + >>> + >>> iv.exp(['-inf',0]) + [0.0, 1.0] + >>> iv.exp([0,'inf']) + [1.0, inf] + >>> iv.exp([0,1]) + [1.0, 2.7182818284590455349] + >>> + >>> iv.log(1) + [0.0, 0.0] + >>> iv.log([0,1]) + [-inf, 0.0] + >>> iv.log([0,'inf']) + [-inf, inf] + >>> iv.log(2) + [0.69314718055994528623, 0.69314718055994539725] + >>> + >>> iv.sin([100,'inf']) + [-1.0, 1.0] + >>> iv.cos(['-0.1','0.1']) + [0.99500416527802570954, 1.0] + +Interval arithmetic is useful for proving inequalities involving irrational numbers. +Naive use of ``mp`` arithmetic may result in wrong conclusions, such as the following:: + + >>> mp.dps = 25 + >>> x = mp.exp(mp.pi*mp.sqrt(163)) + >>> y = mp.mpf(640320**3+744) + >>> print(x) + 262537412640768744.0000001 + >>> print(y) + 262537412640768744.0 + >>> x > y + True + +But the correct result is `e^{\pi \sqrt{163}} < 262537412640768744`, as can be +seen by increasing the precision:: + + >>> mp.dps = 50 + >>> print(mp.exp(mp.pi*mp.sqrt(163))) + 262537412640768743.99999999999925007259719818568888 + +With interval arithmetic, the comparison raises :exc:`ValueError` until the +precision is large enough for `x-y` to have a definite sign:: + + >>> iv.dps = 15 + >>> iv.exp(iv.pi*iv.sqrt(163)) > (640320**3+744) + Traceback (most recent call last): + ... + ValueError + >>> iv.dps = 30 + >>> iv.exp(iv.pi*iv.sqrt(163)) > (640320**3+744) + Traceback (most recent call last): + ... + ValueError + >>> iv.dps = 60 + >>> iv.exp(iv.pi*iv.sqrt(163)) > (640320**3+744) + False + >>> iv.dps = 15 + +Fast low-precision arithmetic (``fp``) +--------------------------------------------- + +Although mpmath is generally designed for arbitrary-precision arithmetic, many of the high-level algorithms work perfectly well with ordinary Python ``float`` and ``complex`` numbers, which use hardware double precision (on most systems, this corresponds to 53 bits of precision). Whereas the global functions (which are methods of the ``mp`` object) always convert inputs to mpmath numbers, the ``fp`` object instead converts them to ``float`` or ``complex``, and in some cases employs basic functions optimized for double precision. When large amounts of function evaluations (numerical integration, plotting, etc) are required, and when ``fp`` arithmetic provides sufficient accuracy, this can give a significant speedup over ``mp`` arithmetic. + +To take advantage of this feature, simply use the ``fp`` prefix, i.e. write ``fp.func`` instead of ``func`` or ``mp.func``:: + + >>> u = fp.erfc(0.5) + >>> print(u) + 0.4795001221869535 + >>> type(u) + + >>> mp.dps = 16 + >>> print(mp.erfc(0.5)) + 0.4795001221869535 + >>> fp.matrix([[1,2],[3,4]]) ** 2 + matrix( + [['7.0', '10.0'], + ['15.0', '22.0']]) + >>> + >>> type(_[0,0]) + + >>> print(fp.quad(fp.sin, [0, fp.pi])) # numerical integration + 2.0 + +The ``fp`` context wraps Python's ``math`` and ``cmath`` modules for elementary functions. It supports both real and complex numbers and automatically generates complex results for real inputs (``math`` raises an exception):: + + >>> fp.sqrt(5) + 2.23606797749979 + >>> fp.sqrt(-5) + 2.23606797749979j + >>> fp.sin(10) + -0.5440211108893698 + >>> fp.power(-1, 0.25) + (0.7071067811865476+0.7071067811865475j) + >>> (-1) ** 0.25 + (0.7071067811865476+0.7071067811865475j) + +The ``prec`` and ``dps`` attributes can be changed (for interface compatibility with the ``mp`` context) but this has no effect:: + + >>> fp.prec + 53 + >>> fp.dps + 15 + >>> fp.prec = 80 + >>> fp.prec + 53 + >>> fp.dps + 15 + +Due to intermediate rounding and cancellation errors, results computed with ``fp`` arithmetic may be much less accurate than those computed with ``mp`` using an equivalent precision (``mp.prec = 53``), since the latter often uses increased internal precision. The accuracy is highly problem-dependent: for some functions, ``fp`` almost always gives 14-15 correct digits; for others, results can be accurate to only 2-3 digits or even completely wrong. The recommended use for ``fp`` is therefore to speed up large-scale computations where accuracy can be verified in advance on a subset of the input set, or where results can be verified afterwards. + +Beware that the ``fp`` context has signed zero, that can be used to distinguish +different sides of branch cuts. For example, ``fp.mpc(-1, -0.0)`` is treated +as though it lies *below* the branch cut for :func:`~mpmath.sqrt()`:: + + >>> fp.sqrt(fp.mpc(-1, -0.0)) + -1j + >>> fp.sqrt(fp.mpc(-1, -1e-10)) + (5e-11-1j) + +But an argument of ``fp.mpc(-1, 0.0)`` is treated as though it lies *above* the +branch cut:: + + >>> fp.sqrt(fp.mpc(-1, +0.0)) + 1j + >>> fp.sqrt(fp.mpc(-1, +1e-10)) + (5e-11+1j) + + +While near the branch cut, for small but nonzero deviations in components +results agreed with the ``mp`` contexts:: + + >>> fp.mpc(mp.sqrt(mp.mpc(-1, -1e-10))) + (5e-11-1j) + >>> fp.mpc(mp.sqrt(mp.mpc(-1, +1e-10))) + (5e-11+1j) + +one has no signed zeros and allows to specify result *on the branch cut* +(nonpositive part of the real axis in this example):: + + >>> fp.mpc(mp.sqrt(mp.mpc(-1, 0))) + 1j + >>> fp.mpc(mp.sqrt(-1)) + 1j + +Here it's continuous from the above of the :func:`~mpmath.sqrt()` branch +cut (from ``0`` along the negative real axis to the negative infinity). diff --git a/docs/functions/bessel.rst b/docs/functions/bessel.rst new file mode 100644 index 0000000..15e05aa --- /dev/null +++ b/docs/functions/bessel.rst @@ -0,0 +1,108 @@ +Bessel functions and related functions +-------------------------------------- + +The functions in this section arise as solutions to various differential +equations in physics, typically describing wavelike oscillatory behavior or a +combination of oscillation and exponential decay or growth. Mathematically, +they are special cases of the confluent hypergeometric functions `\,_0F_1`, +`\,_1F_1` and `\,_1F_2` (see :doc:`hypergeometric`). + + +Bessel functions +................ + +.. autofunction:: mpmath.besselj +.. autofunction:: mpmath.j0 +.. autofunction:: mpmath.j1 +.. autofunction:: mpmath.bessely +.. autofunction:: mpmath.besseli +.. autofunction:: mpmath.besselk + + +Bessel function zeros +..................... + +.. autofunction:: mpmath.besseljzero +.. autofunction:: mpmath.besselyzero + + +Hankel functions +................ + +.. autofunction:: mpmath.hankel1 +.. autofunction:: mpmath.hankel2 + + +Spherical Bessel functions +.......................... + +.. autofunction:: mpmath.spherical_jn +.. autofunction:: mpmath.spherical_yn +.. autofunction:: mpmath.spherical_in +.. autofunction:: mpmath.spherical_kn + + +Kelvin functions +................ + +.. autofunction:: mpmath.ber +.. autofunction:: mpmath.bei +.. autofunction:: mpmath.ker +.. autofunction:: mpmath.kei + + +Struve functions +................ + +.. autofunction:: mpmath.struveh +.. autofunction:: mpmath.struvel + + +Anger-Weber functions +..................... + +.. autofunction:: mpmath.angerj +.. autofunction:: mpmath.webere + + +Lommel functions +................ + +.. autofunction:: mpmath.lommels1 +.. autofunction:: mpmath.lommels2 + + +Airy and Scorer functions +......................... + +.. autofunction:: mpmath.airyai +.. autofunction:: mpmath.airybi +.. autofunction:: mpmath.airyaizero +.. autofunction:: mpmath.airybizero +.. autofunction:: mpmath.scorergi +.. autofunction:: mpmath.scorerhi + + +Coulomb wave functions +...................... + +.. autofunction:: mpmath.coulombf +.. autofunction:: mpmath.coulombg +.. autofunction:: mpmath.coulombc + + +Confluent U and Whittaker functions +................................... + +.. autofunction:: mpmath.hyperu(a, b, z) +.. autofunction:: mpmath.whitm(k,m,z) +.. autofunction:: mpmath.whitw(k,m,z) + + +Parabolic cylinder functions +............................ + +.. autofunction:: mpmath.pcfd +.. autofunction:: mpmath.pcfu +.. autofunction:: mpmath.pcfv +.. autofunction:: mpmath.pcfw diff --git a/docs/functions/constants.rst b/docs/functions/constants.rst new file mode 100644 index 0000000..70849a9 --- /dev/null +++ b/docs/functions/constants.rst @@ -0,0 +1,45 @@ +Mathematical constants +---------------------- + +Mpmath supports arbitrary-precision computation of various common (and less +common) mathematical constants. These constants are implemented as lazy +objects that can evaluate to any precision. Whenever the objects are used as +function arguments or as operands in arithmetic operations, they automagically +evaluate to the current working precision. A lazy number can be converted to a +regular ``mpf`` using the unary ``+`` operator, or by calling it as a +function:: + + >>> from mpmath import pi, mp + >>> pi + + >>> 2*pi + mpf('6.2831853071795862') + >>> +pi + mpf('3.1415926535897931') + >>> pi() + mpf('3.1415926535897931') + >>> mp.dps = 40 + >>> pi + + >>> 2*pi + mpf('6.28318530717958647692528676655900576839434') + >>> +pi + mpf('3.14159265358979323846264338327950288419717') + >>> pi() + mpf('3.14159265358979323846264338327950288419717') + +The predefined objects ``j`` (imaginary unit), ``inf`` (positive infinity) and +``nan`` (not-a-number) are shortcuts to ``mpc`` and ``mpf`` instances with +these fixed values. + +.. autofunction:: mpmath.mp.pi +.. autoattribute:: mpmath.mp.degree +.. autoattribute:: mpmath.mp.e +.. autoattribute:: mpmath.mp.phi +.. autofunction:: mpmath.mp.euler +.. autoattribute:: mpmath.mp.catalan +.. autoattribute:: mpmath.mp.apery +.. autoattribute:: mpmath.mp.khinchin +.. autoattribute:: mpmath.mp.glaisher +.. autoattribute:: mpmath.mp.mertens +.. autoattribute:: mpmath.mp.twinprime diff --git a/docs/functions/elliptic.rst b/docs/functions/elliptic.rst new file mode 100644 index 0000000..3a5a9b4 --- /dev/null +++ b/docs/functions/elliptic.rst @@ -0,0 +1,65 @@ +Elliptic functions +------------------ + +.. automodule:: mpmath.functions.elliptic + :no-index: + + +Elliptic arguments +.................. + +.. autofunction:: mpmath.qfrom +.. autofunction:: mpmath.qbarfrom +.. autofunction:: mpmath.mfrom +.. autofunction:: mpmath.kfrom +.. autofunction:: mpmath.taufrom + + +Legendre elliptic integrals +........................... + +.. autofunction:: mpmath.ellipk +.. autofunction:: mpmath.ellipf +.. autofunction:: mpmath.ellipe +.. autofunction:: mpmath.ellippi + + +Carlson symmetric elliptic integrals +.................................... + +.. autofunction:: mpmath.elliprf +.. autofunction:: mpmath.elliprc +.. autofunction:: mpmath.elliprj +.. autofunction:: mpmath.elliprd +.. autofunction:: mpmath.elliprg + + +Jacobi theta functions +...................... + +.. autofunction:: mpmath.jtheta + + +Jacobi elliptic functions +......................... + +.. autofunction:: mpmath.ellipfun + + +Weierstrass elliptic functions +.............................. + +.. autofunction:: mpmath.weierinvariants +.. autofunction:: mpmath.weierhalfperiods +.. autofunction:: mpmath.weierp +.. autofunction:: mpmath.weierpprime +.. autofunction:: mpmath.weiersigma +.. autofunction:: mpmath.weierzeta +.. autofunction:: mpmath.weierpinv + + +Modular functions +................. + +.. autofunction:: mpmath.eta +.. autofunction:: mpmath.kleinj diff --git a/docs/functions/expintegrals.rst b/docs/functions/expintegrals.rst new file mode 100644 index 0000000..2fc2a25 --- /dev/null +++ b/docs/functions/expintegrals.rst @@ -0,0 +1,70 @@ +Exponential integrals and error functions +----------------------------------------- + +Exponential integrals give closed-form solutions to a large class of commonly +occurring transcendental integrals that cannot be evaluated using elementary +functions. Integrals of this type include those with an integrand of the form +`t^a e^{t}` or `e^{-x^2}`, the latter giving rise to the Gaussian (or normal) +probability distribution. + +The most general function in this section is the incomplete gamma function, to +which all others can be reduced. The incomplete gamma function, in turn, can +be expressed using hypergeometric functions (see :doc:`hypergeometric`). + +Incomplete gamma functions +.......................... + +.. autofunction:: mpmath.gammainc +.. autofunction:: mpmath.lower_gamma +.. autofunction:: mpmath.upper_gamma + + +Exponential integrals +..................... + +.. autofunction:: mpmath.ei +.. autofunction:: mpmath.e1 +.. autofunction:: mpmath.expint + + +Logarithmic integral +.................... + +.. autofunction:: mpmath.li + + +Trigonometric integrals +....................... + +.. autofunction:: mpmath.ci +.. autofunction:: mpmath.si + + +Hyperbolic integrals +.................... + +.. autofunction:: mpmath.chi +.. autofunction:: mpmath.shi + + +Error functions +............... + +.. autofunction:: mpmath.erf +.. autofunction:: mpmath.erfc +.. autofunction:: mpmath.erfi +.. autofunction:: mpmath.erfinv + + +The normal distribution +....................... + +.. autofunction:: mpmath.npdf +.. autofunction:: mpmath.ncdf + + +Fresnel integrals +................. + +.. autofunction:: mpmath.fresnels +.. autofunction:: mpmath.fresnelc diff --git a/docs/functions/gamma.rst b/docs/functions/gamma.rst new file mode 100644 index 0000000..e59ced8 --- /dev/null +++ b/docs/functions/gamma.rst @@ -0,0 +1,79 @@ +Factorials and gamma functions +------------------------------ + +Factorials and factorial-like sums and products are basic tools of +combinatorics and number theory. Much like the exponential function is +fundamental to differential equations and analysis in general, the factorial +function (and its extension to complex numbers, the gamma function) is +fundamental to difference equations and functional equations. + +A large selection of factorial-like functions is implemented in mpmath. All +functions support complex arguments, and arguments may be arbitrarily large. +Results are numerical approximations, so to compute *exact* values a high +enough precision must be set manually:: + + >>> from mpmath import mp, fac + >>> mp.dps = 15 + >>> mp.pretty = True + >>> fac(100) + 9.33262154439442e+157 + >>> print(int(_)) # most digits are wrong + 93326215443944150965646704795953882578400970373184098831012889540582227238570431295066113089288327277825849664006524270554535976289719382852181865895959724032 + >>> mp.dps = 160 + >>> fac(100) + 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000.0 + +The gamma and polygamma functions are closely related to :doc:`zeta`. See also +:doc:`qfunctions` for q-analogs of factorial-like functions. + + +Factorials +.......... + +.. autofunction:: mpmath.factorial +.. autofunction:: mpmath.fac2 + + +Binomial coefficients +..................... + +.. autofunction:: mpmath.binomial + + +Gamma function +.............. + +.. autofunction:: mpmath.gamma +.. autofunction:: mpmath.rgamma +.. autofunction:: mpmath.gammaprod +.. autofunction:: mpmath.loggamma + + +Rising and falling factorials +............................. + +.. autofunction:: mpmath.rf +.. autofunction:: mpmath.ff + + +Beta function +............. + +.. autofunction:: mpmath.beta +.. autofunction:: mpmath.betainc + + +Super- and hyperfactorials +.......................... + +.. autofunction:: mpmath.superfac +.. autofunction:: mpmath.hyperfac +.. autofunction:: mpmath.barnesg + + +Polygamma functions and harmonic numbers +........................................ + +.. autofunction:: mpmath.psi +.. autofunction:: mpmath.digamma +.. autofunction:: mpmath.harmonic diff --git a/docs/functions/hyperbolic.rst b/docs/functions/hyperbolic.rst new file mode 100644 index 0000000..39531ee --- /dev/null +++ b/docs/functions/hyperbolic.rst @@ -0,0 +1,23 @@ +Hyperbolic functions +-------------------- + +Hyperbolic functions +.................... + +.. autofunction:: mpmath.cosh +.. autofunction:: mpmath.sinh +.. autofunction:: mpmath.tanh +.. autofunction:: mpmath.sech +.. autofunction:: mpmath.csch +.. autofunction:: mpmath.coth + + +Inverse hyperbolic functions +............................ + +.. autofunction:: mpmath.acosh +.. autofunction:: mpmath.asinh +.. autofunction:: mpmath.atanh +.. autofunction:: mpmath.asech +.. autofunction:: mpmath.acsch +.. autofunction:: mpmath.acoth diff --git a/docs/functions/hypergeometric.rst b/docs/functions/hypergeometric.rst new file mode 100644 index 0000000..b36151d --- /dev/null +++ b/docs/functions/hypergeometric.rst @@ -0,0 +1,74 @@ +Hypergeometric functions +------------------------ + +The functions listed in :doc:`expintegrals`, :doc:`bessel` and +:doc:`orthogonal`, and many other functions as well, are merely particular +instances of the generalized hypergeometric function `\,_pF_q`. The functions +listed in the following section enable efficient direct evaluation of the +underlying hypergeometric series, as well as linear combinations, limits with +respect to parameters, and analytic continuations thereof. Extensions to +twodimensional series are also provided. See also the basic or q-analog of the +hypergeometric series in :doc:`qfunctions`. + +For convenience, most of the hypergeometric series of low order are provided as +standalone functions. They can equivalently be evaluated using +:func:`~mpmath.hyper`. As will be demonstrated in the respective docstrings, +all the ``hyp#f#`` functions implement analytic continuations and/or asymptotic +expansions with respect to the argument `z`, thereby permitting evaluation for +`z` anywhere in the complex plane. Functions of higher degree can be computed +via :func:`~mpmath.hyper`, but generally only in rapidly convergent instances. + +Most hypergeometric and hypergeometric-derived functions accept optional +keyword arguments to specify options for :func:`~mpmath.hypercomb` or +:func:`~mpmath.hyper`. Some useful options are *maxprec*, *maxterms*, +*zeroprec*, *accurate_small*, *hmag*, *force_series*, *asymp_tol* and +*eliminate*. These options give control over what to do in case of slow +convergence, extreme loss of accuracy or evaluation at zeros (these two cases +cannot generally be distinguished from each other automatically), and singular +parameter combinations. + +Common hypergeometric series +............................ + +.. autofunction:: mpmath.hyp0f1 +.. autofunction:: mpmath.hyp1f1 +.. autofunction:: mpmath.hyp1f2 +.. autofunction:: mpmath.hyp2f0 +.. autofunction:: mpmath.hyp2f1 +.. autofunction:: mpmath.hyp2f2 +.. autofunction:: mpmath.hyp2f3 +.. autofunction:: mpmath.hyp3f2 + + +Generalized hypergeometric functions +.................................... + +.. autofunction:: mpmath.hyper +.. autofunction:: mpmath.hypercomb + + +Meijer G-function +................. + +.. autofunction:: mpmath.meijerg + +Fox H-function +................. + +.. autofunction:: mpmath.foxh + + +Bilateral hypergeometric series +............................... + +.. autofunction:: mpmath.bihyper + + +Hypergeometric functions of two variables +......................................... + +.. autofunction:: mpmath.hyper2d +.. autofunction:: mpmath.appellf1 +.. autofunction:: mpmath.appellf2 +.. autofunction:: mpmath.appellf3 +.. autofunction:: mpmath.appellf4 diff --git a/docs/functions/index.rst b/docs/functions/index.rst new file mode 100644 index 0000000..3722833 --- /dev/null +++ b/docs/functions/index.rst @@ -0,0 +1,22 @@ +Mathematical functions +====================== + +Mpmath implements the standard functions from Python's ``math`` and ``cmath`` modules, for both real and complex numbers and with arbitrary precision. Many other functions are also available in mpmath, including commonly-used variants of standard functions (such as the alternative trigonometric functions sec, csc, cot), but also a large number of "special functions" such as the gamma function, the Riemann zeta function, error functions, Bessel functions, etc. + +.. toctree:: + :maxdepth: 2 + + constants + powers + trigonometric + hyperbolic + signals + gamma + expintegrals + bessel + orthogonal + hypergeometric + elliptic + zeta + numtheory + qfunctions diff --git a/docs/functions/numtheory.rst b/docs/functions/numtheory.rst new file mode 100644 index 0000000..2e72f2d --- /dev/null +++ b/docs/functions/numtheory.rst @@ -0,0 +1,58 @@ +Number-theoretical, combinatorial and integer functions +------------------------------------------------------- + +For factorial-type functions, including binomial coefficients, double +factorials, etc, see the separate section :doc:`gamma`. + +Fibonacci numbers +................. + +.. autofunction:: mpmath.fibonacci + + +Bernoulli numbers and polynomials +................................. + +.. autofunction:: mpmath.bernoulli +.. autofunction:: mpmath.bernfrac +.. autofunction:: mpmath.bernpoly + + +Euler numbers and polynomials +............................. + +.. autofunction:: mpmath.eulernum +.. autofunction:: mpmath.eulerpoly + + +Bell numbers and polynomials +............................ + +.. autofunction:: mpmath.bell + + +Stirling numbers +................ + +.. autofunction:: mpmath.stirling1 +.. autofunction:: mpmath.stirling2 + + +Prime counting functions +........................ + +.. autofunction:: mpmath.primepi +.. autofunction:: mpmath.primepi2 +.. autofunction:: mpmath.riemannr + + +Cyclotomic polynomials +...................... + +.. autofunction:: mpmath.cyclotomic + + +Arithmetic functions +...................... + +.. autofunction:: mpmath.mangoldt diff --git a/docs/functions/orthogonal.rst b/docs/functions/orthogonal.rst new file mode 100644 index 0000000..d543056 --- /dev/null +++ b/docs/functions/orthogonal.rst @@ -0,0 +1,77 @@ +Orthogonal polynomials +---------------------- + +An orthogonal polynomial sequence is a sequence of polynomials `P_0(x), P_1(x), +\ldots` of degree `0, 1, \ldots`, which are mutually orthogonal in the sense +that + +.. math :: + + \int_S P_n(x) P_m(x) w(x) dx = + \begin{cases} + c_n \ne 0 & \text{if $m = n$} \\ + 0 & \text{if $m \ne n$} + \end{cases} + +where `S` is some domain (e.g. an interval `[a,b] \in \mathbb{R}`) and `w(x)` +is a fixed *weight function*. A sequence of orthogonal polynomials is +determined completely by `w`, `S`, and a normalization convention (e.g. `c_n = +1`). Applications of orthogonal polynomials include function approximation and +solution of differential equations. + +Orthogonal polynomials are sometimes defined using the differential equations +they satisfy (as functions of `x`) or the recurrence relations they satisfy +with respect to the order `n`. Other ways of defining orthogonal polynomials +include differentiation formulas and generating functions. The standard +orthogonal polynomials can also be represented as hypergeometric series (see +:doc:`hypergeometric`), more specifically using the Gauss hypergeometric +function `\,_2F_1` in most cases. The following functions are generally +implemented using hypergeometric functions since this is computationally +efficient and easily generalizes. + +For more information, see the `Wikipedia article on orthogonal polynomials +`_. + +Legendre functions +.................. + +.. autofunction:: mpmath.legendre +.. autofunction:: mpmath.legenp +.. autofunction:: mpmath.legenq + + +Chebyshev polynomials +..................... + +.. autofunction:: mpmath.chebyt +.. autofunction:: mpmath.chebyu + + +Jacobi polynomials +.................. + +.. autofunction:: mpmath.jacobi + + +Gegenbauer polynomials +...................... + +.. autofunction:: mpmath.gegenbauer + + +Hermite polynomials +................... + +.. autofunction:: mpmath.hermite + + +Laguerre polynomials +.................... + +.. autofunction:: mpmath.laguerre + + +Spherical harmonics +................... + +.. autofunction:: mpmath.spherharm diff --git a/docs/functions/powers.rst b/docs/functions/powers.rst new file mode 100644 index 0000000..9a5ab32 --- /dev/null +++ b/docs/functions/powers.rst @@ -0,0 +1,45 @@ +Powers and logarithms +--------------------- + +Nth roots +......... + +.. autofunction:: mpmath.sqrt +.. autofunction:: mpmath.hypot +.. autofunction:: mpmath.cbrt +.. autofunction:: mpmath.root +.. autofunction:: mpmath.unitroots + + +Exponentiation +.............. + +.. autofunction:: mpmath.exp +.. autofunction:: mpmath.exp2 +.. autofunction:: mpmath.power +.. autofunction:: mpmath.expj +.. autofunction:: mpmath.expjpi +.. autofunction:: mpmath.expm1(x) +.. autofunction:: mpmath.powm1(x, y) + + +Logarithms +.......... + +.. autofunction:: mpmath.log +.. autofunction:: mpmath.ln +.. autofunction:: mpmath.log2 +.. autofunction:: mpmath.log10 +.. autofunction:: mpmath.log1p(x) + + +Lambert W function +.................. + +.. autofunction:: mpmath.lambertw + + +Arithmetic-geometric mean +......................... + +.. autofunction:: mpmath.agm diff --git a/docs/functions/qfunctions.rst b/docs/functions/qfunctions.rst new file mode 100644 index 0000000..ed2ac38 --- /dev/null +++ b/docs/functions/qfunctions.rst @@ -0,0 +1,20 @@ +q-functions +----------- + +q-Pochhammer symbol +................... + +.. autofunction:: mpmath.qp + + +q-gamma and factorial +..................... + +.. autofunction:: mpmath.qgamma +.. autofunction:: mpmath.qfac + + +Hypergeometric q-series +....................... + +.. autofunction:: mpmath.qhyper diff --git a/docs/functions/signals.rst b/docs/functions/signals.rst new file mode 100644 index 0000000..b305ce3 --- /dev/null +++ b/docs/functions/signals.rst @@ -0,0 +1,34 @@ +Signal functions +---------------- + +The functions in this section describe non-sinusoidal waveforms, which are +often used in signal processing and electronics. + +Square wave signal +.................. + +.. autofunction:: mpmath.squarew + + +Triangle wave signal +.................... + +.. autofunction:: mpmath.trianglew + + +Sawtooth wave signal +.................... + +.. autofunction:: mpmath.sawtoothw + + +Unit triangle signal +.................... + +.. autofunction:: mpmath.unit_triangle + + +Sigmoid wave signal +..................... + +.. autofunction:: mpmath.sigmoid diff --git a/docs/functions/trigonometric.rst b/docs/functions/trigonometric.rst new file mode 100644 index 0000000..9eb3560 --- /dev/null +++ b/docs/functions/trigonometric.rst @@ -0,0 +1,62 @@ +Trigonometric functions +----------------------- + +Except where otherwise noted, the trigonometric functions take a radian angle +as input and the inverse trigonometric functions return radian angles. + +The ordinary trigonometric functions are single-valued functions defined +everywhere in the complex plane (except at the poles of tan, sec, csc, and +cot). They are defined generally via the exponential function, e.g. + +.. math :: + + \cos(x) = \frac{e^{ix} + e^{-ix}}{2}. + +The inverse trigonometric functions are multivalued, thus requiring branch +cuts, and are generally real-valued only on a part of the real line. +Definitions and branch cuts are given in the documentation of each function. +The branch cut conventions used by mpmath are essentially the same as those +found in most standard mathematical software, such as Mathematica and Python's +own ``cmath`` libary. + +Degree-radian conversion +........................ + +.. autofunction:: mpmath.degrees +.. autofunction:: mpmath.radians + +Trigonometric functions +....................... + +.. autofunction:: mpmath.cos +.. autofunction:: mpmath.sin +.. autofunction:: mpmath.tan +.. autofunction:: mpmath.sec +.. autofunction:: mpmath.csc +.. autofunction:: mpmath.cot + + +Trigonometric functions with modified argument +.............................................. + +.. autofunction:: mpmath.cospi +.. autofunction:: mpmath.sinpi + + +Inverse trigonometric functions +............................... + +.. autofunction:: mpmath.acos +.. autofunction:: mpmath.asin +.. autofunction:: mpmath.atan +.. autofunction:: mpmath.atan2 +.. autofunction:: mpmath.asec +.. autofunction:: mpmath.acsc +.. autofunction:: mpmath.acot + + +Sinc function +............. + +.. autofunction:: mpmath.sinc +.. autofunction:: mpmath.sincpi diff --git a/docs/functions/zeta.rst b/docs/functions/zeta.rst new file mode 100644 index 0000000..bc4393b --- /dev/null +++ b/docs/functions/zeta.rst @@ -0,0 +1,60 @@ +Zeta functions, L-series and polylogarithms +------------------------------------------- + +This section includes the Riemann zeta functions and associated functions +pertaining to analytic number theory. + + +Riemann and Hurwitz zeta functions +.................................. + +.. autofunction:: mpmath.zeta + + +Dirichlet L-series +.................. + +.. autofunction:: mpmath.altzeta +.. autofunction:: mpmath.dirichlet + + +Stieltjes constants +................... + +.. autofunction:: mpmath.stieltjes + + +Zeta function zeros +................... + +These functions are used for the study of the Riemann zeta function in the +critical strip. + +.. autofunction:: mpmath.zetazero +.. autofunction:: mpmath.nzeros +.. autofunction:: mpmath.siegelz +.. autofunction:: mpmath.siegeltheta +.. autofunction:: mpmath.grampoint +.. autofunction:: mpmath.backlunds + + +Lerch transcendent +.................. + +.. autofunction:: mpmath.lerchphi + + +Polylogarithms and Clausen functions +.................................... + +.. autofunction:: mpmath.polylog +.. autofunction:: mpmath.clsin +.. autofunction:: mpmath.clcos +.. autofunction:: mpmath.polyexp + + +Zeta function variants +...................... + +.. autofunction:: mpmath.primezeta +.. autofunction:: mpmath.secondzeta diff --git a/docs/general.rst b/docs/general.rst new file mode 100644 index 0000000..7991e36 --- /dev/null +++ b/docs/general.rst @@ -0,0 +1,240 @@ +Utility functions +=============================================== + +This page lists functions that perform basic operations +on numbers or aid general programming. + +Conversion and printing +----------------------- + +:func:`~mpmath.mpmathify` / ``convert()`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.mpmathify + +:func:`~mpmath.nstr` +^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.nstr + +:func:`~mpmath.nprint` +^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.nprint + +:func:`mpmath.mpf.__format__` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.mpf.__format__ + +:func:`mpmath.mpc.__format__` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.mpc.__format__ + +Arithmetic operations +--------------------- + +See also :func:`mpmath.sqrt`, :func:`mpmath.exp` etc., listed +in :doc:`functions/powers` + +:func:`~mpmath.fadd` +^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.fadd + +:func:`~mpmath.fsub` +^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.fsub + +:func:`~mpmath.fneg` +^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.fneg + +:func:`~mpmath.fmul` +^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.fmul + +:func:`~mpmath.fdiv` +^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.fdiv + +:func:`~mpmath.fmod` +^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.fmod + +:func:`~mpmath.fsum` +^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.fsum + +:func:`~mpmath.fprod` +^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.fprod + +:func:`~mpmath.fdot` +^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.fdot + +Complex components +------------------ + +:func:`~mpmath.fabs` +^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.fabs + +:func:`~mpmath.sign` +^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.sign + +:func:`~mpmath.re` +^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.re + +:func:`~mpmath.im` +^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.im + +:func:`~mpmath.arg` +^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.arg + +:func:`~mpmath.conj` +^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.conj + +:func:`~mpmath.polar` +^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.polar + +:func:`~mpmath.rect` +^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.rect + +Integer and fractional parts +----------------------------- + +:func:`~mpmath.floor` +^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.floor + +:func:`~mpmath.ceil` +^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.ceil + +:func:`~mpmath.nint` +^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.nint + +:func:`~mpmath.frac` +^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.frac + +Tolerances and approximate comparisons +-------------------------------------- + +:func:`~mpmath.chop` +^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.chop + +:func:`~mpmath.almosteq` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.almosteq + +Properties of numbers +------------------------------------- + +:func:`~mpmath.isinf` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.isinf + +:func:`~mpmath.isnan` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.isnan + +:func:`~mpmath.isnormal` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.isnormal + +:func:`~mpmath.isfinite` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.isfinite + +:func:`~mpmath.isint` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.isint + +:func:`~mpmath.ldexp` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.ldexp + +:func:`~mpmath.frexp` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.frexp + +:func:`~mpmath.mag` +^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.mag + +:func:`~mpmath.nint_distance` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.nint_distance + +.. :func:`~mpmath.absmin` +.. ^^^^^^^^^^^^^^^^^^^^^^^^ +.. .. autofunction:: mpmath.absmin(x) +.. .. autofunction:: mpmath.absmax(x) + +Number generation +----------------- + +:func:`~mpmath.fraction` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.fraction + +:func:`~mpmath.rand` +^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.rand + +:func:`~mpmath.arange` +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.arange + +:func:`~mpmath.linspace` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.linspace + +Precision management +-------------------- + +:func:`~mpmath.autoprec` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.autoprec + +:func:`~mpmath.workprec` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.workprec + +:func:`~mpmath.workdps` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.workdps + +:func:`~mpmath.extraprec` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.extraprec + +:func:`~mpmath.extradps` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.extradps + +Performance and debugging +------------------------------------ + +:func:`~mpmath.memoize` +^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.memoize + +:func:`~mpmath.maxcalls` +^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.maxcalls + +:func:`~mpmath.monitor` +^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.monitor + +:func:`~mpmath.timing` +^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.timing diff --git a/docs/genindex.rst b/docs/genindex.rst new file mode 100644 index 0000000..9e530fa --- /dev/null +++ b/docs/genindex.rst @@ -0,0 +1,2 @@ +Index +===== diff --git a/docs/identification.rst b/docs/identification.rst new file mode 100644 index 0000000..e581b18 --- /dev/null +++ b/docs/identification.rst @@ -0,0 +1,31 @@ +Number identification +===================== + +Most function in mpmath are concerned with producing approximations from exact mathematical formulas. It is also useful to consider the inverse problem: given only a decimal approximation for a number, such as 0.7320508075688772935274463, is it possible to find an exact formula? + +Subject to certain restrictions, such "reverse engineering" is indeed possible thanks to the existence of *integer relation algorithms*. Mpmath implements the PSLQ algorithm (developed by H. Ferguson), which is one such algorithm. + +Automated number recognition based on PSLQ is not a silver bullet. Any occurring transcendental constants (`\pi`, `e`, etc) must be guessed by the user, and the relation between those constants in the formula must be linear (such as `x = 3 \pi + 4 e`). More complex formulas can be found by combining PSLQ with functional transformations; however, this is only feasible to a limited extent since the computation time grows exponentially with the number of operations that need to be combined. + +The number identification facilities in mpmath are inspired by the `Inverse Symbolic Calculator `_ (ISC). The ISC is more powerful than mpmath, as it uses a lookup table of millions of precomputed constants (thereby mitigating the problem with exponential complexity). + +Constant recognition +----------------------------------- + +:func:`~mpmath.identify` +^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.identify + +Algebraic identification +--------------------------------------- + +:func:`~mpmath.findpoly` +^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.findpoly + +Integer relations (PSLQ) +---------------------------- + +:func:`~mpmath.pslq` +^^^^^^^^^^^^^^^^^^^^^ +.. autofunction:: mpmath.pslq diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..9cca77b --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,55 @@ +.. mpmath documentation master file, created by sphinx-quickstart on Fri Mar 28 13:50:14 2008. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to mpmath's documentation! +================================== + +Mpmath is a Python library for arbitrary-precision floating-point arithmetic. +For general information about mpmath, see the project website https://mpmath.org/ + +These documentation pages include general information as well as docstring listing with extensive use of examples that can be run in the interactive Python interpreter. For quick access to the docstrings of individual functions, use the `index listing `_, or type ``help(mpmath.function_name)`` in the Python interactive prompt. + +Introduction +------------ + +.. toctree :: + :maxdepth: 2 + + setup + basics + +Basic features +---------------- + +.. toctree :: + :maxdepth: 2 + + contexts + general + plotting + cli + +Advanced mathematics +-------------------- + +On top of its support for arbitrary-precision arithmetic, mpmath +provides extensive support for transcendental functions, evaluation of sums, integrals, limits, roots, and so on. + +.. toctree :: + :maxdepth: 2 + + functions/index + calculus/index + matrices + identification + +End matter +---------- + +.. toctree :: + :maxdepth: 2 + + technical + references + genindex diff --git a/docs/matrices.rst b/docs/matrices.rst new file mode 100644 index 0000000..a3485a2 --- /dev/null +++ b/docs/matrices.rst @@ -0,0 +1,569 @@ +Matrices +======== + +Creating matrices +----------------- + +Basic methods +............. + +Matrices in mpmath are implemented using dictionaries. Only non-zero values are +stored, so it is cheap to represent sparse matrices. + +The most basic way to create one is to use the ``matrix`` class directly. You +can create an empty matrix specifying the dimensions:: + + >>> from mpmath import (matrix, ones, zeros, randmatrix, nprint, chop, iv, + ... lu_solve, residual, fp, lu, diag, eye, eps, qr) + >>> matrix(2) + matrix( + [['0.0', '0.0'], + ['0.0', '0.0']]) + >>> matrix(2, 3) + matrix( + [['0.0', '0.0', '0.0'], + ['0.0', '0.0', '0.0']]) + +Calling ``matrix`` with one dimension will create a square matrix. + +To access the dimensions of a matrix, use the ``rows`` or ``cols`` keyword:: + + >>> A = matrix(3, 2) + >>> A + matrix( + [['0.0', '0.0'], + ['0.0', '0.0'], + ['0.0', '0.0']]) + >>> A.rows + 3 + >>> A.cols + 2 + +You can also change the dimension of an existing matrix. This will set the +new elements to 0. If the new dimension is smaller than before, the +concerning elements are discarded:: + + >>> A.rows = 2 + >>> A + matrix( + [['0.0', '0.0'], + ['0.0', '0.0']]) + +Internally ``convert`` is applied every time an element is set. This is +done using the syntax A[row,column], counting from 0:: + + >>> A = matrix(2) + >>> A[1,1] = 1 + 1j + >>> print(A) + [0.0 0.0] + [0.0 (1.0 + 1.0j)] + +A more comfortable way to create a matrix lets you use nested lists:: + + >>> matrix([[1, 2], [3, 4]]) + matrix( + [['1.0', '2.0'], + ['3.0', '4.0']]) + +Advanced methods +................ + +Convenient functions are available for creating various standard matrices:: + + >>> zeros(2) + matrix( + [['0.0', '0.0'], + ['0.0', '0.0']]) + >>> ones(2) + matrix( + [['1.0', '1.0'], + ['1.0', '1.0']]) + >>> diag([1, 2, 3]) # diagonal matrix + matrix( + [['1.0', '0.0', '0.0'], + ['0.0', '2.0', '0.0'], + ['0.0', '0.0', '3.0']]) + >>> eye(2) # identity matrix + matrix( + [['1.0', '0.0'], + ['0.0', '1.0']]) + +You can even create random matrices:: + + >>> randmatrix(2) # doctest:+SKIP + matrix( + [['0.53491598236191806', '0.57195669543302752'], + ['0.85589992269513615', '0.82444367501382143']]) + +Vectors +....... + +Vectors may also be represented by the ``matrix`` class (with rows = 1 or cols = 1). +For vectors there are some things which make life easier. A column vector can +be created using a flat list, a row vectors using an almost flat nested list:: + + >>> matrix([1, 2, 3]) + matrix( + [['1.0'], + ['2.0'], + ['3.0']]) + >>> matrix([[1, 2, 3]]) + matrix( + [['1.0', '2.0', '3.0']]) + +Optionally vectors can be accessed like lists, using only a single index:: + + >>> x = matrix([1, 2, 3]) + >>> x[1] + mpf('2.0') + >>> x[1,0] + mpf('2.0') + +Other +..... + +Like you probably expected, matrices can be printed:: + + >>> print(randmatrix(3)) # doctest:+SKIP + [ 0.782963853573023 0.802057689719883 0.427895717335467] + [0.0541876859348597 0.708243266653103 0.615134039977379] + [ 0.856151514955773 0.544759264818486 0.686210904770947] + +Use ``nstr`` or ``nprint`` to specify the number of digits to print:: + + >>> nprint(randmatrix(5), 3) # doctest:+SKIP + [2.07e-1 1.66e-1 5.06e-1 1.89e-1 8.29e-1] + [6.62e-1 6.55e-1 4.47e-1 4.82e-1 2.06e-2] + [4.33e-1 7.75e-1 6.93e-2 2.86e-1 5.71e-1] + [1.01e-1 2.53e-1 6.13e-1 3.32e-1 2.59e-1] + [1.56e-1 7.27e-2 6.05e-1 6.67e-2 2.79e-1] + +As matrices are mutable, you will need to copy them sometimes:: + + >>> A = matrix(2) + >>> A + matrix( + [['0.0', '0.0'], + ['0.0', '0.0']]) + >>> B = A.copy() + >>> B[0,0] = 1 + >>> B + matrix( + [['1.0', '0.0'], + ['0.0', '0.0']]) + >>> A + matrix( + [['0.0', '0.0'], + ['0.0', '0.0']]) + +Finally, it is possible to convert a matrix to a nested list. This is very useful, +as most Python libraries involving matrices or arrays (namely NumPy or SymPy) +support this format:: + + >>> B.tolist() + [[mpf('1.0'), mpf('0.0')], [mpf('0.0'), mpf('0.0')]] + + +Matrix operations +----------------- + +You can add and subtract matrices of compatible dimensions:: + + >>> A = matrix([[1, 2], [3, 4]]) + >>> B = matrix([[-2, 4], [5, 9]]) + >>> A + B + matrix( + [['-1.0', '6.0'], + ['8.0', '13.0']]) + >>> A - B + matrix( + [['3.0', '-2.0'], + ['-2.0', '-5.0']]) + >>> A + ones(3) + Traceback (most recent call last): + File "", line 1, in + File "...", line 238, in __add__ + raise ValueError('incompatible dimensions for addition') + ValueError: incompatible dimensions for addition + +It is possible to multiply or add matrices and scalars. In the latter case the +operation will be done element-wise:: + + >>> A * 2 + matrix( + [['2.0', '4.0'], + ['6.0', '8.0']]) + >>> A / 4 + matrix( + [['0.25', '0.5'], + ['0.75', '1.0']]) + >>> A - 1 + matrix( + [['0.0', '1.0'], + ['2.0', '3.0']]) + +Of course you can perform matrix multiplication, if the dimensions are +compatible:: + + >>> A * B + matrix( + [['8.0', '22.0'], + ['14.0', '48.0']]) + >>> matrix([[1, 2, 3]]) * matrix([[-6], [7], [-2]]) + matrix( + [['2.0']]) + +You can raise powers of square matrices:: + + >>> A**2 + matrix( + [['7.0', '10.0'], + ['15.0', '22.0']]) + +Negative powers will calculate the inverse:: + + >>> A**-1 + matrix( + [['-2.0', '1.0'], + ['1.5', '-0.5']]) + >>> nprint(A * A**-1, 3) + [ 1.0 1.08e-19] + [-2.17e-19 1.0] + +Matrix transposition is straightforward:: + + >>> A = ones(2, 3) + >>> A + matrix( + [['1.0', '1.0', '1.0'], + ['1.0', '1.0', '1.0']]) + >>> A.T + matrix( + [['1.0', '1.0'], + ['1.0', '1.0'], + ['1.0', '1.0']]) + + +Norms +..... + +Sometimes you need to know how "large" a matrix or vector is. Due to their +multidimensional nature it's not possible to compare them, but there are +several functions to map a matrix or a vector to a positive real number, the +so called norms. + +.. autofunction :: mpmath.norm + +.. autofunction :: mpmath.mnorm + + +Linear algebra +-------------- + +Determinant and Rank +.................... + +.. autofunction :: mpmath.det + +.. autofunction :: mpmath.rank + + +Decompositions +.............. + +.. autofunction :: mpmath.cholesky + + +Linear equations +................ + +Basic linear algebra is implemented; you can for example solve the linear +equation system:: + + x + 2*y = -10 + 3*x + 4*y = 10 + +using ``lu_solve``:: + + >>> A = matrix([[1, 2], [3, 4]]) + >>> b = matrix([-10, 10]) + >>> x = lu_solve(A, b) + >>> x + matrix( + [['30.0'], + ['-20.0']]) + +If you don't trust the result, use ``residual`` to calculate +the residual `||A x-b||`:: + + >>> residual(A, x, b) + matrix( + [['3.46944695195361e-18'], + ['3.46944695195361e-18']]) + >>> str(eps) + '2.22044604925031e-16' + +As you can see, the solution is quite accurate. The error is caused by the +inaccuracy of the internal floating-point arithmetic. Though, it's even smaller +than the current machine epsilon, which basically means you can trust the +result. + +If you need more speed, use NumPy, or use ``fp`` instead ``mp`` matrices +and methods:: + + >>> A = fp.matrix([[1, 2], [3, 4]]) + >>> b = fp.matrix([-10, 10]) + >>> fp.lu_solve(A, b) + matrix( + [['29.999999999999996'], + ['-19.999999999999996']]) + +``lu_solve`` accepts overdetermined systems. It is usually not possible to solve +such systems, so the residual is minimized instead. Internally this is done +using Cholesky decomposition to compute a least squares approximation. This means +that that ``lu_solve`` will square the errors. If you can't afford this, use +``qr_solve`` instead. It is twice as slow but more accurate, and it calculates +the residual automatically. + +.. autofunction:: mpmath.lu_solve + +Matrix factorization +.................... + +The function ``lu`` computes an explicit LU factorization of a matrix:: + + >>> P, L, U = lu(matrix([[0,2,3],[4,5,6],[7,8,9]])) + >>> print(P) + [0.0 0.0 1.0] + [1.0 0.0 0.0] + [0.0 1.0 0.0] + >>> print(L) + [ 1.0 0.0 0.0] + [ 0.0 1.0 0.0] + [0.571428571428571 0.214285714285714 1.0] + >>> print(U) + [7.0 8.0 9.0] + [0.0 2.0 3.0] + [0.0 0.0 0.214285714285714] + >>> print(P.T*L*U) + [0.0 2.0 3.0] + [4.0 5.0 6.0] + [7.0 8.0 9.0] + +The function ``qr`` computes a QR factorization of a matrix:: + + >>> A = matrix([[1, 2], [3, 4], [1, 1]]) + >>> Q, R = qr(A) + >>> print(Q) + [-0.301511344577764 0.861640436855329 0.408248290463863] + [-0.904534033733291 -0.123091490979333 -0.408248290463863] + [-0.301511344577764 -0.492365963917331 0.816496580927726] + >>> print(R) + [-3.3166247903554 -4.52267016866645] + [ 0.0 0.738548945875996] + [ 0.0 0.0] + >>> print(Q * R) + [1.0 2.0] + [3.0 4.0] + [1.0 1.0] + >>> print(chop(Q.T * Q)) + [1.0 0.0 0.0] + [0.0 1.0 0.0] + [0.0 0.0 1.0] + + +The singular value decomposition +................................ + +The routines ``svd_r`` and ``svd_c`` compute the singular value decomposition +of a real or complex matrix A. ``svd`` is an unified interface calling +either ``svd_r`` or ``svd_c`` depending on whether *A* is real or complex. + +Given *A*, two orthogonal (*A* real) or unitary (*A* complex) matrices *U* and *V* +are calculated such that + +.. math :: + + A = U S V, \quad U' U = 1, \quad V V' = 1 + +where *S* is a suitable shaped matrix whose off-diagonal elements are zero. +Here ' denotes the hermitian transpose (i.e. transposition and complex +conjugation). The diagonal elements of *S* are the singular values of *A*, +i.e. the square roots of the eigenvalues of `A' A` or `A A'`. + +Examples:: + + >>> from mpmath import mp + >>> A = mp.matrix([[2, -2, -1], [3, 4, -2], [-2, -2, 0]]) + >>> S = mp.svd_r(A, compute_uv = False) + >>> print(S) + [6.0] + [3.0] + [1.0] + >>> U, S, V = mp.svd_r(A) + >>> print(mp.chop(A - U * mp.diag(S) * V)) + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] + + +The Schur decomposition +....................... + +This routine computes the Schur decomposition of a square matrix *A*. +Given *A*, a unitary matrix *Q* is determined such that + +.. math :: + + Q' A Q = R, \quad Q' Q = Q Q' = 1 + +where *R* is an upper right triangular matrix. Here ' denotes the +hermitian transpose (i.e. transposition and conjugation). + +Examples:: + + >>> from mpmath import mp + >>> A = mp.matrix([[3, -1, 2], [2, 5, -5], [-2, -3, 7]]) + >>> Q, R = mp.schur(A) + >>> mp.nprint(R, 3) + [2.0 0.417 2.53] + [0.0 4.0 4.74] + [0.0 0.0 9.0] + >>> print(mp.chop(A - Q * R * Q.transpose_conj())) + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] + + +The eigenvalue problem +...................... + +The routine ``eig`` solves the (ordinary) eigenvalue problem for a real or complex +square matrix *A*. Given *A*, a vector *E* and matrices *ER* and *EL* are calculated such that + +.. code :: + + A ER[:,i] = E[i] ER[:,i] + EL[i,:] A = EL[i,:] E[i] + +*E* contains the eigenvalues of *A*. The columns of *ER* contain the right eigenvectors +of *A* whereas the rows of *EL* contain the left eigenvectors. + + +Examples:: + + >>> from mpmath import mp + >>> A = mp.matrix([[3, -1, 2], [2, 5, -5], [-2, -3, 7]]) + >>> E, ER = mp.eig(A) + >>> print(mp.chop(A * ER[:,0] - E[0] * ER[:,0])) + [0.0] + [0.0] + [0.0] + >>> E, EL, ER = mp.eig(A,left = True, right = True) + >>> E, EL, ER = mp.eig_sort(E, EL, ER) + >>> mp.nprint(E) + [2.0, 4.0, 9.0] + >>> print(mp.chop(A * ER[:,0] - E[0] * ER[:,0])) + [0.0] + [0.0] + [0.0] + >>> print(mp.chop( EL[0,:] * A - EL[0,:] * E[0])) + [0.0 0.0 0.0] + + +See also [Stoer]_ and [Kresser]_. + + +The symmetric eigenvalue problem +................................ + +The routines ``eigsy`` and ``eighe`` solve the (ordinary) eigenvalue problem +for a real symmetric or complex hermitian square matrix *A*. +``eigh`` is an unified interface for this two functions calling either +``eigsy`` or ``eighe`` depending on whether *A* is real or complex. + +Given *A*, an orthogonal (*A* real) or unitary matrix *Q* (*A* complex) is +calculated which diagonalizes A: + +.. math :: + + Q' A Q = \operatorname{diag}(E), \quad Q Q' = Q' Q = 1 + +Here diag(*E*) a is diagonal matrix whose diagonal is *E*. +' denotes the hermitian transpose (i.e. ordinary transposition and +complex conjugation). + +The columns of *Q* are the eigenvectors of *A* and *E* contains the eigenvalues: + +.. code :: + + A Q[:,i] = E[i] Q[:,i] + +Examples:: + + >>> from mpmath import mp + >>> A = mp.matrix([[3, 2], [2, 0]]) + >>> E = mp.eigsy(A, eigvals_only = True) + >>> print(E) + [-1.0] + [ 4.0] + >>> A = mp.matrix([[1, 2], [2, 3]]) + >>> E, Q = mp.eigsy(A) # alternative: E, Q = mp.eigh(A) + >>> print(mp.chop(A * Q[:,0] - E[0] * Q[:,0])) + [0.0] + [0.0] + >>> A = mp.matrix([[1, 2 + 5j], [2 - 5j, 3]]) + >>> E, Q = mp.eighe(A) # alternative: E, Q = mp.eigh(A) + >>> print(mp.chop(A * Q[:,0] - E[0] * Q[:,0])) + [0.0] + [0.0] + + +See also [Golub]_, [GolubWelsch]_, [Stoer]_ and [Stroud]_. + + +Determinant +........... + +The determinant of a square matrix is computed by the +function ``det``:: + + >>> from mpmath import mp + >>> A = mp.matrix([[7, 2], [1.5, 3]]) + >>> print(mp.det(A)) + 18.0 + + +Interval and double-precision matrices +-------------------------------------- + +The ``iv.matrix`` and ``fp.matrix`` classes convert inputs +to intervals and Python floating-point numbers respectively. + +Interval matrices can be used to perform linear algebra operations +with rigorous error tracking:: + + >>> a = iv.matrix([['0.1','0.3','1.0'], + ... ['7.1','5.5','4.8'], + ... ['3.2','4.4','5.6']]) + >>> + >>> b = iv.matrix(['4','0.6','0.5']) + >>> c = iv.lu_solve(a, b) + >>> print(c) + [ [5.2582327113062393041, 5.2582327113062749951]] + [[-13.155049396267856583, -13.155049396267821167]] + [ [7.4206915477497212555, 7.4206915477497310922]] + >>> print(a*c) + [ [3.9999999999999866773, 4.0000000000000133227]] + [[0.59999999999972430942, 0.60000000000027142733]] + [[0.49999999999982236432, 0.50000000000018474111]] + +Matrix functions +---------------- + +.. autofunction :: mpmath.expm +.. autofunction :: mpmath.cosm +.. autofunction :: mpmath.sinm +.. autofunction :: mpmath.sqrtm +.. autofunction :: mpmath.logm +.. autofunction :: mpmath.powm diff --git a/docs/plots/ai.png b/docs/plots/ai.png new file mode 100644 index 0000000000000000000000000000000000000000..96762ce15a054bf8db922d37dbab1eae59069ee8 GIT binary patch literal 26596 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 P@ROBNdQ&A~6!8B5qiZSz literal 0 HcmV?d00001 diff --git a/docs/plots/ai.py b/docs/plots/ai.py new file mode 100644 index 0000000..7d57015 --- /dev/null +++ b/docs/plots/ai.py @@ -0,0 +1,5 @@ +# Airy function Ai(x), Ai'(x) and int_0^x Ai(t) dt on the real line +f = airyai +f_diff = lambda z: airyai(z, derivative=1) +f_int = lambda z: airyai(z, derivative=-1) +plot([f, f_diff, f_int], [-10,5]) diff --git a/docs/plots/ai_c.png b/docs/plots/ai_c.png new file mode 100644 index 0000000000000000000000000000000000000000..5963ed08695378bd5492263f02c7a374eb656510 GIT binary patch literal 61953 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 uu}uOpQdETq6QWqs%2K*1w%QDgc literal 0 HcmV?d00001 diff --git a/docs/plots/ai_c.py b/docs/plots/ai_c.py new file mode 100644 index 0000000..f6d3a02 --- /dev/null +++ b/docs/plots/ai_c.py @@ -0,0 +1,2 @@ +# Airy function Ai(z) in the complex plane +cplot(airyai, [-8,8], [-8,8], points=50000) diff --git a/docs/plots/ber.png b/docs/plots/ber.png new file mode 100644 index 0000000000000000000000000000000000000000..2646482bd3b438ca1c0fcc392088d13efaf085ca GIT binary patch literal 19451 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

t0OkCv9b7u>cUIvb_&r>PEIhzXnT8k zC=|M@!2vUj7P)?iwOky*Hut%@7x-gW3xI`f9L!c5e_ql#{Ny@4;&u&y4sjn|u(PRE zM@{uWvwW%y{S%jTPg%{$`D4WDEEX$#upTxI`3-pST%U0`@=x7gKc128h%}!FFjZ<^ zUGVuLreKkS!G#y7-bF^i8DI+tzy$!J>e$(5`*K2gYNw(4`avs_xg+5NxnL7XZiJec zJ6^$nOk}mTA_DX05(v-l6BC_+f~xwS3l%^Ph~4&M?JG;7#;F=|(NW~ywJ(d!)N^&d zNT&Lm_;*?7%wBqLFvxSz5vZg%Apty!)tLMrHp-UOir<3O-Pf&san%(l&MY4cx`M)>cugNaw XugWD~ZAbw0E*VFd+ldE12jKn&^PNG= literal 0 HcmV?d00001 diff --git a/docs/plots/ber.py b/docs/plots/ber.py new file mode 100644 index 0000000..64e146e --- /dev/null +++ b/docs/plots/ber.py @@ -0,0 +1,6 @@ +# Kelvin functions ber_n(x) and bei_n(x) on the real line for n=0,2 +f0 = lambda x: ber(0,x) +f1 = lambda x: bei(0,x) +f2 = lambda x: ber(2,x) +f3 = lambda x: bei(2,x) +plot([f0,f1,f2,f3],[0,10],[-10,10]) diff --git a/docs/plots/besseli.png b/docs/plots/besseli.png new file mode 100644 index 0000000000000000000000000000000000000000..1aae4b354db9ed3226e9ad3ef394b764662d2c98 GIT binary patch literal 19101 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 literal 0 HcmV?d00001 diff --git a/docs/plots/besselj.py b/docs/plots/besselj.py new file mode 100644 index 0000000..18b89e1 --- /dev/null +++ b/docs/plots/besselj.py @@ -0,0 +1,6 @@ +# Bessel function J_n(x) on the real line for n=0,1,2,3 +j0 = lambda x: besselj(0,x) +j1 = lambda x: besselj(1,x) +j2 = lambda x: besselj(2,x) +j3 = lambda x: besselj(3,x) +plot([j0,j1,j2,j3],[0,14]) diff --git a/docs/plots/besselj_c.png b/docs/plots/besselj_c.png new file mode 100644 index 0000000000000000000000000000000000000000..507634b31c3e56e9bda1c6819d846731a39f519d GIT binary patch literal 37422 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 literal 0 HcmV?d00001 diff --git a/docs/plots/besselj_c.py b/docs/plots/besselj_c.py new file mode 100644 index 0000000..c27f02d --- /dev/null +++ b/docs/plots/besselj_c.py @@ -0,0 +1,2 @@ +# Bessel function J_n(z) in the complex plane +cplot(lambda z: besselj(1,z), [-8,8], [-8,8], points=50000) diff --git a/docs/plots/besselk.png b/docs/plots/besselk.png new file mode 100644 index 0000000000000000000000000000000000000000..19501d1fe0d4e88d5041e314b04fb3a50999ed41 GIT binary patch literal 15252 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 literal 0 HcmV?d00001 diff --git a/docs/plots/besselk_c.py b/docs/plots/besselk_c.py new file mode 100644 index 0000000..e28429c --- /dev/null +++ b/docs/plots/besselk_c.py @@ -0,0 +1,2 @@ +# Modified Bessel function of 2nd kind K_n(z) in the complex plane +cplot(lambda z: besselk(1,z), [-8,8], [-8,8], points=50000) diff --git a/docs/plots/bessely.png b/docs/plots/bessely.png new file mode 100644 index 0000000000000000000000000000000000000000..0766d7c50e7725cce302c1c21688a083896b05c9 GIT binary patch literal 19658 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 literal 0 HcmV?d00001 diff --git a/docs/plots/bessely_c.py b/docs/plots/bessely_c.py new file mode 100644 index 0000000..adb58b5 --- /dev/null +++ b/docs/plots/bessely_c.py @@ -0,0 +1,2 @@ +# Bessel function of 2nd kind Y_n(z) in the complex plane +cplot(lambda z: bessely(1,z), [-8,8], [-8,8], points=50000) diff --git a/docs/plots/bi.png b/docs/plots/bi.png new file mode 100644 index 0000000000000000000000000000000000000000..b8800d7fdc48c7f7a89a95f3644e8201a91f04b1 GIT binary patch literal 25211 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≈Z`jX|JhTy(=+_ioBZ9JAaTQo$gLr@ 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 literal 0 HcmV?d00001 diff --git a/docs/plots/bi.py b/docs/plots/bi.py new file mode 100644 index 0000000..6ca5914 --- /dev/null +++ b/docs/plots/bi.py @@ -0,0 +1,5 @@ +# Airy function Bi(x), Bi'(x) and int_0^x Bi(t) dt on the real line +f = airybi +f_diff = lambda z: airybi(z, derivative=1) +f_int = lambda z: airybi(z, derivative=-1) +plot([f, f_diff, f_int], [-10,2], [-1,2]) diff --git a/docs/plots/bi_c.png b/docs/plots/bi_c.png new file mode 100644 index 0000000000000000000000000000000000000000..cbd17badae2117be6b05bdc0eee8f32596cc466c GIT binary patch literal 67342 zcmc$F1ydZ~(>3l+aEIXT?jg9_;_eU#?zXTH+#z^ikzm2y77Y%II|O$pSm5RNuX;Yi zJ5^Itb8GHYb=SS!r~91yO+!r{6O9xN1_lOGQQ?yo3=FLO`;CQy^gaSsh);QcAb7|q z>Y%(|fhg9|@9(H?3I-l9Fi0o=-LNX$`ARS_)G&&lq;-6APrFoqv5y#BJOn+9O+ zD5$^qc&N67?Pu3l9_KdvHU0G+_v)6KmWOU}elaX??Vn*RQIUk+{5G^<5#}pq51#&NY)DJ z_I-eRn2eOS@Zi6R8`D{^;{W>yG=y>ZznOx3wo+67P3-)^4e$To-*zzLNv8jsM(%3= z|L<38*v|+Xa}W0|Jrk~P_pUd^l265Jo0~WCA-D39r`p-?)=1cJvmWXXY`#ovUdHx3 z#J+4(z0#O0ehmnC?w2U}&k}9IhVWpyz&%#8ZT*lt{l}A@S0|nHlamvwd*+FE0}JZ2 zVE@HD>`1;^1^@L4$uxURH`_ijdlLzHZV4HidKsU(ypepn5jp_;S~NB@>mgi=q5UNq zy&x&py*IYHI-GcA?h({FVU^S4$Q!x34Y)RMKz0?C?6z-;&##u&VRR)+DDe}uF@|Y$ zW_Hj7-DpXjw4@EEZ0c3xws!RFT|cFGf^g@v77)@r}0#rm~*)o`nfGkrFaxU@sjtTs7PTT(yc%@`l-s5qjU)9So%G;WS0yc&DX#g065c2_QO&o$x(jQbzWBcyUGoCv{ z5gsmq7uN-^vmt?lkxxf4B-6j5&7Mlk{$RguVLw#%Jh8ssnZ2cR25zInyxgk4@tOTQ z)_+`C?7lyIro6s*2HpyGUtxAgh%&J>!ey~;V@CfL_MWN#V2g6DiNeii-swo-iisog?T99BFbln#pHz*lWVTaDyQwM9HF8!8(bB;^C! z>@ZUd+q1R#I`Ibi^>3Ur-0a6c(kK37!Q0@GAkMYB%OZW#+tDZrv5WM-0S|@x=VU@I zY3E8uHH95|?Tku3@ax)px1Wv#7-e2?#Ji@pIp4%%%^u*|d*oXJ{`iPWJeUOp`(eNJ zqvdDymRwcs4k+ETEkTz=RXXJKkbQ?~3LKJs40q2EJG-Rvn|?PSia8|b5O&!w=OBh$ zWxfq?zN};lPrIjqQu#a^Z=qWbDD}O+(T0&2oyeU?4YmQ623z34`d%eqJRunZ5duC0 zi3>Ll?eliXV?oFxrqhW)NuN;7?%{Fog%LV3gR8WNL>SI;)tLNtHNE9Xa|QTupQpFnc& zc)e<6imZV>W2lp+U_h%GCGQJIIOsHxnjuAvXP*_=oF>yp4FA zh?8J1BqOx_f+)BRM}$CzZ~}=yV7x-3^!!C`CbbmWqw~W5K!lTTZz|jAFg?Lr%ZgHf z_{I@|r3U-DhtdJRDUcE10nt95(t-# zmW;-m_O3_W2{13p_slWm+2q+6F(^Q$7?hX3MW?v50#42ls{y*S5;+whH|8H>tvT3_ z31cS&6J@NnQX%2Kf7gA&60!F5`>z6=t1r(pH-hdaO{YA}(GDzb;ClVxx7Im>z6J*S zUM1(bH!ok-$Ca5Hd=Ob<`i%H*(5ELUy_J?fS9PP!x~h`lPX@5uRsp?&r+2v=%j`S< z{vboizsISNZ%fQ8QjH1rqcza)^kT@gl)r*Q8yNpIb>acq6&hlw!>X2f%Vm+84q_$U zBS8l^dgW#^LWSEf+c2-cBYOrO2@fzQo#2|(lLROa(=NWG_un8Kxncf8zZ6vREjsjy zP?aJIHwi)Q4nX}&(dpCok}B7MxLF5!OQnxzGf<38hRV$d5``Tje3i%Qiu=^#G zdQ%V;%9ukP)ad5sR@?c!u7eJNVNret2c%kef>4bwC&fcHyeKlF8u*)gSf!zMP zjU9lNYBLHuv5o#%oL5-d`Eu%-m5$LLJ-I{+=qc;#*QEFp^ zo`oN##OoU_gW>-oagSE>zIfO#)xr?IkbSX7Gz`E#M_eL`4*0ed;O4XCjUw8*DvJ^A zqi|5@VW4Z&?fVbwx?7rq?Ihl&jqi5BjR(XbyJD4MCr&Mdx2F>(5*LdQVf(TyGywP% zS;@#-s5~C}+IqF*^Fs9j+!TB%?kz&FV#iH?@fUPsmE95S?_XG-_y99!1LXM)5Zy|d zXL*Frfk8EonYXw$b<`O45IWf!AZ|*{{9H)~5k{C!Lf&FAXY5$TQ}sYP$Wo1$`jwAW zLNjeQR?Jx_5H`rOrGfybR34aCRGK(_HdFiC(I44QvuF6hspf^AgAL#-yYu7DyrH5Q zc3jcV6+MdRqtqsnFKK;@p8d7iy?v4i5D6W9yPNyP$sV8#OwI`1u_l!`5io8iGyIEj zs_f>YsM--wD>`sKi)j;}^ro>PWnJq8G$JEJk8J^|VTki!uslh#=DfJB zmGyX)RM5-jz^-8pU~T5_zb9qjw+m!w(>j;f)r@xqc4zDj6X&sY>Q$Xu0UP$D1eL>! zt61p5Dp8418_uBo%<(Q7c*iZ3cyUiGZp4j4K>nKJ^YO8#rtT+py@}nDEE;m1D;nUk ztSjdNZZxBMb!*17vQRmaax#c9Wu_{A8e`~WBK;~E zT^7l|Bfw`E7jJPsRtGn~EjNwd6-yY_R0G7rfvL%@Ax0^x)VVL9r{r#$ZUF5h3h~ zX@h)qG`7O#P1wZ|5czJ0dQS_2A{3TRy{LG$F${X^bV8Zgu6A>GbL-ncGeUN4!&E;* ztnLEWi4LsOeL?re_D)=aU!ucPuf3SvWB^<;K-zz0Oj0Y$b~N%;(-;B;QOAqdY4F!` zUz`4Yn0q9!mBQwRe|nBa=72cqp0;kwPY>m4`uIN0SV^p%LcTiXz*7nb|7)t3@_{=* zXq0wyni(iQfk6#agas-+KBSB1vL2yLF53yn?8)mpitL+73~^V|8IhM;7FDJ->N%1) z*NMh4Fcy)pClX5=Wjf;!^AyKY<-Oi`-o87B4~lz;a!WGjLIk@OL4|J9O0SrO^~k@7=Uhez7}p(=WtiG`K_j9y6YbT-?gB5@c2;4=stOV9G5Ep( z=tJQi(?ZP`q8qD5a55Ko;6$u@wI!n^VP8wZwN}jdu7PE7OCNY}9da$>KrWQ}4>xSO zJ&x-SK?RVfK~5xu4PlYAh@eOT zy)&cg2zQ~Bl8;SE_<3ejwvuj{%6x9SNJZ-uEdt15(HMPGGyp@hO7X)0rv&49@S7wB zN-huB2#zs6_Y%HpQJ~fo(QpSBU+Tr)FAehP3?HoSW8VW8^6iOrZd0ZeF(me+;CfEd zVq37Pvp61>X77v7%2%3#%zr8SY-Dwi+UUWQRFJ zTomW%593*qGpmbJabf839*8-~4>DKeeX^*!A1x9-H!ZWy-P+sdUN{AMlMBs7A(ww4 zZqntL-yb>tZA5@*;+#+gE-PVi5MQCE%S{L8#*d(Nz%^!wiNFCrQMw=FmE(gaV2=I> zM`cNoi+z&p69y@%R?^ri3|F0mAD0*NyY+d@{Z8&p`K<^q566h<`KJwlK=`Llpv1OZ z(Y9V`^n+T(6U;iru~~Res9WR{>!l!$DeWsN*&7CtD-Obp)EqwYneVyidAk%&SW_ff zlrf1tEC#gf#ip%gi7~tbv5nx~=Q0@C6d$ZT+Td2(N7EJQ50$vW!JCuQ;bRMCih2&7r&I5FtrWID|UruNUh&1B2}cPUye?5RC4aJ=Cz;_ zZY)i@QRds|qh3<8LzH8E42Pd5P$fZol`dR2>T~KpdkS4o)6PYNBTCCkd|e7c8GEB5 z5+yCIpE3JhdQfB$ERxA#G$GN}GU(}dBpsNs53Bab5{-n)fNhM{=(qk6 zV8jYwEFZB2?e@(mX--kB4ZSBW_&DVA0jK5wvBy8$k53vNk~r;6A8KG8#itsc96UdJ zw*P`1@Bh8&R;vhW9;{p?7|#z@9lfU}$igVBd1Xh-mG7XFCN)a)v@slK@EBt9sUw)u471o>5OIl!_x0t!$ zQFq1W(hEp5EcoDMkds8yBL8e86pXSB_d-C*`ORN-E9EzRyy;~ee}VRx&)iM5&D8xc z>4U4`E(NkaI-sVv8Xw#b*D&VRO>kzdK`R}u5@fq_s60KuxsO454setXs zD4T;yBEPt$NUez>St3Tdz%uLc&0rytC6ZSf?h;4!AnFj>kirv)UQv_PBX&aM9ar~g z&Yts+K60}W+C6BlKGap~A~QZf@~0qTmz^7?m4C&Gz$9W(+(ZT^Esjmgz76Fxc^f&4 zJV621#RPf2Gfi*hZT}`w4vWBLij&#dg3$@f7{D@3DNF$YJ~_oL8KSjef?H0DEEJlQ&hd_vuz{*x&HEfr^c9TaH627)3)dAB08xYC%#rz86tN8sB z+_UGmj+Ry<#lmh*=Be;?*`BB~?tk;0Qm!!-glc+1+29&M!Ub;#w2{yD8-K9Q%ufuw zWStI<-BG22nt4xLor?C6(8*dt)@a51DDKd4wDts&=>on|8IQnox}5P}g;($zL{rx7 z{olegS6R}tg8>FY6l_(lcnX+H9zsNa2!Y?XZMnl5@_!zp7V?5r!x~1i8(+v-i|P7X z#RMEG+tK9Eqh)`yu!pwuB>}=#20?%HKWIc@I8ssHZUxZuEnfq4B$|l&>S-FaFe$N` zfWT}C`>6zc(Yr+16~7t|api+#0eaEOn{xCm9ch zaGyTB4h|$=sV`amp75=P+j=f9_w<(r@%KlTJ+L>ml>ab4Dob1g;ddvfDg*p{N9`UY zs9?n|=DCxaCyVK@Ep=JirTibn^!6jcZ$DMiJ(Zk!g3--5DMOMb+ewO)WuH>sTE0FgfY8!GC3a#%#D<-Qpa5e%5v}Tmp1;>HW~LxN_9LL@{N|21_I*7u zbw`QS{1%M~a}XFTc9RgyMApz_3QBJ;-(4{ZJV+`XVIq@Ix!Ej5BF>=W;7wF zen|57xy{29jb2ANhQM8A)EeXF>YN2D{qPw$$UW@3cNVT=s2yw=Y?$rU6Autuj6Zl< zY4{>me_4xy3ayqW3TxHrLmUW@!xs1D(yJpHy{3Tt6MFfryl$Uc_GO%M%ZD`?ey?50e}Qi0ySj(O#k4xz&+~Hq zh}{|D2^f&`bAfReEb{D1^SNDCJOIGwS0s($D=`qch-y+PQX^J|{8t7fsZ5VL5H95B zrU$Io(F!-Lr)kwU5~!j3POr@v-ELg?;mi^j1w|D#MFg?-%;=UP@}T@BMT9n-*CDm1 z2#b>|N+tb|^j&4A>(Z0pIq~H3e9?reIgcOt0b=_6we**sJ8iZ3%~I{C_W0VxuLJ|4 za@Yn*hHhkA>~8wuMiBL?$m!mCf*P_ZpX`7ys_KmLY{=vRYynlLZl|-fMyJ=`A8VNL?$ z3FJRD>}j6=^MK(k458K(h10FeJ=shqD8r|?VY;Tm&NizWsfRaGZ)`e7G6@BCWrWB$F}6{ zG@?)>ifGD0biYlK;+31&D{5!xD2dp+E6QA#b>K(f52WI$Auq&;F) z3B;IP+vSh=YiILC;fP3E`#nm+0>u8_p3dEP;qUUP=(g5JV9o)0Sm(+=05gyqDXI+8fE<8u{UIe zMPbi=OuB?&_F=u7-hE<;kok*>)Phv1yla8cPz!(K6$0k3e-!Qd@$9UQ_w_O{K+%L_ z{fl{fr3B0ZOtDLa(hT{YaU#)|ui#zN=Lopt)P%wfzKA=-jrqJ)cO!pQdS>xaHF!>8 z_aFJnVvEOZ^kM#$`FVGpO;^qm#r;`^8mdSdY-HPgka+Gu zri4t(Ho96Hc}5tD1Z~H4NmEb{-sT)!}YQUiv^?@L7(U8GQ)r5Nu=3*@+4TZrnkJivRfyX?n8jI@3g$KP- zELOZ=N=3892v1{@N68(xMnR#d6u|zxtswMF{iw-}Cn6Tn!Ufgx3ecI+G!kQY7|Nb} z;J+oD&**DfalnDpY`Prq5T{wgNwZOkYNcGS@)J++_cZ#5Fhm$&&_CF;2NkIqOui9p z?XM>oChQOUt%>EEPv@KKNi0&8%Y+Xg5w;MrSSLMM$g&MHXvyNJJd#kKogd4pN=7yY zqM3BNW@e|Q$uY_XDV0;Z$U!mXRTkJu77_#DB+lM30REutasKKMwpmv9o^ijLse~)Q z^cnu3L;w4!>5X@PMoERM`Hd{1NpKF&s-ho#P_!r7As(L8%GMIj~_=rirVZ8NY&Lq;;Zp{_)yiF_F>KhRs*~N zSSZNfZpkl0x9U6rM_ji@+^9<+{Gl4?AHpmf;8lu_rzVtUf<6nMpBfwf;iVw(N8SZbE&K@k1LL4C=$6G zN|F`JffMflz<)fJ14S*W`~aisG({2q-;waQY%$j@6o-*j)h#jDCh7R}XPB@m%2%hX^pyETvLlvvzfKmXVFyujmNyIcRBvE%}BKWc~R2G^i_%Klqz8paaB z2BAaEny&?fVkfc)VI~IHD(yqp-z)PpT%ZQq%jz2yU0;PacUp<{yFI_I0Pv< z8Cag;?HF3mn1`zh$V+_k_i+c=pQOT+9y^#j{}kvk0y^iz_ZW*S5g!La&KYMO@RV-I48M!R@K z9^QR6b(ivqn?|4Jr9S)XH3@|MvJt_slLORLR+5M=(zCz%FKX@HsM&-x%h+>t5H;J# zZ8LJeBYfHM8ig$jF$R(=bBq!_+rw zpXFi>j~};w-RD^SEV2RLxMTUqd}b{(1Y`{>((Ht(;K`4*NB1;eH?A<|nvIKo&4?EK zSO3O5G)O-4c0W$IUQdc%T<(sf9;dH-c!<^;rVE#1OuU=X2X8EYkI~DcER0@5?T+oz zTu4NDC50-nbaEZKJu6U=Qji@;mVYL3#VcNi{cKY!hzBOo@v0JB)U0Ne@f=L#aBD6X zp@khRE0r_k^DJrR5077JR@m~N<}#v?0Bf}^71yO|RNKKh6oHV(7 z(G)(rKGpsCS3;s+#2WD21u-+h6JVFl_*s&|q3w!kQoIe<#~;+XUlLLf^cQxuKiT$| zSWu1eTSM&=GS>yVe|@OjRP-0l|I-4fd|HWK4sFZeGdQJTuUAj9b*&SP>Z)th&xnoR zqgGVB7Y;soy?Dzncy24$`Hzq8xijh+Z0WvZmw0=e(l?rOn_LK&NQwH9 zC?=*obJP@-St5qC?_Q#R+$KRJ954pv34kfbXvmvW5)mFFu<=6ha`90449ovsAYKDN zBx;TdL>fe60RD9ZBsunQL*?=KB)lw_W)U{cAb!uRw=pGB+G_MxxYPFCj_}6&e@RPK zKaiT_!^=>e1T2!tUaV0My+XalZMN32Ui45`F^*hJy}<|fI5k8MV=XALy2pNHM4!4M z+#*3_DC#q>E(x!)uSX9Q-Ysu^sc(XpgFRokIX6qcWinyXW*3sEW$nN&=bPIl$?8ob z$EN9dJ|j3UGb*%XX2hoNnX67l2#^oD^-U|nc_scp7EAsemW7YZPFG9sn!dnJ%TBAB zo!s$NQChO-J;3NMDqx;yt&0ce_+M&1%)R$J^#>B)9uhsoC}Dl?Pf0k?>tqRZ?#=1Jboo{*u%7Wjfk)^VMU(-e6dm+7IN}YK7(Tp&0jT|L`Yl` z<-fz1JMA&QxWneb4UHkq9=H>s{b`3}xrkhEM))lBTzcgLYhf<(9ngYAO%dWZfRZsS$jr3Ama-}@{9$Eb9;CG6}#;DcQJaf=~JGo z5X2q``h!{=5C>MYTc*^S+*C{w`&LPAqBqZAM1?wc4}}^Va_E&>%VfU9<3dxUC_Y^> zjeg@Jn&i4;O(AH)P>!9Z{)2OZ@p^u7xpDKp>lL1ipVH&hh>>xC$fF-? zc&g+OW2>J|4`BO+VlEtc(q^t<8o58smKtbAGpQnd1y&5((dRLMV@Q_h>N7dEGDcD< zufgc$vI_7~L7A-T8TLZl0^5g>zY2#2k;*Fe+|tA2h*O27u|?-CqgPljC3xr5#_>)e z$lHWV_SvWN98juqv%YR1w#dUU{=T?873~{SLW{L@e+ob0Fc#}y;Jk!4Wwd>Uu1=K>SM1gG z4VY7wTnT~X_hiyC2?`f}b+vxQAs3w-9pSL9a@@>}G5%M@b_eo%T>wUY{&3lG)~d_z*V2>fgo zO}tv!sEB;I*4-h_I*557T+>zd88$sx>i~T2d9N#<>QYeN!nWy*vK%K|73~VRlcckE zqc@81(HvG(p6fD<`z552twhcmqJEjiAH&}g9rSJKogvgORO9^`OKu(#u~hcGSy}sF z@*|+2G`Q(MiqN8s=XPk?Ca0K4*2VUt!dNYngc;dg11yq)=%@F#>nARA3|geQ-Hc)t zDfYe91B}D}d{yMDLYJ9IIq9#pQPiZdfE%KXZM9Ow+lG4p?OX|1U+Q)&G7AW_sZV~& z%hvT9i4PX~>9o?W2Kxc(yoxj=45y0daHj(H)tjk6Dh91h`f1GCT%}W)5H)^~6cHdy zugY&KKSVezUp_Cts%mnjFldu%U|CP5)&043sQi~vh=M0K5q0tvpKz4#u{(jl z(pIf0@@oIX=yOKeK0U)kYCsE7<*Cr*D4v^E7v zRu)Iz&Tyld$oW+JI#4gV3%)~63uwm`FhD7t;3fvSp?oSTfA`i8{1C$ghjlj0vpyUX z60{G2h{r|Wheu2mfo->_eOjk=8q%URg;MvWBT0m*l)T~otzY&<(*N_*=GU4cn};5r zc6_vcZ^Q&$%5}QmRAWE(X4pr+?%dL+qk^M&N!=6iHTcnmCQ6J`mz$&L62Jsn=|+z9 z6|;?Tq|liIGeBj4{F9^8JSI%@*YU(=T@%Id+i61PUzjUGv@Q$NC2l35?Nc?L$782s z0*0MOy%paYPyQ%IKynt~oT#QZ`tKDg#Dt}Uiv04@?ofBsjU!l%?Y~kQn}>SUNnOU= zUv40e9!dPPyN4BzXzY%zox7S{R3sfoM~cJA?91a16ck`{ixw&p^0!(t+Gg+A9;z`| zBCGW=P$W{YqjyO(PmIppnu92-JEmA9b9ilKVmJwZ=bLWjspVTLfpB1;XCh$)e8eUn z4c(CX9v_g~%(kxee7vfUu=aga;3FXC1%OxOvHaKhtIE5+kP$1dO3Ql7(J$~mftRyv@NO}m3qhGBba~8!qyg+u&?%(NzYYNBSSJrNZT6H6=sr(O8pD ziLmW3CJ_>SB7HK$AGfl$#=8nEMi6 z65^qs7OF^I=Z=|S;x~SAB-2!)x5?5SGToMlI)uXp2AhRoR=b@a0+a;1(-t*q6G@J) z_!lyu18Ah#2iKBa<@>`Uy!_Qx28w~GHDx-_2^ z?>}4D3uL3ijA>P2P2}J9;hFFPh~+4d@6uDwC%4EiNkJ})9JK((0j82d8#W@9LXM=3 zVhcoz#Ql{ocZw#JlG6`PU*07jjSfhaFbiewE~#f&@o4f>H`rpPH@|7yd{EFZkItPC z9S{Xj)3}B1k9}Paq5`SURTz-%L|L7u7>c3J6&gmG63X%O0n~V-QF4j%4-T}#bd&2GM|9l$nTY@ zgv=DBZI>{tm(B2k3MZNe%o!gM30_*|wj>C)9~sF5jN~!;yIg{{SUD`oBiH6bGdy_W zd(6TUyg=#t{NC1r?RTffYw*vi1eFCX#f&mR_v2`mh%DP0CCo#<=1i+v3tOguleh5Xy|(&VjAUKMa-7x@n2d@&aB~9uS>%qWtMcF63oCMKO!w*)(+s0*f7~tyQ z#kE8&9&Ir96Ts2Um@Hx^8ob}6+t!Wo@5S! zw2%|JoBgGvcez-WC11UH$N~9X8UqATJfP2&9q@tgXxA>;jAuZgNI^c_xKg-O_gNuw z_>N~W4dUy~I#q8tJ!%v-%=&sim!u||wf@+kcF25^gyG21WS<14%q_ecDAhFyAMAh#+(xSuGi`F$plTBkij(ngZifJ56(I& zlQHPas7#Lj5luL&AanUoQ5I6Q>ck8A2@n4`W^GsXUH+0oo?_)C9#RZlIp0>^##T;+4#5i zkU+R}IG5)Zw!4IllGYnX5ImQGt`q1Mo1>l|;6V8p(7#&6tlNPh4SlEc{3mA|Ex4DCbwGwi@O7#Uo-nyXBQ7 z@_J{N(L1R$n$h6KW1va4ZH`wZI8@6$hC5gK3np(-fYo6KGJxR79)gg?=Yy3} zc*d%LtoU&xiNho@CSFgr{l1KmD(CpS{uJBo(f`;p%C=C3+1R+y8bsHwWpiCt;R(!1 z{9`4F!9p|TqrcPEZ6Z|hcKLGtH6Ty+9IKytRayDRrk@oGDi0fbehvxPAJ^*{kQ-9w z1e)SY9qg1-a~n{oevp|wcFWdU%$51&P#8E?y5O0e^n#{JGvvEP5b8F41Q9|Pt^3GlPG5N`x>>V`9JnLPyqFL^0$Pu|XAWty15+oDX&IqXTAh)YhCO z7DqPW>Xq8K5DM+o4FMnc?3u)A|M>z(_fjm~hl|U9bJ-|`@x#{+Giy_qiC}lP6Mw%A z{vPNdE95H7b+DxRA0CL8&d3)xQ?#0CFzh9TCNE008~?T`bdC5^ICkO(H>Sq;?xNxT z5#wP;uBRg6<$iv19r?T^q$Bj64-U4~=6eNca4#Kpj zV7TDO_w2=6MT8uhd&Id-Dj5c-d3r`*I2M>!2RBN6tTjN$Kp5vLn=LO#uT5<~Ph|{h zkMu>iNDVxyr8!1SMi2jTS@qS~x0HyS;EUC+ZI^*T)bM*5h`hk{UUKgS%0oj8a8>imaFC1@B?7kG+Au znT0_AI!>te*;&~9;)jHHphKggns02HgiHc&m}0fX zT^l#(W-s!uNAkfD*nXf<-Xt=vMe`b556pd3%P)2AKm%hd!py7*(tu_2zZqIHE`Q^a zB=)k?)KDnMJ3J@kQjzedPBcZ}Sx5Mp;i_>7hh~W7>9R>_2=0vAWeGa0pu271cWy!G zO^CDtl0Ny@OalFMb9uz$R>AeTKGeT-Zl{ZQv2)AHdtM1x ze{Auv`dS!TvOhksfVK8VjfXJUC6P<9(Yo}evnL^;E2NYB;G3RM2_5K=b!GqO#xKnl zmg)1{uigD3Xbfm-?5b%{+%yJ9kf+w{`u7y9ik(wEG+252MFho$8jQU23JIubfZ8oN zzxzkGIS-BJMuD(cRdWaUA^%z$b~%I00+Wa#;LX4@Mcr!A#*dj@o~P)q;$NDqZc>w{ z-s<;=@;j$O0KvaKdcVmS^nurU0^MTKZqqdJ4}w(NEdj*2rg?4||8gvH^N9yc zX0u`04oxF!lQceHFf+j>BGe=9KOM_yy;oov)Ro-K6B2r!PW2=u*F^T z^*O->n6ZxDcwoGSHzDpK#$n&i-&9m8q z>IoLy5Ci(L*_~ugax)nPM_AyJ@JG%{<9)-uVNQ;{gGB5)kL5Bq1O7#&_IeR$SJjh9 zRd_7B%Y89=;d413?25COJ#o6{Z#i~Z>>%Ag46F`Na#qum{22$F8SH$|VNG~sF=RM- zF-EbnKbrWhj_^eKVMEsRXP*_UwBvo8(_4|&ft0WX04u}VOqu5lOG&yzv|wlUQ;OEl zOMIix=lMoHyNBM?4%zy24(=6vdrrY@q8R#F_js$Omil7|$Eg%Xu44Jean`Zr=#L^P zySkYl>!XJjQ&K^ea$?F|Ab zwa+K36Z%GGfE-B@Ekd6ahSx0dstG(*oQjFd%73MKo+T9#hgF^+Uy#xmnsR;?#HIt8WT>#5P_T~nX>6FTLdRarQmuvu${ zA573by3b{xnaQ3_^V1+z-HCubiLlx~HYM=LV%Mxz)810n2nmz7eAU^<6Sv(IRYKQ_ zQOZu&gi`*JOdjBjQiPy8YTfYTy6ibO!)F>78UPpNC|sdlD3E`mYie&2lDbO!kJbgf^^)6Cd60U z;UtUAow;&UDB4e{U0j7q|Ib#-S}d9jelsHZ>dfKane20P8^I(^+IoE`$3j_gUsGl* zqL-?Ql<&R~ny-qPD=5gZCtqP{?#&9!;1sJCVk%K-v&&QcKp#nzz*G|HUv|=iGV5}d zobgBO4VwFW6k^l}pm4!f-;$cj$0vh(#yAern~8#RKov7U;BbZ0vz#2O5jiK(K)zir z@(MR#`UBn646LWxXoES3hy*s_bHs0#0M4ORz|)Uz)`>*a>cmdMHa2+{6EwdPiG|Ir z9%?8AMd<+-C~G;SkdyhYqyzn*#2kG*{ZkzvUL|zX7p-tl6m9&K@QTE6TjD zQI3u+bEbV6ZcZ%UQE7A?uiQMD)kg{y=Y+I;ZsW!~Te-T=DU3Ni=Zj7LHdCX3LH!T6 zOH#~1D2>k5yPnNO`9UI4N^*(3&Shu(3<9OFeWcLq=x`2Y3C{SrB>|Fyv%;bsm}86H zX(m}rA2y|S7}AfV*{$Zds#-H`s-K9`XP4R92}uM+MtSWxj91?03%V{W$T-{|*=X1RwZ>210C6 zn0Lop;f6ZpCgt)=SB`YJ4lhTSkc*pzE1(y+|Ckw94(+`F?hQpZEFTm$-;jmM9P&S5 zA{j9so(g@~(}BtJD4Mq=aAB{LGAs-Bl!rNXPADp(8*={$AlQp*IYq&ER^P ziCqFq_xHja7Zqk|3P+}`F9nEP>@MS8Jtv>2ZGumaS{Ly%5t`*x zhkLm{tG&M2U8q_%jX*mlH$Bt_MAx>T-Q7!=D~z)|RbBYYiE5=B)2)fB3FFy5f-p0< zP(Bi(&@9#Sq$Xjojn~T5ayj-_{9vaevqw!2fK()to))c{6X~)nrakTnuK7eqd*qvn zO$mRcJ@5MdsOy*Rr+#cbAR2rT6lM0Z6Ou1Y!oZsD-vz7%c53oH$(K;47GQpqBu0y% z9>B8XLTsOr3ZZCQ`&5JSTMNo}4Q|4cZsc7fqFR`^W zw-nN%GHAZt6Bvhl@a^$Mm9_W3{H`y8u}ZVM^t3Sjn)y?NZ`;`FvW(q2%Ko%Bp;5uC zCm>6`pL6?O@JRA{NcFF{^WXjIoyc>_KFfWdi2!o&s2cb8ezy33`-IT8wUY8fU9m?0 z-}2w~vy8hkk#w&XMhkl4s`6{0AiGa&cP4oWPyeR{Fd{@V{J=+cYsV$XJAQ1y8tfwZ z*g?C(WMA($Q*;{yGM$k6@}bFFEp#nnLzgq#Uv2%Mm%|6k-V0Hj_1J>vWTz8!ne*-Uhjk6eW+Dj%_!NG=A?D*WB} z#it7GXf1%X(w6dvqMzvgc=3Y)6psR6r(LCk2&P?_YgtrKfUnrGKq~#Uo*n;A zn1wR20T}lQ1~>$hg#(_dC2%F7_oz}D-m{E6SrF<#LA)mR)ouw1a2lA&XShg*EDl`= z1Vv>QjXd(M>CLnu#iO4r&*Pdi z?1TV4@mW#0=v zo$paxp%A4j+;^G$UmN!7Pl}xMmGr(}k@{%h=;F)(k^SCfHFxd@ejAZ1m0Js zuG6RdkWrsBdajsymU7R-?fAaE`2NH0zJlt$3T2ufD?vXZTE=>=gD0eYRQ*GFO+dE} z4$Q&BVx~HrdtFvfWSvN4?}#-p$YGXZl9qixD1%=#?YO5SDeuZu+0cCxGCI4T3%z{Wg_{QFm9kD zOCI7Hl9*TI3CpG%-iNRsowQ_gi(FVlq+e_qK^JNt2k~F&8tv+f`U;{(;)H45mLy|q zB57Df!IF%Zm07*T*=xEY^wm`9x;6CUKQKVcMpa292`VXBwl7VUai^-ME-9dU=`>k7 zBR6#X_297KO4IDNF8j#2um(E8_*v)a~5878>i2*jPhJ%o;rAG;-&5uaXEL?*z7Lw*jRZ+m}Fhs zIPM>-bxV=6_6-_B$_Qo`tlC0^te_BiadGQCaTSJm0R-&w++@v)gB`c<*V%=OuhV?~ z(Y8J%N&x^Qy75OMNt<(3Ud;G_9plZCvvoz4EOEtLUS%F#I(U7AY+YZ7J?8Gb2wp~% zYJl#+EAQ!34NK8UkwRR;=DxuIDvq3+&VQ0oSu;{2TeU|Go08~30(}j75MmsmEHq{c zae|VY5>ja?&eojz=@ZJ+@wYOPN`h9mf30AD5}{T(R%t9-{dRd>(6LBeP$&v@^PYpq zJNl|4X#@|LQ&@lE2gzCqTaWYt{W3Pu()i3VTLQwkotZ2nqk=Oi8p2$R37<}-Dq0lV z;y{Xcna1FRO6Ol6ILp_3PcvO|pflvTkoWLx{w#x@_=-XXA%d)s}6Ys@>bK{Yh=o7*o6n`BS4hvR?DSQ z!&Op{lVzGD6^tubBZI7LHc=+2o&P*TN`-56ft{aFwBu36_JD{XAxgjp(ZBtGI3A{1 z(@}VrQ-B4!xKd}dD_;}vl2;T+eax|5b6>zZl29fS&H|j6nJ~na734w47+O*Nr~W>E z2pb!;2+VElfX@Nis7!o-3Lt=)VAYA-;SPVms!i*szrdA{SSG9o+!wY=Q0(grRcH!6 z7pI$Kfi_Wxk7cwkB`IZkR1U$U3X25_TuyeA2v;`2q&ZBpe%jc1TB47u`09lsBMG*Y zpu|0-2RjAEF7*3k@8{!N*wg`1k1R)biU#;z297DWmbz@Z6FC3CZJ(%Or_?P?+d=&* z5mfv4HrQeD0N=bVs25uyP9yeQ^R#v&4pi#!39NZ%D^$&Z9mOq^+u}z3trgei<2ny*63#QkMgLB}s6b#J)xcg&C}EO@7yogc99%mJTf z@C7Mn4useSHnH#&QRUHbz{pr&G%;c!m_j$n?yMXpXn!e^SJQ$e;($KOu5Pr)+UH z|7;(xAs>P7?keS$IZRSf5|a=^c16^6iFF`>FhQs;c|<>v$$jo)-j>jhT}GBf_IpNB z5mp=-R+5`hAg^~NQg$a`{*p52k*jhI7z)jDY0MJioffJslzyWUegJz2Is*J-o#O}}m?ws9X(8vsa>#B{{w165Ej}UwhwQ9Xd}YI6k({ZQgjU7A zTVANnfwxM;Z@1yCRlcyfyF7CzYHkB?ygt5lW+uDDKV>xUK$aht@eFir6vw2KH~iIFypWgOSjruB*p9A1sLPy?ieN4!G51hG}z zI&&-9XX%6ehFM?iCplHUJRMS*y|8zX`;?t|)-Q4VqK!i?+_L+!I=cJ-?EwT6NPg&j zEwL@7eZcm-up+YXQ;h_X^whs!RDR*QS3muF=be4PcG`t`h!Px{Y+W$V7ak}PA5db+ z`G~8dA4Jrmgr_ysoBG-_gJ)0|;5<5P&M(YaTG zzVZcs+H>&78%jED^j8AEG)&=|LkrjCvjAuk#5OCk@~@AnFg2+foz-<%J8P8u3%4_h zm0cUy$C5na_zDZ!FKXPWmpVFhuKk)e3WXn)O+B+vZ6@R0-j+TN{*=h+OzC)IQ`2i! z%eb(BaGGb0u*<}#^e_0HZJ0B}QMDYu0<@$3PPH@LrTDyT?hcDB;cP-O_Km&J2JOjp z`3mmwS;h0Z$21kY(B*r;%#J;*dhjjv6&cACFBg=`bGB1VAQjCGAy12up_R-K1;2d= zVSGP1ET@zMCLum0;hGz@oGiw5M#a#`$HmXd4gD!-%Smu=jU}C*;$29KJyzsW#Ca$o zdJ(<>lM9XX)p1z!?cBz6tDc*ye%w}`F;Z$%4Rw4`CycaS#U*r4nMd9RE?=4Z>%0{# z8*%?IgR;%HTRbSLIBJmkgBZ5DOu#N%$!DpB5=|#$-VB)vtYhtoOZ*!#YaBqNm}(60qfxG4!He?jlc;O%kZb;MEVF1`&{o!%YwmL zG|&m!P-loL?DgzWo&f|k4oXhB!88&BFq>yfC6O&NRsy^-Fz-`RjuAzT9aF?b`RTG) zy_AuC(kEm2?y?T4OpkGxv`e`6@4tsfBY(TKxWvLNa5Cld;dr$D#J5k?}FT{y6 z&sX@5`&3hJRJ}GZ2&xyK%aH>l0ct8&)O878;YOVq&_hX>xU4iKf|?>;jTu1NaSQIoNmW5B?#41W*&1YNOse<*(4j0Pb(8&- zx$g(S`FD#2+Wbxy`o3j(S@gzF z9GX>WbZEa*U4;A!kxMYJ6s@U}V~DIkl!3z5(DrHIR%@nPRMgsWU42G<$A~dU^inTqoQC+-CZpmVEfu$y+O2bfZjc{eJ7>4exHn@PMu$kO8 zO!|C#hPz1Z?DkhsS@uTVwK%Y{msC6_CJ~f~tQ8d6BhCf0gR}EJL^Pms+7Yv6C0tg7 z3tdJ`a%Gf|mgiRsL-EnolBaS97gVERl_@6+koY{UGKmNwFI}fdEWx6;g~cUEtlmh| zeLhs{VIrg$aP23GuUUH@2LlE82a%`mL=C;)@RuccrV|uu4#vtCWn^lklxBRNz@~0^ z`m5FDydB#4IS}WWcc~V?iem_pVkSe)gU-^F=hfPgrV0X-ninbGf!C+Rqw?D-kA|zr zn_bBMjQvn)T23;RXhB_kYKBmfkYFkH-y-hpPm`Byx<%)V_ak`gRz7(ZsK!C*S`61w z_Fdylj9KLDq8}eA6nX4r4E5sNCAxwmc)2RnJv5SxW$YY<19sK{si#sI;8Xfy6ymDj zE6F}328NSzBe6*n6Q&WmAqWpUK{(JNI5vIEHEMTNMU)hWn`$dY$b%n#+}PF(%T*`Z zRhSxTv*QkS<}{*5>|dKlgAkCSi(zKKwDa~60TA^mA0rNn{X)3*Ef|m~4MmoGK_y99 zSj50TW9`F6jodPFai;W5#Wv>Q&VfxoI*FW>m?;^xano`7)S4F0%@T<;11@r^bFi^s zc+hT2B%J5Nd5V-w(H`>&C)?|M1J<$U-H@`@@CeHpvD`H2UCKQSX;()R!)zOdBb^}* z!A}8UD%XW`x;afyuA#1BG>vkjg-znh4hzu`UM;*;Tlv}}lPbFeEO=cUQkG8mM^nw( zgbZ1Yzr`3V(^Mk(IwCEw*rLFAqN-2Ducytf#e^ND5U{}*O~x2)j{BjM{^89ST6txi zBj=(yL0A>1W(}aIL!?**KwJO(1j2X;S1j~(R{K`@gn33`d!)9DM`drc^Umz5?a2S! z*fc-q62u`G`(Ltwwg?9Nn5bCr1+8Pr(SiZ z_oj=*v#4@q7F=QrxL7-z{DIb|xZ^V?e7~m##2#9RTr|MQVOSeO0F~D4gjFcqku4cU zJ)xV?xIlGR`5n-U{OOEl91JP$hmxn<1C}lPcU;<`A0|12{c%zw2K>){GB&fuHFJ|B z=Z^E@N~O{a?V*v<=!IspKYylF{fk{C+kwtBP;Bs!ks+o+m393gMj-(r_oQ22*W7s} zIA>)5!x2UQx4tL=M07w6@eS|=t%T;-E~EEL!J?C?H1lHlHT{w*hru&b9II}m`kZY# z;i=wUx0yJ}qd=HkQx5glJ1xB9X}O}%nBTorK|%+_^E&7n7ZF2$-TjLtBX5)+rzLLs zKPQ9`kwnyHD43C0UHKDQ=MOBbPk+?w!VBK}nS881|2bIJ_Cvxc~If@m{YH39B(_%LZZSQBH zcXO0C9IsQ9O4*QF6P-DVVWqQizMUb;4q6yFU*<1EEG92lmbCRNPa4)bCO#Zv?2v3& z-jxOW%u}lAKv=VY;~ahs;SBzaPAL9D;Jv>Eg+&WmQM43t_S7HIFY<(H zSSQ;y&Ne*kN2B5^TBDL$6LZ^Rr|LB3JZa;a*|KG*OX&UN`*AjyPWZ@g7ZK-zhi2OC z2X$KLlSD>2-BLOVS4f>1O4dZSpDd0KaT_plS^vg8%Os5+P59xB@W)qSk&`GMRxZFB zP=@tjvA@TQa0zk2c?AuODCn8Q#qeqhMBzqQfbwmjLEvZ%I8$7_Ax&dE;K{I02A^Z?>x z%io^=BA%cn>dDg%vp8PohtvC2$i2oFvxgi{=BX_jxy@w3 z%yfh&!p+1g2NNqpr*C{{-3jYzX3!UBigL%qQpMA>gz@vDqjD5)2nCwTLA#>Z-i6~U zhY`hh7Wj6h%!CMe&21DVMKIGCGs_y@#);q;sD$aij%Zg;oGkXl>G0`2P3Jsj%pA)C zJulPfsv28IZ&EBY#)@;wQfZ~0LrTcC;wid{_JXW8w7^K^5Pz+MhC5KM3CGFJBSU#+^GAAPFU8or(n1zr`Z{2H2;{*?b7Hkk|QcD z)>2vUF=q{tZ7;+@Vj#-t$loiuh5(FP!cDezt>uTyGv7S{i?m=hEeu2>^KjdyA-rX4 zX6*Y9=9B*&CGcrwI35iy#=ZPt4-Ug}gluK4_e#B{d54aIU%$i^MxZV<+bt99d_gZ> zf45YHsECb!(%b)DN z?tdBTUcLRJ&Swu552M^m3K(FV!OXKUn_MBrU6fYrl645ZVE)*i7$mgZokgw^QRdG0 z1o?>FqTV2l=8Wh3jjQ5C<4qyNf9UK_ZG@k4+2_1R{F$tOo=7}?6^!d?+Qiic2#|vT zpu9u(WA!gCv)p6OVva-2SDxA89^uLeg;CAKj0k0?cby31xiYk#kpA9v

_f-Pks@ zBpqGvrH%U2=K2$iE$2>K8vCB%{#8Y8&&=6-*hybw$r|GFW{Urt*H5Bv?@6Ml8q*K@ zS65Z2fBrT+qpip7Q(LwnNN3eCE<2Y2>Z?i{p42Y{j4a1k)(CE=sGRL=;!qJfbpDFg z>bM`p#7St+uvXm8kbvCeUnfsCdXN@9Z_vRsrk){zDtGiH1x#O%1|LZL20pQBRJi1X zx7A>?to&Gy1(18}){ezgK?_Wwa+Si*O8$M6^^1FI@gM39m|C!tS#-m_{{*4KIqHT} zF}^?zA@NkYeplACf^8xvgf1MgFNyWa@1g<9?YrAFfW$H0#y#XcEWyhpqNXUi!Y$MI zdw3K5jl7Jev)uRbJgq+sbS~+?U3JU~$NPS}FtvnvN{+;eBg;^APg02_Oown-$Not^ z)X^_*ZxOx-z4aRAe*_G)|6V}(A0_Aic=0rWdE&)3#8Wt}+LTnlgfZ!UpQ=fRqJ19Nq9_Gn**yLadU`eI-{HP; z8$BAbohxGML-s(mV#m#uGN+d8`F^7o!2^cOX7lxQabq(W@9XSmY{`oI;&{n20o&`$ zsSBH-J8%jsw^PBH;dq7wfUa6H6X)<8Vs>kUII2TW(_QvDt1NWJO757CF6ilCSL2c=63DPowaRM_$aSC)0PUs2*uAy`2^!pjtFCEn>&J2d9_MIUGH>qL* z3J&`TXv}L=2S(1M16!@g3~@KAC+Y)Sz}UZ{YvO>!DEXg&-LiIdB9B0Tz3*0n4liG? zC-?uzwQJ%}N58ire}Vuezw2mOAyx=Hxh&jD7(fF&c6PI~b9cs=h+%$>#x{V?;^)8l zym+Mv)RmMBg6bM2`CnOzlOQrn%4nl!hS(s+qbXyTgU*!Gf_ATIcg!0Fy)!)p4^Mfl zxo=(L^z>?6@HoH6^8@chq&a&pUVdE$EOJy7Hgmyem7dXTKupt>?&urENs7ep!gMQ*?dmHl`(2tDO{0a&%S$&5qbkR}C(RlVntv>n5Lkx~s*!l; zaTf7Y^*l|!%8tf-X7$jl~33v#;ll`(Ji#R7UNBEkuZU^ zaxZm}poH}WA$WU(ODiwj@%&UIn0HNwc(sCKj+*C7rWt2X^vV@ik}Qi3Cq(*VsumQC zJ1c9znOBo`9#n<(E$yjG$8srVfbv-I=IaPv{`|Pudhf7l)%n=fe~Z2c4m2LU3HDt1 z=f9*7KaI27`<5`ac`uV}3gwf=Y!;S0_Es4BDWMMLoQ z&S&0)Z!R2V&aRN0QEWkK&BS!Ux8uwaibp0-x=Qds)N!64s9b zw=opTNkrGxIy6AfzS01f<_?t3bx-Bs@Z_m;h1*SfN(i8=`e_dH-s>M4b?;z z#5wF2)?D@5Wnwe6gLLCC`w34Xx|xi)1bmcvjSM{+zHsbCsw$;v+@Yogrjh_dOD_y} zv-+N%zSctjGVOCL?fFKd8?krJTtWY1=!NtPQtUQ=Tc3I-`1}&Ekas@~GTmT*^y_b5 ze!lmh^d5dqyS?ps#~F23?4C%!Bte9hNKq=^P&-3de!*HP)A0p)=GTR+E{r=M231|w zsQ+`ixQgwAl~rS;1%|9@7dUHtDR?9H>mRiuZya~1_41k-LWH_`MJINTCsJJ~RMAOx8PY#Jmt8Z)IW z?Tdwj+okq0sp07>e$gfBdBS?N>K*)iWoPnBIy135_Cp za$SC=v()9@b~x-AvwQ~{e;AEvSz=&wT`4%oxSq@98H2mP5bn5LuW2I0!Nh95;Y%-& zdLKuvqxQmxF0=HzTAQ>OpfsZm2FM&sVpyrh$iaIrbedBolR`113!$SFfcBJ&i2v@mJF;04z$?WtpEhXyb>&{Li5t`Jdu^px zxQ!EFxZ(eRMl^M!)jX2ni1C7vLl1X9L%P+AUzT74X;R0e=zd7Xd?^ZrQ%d|)Mkpsmzu*(sxp8M#4)+6 zS~8i|`o;RAwVYf}TN!Mcah}P#V+eSibL{CA3t}d)`6)efLSv21_U0&BNm0FbxhN%BPwp z7gJdh)0nw5^&Ezuoq$szYzQz>YK2;7kIwWIu5?TR0sSdSfb& zh%;pl1t8-V!5<0iBAT!gkJ9O9(XfKIiNv!LcZ{HWJH}dT2AGR+#(FUlx!8uQr8GHE z)KfM(H+PD!BqEI?g9y{*N{1zmcqRr(tev+5BBR{ZmY2^jG4|2= z^=25Rg(I%YS~GBH@TNPcZFt5`IxyN$xe{@?SXbIH$sqclkrgwEZ5!~Syqd@WHBq`v`+BSjFUBMfaKbDLH7snQ%+T$)2rU9C1j~WFXu6{2 zB8+hvJ!we&DqS(BG6roKgLXw&JvJImmT-=rGbtgzb?*NP=!&O;dFP&s* zah3WhAe&}VwQ`IeH1ZSy#APoNHmS7(_<>)8Hpf2`#p$jxIabT=7Y1=MB8^^N#_| z|BD5o%DmvXtc=ipZmiV{PL(B0YdeT6SzznCOcZ|7f|2%|)BEIwd~#I*Ly>G5VRQxo zT(PxmoU&e;*SB*SVQUDB-F|;N|FawfqoIuED55}${b@$zRH-=bb@`1!zJ$~SH|o~> zVDl8Y(!a3JZjjVxQ$$(kZHOz<^|o|X`|;nLlnNK1kqz3 zq)ckPHYqN)l7B)5->)Bi$Q;R%zjKo}%@uNg3F|m3&1q=8ppJQX^KNZzglu2GlQ5>< zebXXHi$-erb}Q4eN!v&2{!LZARqbi?=EI%l_7DheYs)Bzi2omHm8goVN{!$gbzdXU zfda6F`je~{MZp*#A8K_ds)GT8z)9gr+Gb}A!`O{woy8Iv_^HpSw-i@Va?j? zLg21uGYH;g*Zd^R(7^SX3VVlqyh(g|z$_jJnVnIv_>Ht}5Y>Pxkbfo>vKWl5U&=tl zYGi3#>@Z+dvl=K%GsT{Ex!tPsqt z%V#R#@ROPD$Q$gVOV1DI>vvZY7ZywNsF%bHoS?d@tXi+=S=?Uu2TLqw5&JnZ>!?A( z{PT9GzmE#zm0dU^Et< zd|6WM__gJ1j!B{GM zw%=W7zc@!e7W`@^@CGBH4JdVF%&xu zC7X6ZS@)AFy>OXjJH4008j4WLC_x_iBxaobQb~sIdknNy3#_?83-^_{Xhj91qMkMp zLzyYqc?VisbVaIHw!wt`pqd+kO(NE^L#coME!^lM&qb4%!0!JFTvS8zmRCKO1dNO2 zri?AU4zbA~Iz3SySvHa=RtH1~iIkVLeyX8ORosp$N~4)10HBf$Qw!-nF>= zT1mUsNPR{aVzAVv`cF(QVpBrSpLpKlFxT@kr`$nQ9E>=MIEFv(HpS!MC#q80N|5(3 z07h&FPL3)L|CVv{w_AsUU&jMH%BMW;T`TwiY!I`MJ>hJl7Y7u7Bs0(}2NrYEw9*x! zTHKV$;KrKjFN(M|rJUMqX6S)Z@5I*}Yt7eg40iqjAoZzhh_iL@I_LQUwtg9V(NdWo z$;Bhj|F+ijplTw1RKZL^j^3piGm64ZK_yxHAmesb^Wi&0Gm|&!8br=gLxolNBNdwv zHoqC(`8qYI;t<=#Rxga4XnGSSF+RS9P;Wj2NqE70LwL+Q- zSN_FZNwA|}PV2cBQ19IzKjJMO0KIWYw@c~ArAhUMr6xr{!Uf+c9qOZ9vP;vX{-k|S zFsq|zzhzmePf4fJYA1^OOCJb4l?!IRu*#MEDSi|c(OS{hsa?ENa&9 zC9adT7=L+r9?%1{=QmfGt9gpem zpe>P!y*yl&EFGLh7S~=I`(K*Qs|9)0(`c+k*v(v{aL(G)&$!cO|LD=+8V33>uvpm)uefR-zk>)8`?!R52_Ao}B}mn!nP8 z0bmT(8$Trvn}pDNxX9NYqK)~C=W_Z!OxO8FVQ1KVb!?6KFO}y8Kfc@1*|Zyfc9HmE zm+8>$zq-~5X8rF(ej$>Jy8)5Q-@N+eKUt9@D^#z^ZlOYK`Qvo!^%rRTmaqcEg)7@8 zc1mAr^1Pwt>9Oh1HHREDD<;X7O55Puh^+df?y2YT29>fB&!+fe^O6-m^LaAo0+Q&m zj@0<7N87_~@+R?cT{6_zWbbuzSd16jxvD8w{J5E=w7ZE;{ulTR~KGFX8LDn8BcR?PeBsv+N_|1InjR7cVqwC zzno(D4+?$j=Oj=Qq^bsXuNaMnUD_BW!ATK|5M8*8pNys7<7obtxA5>lmsWjN#-A6^ zp*9_%k9;4WaZ11Us>K((irF-vB_saUIM4B87U5lD*#&;~x^t1~g$uo=P6JhhME$naAXYmbE#-)Gjmz3kk z?SJeTjm2wF^CnYPeS&EelJStROhLY<~)kbM{ZV>cPqn?YGb>WT_9bUQ)tL%j_P8 zLKBGgWr7i~>V?hWRRWlX1l_Z-h~~JPg2zD^P6N7M>be$&-dvm%)ARKSR-vlY*w)52 zW`Va73A6PJq;mFaHI2jZ$_^_sUXu^ znw1`e^(w|%OkaNV_Itbw0V<1D6s)0*{{?P?HmuGy!aQ6RWpwSmFRO`$(}EbGN-~a- zPBq-_oOS?9G4xGrtE|&12A9{6y~?I`bs$eBy`ZJAy+5YKjc7EDH=UY`7`MShnMkuV zUgeVL%3HWv#X$-@&wcMc6lqiEq?==1uV?fjVKULi;Z<AIUveq9xOGC20fa=Y6 zlZSjQk6Bqtz{kgpUaPh9)%%q>WKC8 zE%(#;=F^$Oy5SnBDbD^2gS6af`hZ3`Gm)kcRS~r=)AUd4~*T?Cx^yds)R{%b1xZ z4#XioVJ?u|K?J`YAi3c^V9w2o7dIch*tC5Ab~I*FyFg) zU4QS~8as%8d71`)VfsAi87F=b^^eGXU&%FXUAwmKd9ePnIK9O`|K~!uef9sm{M^jt zGQT_P@lqQt&{!NUn-gh9rh*w+*{{OT>QbzvN`Xku;WLn-d;3B18n2#kn1T<}G$OnJ zyONjYJd_79f$MiZNhYb0eIy}H7oq*5fKqv?R=S<^7W@u;*&s0VWHOW?!jftjTnuV< zR+F%Lmg%EW3qQj>p*6uh!QEo>=OKD2pVK1uh1PYc8YQ8SeTW0Z4)ho4OZ!aWkW-B; z?ZH5+Q`U)TN5>XmVmHtE?5*2SF8g8xVbqR3x~@FMK2uBZ7rx5+$HR#ezXdbZJlpF5 z`?{{$B{-6SU62YECIvR z2^q*actO6-r$K5?(V~cxL~!}-wI_|u+F{rvoZsV=Z37AzCjcC08C{NI_d|5+;a{OL5#+2)6Y0e2f!r=d>UJo;FpRAtoK z-gdQVcXlvDwlxX0C62X;1H9wKi@oMqsQ3=1$ zdGLpR*5q$?7~}veDPQ=N2&$mPa6o7gCNW@QbC`f&T&LkeTK9>V$)qY{3su-1?2Gu$ zttOsV?<#2^`8iI@Sb2&4*-np$#TB0bJ#6m6lX!zv>azQXD$FJZ`$)~NrpR457ydf5 zUl?(EVO24@#X?)GoTtd}0ZhHF!m61c{m+;#eaf}d+83p^eE*L106lh5Ro`U?bx=`; z|NFf4SjLdN@|;%HQw_p0{Y3A?wrX^ifkbc3tr1aFS7Nmbvey6ov?q2dV}_=k>Wol&E+@r}fFK4gdXB#f;u-)u73!cWOFY?@&D_@W z8dIbo#aD3GN)!MgvI^xwoCpZ94PeYe(-rs5DR4{RfCl=&%~6)lI}Vc@qgDqy_(?`8 zkO+ici9`&6Tco8f>@ut6&f|5-yHMOO%HI7dW4D73^ugV!5Us^v#9V>#&i1_$h=`41 zJUY~VJF0%qq!E@$KNdu*=pN?F~t5z~^1BIc@Fy6`=9p>gkDe-+{N~r~2Pg{gm)KH1`YZxpn_Q{*TxC zTr12|o6wt%IE>9sRagSuMGV~8B3n`vI$+6f1$}gg_-I5j)!pqto|sap=_h#f{|%F_ zrprB{G~fOKT~dkcyFTCwgN?c(7IAU(17&{#pG{u~7J`agah;GT}#G1oh)M#o#TjXx|7T4S1W|o9(b$OfzqINz? zDY8g~KQhG;W3>ykfa*2f$cMMG#CpYQ83ZKg(H&iMANl3tF6wC6YJqwC5-yCk7Z+U0 zeD~=YA$+}bC44-qUATqF!rl`_0`=UbeV~2wKd61f{0~0wo;OAbhwy)x`p_cc_xf8| z+daDVqT`S74O3jj?Eok_XNDM}TUb6Jr}vptxCiQeEl42PK^dWAZeQj; zox3N4p$SoFYdVEg{p=11*t1&z7J={`Orh)*5@g%OMfbKCo`xM zX{S9SO+Z7u+OM$%72i2w;@x~&ysz2Hxmq=vvoq`vyt4uLL4?zTowqjl_fd6`e#a-R zq8sYmyJWq-=i8Hv;Z|wcCB)rpA5d-npylgrivJS4!sngBt;$yq(E0Il`pxH%HFxL8 z@8anH&r5&x?YZ+UKh5HRtmcLlOGz%NT zgSc5brTD!4g84lACu$LHs)alb4Gk#!Ndo(>q<-K--gA>*I?T7uybbWJe11FHdWV0( zO^Ao_V8~knYyWHoIOq^|4!VTldEU`S;N~G&pWI)E>)ni`lkP9X*P9^{ z>8I=pl>G%B)3~N2*7f?EhVz#Wi}s0kggJ+t?aw|M8>hA^2fp>fzT;c@P{+1%Z)9DAJLd#(8$^`ZUinz$UzVdPkSAAx?L zS+R(wx(b21Dj^RPp|#K+*T4T5n8$8_uX$Y&9|qT#(g=sHz8}1pobJ6k7vXm>N=vN) z)v|d##AWyVU+CMmC?ZJk*8BtUYnrm)SnbD2ZIbihZw(+!qY}*VQF6Av&x9UU)=)0{5#kwD)S+l3Lwa-+NBno@a(x<^&ZH=a#0+hTk!x zxJcx*oMo`w5+xtj`$Ii_6PIsXd+{ICJIT2rL-9Mx)!weP(~m7Swada}a51Q1NLddK z&QO=StiJb2RG*Fm#~t?0j$C<`4NHj&X!1Vbc8(@&nopt=26Q7BqzOhto}XJDAFI~C zdXD@Gv z_u?3o3;C3sM<#0wADA;!y(TEm)po_YPA8-RL$^=`fXd3X!{^Xxf29CM#|c&O>{ z^{kJ*ZRt4TK6&r=9zVPvtXo$&u&0B`J-kf4?_Tf1%>Vbu@I!VCHg#zu=<3DUlf;Er zpef+vFO@*w*2kLvtGC}9mEf~<_sc@<4a`R`@ph&DJ8kaUJ{8z>=24da!DpCoq`gnj z)6?FS*mfq^vy9{;F)=IuGpg6KtI-oypm_uVYW)j-|F@rg5{YsWZc%3`4p1AXGEY0L z?QhGRAmUu9c;vcIE*t&e4?G|Otan0epB)Ac4;TD8EFZdu#nUfF~Da-640A1Sb#trwp&Lmbok;tTQI24VTZcTx2=LM!m{&6($cm|A75Uyj% z;1%|mF@H_CHFSC{WwKbWj@Y>zHf`SxZCLBDXRo~19pVN*@!h|B`=p~MQ@|&fdP`y- zO4+G{|5sruk(G4OBhi=^=TkUL( zR>3@6^LagB@(*S5Kj`_^KUb=-b+q(6gG;O}1o76@^Z595p#Nd|O)NzJZBYNMH~#&< z2Kx5sV?B3I!|y=DlXkPryK#lH@lzVYm}NWOwa!ob#wdf50U=myB|A%6Uy7U1j(9Q%Wu zK46g>efCrr|4)6-UTALz*k;@?rx$RmZTojA;p-J`SEn#j&%xz^Gq_}x?uRA{%Wipw z?NpNICg-a28T7dJx5a)A#+}Xwa@wp8-ix_)jmx$R0b}i@)-_d`zV{BkSQHB}jo$99 z2R#L2cF){*w~o#W=iGOK+}HIZu&1=1tJ;gFEP;&wir?p5?g#qki~i^7<#9bWHuhWN zSLL$-=#AA38dZ;99 z!M)IIqcH|XUj;coGBovw0g?U^!Qn@E3z5P^_i`b;h|o9ebo928_|RQheCd+MADD3lLkkqhZ^UNo>27rD{PEfrks!s}Oa|g|=n6Vz_n`uV zMOSHA_9$<#f9KIw?LdS$jvMF+t2AS@_mHa=ig>yLy0*MC#@FHYbqu-(R2N@+-~1{A zi7|#_W3K}Z-t?BPiC$4ShJ629tSSGG+qX>rTYHZ?5GvWtd$C6vDR_Nj<0A-CS z-)Y+)H0=Dlq2-1>IWm|af_kE9Cd7rArN1>HZ?qwtDNiL1ewanrExpCmQv(PkPZ*K4 zLLQKYS85X1XTnvJ;gt%$w8P0lK3JarkEU<_tMvW84wG%WCS$T)lZ}a!ZQHhO<77A4 zJYlk@sZO?=>N)Sv^ZNd9{{#EJu6^yj*V=1Qucx+z(-Cp!MN?DCB`Mr1v8{;9#vu?T z2~D&Om`WK!@1P&RrQb&+`L+x;z;OSGQ1>%wm)`=5YlX6xb1+sg5!2i}+qW_0sY@q7 z_Nsc-Un6^!T(|3%jr|%r#P33{Ew?lW`T9`ici6|f{FQrM674=%noC#A2?QU*(g1~Iy0*5s%>BKTntd_~Pyf9}m1RGDed?_Xc8w1@0 zg05(%)OE$^UOJ(2X~#J&!>7l+Ix{)i* zsd&PbPCo;CysdCeiaTFFBw+J>j5~~3IES2cgCeyaO+oibeUIiFrY8Qg$)`qX9<^(l`j$k*P|e zeTDP8h0HGetvGz-3=7O79T5OJit8Tn3kKyn@ec$^$`#vRv`$|qM5~aBY-xJsj9bvK z881!>9DY+R#M-Sb^#!~3_tZORi#qlmX@MyciLiY*8}ev6Z6`e!jrl}9-6~(P5tfnl zOqMWDAyY54WMw?(WQfOZ72xhBuvrs}<)Z?3Q5c|OlA`yVKqrGJP=6nn)2ckEDIPOqd9 z?wD6JS{~AJVcT`~XH;0n4Lg|7vD^h7Wpj0_B8JOOZadj~>mZj)-bFyCP%b!N- zEW;T8?tH2|NA8b!fc)35#46YibN)A9)ZKh_7v@8+)^nTvn{x59pxQ3nMrC}`!GRV* z-1WJ#@25YA>!(P2&`1963kmo&4md3Qh?WrT4v)oQ)hr-au-T?vPP0WrR&WD|;#L$y zR}7REh3YO$BD$eOmZReaB%g7S=4ZVwZ=F;=mkNy_T4ncm9aV)bC}H=4C#@I-@BlWN zesbT6mEBb(!VS{~>yyW6SU05ca_=RmFU$==N=7MLZr(LPEY=RRnWr^esei3j;?76N zdy$UhuQ1=uXtQDD8Y~Fb8*E~jn<=`*GX$@svW9b_2&fh~1Pd%&&3bZUfGeRAI=8>9 zuFQg)(KBIPG`d;jV{ot;%k;Xi){?yb^ET*do1G z*0?W$F>n7$1N>R20cYd5^Zn1U{XZ(C|EbrLi{tUTAZEV`$8hDJwFLmcRV!=~K#OEP zU&INL3CBQ3;NW-*Z6nNMipEmQ*CGhr7eJ7V2ULhriitawS-|4ah^{{Eli9#hd#&Z#@omLQ8nl- zdLng_gXD%jeuDVt_?pXI-`!kbi<7+&Yeh~Vl!6fBM9^nG#MA1rZ{du6~0kQ;oGrGGJbo#SOud2+134;>wb8)Yp!%P{}ua*jK^UxYh93f9pWk z`am~#A#&08J8FOcc=uq6bo}$R{O9Z3ZI75jV2GFC*wSH1B#qW}X8yP+c`L8Fc3gz9|3O7(nG0)Y>tjbHQV{WWzIuC}>PHS@4=_lUWzrEpL#ow%srMqCFOk$GDnW z`^oqZnMR$dSS_^8-Onjn9}jlxPz!9)=_+A2)dj?q#W5~3DOvr|4(4z*%*bFq8B zUR|eDeML*f>`MK_1LI0p(T;|1TDFvo79=VpNl+k|n%JW=4_kO4Jq1~ad|EZkF({uBrrQpvu{J`i!cT6kgblQXo z@0V9F`7p2^2LqRsm>%*DzBc^EV?M{pTvPy3b9LB+jnTO1zHT0(NO3dLE*+WHLJFX( zdRFd;3S`ZH7n-l;-Zh78^UldMZAbe03Ag1lSxsm$7&aL21Spyc_ly`kmn)a@YAtT8 z4keDx$+@VSSb+3b;f3w4t^wICFYWRclBY#g|Co;BdycN$AbsrKK)PQE1@?=%?*ArD zN3=AqvSc*i?!cRa_~n_ETldYnQM*KVc{6a^Pe55`)uMq<<@>KWF)fQUmcXaEAm{b_ zmrb$VPoZMfxx=>u^@81K##`j)_m&h#&%=F-DkDA3kJ7jkhKuT&{b+z<7;L27wn^aKQ8S} zfQe#!J5R1uT41SN6#S_hn}a)&{*QQOJrd`lPCKd=Bxo$v5+hs#!;2<26RfrDqHEK8 ze=M>aAa|v5NwVmjFl$zR*5bVmdz|629GGxeS@rm%Eg|@a+X=eiMz)-NW1UQdht`Hr zDXcWkF5MfVB_Xe^CeZs%A-yE4tw-f^Q(e>m0IRgO*@IM3wY>Xu^kK0p{yOO&ykV%D z)pj!R$^8j-Z7rmjEDXP`9ua2ULq0|`TyR!FZLgAnn;yjB%wWtD`By#r;(9y=LBXweJ^ML8QK`vOy@cttky09b?*}!_ zK~&frxa(hd2WDl#)kihXXcu5ag}629a!;irTB{4gLLVF98}Vhp)%woqV4BQ?* zeXOl;z-eW!>uJIY?>^GzY(HIMnn zEc)s767+wSqP?l6#dw%(-=_~*-=V{e{5-1c?-e@pjS=w6{b?3?yK!eNJ!S1wUm^tu zg_OdC&@KL>v|E}TU_P1!i%NN}xz>Ri&+xY;323Bs)d5xwQw7$gCkN{rZ5g)hu|4|V z=}8X~uDiPvdMdKNu z=vv@xhq}c?AfC8EomAQAFlfkFuKqgb3gKB=RRyFp`dthNOPk$_L}yl^Wi8+ISti8) zx$V1niD`8~5$yvm=%BhOm(W^8+!M+YrJ*p$qgI>JHmVzE0#<(ud#S|;aF-5crG1kL zzAGz`U)&&!kEy?k@7J!6;)o-{o9>SE;iWpNYgs!0)PEIFM9H#T4~VB)mh>;&sR!L} z@ICs+boIWMR{gcyq4dABf6J=}3#&RwA>ho{mw@-Ztrp_bqJA-2Ex=1$*|gDE!EmTm%1@D#fuvnB$0<6otS1PPBodrlLKcbZDT2iUeh#`dnu! zI9{!^6irJ2#L`q$hV{)AAUfACZnOdwYw z-VG7yCMWN)=$z;-<&SMnTT%2R%NOMYf4M=8|NfYE?#OHX0M_RQz6c3sL#P`u{dlvQ zt2zI7PUb@d%ta!!q+hF45oR{D3;Jhw=mmZF1ohG~Aoz);ZSLR9DRMrEKJ>dmr2h#I>c`R<4?rB`h&LtL>@h7R}4U3yV#X-~lSEO!Nuo)UIf{=Wi0K z^R)kHYn!z^OdIHPYF1NQ`;ku$5c$tQ$ozvWO>a+G7&#RAWl-K<@>{zUv!fQN0F(gi zjG9OGC3cH*^TVFwxCO**K`+twk>XL`c0}=qpg$+p7N}6_%W9lHiC)V%z>5ta6YHiz z&Q_Jw3wbAk>ct_8@{&r1KPUSh(^>>4+88XegLPZ*Q z^c?7$yFdy6g52~1!b5~C|MdPu6>ycI+B-6wm<<_O;a;%Di8k67Ws9~m6WQ7vFU&ur zmoAcAQ-~Hn)Enr^`^y0vc|`bck!|y{RrAa7S%8-H4lc6=K9Bo1dzzQGo6j zg7qK$Tls&imVs|*PjMm_L}lKWerG-Z%i5!%5nZEAbO`8DVsp%>ZzB`)gW?vP%3|A| z91yy?c`)$h19L)FlaCQI1%^UU{m383*`fP3YPOdyO?0I4o{TuUlY*1kdOF5|RTnlLgEPr+KZ-DQhJSERsM1kUd+qh5#5t(> z94+{1-{ckG_hvQTWTXDg*YJWL`W0FmTirzp8xB4{W2P%_16TjBSjXY*;izPMM$1Bq zfgo^nAHAFkx)F9i#y&~je_`mezHALF*b=KpJpc7H)~p#BmA0hy$eoB>gIR0{cQKzy z#zGXEw^obBKF9+fPsNhWR&`Mb_P898F@KZzg1c(|Bm}~S_vGW zqgt$ofn%Dp135;tq~*$*93@#cTu<78+DY+1`cJ#)2E=<-S}%O0SrQPDA1_lL&KYJ~ z`b0|&^tYan4U~2YWvL*=bE@+mBkk+G5lIiFT2~mOM^OG|RVAhJ+@#hJR%809(FVLI z{2iD)wp*1Hz{2p{w^=v3bb5DZ^S`1cM3<~{td0DBNUcV|2=_azTR@}0bk~f)CXqoK zFL6Fic%7x#1jG^{H4|S8LHoyj>`EXDqHZ)Iwx}&fw=y7vt-k^+h0x!QtuG|+?0Xie zck!i!1EyEB+=9(E#&vhSW9bJ;iV;Ct+rWnA9lMaDD>6bz0<3vt%PGdeUXd`l5A~t9 zu1jO!6QYFrWY?3_cgn7M)Q=p0U~%v7^B|(HJJq*JdnDX?d$@mC=o2Xgj}#Kjz}~|6 z)WewFC~meifO5`Jo5d=Gx)?Be=nHJAyUAHzoL;CJwOpxE0;ayi9p($Pd7Wx2-w0~= zKb(P#b3YZE{!Z0=Dfj;O_H@C~;r{~sFZ~%ps=KH_l9;+Pv*V-0=%(ASR7`wzPTp*j zky1_EtIbqlisp-zC}23|drQ}ZKAzIC#&!$m46+H5%8dU>W>&idQLIOtv+N3O6`W8G zy+KhIpHjgSyjcUv)8(;{+5BlvQtM(^-HhBQ#~Fm1F{ySfX+UrWxd?f)S%Yf6jkQ=X zLh84O=n&Y7$hVM;FH5bxAXGDL5~)CaiQknmphxMrWC6WPzI8le;6lca5Lp#RxnkyI zYm`w}VoOn1*YBZgzKm>L9#$?`O{zTC_6+7rqQXks7Xhlwe7|=6-0D+j&d{sd+M$|# zZv*YViRv4eSbqp;&f~OICQjy8gXSLXZpXcy!AX%-eSUFm%I1Qrb)zGLkAmxo_pNtv znKSk)dSx*zJTqQ5!|{^WJdHc^A<%O6H(WL35bS(@PN8D14d_&ddZtCrf&onL6;%1r z!i!B~lQryJQBe1?7LOnsJ2>#%_yZ@{>e z5k%*Og|-Ld^0-4<_G#;ci?Bl*gFPXwT8nBmE)E-hWnu`lQ`tZcKYqAo1yr+N_bMVM zN2$JC#kU!&cTXGrc_Gi)Zt>s?+X1EaQRoXFvEDM%feUn15$OY{qOuI2+3B-ZY=x$o5>gF`WV@M%87oI;NxM{&!IRtA2+M;yir*w;gm%@M-&q;vp+VWFclSEg z7cWf_r;LIVyECjT$tkQdUrurV#>1OazAt>gRWR{=hqwc$dnNN5HZ=&du-)QL2qt2V zve5FevSSW>Wh__G02OM@$IJu(?qjDM9Aqh26N*Mv%rQPHPLFTIi*(F6S zpLra-DH`}$pFNVgu@Xnw#QYIs`$3%I*K$J#r+jn(fgiL#V4Pv>?@(J}47uY(t;^}4dx1AbMiN;(YLB?MjQ*LJvW8C308wz@`~;dx z0Npk0pL(S8Ovr7e3GO01xo-4D7#JYIjCEaEdiBX(pX-jB@w^grm~gh-B{t49zbrfE zlt9w^-L1pg8@(b6?|S>S?`<)hhxlBFLH%Rsm5~BZxsDIG2|IHxa4_*Ky+jY)pae0r=%G-p{u8Jr;P05<4 zA-dW@oz$*GIQYkrSWp~57&q~Lb!O~38BLz*X;xoE zI}=*)PP_6?q>;MG?O_XQW4R?%M>)-vP%M=V9#Gosfu5Vf{eu-pOIGMPC&~q4JJ<|7 zvZU#<@emz`9)hlULcdHtVBqg;pnP#O(&nOfS2#)dCgie|3@t>}(7B-fPY5eeVs00{ z2VrPfE~_1qC3KiCxgByn2TV480}|2HjY>zh>7}Y=MwsCQ8@SL3Wbvi&hPUJ!)w&2j z4qK z{F&XtPeC6aPyKT|{V%iY{{64D{Zl-BHz;7J_ai1ZHy6rsV%ar8EfdZDLY761PqVg* zKDpK4oH)$>G}TH8>TKfu=DrJ}*u>i8L-yDrFFkuZU!OYSwiMQC{b zbenzZ#%ahL`k*9dg>uO@I@=t3?2bN;AVj~H<+L`39Bjp0RFg%Ld6zuJq$gJ8!_Rv7 zk>_K0h8KK0$$RKj-zH zZx82gv1$V+OP+RSN41>j#G81@2lh@#FS#=GO>@{1I*2a_lDNm$;8Ie`wEn1a@J%J* zwgupH#Wbpq>P>og@n|ly3bac8Ib!fZjEDx;3XtOb=q=nS!ps`78=AW>Rm=NUi)WOL zCrdcsBb`x~%$$(Ew{tg`epg>*l}s{0#gGU_BPr3cdYv7ME+5$xCZNpFr&Ugf7Xy|{ zCVOsL1I>DNZ@iuce*H?gif4W1*$f;z!wWvqjMP`0&_bCyGvf;;+N@~j@a$NjGH_=EgbW)WI8L5d4h=QA5$zR(AqW8Z{4Vy6n9FAW=wEa}r-Ve3(EYdx5^ zDcKgo7phCd+uO%mces|k%Dpcg>nJd~c zXyvKzNl5tP+4R3J;9@l({(nZ#wm;tpA#!6GKWaaZ`XzQD97T5siCaunr+N&;Hi4|D zk@Za&6ZE((4B~@5>%dz0*e51Zas_r*`WcsNJ3bfGR?btM*fv!hPV||;3~pA(ih|!* zFa{jF8Q@tEHEq{?H#y0cudp~FNbCEpPm)v+ms;B0#zlOahKLWn9PKzk8rR#bR|ae% zorV1Xbjg#IUzmerMhFX3v&zsAwjDmXyGaZ`{Fho2^>9v_ioaZeflrRlBA$8y*9$m* zOwP=&5o5zxmii1C0N4h#V*GEjJL#OvX`FFo4wNwRZ?&6)#pj4QfC5^LaYA&vy*%YK zF?P;yrEo(^BuXTH*jtsBJ#7^^kh(R0e}3Yel5U&+@||mMYM7k>Zyu5zW7#z1OZJ(h zkihT2OUr)X+~@gR@Ic|e#LwS}w~~di;1-SJtTOPm^5q~^x!|2gKCo772tC)(L6?J< z9X`Gn%8T|_sv%w_q_`MHQ?gR=EdAqYSVRX)*hSk6!0de3T1(#E@00g0F@f>n$b1eFuJAOF$Q4YQo#VtZ=46{W3 zB9z5MO~q-d2`WlHHZL^iL}Ua51OQaChFkT7OS9-Ulv$PDv6v4u5451$Q2(ml$LEdj z#6Bp=DSTpV$8T_FZzsgBdHx^L^WIOX$goE&dQzEl#cyW`)Hp!h0YCDSWNK6BqlK2n zv%qz&Q*?lyyP52G2v9!z2pZLe#8Nwf*gma=+whJ)OYz6Sp8*=iZuo@rQo6aPFYnlA z`|H2bbx68~UwcmfYiKe%<)lTW;8h94H34@F;&r_NG?cORK#Fth8wleR#C|QY+|P5q z(RKP6zWF*mj5Mj$R(yAyZEx7fmpLvpU5$B`AGr(CB#XHr4jjB+=YoN{ELLTv;Zsvf zx7Bb$Qjsl|_G!qdGGMkyBePxZH7IsNq6$v?}d`^?@3NbBqG#NVdeJ~(XQ&jLX zE1h0n=j#ySkQ_$CJ`&PD;c4?etY@_E&zt^6a!)c(tBpNj0l{HFt4l=$|E>6r1d;_~ zPBwukXn{CTNW}kf0b1#fNW{>p&gboHIUgf%eN7phv{FcHDUk_vqQ%zR_fQ}RO=ZuJ z$2YDYuz@&J=D<>hl;(V~Y~-)ZVR}}l^kId;w|5HzuWQ^XKl*yR?vPKKfk7Vwn%J#@ z7y{W`6|r7qsF5uP$O-61$H^6|x7DK*^+qLM9RFJ5MxtL%( zZV2wm>7%romPw3{3>C%2LiIy_i%$UHl&^Bx9WGa<4Yd(=h1^z3JH`&%0M1MZV+DOR zNlRRx@h>83e!BDLw%GG8=&eU6Ys#>W`A?EXw0YXi?k~=*Rd#_vNq(Hfi4695V16>3Tq7f~Nwk4^e$|z79&ei6Ku?Udv?r~1oxy)GNu|6v=mP_>KOhh5;1fxxF^5q1 zxkh+okkO^71;zDsktCuYg{_+PUHLkFfJQs{UUR4?B_%Dr-!PPXg~@Qy%`M0aNvXB z(i;BIXUzruuXEIYLF7?pBjC?|nd6o{3@d?|Px>zFY~q;JmT9{+V-HX&DJQkUTm-~4 z!ot_apOjc25vZcJR7IAYsUJ!7Aqht^xPOHT!h!E$aAegC$6{xWAh z5`o+rpR6y|km_aB7}A~DG=RVEeewugYULahTNm>Tf{Ci^ZW_eCTXA$0<3ZGuVxj!A zxYt9JrWd9xeVAH$z46Bn3viR{jzp|j9;ruUjS0cGC!6kpo>ml8Io(7%nK0l`Q8F95 z<7<--gAhKlcj|SfvO^9~&FFr)zS{WuPAGqI%|2IX2nZeUgg}e82k!|PsS5^54 z6@N_L!s0=A1g#^5JWmq(vq$&uTMGJe*;;TerP}Cq(VbCwHOTrgSOccJE2Mx+CdMAO zKC+4$x|`~>xogcsG!_oN7a)-DGv6{L*21Y(4ds2PH7>UV9SR3upO*>}m_{wWDXt{J zeA0SuL8wUSs4wWiSPybw%zA+8)aZ;le@(WsH8)#0(#SB_Q8z9ZAf2pPnNVR7p#W%_w9IMh ze1sJ(0PWIw`ZTn;aliu=U@BCr<}9ZXu%=7K(!$`{r8@%R&zGTn43~c%lP}L0HprG4 zu*#2h!?)f){~N4hx+JL##6TkzF^B%WcfkO^`S-?;dxE1ShH4hu-^>&>7SHC{SXFND zO4U*&Tzv8$f>!oLIuLcyk3-94nFt>Qe^fVVqFH?Maq`VaPe3KZ0=t=XS*(@u%juOC z#&#eShbVPb<`++FVhkYNwdRV0n>iuHH~xWykP+6yN;1~FO?J3DSc8y~kj%TIwmh$K z58J*PGQwJhblLfV>dF<2g6qrGOXA?^K zNoa=Ga9o@qg;MEp7N()`E9F6$d?h!$pQwYRlKyo02R2`CNj;`}bxi0O<|hfGgl{c{hnSh3o5 zmDg{mbj&}bl{BkSG=qCEMBxwVQhglGUQgvo(&I|k54q!^9(>mZ-%+v^>!i?&61Qx1 z)%jH&=;Cz9V^X z*zKYVDVJeacp)2VT4=G`jQi})F1s?PO&U26J=FT+um2`)>F8&M~r%=7lrl))(@O_JrrDFY?4(XuVC4mI&vxxvC}J zK4P<#_&pdBEf}&Dx>f2TpCW+ygiM!7=tWPWz2QuQrA^qGq_K3jqIqXgQLsD!fIVh9a2S^DrG$SCdSSyq6yTYYh8h6oVjd! zo$4tz`4w}AHX%0<>;dq?i57yRVYV^%}d$XPA@*vB)_1b)O5V#(Hg=9ST_EQnrDjfIu3&`dgA%9?Vv z`EiodYA@`cW>5BjG&fzTRI%^b>HKq&CPnqr?{=6`;JAFt$c*Waxoa&WU9@KK)RymX z&hebioB}GR^rCyzy<)lc0>>wQu{bjiZBxfJi!Y8*)Z5 zm8>ilqF=2idC@0pyY)zRihGIerI;c5SU6I*@GJ>}927=O#S;+t*2Yv-;o7P}Af|Xp zR70C7+=#*~R~FBi1)JTes0_Zm^e90dcP$~dwI2kNA|x>NQ^>%J*~f2u)AfSXo1P$; zJjnM!c_$)?qS)oS#wI77jr|)OkyjH@-|TgwE-^JB;<^yFc?!iL>>TJYSEPjk!x-0; zHwUw1VY2*f zl8eBClv)U}BA>E=Esd_|Hm6KXM$_O|;DCOSz8%V4!4py^f>Rx;)sjYQ*0M8757w_M z{-IzP20U`TTGBL}99-^GYD@cPU-Q79fwLytcVT*aBqW~t>^BUEWRPUh*J|OJn4L?} zj0pOjF~G+$-C5U!NJ(C5v%;j_4eE{1lJ8Ops7HXj$}lAa*%NB~EKL_q{Bg*5=#Wfh zT@b!Nd+S}$v2R*S#CnB;aazFvpOR0Ce{qYRNOz^5MvJ#;Ikm_2jiH5CeMRy5N9gy}*vB5M%6Ps1knuEutKi*Ic3;Dj@UES9CoPs#X za6vIFKGZvRkm*RkS2PJ}jr9*Mk*Yh2erb>E!#`BJv7#ZAsXBOH*eE^8dKH%UXznO9 z8gg?lzPy`{YL}5CGT|a0dAzGGBR95Ty#0~y@MIWXgCjV>@?Tk^q{JF3B-b{_SSWs( zbp^ofCiI!>nc5BLvvUcGVo+`{Cv^sO4)9BPW2F$U&Ih0#Hgg#P<+6`Q!xeq?u2}6m z=#yBK<`YGA_`LHv-QVqCUO-;|<@M>sbBN&jX@~G7Yyr5|BikOzw%D0RZ+fP3o2H^>2iM=c55+G8|o5buYp~TKr zk8>!2^R8|6|;0_PKAr^-UUmP)TTP|*{ETy@ae9X2aTtf*M? zvN%K)Ca~gjP|iR`;U}BMis(l{Z8$k1kD@<0QVz>vX2J)%Uka&1igoI=(o1jUZYwda zv3z&%vjc{fLR`OO8}r1EG6?LuINvHd)tMovTzls~Qjg4@Mpd(rAYDF5c-v?qoTjw* z&#V{pp~Dinx!mHle-iq?YSUnSe?H^ase``i{L%~!#<6KO>napK*JDIA2!gMU#dq#9 zu)s{F7~j@6T_>lo8Z;K@P!h8Uqb9TRR9~%wz zXrSoH7nu4>d&iZEXSYKqlpDsMBAO)*_2`_7Y-^Es?Ocp6NMV9K%Lf?aO?B*1W3+>) zKV%H)Ie_9s#U1{(;rNr0LuME1I{|z*jnC`&2ri1ABoCc8x2CdUEzyOV2#xx3%%JWg z(@=73S&!N?)Kn(%;!i}Z1`RKYV7xCYeRI3H%}Re`jpI^$sq4wf>a ze=$$AtUAQY6w0`+1UY|O`7=&+kziaJw%qK9<~_kodG7|fR%9)seSDRm>evEK@#?K? zYzfZ}q`kEN+lY+;A}zywKy(He<$Xh-lBnmwF-QLdr~Pr0`C zbp4+t{c?!>Q)y&Th5(Z=gchoU?ZGA-CowZ(jz1`Vz#{~yns1JjSdeMY5V@xVb6`O6rLZ^APBf>&J&Izqc%PkLKEi^UbNsq@)bY@mJo61=8_9oAK}M-o zns_pVTN}s9?Fo}0Gu+58-X^AV>=AU4^y@6sPv+lJkdP%{on#+XSlx^ZSn{z%f@m3D zdxAKMK2;fG%<0`_ROi-r=6_RxO*(122Ki>q+ApN2nkKibexK0=Ww>YxROM4!7~IF; z?E{Nz`gz4%KU^DD?#DrU>9?0(+ki{1)KfubpIvm8(>oJgbEm!BYPp6y>!NG!?HnmO z#PZ*Ez2W5)Okr-27bEwD(hbHFm1niws05fQ7wkk80QEs!bw!84Qx|_>S9@_&U=g(^Y=o5JZZnw7>LWhOrhi zIbi+artn0#LMz-O|v`{@h5Q`T@>`SgUP0zRIEhBNloZtEF6vJ{|MsNt>`en3r}>_v2$agOF0UMiUJUyZ z-r2VC$o)ib>a|L&0H(QQbAg2uX{EkK4dzTSYwCZi<(iYXa7Lz2=HP5D4<Hy6OhDN z^u)z&Y2ak)f+(Y3qyPC2ky7+@c^Qh`;a9FmD!>?5YAu%G+ zx3Bwk%84OyT3IEjo5ZQ)2wDq5mJc;9O=68mO_#QqsB9OOD)px?5ZhtMP`Od{&JRpG zwqR&YI!g-+Z7}V&m+?p-dlxv+hbQLt*QlKV?{J72wLe~mSe3h~es;fzqxmAiNpPq& z*gBLH?>Hc28x6s9kAXp?E2vQXd6CKuZR(y*Au)+`$znrT=~95Fz*TFm;7mbZG%(yF zQ>I7sH?Az6)rMEXBjMzVv~G8dVVeCS@M{`loUBb#u}S$G1FB_y2bx#KXNwFoOI%m* zPiB@p%>Y{!&Iv##8&6B_u#O1ygryAVe;gGtcuF@DQNTKp3bljQf(fEeMsM;vC~lK@ zfNGPdtFBuy+*XsrJH<0v;1n>jwUM{7pT;gGMHka`AywzRlea7Ae5ZTS!u11I0s&Pw zB6>c3J!IW=Wg)HQtNgp3G};#%6jYWJl#mNED0g{N@VTcTNqBta##mA7wVd0S&>^;_ z5`+!C5rWM>p#Ve*srFt#qag_{zu$_~NiX}VSPO3dN={iPM`lM+CCk#2x6 zqEH;oGwkg%G;oKuuN{3QG1waJw~`E;jIa3yk~7>wF~Ik!patajvoYk;c2A8a~X3MuvCJ%nGAITv|b}tIl(Wc+tk>0Sl)9|j* zS>~si--|IrF?R6c%Id%u-1$Kin#KQk6Ua`es2E=OD5|w;9|NP#*)^k0P4B`{p#+zj zou)S6_X|mYUwe|M^`4w-6!6lnUv!G-qg#qba)W!%UCcAcV0a1TLR+Q0x{uLWnDoYa z%lf1jAP42FQZdNb&@|M?sU}L|nlZM6sKOiLJw1$+RZp%j)-C?Ky!MDWrM>TGX}8#7 zjRt>0H7F^p6!CjqyL+|yf_EGT%Or)#8?XQb$4UN_ynah{K zf01boBDVS8^8ZHyX{JM+VNUQP&xjF%5f|0-CdxV#2EpabAES40O&L)-vhu4h{2=vs zEV9Mx&FNKzPOn$&R;k7JZ7a!USM@`+9%X@p>eV4SZI}LyDI7QGD`*Sp<`PQDAS?$5V1CORcLHE^>$8iqx>2-Mmb z(~5H@eo1F;ssbUIM6_nc)9JbOQ*ViOst$35*&27FI*}w1i8+3$hwh1;*cAO2nZVj_ zaPu?q6Z$hwq*U)?Hw;g}tW*m?Yjailq_NmL0C{)w##rVXT)}AO6CA-X#u z0Q`=@&WNLLmQO)KvE?>wQADUHDnORVn5~wu_{F?liW9PN(UvK8_;p=(D zW)7CZ+^y9Vh*9*V_lV$6JB*5|Jz*20o!}pmn^~bt8cVw)Oz1rz*b(H9BSR;hviCyg zwfb(h{XFaX5T{>6u=qRTdA@YDXf?(V=_DU>2jL`t;8##z{53bFBh~HKMz7Vo^x=>~ zP)H#n3F|XVgha{bpIvleZ=_+3O=;y|PDEL)k;ZUf8v9B3MI~btt5LV)FbyWlyqoc< z_ePPklpGQ&JvefkVTlrIPx-S`SsK}bY(Mm%*SuOC5Voyz+?4GF7F$79Ha~hc^kVak zB^-!&QyY+LeSqWkQck_)b6c!)d#`sr;K@bW!)E_Gc=2&__egK|RjW(+np40UDy(br zWhOA-bztigEFe)b2f6VMS`sXGq>v#E{jNbqQ_D6tI^6)1tFjQzj?T?!onDi-StBNe zanVEeum?F*@3tyWqgSd46qmu>{^@pV2+q?c!(XT=a)SMWmVOYkQ?hUDTA4dpvb`ophL_w{5P zDS9v}crbRT4-~2UV)f5``T$db7-*BlP3*CV=6A08a@N%VQu#8R3T=vztD~cU;k-P0Np*tZ} zqxNj$vCCyFJg(t`gHdniQ3Hq#O7nr_K@U@T=^+r7d|WG^D;D)!gdfJerN}ePaNS z@l{dxh*Cldu7jhwEx9=F{U` zvVJKSWHLKsab5a5IQruiXXS`-V%tkJ@+6)O?i#$Si^tCoeGOQi6K z(%Y(Tn2)b$ROmKssXI%$gGb{B^;yfes0XA;Hts_~@f|nGA+xUi`hrnN`=T)i)^Ld| zKSBz@n(K*?(t*+;s-aT&?-%fZXp?NzStIcdE`WDr1@n1?iRHD@LPyb6<2#YqPolgR z$!JHY+V6*@*e^2teN|JLFac11V!v?vT<1C4c49*sM>~CKtVP3B03hh2FVf@~3aT#9 z+Y|gwdBg5h9tvG;J^!jbx3GNoC360GmLN{Zjj?}$p6Iv;ccL<;n z7*2EX^*_|Cru{s0ugTE!C(fyZxfSH<9(n;3f&PnYL%>5V2NLvN;^%W>S6A1c)xh`F z7oca~@PCRxH`CxaLu_ps-qn3RC`S#iK9O$t=W1VC;HkYmeBs%#>8`4;@J{Gvx3bT_^$35v*yLKcs#dM%NrOcj- z?<&SoTX@B)K50 zIbJ~xNOg3PsuUept{>+$K4q)Lv39_{_*iYeJzr%!b2jQ3b)c+cc4uq7(5o%@pY3;8IE$vkBK5r z>4RIJsZC~)#+Wft}W0rpvLUkwgx?p`5B2$JxVVURF!u2QkcZwO8TQ2 zsQsE*wM=V?dA!U(t?|MmSQ!7rqqEmvmJ)u-_Vm zaZ`A3nJs|gH`08j%xe@|zz7|lVA$XoSU-mw{z3JrkMke|=pQ`vVrj4*aNm#{c{Q05b$l-%koZPR?zE_XRSin>?;JY* zW`Tp?RVRLeD%G=^4i#-6Vko@7nolq}75AyHB)$+^mTxM!l4hc^QRFvDM`ukirB~=} zL_@XGp7DjdBdsOkvl0El-7c*)XS*TW%wMmNV_4hPIbCq~Yi!>OsVDry@Q2bAH+-gQ zm&Tv)8c5wgI3x|&^=6cfR3{14-yK)vpik`_nJ(NCRE;-Mqs{EX$t4##_1g z{wBNTRgtSVO^JEj)2mILG4Bz4KeJesg!|b!e_kuFFRX|q{?hXw(b6fpvz%$^zT1*4 z%u!7aWzz`}@V7Yf|F{5Ea0)J}a&5dti2IKME^0Eh1yZeRMCRgNmnNybN$0U)#Kd`T zb1z{SO5=75#cI8v#mS|}0UJ#5#)bCYl^~<5q)W9Lwz0Gp>P_3Mb3FW+jU+oq-YRo? zg-|E>A=1zrL!$;w*{h&Z=@8@$rOH~^8LnQ`b~cs;0ebt3d+FI7FH0gF*`G+$`OR8u z(w!<`Fi}lkjIn|9+9pJv{1h6?Q^6&1*@a9c;h-*hOhsFqzk`0f`1BdWlAj7-c5z zi*(w8(pP>i8}Is@!D4_ob}_BVix9^wCgN-U<5(1YW4)lbm$J1ua~%bKp%8F2TIjMl2tu9xM&;&f!tisK`(5QP}oJPodn z8(j6H;Ww-FtDK4}3K}%Li?7B1ln>EhId{|OyuvvlxuCs@nnb#M!HfnHDo4|w zj)aUvsN(riQZ?WhQuXAAc@8oq{F3cNTRC=!8<0COgddit(U(wj4nG`Nk8i1>O2Y zq3MWQ$Aict9@v1KT749+w&(Z9sgJ?eycVv+u=Eu+dnc$z>VYtK*CnwTTKetGMwzmR z|JB#w#6#p@Vz>5IB3N0&3miLjwokt7x&O!3TL#6|HPOPjySoOr!QI{6-8}&Y2=4A~ z83uQE2@u@fApr&mF2PA4fjiIpefQt}ajK@KW>@v;)BE)9?lo)GyOso2JTyh!v>HUj zvN2dFX=MAl$||ceVB+hF;n{0uz$>RZ^q;0HD@3Gx$e;bpnEgk1bLoWkV6rsK7|1B* zpCyL`yuKyYjtF2JugAAA7<0Thp#BOy$uF*<{LGJ-kM$R*(Y%lM(~uUldP*F=RA*Zi zx5+`;tSArgXpZ)RKfzN^N+5W2BT8gBSr(2eFxk*%j8}F4EMm9-g}jkO)HyVW=Xr6c z!Fn0YIm-k!ZEbr1W@KuS`vH4@n1_c-f~17vJO*b9P^-NgLAgq(7>$^Vr1OW6os2M3 z>4v@Po%vv;Pu4AMtApx#ovd#9J&P`%>=1)%;wvQZctDT*r=~IivhDjOA9@tMQvt?% z=tyc+SRYbQhWi;q2E*TJruSI>YDI0Z_Nj}1Wma>ocvT{ifb&PZB~;_e@?$7xYZItM zk28G+8ginX)r|NHZ(@VAq_?R301DIX`7Ki6(1M$$ehz?I=1TK_ly ztw7FwIaoep+k*A_n92fq2e8r|jr}8d2mLuMHQKjUf=5WTL*VX&)Bhj3gx5^TkHlpL z6qj>jJDC-}NIqKH;AROzEIT4pV*pgfvb8%P2Hqnp%;iHfj4|A9GuS7*eJzr!Z;bLM z(dPiB9u~d;rpdxqAtUQK#^Le(KK=dWwfhQILp0OE)5jD>ZL$ac(oZNZ_rsC9cz{HB zY$8S^g{O7qBGs&T>ej^uwsQG!<$uuh0cp87rz3tcZ?wE|-ByAc9LoW-OeS`{7G|S-iB^l1{dU zH&OniROgRC4)uyhg5^Ke8&J(SRrw!JsDSS?nG+%4{~8&+qX(ZDr1JD#V{B2jMtP?a z`L@X45dL_j6_hq%oe<6badzyNT&2t^&C7gUGjRs@Pv283US$_Tzr5ezNNxzvW&T49ydqXF-Pt$DCx1vF&rPuQn*;(?XZkJcpknxq zw$+PLTvOJAPM(F#Wb!pu-$oTR5$&d)Is{_yrAyOQF^5aZqf^yQ)B6500-P<=qlW z6WtwaNfX|RYReTOLYF}0jGURe+CMN3bsW@j;qi)esULPS?G%?I0~hG0W@bgLl&xeZ z3c&osD%si9k;19)0s!nkEavy$ybQ zzZiXok{=apG|{~*h4q0Tth9!xjd(9~C0z7ph-$eieNoQAAD%=j{WKxqN%2ex9KR3K z^qHpQ;o{-&>GTK9=B{>Zl;l@iaji?EDtK&+IZ^MMyI;q*eb*nq746+8aW}cn`|Nz> zs2DRTuSNi-Fdj2=p_B?s|ICYPqG@S4Ku1wf>yC0oNFHY%B2X){akar8%loUI#B38q zOm*Y2svEforxQ@Jv*X(ITJU@;_U-<_AIv=ta=lv4`N-n?w_r(l19ZsqY+@TS@i{0o zm#eVbf?TsP#!P(t3(B--+Cy!;HG-Wwk0OF2!&DG%S|(G@D&Bs<{OcO8NLZTy*@F%r z%s;hKtoisknPn=2O`!R%(w8d13nmtJ$KH|5fH8|H{+y`uZSY)cA1(z~L|^)OVJ?~d zG;4-RAS3_u?`BMJtCk!+3)h6IqVb4Au|_e7g!gf5E!>7`-ql7S=PX^?2{*vH((Wz;4QU{P!_f6DQOwW% zJtDH%v$2ZAs>mGGMyd<3@jx}}h>g-jF9ja%2dX}h{Va~L*TWc(y$_$0M@8-$>~eaV z(<1||Qurh9H&iE>7iYx`p|G}|R1rWC{QS(I(WeN)`5fA4%GjVX1Be+_H`aV_eRP%v zoHe}W-;cP`g4gmn84=vpq#cf8)=;7??JAGcQ^}!yg*B) zKX#c3a4K!LOi7f-L+TfIzRx4%ir^?L6=2(bk;adI*|RR@px)w2HrUIw8A4*l2RBeM zyz$B0s`Xt)gg7EvxsF(LgfM_A0N5@%(nT!9JMIy2NPwR^3NBQGi(F^1I5rX!$B^Y& zpCYBfSZ!lW;pcxCT~WO>&Q9_3I%%G_0!e3Js%qQLYj11)N;fJjIhxI%%s8U>#I76@ zUHN3x;+Xt7nQUQr+xW+*3N!+f=vB~F0}%Mr*0aBNhycLOAq@%7nI_T|dF9i|2&-#v z#kFIZ45Fn0Ud{7R8G^8w0}*q?Gdn!3FY<*JZFu4n6_->@_a#^cD1>A~OBI)>$Tl_r z!J%q_De@N11=9)~qiVbtOa?I(5{ag$vjDj#3Q<#)C$%LRhjiiCeG2OW9nA8!p2ZBS zv9;tdikNgpf7#Qzsxgcu{6%^_TX>Z$emtSFz%)u(N8A{Z3g3|q4AOwv#aPzF@HEUU zZS*HOKRYDsd5p|XIxkXZ@uYShCqnBygfGgdlJKfjs>Oy=I8v{Of))c!)N zDqFr{_l{PsFU!+RL6q}RqG>o!T)i9`efguT*Tcq7ecpG@s10?x9)?3bRBUBpC^cg) z0r#Z-w)*R)Dp;JMe)O=QLx|rYm5TR_X3r9X47EItd)l#_4P$SSk;4l5`=D_5MQQcQ$$YS*N0N-pC(`v{Sk55`XzN1)4tC76jre|{qv@LUh_taj368^ zL*NBrr>80fgj(`;jj~2xZnyAi|A^!j=gsJ32=8jqHYv3fPA8k%XlnGliJ3~uOiypd z3EA(tt48@sbR2DEhvWsbg&*f*rGYA2px%9P16zV5k5_hX z;*5yI_^`uM!1LF~mo&u;ko3At5m9|bVeA-+kzX37Ph@r`(6&v1)Su(>M@=PhjCO_* zS;K7zCNZXbQ&sTBoj2PJt>mx5!;5SEXkH)Xa zH8nroaT-3J;Dr1k)1D^J8Z`64)M+{KdY7pY-v|#t1l?x+QKa{DVX=}|Q6yIO{%W@v zFFsLL8PNCP)6bHzFYJI5m`Rh?j)3HfTT)T%2nwXm<}c-*YGC<S6 zhOjHEwNYmKrjE5P1v$z38V|vQ6o)I`CUD~wW)_%Q|6-RtyESHKl}B6WBGbC?bALF! z1GO^ReUW0#z-~^sV&}71Y+nJ&v`%3tYTgRsx3_-b`+qLwDFM*!%U6a4W>GQj;`B`O zYpi!OwVh|jPSG}+7Ua~wV9D+<=wcb8#d^QuRVE_zy|=zZ z-(^XOwMMjRXHm&XMcLQ1W(NR$5#qp_)SYOcbj*`^J8IgvFUh6;?nZ&I*H8zsD)G0& zw^c)VFcFSLl%5HD{V;4H2y3>BWm+9W+$xL3(e!-e^yaBgm=A46>{pTPEsf3B)+2|p z;7l>w1wXLX5x0+6Q(MxT!!6O)@Dnj)F$W|z#v@C>7`Knyx~Xd7vlJq{`zf~9xniyDH*x3=BQ|X) z)8>=g)$O=@SQ!e=R(XTMy)g2fm;41QIjJ*{K7@f=$9T*50C@}9>bn1>4!cQ{p~BUl z$!&#^9ar=A^E!cD2@JwtM#1>ZuI~06Tfo{s8`X)cu)S~o7}>xX7l9T&w77SPrJIi~ zIj9&Sm=oxi!X1Np58q?|*>{3KAM#6$nLBjJjoRqLTMx2?DCz#vCm|5)D#H}A3biK_ zcxp|6bavB(ot=<w`K*gd!#tytr}VyIKZUd zU_Gp6hf&6y#GhseoK~9#6GSQM+-|kD<`}XRJHzP<7!Ee+@#xtcQwjD|B<%|SjEQ0M zWQUswrShqi?j}^mqpj203#_P;kY0HSBMpbN!@Xb}8a`mEL(F7P5hfX!4NpAGsoY9o zPaUaR?_{$CJ}gJDlI9k-z#XGi%Vw#Evt@Xp&YKvjxkPlV8?N5Xz z%=~SPrM)%Ls+fvP%+o#O*QnUd9E0w%Ni;!(NNyzH@qn%>;WN2uJ?@$Juv(f)<9<3z zi;iEZ#qi{YvB_x{JQ-jWQ2mQ;Ao4}PCgZD{cg~~mu3bwvJ;0%A$(RW&F(48>4Qv#v z`ar>xEnjqI>f#F4r#~NV4%jKBORB&=(4| zJ=BKw!3=LEW%@Z7?jylEH;sB_3WW@kQU)R9Z{iItNA06x!446DiEAcxC9xPFRNyKo zhWnP2$63)M0GCIN`5wlkdi1-?c3fLahX-j^KJu|!!XW578Pc2H9E4VSQ`bowC!t&u zS3_G|{!$snRa{y~dXB`IZJSq#D*|XKTX;99cw9gGbviy7QXek2vwgm-V61J zX2#Qv&Mnfdl(RSSHH##PSTH{_1xI|iSu<6)QL)DYf(W;v>p>Yq$?c}-Z>uGA7eACl z;pLK+E<1shPvT8R`tT!%2x8(l>QH?}@9&Y1yC)k=t$ZB4-N=1Pbk5t?GGsFoiI1wI zXEz-#Y*;T`SbKCvT6e2-w8)a$twXj0^RV?_I*wTd#~Da6xtAspvV3sv_xWec7GIVn z;B9;?B-@s~+M!e8SN=Ww+4|}cg?u48nXzvPv@&4{Aj- zOAB>=dsp^FAwDyozh8N1sE7Bh=Zi-(oKtu^PVT!RD78{aDfqD}rE-4pV)5Q3brYzj@6hWo5Fvj-rJ2TyJEe$mM%zfo6(BSTUH6 zPMR2AW;9hu8Wc%xFej7XNJRs?g=oxdl0P_i{Aql3?Zn8+pV{C?PE(K6WA0<3VNaqD zK$XK@6pC<1-mE5Lb~lXEP;h&=lbD8eu9eXLIyyM3trc*~k& zHcbwV>Xm}yLqywf1kKJoqA|iT z2-aOzn6nLQ*_>qdU}*qle}f*fawUcdFIy^>s1 zlh`dVZ^zz;z%W|=M%eHn8DX|Vk#>@p@u_|jwCp8+p(=)`EAzEawA{SN44Q$P6&P8=LzHy zTJv53I>*J2WB{f>Ks<5!HOMw`#cJy7)kFwM4*bUyVz{(FEDjOOVGtRBIh*L5@>cU4 zbu8IN5#AYsJVc|N!!R+va3V0l(?94F^VGWOo-cx|jTYPG_=5!kJ~A1K#7 z#Vm@hc`->FiK;8DOPzXM) zE{=nry=k=Lj^Es(Ekn@Tn1v?;XzM^pqVbvs8uw74kxiP2Tg6<2k zij)SgG~d#7g%0-gu=A&icKXKxx2~WX6LL3%pA>6$6pJ!^6M^vPV_bu)NZwATE~oB1XZKsZNx47Iei69=?)T1xyaUg=NQ&Y)LnyvX|;uW~SbJJNi(@UM6;d0-<9 zegFzQ4{7mIpq5WuF3jqt#JOnP)bO4XF=PT_a_WY3KheC~R_OoHcbEfBkZAEc;ScU^ z{8VwzA@iqI+OnI;P2^)S!_g<2JsDHexbHAuu|g<+!0^M5)|WJ<<_&^REb{#08J4Gq z(W4C$5N62~J@dAcb%;dP+?kWHR1?ua981DxtjFvLcvWQUVLf*9wfqV@2z)TXYu&Hc zU;q3)k<@J3IPCuw%=P{IcSl2!%tfWkKbNST7->4KtL-m%8yYz#40VnDrV&1rpo$+` z#?JI}Um9CZ9)#N^B1P)6J1si&F-+I+BiQ1Hyk=QC7%*H_MB3;9fU1&pVH7ST>hvFJ zWf|e)HKV(7S~-BhD!Zj(bXsDDuB>Q3j@qAAz z-e~=pJ;Xad$odaB=k5T@%^VlXi;1zNbW7h{g_>2?&iyoLrhP`+K(?XyH()ns!1}B#NiH(j%#&v!6#)1Eu=Gr%=otW{Hm||ga z7s_wa=W)Q?X@>CjjfWl{wK;jQNea?)OoFUpcJv`)?NHx+xD4`mGtu94>qnTXuxjvt z*nv+`#(s^uat`~xeYR;S@}guaVPCbkPgxq#ZOI4!7-iGfw4cChL(>V(w9vVs+5T|qNi1je5|0G*oxR`r z5JoFaLSA14?`#12S07jtQAnqDSA1Rr$~<`-c?4zg$1q-VMuR*NYnQD2Y~HzOPJ{g# zY@L|7ta^I8oulQ``xk@0wKSTXhmFSh?6vn=^3fNXr~p_O{Dr`Ps=FS74NeXSJUwoL;fLqV557UiuiPEjg>*2Dui8?0*w}?#UWkVRB3m~ z&p{ugLo`#4AvVMQ@};YDa6a1IVb_0l1*?pI;orGH?Zcj9y* z`r`YLLt-z?bJ(-J`%#hEOYm) zjv|GqoNBBRD%0B&&V1!p^O;}!#r_3)+{rQh2$hTY`*Yb9Tk(B1e67Ks&(>s}*|CxK&?eecs=lI4MUcnq)Z=d2Al^M1TqwLHTa<-S)(dI)uQQ=5Iz2@O!4S5)7zDV!0v&ew^wu@A)`BC07(;Xo z0Ah*PNO+8Z>}t3^s)Rpo@deFJ%p}N>Ukl9f&YlP}>#o|$uXb&`tQ%x#+%7}Ai|WB1 zFCLgemnfk_l3vn2Ri5gSM_y(9BUBA-{3oPd)0g>oFlO7oCX_d{ZZ@K=j?+eYU*NVO zk(uPx&9^h8u6$GD&+o`rs+PkRyYiP99gzN^R|+my+OynGWsERDtNHIrxMmJ(?!^vD z`vGbo`=3$T2q!hs@rK75d7}0oZGw5F+2(D3CH#s^gt-z;a8{iv)=Hy^{=Zm&U@XQB zKibBsAn{tNY;R$UUiQpukr7fWRm1tFop=6$r@;-JsKYY)0+ic6< zFDm@w63dLxO|%s#$A6L{-d3Cs&Ku^eSa`u0>FC_i4%hXTkFT^Hb?^u573+Yj@z0^w z8w3t&d?jjhP&FVkEuwu}_E1VQS$U{f5L^3OjO5(yE99BrD{_co^Vfua_TNnG0b3bY z#xGT*f-Z@sa{j0}=m%3h`ANb#v9%_-{6D5h+nJ&Y&CpjnQQ|UBz1mhNd$U` zS!)&Doc86JY-(YsYu5&dY!&hdlE0UU?^|<2sF6nY-X8bBuZ8{+3h;}D@&ECj1lL%2~%va>$z#GOYK0odsPDFrMQzG*@`V+x)Cw4zp z)<(5}?G@}*CU|nC$Crmy%2)>#fkH%ti2pg~Tv(?#2MZ=N9A66XTrZ7F>YOHcx|SRbvUK$|5H$3K<%Ok5t&NmooQT=1e|&SH z1R?uLN}Y&5D3!7qBn$sy-NKLfR(=yTbZu{TlX7v>2eU9o_an}F5CbZiiJLey83$8F zq{*b_zmA!-m(n_pSiU*_MIA)W#sVGS@OW7*EGROU?R63IGrI zXY#JX^;GXY*hNqj*yoeyq~YM<=MyIswRmmGeR9r+3cj^&IjS(8@ns=jm_4^>iYLI7 z3KjKSE4TXRV{Fl1`P|spF$*>n6mhg4o<90oGs}s#HghkjR*|==m%spoX$X$-GA61$ z#El@87Ed&)o2qNGFWT(Kk%+9!zPOL%z3FrqFAbM}z2OB{iy})7l}jQ6iBKUFDxz^j zZOk=S9w}PI9I|dI7_&WP3`$>p7i|>>mH1zBMwKb?@*ecqYdKmM`KSi-C0Jdh57J;w z9+o`O#SubrjPI%JzT$$o&|ji#d`kjZ-7-9J7ko<1QoYL1h~=HjsylIw>4kVYhs`la z!JHbgBwNF$h@<8RU9#2!q+hXO=RJNxHd&bXJ*X5tz=0Kj2=A=*-n6Dj$_VNROWh#L zn}3r9UlTqdTN&sU^XJ~cEZ5Zb+svJr@w-fprP;c&K+ZEjF_<4bh}KTGTki-1z|e|l z;R86MT|1Ps{-fT+Q$UrUUqcL76%u4nEy3m*^*DVC<<`R8XrOdu5muIuV{6(X?C*KH zs_7ld34iI+v#AiBkj5}+8k=FAHh-Bxnsz@RSji&57`cfugLj=5H#(%FS*S^Zen^@mBaN`h6H7CUs)V)o^(y?pJ{Yi;1mGx##1f>KPhvBuaC&&iHB&jhT> z3#zof)HQFD8{A$OD7zy#4`qnq%%m@Klb#mRNVw&bHV^PPOaCymM>JD`kS{g=$y8u+2Lxq;zLs?RX{O`x9j1Zm<>)=cq@2s za;EQU|HxiR8sl0WA&V+5JU`pDD)Rw?H0BI@cqXP=ZY;j$9^?s>DsJqnX#|%-YBSb( z`!k7Sa82la1b#k#%Co{jB6TvV?C$LDAC8f-#3H<=!2CI%Du*bK7Ek?j1R7f$|Ldv| z_yGPITxjuZ>N}SWI8atEoX?+y`s|RNPXmrBYq%?VBjS{NF7HX8r5_4p)*qtge#qc2 zi_6xMrIM2eoZPRP(d)Msdwe&UmN{ERr}}Ia^t(TLiOTw?b7eR`vOoh%B+2(y_K)8vh@DZIn=?EdDUyjv z;yXzY^6j_YKn>IoVux%pmth~mwt;IIp*#6v@ z!GB(4Pg;yfUZ5>j)8XN)e6H+Z<6OAm(${V{+FdQorS&b2#vCCnN3N?T54iP_WGH{y z(YKT=09`)6mS?0m!uWVtNmAK08@KQ#y^;atxJ%Cm#MHX|43yk{ z>n5BOcC`k`OWH5yhQx@=upKPOr2t|Z>{zc2vQ;m-TixIkNfmyQ8n?)Z3fxJtFdJe- z25tM=GNEd25*An>rjwOpDX%?Qq>Xi5(Ufmu3ssfO?6@N9PUUz>E(zMJh*pwnr!a&) zE2#HK(ea#Dxt+>9r>?W=w!zRU0#)%VQ0FhYKthFx-g_>l}rb}o- z^THPp&>}y!iVs2Q9~W#MLvsrrXy!Vb=93fqd$R=K3p34PcRfnz7*4&PPDh z@^fKXp(1jP51Xsi@v8KmD1a3flp5K-kBum<>TzY$_J)obEj|{FbU;b(F1*FxqAl_Wc5av4d+f(6|hSZ zOCpyIG);r*i5Jk-f>NmaFDsy=>LxAM+4gerBxPDdZJIbwxOUCrZ*DWpvDQK3_V%xt1s zN_NULtXlIl&U2AdK3PTS=tW!9T^z7r z=8Wiv`KZX!iTD-K!Mx?4aHaT2K5mLGg>90C2hSK_?2LjQSkEaVD!xeXn4BaLV^K|C zQWlef@bqIiGcZy=HGU;~l)*!T{^oqvxDIH7;lfUrBg7{HrdOAO=2G^7BgBEq_i@L^ zDW4hFRm=K@c2*Ke#F7Bxz((Jm?GB?obJSJOh+Q4V+di4I3ap%$%`X8J-hE1OdZbYJ z5d+CQ#woM>1#`?OQjAF<@;b8uywHVCy0_i3vE95eYgS6V@be-bh<{ewWuSFw7|`A- zXl zO8@~e$5X_C@1Q&CM^%;=pqrzDwFUqL@s7&GMYLttrO>UPziAyT^~PDBBz2mX!Vu?w z7+cZRRFwIuW-8tjP|rK`y&2GQFMqHn#7^j%;s`bZ7#l4QI>oPvxfp&UT)2SNd}rgj zNGG5Eov&rPoVFm*DfuGOkJ#0hrcJ?W5nx$W?L=Yg%n%PwBHfk z98Xgwev$m>gme;a6dm^Jf`o^rxHUAPxDneZ`qTMnwU*f_tzHz_IpbU+_(zP<73}`R zba6$iC#nkq<+qqbp%+e`Q@pHT8zj^`;HrZXSs-!dSZ%tzE4}CHe~E!$4OLGQle5TZ z@8H8%GF*$`l0HJS5Y5~W6Y=W=QmKHdCvz6=ZavoJ21f$5^OgHXw0R#bgXL)2%Y`&D zJ0;z$jBAP^VV@Kx-HW5fTeR{yoqR>Ovr{`G_!CID65=u`L8pUx^-N9-`d}vg@~{h5 zJF0Ci9MHhsmY7rxkusXaD?r8__fl>Ot;9CVf=JEzRYHZ_f~0nNB_fAf+BfW6;`}*{ z+1-g~$e2s^6~#Fj5J~6#*nnDDrzDD}CVDu1Cir_HlXmY%5VwOZ<;v~iGvJ|le~BaY z`%`xZqP$~52a}NSu%}wdgpn{Rq+UAkiHAY=l)hedKBm)%cm`(zW`rzfARyr2%un$e zqw@wf@1)V82X9CiGv(>554AXHZiN2Go1wO+yhLKxHA}1=_VmAU;rlItMkO%nG(%j~ zy`m0vy(62g-`{`ZMBlxf(2jI&=8&=8eEY*W?a@Qu!;1=I3#VhbqUCj^#Cw^5JXf}1 zwt_D^_e-zzAP?|~CY__oL=6ACWm0hsQd6aLW*~^Wob(+yY;=Y&i;lZE{o3q>^9edG z9-y2xV1l!i*9=cDrkB@5)W~*2;^6YGU%n?4de$ecZq8w$EVTZMT5uJ49l592%I1_M zusE~dzfN)%=8mA zP*f&8S_*eS)Da0269LG^Cn*zf?W}ICYT~wJBg;X4K;nYn*iv6pmPlJf?q;3F_r;qk zA858pI{G>o#cjp2+KGcd?YN2nAA>MkOIq53Wg*)zkefF9vG%eSx<`$n2xLiNCrkZp zBl|T&+MfZ}3CiBuA@o#}Yy%!?t(C<~%1Xt(3p1h3x01 z070C>7Vi(6El5P-cy{&eMAleJ8#0C?%c(O3GMbb<8aSaz=dh)()z|cIXtjnB?L+pb zg>?OJNJx|>Mhj_}v0DU}y*4q;rznB7LAIP?e372q^0R{J>qPB{+d!v|5v@zH*`cQZ zGKOS&5QVNs(%K-nK?sRiblb+%#BG05Zz4u$og2fm-uld6QKHKZof^Z*k*YAz?s`#v zv9QAf6Mw>e(&|DVe9-%b3s!X>J==<+xi!uZ%1G(fi6x%$P(i^H zMKs(o#RF(D@$% zQxPfImS_`mq$beJ^?Q3~!J;AIYuvx-`9$R&U>;wg;I}rmxa-m_+V+!4vQwH!pVa!m z&4<%U*-r6#HYOyh&(ngH!?Hxa?j*P_B#}FauF@LezH&W?=i`3(6`0Un8MT6+G;1W= zQ!GsE)Iy)*PGQO*M2x}TeC&2T=|Ge}dZyCW&F+?WZuy(WorZgVL!ph6&RlO>Nq9{% zz}lJUIH)dOwO~!>YfZw568xe*wBMh6=(72;8WxbxBH06%VRU&1Hgv65?URIG4e&jB zAkC2?TV54QuBIBOoPn6NoC1?z`L1~GF3ZJT;VwSpt(u@;l;uv-o#`d!mSc{o@=r6V zrJ&H5S3|3J$GR~*>7Z|Zk4-zg;gZC1zU;m#spoB+6HO>hq{>i$h}-&hS~PkF_(8fOU@r0D3Viqg^au+lWD^^AHAG4Ga+$ zuUbR#g`(8|qKZtDmfP=otBNhP!(91aY}@udo~wqN?;{D@)KI(r=A(YKZ?brE0|0Px z39}~NJTciU^@?OXvs6MSi+v#xcJH9T{XlUHHop~X1S>Y&d0hL;?c)vCILE%Z6(Q%x zlE#hi-D3P@IZsKmq0Tlp;jvMLApZPO?B19Q81;twY^+s&D8}Tjg`ON4fBPp+BYx~G z`)p}>%jB@-Wc!F5l$kb9lgw;rNho@{OM7KFFp;Q%AWeV2->aH%_ znLOX@w)3QMEz^5bM#P1Imf4C0OYY<69AC>umJ|G=iioqdY=&TG)^RgNG}kiq1;8np zX(R7Rz>x*TNqD-^bBPM`MYSv4z;=AP3ysCHEYA&N(l3@90=Q@etMNly^|&~BK3S!y zp~+(O#C+9;4y~n{;ua?nU>bTmdpm!3^k;%z=vi_Zt_*)sS1)yl8vo@RwiNb=tz(2Y z!IdK|snE?NKz4&IjOw%4>$+DyNcG&GVxesOUa3;7wpkIK=}Vkj@c^jSKsZ^S%t%H1IXdi&IIl)AJ6sAKxsP}+x?xVm zR@WmMdhE#t2?}MuV(oU_j34xs0AxxadYz*1!uYe$Rn8hAN5O`1I{9UE9 z_Tv*_g{;ynwQE&zet=)2=|tWV*(RfN2z!A&fF3w6je`@T+X%+B{EW|y<13aJObrmZ zantT^jGFpfVg5G=&-OciwsYKAy%!8c`)XEjA7rr_*SQ+PxxrcU#9F(7;G!%n!6NPN z&(&g^cIso4X>9NXPxTn%NNEKP2+xc@w9;s^^oAjn>l@=qV$mQY!U(hKZ*8kvt#!3uzBdaBXw=PpjlQ7EWo;BD5HgSd82B+j(=@6%r=O ztv00iGJDD#(pp=qD7hz$8tlIa&RmNEN(-gp)Pp@u0F>S~Q{9wguQ12dB)0P{hHzk- zV53rQej{zYtv!Cnm!(7x%xsN5au0~&X(`G(V<9&H{iDYXo&JC!Brw_I-|&hk3jmo2 z3l0D@E1n=CH741j`6PtCMfTHlaT~5Jyw_*6A(9kf@hKSetNA7;pLbTB`O>I5s#2y& zi0g<1OIfW4vvZ78(ZfZYE#o?;0ER3qBg^o+~P$Z^@N{Za{SEW=MT=Zatl80w5|0ofIbnW*Xn3;b_pH# z_{cO2B1<{rGOw`&RX|loRB0NK_3t3l62guXjM!5G?}$yOq>3sAB~{mmCxTui5dk}# zKr-}lah?Y?Q__j?wkGgDJP@xh1K@OKP}v*3t5td&35?mdC3Pj@@;X$dis2OAi*JM? zvZshX(aX}^1J6aYZxB5vJE=i{MM**6kySE*2r~Lvjsg+;L=Oe+xa)OEcjC6iZzsdd ze+=7(J@1`OvUiEjCwAap2ZagGDdWL+Hf*M(jkP+thkpwHnc$$KOrG%Z@;!P*4Z+Wc6|}$<6Q!{Nd;jZ)>Bh`a z+PeJFfSOnw5yWVzj)pAzwKLbggA9FDy%=_zG(nyuJHL)R8KrTy);JvnvXqk{6%SVy zrfe6h1rC}r6t9TF;tx+^3RfuJQFg#~MCdSegU(z&kgNg|+jY8zS@A%4F)3L4Nxvt; z(J*V~yoWAESUd@9%8s(>!p`l$9)5vww;q|OPGAzZjAa(yz!bkx`OLqDm|W1^7S# z8SAX!*q=otlOihXgyryzA`z-=d8;ayFYB`t)5X0_)-QDeu$WSo{2{l*q3Qb`leVei z=91K9840(1f;32PUBx}LVhnK5WcH{ zww5mZDUG|B-}^@QQH5ljX9O1?X){h@`I7vNV{C2(v(ruJ^vh(X>16Qj0-LU$d7v`OBRvoc;*Q|EPmS}z@tC(9cE{yeWceL_tb(hX&1LK z39`p;`Xbcnktor7va~~borrA{C^%RX?MYtPk3$q}5$1?eE%-Ycats-VXb=K_0?ObR zg- zTTL)W?sLR(z4CUm#HNEX-0=g+o2Z0HP_N_5`zACbL7RJfjy(A{Ordv7fA^{1Xf1ya z6Z`F4SQrk3Qb|=A*{o?IC0Qrm3)YY2y2A7a;Tmm88B$%ig6hPJ2y1_ZLc(DdTLY5p zk*IwQ?+$o-6iVLA=!7mZ`fdjeuTNer@9=WaD_o7|R!yi1P4SeNQi2_HSa7zxFhi!Y z*6$vX-x5tN|F#P4eM4H7U;Ltty6f+mq*$GS)$n5|J|O%Y!#e>WUzS(`khVvoOl@c1 zHW=`F=j7VzwpQNGGdUtSQWB44j&-tIfWm)jn$s}r8`E22a(()+Tp@kYmKYw$M4u78 ziGLg=9lZOad<~|*B_eszd_2R;D%Kv-K4$+Tp;EC#mU`3DmbDF~NH+r3!MmdM8< z&BRLwa5h|FJ2HtJ%@-h%nHqeB=$m(oKdh2Lw+swI3k^J-|dTI$*J<4wv{vi@uV)D!0-{Xpn-QVMaHc3#)mwn>j zFB{+H_uj7dB+7zk{D1piCujn{*|%QTTPmx%U}&FY0e-u=L)_eCvd@J&*!9x;VGGcv=WFkM_7meIJ8 zAutb99;E0slBSKL4U&!EI7ch9S z+n(U1$%M7opcncC*%5gwPyPzGWj|(a@Pi>z%z9rNes30#dg4~p`3IKrhUe2(&U@i} zi;d<0Dvyl(?N1@&ktvZF%29LVG5OUP#TMe?&2dN5B=dPs+7ESx#C{8m#KM7yRt`tN-~<^xM7Y-<`dGl$L*{4S#1_{vm`3`KcHZ#XfWT z7*OBT)HW~>qAoeJw6w%8sQ|1$HrhM0rKF_vB*?!%n#uihK>W>bF6D6t+EyvTKfKKo zz3z@A|J0YRNaY`e9Rz{@JBa;v@bil;|B@X3LK}4Vkd9_D;1pf`KagD;mq!1ezK`Vl zz2yCrX2@Lu<>BoF^~uh!5Wy?oFSmZP6OwP9cVeLisoYelf1nD%3^60LP%)do>!1Fu zxAndU{5_O>qYr&M3DwXh_z`f?(D&T`tKVS)jyWNs=udvpes)t3!>^zd(WWg=z3k_{ z6LAU(3J>Tz@&>Au^IkxC(K11GXc+ov=s$#RF*r4aO8x$OqG_vT;7jS*mVZGyUL5tG zNkzZMI?FlYftzSo-+I+tPR|AL=S7zez&L;c3;J7_H|Q%NocP=Cv5f~37{MPN@SpdB z1wZfj5Q~b6iroytFT8L|+{SC(v`N0U{qWf^hnjG6GlDI7F`;yeFL{gqcZvER?tg2F z{>(ZyZh7kbz5eyaDftYP{5+R>|DSb1Py@zHW{!`K`Nh|vh9Fuz)*9ZXrv^p+39^`` zTzK}|WkM}tZ?uQIt)aLeZ1O`a)QA=R)t4Fgt3UU@>-(o~{|YEBFhBXhzdwC?ACHy% zYyI}1`Tp=-Vmy=b0!rXR|H*HJ{P%O{`{$I@RR66Ri>G=@uCcOIiRn6vM^+e%hf?C( zN%~Ze`#C|V;WZOGpZEIpu7Df|ZAJgsp*I?|_Z&5eAyBT7qTi!T5<}*ncOKoT5Bs{$D!=%hmc;&Up?>A23OJ?x*n3v#>IEX8 ze!Gcv?)ttk-E(7iTca7A01dmn_kVl9vtUeMMIZGrtOaRin>~rE)6I&Hg=;? z;tx3RdjG#TZStRU_1pNmI~MwPBJ}1z>k24;ePtlSd~qHR{S0?2{>vXbHa7P1>gu*7 zHTbf?brI&jCEyOx$oqqDZx`QwLm$6(2sDJL!n(fwy9$Ep4gG2hNP=4NW%q$mKtO;5 z1L-gHp*{UC-l13=(*IV?*IE8^sqYASD5HkPyUOj>LP$<1)Ytozzx*cnMaS)U_SHWH z;G3G7639b^w1%k7+HC)wguXFR|Aip;{O1cO)6E~~?y>FNx#kz${jACdvm=ME&UZ_Z z%&5Hi|9?U%{^C>`&j>DCg7L=R>$uHd2#TS8zokBD_!P|66#DOiG+DuM-Tz@p@ec%9 z((n4|lmCoq-kih6i><)YC~pdM{qIIh+K+6B%a$P&Vhvi`Fk=#t$9~G|M6n^zZ$?qxMuCzk5|{%O$FYy21+k^`+lZ@lFWs6yCXGr zz{Qa77T$jQsVjU>lYjlM%lBW_y9xWon9j0#p|&sE*`?xbcKm7U`#=5O?Rd<$?_+O% z!ExF04~N9}ZHPJk`_$aWz-amY0~nN!b)VGhg&O>pAAb4rW&Q8xd5b!iOYt`6J*s!9 zdEqR-=>Gf9+qUoRE#LE4_Rp>Lf2A0trKNq;gx~FcZ})E3YrTE{zOMhg@B7~Vn>ojT zi6yn}Mf)tS%WCs>U%cXI{?Xpb?(a)~P|^d{fb%Nf|9xL?Uw)^MUBBvL_de$NYdY2T zm74JE>imE4}%r=oHkIK1q{09 z8HgB0iC15fi${u0q#A%8QJYoK2OIETwoIpoTsE&;H1$RA=7o ReRT{#;OXk;vd$@?2>|I-TDSlJ literal 0 HcmV?d00001 diff --git a/docs/plots/bi_c.py b/docs/plots/bi_c.py new file mode 100644 index 0000000..25a0a3f --- /dev/null +++ b/docs/plots/bi_c.py @@ -0,0 +1,2 @@ +# Airy function Bi(z) in the complex plane +cplot(airybi, [-8,8], [-8,8], points=50000) diff --git a/docs/plots/buildplots.py b/docs/plots/buildplots.py new file mode 100644 index 0000000..aff7352 --- /dev/null +++ b/docs/plots/buildplots.py @@ -0,0 +1,22 @@ +import os.path +import glob + +for f in glob.glob("*.py"): + if "buildplots" in f or os.path.exists(f[:-3]+".png"): + continue + print("Processing", f) + code = open(f).readlines() + code = ["from mpmath import *; mp.dps=5"] + code + for i in range(len(code)): + l = code[i].rstrip() + if "cplot(" in l: + l = l[:-1] + (", dpi=45, file='%s.png', verbose=True)" % f[:-3]) + code[i] = l + elif "splot(" in l: + l = l[:-1] + (", dpi=45, file='%s.png')" % f[:-3]) + code[i] = l + elif "plot(" in l: + l = l[:-1] + (", dpi=45, file='%s.png')" % f[:-3]) + code[i] = l + code = "\n".join(code) + exec(code) diff --git a/docs/plots/chebyt.png b/docs/plots/chebyt.png new file mode 100644 index 0000000000000000000000000000000000000000..ce78cecc25022efbef0aa69a0fd84f2b909f9575 GIT binary patch literal 33025 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 MiOY*sis<|Oe}>9x`2YX_ literal 0 HcmV?d00001 diff --git a/docs/plots/chebyt.py b/docs/plots/chebyt.py new file mode 100644 index 0000000..4cdc0a9 --- /dev/null +++ b/docs/plots/chebyt.py @@ -0,0 +1,7 @@ +# Chebyshev polynomials T_n(x) on [-1,1] for n=0,1,2,3,4 +f0 = lambda x: chebyt(0,x) +f1 = lambda x: chebyt(1,x) +f2 = lambda x: chebyt(2,x) +f3 = lambda x: chebyt(3,x) +f4 = lambda x: chebyt(4,x) +plot([f0,f1,f2,f3,f4],[-1,1]) diff --git a/docs/plots/chebyu.png b/docs/plots/chebyu.png new file mode 100644 index 0000000000000000000000000000000000000000..591bb0e6fbf279ab6620a41d870fdcbb3d2553be GIT binary patch literal 22495 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®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 bDf%z}%x-Psf+LTW7=Xaj)z4*}Q$iB}?>c*+ literal 0 HcmV?d00001 diff --git a/docs/plots/coulombf.py b/docs/plots/coulombf.py new file mode 100644 index 0000000..16a5344 --- /dev/null +++ b/docs/plots/coulombf.py @@ -0,0 +1,7 @@ +# Regular Coulomb wave functions -- equivalent to figure 14.3 in A&S +F1 = lambda x: coulombf(0,0,x) +F2 = lambda x: coulombf(0,1,x) +F3 = lambda x: coulombf(0,5,x) +F4 = lambda x: coulombf(0,10,x) +F5 = lambda x: coulombf(0,x/2,x) +plot([F1,F2,F3,F4,F5], [0,25], [-1.2,1.6]) diff --git a/docs/plots/coulombf_c.png b/docs/plots/coulombf_c.png new file mode 100644 index 0000000000000000000000000000000000000000..bc34dffcd48483f924a525c2e3e4d5db3217ed2a GIT binary patch literal 39897 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 literal 0 HcmV?d00001 diff --git a/docs/plots/coulombf_c.py b/docs/plots/coulombf_c.py new file mode 100644 index 0000000..96611b5 --- /dev/null +++ b/docs/plots/coulombf_c.py @@ -0,0 +1,2 @@ +# Regular Coulomb wave function in the complex plane +cplot(lambda z: coulombf(1,1,z), points=50000) diff --git a/docs/plots/coulombg.png b/docs/plots/coulombg.png new file mode 100644 index 0000000000000000000000000000000000000000..bc6c02c0df220c3ea1a925990cb7c69568cb1db9 GIT binary patch literal 34197 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 WFXxDyp$`Q3B`c{YQ6pv)`2PT1LJtc7 literal 0 HcmV?d00001 diff --git a/docs/plots/coulombg_c.py b/docs/plots/coulombg_c.py new file mode 100644 index 0000000..a1fc095 --- /dev/null +++ b/docs/plots/coulombg_c.py @@ -0,0 +1,2 @@ +# Irregular Coulomb wave function in the complex plane +cplot(lambda z: coulombg(1,1,z), points=50000) diff --git a/docs/plots/ellipe.png b/docs/plots/ellipe.png new file mode 100644 index 0000000000000000000000000000000000000000..d816a6b0fdb1ee8931bd3043eab787963500e272 GIT binary patch literal 24597 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 Xn`zJ)#Fhf!x+T!jG|(u!WfS^8y8iW@ literal 0 HcmV?d00001 diff --git a/docs/plots/ellipf.py b/docs/plots/ellipf.py new file mode 100644 index 0000000..a7932e1 --- /dev/null +++ b/docs/plots/ellipf.py @@ -0,0 +1,7 @@ +# Elliptic integral F(z,m) for some different m +f1 = lambda z: ellipf(z,-1) +f2 = lambda z: ellipf(z,-0.5) +f3 = lambda z: ellipf(z,0) +f4 = lambda z: ellipf(z,0.5) +f5 = lambda z: ellipf(z,1) +plot([f1,f2,f3,f4,f5], [0,pi], [0,4]) diff --git a/docs/plots/ellipk.png b/docs/plots/ellipk.png new file mode 100644 index 0000000000000000000000000000000000000000..e44bbee2ac320fcfcd808f89c311614032cf8a4d GIT binary patch literal 12183 zcmeHtcTiNzyDd3LMxrE9qD09V0RaI434-JxAd2Liqaayw9zdc>63J0Oa+o1Y8Zv`q z7+`=Qyv;d!j{a`ls#o>?c=wM>6*X*T@9y5+{e9o+wO05OHAR9Ow{D=Ipb#iMdZ>wl zf@%%?65?P1|H*$89S?k9ILj(&;{ZQCI2Lb#Yg~s%dd?^)Sew_ssE_Z1lu=M_qbNO; z(e}vP&hqxq9v=^zCnkQxOF}|CK#V3Yqm6-yidSqJxP+z=K`qb+cyaG{pty z+Z)+zH;Myqkg1N}iwHPi*H6MI#~@ddZE|Gq3JH7^55M>&7D}5=_+1Z(&pYO2Sj6fqM468re_@tz){N5A3_824~`bf2dH|oJ# zxP(0cgv&aPA1skjppt^d>%L=1N=jO{*N#Um?I+xjKQU5)o00=%UO8jqvI)j1rW zPAsv-7nz*h4+gU`Gh+sfjcE(n%~B^?bt=lY0@Gg@_7!&1w+zf&lHP@zNs6zHkhHi4F{&vynxL4P63Ur z^$}L{j(&5SFM`W}DSxI4ciLhX!#KE%00hGXJ0P?F*=3 z$-VkDej`LY>B$C%$hm{4p`zs21`y5w}@7Lc(>prJjo@{`(PGzntdo{d4;5Aq66`Zlt=+>4l2x$_{FTk|@;t$NO z4e43zuf=n@I(L3a4DzW&+BiJ?kFI~ve~`|0(X5~s1Q!4IDJX2L!D3`1__LwffMeX> zTXw$MSL_)G$&2~ekWkzIAKg0p!&TWhf@Oja|I7S>kRt_7;?_@qZsq^%7KEg+@7ewu z*yc4xmV|2i+e;ekczRYo@}De&6skgm1m*sGE*V*A>GY}ezbx+T-BkiS>+Fi(7KzRs zoDdKb8-|J=RP#YJ5bLngvY$%ajZ@dq5!3$?4&)rz$_k{~Mz^f_SfAc=CGH;h%kPgP zN;tn);d&uL<%kHT3)ckj4?3|4X?wn2)jY)}xb-bxDFNkeWaM2@(X{vP?@ZU&8mxN$ zzSzqxrh&5{cjF5BYGz144DX8}+96R`=E1I(mX==iD{@OqOMH5X)Lgl+_|(*AeyztW zX0o!fkXwQZzxHCCH@yTR)5mx5P2k6|^shD$ts*sMT0(xa_KgQ z*Ubsh<@fvWN^|+tCJB-KZ;xejHyBcqlH?T>u(S#v=jP;~r2LiCiF+s_e*E|`>kO8} zza{YYPj^S~rkt#)3 z*6Mc!TGp=tvN@Hcmq4CkEOgoT0*NR;_MFZLDI0^m3i`Q$gWP$z-n_xfl|iQx<>QS; zXW1iFwEb;G89flS&>#lCcPfnr)i<dF*>dARwlVfUVmfqTmci?h-Dbx+bs|>PZ#kx| z&i^+`nUfi7cI;c&y$R_3L?n)%4H$RfYGWNvz_L@W#9x5eTa_t! zLT3%hb4#oDe-?z$@AZ@v?c};Wll)d+;}pAAEHfD)M$I<;smWYv2G4LhJBQtA9WgR} zoDLnwG}<2B3e|uadY2j|I$fStSd)C~9Nz}l8Z~bQ#J2^T4bkegY;+KD#KcO6GOMh- z1#;IogwTPq*Foel5Y?vTCI&xi4lxa)K!rKepeAud!6@uYWHJ8w_DpSm=yO&Oo1%ql z;*$BvO2Rti@=(j;2eas%5ZU2C-w~yS(Xr;HCW)kK`JWYTtl8VxxKq2~>cRx~M=)v< zs?l~*NHmRZh+PPsxanZ}I>fkQYN9mDm)4g2#3p!#4p%vx_w>}{I#lPu2I=b>Z9_64}v)eqO$E@nEfpqy4`r%)Q* zI^C1=Kp64O`=-z*W$TJosEa9Gf}DwMo}B_Uy|@_wT3@MO^w0+b%Y%T@po_|5OfwnQ zyru?AB50YJnO(NN$0;SSpgz&j=@OhYl?V{(4tW{57O<>LYp|FcXtvzTTWTR|+2L5UXl(6i%J>9G5H$VmE z;EIm>E>(Aa&>!r%_{!n^xBJ({GCUDG;QZW2Q&SVgdcH-HM%XUOtLb=Ep?}kPFunJk z*;kZPDBNPWs4s7;gwx~HQuIM6SvQ^T{q~(v>=;p&HM}MI0fJ9CkPQMsXYgQBuU6bA z%xs^^%d%ZVEi`|cLbWVq5n8ME5ZqPR-)9Pxm6a9Z8*ko&w4Ocd3Bttd>FF`uU+P4$ zc<~~Ihm>AVslU@10J3G5j*gBNyR%9sna-xu0(=bK_MTdh}Fdl!g~o;Zt|S3p0q z$0d3pw>HaJGviRNPSvxn+#lKn`(3?qF01s107sI^G9&R9hUQ0} zp2t&<)kHCcUiVmid&}z#Ygc4A-OT*6CgW6LWr09Ll8D)k9GT#^v9Yj_n`*boZrr!% zA?}K%QA}V_W#i^9(+rkEZhi6ma4>xtRLn#(eF(`SBy5!kipwfODHoKC+nuCtk1dPvYvXg!}$F;q@y zGxn%(JDh}dz^*(TCxiO?X zo7NZEo*Qr6T2rYmFMd zwq7#(f2Hb6w6wjw4hv(4 zK*Ke^f%}y*?W5`)lO{7#@9sbRB&=PxJB{f+iyDljBoa$~_cLMu7z@!;gk$2&nXw;zIcSYmJUa?Dx#U}m?)E}}#2@KV4}b`2p*OBhiK=X8(hd*T zFLMFf$M=k^HWRVi3b!pc%I!bIhu^z|M zRi~XWhW9kFHWOc+!M&&sD{++($ghD;GcCTnJ5tLZYhi8g<#`?7bK)f=VgNu1z!KUO zUya;ti#Z*y51i&3a}Cvr;)SXS2i-Nc6&B$xsXz@L8a+RC7=Joqy8)581YIKMnP-Dt zVj0Uky+Mb;-p7fr#)ap5s_M61PhBvP4lRHhbxOXO)v251KE{03LYvH$knU#L;8sdy zMnrX6>mo{Qb-Q+PNSuS4Z+q(IPOAy@_$iyr7iaUmc$eDfTP34pCW5^KhXn(`& zCqe5g;B!nZamI$vz%Bk6vB5lgZtPFjZsrXp3Mk?9+(aHFIP6T58+*ubOHCPwG%gr3 zRFG8VW=GSQb>5&h;xStOWkFSWTX z$Ap2s3vAq|iM8-iXzn}CgyW3)N4bZe%9)B&c*0iuamx*r+WV3m5ObB>ZmoXg%yD*e znX0s{^UmM|!7qhsNg7UiX`J0a+&Qc!2_l=<4q@^n_f%-F@wyn8T^)ubpc+sh_?}4feEd z+TPLSn1mnBDTa6YgBf1eC3D?V{CRw+$nef*@r7lPj?C03J(+h5E7|z+^5#GWtN&wpZq_&&&>u!;x~^&QD^0AX^JiR?K{oO?Rr(#X&#Sl+m? zVxCr@Qb4A8daqlKDCtdf7=B=`9P?u06U{rQ=VMxxdNz7#hDrWYmX(-KC(C4L8Uk&m z0Ae9~cv=F$P=YS%YBA7zK!LL44`x=e_;%5b=M<2y&s5@ns-o1C)AWf^oMvqx@J05U z-IfIBQvKeEq~-2zyR3VI*_0_y^p^fUqlKco>_TKlwhB@#7K{rd={n0s>E(K|*+tN(zwouwt;*KB@lkiT*ADpnfAsgs-!{WCp6d=r1N z@@Jj_3Yqyjw553rmygfFrTI1W`6Ysn{EoPRkRX-BGlj_)SOc-y=4egvur=?fQXu2?F@$z>|R?&{Z{S#rVFwjk@H(12MvMmFz2jy6J(%$?*&@vdwZ`AK&F@NV7OT!w|TYUhQl{ zq3KG7^72&ud!@Xewqa&sm10x?d??s$I|vD$rU`6?9bs@dQYpMkO#JG-+t$ycRVfl2 zYEsJ4Y9mS4V=BVaNyKVqY7&~w0IrAi9ZV^_cHlBFpI{p3?KL~znvyu^xmyy&dB;HU zCt23GBYlN$-#q$G+~k5;GGz|TxcTMBfWF;xzTNJl_W|YgX&;*Orn7Y`r+IfSKE1qX z>nGO|pzRt*7}DIQmQhdT4J=g67^$^4Nf)y1gJt`x>F7|@&N|^4Z7#$!Px|#!cU>-c zI+-nPmiK=)$J8#HsJ3D4_s6H_`~puW_d(}rVS583=dh?E7(t~K)1GLh7a9;^E0)Xb zrvSqzJX{Bkw`UN@a{wSUbClJ!Z%%!AF?LEoBjnLd?_DD7ckZ#f7{(COusfo{Q!1{P z{b8rsk**X9qAWosbAf7217Sw4Nw8`gm?!C|lYq^d=ZoxZ&seDqVpt`5b;EZeT_ zjD$T@0^GQO23C1oa>7VJ;{{E7;RTJN2Bl>0?)uLfL(V+wuQ9I9+dC}GF(DxsAm8Ju zgIxyS!{GyXV?um<`_|N#m&5Pn$4=|&MB)=Up|d!#@bAlS^i0G;f8fQkPmL}bWTf48>$;ynQiif zXF7iPQQsL!IoS(FE(AZ8x}>^1EPBmd&-6Hq5K?2ya#yjv>gANro=j>7QP`+Dcx5qc zF-f;luTf~AL~k$B#A4*V{Gc%+%N;s}SZ+Cw-&103OJuj;*2iG*e|$_JOuREeFj0CJ z9*CWz&QLj1#VBEhR${EWwlRL+e^=1gxU#bH`n=GW$WBH^wm^D6!|Hlh(R-~kUQcbb zFtq1=&ze;u31L{8h~piVWKJ_+N?mrLSx?v=nw#GN2)4xJx)kvO}1(T~Y$~=YWrIiGAsfFzFW~JiKQbpr59~)AVdilV5dhgQM6?G_HLuoIX zglzA%*V&u&Rw_gX!KH3b*CZvK`Xj`rv=Q|%rjjp}D|M?BQ;!kxDhok)`Hep!kTQ=S zD?o^(PHrpGxsm~^{d&G7YdKaL1oR86qjZy+UyDuaD!w~AdmJu79P!XVh@j2Kt(IPFt*6GerGfUme8__zJ@csv`j!%2GS33$_*OK5Kf6r zmOfp%S=PAIAcj1hE@#>r&QrW*WP=eyS@FVgw}<-TC6C>91~Bf;-XzVJ7|sieits*s z=TP=zX#6wF+ODS|ZKgvIZ33%GobRO%`_I)Pl;~F;igIQb8YM0pbJ_7c(=) zFIIy~=WsBObc+7Vq%!o*>UpQotMkKzv9}yy`!BNPC!=Hvi4t3JvRTRwiPNg68o3J&ef6m=Akn%Uy(lJ#w5 z2EKsY&65*3I4V?q$}pdU>avA1*YFg>5wSBe2A`2k7FnFj506S$jCvR6q7b_E0NF(Y z$b$~^zMAFq;uJ%oMNMieA>NV?EFa7y0kEGEYH9f{Mb}l}{1jF>{A1_gEqCDf^toGS zmfqK8u;xTObsjFp%&sIPd_HJz+JTiSX%N%1q0VK{zM9hURJR^h_ERPJ5bmxN{<^cu z8|TG~etq)B(W(zo0UTj_pS%0y8C>s|Zku|5eOb9w6~ONE1m(?GU#ygHSM2?RTQ8yq zZmQ>Zs|xofO1>!|)P15C>{hDg!dvOvIKXV&+Oj0%w6fO6t=^cBY3M32TM(JZ>ubo; z{+4{+7h7GLKwV1jv!1yosiD`0#0L%TL=*PXKt0rs$+1;$|Gw<0c@-V9)8J}$a6sDE zdbJp~kv-E;Z75^tdEL_3+u&5)l2yrhI#;Oe^jF*3Hm+J?zimNMkgtxArq9-WQLn>y z+g*y$@$(a1Qh!maB{Y7Qdav4(6kC%NJBRI^>2E^`Xxe%F7^53v=nBY4PQ|zg(gpFB zS_{1ly}&jAgMLfkHV;?y5FFMokm(7}$B0kEoixH0p2w#&e)btVPlHQmsTf8^rp+bu zum#+o&@~5or2G+QZZ24aSzdOU_m~EN(GNgAn%wte&yS_I1yV&c+(I35hF0}!iqBtI zsmhvl6Q&V072TCKu|w2&nJX)glQ^twb`ZsKD~iljOk-c4+B@iHmzON~1GUrwKWHtc zy#VNuskwzgVar!TyMC-+9tb44nny+F(5=FQgzI-$QZ`7H|IVc>nChC>wS|j z$MN|G^f1|RhTdM+$waNmijN^WfK*wLy|cgXuv72kbHXTb_^t%~V6wG6n5eJMOLz{0 z6|^2(6%4>C68o)m6ci$^YcD`bMo2*cA2PRLVXlHPb}&O+*8qw6VYkNKgrtg{{b>i{ z?cMV%lH3}zH`h|=6FfwMda~6L!1^5i*gIL)#PVFD)s^qxe0 z-w|?`BWAZH@>0s_HPp~|nP3GzUN{I}OP#{i=9Bk^xpl9S4AVT8!b!(oWE+7gx5~oqu2=F+CHdK#(NJGpo-AKB z8%I*`ZAc#UG&2ll%=Aq1ng_w!Mhf=2rFC9UjejZP{4U_(kcC+towFHmw5j5;5+@n9 z3*>}J4CqZP%~`ecWnNNJ#v8TcZ){gJ9G>VlzWkTW58ZKebkgWA_vcc{hWgefN?{QM zp`u%$7WrLlT)LvmT88J@Ox};8=C4&oskJs^#_A9$1~f?jbM(tYRy`q-d?88O^v9Rw zjAcn8+Fg2Kga)S_3=5V0D?A{8Jua{o3~{!yvI4|y*`H#YR0AL=Y(#HU#>MFNYK!&|ud*T3ZpC(S5vs0L6Y)oXr%=6ec{+C+m zQ8$3_1q#PM0uq{w{m4TruM#7?jxag3m{~#^EFk+vjiTo8;{C5?MeD@C_i$qrs58_nG2#}`!`gW)+nQ^& zeY-#U8Y%uIX2;YE+i{9FmlzYUW}@^b_a^U^H<4CN{heX{(aJ?Opapp6$oa~|AIV&r zDIgJ&h{#}}PZWPBG{GG^s_8wiJb9XK+we;q5@&jS{K6dj09{O4I-8@sDc-o*OFoN- zgX1}1e=zjku8yJ+F6)C|6C^+NfhP-6J|Y1BZs=O)C3yG)eQHX^;3*>{Kut;M`Z)1a znSw*NEFTa!C62#8&o3zW%vU+n?hhQHerfJ9;ofW*_Dzh9QLxqCGy0fCi}1TZ=ZT4A zDATp}U;x+CMJ<(%>?H-jbc3Rqf>bN(XgULe!+dq3C9_1C+8{D8AWjouk0Zk&vY&a}W@R#jt34$@Ig+ z!`q2ORHZ79Uj6Oj0GuE;k<6HY&>$;|#{XhycG(xe2Os|uNbhYpH~#WG%{q@IF*-i<9Blc z@Dkw<6d`z&+!iN$ivyXW3BWNk>1rbSzxOlz^~hiGejRd}T3SmrX&K4?R{;Jgi2QV(y`>}y z>xiNgPwpTTkFpv}AO8GY@jpxow*YMP|On06amxRjh{;Xx+@PG8;(9>!~=A%#r8M&3{shC{4XkjfIN_n)G0Gm zDpJex*!tZ4NJZuOYG0BDI5IL4PqtPKQ2vPiwbTJ61k;8201gK{d#JYs1(TwI1E45y zQb!)ZN&5Qw&dE#VA|fMyGulD6cSs3>efIyZioVEWW?|u4iA1?vVj!l`ZnLt6X{k#J zZg?8hyk3mrA418eFa5w`^yJCcPvtGQsuZGsnbNosK|w(Wad3dXL#qjA0qm`U4{C24 zO8`bCx2F?6_ZWbvu_LL8U7hVzwf0)z2f3TF+1@Z)#jBm(d5f4bAS zTeln|ZarIff6*be2;>!^5Pd6Y{t6;Tv_JJCTLb7DKz!EYAD;%)JiYc&K5KAczlIHf zmq`QR3UDdVX78H}3}C=!iOx66?+tgmkZj$qnK=Qn6FVp8N-t}6{>#zARYd(UD6GS* ztmW(#;M}x^Hi;&%s$=CRx(^CYLIB$+;GWE)E!KvC5)wKp%sSTRTcwQ}Tmah@E^3BC zLUuOT$bGJ}DBG_Ox!Y>u2dzdJb_z5K*-l@3ysU;Ydr1^&+ZtRq`*vsQomqhaUUzH( zzyl!B@;?}r_!5;v$3=1TJ|SP8Ej{4G#UAWot>Q6*w{?h2GTNJC-gY= ztJpUDCZX_Yp?;$rF~lFdAgX~5-~xC9P?@^*_ok{55|CSmAiyY#K567`G*%CA7tTKl zQsR@6I@P5wzBvNgxZiv-z#CrWKD86TY6H#kG;Z+$Q;fOgdE5)=`j}q<_N({WB`4xw zx+U+PR|qX@g#qhuFZT(~I~MrCYJc92`=Vs|V?RGXPj%)4zZ#oKyX-+H%HknOV9NpS zSq;F7u+SDvE#|@oxWiCqc4#J{&M-7FE-u^H!8ha_Iu#C!?f3Gkfngk6Am-8eq{R-1 z_;Efu$1ZppS0m^E7akb}MVG8i_So3W5ki8Mm32dXd*eLF2h5>0V^xo*4WrNf$qfR$ zU;w^NsLgJD#I3`#qfOD(M6GKVE{gw22gCOkUva?sy9D*#wcHr{P-0LU14PvRdS0x~ zhZL+qU&LHDKpL)(77+>0`|yKcpRI@Q*fs4d*tMKmk>0!sFt6qlC3>_@J&ZD&$y!;p zp@{Pd1N(yF;BpE9wu|@wL@~{en6WW z6M%6B2;p~#P-zS^30!}EzP@xI!?VsaKIo#ccL0_x4%l(>ltxvPSfyrl5tvmPJk$c# z#QXdEhHg`q1F84rj?fIc+9H83>`&J+?15rhGbf2({pNEW{n$Sl1Bob@P5@@fyuWKDzq E4>c~V8vpBYnNXidFD(_ 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 literal 0 HcmV?d00001 diff --git a/docs/plots/ellippi.py b/docs/plots/ellippi.py new file mode 100644 index 0000000..894d1a0 --- /dev/null +++ b/docs/plots/ellippi.py @@ -0,0 +1,7 @@ +# Elliptic integral Pi(n,z,m) for some different n, m +f1 = lambda z: ellippi(0.9,z,0.9) +f2 = lambda z: ellippi(0.5,z,0.5) +f3 = lambda z: ellippi(-2,z,-0.9) +f4 = lambda z: ellippi(-0.5,z,0.5) +f5 = lambda z: ellippi(-1,z,0.5) +plot([f1,f2,f3,f4,f5], [0,pi], [0,4]) diff --git a/docs/plots/gi.png b/docs/plots/gi.png new file mode 100644 index 0000000000000000000000000000000000000000..53e22d245760477d0a7fa98df50c3b08ad4b2393 GIT binary patch literal 18186 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 literal 0 HcmV?d00001 diff --git a/docs/plots/gi.py b/docs/plots/gi.py new file mode 100644 index 0000000..9679f05 --- /dev/null +++ b/docs/plots/gi.py @@ -0,0 +1,2 @@ +# Scorer function Gi(x) and Gi'(x) on the real line +plot([scorergi, diffun(scorergi)], [-10,10]) diff --git a/docs/plots/gi_c.png b/docs/plots/gi_c.png new file mode 100644 index 0000000000000000000000000000000000000000..d627aeb7f9a3c429a13053b893b4a96db3dca683 GIT binary patch literal 57729 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

  • Fo>e9s9=Ab(@?i0qd0< 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≶ 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 LbEm1Mu95!%lO#i( literal 0 HcmV?d00001 diff --git a/docs/plots/hankel1_c.py b/docs/plots/hankel1_c.py new file mode 100644 index 0000000..9475cc1 --- /dev/null +++ b/docs/plots/hankel1_c.py @@ -0,0 +1,2 @@ +# Hankel function H1_n(z) in the complex plane +cplot(lambda z: hankel1(1,z), [-8,8], [-8,8], points=50000) diff --git a/docs/plots/hankel2.png b/docs/plots/hankel2.png new file mode 100644 index 0000000000000000000000000000000000000000..f3691442c5f0dfbe9932bc5d9fedf9f2b006edd1 GIT binary patch literal 27100 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

    fMRStTH4nwxc6jewEhlHFR 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 literal 0 HcmV?d00001 diff --git a/docs/plots/hankel2.py b/docs/plots/hankel2.py new file mode 100644 index 0000000..7f1d670 --- /dev/null +++ b/docs/plots/hankel2.py @@ -0,0 +1,6 @@ +# Hankel function H2_n(x) on the real line for n=0,1,2,3 +h0 = lambda x: hankel2(0,x) +h1 = lambda x: hankel2(1,x) +h2 = lambda x: hankel2(2,x) +h3 = lambda x: hankel2(3,x) +plot([h0,h1,h2,h3],[0,6],[-1,2]) diff --git a/docs/plots/hankel2_c.png b/docs/plots/hankel2_c.png new file mode 100644 index 0000000000000000000000000000000000000000..4eb83ba2d078b800ead1ae1adf72798f5b49b6d3 GIT binary patch literal 35840 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 literal 0 HcmV?d00001 diff --git a/docs/plots/hi.py b/docs/plots/hi.py new file mode 100644 index 0000000..d8f3423 --- /dev/null +++ b/docs/plots/hi.py @@ -0,0 +1,2 @@ +# Scorer function Hi(x) and Hi'(x) on the real line +plot([scorerhi, diffun(scorerhi)], [-10,2], [0,2]) diff --git a/docs/plots/hi_c.png b/docs/plots/hi_c.png new file mode 100644 index 0000000000000000000000000000000000000000..bb580ee9d8ac6f2f30ccb8504deff1e06b0022c3 GIT binary patch literal 47358 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 literal 0 HcmV?d00001 diff --git a/docs/plots/ker.py b/docs/plots/ker.py new file mode 100644 index 0000000..19a4bdc --- /dev/null +++ b/docs/plots/ker.py @@ -0,0 +1,6 @@ +# Kelvin functions ker_n(x) and kei_n(x) on the real line for n=0,2 +f0 = lambda x: ker(0,x) +f1 = lambda x: kei(0,x) +f2 = lambda x: ker(2,x) +f3 = lambda x: kei(2,x) +plot([f0,f1,f2,f3],[0,5],[-1,4]) diff --git a/docs/plots/kleinj.png b/docs/plots/kleinj.png new file mode 100644 index 0000000000000000000000000000000000000000..098c79adacffe5b59bb167925d48035506e83351 GIT binary patch literal 77369 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

    L$h?soD`&H5MDFj*m)atO`7n70tj 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 ZiqN|Q(HZq;fNN$MJYD@<);T3K0RVHJ+!6o) literal 0 HcmV?d00001 diff --git a/docs/plots/laguerre.py b/docs/plots/laguerre.py new file mode 100644 index 0000000..8fe4025 --- /dev/null +++ b/docs/plots/laguerre.py @@ -0,0 +1,7 @@ +# Hermite polynomials L_n(x) on the real line for n=0,1,2,3,4 +f0 = lambda x: laguerre(0,0,x) +f1 = lambda x: laguerre(1,0,x) +f2 = lambda x: laguerre(2,0,x) +f3 = lambda x: laguerre(3,0,x) +f4 = lambda x: laguerre(4,0,x) +plot([f0,f1,f2,f3,f4],[0,10],[-10,10]) diff --git a/docs/plots/lambertw.png b/docs/plots/lambertw.png new file mode 100644 index 0000000000000000000000000000000000000000..0d96a6265814e0d9ccfdeeeaef7ad5047d5bd9b3 GIT binary patch literal 15860 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

    H{g+-u5vrM<4t~ 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≪M5 zJmU%S4cz3VVai)44$fUM&1E4Ta(-VrRzoCIcGp zS914Xs!AumP^-G@F%t6i%5wCs00?zAN0GczjVE)k*`&IF{+*bu8oBAPy(uwdn4h+m z1g#nHSvl(2*W0aBY_Fccv4V@(>6*x8@bd@1|T!<4%e-a%8DMJ~he zR6BY8>hk>e(yn9Y8J><*@3~9ZRZ;D}$%L;!yK`G_^`^+S=jx;-BqYq^*@>~SvE3yj zBO~Q*IAFXGSX)OYI+%yy$;Ew}?OIdI6^75{3Z|Bz6`}VGUY6PEFJ?>41 zf7>QkMJG6xJm(PhX>7D=BW08G6t=yrGY% zv@jS9|9JbU9Q;)}KA;rqu_D(G^(Z#^@V=GIs;byXX353l^NBhSq4p@&%peYw8APXEUr^WzTa#qVra zb?!%t`nekj= z7z`$)%}OEQs=Fsil81-K3O`(Y5+frYss{9?2HlBoUwX($F*rM`%}v`Xrlp+GpL`rL zw05vDh7baO=!zHEn5YBcpSr@-sM2fb^jC1>?^tgkgkKd{{I^vo6yf~-X+mP+ z;zXql-W*@PdbPH?$`PlryFS8a2R#hUuSU!U$E(7eoMMi*=inzh3yz~Qva)2KlnsrH zq+?z$U$2pY=#evZ9tH*mqT3*0MnmbZh4^du2|gee`2Pw1`nfvxziBt@fAmN&`y>55 sXCAt$&!08((^X%XX^`CfG~m{)xW79UMgnR0)BFh3Rq-evWy_EM2df-*N&o-= literal 0 HcmV?d00001 diff --git a/docs/plots/lambertw_c.py b/docs/plots/lambertw_c.py new file mode 100644 index 0000000..7562880 --- /dev/null +++ b/docs/plots/lambertw_c.py @@ -0,0 +1,2 @@ +# Principal branch of the Lambert W function W(z) +cplot(lambertw, [-1,1], [-1,1], points=50000) diff --git a/docs/plots/legendre.png b/docs/plots/legendre.png new file mode 100644 index 0000000000000000000000000000000000000000..a02e14ea8e46ca7fd375a7105a3a63fc6583159f GIT binary patch literal 28034 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 literal 0 HcmV?d00001 diff --git a/docs/plots/legendre.py b/docs/plots/legendre.py new file mode 100644 index 0000000..05723a1 --- /dev/null +++ b/docs/plots/legendre.py @@ -0,0 +1,7 @@ +# Legendre polynomials P_n(x) on [-1,1] for n=0,1,2,3,4 +f0 = lambda x: legendre(0,x) +f1 = lambda x: legendre(1,x) +f2 = lambda x: legendre(2,x) +f3 = lambda x: legendre(3,x) +f4 = lambda x: legendre(4,x) +plot([f0,f1,f2,f3,f4],[-1,1]) diff --git a/docs/plots/lommels1.png b/docs/plots/lommels1.png new file mode 100644 index 0000000000000000000000000000000000000000..555a7158397061076440c0ff77b7f05bf5b3f07d GIT binary patch literal 26654 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 z zqRVzyZY|zrZmo+5GZ=BG&ohFyH-HPQwSB6TJ=D%ehN|=1jn7fI8PIai6)Wm#=1I=~ zVzy!ALH4CX?wFBgZ6D@{QC6L#q@w$@A%?v8>Z{ z^Cg5nsE*{a*44m!8!nNLMrt8XYQC9ybQH1+jIa`Gc5H(2FoS zsbCn8%#VvAbCp2@>90h1-0(XWnVp}P%vUj4X>e!?d=`Y9Z^Q$XB4(pD!gk*qNe2g( z28R{I)K=kbb&zsjS18gchvk6f0J;VArfPKEcOP|gkLIa88U+BDY9RyhgMVIIzW zfb*)~ZK7JG`piM-n_$NG1fr6cPCMa@OZv7Po4x#Llonai@rbPOhhpM*!+`HA@)g1E zVM5TB(8N=kl9At`Zfw>~&?!=hY6)j(q2D9CI|s?9M)}LB!R#=&L5o3eXuv!d<6l}n z7#O6i*BSxf6J7kwRxh?ZjhaB?$6NSLsEGP_PbB;!WXVtfUL4A&TlP!ec)iAZqbAf+ zWkaLqKp>Ylh06;&qagL$r~+c=QK0wkfgGj_7hF#j+JMxCu={65YdPselnkM_;Y1LS zST)}DN4Dx5Cm;#(`WT_9x%yXho3P2T#wI9t@c=#e+}+sy>MTMC+5xZGF?{kY|B?P7 zOyDOx0b;|w;Tbh|_=q&Iyqd(7lB6lXFN@fN5&*YBN0U<;fJ}q zTlD*lnB^z6V$lH zy8Ww*)uX)$sRJ#V=PLszUEFl!iiAH&tn>AeZUMBNa} z%=KmVXm)>NKszn|UxRmq5=Xva+d+7Fipk$9P1Wv?AA1*77ErkNYSaw*q z^FTbi0c){yx;(rBqs?T@KuwKd_#auemQ1^yo74WEAvn9k z`d_$h9Jy(efztm7gX;B!+_PtflKPGN$FHyZ%95r{J931q1nU2-bzaygJ)~m)-=>&o zBea`z!3p1OhSIa}I5^cgt_hB23lU6BP5l_z8*9MB#dV*@rRf6_drm|UBE$aEFQ&LC z;q9PF)KE*S>^bA?q*{A<=Daj`Z zFU78xwl?9mn|b{)(M$kOWaN8915=FB2=I41I?4}LgJI!SG&E+vv?_zZf0q;q>h&DX z8ThIHYY0OBX9@8T!s2-hGB!3|?Fe`e!)5&j0fA@5NJ}dMlp9D4Q!D3|$t{(yQpa%M zhxeZj`A(0E(lgv#Brvx=7O*_bfFFGa$i<~?jzyUr1Y5Pt7_ng~1J5#mRpOrGW-_C$OH{Rl& zN`7nx3IG(CeOUr-m}F#*=M0{L1M%bo>+OE|Q#S0*o8n5DyklFHcnUnRYd}#0 zfS3REXa=-1#_)BN5nkUWopMt@-uqS5%Hk$?+aPQf+N*egTE!WKaH= zmlt{l23Q!IdOe|u{)>M%H|-!UH{f0|EiHn*y}kUq^;D&J?(Xi~MgWc)sFcz*G7?jj zlgaPA2@^*q5)J%nG)rJ+eWscTfIk2V`P%F2zF-z+y*gwdY2Mk*t>+9V$DgMOp*Dox<^A&^~W^X*tR=+JS>a5eoeo*jE zBaQ6Y;-~jV7~68@M@P|~1X{3@=ls9-@ky+5=;wi1Vp=&2@KTUbmaddE`TBnV9G^e; literal 0 HcmV?d00001 diff --git a/docs/plots/lommels1.py b/docs/plots/lommels1.py new file mode 100644 index 0000000..327119e --- /dev/null +++ b/docs/plots/lommels1.py @@ -0,0 +1,6 @@ +# Lommel function s_(u,v)(x) on the real line for a few different u,v +f1 = lambda x: lommels1(-1,2.5,x) +f2 = lambda x: lommels1(0,0.5,x) +f3 = lambda x: lommels1(0,6,x) +f4 = lambda x: lommels1(0.5,3,x) +plot([f1,f2,f3,f4], [0,20]) diff --git a/docs/plots/lommels2.png b/docs/plots/lommels2.png new file mode 100644 index 0000000000000000000000000000000000000000..4b496808e9545921663e341efea6142944f644ca GIT binary patch literal 17657 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 aVvE!jrbnl0X*pmFj;E@qp@5RJ4E-M@p%%UX literal 0 HcmV?d00001 diff --git a/docs/plots/spherharm40.py b/docs/plots/spherharm40.py new file mode 100644 index 0000000..de13991 --- /dev/null +++ b/docs/plots/spherharm40.py @@ -0,0 +1,15 @@ +# Real part of spherical harmonic Y_(4,0)(theta,phi) +def Y(l,m): + def g(theta,phi): + R = abs(fp.re(fp.spherharm(l,m,theta,phi))) + x = R*fp.cos(phi)*fp.sin(theta) + y = R*fp.sin(phi)*fp.sin(theta) + z = R*fp.cos(theta) + return [x,y,z] + return g + +fp.splot(Y(4,0), [0,fp.pi], [0,2*fp.pi], points=300) +# fp.splot(Y(4,0), [0,fp.pi], [0,2*fp.pi], points=300) +# fp.splot(Y(4,1), [0,fp.pi], [0,2*fp.pi], points=300) +# fp.splot(Y(4,2), [0,fp.pi], [0,2*fp.pi], points=300) +# fp.splot(Y(4,3), [0,fp.pi], [0,2*fp.pi], points=300) diff --git a/docs/plots/spherharm41.png b/docs/plots/spherharm41.png new file mode 100644 index 0000000000000000000000000000000000000000..25ff1e9fcde75bfd4ed9c839b29f1f4cac6abcc9 GIT binary patch literal 37882 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 B_JRNa literal 0 HcmV?d00001 diff --git a/docs/plots/spherharm41.py b/docs/plots/spherharm41.py new file mode 100644 index 0000000..bacf85b --- /dev/null +++ b/docs/plots/spherharm41.py @@ -0,0 +1,11 @@ +# Real part of spherical harmonic Y_(4,1)(theta,phi) +def Y(l,m): + def g(theta,phi): + R = abs(fp.re(fp.spherharm(l,m,theta,phi))) + x = R*fp.cos(phi)*fp.sin(theta) + y = R*fp.sin(phi)*fp.sin(theta) + z = R*fp.cos(theta) + return [x,y,z] + return g + +fp.splot(Y(4,1), [0,fp.pi], [0,2*fp.pi], points=300) diff --git a/docs/plots/spherharm42.png b/docs/plots/spherharm42.png new file mode 100644 index 0000000000000000000000000000000000000000..6ba62f79db409c703c0810dfad3cd97003070dc5 GIT binary patch literal 51314 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 z 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 Nl~j|ZlpUTq+1%LYjl?g3?!vQ`rY^U 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 aJ|ZI+HdRj(X%<6(hpeQMM77wbfd2q~Xlu#< literal 0 HcmV?d00001 diff --git a/docs/plots/spherharm43.py b/docs/plots/spherharm43.py new file mode 100644 index 0000000..88f4fba --- /dev/null +++ b/docs/plots/spherharm43.py @@ -0,0 +1,11 @@ +# Real part of spherical harmonic Y_(4,3)(theta,phi) +def Y(l,m): + def g(theta,phi): + R = abs(fp.re(fp.spherharm(l,m,theta,phi))) + x = R*fp.cos(phi)*fp.sin(theta) + y = R*fp.sin(phi)*fp.sin(theta) + z = R*fp.cos(theta) + return [x,y,z] + return g + +fp.splot(Y(4,3), [0,fp.pi], [0,2*fp.pi], points=300) diff --git a/docs/plots/spherharm44.png b/docs/plots/spherharm44.png new file mode 100644 index 0000000000000000000000000000000000000000..83a712d48717b1d0ada125992bbb3b70082fb7e6 GIT binary patch literal 41468 zcmcF~g;yJ2v^5lWf)$sbMM`lf4#A;laVt%cq6G@HxCeK4FU8%hh2riKpv4OWid*2D z-+Sx*3E!-hm6gmSb7jswd+)Pvgw`7+0$gfb6ciK!6=jGH3JR(%@FK*<1imR&j!6XG z(B0%z^ss@$2OA0lKI6D38@ZvNU>^SWLRII>RYgHzL{Wjr>Um`!<#=YBzC}K6GzfGE zn9M56$&tvBsG{IJL&blFs@IzjA|?hYL=;*&TVMp%1YqEuA`1cnz|^v}YNQFAj3F>( z+d!0q&^R~&uNbD=w|<2rd{xj z#QlH%BDC=T=S6b=HO7OMB}wJ0xA(!pK|ytOtmP|Z4Gn@o8Oz!%eFbuPlD9$Cg&WFj zNh~Bdz(p!-N&Q=&UW<#PVxvDRH{c)xli`B!F)NCJc4OoNZN0$z=ap^9t;nB&en(Qz z5CI87Ae`DeI__x=baj*Jn+1GpA~`**^>ErF6E|DZ1Ig-Eo@Nswe763H!r}0Kt2(Jc z^x(ea60;QL?@JlltoxjiK?h^gw#mvYGrVUPCewcXR(wE1TEu>_By!pr)HVEfSF#W& zzX~@6GkqLdy&qO2OM}s>wH?}v#4D54w-LpWbOb;z!0clOdudFVXv^-7v(4}8C_jJx z{KBRf)rI~nP^!SsBU*)}sj2C$Sw|#OC@@oX z1pErtWWjwb(j6WzBUmIaCY>FD>0>Pb+EE;@&IXL*i;Q&A2=tts9H2kR^B-&aQmI`- zA4?c?36K?qI$A)dbo$rk=M_Y0UuMQ94zrkzj9896rUGG@RT_ z7d4>srW?xN7SkkY$iaHSONp=Fz+6E|tcUZJ9nw4$EyrWbynJ=#@#FU^4Gj(7p%*mT zB}sWk2w+J1xBewJIlfZ!u2rG=Uf};0pYS_+7ij@)0#%)1banMhpt#EU&84$XV1}dGxNk##*(y41YB8&d;PXQn33*azRKKh4+SqMCKob z8-GS|LqifU$t)yHl=y@Y0lc8RM&$A6PBg8$*{XBQUXn6@=wn8c*}y~;r|e07?NB5H zteuE9N*hpTSg8qBEa`KO3H$kb?%rCv>~KZ#Yl>wZ1@)bcV-;{%DNd#mWEPyM^8n5))&ZO0XgKQ)NDnMDBGhCOYu_&BTOnoQP z2*d+Z^ur?ofJR8<}RE}Rc0zByaq5Tt(rOkxqw*vy@FHAa{Ev$y-4OWX7qHvJsHp=>+j{{{> z^0_FH@NpJmY@nHT&d%(V_^CPRNaF@E>fk=c-!841&=3pen&ctaaf~{N3SYZNTS>mv zofOdK-~PbET7IQ3qR7DjESm+dtX>NFE`Jml-1?N6l{MPU-E>tfPHq43BQR4hM7Jgp z4XP{jm3a^C&BLrajNPvBT~Bw9-a{-2dh$=INVKG76y$1j78R&zJwQnEd;u%G50<_f((Y!P?j}1H?0~tk_5ECD+-H0%b(I zuFbQ;b}PpaKsnUqM*3Sf>C4^ZU8;S3x@l4WZ)R}5PFaS`PERMeeka50dH*sAt=5#N{$M#!hOtCI zsc5=ByF&|Ve0BeGSsgEY6Sa;k@lL@$Xqo}SkpW+`O2_DMZF%fgCFRB}&1 zDCZ>5XJ|(Xy>H40Bzw2pZeD(RICgn?iJ=Kb1AUG^el08mEO-gZEH(vCa>^Y#JpUel z^l$>nBIM`qph%@1=3`fz80*&-!VxTH3(Fw+|NfFLq1@Jw4{E)=zPxNi{4Vv(#n@!# z^2l^hpXAK0>40!a)wR} zL|K@d+iuEKQ(z=kJ{ZfmIJ$Trj_YdIMA4mUZrY;*v-M`SuqZiPxoRQ#TLT3<;%SVf z(it59i(3%A3>isA!er1J;5qj2AXGMSJjHkxNLN2+`?@|9Nrjq{dECMbHb{(-+&Exz zPbsyxNmK^1S&QAZ8gEyBK{)+u%JDH6rz{c52q@!SsskIArC15ZQ>uP}*0*o9ZK}zl zcWBZ$8NpTY-(WczjmMAUe{gTRc z+ydmvh@oPYl?^!te1E$MD_XY!DX&{7seBz#Sr6SKxeN!{->plLBaUJ{{Ik&dx2|R} z$1xZCxbofHGY*rwFOrl>Zr=hi%W|e*4dmF)VfM%5gS)#sy^r=x9J_%HSmM+%6e+ek z>f#SsC`-7#?#m7eN66r@^;IJkaSJm7=6qFV!k<^zHKQf=24{rFJ2ENxco4n>s%1=F zHnoKb0}kA}DP*zTJGan6N^ouRZ#fUt$);i+p+jdSx$c|y{+#y{^jBD+tjkM;$1)6R z;wJuY<+wdxe@D;38lV`TY@Vw(YY;H1-+5^}x4fLOB34pVRJ3_Qap!XPdH2_^Ux|{Z z!iFF1?HgXl;fr(GWj%iUVCEKzB%rWM6R7_ty{F3Vn6(ttXDKV*$W9WGJ?Sb){b9ZH zOJif>E31C&?(43T%Ud7di@AjbSLEelnh((dFr*YqjQjih9N!~TH-0o}HLtIm|2;eu zSWtgCFq*9*_s?VxsUkQmWOVb{`iYQw2f{?}ADq|dNdX1&OOi)vZBJnIe6`*IhQtp| z|2G1cix0QwwfBy(Q>UnwuvW3)M$N7Z#Z7-Ho??GAKM42?wE|u3}qF5yf zrFAFOBwf;5SX1I3#OUPR-k18QfRd-o1=FK3C$-9}tE*(RS0=LeR@spmmJ45OY}gZ& zL&Czs60xkROY-1x!&GQ*^Iw>BMAtGWU<0RIM6)>)p>=xzw7CF|i0{i(o9qr1({vG# zZ&g*S#Ms4=JD#6EPs8CkGfrGpam`A(?@b8`Y+Js6{l~;Q&Ns^ORn zpWOB0-jH=p8d_+`E&=9DT7!N8Pg+sc)ILnLZSWCnQrYYns;>^|p9p~R4+Eh*@}ev| z@!xp_DSN%cGQahB=0N?2Q5~r76Y{#jdYt-Y#}PlpOVgnrp?G53@$w@xrJS-!;`~Kg zc#GVk=b~l2arFhseU>zRyIi(yjy>6k687F;Z{!P((lXK#UK>5` zil4+jI~Q3>l%mAHijbes@AR{YVpPdn8k0_V5nYvB+tZgS`d=>hK)PC%G1)exi@$yy z8R>pX>i#wQpA3mxwMxEWnU(Mj573t|y)|3^$&R94h8gSAq~^SM59^r;@?ZMu5et29 z$m|>)tBj{sX$}Z`tr&u27~@?>$VoG_IUnpc(V9RG!e2_4Du3mG2I89smQ$I(6ze3) z;>y0LTPb}aVw3gs@R*sJdS$yf8A6wue?C9Pfu8yR0`Q8H6TQ~xx2YgSEn~x6CWG>6 zSJB4@{WfWLdnHBTnP$KJlNo=d_(c9o&;?Hjw1jNNfO$2lvBeY9nrZ1bb~`baWp~C( z9^Z4qaNRfYfey|E7%{580qQm{_c$5NvP3F6o12BVNIY#4Q5|Ztf(cxCk)8`H)D)>7 zYad6*u399mnz*V?JSl@-3i2~E@RgldJDw0gypN3oWjt2jkiH4Y^b6(*uK2#}8iHcbE8!`8Ni}Q}OpnqZ(=#x&Fu-n*qS1 ztCiAADn=9A)d}SlMVZG}qjJp5m?S&Grm9SHrp3pTVLTA?3P_c|GD)MDdF44?9pP+` z^Bb|D+>x~n;T202wyvCp21j(0*pjl6ta~2gRsHUCkG;FGA5Q>W&_OHC9Q|4=JGe3GEF|29wE-s1T`3<%Q>Paee#v6d^;a4-Ecsr;@3RF= z+NNgeFGwwrq=9zYU-4sd3Gu2EA{!ysUCK8~2iDs;P7lt;Qjb0hY~ zI5Vo;HDX#rZ@TsUp zu%n1d?|Y&BM*LVXjDSXe!SHV+o2@jT2{Jo z*?)Hi+!ShXUEpA#Ymu(?puF{Tch@p7Xc?b)aeL?Y3e)GcUD;v`{YWu6$HJV+EI*#h`&i5uY zV@4&f*BW}pH46%HR_LQ0(34{F-NaDTQb_Cj^5(2En?qyCBjr}Md)~aFq>WG3GkG*N zz0#tDl`y82I<{9SU>!Sz9?g;}F^b!*Zm2dF<0$}xw=SpBHWD+DE#}6U`F0tdU?An$ zI3zV=!r<>2JDkP|M36d{SY?KkEj>(c)W*h4d`#tk5T1LMwbuBck5%|NytS_$3qfWP8!TtC!@xfYuw_pr=UqnO0p*S zOJR-01~{Z%St_IKM0avvJusy{*7m3-Dn+vHr>3T&I%wEY&Y%+*r#^2$>b@X?fxZn0 z>?NV7*3BRXpX-R0Q{pk{nlu~CjA_YtZ&Xp@^!|X~Wy$E2lgROBtSL&3&4~nS8x$=` z+1uM!khs4XS{SxAV@n!veA46ml38k|Os32iD<-~Poupa#kHKQBjo>!X`VNoT&;M_s)~0fDZ!0Vtowk3@hh5Yw#J?iofBBW5}eEwi>3>4 zD$y8te998<4%@xuE5l~^FSP{G?{9vPOap?Cv-3%S zVcNk^@wo9vuL6#K)$1Dc=()%*`M7p*A5ej)F*Y_Hks3-GhH^&LX(2a;mXtdxD!9n~ zI|V3|IH4UJ%;j1fdfFWg_=1rMO8f~PbtFn9$4c119BU+vD5C5{{#4({S}6gJ?;)Ymq*h6-G7wjL!prhr7D#j9pdxS zDUITzmUEXw3M-EUDdU?jF1{QbAM>>DuUyFtkh%k+#I4q4T0Y(jTfNHnd?qI9xXK$& z-;@~msdvFCyvaPaBx?gxsQM|_Po8|B zT<0%KUR5Ied{TPy3Ayp4t*a}~Lh^gXzJ}+p>+J)tsFr@9*f{PB+qsQa>s5E*1Z8OD zq;@P$Z}mH&OynX5)|UZ5hJ5{6=Q_UPN~sRn30U#>BtwUjRJF{O*hbbjKUb`05+kED* zzS6KRsG_DjlYmBeYGoz!(no(75k})=34l{>i*DKgG(ypUyr+k~HTPMKO5>*Q-&pG9 zwEm&z5S4_CvywzeL6d6owaW{>pc&yzSj>HEXb|`u1JE1Cp`M;as&C$g^-!FxU&rH( zcH!rT7Sih63+SD6OL(@>;lz||w}~`cJF0=Dx}+~#F(P+98<_9R%;@?oVWlwwkGC_vVNxnf z?PVJ@>ty#k6wBp5EPK7~o#uPk9+@VJ`$>ZQETxg2Yw;G>&A>ra zM{ct_PG+UpF|T!XzTr@aDt-uB6UpXcqwFzm_|J0eOw~_grR-e}y#Fzu%|NXBMRV^@ z2PJ8xD~W^^P!aouN=MFRjTV(o8*WZ;KzFA;b(WkB~N_FHgp?$&YuQ|FU9N_6dN`ZhaWfi#eC!o*TXo z=Xp=tqZW_+S;CHg1oCe2O^nHugTmHWvJV>^gkDE}=B98p{pw||q@-jtN#tvQ z0w`Ta=6gV%j~qofr@m|U1_A-i>#(Y+mrn-YFl^{rAJXfOvJ!CeOf7&tfCGHO*XtxB zo@VC3>bZXVoPzttM#WJrF8Nhi)-cnqA?LvN&fDFr+p{!Y#UxES7sgtKT6#kS0x7fk zhVdC~KN`{6FEpY~!phA^uI|1)X@A-2>v@K4w=wI;((*Z2yiWYdN*m{Lm3h}!#!Pg4 zP|yU1TEDVVwOPAb3K{>qeBH8R>PO`ySCcP~5o5HaXo0uqdkr3rWJ}~;c%q~?7_X=f zCIkX4P%WO(Kch#L!a92P!0RGBh4w(d)DQ5OUrrqwALhR=-ou5N9V=}2YOD>ETP-|2 zMSyS>< zSl0YjS=?85flddrJ}Ca5*{(si(h6 zVO2Bd_V&iZxjsBPf}V8QOwG>+vShuJAx-@7t3ZYQH)02zKhSkeeJb7SKc)IxeG_-n zYE$*kzjx6LJ-pKQ%S58L+eFF8b=A-w&4;XdYurHYutqF!WRcd=-*OT@pxw`-JEQ8L z_cRN;EfdIpV>=-V0njYlyL*}2sytVdM`24O+WE%QqsUYgb~}wxtTWT{pSPP{o93JN zmM_3n;Q@mIp3nBsh`=OIuUy2zhxEoKt6%oS<39ZU5xD4rL>IDONZ>Z_9BE=kQJ2^E zL)YL~?6_({b4D{q??z0xV}LI(SwaKYn6>g~(Hu5A&$|`W)CAmi$EZ%dxyUpA#5a6+ z*!f2n+Vup-V&s*2>Bc^?zCyzpJ?hAtCO1y@7HWuNGwNoo!? zeXdDvnwmuav4$fz5u1sFUTmnO2#N}e0Xp%!aQFsW@$4gUE6p&?iGofa$tK38_|V;> z=uE}om&^IZ(NvcB8vi`sD0g~}<=a`EN-XIlcjBbqVpbOh6Uz&(Y z`BaY?U!pmp@u3x|EP=@fbO70WXlpsf@W+OhwtjwkjNc&)Bz1&gq4BnO>y zK*a_@w@wOpZRlXTcnoR}|3%cO0KCA1UJ zHC4S-0w*XNZ9U48W23LV8Dc6Hsd=^zDHdWd%G(MgfHn3t2+{=BF#fq`1b>MCa7zt7 z_SV`SnvyRST90YeVsGj#-y72_vB|a=%eHBT$Wk1! zhhNLo4?4J?XkS0l^^DQk-fN4(7oOHOxN{H%a>=F?yYUgC}UjX3E4d7bINKuiBWpnqc=qU4V7u=H$Li zx(i3)1-HUidty;+&?%Y>mkVn>qc^;Q)M(`A+WnJE@l_A3mtHZ`Nn~BFmVSTxY_w+c zPgKv_=KHlqJJKX}O}FEmNbWV#K+g_lCyZ{Q>_e%RkkD~k@&Nl2re2Gw?RxrzhnGWN zmSQURJvK>uk-dFGKcuj|l2@HNVgx278mum7T$!@mgjYR`51JaeGV&R`Hc{!lkNC{J zkA(n=(+aG!ZuHVn>=lm#4key_Q}jJvv=s(>;SKyebBUfcu?PFb>K#DT*<1HM*(pgg zw?^LSCk~@TKx(p<{s@YQ)NT0P|3_^2Y$j;Kh!}D|T69OSQpY5!TW^g2@{jF#LTW=* z78qS~P{w46g-}7OK9;1X7*K5wX0@+)GqyG;7K(3Le+K5E-ZKua5lsUR5%&2{26J8E zAU`0niawk&$0zgS8a;ikuHxIqc5rpfjbvk_(bq)n=xFXnbT>Rxp;l@=dE`BPil6Al z0G-2?G24kh==$M!MFa35QCCy)bJ=%<%}_Ga$Ksq^rjP!3tbYO2tr$(I{itZu+p&Rk zB9u6lnGipbsP4Y|+c`*t6NETEW+lT-siJUa3u-R;+QuGufyDyn=lx(=($WLe)$D7! zQb&1vA`yt!ET}9ALc*k>)k0vb1*u+c=zx>ezdt6oTMQQ4L3mDb`u1cBhK)08YyE3& z?4Q=Z?0q`z!y?5-zq?4#fZo<~sB*o* z^!`LL+-k(Z#YV-mH~6!lr-5DtD@qG{^|`N&StxAJVP&N~L;6W(kOE5Vi-mUWRtZk5 z#YI4s{u`&xu3!`dT+R~XLw*c!0-(&yaf2$t)mBvW%wnL=OEi zu_d+$sT|RV*wWdybrG4f^fi2Yl@QwaxsdhV3lgG2<~W6t9Vs}=23dKI%FY;d^%;_D zLM7#6ushk4>)AD$ZDn4-Dz7$b%c2{tpzrofg|q`zli5m~-Y8EkK4dx|+c^ndr%`>J zmEh#tbboyv@8`?sD!9PtDDq=a+K$bfJc602`CIoYU<*@hxkWS|&Hz5iqkb zDjsG}csM119!tl;UPo3QFcd|cw7~jJ60RhktI%w?4rWKlVYL))XB`s1vk3jubs{&T zfxchZbXgL8Jb{^n8eRM#-f&^V0J)Drn&@3(0ctbGz2#X!SsDB1xD3lLq=06{@OT<7 zaXuyhaHYg@-7?o9rtnvuh&8FLi?p5xRssToY`?n$pS#(--5+?o(3xVD^2z^-0*H>2 z?)pYaI(hI|^;&IhTW&5rH4P13tj$Z|wt+tl=b_`|UmtJJ{eO;SFj(K7xe@{}IKL-p#%O|4>~o@m%T=30)D6*z+*_T(VHXQir_V9VY!!pH&o{=!gsPZ9@v1o1Ph=R?Ew@(ondq} zzGiM;#tH~^sWWc2?|q3fKz&G?;uD{KSb~fMJ#XuZY4!OAAo@pBxe=f5`l1r(B@}V5 zpMZ6cxb^D(d);q?NnUryaf@hG?Yxf{T02o#3dQ!Lo>5$6JiK@f;mjH8yW}C$ z@pu6)$t%S9;~!vW>UTRow2 zS0X)FJ~cAS-Q(8FJ^g-!2UMQc**>$C#d&4?!}zImaC7DRFzkC#QLsGW7cB6Pt~I10 zi0`StscZT2*x!ui#66E8a>%>zaC9p`x&Rh!!Ps_y$-;+3)9@$?ybsW1w!eGN;ZQq} zvuDm?)YN`|Xi*}_TrF2|f2-oH1XzVw$Z+{S{ekRGF4^ZU&#d}y^uP$Kf!XJ`-+|-|Dbkj@9oFRcxp4uYO#183oTTh; zw);snx1%f`!2ZArkS<_D_}s#WZ|heK*C0=Etvs>&%dp?4=m5GEGsiL|| z*-b54F)wE&B`&7qeNKb-NMo9dP<<)Ddl|j=Yuc0G;ooYoZJF4{P+0mPqfRUBLAr$u zNoCihvS^QXXl9|gwqjj=zZ_U!3uY<8NMKL3&Y zLU+0L9akpzdSG_I9@DTbb0)fQhrJPBv94C*FZ{-5l!%p%`6joNj-JbEjToQG<$hO?!Q))AysqfE>C+`5+4NJS6m_;`(y1{dN z$PSvGqLx$zc~d}kZYHNAC%R4 zNR4`H+PV)uAnz{4{KNml#G3whK8r0Kt@d;6iy&K_*URv8EYqh>n$VS;7?xZy#N+y| z6jA;#*Z!U0(cOyRrJL4#x0|*X9CL9p_0M{gKAuvCl+S3Few#-qyv=bo_{}2k0wTcv z+8ps@W*a(#h;{s-R^#l7{O~TeB2eSu&vS$GJ|e$Y@AHiGQjY)G5Gu>_{q|G53Epw3 zByp#TkIF}GAJpVzbA2o{1dN6VHlVId4}L+!D)^=rQhJ|L-J zuVmEr{lSNyiLTeHhUD&uIp4QY5^Sc}Mo3}V<>~9~gp2o5~{o62Lv>v^aqSTYH`G z)-X;V$CK%g7*-XDlN|I53;58?O#Fuok+c7Opaf94FuFH4f(qGEUKCQ#iF90dhq3hp z*bkP)O9(E*pTH0Ye;F20S`E3`HQ#hMURdSIOGu(53}z4iG}vn2xzoMz`_9VXdS%@A zTD$mq)vb%_7JI@2i2+_0U+c==>d@udz0me_cxH4@#1uro^XObL|JL?$W>|%t?&rg; zNHIPg^S)_n^DuLmL$Mj_{t+cV`Y~^ME+Mn#y{9Pc#Qpo{YEx`RKysrHLTJmjnjE7y~1JD+<9) z8gWE79l;XgOqvLznLEF+pLBSkRB78`3s~n!0m6rOF#IG#ahF>iLzCdK8jx*a@!8QE z1R9qp-m3nzxK02vpY!de0Hr7BQ5qpT@^P#P>aAM9lAZH#H=az&g*K^=ScyiS4o-G; zcQY!Uh%N0U6YPp)_QKagbPbetx7TgnCgI9wIn{Lc9UH76LtmNi=$^MX3%1{9Y)|w| zbg{KB<|5Kpr%xP?O+vE6@_wF+NW^7F?zKJwX{*O|kBO3~9m@nw#$~xEEy6tIPDKc-YuFICj82-Ue9x*|?CCYxHct-9- z%t+UliM$`9-pDLi;T!dn zYP0;v>J>sPC9T;=4rSbM%oJ$KZvPAE_?1xYE3E+W#s`1vN-#%~dd5-<7KE&Qeh+&P zW_(t^AB|ogp9Q+z__N|6u6orvP4`@B!H)4gYRRf|PYz%f*|;>p))1tL|LNx6sJgs) zcjC;U^ImesMWo#u#x#CsXK8WmM+>Sr7>TKVGJzxU-SSRO5bwEiOYTb#e>T7+uUnsL z6i8wZMkfn#4s0QnOi+xTSbfRexFuNS0m@A0_#YuKLs%|G5{mfL$B(%5ea@eJ?WqcQ z0LO|G5Gf4zJ1pv&^TMO;Jz2LDYEuRlzMp^PbaJ7}hvjSVpZ<&_Fgo?+GYS}B$z39Z z%k&*AQ+zbA%~`e;RCRXd1;8w={{wBVk1I|jrLE%30GDmRaUkXa@04Ywe8{~DlmH%V zWvl77;k)kZk4_CEO%U=R1qZlIO+ImL?~BueP6S{#gsy|!GR6lTYi<4^l}j~XymLj! z{uZ%aUKI98R4=L*_1Uy?uGf^=o&oIQ4FrcHRnnH?9RYZo0ER!vI8&iSvHu9eW@r3y zB1<}fefa+nE%8O zuUqnWZoK0$E{T$NAKtLEj46ejf&`6xu#B?zz&I>0_tg`os6`U9H!jCFoS7_tstMBC zn%P00L>R&9ts84NQ{1GqJh|1H*gr1t<<{c>dAWu_tZ$WQ_w#%_1y66cwff&MaxM4R zz}jRbI1W@^{RQEw?=Dg{<~#J}N*r_VLS9R!v*&SlAylL!#SL05O2>VonDzmyk==j1Y=mQO)=c&Ki-SXdBc56c126mFGh|^EOG|Lb z2jV9%z1hPcmSo-Lh9m>cml0>P1#7?Yy?B;38Vc+ zYHGpzd2s{At*S;@u0%RK3TymXt?`pMP*dhHz!QAvCXhS!F7$TN!x32|ej7Bx; zmz?FtXfaIkKoWyDwzQO0j=A%?)U7T+qwFUrM9hwaD%X|TnfXa?S3f%{5J#l{90Lo| z%cK2fgmil{1vMq6xlQql1#o}AkN+(4oX5L`F?>x&&}HL9ss{};%OXgu#3XtD88TDG zI5-D~`FuUaovM*T6NIJi#UQeClm}@MD{koEa>*(5Pc*n49rQJizx+=<3~q(NzEP%z zk=$DW9bw1n^#i_T17;hAB$;bp7TP>8Yqdn&$OvhKNn#Gr>whIx^U4p^H+_pGuXc7> zRBv~y>cg|C@H`PIQaK>pmC;3e0lqLQ# zw{)E)|>qQf<@^*EblBnH9A7V*2zz?S5k7$wY45Je^C(T5tCmsALMy>y#! zb~IbS8=}E$$~#Z!rxOI?S_Ufl3-0UDabo=E7p5R+ zu49r=Hdc!Z{cUiiffyZ7R*V?1il%LgI=p0qxP~*Anar5oD|k*|=j!r@J)eI^^|fIN z(uSRIr_VA=8<+jDqvJh7CTaM78hRm^S-Bp|);n*@DnJgp1_+`g2(*cYSQ-3%ut8Kr z{>$7JT>ZIcE4aF+tmCmo@3st-5e{lQIk;ntWDD|d^S;X$Uu1wIX=5vI80*h?bYF(C zrPyd=+K}BaEtJI}|TNgk^ z1mT7PJ1o|`wqlS@-fZ|(L9qi?zR!NoYJW6skal=U60yBj#+#&&jkmz&kLv~O6ZwTT ztvNMYHn}}6l9hmg#HQ}YDnfnar0(Z~zMuBVy|occCvwY+QA9g`*;pw_W;7iI z`jV+TveJmjGn=*MjS7`?{9!`RzIKQqXi2m7#R}mZCvJnnlgGn-EYta#77MInD@(Cj zr@Sil?4Y27$=)gT{s{p?FRUi89{2cdHz8yjAx)s^x9>4H@a3?og;Kzp8+=5ZNonrp zc;s>YZkV=cP3S9JY+Q96L*whue;=J`k5)u!j7mp|aisQRb3$#7JqqRn9c@CrYsh&I z8!$mnl#6A$r`hqm`4K<4kA;TXuEXqSO*_*MLSa7MQ&7tVS6=gE4Xi}xH@$cbCUM)9 zX{H;;&wPIQwqR+T#kINR9(B*6`Gm_@l4gnU@!w&!H?&-M)cOBs^07OpaY?mK{@lAJBkY`Ua!PZ>hzob(#_mYusofJcS zD5>>`z@zp8=47&6-Z3&E{8$H5hj4ogutr6dL&nGaz)qZmyi48Z;BrhzqxFH}zah!U zp)$<40Eg3~aPDJz!E%TGRml$+U<3v%+q%8EY3uEvy=?LPacoRmw{a*oU(kT8d^TVQ zwdjhWfW8oJ-5Sh)8!ny9`!^N`T+eVEo3VLk&#;IXN0=i%U5l-EZS1V1Ky-8^!G;Z1 zi{NLDkp6ZIkcBuHi3Xy2oQ1FAcUHqyjC1xZx!BLacMbR{aj7Qz9mMGmQOy{5%_%BvE{P!;Y@%Gs0$!Ecl_`|jYdPm zo_GMu+<2DA?A%;GP>}f|2|7eOT$&B26b>XzAbfd&BV^qp)4*jBt>U&EU~;16-UzDdG9$ zkoNUUz2{rnkfqsa{+pjLi5i99Za<0N#VJAf>E8N!QxnY0+XhHQaUaO9JrT(mUmU|2 zr9)YWr=X`?kcWn;{&xu^(vadaatLRXw!0EBYoCKc;;`D#*P~B^(m`cBgKL)t;K6xn zs1QRW>`D{Xxe7;SG_6J3#AHf2O!RcG!Zh-v=p@gC5Bg2-v&^K8#MSBT+<6=F7mILQ zn7}=QYhOG*Inu|m5t+PbESlHoI6Tybk>z<@3&1b0w}xJH&?GkKga0%ing;r9uunl> z5o#DVA+B$5>g%3W`|cIkSkq*s1+VxD`VBW@*>$e?1yfDMxH?vWJp1rEq#@O4P*u zz?hqFoy2N&jiw$Qs{q3#P&`x^P^0)tkR$-?YU*2UE3;p-$^Inle>{6=BxvJr{7CuKX;k}rE_ z59Ki?Q6)JjeUc3k#z@$=M@S@1vDAVzUg~P?!}Ex7sO+2}P}@(vRYM<-T^#)|hC!bZ zH-r#B3I&jdW1Yi~Dz8}S9tjGUYpV@APvkqADP!ud{l5P79X-}iM8juCd8i9Y(wNY7w#gjw~8xh#5jpV_?@bp8p= zXowW19}J;#I5wLDSv|4EB_FC8;^gXe3Cz9V-Ci?ON>k_|f zbG1IVTji23&kM2#?%YC;KKuZTMK;zG*~3!zM@j$A9a4_oi2g>Gx-8cOML+-#YW2q{ zf~2BKiflb;-sS>B-u zp{-nC@71mnD$w;;NC=Iow;W7Hu|T$mKB25iDy;1uuXQq7ixj3_h!Ehi|0oV)Kdr|& zT)W4C)YBm(SB<$slv~y>qd=?J*M4I;9R!-Z&FbAw5;`Io#b}dMcE`3q|Ay>mf;>%W zjiysA8^m2#F7s0+X{vKb6MnipzrN>&Ky!gy>onA!7r?gtw0SJtAOil%Ro?y!+1*?* zD(Zu zuMXF)E#wm%phd;o``YGH=+Z?9^@bw8dDCc$#+6ORKG4 z7Wse%Or0#kgm>X8_$qd=fxqe*d`r1wExNKmclvCJR*8tw{*q!Wg6)qNgZY>Pmn*GV zMA{2a7IVjgy@VZia_ZLbBI6l*Ey3Xm82(=Cl^-SuF+KF>skCE4{FL*k3k@XCjEk}Q zxB3Ir@k|i~WEt$udOFlj3R-yu$yD+5!FFb+N*Yz4sNI+tslz<2+`-NyTRCML_Kio- z%9HMkLK;otY0qDFYBh=we>w?fYj4qd+g5vs74(Hr(C=pn*w$QE9_`GI0Im90_$FzUxi>gwpH z!SQ!}K_c<&z8KVcX^i$eWGpt7a+w=>Q=8IsMtV%e_7jS!Yg@{r)qNGr4^+@;>Sjjd z*;h!Z3S@T>97LE8iS#d^JR0|+-eaF)?7>KoEwT?cYPe=TqYZ>Pe79Gt>B!^bn6q!N zA2jT=4W;&kgQ|0yyatjh7hK7lxj4NiD-EU9cTqrZP~y@%PPajLRE_T)`C;wN)v`&e z%GY;Y;s{H@K*`wp7lVa3HWrY=IIvu`+aMjH`zh@HNJ?UcJ`Pvb=-)R{@Syk9$Uvc9 z7}F0QykQzHxbsvfwvT7yk1yB>PvY=PC#9YSh2EoZHt*RO@6$iuEj;&VqH@E(4Dod` z)GsmgbBW!fZ4Mr_Z1|OHi9E4Y0oC5sVtb<9$FP*{+1vQ(e7xl=PBPpQIS*W%)fd7J z)m>dVfKRec#=m(hsyuyheSIR%&s_+xP4UrF zU$Es9_vXp0wT;)FFK3gzPqTv$;bPY=z7PDlZnabz{ML0Q9DnFJ{#dsk*05GP8?-hb zMPfTSU&9rKK&@j<9|&;g?-|V(w)s8BDys9q)&3fBQX@Iu5r?yvUNpR8?ma0Q$y^EN z|2_zc{W8n4{_U6>b@-&P-*yB^IySv-A)UYXN?cf&-wyKkOwG;BO}1X!fEz^;ELUtB zPrVAkrmlZ}tjR(G>`DT7MdS*0Gi4C41-r4iSqI=sr$UkC=`A1UISEBK4PU=@VCdE0 z2sxlr-m8*bm}R!D?L@0tSIFy-(?5OI*|>pH-){{@uJjZiDyl6$F;-0d)reMve!{Lt z+2UBCJ_%5=Bygx=X5o%EI76v*kf0-o?ln7@_FyG$(2-)tOMtY;=I?>D5HJKs0 zKV8pkAso5r53U$>BDW6un-F)3{i#z{!#sS8kA&CHCJ$^?UV=KSDveFEI!wKTVZ>z} z&7F~cEw8{WEk)!%-`m#XGLqOUa1}C}^(>v#VTwJZwszaQ_w?aIa-TdLSBVsyBu@Sx zO=lSuW%sr52T{7aYmg4PD&=}yI=Tj?(8_x8Wm`*l9d ztU336&faJ5>-z0UO8LZ5Hu;>!s!Gh20Gh};sU`TKr)XWY!Mex?Z<+3ke2Bis_@f{< zkZF~Wm1hWzK1#CB$Uic@3Elg#wz*e)@=Yr(>CM|exz@O(ZDM0_x)rn$WHQBwuN(U9}JS0we04ztSOk1l>ar*e@$alrV067D~mr~9dCP^?!`YtN2^-V;mVwDG{zRA&vLxd$g5oJHWF1{H?y&}tlnF2 zHv(>fc9d$3NQKZaeC?xy#X1VjF`BkOLA$tuV__6mJise1SNb$!M`W7T3`zGo+Bs*m>-Xp)x;Z0vcBSqD-rWD@NJY@K^ zqIQbK3wSTD{GclShXtHj*f0T*r#rbF8O0WRJA)ZNrZ|%_#7;3ddhpDuo(!_<@{?c( zE(JfJ$orB^eZ=7}CwJ1A3KJXLj`qFD!7p$Uxnjupd95J8Wme=eCpzYj)&7jwJd$+I zIAgrLKa6WGk5x}&UkNZ_jBrX3o4XpTuQH7U&H7ISoNyw3)i_qeAF7VK+DxT7q54Eg z(`R|%)m7q^Zbh2#fO*Nv`Bp@53EdxGgETb(5(fQ{i0HTXVF z+=nzL=*gTY)Ixc#8`YvM$MoaEhDy~dY`KIy82=AU9OCvEK`Z9nkN4Pr*t9dy7vg~3 z5A>NMLUBY>Dot+W!|>$qHvM)TMV#a}9J$+`1UfuaHu4I8#*hFyKrv)$!U3uwg)7$|5Oz0F zeUFJHJ2*aAJ6>~K5!pW@c%N|^E$G?e7?~hI>vluzx|X|rXUo4TXw9C`*!63rTK$d0 z@daaa-aIM3`(0fZwgg{w%f);kEOX^?s*gRw$}oQWieSwtYKmoHyB4xAS??Lk|XTJ-w|2)D%uTyC`^{1*lLTGffss3?3w$3W(ogS}H zc9uRh%};k+ZcWUz0_WuQ%8envu;7N%r#m5jmbl&@NQ(K%j(sL}w|BNy-W~Oe(e)lt!+4JX?QGI)hUpj2x zD2O_K-s}^?;d_kvHKOB9>x&LHe|qmpp5vBAXocUq+>bJDkptG5f*qK~oK)+JHl+s< zLG+!^MjhFfYV1v%4IE{`YSt?Cw^~lP zV_>zG6D~zbycn|D{w!_=$7fL*$hy#L1^z3c=w7QhKx?3@?%7Q^o4c+*?z|P~zeTzB zek_F(h&iqo6GAt&v)rARI>%l2&QK zzCk(KW~3XUJvpIxgL%3BR1}9&3SpO0(1%UvJB~vqOp325EEaM7EhpB*#}%Vldl{ga zm*-0)5>FSimQ|eMH2>M)b6L;THxbek?`}llq~X%T+?V?KQIdb|J}|mT zYJAFd^0>k8%Y3kHyO4~vsvb&k_rdmFK6BR)KkMf>8pGi;?%nwG6pAS=DY5hw9!VRR z1}GCkVU$8{$E`cO?tj#Z)H73NPEFc8SOIC;YlL}_1dwlu0jZY%Wg+d6`*?HrqZ4ok z4XY!(OsLBPfErB;snc2xo66!e#pUHOfSFbZvt~Yhb&&`L28a*x4EVq!lQ;9{iAI^x zw-Q+FTlmZ%IQi+hxvg8@yT?JZ<4329@d+37)6nLZmsfpF?|Cs16|0%}`RcNvx0Dbo zO7FGu9+O2AEH{Z91-lcYt2W`f1Vy@oLoWGIWvg%(ubU@9c{BsMm|I!#Vk07TStq4T z+W6>8k9Bon^<%RX2G;Lcu>y8(b;=ExE(I7{#EA}rV@(ZSOWPiWVg`|pQxhzVoKZ|+ zJjZL<5PCn7P->60MnjkK$N*8}xt|;GR($`W4|bUZhcQ_VQf^K)DuG^3_t+8bQ??-9 z#t}b(Xif9jtm>;O%^df~*^q{vKDhNGT6FvxE%%de<-yKQf~VArfwdD*Wo|!uuwvQUgTNFj>O3ZAe-d^d@|d>+H#`O`oFn9Zs!J+kAb--)IbSQEvsa^ zawLiQVp)+I}~xyk+OOixU|hL z?_(F5nQ6GBcks^efme`|&)UI#v&f%ctrt1b!|%}<8m8zMeyht9c{8Z4jrxH z{~9t`e*7qZ*2pCEa;4i`mS;gf6!h6Zca7uilIp3j_apAQQH@K%+-NOC{|xX54ynhUV_>l+}ra{Wv&*(c| zDBa?LaaI-a&YWeZ=9je3bz%<6fpB~r&Gf6&|Awcx(UM+8-d@gaNMhL&!THFr)1#&X zaCtubJ>AHHY(@oCJOc|`J>{yJ%B4=cZ{Fr%A6?Om8bKtpBg?aG?*<0gN7}55fT5`T z)k$O!XdZBc&He`J?9nuL&C_O~^tvg8;+uZ z>2&<4V0RiX`_jp^*9M)!=zCLG(&~`@OFK~Awx-8G8WyYb|rkb)6B7n%d(|C2h+_nvaE zY0D|YkuY6ZlyJJgfjwh)Q|Y`W+TWAK@yx!V5H{ecsnEScdF*GYjj~~Uz3NAstSz^7*RrnQG7i-~_X>LV2!1-aVbE!q=5?OS#d?o?oRLI+I@kI{l9@m!yKAI;?egQ2*6qw^nx2UX`ZtGP^nB z4|ltu9=4x9T95HfLzRNFCsMT{QTYP7@VwDr* zDoI_n35p!^>C|K-5L=WwNFnq}?QVa_hT23oY)#hL)2U|b(bqAv{TMNm_^hA&5?}tM z#H+o^_D%c!HTlE=MsdY;6nUBht;_H1m!4{Z`QNUwyt7fKb7DaW)(>TlE~$?0q-G1c zeZ_FKpTSv9^o*>WvST-hGg*?#!Fr=^8|qA}H@yKnsK?Jt_|#BRb<5f{*A5GN!qiYx>X-9MAYAXN4 zy&k6p2Yy$jmQG;P6M|Y&UOwh{btkv{z`m+R@BEu_*Z&^BXe)|2hmIiXuh*M0aV*vl zt|Gf`eA_o^kAaa{FgYsW1*zxj8 zh;ANRYKmujzcvHsKbn~y8(S_rHXKBnuB`PYRjrdApA0Q{{Z7P02M!1ak7`g%E6$v6 zsSHlPH{XX|Su@|g+C(J*lTk0;bPfmN%nOa>Fs+iFDDfWl*o~7m>rM2bJxH znQX0OOQV=uE6&x1)*xFaGoSrvXRT8~v#q4u4`UYQ-U=@vbPO~x9cgjRV+uS_5Q7A< zk!v7zD>mnh5a?VJ5nV|aK$xL4NXKj{mSOb=bZ|)^KY8MYSf1;F?Xz1_;-Yg58cHhOp$bndnjC=cJ5FUZDYrGK&9__S21i8 z4C zm(yRBueuMkt;gcLB(-N^N|1?FK~yd!?#9nc@)XCEeF-_>`z&VITF1VFAxQcn_`4t@ z(2r2Vc*$)oMutUlB+eno@D(Vzedy!;qFF+9sPz)4zUjP{!5kUlPRW}HsV*&Lwc8;Q zd&U;>&ji{Z7sQa#wQhMUiUD_u1p>j99^0{XCu11Yg^(gykUZ_J zE2-5UOhG5?2-}Wzq&(rUjsmWXSw~@_RnY5moOMGVWl2>hADKVoSbP73#6s{r(X?q@ zf$C?QBXy|9!VlASL3-O?ns^)7a@!Xfj`j9aDO6jj&fnP?pLtdCgGee>{?JW`Yn#vFKtVn$foE2 zc=xRCeIu3V6j_2_b!6jASO&k%K9*qr>S9H8-C4J{BDY|n5$-hEqUq8_NE_A)MO_!3$DflfMvM;DzYW# zJ?i_sA>$|`Q{_$HUa%$25ALEcio~{4{zx98KeXv|-1%x)#t?@CT)MooE`ldSPhbXf z@c+vx@Pk_sG;zryukx%ASLk@g(XgYXw5Yfug4huGrVlFrD z<~T4{VTK$Nuk~H*S9pBk$cD9*=QDaZ$|4Pf@6Jlt{`Lx5KIVftzW>9Vq3f^tq-MBs?VhT(RXmLzA)J^m~mC!(9w$oxr7;FNd5 zO+=o+PP0d0_`Jam-CfYQG91tBb+beQ&QiF6#W^ui(Y2R+f>5FL$xClqLVOXSjXCA6 zp+`Sn2BH|}nt)fJuBnXaqHjpv6KjQ07QAJSoirmtm|{&1X;+q0x{AXH^O*y6av1u#!oazcMJ9GR~8WNHsGLraj4#ebX< zL~yiO(>B!AsGIavJ=@UvY&t@xNkQrT*JxN9POqCX#h%fulx^LZxa1bE6^M5D`&TZS zXI?r!)+4DGjhacEe(&=+td%12S#{Dr#9>K)pC7m<#`PQXs(rt#yk$F4mCbjooZean z^+j++yT{Vbz^+z0$1dPPQ;`7}x_~-_47iEY{`}ocAJx+!Luof*_l6V9xQ)nEx4mSB z{gtb)_!~DMo*7|$P-PufN18|l&ij2y(V^sUK6zbtkq_5rXc>3zxrDeuYPM??AeqC55r) z{cyO3QP`L~KkE}E{)IPQ>+^ho3m03ZFNiHR8GQ|9a&Rn~|B|6~TMkM@d3t^8*azAP zm~nYP%SblX;vdD5|2fsNRV`0A?99^SgK@XwFzwR$^3jaDWy^`7R=JNcn?(M&x!wKe zmi=^K`Qm=Ue7&WK2TYnBTNS0uGvKf3dZhTxJF$O71=}1So0KOutbzT(^mPE7Qe|tK z2!5zjqDM>uawd`(9+y>d0D8C3;tB#h^HXFz7V%X=5Mpw^+ zOJXh(Y!UqE@X2SFlku&uEY+RQM+<2%@@s3G1U>w479KPetP&aPu~$hg+k@{c)#N*s z)na>MTtQvcV|p&jhW+vr0?}?Rm+Ed;4zZgdA3?l)hf6hBSi*0x#?_{NjolBG7MHh` z?pN(s<>coFe_Y9bGziHlh2x=5Vd=b|2|IWGMghE@ldvVbisPrM> zPzisY+OY(Bp0Tu2Ib8vulU7|y8Zoa}g&2}EfNW9waiK0D(Ntfb1Ss?o{HH1P$j`1{ z@g5-gROP%?A-9VkBHW}id0b(2!s&f=cT~fdC9TdT7l~tid)}NTeyhURRKiI0MH)sR zs59qU2!CA{O<3loQMnsVURGPaNx_b*UyGOQyJXl8dM37j?Bi`2!XN>;^r{AWL~isJ z$4GIDKpT@h{zMKq>SQHkcMx$q&{sAH>-vANV&XZ0PCX&(h+ZrAj61xKNuB zfki{}xu!~aO{}s5pqaXx)J$-35*|isCKLNh6OR6sTekfke*L(FkNb?fJN~Nh#VU$N z)MVkJX1Ui&2A+!)A&+xz;SKS)qPTgrms12?&L#bkKR4LQx|u#d1+`)8dgge5h4JBM zz9~XoiJ=z$sc7)O{?9ja#}ls>g}Y?^i6)S~)8g-E6ZDlyGa2IM26cU*1t-EaELh6c z@?=8)0_MIcAi)~J4Ln8GuQrDZ)E&g^QqHl z#b1H}rp(q*d}JEAS1-AS#dKhIE>KQ#b9a~1i`W`8mDM9w8ut{k_>RMXq6ujJ@TZ!E z&Ns=E!bg8-FJcD3NlR&cEym&}%e3yMY3I?2Jme>ke9A@cbUU2P+BkDMJPb?^Zcn}5 zlIaCmHeNtp@H9~?8Gesca}I4h&<<9kvR2ZmLqT_?`qk7*lMXdbu&UUHCc!3+Xo{Sw z{VTi})xPBv`(C~=WcPjhpI+Wq3MwmyXEvNxoj2kUYX1o=@`xqa?)b}-e?m`B_Y?8; zK#roDab}A;9McdY{$|w-IkR}nxm#Ao_Kyhw&>?qOgse$fZSw$9AqVmAtcFgVjaX?q zX>DzIdG>c$gSf&Y-)1i+^IIb$SjYR(LLKYn@Zp$CoO3q-Siv|G$91;~mSCJ6(9D5+Is%y;~KhKSv-nYeRBfsIe<-fOIy?!(5t$$i^ufGP5kUwg!d-K zPlrcy$)&xJzzcmqJ?gNbA3RnFqm@89dshs~N5=JCmVoNij*W*ED&j`Q8R~v*8Ix$R z3@8hDRy%522`4MYM#J@yvXBpo^{HRwM05+SS)7tR;lW2PSiMDySR6en-0fk*Vw&l)bw(8rrZ|ipY_$J!d@K%F+13hkujJyBW0>FQR zXx1eA-8-?)GH&wxj6isDcX)Nlk9bB5CqE?XdwIeFZ?HC91B1!B0(P;jr@&^HAD}`{ z!a9d%QC>6%hn;iV{`TxBH62>}AOm%S_(Dv4>%0;#Z0xHKtx*L9G`l(l=}I4O$Qi?G zNR{(U!x>-C#9dKZz51jp_UnyDbt`ItL;(|LLv6)O;5B{JjL~9Cl^j+Pfh`6diWeF$ zc@<*Pl7qn57s=Gtnvs{s6LH+bbkzQ8_$8n55|jDT2@d0OBXU5UzFvgqu)Cmid)z?8 zR==@;KK;$6*kT}Cf^AW-S?A(Hor3r-q;B|ea-cx^QO)1(2D+>#kALZ;wP=z z_pi-}T#mZuLT$hPUWl?>^F(Np3plW>Jswkq^e}9P>#B=<`D%qy_p(}sZOwnT0Ib+B zC6xDLd=XAsh1>grER-y!5E@xlb|((q2qZkq1Ji*&(r(gUBL-asEItLo$co#iTM-Lk zF@O2f?@r)8aT7GP5Gp4InzlPX7s2$x1HI_=l2qjPar!^m5IJt!UJzbcxd9YQkHExV*Ok(bMPBSZD8@mR#~8 zO@CfJeet5jVaRFbF-P(>li6!1IU*W*=lLI-ti|s&6v=}8jd=P{y8-pM=8s|vT0c9I zsDT~7wO3|wh?-_c@FsDz7^^#xULeNOL_5l6JWWPb75irO9uIgmpMxRF$kUi=$r_5N zZ5T_pyLu4I1Ys8x3pFCjuP8i3Uv_;*j+=TvqNKlZz&m*iaH`;T7;4uRTSGLuvJlIBqZ7_K{r$MsaFK2AmJ^yV zQcTS1>w|~x_p;H|FCpsWKZ`>}2J}UyBT=(Tm}jXMOeG=82d{3_@T(~|@pW318cSgR z57Wpn2hnNfTb=&-GNwi_wzHV<=dheW%fE-J>$Kx(`R2%$3xZ+nivw?)vOl)SoE%_% zeFp_)*mbX+6jxSq>Ou%u^IX6a?#7VW*;xXDTLag_tSl>zO)Rf3 z^31E#B`ssh-@kvyQdm#wu&}WFnpD@@7I4@JV((3xj|(&Lbwuo#H$FGpBhGYs$??pr z>iAW;Sc!YR7`;!4`|G)6aW6i1ZE7j6buGFAuajr~wO`Ot#^Dc1G2C>HHV{VwYnWFtjH`m!Gr|7AcvmmFVkCq|Qg0-L@lFe2=Y&XqkNJAROqQJ0egMkzSAu ztQbHQbB%gb>{W&qwzj5AJ??2P`|M<+Q}Zn-i6B#}6B>t;pI= z77s^|3{&C>J@=C$)Ycb=fY2=O2!zXXF4K?9C2gLes#IKYh}Go0^3g{E6N4zUP-*FT zN9o~L=;T))<#zpMI^FIQhn)`cvqQ&eh2wTdgih3}(&gYX433%Op(Chv7m_J{jlz-( zpT#9*!*h^2payI_Vy3TMKJHQC7oT4%hFaLNAR&McIF4ruWBJStYMG z?kl2p&xRwLt!FKSbxqtU8|QUn@Ump!4VMqU0QfY%~^ zi^9*A{XzU4i z|Cg=}tC!y6D>RLNcljdmKB48#rLlJW$7GOHZw*E4@-3>Q@UhlJU;AO6=LuXf0OKR>xbr=K=(3EUSbs3xPpg& zvq?A+zmzWdWpM&(WiR>B(Gd|b@xry?k#9>`KNVUy|KWEsg4z)HEr7x$5!kK+5RSm@ zLFQLw`R3_{<;xe~*KIF6QbOTE)Y7R%x(j}<8NQ|=MF)D3v1;taFsA0v`Hr6p9JQ-p zw&9<3Nh!**tHj^3^;13sL3^C#u&%Hdy`N)=tdr^X{eDcdljG8X)yQ}CzxSiGjmbdvYBcox@)RQ35IeP@mo4Z*r~iLuFi(>A7hv5)^*lD8=J^X7r41f`^;_(kRgrf2SGGSGQN zqM|5oR#sOvoSZm;g|@P>sq?>e172QS5x1}KtSmJYoI|x5GmiO_HZcvJglmuD0XCFpN-*cwwsRTF&s+v z6NfQ&R`vc7cabLoG+2aX+Vzt~ly*CcnFgnPStm-SQCHB{bfx`fZZJ2VEQ=htKrt|C z--o3%gSSIWKD6?zF4oCk@wJvD&#jUt0-v3BEx-p>S zcn!9@;_Ludc6h_jX0 z{`#-Ui5n#Rev-aq6P*?rQX!RZ%*zPnn1J2mFx2jK7v4PFOX|3KW9u}nV6$S}zS#To zF9mYL3r&TQVf+?QmG$U{_UP(X#P~YJevHeJxTjyWMDmTg<6PvygomwqeI>J@+svj& z`b-VAZP&XOi6EN|+;z)t9q#|S$lMvsXkdNf z7|{Dj`jaMKjW%|h<^^1{5)@9#SUi$CA^;b=v+!fXL7*f7m?96>s38`AH2iWtcG)ss zFvsgM%@@h7yZHjJJS9fbWb9CQFWBSu*lFb}=IckNQgia3|LcnYes?|~s|kzGR{Ls& zC8~oe3O}qS7vN42R2286#cp$3!OV(QpA%h7l?mWYGKkzfG9=z$d3_w>-2nd11iZwnqfd)&d z}DfkGP{5rFQ+6Ve&kcycWrTO0lQMDAuWbDDB5Zsy6f zD8PJRMnZouerIo9l{IGAHI0otHqHJ zWkoTL=g8r5F2}U~s?kwr@aCi=O>hUVeYDC|{5#txd9oAIfhcb}R)LDqkrX+EDZkJmGI@(<=62CpEag zWiP%@Kp#I0Uyf1}55>b+d%?@cURqv`bFU8uqm&y||H*K4UZ3Y7e%5<9mOYwpUF5+j z5JEZyDfe4A9kP}BNN)BB(GyP4SiR6z|2QS1j}|aPm~@}uSGkzU>bt`wP6OG7-!A(e zaDLeG8Vik^qh3A=DBXFQGcf-h^t2NMq*{3f)n@M?8{rS9ky88t-d-oG6F>?(3d~+m zZxklVfo!6K<7pwvxf!XgunOb{+zy%&Jqfva(8YoWv80By&%~{=9IO<~+c_ zz_8#|iB+Hqmj2F2xf;=t7Ed2xFP6xmt2{{MG1;gyDi%xDpd2Q|<|NlK^cxD#j=kg& zk4=t|ifU*slfzmd>L0F_jUB@8m(vv;NPG!H2qOcb?9)RF20an|$fyaPixKSPHDd#z zbnJn+(*dWF@V0gTyfwY+nf0#e1PtcwN4I){z38L2b>@Mo9=GpX{$jiAeFmVQ05M4;oV;*LLn=tC<@n53Az!21Kwy|a70)xePko0s^m zG##G9$(QVGKzdyT*$@#CDN;@j3F#V6{ELA0ZzIr()6zErtNf->Eo=~IDOIBw~+*k?v@iQR0CpXWE+N_N}s{Yp>afBtZD4wW!zSd#q% zQUPY26or^TP1EdI_v1JXy2Q;^e4R?5bRwR zcKgazH7UqO^^NJZ>CbL_<@aX6(}&KplfNayE=-`G(#>VCj>w^@QdFJpBQF`R#TK@Q zWvmz=$jZ$%M}#%? zIqK7tSQxN`+M@Qn$E!v-+E2rjzXRJ;i>&wfsZym6baQY9`L{t)mJdJZ(j=~(*q(3O z>*@s%E5CUpFgo_dm$M}5cTw!9e}X>sfp#B^X%iVA$f9g>;?WLRIDb$y)bwM&*jZ|GYc`M~<~IX@lJ?%pJx9ME6;x|e#WMCf&}#32$HER5gx=-i zxO6NnJQs;S#gBTXQr~Fl|G8fi0V0$^E|YQA&_Gd)7mSP}V(l#y7eV9J(GFK@LsS&n zPUT=x=ni%TMECwewfDZF4%@52Ed7)f1G$rcyItb%T4+-%*;!E%$UXD%=CdF@vp1Xw zF(e`QzW+BE$2qujF~DC0;Ckr;qK8|m(^D&%xGc|7Y8B0SKm4}7e|&nw%$AXQRD^nD zW&Xd^9v&=sP02q9*;2k!V9-HcoP-qgaCuJtH4Pt5%QuC3e~GliJ&_eR)6=Qw&xix% ze?Hp%y*b4|{7FhCR+lgl>WdQv3#8?7sJ2(U%V`2;TLlU^KQ(5OFHnG80%B($ODT4=2?KS0EG#}sX|Um!BXd%NPngU}xb^u7(yVdm z$T$$`{FGMY^Mwc1|8RM;nrUsjc6O<(`+*3W^Nv5z8Jt~@`TOi$z?IPLMJ{E+%iMjM z0HRJg2ri?B|Eo<_sYpm}D-_(P(mxzXtW4u9Q-&O>#Boj7uN9o#dP)V@R0S%Gl#_LSsAm5JYD zF;>(fpFNr2g9mJmI_lx*E-z1U`XbpMaj8c?H8wX#l9vM~b?S}dZ);$_M%WC`oVVE6 zm|&(B>`N~ELph{lNtdTZ1D?oZC+1XL2MkNWycGMbAtWf6n3QDcxYqN2S{so9Oc2sC z@~MbzIHz!TcP|{wD1-r?D{7!06Fo;lbSZ;Kdm^?-o|6oP@<@b{72(2b;l7=ff$(XzZC^`})0AQZZ^`@FGX)(KGwByg86`rALCKBSN&!+;&xo(`=F1~l$D)Rh|cQ0jwLN#gcW>1f~SnLP1VU#wg+`JDWN56gZp z1gpGbLx0=O{rQ|I+n0u!m+B?1B!u>6?b#fP6o|#cuaZoANfTrXA_By3UpE~XSC>QH zPz6S4?UB&s2M41~wW3u;){YflbM2txL+vo3#z+O~jfV z`u_`xtx?aBvH0OlpPwMb5H+y@MH&1gFW@Zlhdxg9=w9+Y2Vh#aAV7S4U@LO$xY6GT zHdGcaKS;9USW?%VYgw#6uLG6;M|f5TiqFPSQbu<2bdSwK^!Cacq`H7-zK)NpA?`{P zbapBlszA-!Qs5%A{_B=lEYy1UzjM`rz@A41Ql?6@9~<|(vXV0W2NEH6Zy(4%aE;4h zAu5&p?*bV21kulur4$TtCBs(eCmfj7S`vaD`qDpsn>lD0i{%L&+S)kXc)Z+u8x0vW zfg9dkEc)cegxL>*3KU-{@m^F9V=!on-GnPyG@_gK3dk*0kr?*XS^M?IRT9{F4bqC# zroG-^IB89K6oXk0Q3kq3+ff^jTPbCeE$i}3CKRA7VyVnxMHkqV`Br=bhKqX`RN!J-q9h>#*R4uQmr;E zxe!~Gaf4LH`}alu@)vqj^J8hOH^cq>#8DEYpm6%uVYW@5_fodmo`JhGrPrFZ+qQ-D zDXEodVnB3v{SErbQ_*p#q}k4Hu>eo{g5lHD)U>!Cy6fe}oBr=nb)14V+&_HSK$n;% z2+n4FFAi16u3rR`@7VE(@WqeqTx?wV%5f(~_ksONNjMgBx44=s zT8xx^n}3ZqS4E^uK_eHf|MWS`KBA!fkeHr5<_MZ<8mWbeX;{1I@>wJIZC#ynZ^?QQ z+q=pV=QA=UU{kkA4a!%L;~K+?`8qysSrKEQ$bGv6gK_b$seHr>Aod0oQW;tV{~NRh zjI00(p;bAVvMLmIZ+7@Y#TrDu+{XRlWj8xv)SeS59@3Sd(mPY-r*YjLyRUQ!i23u! zg~#t&pyOs0qksB(Ht;;*bpX50YGqs4PYTHsu(mdmYHC+6FnO*Z?uP^G8c+f{$Ix?R z6f%DTioG=-`V8Bz-wwhki?c16be>E@rnjHu#Ja~4yEA6 z>xe;2Ec5Qp*Y|4Qy0v?y-P`?ep68#K8Gw5X0;t&D(GDQHIrZ(lJV0>U?<6OpEbd&) z{QT+hqL`dOXc`RLv->GhjD`}LAIIU<>DvB^*+y&54jw^I5kJ5GI{@&p{5KCE$e-`3 zr3yPbFuKhfHD7!aHZYuzznIJ%zv&L*1YD^%gek#S_m2?q9Id$`Vo$Esukc!yZD0mS zK2XyQe2kc0_J5f_DFB+yl!GT8>nUN0BA=BgGQ`6E>-AfWXt=&h{5LRaLIAOTI^A)v8T?wvkdsciuHwX-`@IG!s-8$YzA z#8L{h@ON_j;{Jh507&_3rw3O1JU!t)&aKi8{oj=XvuSY^*D>Khwhjz%=Jbk~8G{wC zzeGE80F}>F;IZR>Gwb*_WCS=<*AkI7Nq*LXutVqi3pTI84*C^3LG>e;>to#B7XVB6 z^dff+7Ut^95Y;V39|0kG^m+r^4e~ZB(Ov41`7gm0m(Zi8a zX)z9_a0I4vF5d`eq*fjzcsp~50oQooSS-@aodf`%y-(Kwe?085DlGBm^Osr_7H;lh zU;%+9coL^cQ;o--&Ofy}Vkk3F+-^%}0_*HFBe-Ugbp}|qyLyv({3JMBM zX5ic@(*4auDB=)BQ?G}N&d z&C;*h?9B$jX|}`n25*gIc(^ONXqB@a?d@v?XbKf~b^XB-mnuplPV&AJ@Nn8F-*D$Q zR2A3dgZ5U!!sI*A#O!GT2NWG<3VgcRwS50c1>BQd{K>5T=L8y*Xe~KYXg}GfnKvaL z;EOP%CluRi2&v7ISZue+B8uHmVdpmKbn&Xb2j-Uzw76~w-yhan2DQe(6X|h;XfzbY z-kK_99rEc1e>PGrx1+}gd2;ClPp~b&Cqm`LL~VIh&X~MX1EqFRbxQge5!!~ZsYTdr z0XX!6U#ino4sOa;3iz!~+gG{|_lJjvU&(CS=?Tk25)Bb=!!-`oxZpYN6^2be4rb=( z?b=uL3_!23Js94oz8Ku3QS9ZwPv_WwXH}sU&|O#v;#f|=5{?ouf$>q8FdCWFhfqhz zHMlPN+$JgHzD4}lI-6cyrJ?9H($-=4;_20C%q~xoT*5<9U2L)1e{h@WGdajWaYa)S zWI0erNlD4d#pNX)SM}ZAG7o6qx7TIsLm#@R_uqX*r8fGsxjj#ZYv=6jT=L%l*|n=2 zWN^GjM24dYN~v68r!^EM(AwYU5d5hx1?!F~&9AXG@vD~sXF^9ply-h~W23s@CS`Hjyd4|Mo&>R$A`kh<44CgdjUkl;>ew9FB>D*I+G^ z@h1zR<@eW{gS=tRkcry&^k!c1rrc1nwUa(Meg!5f>cFFa~Zie#EuKe$X7@qKfs`z?e^4tBo^q~Z^v&rVF9fJ=7X z&Fn0*APe(TQB$59j}W^gza>hb*SiQTixM zIQD1j^768lZ0{t5mzP&IADx-z123j*a&SvWX3qa>0rC3ye=@{DkQ38z^(- zJmgw!I5V3m@DX0-a-?R3JO+G6&txU?mXM{h!{NElDlFv(KNc4q{0J_6J1gF`Cc;6A z|H|vH#hWJOM}4Q^1&@P_dFO)Jc5Lh}OZz8-5taKNw0rq&YE)hEw@F|$X|(Z#2PB02rWTQV9WMbS;1n;I^mG99?*$I8uJxVBc~C+GFmsT%~nHn zE4O)97SJ|iV`CHE$#S3X4*HiZ>5qm966q|4>gRCoDxl>xch-p&qK3{QYek()d+cW| z+|2EqY3)c0iH<^BOwBNlDK+@wr%!H@va?sLbGpg1AYoDGAxCtJBJ%qC19ItyvF>&* zF1VHYNJH!uhDzI105;38(^mlan^waK^#82M=jU#K_2{@E_?QFIY1Uy@ZBL*TE_KL6 z5F*p*k_W&2z<;N|X3$Mp>aKy$uHkq!yLe?R%D%jQb1%1n0_vQKDheT978JEX=mzA( zMuVZ;9@g)Ams*QPxKIsR$+p2CSDWs~)*761n)#RT_}`{3*^lHHFE)Ke%$%@aT&lv_l~V}CZ1og#DI zjsDg~oW-#r1~PK^BJZBT5))y?JfoXr<+-+!^X_~0Y1)E-!0I!^Knb|#SD3b^0FjhQ zEe+2vl&BX7{m4jrnj|o5q>Z1g@81C0oVZQda)P(a^`3PDFlk&ND=VuOnjsW#>|)|_ zh0gJj*({Wuh#&SPtP94V_s+sAS?>}hlqo!|V+SO?m{WmTQXMcm7x?<`--D4&Y0V9` zlKH3gl{owXiA&WiwU&&v~x;-T?IVSkvYyDC;g9ysyjCXXc zZ3{hlmzyy}m2U2CNZuGG6^Zft8D(VNby?`l%H~^-Yh3|ikvP;ZY6|vH!2F>kg;7fB&dQh=!6CWoMK^)MH$RWb9 zGm|Zk%0Z-5zx(q%-{1B7bhLl$@z@;ec!M9b&r>))OuEG7%D~nz1atwf?7E^Jf?ZX4^mnb-~z^ium74|^Jt00cQDjO2gPRHN)^%L|3+W&S2&~X zN0!75S`9vCg79@&o!311s%&J%`k%X>?P0V^H(P!TOYUd)YHbH=YX-6(Spy%q_hmU& za)9C^k=fc|YpDlB@;Zi;6%GlyYc%wI-23r?Gx3FtFvKReiyL;Nnt>>=Y|?_gh5@rGt-Wy3O=lJ(-PI zPU*V_WI+v~!NHz8|6I#Xl>U`fz3^d*WwEMPLyG#%|@Iw_$HSTeuU<2a2ZL$ zWgAfndgKu@UMG$saw|1eQ%FS1_(Z3sY@D6%W&5mzFG=v@lB3?!m4EXPd zJd(QAdzJIGE5)@5G^suAnod;+?R(^BjnV0peA7j=0j(1D%rNm#g zFj%#7>`cuU3k>b^{VNvR?r=RkTqzMBiD!)0sJ?7A#b83-FvYqH)XqDgQr0tAOJnoq z*k!Mmom!T$SUwUPBsjL((`y|XAgZch@8Rrh^LeUqWBOTqwt~Mz@B#kE?&o*VNNdMA z60LgE$mk6$6FzPJJlp-Wyn>d?@fZ2|Tq!iscUfAkyjTuhj*LM`*d1V!ZXKWkdaq=;!)3^4__LNe zr_uNw>l3u?dLV8UTz(SOiV+aQ%%Sb1p>k;aZRM%J;}R#&Cm+seU)$clE1r3bm&*Ra z>va~)j^ftRb%2jIu(a%lL@!-A z=uSsIhcKOGu5u85IrXM0Q*8{hC~8WF&rFVwp+Q0i0wNZBfA&OS)L+ZAYZa+Kqd9+_@1zckFIVUNbN_8MwP) zyFOZK;5Riw(=BM5x2Q)|rlwc-EpT?LuhOovw}f}vjru322lw^80Wj7(;jY}iJaLr? z6*tn&A~7Z=CJ%pqk{RVa6_3-=)I5?K^7}GqciMtJ>i1v4&>PV1_ui7($3i&R$I+2b zU+LRvR#w)fm6feNNm8euRcUAn6r3vi%06}u>}p^O@k#cz34JDos&n`7aQwDDmh$KkaC&$D8dUGwcq=wBFKZq2 zZR~&?9}+vsAc#&h7wo4&kPT?u(;Yo3C@7e3-4@&w`@mr4?OQAXeU)aH0q11A?&#of zFG+fxMdL-51=dzpamw_)Nn{DI^f1qooEv_V@O3pP;#y zakx1huWS5t3}q1gLot2hT+47aE?__LRHFO?yg<5**jw0hj^Jj7ib6i{yk1OIJgTBWU|SF9a@WgQu9x;GNYREj&+jUy%{?` z(QBww+kKi9P7<01B z!5)YmmN8O)niQ+*o=LAAgdpvU@=*dBe;fZSkmOHywmqe=aa3U~cA{1Oer?ISo_Y%? z&Q3MDJzpz`VG*-YG3>(2FQmv0g50XzpWnZ9(_+46BN=Qr71mp&% z0K0Ly^G?9b9qr^vg85ZF2_aj$FMOG&XNpK>l{82`xSH6f-R06O!ijw{a3Q14+;JiR z7twxR!T*Z`bg1_|7UwDLnH@P<+#1$&D3{a?K5Fv0kARt(+0ydzP_h0wKn4YD8+f~Z z)P6^Hz5)krM)%AQsP#aVWy{(4PsVCfD$`ITS<(QCvctJ!cLtboO6q9{Bg-^OZ5VPE)vwUY6e zLg2!J-CpN{tbVq9s{e<8ZgQ}`dXZFrReeHYVtcw%PR}WQ#rBC>`~9K#;7*uYF>S$J zY|oRTzi{c&(}sqWg@uKC^i4voJtR{1BKaPDp7|Hm1)`)U-TLDpQc9wM<7m%LMm9f{ zJvp;~p(C4k*uY+FJxp_WnehpVzmAILFqN^fF))6h#fh6C1&9Ruu*nZoPf-R#BYdRd zecLzp#oZ^wRIK3nvVqZ_E4aYK%xq+00(~Y*?&Wt&O?@25x<$4RpW&$)R}XcEcQ4W! z^cxpvYQ3sqU*7R#q9QKHmQ!UwUAu@mXCj?qW3#rhs=DA4apXk7e*h%_H3U%N19=L_ znIkls@|}r?p=R#t=2lu(Rsb|C6f2^mqc2>%_ypQt1X#M%lAUnIoANx9G`k%X0)fFYm)ID4wQj~#k(Kh{(im?E$n`iQX_(?EdU%m2upGx zQovBQ$`1VcGA-=T5&pZa#u`&9{#4=Z=bV*TLGIgY?p4)G8W>F)A0Ho#aqgz*I`r(54<<^S=!&$Jt<#;)@xf-`b=5cqXe)${ zf8MMGq62YAAW>uw8K5@1J=+krw=>TlI)Q zQl0e+9_q&WL6MbC-u1+5k~|!GIW(-1C=gPRyhZkNMfa<=#{T;lP$3F^Gr> zblDuHL#tF_6whXE9|ea2ffDwz1$3`U`G2|P*-F;XAg~b|-&?)=8uI6-kvXq67ciWD ze$tQ&T1+^BE*HIY&RGcunsawIudaE4l%(R`j1RPwg@T%QwNbb0eR2CPhlYV=Lh;vT}7 z7{)NeQ+g65$v9#PcXxT0g!e}k5NKpoz=^<&-bZ+sAEAKY1RHk5_qMy(y~H5!p9u2& z`r&03wu|*HD3F0zWctC6_iZcl56-PD#$Q=spZk8laQg>QG`Rg>e>)v=$v5{I6fVX} z7L|l?%KNU-D)vDA$V&SatWE4*R8BL zff=GV+EBo4u*|;j^KqB@eB*g6SE9qa5>?+qR>IV3O0=_vhYx%7^oL4|XM@rWh0$xL zG3ER%oBMYWi~vT5A>}ZvxhCRl)pN^BOKHWW@6)q(ybAhx_A@h$(rVj&zt_UnYa=Eg zNBYJ7uK{KNIbTp1gA;{JEP)92HY|eY2!(IS+3A|3helglQC&ATVL0W896OY6VYn5- zknKz}mNwAQgt`Xi9zhca_gUI{yS_2mqB0S6jCwP#718)d?(|{8by_U`R>m zdjvi_;^n|albTF?tU!^i8XU#xCJ*}`JD+IlwJX+qKtqn{3W&$O?eBjAcX8=B9#9#g zY^C`7`(pl&h!IMcLmXl7Mz_}r#l&o^u;DnEOfPNJ}BR(m4mFqc_#)ua4kpj!!uJe=rG9z@+OL0 z1gI6h@cSD=rEZSm<%_<)Ned~o*v>=6JsKy5e=F_Hynzr6Ck|Ci7^5@cs{_N!g4l!n z4aFx|G`Y_sTg=hIyPv1iVCv)s(VBO|aDdx>bRCZ?n~sfo?ag-rVFbJ2HR#&f% zTX=mAH{g$hcxt#|#9@&o{3&=a&bt0~5^G?;%4(G;NP zke6D2Rk1LsoEx9%^(r0Ccih>_9Sg*mHyLg?8E`>kV`B$!Qjyu+ZPJx1qn&vrC2#$R z%H>};1PVRtr_#_fmaMA|`ckMjDz0enrMD-Q_m@s>ZEcmn%>=@#eL#O$0+)I43Q|i1 zNi%lf8@!(^quE>zWgAZroUZ)Z8KGyFi3K{&xBY@2Z1sP4cYitUU@NVS7t(tGGj+Ca zs1Jfe9^2(wQeKcW<)g|@37o!K_f0-lC<69zNsT*rXL8lvvj^)bH>oUulR7#&S_3p0 z!gRoUU39*J%UOlhX$j?3FN8euEIV8{B5Cd4QEc9iUn2y?N;Q_XT=+YUSOSBQTurVoYOQN%4Ex`ZwPSj;u^_X^v?K!_lH*oK$*A=MXt)XuK$*TbWR=P_uzU_Heqa zLlMj#|MCNvmWzuZ+_G~1&ZG-#V`{sJC8pxXqTl_lNT$Yq5d&A5o(T>M?!HI=qIo%h z%7Er^8)MzuipBHF!(4H^Z-jD`AWYmp#?jlCF4@!5GygQx(+<3drP53@VD8JemuMht z6_=Jq!P^Fa4`oeugv|rKsOr)$9{Y}U0a;nuPqdnnl-vs51>tGHazXPX!n4#n>A%kv zEME-(CJTutKs+YG1KS?S{Vk>Y;ymFyU{S$)#yW$(zQ{(q3|LOJvF?0jJH$7%CyO3z1p$QlXQ^WuI%gJd8a$avEhHSwP$&BPT@u?OYaQV!!+I7w~t<>FGVdbtcIVNXaw1913Dw@YNnX zefso0+SP`J2Bf?K)U5uE4Wn99Vs6v0d{g*{Yxe-7Q8X^gG`SC!Dr#Y|;((@Z`3|Ym zvr6!~Eb11zCB$7VK7o36SkJzcjo6VR?pBR~n)jh+* zI~05mEb}gqjL2EXE@~)p9*5a@--jqC=2AmlKY4DCJbfPC`bBk#`R)Dh0N$^|Z=UYf;tqBM`fN|IxjfECd%tBcS$;qd>PE-1qbDa8A$pT=jEm#Ra0|>${=WoM3#}RW3 zD<7}>!YjeDaiw#JCPl&VgIc<3#)oM6mF@irXdI@cy5Z>P7{}0LR&upg$c~95Cq5B% zA7yTCZrbR|#G?^wv-CF6HY7$m&L|)GT~jK#8d{j{7zgOBP;kg1yzT9!b33=P_Y=uC zbAx}llw8oDRastL?RKAI5*|%(E*JYYi=V#7A?F8<6t%<%DsyIuxTMsOx5XAzeo ze(()a3*l%*1tx%gELK2)+$d2YT0+JO={^|1(9{&0;fCT=0RmqT6r}779UmJrFfh0Z zGf4p8eZPLlMl65llReu}nK~&ugS9?-+<#^R0Kh6h==t??Z+rG@aGG+^4>@I$v1@SF z{mcJ*y@7D||4(Xy`{r$VRg9(R)vFw!|Km6m&M<}w{hJj0E3^z50zo+O{ovs13~9IK z&5nPVoytlAGSOkzNdvsXM`!EO@dG&qpCSfdHp^q>_w4^=po~lMH$XmSgvg_G5<~$J z012Lb=w+Zkh{J$r+4WCzZEb~vosj)b07DDSr`|I;@1jADFR>jg!SwlUJ_LE29$~WI zUfi1t$qf&u((KkP^a1ba;(OVS?<{52s~@hvftK$g{?U^G*wKyTw4g$B4Yt^omXzc{ zArYK2L{WI4`5}LPKaCWGoeRJlQnk&ImU@yc?US-D^~dY`71{siSI!0PS!%?c4KQSL z3O&M!KX2R#7ra+fz*ha99nJ-{1+5Bnej*(5fYRQr`wZ+W>yII`?9Vt?jJ?hM`D*dd zG9F?V5-*^s-Dx1}^1*c5FN+f91(0n)lD6YYJ2#l)hdGe-?q+5^NOXh<51&1r%%aD` zz=NEIOApW;wg1KNKvMo?eg0{p21rWejnoj3M-qSK_v1{@7g5l`QU^UZ@5{f=grbLQ z;ea6;#?U{6!SEktzO=EB2>;JFOHnClSndbq|{v43NMQ3g95o4IQ8>@pV zk7_*9kU(CGTnF3`^lvDa5;6_4_4zoMU`Hj-tDm!94R77bx?O8+Z`}(Br3SW<)U@41 zqam`5^xNrk6_r})M^jKx+G(p@HHKr zEKa)?u?FuL&gnF zj8L-U?x)e_5d?2HqQyg3!59a?$`M$zTeo}oEHAf8p|FVExTuApJIJ&Cb5EL@9wBMX zq*E>w3O=O1(Oo>P4nbUl&ESNd2~(%@6#)DI8uDT^GrCG2HiV&g2vWmx+KI=oMere) ytfGIeL5qiwd>V0sY%WFEB#2`0`_ is available, the functions +:func:`~mpmath.plot` and :func:`~mpmath.cplot` can be used to plot functions +respectively as x-y graphs and in the complex plane. Also, +:func:`~mpmath.splot` can be used to produce 3D surface plots. + +Function curve plots +----------------------- + +.. plot:: + + from mpmath import cos, plot, sin + plot([cos, sin], [-4, 4]) + +.. autofunction:: mpmath.plot + +Complex function plots +------------------------- + +.. plot:: + + from mpmath import cplot, fp + fp.cplot(fp.gamma, points=100000) + +.. autofunction:: mpmath.cplot + +3D surface plots +---------------- + +.. plot:: + + from mpmath import cos, pi, sin, splot + r, R = 1, 2.5 + f = lambda u, v: [r*cos(u), (R+r*sin(u))*cos(v), (R+r*sin(u))*sin(v)] + splot(f, [0, 2*pi], [0, 2*pi]) + +.. autofunction:: mpmath.splot + diff --git a/docs/references.rst b/docs/references.rst new file mode 100644 index 0000000..e49ef4f --- /dev/null +++ b/docs/references.rst @@ -0,0 +1,213 @@ +References +=================== + +The following is a non-comprehensive list of works used in the development of mpmath +or cited for examples or mathematical definitions used in this documentation. +References not listed here can be found in the source code. + +.. [AbramowitzStegun] M Abramowitz & I Stegun. *Handbook of Mathematical + Functions, 9th Ed.*, Tenth Printing, December 1972, + with corrections (electronic copy: + http://people.math.sfu.ca/~cbm/aands/) + +.. [Abate] Abate, J., P. Valko (2004). Multi-precision Laplace transform + inversion. *International Journal for Numerical Methods + in Engineering* 60:979-993, http://dx.doi.org/10.1002/nme.995 + +.. [Ainsworth] O. R. Ainsworth & L. W. Howell, "An integral representation + of the generalized Euler-Mascheroni constants", NASA + Technical Paper 2456 (1985), + http://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19850014994_1985014994.pdf + +.. [Bailey] D H Bailey. "Tanh-Sinh High-Precision Quadrature", + http://crd.lbl.gov/~dhbailey/dhbpapers/dhb-tanh-sinh.pdf + +.. [Bellman] Bellman, R., R.E. Kalaba, J.A. Lockett (1966). *Numerical + inversion of the Laplace transform: Applications to Biology, + Economics, Engineering, and Physics*. Elsevier. + +.. [BenderOrszag] C M Bender & S A Orszag. *Advanced Mathematical Methods for + Scientists and Engineers*, Springer 1999 + +.. [Bernoulli] The Bernoulli Number Page: http://www.bernoulli.org/ + +.. [BorweinBailey] J Borwein, D H Bailey & R Girgensohn. *Experimentation in + Mathematics - Computational Paths to Discovery*, + A K Peters, 2003 + +.. [BorweinBorwein] J Borwein & P B Borwein. *Pi and the AGM: A Study in + Analytic Number Theory and Computational Complexity*, + Wiley 1987 + +.. [BorweinTanhSinh] Borwein, Jonathan Michael and Lingyun Ye. “Quadratic + Convergence of the Tanh-sinh Quadrature Rule.” (2006). + https://web.archive.org/web/20080221230631/http://users.cs.dal.ca/~jborwein/tanh-sinh.pdf + +.. [BorweinZeta] P Borwein. "An Efficient Algorithm for the Riemann Zeta + Function", http://www.cecm.sfu.ca/personal/pborwein/PAPERS/P155.pdf + +.. [Brent79] R. P. Brent, On the Zeros of the Riemann Zeta Function in the + Critical Strip, Math. Comp. 33 (1979) 1361--1372 + +.. [Brent86] R. P. Brent, J. van de Lune, H. J. J. te Riele, D. T. Winter, + 'On the Zeros of the Riemann Zeta Function in the Critical + Strip. II', Math. Comp. 39 (1982) 681--688. + +.. [Buhring] Wolfgang Buhring, "Generalized Hypergeometric Functions at Unit + Argument", Proc. Amer. Math. Soc., Vol. 114, No. 1 (Jan. 1992), + pp.145-153 + +.. [CabralRosetti] L G Cabral-Rosetti & M A Sanchis-Lozano. "Appell Functions + and the Scalar One-Loop Three-point Integrals in Feynman + Diagrams". http://arxiv.org/abs/hep-ph/0206081 + +.. [Carlson] B C Carlson. "Numerical computation of real or complex elliptic + integrals". http://arxiv.org/abs/math/9409227v1 + +.. [Coffey] M. W. Coffey, "The Stieltjes constants, their relation to the + `\eta_j` coefficients, and representation of the Hurwitz zeta + function", arXiv:0706.0343v1 http://arxiv.org/abs/0706.0343 + +.. [Cohen] Cohen, A.M. (2007). Numerical Methods for Laplace Transform + Inversion, Springer. + +.. [Corless] R M Corless et al. "On the Lambert W function", Adv. Comp. + Math. 5 (1996) 329-359. + http://www.apmaths.uwo.ca/~djeffrey/Offprints/W-adv-cm.pdf + +.. [Crandall] Richard Crandall, "Note on fast polylogarithm computation" + http://www.reed.edu/physics/faculty/crandall/papers/Polylog.pdf + +.. [Davies] Davies, B. (2005). *Integral Transforms and their Applications*, + Third Edition. Springer. + +.. [Davies79] Davies, B., B. Martin (1979). Numerical inversion of the Laplace + transform: a survey and comparison of methods. *Journal of + Computational Physics* 33:1-32, + http://dx.doi.org/10.1016/0021-9991(79)90025-1 + +.. [Duffy93] Duffy, D.G. (1993). On the numerical inversion of Laplace + transforms: Comparison of three new methods on characteristic + problems from applications. *ACM Transactions on Mathematical + Software* 19(3):333-359, http://dx.doi.org/10.1145/155743.155788 + +.. [Duffy98] Duffy, D.G. (1998). Advanced Engineering Mathematics, CRC Press. + +.. [DLMF] NIST Digital Library of Mathematical Functions. http://dlmf.nist.gov/ + +.. [Froberg] Carl-Erik Froberg, "On the prime zeta function", BIT 8 (1968), + pp. 187-202. + +.. [Glasserman] P. Glasserman, J. Ruiz-Mata (2006). Computing the credit loss + distribution in the Gaussian copula model: a comparison of + methods. *Journal of Credit Risk* 2(4):33-66, + 10.21314/JCR.2006.057 + +.. [Golub] golub, "some modified matrix eigenvalue problems", siam review + 15, p. 318-334 (1973) + +.. [GolubWelsch] golub and welsch, "calculations of gaussian quadrature + rules", mathematics of computation 23, p. 221-230 (1969) + +.. [Gourdon] Xavier Gourdon & Pascal Sebah, The Euler constant: gamma + http://numbers.computation.free.fr/Constants/Gamma/gamma.pdf + +.. [GradshteynRyzhik] I S Gradshteyn & I M Ryzhik, A Jeffrey & D Zwillinger + (eds.), *Table of Integrals, Series and Products*, + Seventh edition (2007), Elsevier + +.. [GravesMorris] P R Graves-Morris, D E Roberts & A Salam. "The epsilon + algorithm and related topics", *Journal of Computational + and Applied Mathematics*, Volume 122, Issue 1-2 + (October 2000) + +.. [Homeier] H.H.H. Homeier - "Scalar Levin-Type Sequence Transformations" + arXiv:math/0005209 + +.. [Hoog] de Hoog, F., J. Knight, A. Stokes (1982). An improved method for + numerical inversion of Laplace transforms. *SIAM Journal of + Scientific and Statistical Computing* 3:357-366, + http://dx.doi.org/10.1137/0903022 + +.. [Kresser] Numerical Methods for General and Structured Eigenvalue Problems + +.. [Kuhlman] Kuhlman, K.L., (2013). Review of Inverse Laplace Transform + Algorithms for Laplace-Space Numerical Approaches, + *Numerical Algorithms*, 63(2):339-355. + http://dx.doi.org/10.1007/s11075-012-9625-3 + +.. [Lune84] J. van de Lune, 'Sums of Equal Powers of Positive Integers', + Dissertation, Vrije Universiteit te Amsterdam, Centrum voor + Wiskunde en Informatica, Amsterdam, 1984. + +.. [Lune86] J. van de Lune, H. J. J. te Riele, 'On the Zeros of the Riemann + Zeta Function in the Critical Strip. III', Math. Comp. 41 + (1983) 759--767. + +.. [MPFR] The MPFR team. "The MPFR Library: Algorithms and Proofs", + http://www.mpfr.org/algorithms.pdf + +.. [Michel] N. Michel, "Precise Coulomb wave functions for a wide + range of complex `l`, `\eta` and `z`", + http://arxiv.org/abs/physics/0702051v1 + +.. [OEIS] The On-Line Encyclopedia of Integer Sequences (OEIS). + +.. [Sidi] A. Sidi - "Pratical Extrapolation + Methods". + +.. [Slater] L J Slater. *Generalized Hypergeometric Functions*. + Cambridge University Press, 1966 + +.. [Spouge] J L Spouge. "Computation of the gamma, digamma, and trigamma + functions", SIAM J. Numer. Anal. Vol. 31, No. 3, pp. 931-944, + June 1994. + +.. [SrivastavaKarlsson] H M Srivastava & P W Karlsson. *Multiple Gaussian + Hypergeometric Series*. Ellis Horwood, 1985. + +.. [Stehfest] Stehfest, H. (1970). Algorithm 368: numerical inversion of + Laplace transforms. *Communications of the ACM* 13(1):47-49, + http://dx.doi.org/10.1145/361953.361969 + +.. [Stoer] Stoer, Bulirsch - Introduction to Numerical Analysis. + +.. [Stroud] stroud and secrest, "gaussian quadrature formulas", + prentice-hall (1966) + +.. [Talbot] Talbot, A. (1979). The accurate numerical inversion of Laplace + transforms. *IMA Journal of Applied Mathematics* 23(1):97, + http://dx.doi.org/10.1093/imamat/23.1.97 + +.. [Thompson] I.J. Thompson & A.R. Barnett, "Coulomb and Bessel Functions + of Complex Arguments and Order", J. Comp. Phys., vol 64, no. + 2, June 1986. + +.. [Trudgian] T. Trudgian, Improvements to Turing Method, + Math. Comp. + +.. [Vidunas] R Vidunas. "Identities between Appell's and hypergeometric + functions". http://arxiv.org/abs/0804.0655 + +.. [Voros2003] A. Voros, Zeta functions for the Riemann zeros, Ann. + Institute Fourier, 53, (2003) 665--699. + +.. [Voros2009] A. Voros, Zeta functions over Zeros of Zeta Functions, + Lecture Notes of the Unione Matematica Italiana, Springer, 2009. + +.. [Weisstein] E W Weisstein. *MathWorld*. http://mathworld.wolfram.com/ + +.. [Weniger] E.J. Weniger - "Nonlinear Sequence Transformations for the + Acceleration of Convergence and the Summation of Divergent + Series" arXiv:math/0306302 + +.. [WhittakerWatson] E T Whittaker & G N Watson. *A Course of Modern Analysis*. + 4th Ed. 1946 Cambridge University Press + +.. [Widder] Widder, D. (1941). *The Laplace Transform*. Princeton. + +.. [Wikipedia] *Wikipedia, the free encyclopedia*. + http://en.wikipedia.org/wiki/Main_Page + +.. [WolframFunctions] Wolfram Research, Inc. *The Wolfram Functions Site*. + http://functions.wolfram.com/ diff --git a/docs/setup.rst b/docs/setup.rst new file mode 100644 index 0000000..b6b8996 --- /dev/null +++ b/docs/setup.rst @@ -0,0 +1,160 @@ +Setting up mpmath +================= + +Mpmath requires at least Python 3.10. It has been tested with CPython 3.10 +through 3.15 and for PyPy 3.11. + +Download and installation +------------------------- + +Using pip +......... + +Releases are registered on PyPI, so you can install latest release +of the Mpmath with pip:: + + pip install mpmath + +or some specific version with:: + + pip install mpmath==1.3.0 + +You can install also extra dependencies, e.g. `gmpy2 +`_ support:: + + pip install mpmath[gmpy2] + +.. tip:: + + Use :mod:`venv` to create isolated Python environment first, + instead of installing everything system-wide. + +Debian/Ubuntu +............. + +Debian and Ubuntu users can install mpmath with:: + + sudo apt install python3-mpmath + +See `debian `_ and +`ubuntu `_ package information; +please verify that you are getting the latest version. + +Current development version +........................... + +If you are a developer or like to get the latest updates as they come, be sure +to install from git:: + + git clone git://github.com/mpmath/mpmath.git + cd mpmath + pip install -e .[develop,docs] + +Checking that it works +...................... + +After the setup has completed, you should be able to fire up the interactive +Python interpreter and do the following:: + + >>> from mpmath import mp, mpf, pi + >>> mp.dps = 50 + >>> print(mpf(2) ** mpf('0.5')) + 1.4142135623730950488016887242096980785696718753769 + >>> print(2*pi) + 6.2831853071795864769252867665590057683943387987502 + +.. tip:: + + :ref:`Run mpmath as a module ` for interactive work:: + + python -m mpmath + + +Using gmpy2 (optional) +---------------------- + +If `gmpy2 `_ version 2.3.0 or later is +installed on your system, mpmath will automatically detect it and transparently +use gmpy2 integers instead of Python integers. This makes mpmath much faster, +especially at high precision (approximately above 100 digits). + +To verify that mpmath uses gmpy2, check the internal variable ``BACKEND`` is +equal to 'gmpy'. + +Using the gmpy2 backend can be disabled by setting the ``MPMATH_NOGMPY`` +environment variable. Note that the mode cannot be switched during runtime; +mpmath must be re-imported for this change to take effect. + +Alternatively, you can use `python-gmp +`_ extension. The ``BACKEND`` value +will be equal to 'gmp' in this case. If both extensions are installed on your +system, the gmpy2 will be preferred. + +Running tests +------------- + +It is recommended that you run mpmath's full set of unit tests to make sure +everything works. The `pytest `_ is a required dependence +for testing. The tests are located in the ``tests`` subdirectory of the mpmath +source tree. They can be run using:: + + pytest --pyargs mpmath + +Developers may run tests from the source tree with:: + + pytest + +If any test fails, please send a detailed bug report to the `mpmath issue +tracker `_. + +Compiling the documentation +--------------------------- + +If you downloaded the source package, the text source for these documentation +pages is included in the ``docs`` directory. The documentation can be compiled +to pretty HTML using `Sphinx `_:: + + sphinx-build --color -W --keep-going -b html docs build/sphinx/html + +The create a PDF:: + + sphinx-build --color -W --keep-going -b latex docs build/sphinx/latex + make -C build/sphinx/latex all-pdf + +Some additional demo scripts are available in the ``demo`` directory included +in the source package. + +Mpmath under Sage +------------------- + +Mpmath is a standard package in `Sage `_, in version 4.1 or later of Sage. +Mpmath is preinstalled a regular Python module, and can be imported as usual within Sage:: + + ---------------------------------------------------------------------- + | Sage Version 4.1, Release Date: 2009-07-09 | + | Type notebook() for the GUI, and license() for information. | + ---------------------------------------------------------------------- + sage: import mpmath + sage: mpmath.mp.dps = 50 + sage: print(mpmath.mpf(2) ** 0.5) + 1.4142135623730950488016887242096980785696718753769 + +In Sage, mpmath can alternatively be imported via the interface library +``sage.libs.mpmath.all``. For example:: + + sage: import sage.libs.mpmath.all as mpmath + +This module provides a few extra conversion functions, including ``mpmath.call()`` +which permits calling any mpmath function with Sage numbers as input, and getting +Sage ``RealNumber`` or ``ComplexNumber`` instances +with the appropriate precision back:: + + sage: w = mpmath.call(mpmath.erf, 2+3*I, prec=100) + sage: w + -20.829461427614568389103088452 + 8.6873182714701631444280787545*I + sage: type(w) + + sage: w.prec() + 100 + +See the help for ``sage.libs.mpmath.all`` for further information. diff --git a/docs/technical.rst b/docs/technical.rst new file mode 100644 index 0000000..9446c31 --- /dev/null +++ b/docs/technical.rst @@ -0,0 +1,158 @@ +Precision and representation issues +=================================== + +Most of the time, using mpmath is simply a matter of setting the desired precision and entering a formula. For verification purposes, a quite (but not always!) reliable technique is to calculate the same thing a second time at a higher precision and verifying that the results agree. + +To perform more advanced calculations, it is important to have some understanding of how mpmath works internally and what the possible sources of error are. This section gives an overview of arbitrary-precision binary floating-point arithmetic and some concepts from numerical analysis. + +The following concepts are important to understand: + +* The main sources of numerical errors are rounding and cancellation, which are due to the use of finite-precision arithmetic, and truncation or approximation errors, which are due to approximating infinite sequences or continuous functions by a finite number of samples. +* Errors propagate between calculations. A small error in the input may result in a large error in the output. +* Most numerical algorithms for complex problems (e.g. integrals, derivatives) give wrong answers for sufficiently ill-behaved input. Sometimes virtually the only way to get a wrong answer is to design some very contrived input, but at other times the chance of accidentally obtaining a wrong result even for reasonable-looking input is quite high. +* Like any complex numerical software, mpmath has implementation bugs. You should be reasonably suspicious about any results computed by mpmath, even those it claims to be able to compute correctly! If possible, verify results analytically, try different algorithms, and cross-compare with other software. + +Precision, error and tolerance +------------------------------ + +The following terms are common in this documentation: + +- *Precision* (or *working precision*) is the precision at which floating-point arithmetic operations are performed. +- *Error* is the difference between a computed approximation and the exact result. +- *Accuracy* is the inverse of error. +- *Tolerance* is the maximum error (or minimum accuracy) desired in a result. + +Error and accuracy can be measured either directly, or logarithmically in bits or digits. Specifically, if a `\hat y` is an approximation for `y`, then + +- (Direct) absolute error = `|\hat y - y|` +- (Direct) relative error = `|\hat y - y| |y|^{-1}` +- (Direct) absolute accuracy = `|\hat y - y|^{-1}` +- (Direct) relative accuracy = `|\hat y - y|^{-1} |y|` +- (Logarithmic) absolute error = `\log_b |\hat y - y|` +- (Logarithmic) relative error = `\log_b |\hat y - y| - \log_b |y|` +- (Logarithmic) absolute accuracy = `-\log_b |\hat y - y|` +- (Logarithmic) relative accuracy = `-\log_b |\hat y - y| + \log_b |y|` + +where `b = 2` and `b = 10` for bits and digits respectively. Note that: + +- The logarithmic error roughly equals the position of the first incorrect bit or digit +- The logarithmic accuracy roughly equals the number of correct bits or digits in the result + +These definitions also hold for complex numbers, using `|a+bi| = \sqrt{a^2+b^2}`. + +*Full accuracy* means that the accuracy of a result at least equals *prec*-1, i.e. it is correct except possibly for the last bit. + +Representation of numbers +------------------------- + +Mpmath uses binary arithmetic. A binary floating-point number is a number of the form `man \times 2^{exp}` where both *man* (the *mantissa*) and *exp* (the *exponent*) are integers. Some examples of floating-point numbers are given in the following table. + + +--------+----------+----------+ + | Number | Mantissa | Exponent | + +========+==========+==========+ + | 3 | 3 | 0 | + +--------+----------+----------+ + | 10 | 5 | 1 | + +--------+----------+----------+ + | -16 | -1 | 4 | + +--------+----------+----------+ + | 1.25 | 5 | -2 | + +--------+----------+----------+ + +The representation as defined so far is not unique; one can always multiply the mantissa by 2 and subtract 1 from the exponent with no change in the numerical value. In mpmath, numbers are always normalized so that *man* is an odd number, with the exception of zero which is always taken to have *man = exp = 0*. With these conventions, every representable number has a unique representation. (Mpmath does not currently distinguish between positive and negative zero.) + +Simple mathematical operations are now easy to define. Due to uniqueness, equality testing of two numbers simply amounts to separately checking equality of the mantissas and the exponents. Multiplication of nonzero numbers is straightforward: `(m 2^e) \times (n 2^f) = (m n) \times 2^{e+f}`. Addition is a bit more involved: we first need to multiply the mantissa of one of the operands by a suitable power of 2 to obtain equal exponents. + +Contrary to popular belief, floating-point *numbers* do not come with an inherent "small uncertainty", although floating-point *arithmetic* generally is inexact. Every binary floating-point number is an exact rational number. With arbitrary-precision integers used for the mantissa and exponent, floating-point numbers can be added, subtracted and multiplied *exactly*. In particular, integers and integer multiples of 1/2, 1/4, 1/8, 1/16, etc. can be represented, added and multiplied exactly in binary floating-point arithmetic. + +Floating-point arithmetic is generally approximate because the size of the mantissa must be limited for efficiency reasons. The maximum allowed width (bitcount) of the mantissa is called the precision or *prec* for short. Sums and products of floating-point numbers are exact as long as the absolute value of the mantissa is smaller than `2^{prec}`. As soon as the mantissa becomes larger than this, it is truncated to contain at most *prec* bits (the exponent is incremented accordingly to preserve the magnitude of the number), and this operation introduces a rounding error. Division is also generally inexact; although we can add and multiply exactly by setting the precision high enough, no precision is high enough to represent for example 1/3 exactly (the same obviously applies for roots, trigonometric functions, etc). + +The special numbers ``+inf``, ``-inf`` and ``nan`` are represented internally by a zero mantissa and a nonzero exponent. + +Mpmath uses arbitrary precision integers for both the mantissa and the exponent, so numbers can be as large in magnitude as permitted by the computer's memory. Some care may be necessary when working with extremely large numbers. Although standard arithmetic operators are safe, it is for example futile to attempt to compute the exponential function of of `10^{100000}`. Mpmath does not complain when asked to perform such a calculation, but instead chugs away on the problem to the best of its ability, assuming that computer resources are infinite. In the worst case, this will be slow and allocate a huge amount of memory; if entirely impossible Python will at some point raise ``OverflowError: long int too large to convert to int``. + +For further details on how the arithmetic is implemented, refer to the mpmath source code. The basic arithmetic operations are found in the ``libmp`` directory; many functions there are commented extensively. + +Decimal issues +-------------- + +Mpmath uses binary arithmetic internally, while most interaction with the user is done via the decimal number system. Translating between binary and decimal numbers is a somewhat subtle matter; many Python novices run into the following "bug" (addressed in the `General Python FAQ `_):: + + >>> 1.2 - 1.0 + 0.19999999999999996 + +Decimal fractions fall into the category of numbers that generally cannot be represented exactly in binary floating-point form. For example, none of the numbers 0.1, 0.01, 0.001 has an exact representation as a binary floating-point number. Although mpmath can approximate decimal fractions with any accuracy, it does not solve this problem for all uses; users who need *exact* decimal fractions should look at the ``decimal`` module in Python's standard library (or perhaps use fractions, which are much faster). + +With *prec* bits of precision, an arbitrary number can be approximated relatively to within `2^{-prec}`, or within `10^{-dps}` for *dps* decimal digits. The equivalent values for *prec* and *dps* are therefore related proportionally via the factor `C = \log(10)/\log(2)`, or roughly 3.32. For example, the standard (binary) precision in mpmath is 53 bits, which corresponds to a decimal precision of 15.95 digits. + +More precisely, mpmath uses the following formulas to translate between *prec* and *dps*:: + + dps(prec) = max(1, round(int(prec)/C - 1)) + + prec(dps) = max(1, round((int(dps) + 1)*C)) + +Note that the dps is set 1 decimal digit lower than the corresponding binary precision. This is done to hide minor rounding errors and artifacts resulting from binary-decimal conversion. As a result, mpmath interprets 53 bits as giving 15 digits of decimal precision, not 16. + +The *dps* value controls the number of digits to display when printing numbers with :class:`str`, while the decimal precision used by :func:`repr` is set two or three digits higher. For example, with (default) 15 dps we have:: + + >>> from mpmath import pi + >>> str(pi) + '3.14159265358979' + >>> repr(+pi) + "mpf('3.1415926535897931')" + +The extra digits in the output from ``repr`` ensure that ``x == eval(repr(x))`` holds, i.e. that numbers can be converted to strings and back losslessly. + +It should be noted that precision and accuracy do not always correlate when translating between binary and decimal. As a simple example, the number 0.1 has a decimal precision of 1 digit but is an infinitely accurate representation of 1/10. Conversely, the number `2^{-50}` has a binary representation with 1 bit of precision that is infinitely accurate; the same number can actually be represented exactly as a decimal, but doing so requires 35 significant digits:: + + 0.00000000000000088817841970012523233890533447265625 + +All binary floating-point numbers can be represented exactly as decimals (possibly requiring many digits), but the converse is false. + +Correctness guarantees +---------------------- + +Basic arithmetic operations (with the ``mp`` context) are always performed with correct rounding. Results that can be represented exactly are guranteed to be exact, and results from single inexact operations are guaranteed to be the best possible rounded values. For higher-level operations, mpmath does not generally guarantee correct rounding. In general, mpmath only guarantees that it will use at least the user-set precision to perform a given calculation. *The user may have to manually set the working precision higher than the desired accuracy for the result, possibly much higher.* + +Functions for evaluation of transcendental functions, linear algebra operations, numerical integration, etc., usually automatically increase the working precision and use a stricter tolerance to give a correctly rounded result with high probability: for example, at 50 bits the temporary precision might be set to 70 bits and the tolerance might be set to 60 bits. It can often be assumed that such functions return values that have full accuracy, given inputs that are exact (or sufficiently precise approximations of exact values), but the user must exercise judgement about whether to trust mpmath. + +The level of rigor in mpmath covers the entire spectrum from "always correct by design" through "nearly always correct" and "handling the most common errors" to "just computing blindly and hoping for the best". Of course, a long-term development goal is to successively increase the rigor where possible. The following list might give an idea of the current state. + +Operations that are correctly rounded: + +* Addition, subtraction and multiplication of real and complex numbers. +* Division and square roots of real numbers. +* Powers of real numbers, assuming sufficiently small integer exponents (huge powers are rounded in the right direction, but possibly farther than necessary). +* Conversion from decimal to binary, for reasonably sized numbers (roughly between `10^{-100}` and `10^{100}`). +* Conversion from/to machine floating-point numbers. +* Typically, transcendental functions for exact input-output pairs. + +Operations that should be fully accurate (however, the current implementation may be based on a heuristic error analysis): + +* Radix conversion (large or small numbers). +* Mathematical constants like `\pi`. +* Both real and imaginary parts of exp, cos, sin, cosh, sinh, log. +* Other elementary functions (the largest of the real and imaginary part). +* The gamma and log-gamma functions (the largest of the real and the imaginary part; both, when close to real axis). +* Some functions based on hypergeometric series (the largest of the real and imaginary part). + +Correctness of root-finding, numerical integration, etc. largely depends on the well-behavedness of the input functions. Specific limitations are sometimes noted in the respective sections of the documentation. + +Double precision emulation +-------------------------- + +On most systems, Python's ``float`` type represents an IEEE 754 *double precision* number, with a precision of 53 bits and rounding-to-nearest. With default precision (``mp.prec = 53``), the mpmath ``mpf`` type roughly emulates the behavior of the ``float`` type. Sources of incompatibility include the following: + +* In hardware floating-point arithmetic, the size of the exponent is restricted to a fixed range: regular Python floats have a range between roughly `10^{-300}` and `10^{300}`. Mpmath does not emulate overflow or underflow when exponents fall outside this range. +* On some systems, Python uses 80-bit (extended double) registers for floating-point operations. Due to double rounding, this makes the ``float`` type less accurate. This problem is only known to occur with Python versions compiled with GCC on 32-bit systems. +* Machine floats very close to the exponent limit round subnormally, meaning that they lose accuracy (Python may raise an exception instead of rounding a ``float`` subnormally). +* Mpmath is able to produce more accurate results for transcendental functions. + +Further reading +--------------- + +There are many excellent textbooks on numerical analysis and floating-point arithmetic. Some good web resources are: + +* `David Goldberg, What Every Computer Scientist Should Know About Floating-Point Arithmetic `_ +* `The Wikipedia article about numerical analysis `_ +* [MPFR]_ diff --git a/mpmath/__init__.py b/mpmath/__init__.py new file mode 100644 index 0000000..85d5151 --- /dev/null +++ b/mpmath/__init__.py @@ -0,0 +1,469 @@ +from ._version import __version__ + +import functools +import sys +import types + +from .usertools import monitor, timing + +from .ctx_fp import FPContext +from .ctx_mp import MPContext +from .ctx_iv import MPIntervalContext + +fp = FPContext() +mp = MPContext() +iv = MPIntervalContext() + +fp._mp = mp +mp._mp = mp +iv._mp = mp +mp._fp = fp +fp._fp = fp +mp._iv = iv +fp._iv = iv +iv._iv = iv + +make_mpf = mp.make_mpf +make_mpc = mp.make_mpc + +extraprec = mp.extraprec +extradps = mp.extradps +workprec = mp.workprec +workdps = mp.workdps +autoprec = mp.autoprec +maxcalls = mp.maxcalls +memoize = mp.memoize + +mag = mp.mag + +bernfrac = mp.bernfrac + +qfrom = mp.qfrom +mfrom = mp.mfrom +kfrom = mp.kfrom +taufrom = mp.taufrom +qbarfrom = mp.qbarfrom +ellipfun = mp.ellipfun +jtheta = mp.jtheta +kleinj = mp.kleinj +eta = mp.eta + +# Weierstrass elliptic functions +weierinvariants = mp.weierinvariants +weierhalfperiods = mp.weierhalfperiods +weierp = mp.weierp +weierpprime = mp.weierpprime +weiersigma = mp.weiersigma +weierzeta = mp.weierzeta +weierpinv = mp.weierpinv + +qp = mp.qp +qhyper = mp.qhyper +qgamma = mp.qgamma +qfac = mp.qfac + +nint_distance = mp.nint_distance + +plot = mp.plot +cplot = mp.cplot +splot = mp.splot + +odefun = mp.odefun + +jacobian = mp.jacobian +findroot = mp.findroot +multiplicity = mp.multiplicity + +isinf = mp.isinf +isnan = mp.isnan +isnormal = mp.isnormal +isint = mp.isint +isfinite = mp.isfinite +almosteq = mp.almosteq +nan = mp.nan +rand = mp.rand + +absmin = mp.absmin +absmax = mp.absmax + +fraction = mp.fraction + +linspace = mp.linspace +arange = mp.arange + +mpmathify = convert = mp.convert +mpc = mp.mpc + +mpi = iv._mpi + +nstr = mp.nstr +nprint = mp.nprint +chop = mp.chop + +fneg = mp.fneg +fadd = mp.fadd +fsub = mp.fsub +fmul = mp.fmul +fdiv = mp.fdiv +fprod = mp.fprod + +quad = mp.quad +quadgl = mp.quadgl +quadts = mp.quadts +quadosc = mp.quadosc +quadsubdiv = mp.quadsubdiv + +invertlaplace = mp.invertlaplace +invlaptalbot = mp.invlaptalbot +invlapstehfest = mp.invlapstehfest +invlapdehoog = mp.invlapdehoog + +pslq = mp.pslq +identify = mp.identify +findpoly = mp.findpoly + +richardson = mp.richardson +shanks = mp.shanks +levin = mp.levin +cohen_alt = mp.cohen_alt +nsum = mp.nsum +nprod = mp.nprod +difference = mp.difference +diff = mp.diff +diffs = mp.diffs +diffs_prod = mp.diffs_prod +diffs_exp = mp.diffs_exp +diffun = mp.diffun +differint = mp.differint +taylor = mp.taylor +pade = mp.pade +polyval = mp.polyval +polyroots = mp.polyroots +fourier = mp.fourier +fourierval = mp.fourierval +sumem = mp.sumem +sumap = mp.sumap +chebyfit = mp.chebyfit +limit = mp.limit + +matrix = mp.matrix +eye = mp.eye +diag = mp.diag +zeros = mp.zeros +ones = mp.ones +hilbert = mp.hilbert +randmatrix = mp.randmatrix +swap_row = mp.swap_row +extend = mp.extend +norm = mp.norm +mnorm = mp.mnorm + +lu_solve = mp.lu_solve +lu = mp.lu +qr = mp.qr +unitvector = mp.unitvector +inverse = mp.inverse +pinv = mp.pinv +residual = mp.residual +qr_solve = mp.qr_solve +cholesky = mp.cholesky +cholesky_solve = mp.cholesky_solve +det = mp.det +cond = mp.cond +hessenberg = mp.hessenberg +schur = mp.schur +eig = mp.eig +eig_sort = mp.eig_sort +eigsy = mp.eigsy +eighe = mp.eighe +eigh = mp.eigh +svd_r = mp.svd_r +svd_c = mp.svd_c +svd = mp.svd +gauss_quadrature = mp.gauss_quadrature +rank = mp.rank + +expm = mp.expm +sqrtm = mp.sqrtm +powm = mp.powm +logm = mp.logm +sinm = mp.sinm +cosm = mp.cosm + +mpf = mp.mpf +j = mp.j +exp = mp.exp +expj = mp.expj +expjpi = mp.expjpi +ln = mp.ln +im = mp.im +re = mp.re +inf = mp.inf +ninf = mp.ninf +sign = mp.sign + +eps = mp.eps +pi = mp.pi +ln2 = mp.ln2 +ln10 = mp.ln10 +exp2 = mp.exp2 +log2 = mp.log2 +phi = mp.phi +e = mp.e +euler = mp.euler +catalan = mp.catalan +khinchin = mp.khinchin +glaisher = mp.glaisher +apery = mp.apery +degree = mp.degree +twinprime = mp.twinprime +mertens = mp.mertens + +ldexp = mp.ldexp +frexp = mp.frexp + +fsum = mp.fsum +fdot = mp.fdot + +sqrt = mp.sqrt +cbrt = mp.cbrt +exp = mp.exp +ln = mp.ln +log = mp.log +log10 = mp.log10 +power = mp.power +cos = mp.cos +sin = mp.sin +tan = mp.tan +cosh = mp.cosh +sinh = mp.sinh +tanh = mp.tanh +acos = mp.acos +asin = mp.asin +atan = mp.atan +asinh = mp.asinh +acosh = mp.acosh +atanh = mp.atanh +sec = mp.sec +csc = mp.csc +cot = mp.cot +sech = mp.sech +csch = mp.csch +coth = mp.coth +asec = mp.asec +acsc = mp.acsc +acot = mp.acot +asech = mp.asech +acsch = mp.acsch +acoth = mp.acoth +cospi = mp.cospi +sinpi = mp.sinpi +sinc = mp.sinc +sincpi = mp.sincpi +cos_sin = mp.cos_sin +cospi_sinpi = mp.cospi_sinpi +fabs = mp.fabs +re = mp.re +im = mp.im +conj = mp.conj +floor = mp.floor +ceil = mp.ceil +nint = mp.nint +frac = mp.frac +root = mp.root +nthroot = mp.nthroot +hypot = mp.hypot +fmod = mp.fmod +ldexp = mp.ldexp +frexp = mp.frexp +sign = mp.sign +arg = mp.arg +phase = mp.phase +polar = mp.polar +rect = mp.rect +degrees = mp.degrees +radians = mp.radians +atan2 = mp.atan2 +fib = mp.fib +fibonacci = mp.fibonacci +lambertw = mp.lambertw +zeta = mp.zeta +altzeta = mp.altzeta +gamma = mp.gamma +rgamma = mp.rgamma +factorial = mp.factorial +fac = mp.fac +fac2 = mp.fac2 +beta = mp.beta +betainc = mp.betainc +psi = mp.psi +#psi0 = mp.psi0 +#psi1 = mp.psi1 +#psi2 = mp.psi2 +#psi3 = mp.psi3 +polygamma = mp.polygamma +digamma = mp.digamma +#trigamma = mp.trigamma +#tetragamma = mp.tetragamma +#pentagamma = mp.pentagamma +harmonic = mp.harmonic +bernoulli = mp.bernoulli +bernfrac = mp.bernfrac +stieltjes = mp.stieltjes +hurwitz = mp.hurwitz +dirichlet = mp.dirichlet +bernpoly = mp.bernpoly +eulerpoly = mp.eulerpoly +eulernum = mp.eulernum +polylog = mp.polylog +clsin = mp.clsin +clcos = mp.clcos +gammainc = mp.gammainc +lower_gamma = mp.lower_gamma +upper_gamma = mp.upper_gamma +gammaprod = mp.gammaprod +binomial = mp.binomial +rf = mp.rf +ff = mp.ff +hyper = mp.hyper +hyp0f1 = mp.hyp0f1 +hyp1f1 = mp.hyp1f1 +hyp1f2 = mp.hyp1f2 +hyp2f1 = mp.hyp2f1 +hyp2f2 = mp.hyp2f2 +hyp2f0 = mp.hyp2f0 +hyp2f3 = mp.hyp2f3 +hyp3f2 = mp.hyp3f2 +hyperu = mp.hyperu +hypercomb = mp.hypercomb +meijerg = mp.meijerg +foxh = mp.foxh +appellf1 = mp.appellf1 +appellf2 = mp.appellf2 +appellf3 = mp.appellf3 +appellf4 = mp.appellf4 +hyper2d = mp.hyper2d +bihyper = mp.bihyper +erf = mp.erf +erfc = mp.erfc +erfi = mp.erfi +erfinv = mp.erfinv +npdf = mp.npdf +ncdf = mp.ncdf +expint = mp.expint +e1 = mp.e1 +ei = mp.ei +li = mp.li +ci = mp.ci +si = mp.si +chi = mp.chi +shi = mp.shi +fresnels = mp.fresnels +fresnelc = mp.fresnelc +airyai = mp.airyai +airybi = mp.airybi +airyaizero = mp.airyaizero +airybizero = mp.airybizero +scorergi = mp.scorergi +scorerhi = mp.scorerhi +ellipk = mp.ellipk +ellipe = mp.ellipe +ellipf = mp.ellipf +ellippi = mp.ellippi +elliprc = mp.elliprc +elliprj = mp.elliprj +elliprf = mp.elliprf +elliprd = mp.elliprd +elliprg = mp.elliprg +agm = mp.agm +jacobi = mp.jacobi +chebyt = mp.chebyt +chebyu = mp.chebyu +legendre = mp.legendre +legenp = mp.legenp +legenq = mp.legenq +hermite = mp.hermite +pcfd = mp.pcfd +pcfu = mp.pcfu +pcfv = mp.pcfv +pcfw = mp.pcfw +gegenbauer = mp.gegenbauer +laguerre = mp.laguerre +spherharm = mp.spherharm +besselj = mp.besselj +j0 = mp.j0 +j1 = mp.j1 +besseli = mp.besseli +bessely = mp.bessely +besselk = mp.besselk +besseljzero = mp.besseljzero +besselyzero = mp.besselyzero +spherical_jn = mp.spherical_jn +spherical_yn = mp.spherical_yn +spherical_in = mp.spherical_in +spherical_kn = mp.spherical_kn +hankel1 = mp.hankel1 +hankel2 = mp.hankel2 +struveh = mp.struveh +struvel = mp.struvel +angerj = mp.angerj +webere = mp.webere +lommels1 = mp.lommels1 +lommels2 = mp.lommels2 +whitm = mp.whitm +whitw = mp.whitw +ber = mp.ber +bei = mp.bei +ker = mp.ker +kei = mp.kei +coulombc = mp.coulombc +coulombf = mp.coulombf +coulombg = mp.coulombg +barnesg = mp.barnesg +superfac = mp.superfac +hyperfac = mp.hyperfac +loggamma = mp.loggamma +siegeltheta = mp.siegeltheta +siegelz = mp.siegelz +grampoint = mp.grampoint +zetazero = mp.zetazero +riemannr = mp.riemannr +primepi = mp.primepi +primepi2 = mp.primepi2 +primezeta = mp.primezeta +bell = mp.bell +polyexp = mp.polyexp +expm1 = mp.expm1 +log1p = mp.log1p +powm1 = mp.powm1 +unitroots = mp.unitroots +cyclotomic = mp.cyclotomic +mangoldt = mp.mangoldt +secondzeta = mp.secondzeta +nzeros = mp.nzeros +backlunds = mp.backlunds +lerchphi = mp.lerchphi +stirling1 = mp.stirling1 +stirling2 = mp.stirling2 +squarew = mp.squarew +trianglew = mp.trianglew +sawtoothw = mp.sawtoothw +unit_triangle = mp.unit_triangle +sigmoid = mp.sigmoid + + +# Hack to guard against setting module properties instead of 'mp', Issue #657 +class _MPMathModule(types.ModuleType): + + def _helper(self, *args, prop=''): + raise AttributeError("cannot set '{name}' on 'mpmath'. Did you mean to " + "set '{name}' on 'mpmath.mp'?".format(name=prop)) + + dps = property(fset=functools.partial(_helper, prop='dps')) + prec = property(fset=functools.partial(_helper, prop='prec')) + pretty = property(fset=functools.partial(_helper, prop='pretty')) + trap_complex = property(fset=functools.partial(_helper, prop='trap_complex')) + + +sys.modules[__name__].__class__ = _MPMathModule +del functools, sys, types, _MPMathModule diff --git a/mpmath/__main__.py b/mpmath/__main__.py new file mode 100644 index 0000000..1cb1391 --- /dev/null +++ b/mpmath/__main__.py @@ -0,0 +1,186 @@ +""" +Python shell for mpmath. + +This is just a normal Python shell (IPython shell if you have the +IPython package installed), that adds default imports and run +some initialization code. +""" + +import argparse +import ast +import atexit +import os +import readline +import rlcompleter +import sys +import tokenize + +from mpmath import __version__ +from mpmath._interactive import (IntegerDivisionWrapper, wrap_float_literals, + wrap_hexbinfloats) + + +__all__ = () + + +parser = argparse.ArgumentParser(description=__doc__, + prog='python -m mpmath') +parser.add_argument('--no-wrap-division', + help="Don't wrap integer divisions with Fraction", + action='store_true') +parser.add_argument('--no-ipython', help="Don't use IPython", + action='store_true') +parser.add_argument('--no-wrap-floats', + help="Don't wrap float/complex literals", + action='store_true') +parser.add_argument('-V', '--version', + help='Print the mpmath version and exit', + action='store_true') +parser.add_argument('--prec', type=int, + help='Set default mpmath precision') +parser.add_argument('--no-pretty', help='Disable pretty-printing', + action='store_true') +parser.add_argument('--int-limits', + help="Enable string conversion length limitation for int's", + action='store_true') + + +def main(): + args, ipython_args = parser.parse_known_args() + + if args.version: + print(__version__) + sys.exit(0) + + if not args.int_limits: + sys.set_int_max_str_digits(0) + + lines = ['from mpmath import *', + 'import mpmath', + 'from fractions import Fraction'] + + if args.prec: + lines.append(f'mp.prec = {args.prec}') + if not args.no_pretty: + lines.append('mp.pretty = True') + lines.append('mp.pretty_dps = "repr"') + + try: + import IPython + import traitlets + except ImportError: + args.no_ipython = True + + if not args.no_ipython: + config = traitlets.config.loader.Config() + shell = config.InteractiveShell + ast_transformers = shell.ast_transformers + if not args.no_wrap_division: + ast_transformers.append(IntegerDivisionWrapper()) + shell.confirm_exit = False + config.TerminalIPythonApp.display_banner = False + config.TerminalInteractiveShell.autoformatter = None + + app = IPython.terminal.ipapp.TerminalIPythonApp.instance(config=config) + app.initialize(ipython_args) + shell = app.shell + for l in lines: + shell.run_cell(l, silent=True) + if not args.no_wrap_floats: + source = """ +from mpmath._interactive import wrap_float_literals, wrap_hexbinfloats +ip = get_ipython() +ip.input_transformers_post.append(wrap_float_literals) +ip.input_transformers_post.append(wrap_float_literals) +del ip +""" + shell.run_cell(source) + app.start() + else: + ast_transformers = [] + source_transformers = [] + ns = {} + + if not args.no_wrap_division: + ast_transformers.append(IntegerDivisionWrapper()) + if not args.no_wrap_floats: + source_transformers.append(wrap_hexbinfloats) + source_transformers.append(wrap_float_literals) + + try: + from _pyrepl.main import CAN_USE_PYREPL + if CAN_USE_PYREPL: # pragma: no cover + from _pyrepl.console import \ + InteractiveColoredConsole as InteractiveConsole + else: + raise ImportError + except ImportError: # pragma: no cover + from code import InteractiveConsole + + class MpmathConsole(InteractiveConsole): + """An interactive console with readline support.""" + + def __init__(self, ast_transformers=[], + source_transformers=[], **kwargs): + super().__init__(**kwargs) + self.ast_transformers = ast_transformers + self.source_transformers = source_transformers + + def runsource(self, source, filename='', symbol='single'): + if self.source_transformers: + last_line = source.endswith("\n") # signals the end of a block + try: + for t in self.source_transformers: + source = ''.join(t(source.splitlines(keepends=True))) + except SyntaxError: + pass # XXX: emit warning? + if last_line: + source += "\n" + + try: + code = self.compile(source, filename, 'exec') + except (OverflowError, SyntaxError, ValueError): + if sys.version_info >= (3, 13): + self.showsyntaxerror(filename, source=source) + else: # pragma: no cover + self.showsyntaxerror(filename) + return False + + if code is None: + return True + + if self.ast_transformers: + tree = ast.parse(source) + for t in self.ast_transformers: + tree = t.visit(tree) + ast.fix_missing_locations(tree) + source = ast.unparse(tree) + source += "\n" + + return super().runsource(source, filename=filename, symbol=symbol) + + c = MpmathConsole(ast_transformers=ast_transformers, + source_transformers=source_transformers, locals=ns) + + interactive_hook = getattr(sys, "__interactivehook__", None) + if interactive_hook is not None: # pragma: no branch + sys.audit("cpython.run_interactivehook", interactive_hook) + interactive_hook() + + for l in lines: + c.push(l) + + try: + from _pyrepl.main import CAN_USE_PYREPL + if CAN_USE_PYREPL: # pragma: no cover + from _pyrepl.simple_interact import \ + run_multiline_interactive_console + run_multiline_interactive_console(c) + else: + raise ImportError + except Exception: # pragma: no cover + c.interact('', '') + + +if __name__ == '__main__': + main() diff --git a/mpmath/_interactive.py b/mpmath/_interactive.py new file mode 100644 index 0000000..7a11fd2 --- /dev/null +++ b/mpmath/_interactive.py @@ -0,0 +1,90 @@ +import ast +import io +import re +import tokenize + + +class IntegerDivisionWrapper(ast.NodeTransformer): + """Wrap all int divisions in a call to :class:`~fractions.Fraction`.""" + + def visit_BinOp(self, node): + def is_integer(x): + if isinstance(x, ast.Constant) and isinstance(x.value, int): + return True + if isinstance(x, ast.UnaryOp) and isinstance(x.op, (ast.USub, + ast.UAdd)): + return is_integer(x.operand) + if isinstance(x, ast.BinOp) and isinstance(x.op, (ast.Add, + ast.Sub, + ast.Mult, + ast.Pow)): + return is_integer(x.left) and is_integer(x.right) + return False + + if isinstance(node.op, ast.Div) and all(map(is_integer, + [node.left, node.right])): + return ast.Call(ast.Name('Fraction', ast.Load()), + [node.left, node.right], []) + return self.generic_visit(node) + + +class _WrapFloats(ast.NodeTransformer): + """Wrap float literals by calls to specified type.""" + def __init__(self, lines, type): + super().__init__() + self.lines = lines + self.type = type + + def visit_Constant(self, node): + if isinstance(node.value, (float, complex)): + line = self.lines[node.lineno - 1] + value = line[node.col_offset:node.end_col_offset] + is_complex = value.endswith(('j', 'J')) + if is_complex: + value = value[:-1] + value = ast.Constant(value) + value = ast.Call(ast.Name(self.type, ast.Load()), [value], []) + if is_complex: + value = ast.BinOp(left=value, op=ast.Mult(), + right=ast.Constant(1j)) + return value + return node + + +def wrap_float_literals(lines): + """Wraps all float/complex literals with mpmath classes.""" + source = ''.join(lines) + tree = ast.parse(source) + tree = _WrapFloats(lines, 'mpf').visit(tree) + ast.fix_missing_locations(tree) + source = ast.unparse(tree) + return source.splitlines(keepends=True) + + +_HEXFLT_MATCHER = re.compile(r""" + (?: [^"' ]|^)[ ]*(?P + 0x + [0-9a-z]+ + (?: \.[0-9a-z]*)? + p(?:[+-])?[0-9]+ + ) +""", re.VERBOSE | re.IGNORECASE) +_BINFLT_MATCHER = re.compile(r""" + (?: [^"' ]|^)[ ]*(?P + 0b + [01]+ + (?: \.[01]*)? + p(?:[+-])?[0-9]+ + ) +""", re.VERBOSE | re.IGNORECASE) + + +def wrap_hexbinfloats(lines): + new_lines = [] + for line in lines: + for r in _HEXFLT_MATCHER.findall(line): + line = line.replace(r, 'mpf("' + r + '", base=16)') + for r in _BINFLT_MATCHER.findall(line): + line = line.replace(r, 'mpf("' + r + '", base=2)') + new_lines.append(line) + return new_lines diff --git a/mpmath/calculus/__init__.py b/mpmath/calculus/__init__.py new file mode 100644 index 0000000..040a380 --- /dev/null +++ b/mpmath/calculus/__init__.py @@ -0,0 +1,6 @@ +from . import calculus +# XXX: hack to set methods +from . import approximation +from . import differentiation +from . import extrapolation +from . import polynomials diff --git a/mpmath/calculus/approximation.py b/mpmath/calculus/approximation.py new file mode 100644 index 0000000..fcdf61b --- /dev/null +++ b/mpmath/calculus/approximation.py @@ -0,0 +1,252 @@ +from .calculus import defun + + +#----------------------------------------------------------------------------# +# Approximation methods # +#----------------------------------------------------------------------------# + +# The Chebyshev approximation formula is given at: +# http://mathworld.wolfram.com/ChebyshevApproximationFormula.html + +# The only major changes in the following code is that we return the +# expanded polynomial coefficients instead of Chebyshev coefficients, +# and that we automatically transform [a,b] -> [-1,1] and back +# for convenience. + +# Coefficient in Chebyshev approximation +def chebcoeff(ctx,f,a,b,j,N): + s = ctx.mpf(0) + h = ctx.mpf(0.5) + for k in range(1, N+1): + t = ctx.cospi((k-h)/N) + s += f(t*(b-a)*h + (b+a)*h) * ctx.cospi(j*(k-h)/N) + return 2*s/N + +# Generate Chebyshev polynomials T_n(ax+b) in expanded form +def chebT(ctx, a=1, b=0): + Tb = [1] + yield Tb + Ta = [b, a] + while 1: + yield Ta + # Recurrence: T[n+1](ax+b) = 2*(ax+b)*T[n](ax+b) - T[n-1](ax+b) + Tmp = [0] + [2*a*t for t in Ta] + for i, c in enumerate(Ta): Tmp[i] += 2*b*c + for i, c in enumerate(Tb): Tmp[i] -= c + Ta, Tb = Tmp, Ta + +@defun +def chebyfit(ctx, f, interval, N, error=False, asc=True): + r""" + Computes a polynomial of degree `N-1` that approximates the + given function `f` on the interval `[a, b]`. With ``error=True``, + :func:`~mpmath.chebyfit` also returns an accurate estimate of the + maximum absolute error; that is, the maximum value of + `|f(x) - P(x)|` for `x \in [a, b]`. + + :func:`~mpmath.chebyfit` uses the Chebyshev approximation formula, + which gives a nearly optimal solution: that is, the maximum + error of the approximating polynomial is very close to + the smallest possible for any polynomial of the same degree. + + Chebyshev approximation is very useful if one needs repeated + evaluation of an expensive function, such as function defined + implicitly by an integral or a differential equation. (For + example, it could be used to turn a slow mpmath function + into a fast machine-precision version of the same.) + + If *asc=False*, descending order of coefficients is used (the term + of largest degree - first). + + **Examples** + + Here we use :func:`~mpmath.chebyfit` to generate a low-degree approximation + of `f(x) = \cos(x)`, valid on the interval `[1, 2]`:: + + >>> from mpmath import mp, chebyfit, cos, nprint, polyval + >>> mp.pretty = True + >>> poly, err = chebyfit(cos, [1, 2], 5, error=True) + >>> nprint(poly) + [0.949553, 0.174141, -0.732491, 0.146166, 0.00291682] + >>> nprint(err, 12) + 1.61351758081e-5 + + The polynomial can be evaluated using ``polyval``:: + + >>> poly = chebyfit(cos, [1, 2], 5) + >>> nprint(polyval(poly, 1.6), 12) + -0.0291858904138 + >>> nprint(cos(1.6), 12) + -0.0291995223013 + + Sampling the true error at 1000 points shows that the error + estimate generated by ``chebyfit`` is remarkably good:: + + >>> error = lambda x: abs(cos(x) - polyval(poly, x)) + >>> nprint(max([error(1+n/1000.) for n in range(1000)]), 12) + 1.61349954245e-5 + + **Choice of degree** + + The degree `N` can be set arbitrarily high, to obtain an + arbitrarily good approximation. As a rule of thumb, an + `N`-term Chebyshev approximation is good to `N/(b-a)` decimal + places on a unit interval (although this depends on how + well-behaved `f` is). The cost grows accordingly: ``chebyfit`` + evaluates the function `(N^2)/2` times to compute the + coefficients and an additional `N` times to estimate the error. + + **Possible issues** + + One should be careful to use a sufficiently high working + precision both when calling ``chebyfit`` and when evaluating + the resulting polynomial, as the polynomial is sometimes + ill-conditioned. It is for example difficult to reach + 15-digit accuracy when evaluating the polynomial using + machine precision floats, no matter the theoretical + accuracy of the polynomial. (The option to return the + coefficients in Chebyshev form should be made available + in the future.) + + It is important to note the Chebyshev approximation works + poorly if `f` is not smooth. A function containing singularities, + rapid oscillation, etc can be approximated more effectively by + multiplying it by a weight function that cancels out the + nonsmooth features, or by dividing the interval into several + segments. + """ + if N <= 0: + raise ValueError("chebyfit requires N >= 1") + a, b = ctx._as_points(interval) + orig = ctx.prec + try: + ctx.prec = orig + int(N**0.5) + 20 + c = [chebcoeff(ctx,f,a,b,k,N) for k in range(N)] + d = [ctx.zero] * N + d[0] = -c[0]/2 + h = ctx.mpf(0.5) + T = chebT(ctx, ctx.mpf(2)/(b-a), ctx.mpf(-1)*(b+a)/(b-a)) + for (k, Tk) in zip(range(N), T): + for i in range(len(Tk)): + d[i] += c[k]*Tk[i] + # Estimate maximum error + err = ctx.zero + for k in range(N): + x = ctx.cos(ctx.pi*k/N) * (b-a)*h + (b+a)*h + err = max(err, abs(f(x) - ctx.polyval(d, x))) + finally: + ctx.prec = orig + if error: + return d if asc else d[::-1], +err + else: + return d if asc else d[::-1] + +@defun +def fourier(ctx, f, interval, N): + r""" + Computes the Fourier series of degree `N` of the given function + on the interval `[a, b]`. More precisely, :func:`~mpmath.fourier` returns + two lists `(c, s)` of coefficients (the cosine series and sine + series, respectively), such that + + .. math :: + + f(x) \sim \sum_{k=0}^N + c_k \cos(k m x) + s_k \sin(k m x) + + where `m = 2 \pi / (b-a)`. + + Note that many texts define the first coefficient as `2 c_0` instead + of `c_0`. The easiest way to evaluate the computed series correctly + is to pass it to :func:`~mpmath.fourierval`. + + **Examples** + + The function `f(x) = x` has a simple Fourier series on the standard + interval `[-\pi, \pi]`. The cosine coefficients are all zero (because + the function has odd symmetry), and the sine coefficients are + rational numbers:: + + >>> from mpmath import (mp, fourier, pi, nprint, plot, cosh, quad, + ... sqrt, fourierval) + >>> mp.pretty = True + >>> c, s = fourier(lambda x: x, [-pi, pi], 5) + >>> nprint(c) + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + >>> nprint(s) + [0.0, 2.0, -1.0, 0.666667, -0.5, 0.4] + + This computes a Fourier series of a nonsymmetric function on + a nonstandard interval:: + + >>> I = [-1, 1.5] + >>> f = lambda x: x**2 - 4*x + 1 + >>> cs = fourier(f, I, 4) + >>> nprint(cs[0]) + [0.583333, 1.12479, -1.27552, 0.904708, -0.441296] + >>> nprint(cs[1]) + [0.0, -2.6255, 0.580905, 0.219974, -0.540057] + + It is instructive to plot a function along with its truncated + Fourier series:: + + >>> plot([f, lambda x: fourierval(cs, I, x)], I) #doctest: +SKIP + + Fourier series generally converge slowly (and may not converge + pointwise). For example, if `f(x) = \cosh(x)`, a 10-term Fourier + series gives an `L^2` error corresponding to 2-digit accuracy:: + + >>> I = [-1, 1] + >>> cs = fourier(cosh, I, 9) + >>> g = lambda x: (cosh(x) - fourierval(cs, I, x))**2 + >>> nprint(sqrt(quad(g, I))) + 0.00467963 + + :func:`~mpmath.fourier` uses numerical quadrature. For nonsmooth functions, + the accuracy (and speed) can be improved by including all singular + points in the interval specification:: + + >>> nprint(fourier(abs, [-1, 1], 0), 10) + ([0.5000441648], [0.0]) + >>> nprint(fourier(abs, [-1, 0, 1], 0), 10) + ([0.5], [0.0]) + + """ + interval = ctx._as_points(interval) + a = interval[0] + b = interval[-1] + L = b-a + cos_series = [] + sin_series = [] + cutoff = ctx.eps*10 + for n in range(N+1): + m = 2*n*ctx.pi/L + an = 2*ctx.quadgl(lambda t: f(t)*ctx.cos(m*t), interval)/L + bn = 2*ctx.quadgl(lambda t: f(t)*ctx.sin(m*t), interval)/L + if n == 0: + an /= 2 + if abs(an) < cutoff: an = ctx.zero + if abs(bn) < cutoff: bn = ctx.zero + cos_series.append(an) + sin_series.append(bn) + return cos_series, sin_series + +@defun +def fourierval(ctx, series, interval, x): + """ + Evaluates a Fourier series (in the format computed by + by :func:`~mpmath.fourier` for the given interval) at the point `x`. + + The series should be a pair `(c, s)` where `c` is the + cosine series and `s` is the sine series. The two lists + need not have the same length. + """ + cs, ss = series + ab = ctx._as_points(interval) + a = interval[0] + b = interval[-1] + m = 2*ctx.pi/(ab[-1]-ab[0]) + s = ctx.zero + s += ctx.fsum(cs[n]*ctx.cos(m*n*x) for n in range(len(cs)) if cs[n]) + s += ctx.fsum(ss[n]*ctx.sin(m*n*x) for n in range(len(ss)) if ss[n]) + return s diff --git a/mpmath/calculus/calculus.py b/mpmath/calculus/calculus.py new file mode 100644 index 0000000..9d1b2df --- /dev/null +++ b/mpmath/calculus/calculus.py @@ -0,0 +1,6 @@ +class CalculusMethods: + pass + +def defun(f): + setattr(CalculusMethods, f.__name__, f) + return f diff --git a/mpmath/calculus/differentiation.py b/mpmath/calculus/differentiation.py new file mode 100644 index 0000000..ff3ad2f --- /dev/null +++ b/mpmath/calculus/differentiation.py @@ -0,0 +1,656 @@ +from .calculus import defun + + +try: + iteritems = dict.iteritems +except AttributeError: + iteritems = dict.items + +#----------------------------------------------------------------------------# +# Differentiation # +#----------------------------------------------------------------------------# + +@defun +def difference(ctx, s, n): + r""" + Given a sequence `(s_k)` containing at least `n+1` items, returns the + `n`-th forward difference, + + .. math :: + + \Delta^n = \sum_{k=0}^{\infty} (-1)^{k+n} {n \choose k} s_k. + """ + n = int(n) + d = ctx.zero + b = (-1) ** (n & 1) + for k in range(n+1): + d += b * s[k] + b = (b * (k-n)) // (k+1) + return d + +def hsteps(ctx, f, x, n, prec, **options): + singular = options.get('singular') + addprec = options.get('addprec', 10) + direction = options.get('direction', 0) + workprec = (prec+2*addprec) * (n+1) + orig = ctx.prec + try: + ctx.prec = workprec + h = options.get('h') + if h is None: + if options.get('relative'): + hextramag = int(ctx.mag(x)) + else: + hextramag = 0 + h = ctx.ldexp(1, -prec-addprec-hextramag) + else: + h = ctx.convert(h) + # Directed: steps x, x+h, ... x+n*h + direction = options.get('direction', 0) + if direction: + h *= ctx.sign(direction) + steps = range(n+1) + norm = h + # Central: steps x-n*h, x-(n-2)*h ..., x, ..., x+(n-2)*h, x+n*h + else: + steps = range(-n, n+1, 2) + norm = (2*h) + # Perturb + if singular: + x += 0.5*h + values = [f(x+k*h) for k in steps] + return values, norm, workprec + finally: + ctx.prec = orig + + +@defun +def diff(ctx, f, x, n=1, **options): + r""" + Numerically computes the derivative of `f`, `f'(x)`, or generally for + an integer `n \ge 0`, the `n`-th derivative `f^{(n)}(x)`. + A few basic examples are:: + + >>> from mpmath import mp, diff, nprint, sqrt, cos, exp, j, chop + >>> mp.pretty = True + >>> diff(lambda x: x**2 + x, 1.0) + 3.0 + >>> diff(lambda x: x**2 + x, 1.0, 2) + 2.0 + >>> diff(lambda x: x**2 + x, 1.0, 3) + 0.0 + >>> nprint([diff(exp, 3, n) for n in range(5)]) # exp'(x) = exp(x) + [20.0855, 20.0855, 20.0855, 20.0855, 20.0855] + + Even more generally, given a tuple of arguments `(x_1, \ldots, x_k)` + and order `(n_1, \ldots, n_k)`, the partial derivative + `f^{(n_1,\ldots,n_k)}(x_1,\ldots,x_k)` is evaluated. For example:: + + >>> diff(lambda x,y: 3*x*y + 2*y - x, (0.25, 0.5), (0,1)) + 2.75 + >>> diff(lambda x,y: 3*x*y + 2*y - x, (0.25, 0.5), (1,1)) + 3.0 + + **Options** + + The following optional keyword arguments are recognized: + + ``method`` + Supported methods are ``'step'`` or ``'quad'``: derivatives may be + computed using either a finite difference with a small step + size `h` (default), or numerical quadrature. + ``direction`` + Direction of finite difference: can be -1 for a left + difference, 0 for a central difference (default), or +1 + for a right difference; more generally can be any complex number. + ``addprec`` + Extra precision for `h` used to account for the function's + sensitivity to perturbations (default = 10). + ``relative`` + Choose `h` relative to the magnitude of `x`, rather than an + absolute value; useful for large or tiny `x` (default = False). + ``h`` + As an alternative to ``addprec`` and ``relative``, manually + select the step size `h`. + ``singular`` + If True, evaluation exactly at the point `x` is avoided; this is + useful for differentiating functions with removable singularities. + Default = False. + ``radius`` + Radius of integration contour (with ``method = 'quad'``). + Default = 0.25. A larger radius typically is faster and more + accurate, but it must be chosen so that `f` has no + singularities within the radius from the evaluation point. + + A finite difference requires `n+1` function evaluations and must be + performed at `(n+1)` times the target precision. Accordingly, `f` must + support fast evaluation at high precision. + + With integration, a larger number of function evaluations is + required, but not much extra precision is required. For high order + derivatives, this method may thus be faster if f is very expensive to + evaluate at high precision. + + **Further examples** + + The direction option is useful for computing left- or right-sided + derivatives of nonsmooth functions:: + + >>> diff(abs, 0, direction=0) + 0.0 + >>> diff(abs, 0, direction=1) + 1.0 + >>> diff(abs, 0, direction=-1) + -1.0 + + More generally, if the direction is nonzero, a right difference + is computed where the step size is multiplied by sign(direction). + For example, with direction=+j, the derivative from the positive + imaginary direction will be computed:: + + >>> diff(abs, 0, direction=j) + (0.0 - 1.0j) + + With integration, the result may have a small imaginary part + even even if the result is purely real:: + + >>> diff(sqrt, 1, method='quad') + (0.5 - 4.59...e-26j) + >>> chop(_) + 0.5 + + Adding precision to obtain an accurate value:: + + >>> diff(cos, 1e-30) + 0.0 + >>> diff(cos, 1e-30, h=0.0001) + -9.99999998328279e-31 + >>> diff(cos, 1e-30, addprec=100) + -1.0e-30 + + """ + partial = False + try: + orders = list(n) + x = list(x) + partial = True + except TypeError: + pass + if partial: + x = [ctx.convert(_) for _ in x] + return _partial_diff(ctx, f, x, orders, options) + method = options.get('method', 'step') + if n == 0 and method != 'quad' and not options.get('singular'): + return f(ctx.convert(x)) + prec = ctx.prec + try: + if method == 'step': + values, norm, workprec = hsteps(ctx, f, x, n, prec, **options) + ctx.prec = workprec + v = ctx.difference(values, n) / norm**n + elif method == 'quad': + ctx.prec += 10 + radius = ctx.convert(options.get('radius', 0.25)) + def g(t): + rei = radius*ctx.expj(t) + z = x + rei + return f(z) / rei**n + d = ctx.quadts(g, [0, 2*ctx.pi]) + v = d * ctx.factorial(n) / (2*ctx.pi) + else: + raise ValueError("unknown method: %r" % method) + finally: + ctx.prec = prec + return +v + +def _partial_diff(ctx, f, xs, orders, options): + if not orders: + return f() + if not sum(orders): + return f(*xs) + i = 0 + for i in range(len(orders)): + if orders[i]: + break + order = orders[i] + def fdiff_inner(*f_args): + def inner(t): + return f(*(f_args[:i] + (t,) + f_args[i+1:])) + return ctx.diff(inner, f_args[i], order, **options) + orders[i] = 0 + return _partial_diff(ctx, fdiff_inner, xs, orders, options) + +@defun +def diffs(ctx, f, x, n=None, **options): + r""" + Returns a generator that yields the sequence of derivatives + + .. math :: + + f(x), f'(x), f''(x), \ldots, f^{(k)}(x), \ldots + + With ``method='step'``, :func:`~mpmath.diffs` uses only `O(k)` + function evaluations to generate the first `k` derivatives, + rather than the roughly `O(k^2)` evaluations + required if one calls :func:`~mpmath.diff` `k` separate times. + + With `n < \infty`, the generator stops as soon as the + `n`-th derivative has been generated. If the exact number of + needed derivatives is known in advance, this is further + slightly more efficient. + + Options are the same as for :func:`~mpmath.diff`. + + **Examples** + + >>> from mpmath import nprint, diffs, cos + >>> nprint(list(diffs(cos, 1, 5))) + [0.540302, -0.841471, -0.540302, 0.841471, 0.540302, -0.841471] + >>> for i, d in zip(range(6), diffs(cos, 1)): + ... print("%s %s" % (i, d)) + ... + 0 0.54030230586814 + 1 -0.841470984807897 + 2 -0.54030230586814 + 3 0.841470984807897 + 4 0.54030230586814 + 5 -0.841470984807897 + + """ + if n is None: + n = ctx.inf + else: + n = int(n) + if options.get('method', 'step') != 'step': + k = 0 + while k < n + 1: + yield ctx.diff(f, x, k, **options) + k += 1 + return + singular = options.get('singular') + if singular: + yield ctx.diff(f, x, 0, singular=True) + else: + yield f(ctx.convert(x)) + if n < 1: + return + if n == ctx.inf: + A, B = 1, 2 + else: + A, B = 1, n+1 + while 1: + callprec = ctx.prec + y, norm, workprec = hsteps(ctx, f, x, B, callprec, **options) + for k in range(A, B): + try: + ctx.prec = workprec + d = ctx.difference(y, k) / norm**k + finally: + ctx.prec = callprec + yield +d + if k >= n: + return + A, B = B, int(A*1.4+1) + B = min(B, n) + +def iterable_to_function(gen): + gen = iter(gen) + data = [] + def f(k): + for i in range(len(data), k+1): + data.append(next(gen)) + return data[k] + return f + +@defun +def diffs_prod(ctx, factors): + r""" + Given a list of `N` iterables or generators yielding + `f_k(x), f'_k(x), f''_k(x), \ldots` for `k = 1, \ldots, N`, + generate `g(x), g'(x), g''(x), \ldots` where + `g(x) = f_1(x) f_2(x) \cdots f_N(x)`. + + At high precision and for large orders, this is typically more efficient + than numerical differentiation if the derivatives of each `f_k(x)` + admit direct computation. + + Note: This function does not increase the working precision internally, + so guard digits may have to be added externally for full accuracy. + + **Examples** + + >>> from mpmath import mp, exp, sin, cos, diffs + >>> mp.pretty = True + >>> f = lambda x: exp(x)*cos(x)*sin(x) + >>> u = diffs(f, 1) + >>> v = mp.diffs_prod([diffs(exp,1), diffs(cos,1), diffs(sin,1)]) + >>> next(u) + 1.23586333600241 + >>> next(v) + 1.23586333600241 + >>> next(u) + 0.104658952245596 + >>> next(v) + 0.104658952245596 + >>> next(u) + -5.96999877552086 + >>> next(v) + -5.96999877552086 + >>> next(u) + -12.4632923122697 + >>> next(v) + -12.4632923122697 + + """ + N = len(factors) + if N == 1: + for c in factors[0]: + yield c + else: + u = iterable_to_function(ctx.diffs_prod(factors[:N//2])) + v = iterable_to_function(ctx.diffs_prod(factors[N//2:])) + n = 0 + while 1: + #yield sum(binomial(n,k)*u(n-k)*v(k) for k in range(n+1)) + s = u(n) * v(0) + a = 1 + for k in range(1,n+1): + a = a * (n-k+1) // k + s += a * u(n-k) * v(k) + yield s + n += 1 + +def dpoly(n, _cache={}): + """ + nth differentiation polynomial for exp (Faa di Bruno's formula). + + TODO: most exponents are zero, so maybe a sparse representation + would be better. + """ + if n in _cache: + return _cache[n] + if not _cache: + _cache[0] = {(0,):1} + R = dpoly(n-1) + R = dict((c+(0,),v) for (c,v) in iteritems(R)) + Ra = {} + for powers, count in iteritems(R): + powers1 = (powers[0]+1,) + powers[1:] + if powers1 in Ra: + Ra[powers1] += count + else: + Ra[powers1] = count + for powers, count in iteritems(R): + if not sum(powers): + continue + for k,p in enumerate(powers): + if p: + powers2 = powers[:k] + (p-1,powers[k+1]+1) + powers[k+2:] + if powers2 in Ra: + Ra[powers2] += p*count + else: + Ra[powers2] = p*count + _cache[n] = Ra + return _cache[n] + +@defun +def diffs_exp(ctx, fdiffs): + r""" + Given an iterable or generator yielding `f(x), f'(x), f''(x), \ldots` + generate `g(x), g'(x), g''(x), \ldots` where `g(x) = \exp(f(x))`. + + At high precision and for large orders, this is typically more efficient + than numerical differentiation if the derivatives of `f(x)` + admit direct computation. + + Note: This function does not increase the working precision internally, + so guard digits may have to be added externally for full accuracy. + + **Examples** + + The derivatives of the gamma function can be computed using + logarithmic differentiation:: + + >>> from mpmath import mp, loggamma, diffs_exp, diffs, gamma, psi + >>> mp.pretty = True + >>> + >>> def diffs_loggamma(x): + ... yield loggamma(x) + ... i = 0 + ... while 1: + ... yield psi(i,x) + ... i += 1 + ... + >>> u = diffs_exp(diffs_loggamma(3)) + >>> v = diffs(gamma, 3) + >>> next(u) + 2.0 + >>> next(v) + 2.0 + >>> next(u) + 1.84556867019693 + >>> next(v) + 1.84556867019693 + >>> next(u) + 2.49292999190269 + >>> next(v) + 2.49292999190269 + >>> next(u) + 3.44996501352367 + >>> next(v) + 3.44996501352367 + + """ + fn = iterable_to_function(fdiffs) + f0 = ctx.exp(fn(0)) + yield f0 + i = 1 + while 1: + s = ctx.mpf(0) + for powers, c in iteritems(dpoly(i)): + s += c*ctx.fprod(fn(k+1)**p for (k,p) in enumerate(powers) if p) + yield s * f0 + i += 1 + +@defun +def differint(ctx, f, x, n=1, x0=0): + r""" + Calculates the Riemann-Liouville differintegral, or fractional + derivative, defined by + + .. math :: + + \,_{x_0}{\mathbb{D}}^n_xf(x) = \frac{1}{\Gamma(m-n)} \frac{d^m}{dx^m} + \int_{x_0}^{x}(x-t)^{m-n-1}f(t)dt + + where `f` is a given (presumably well-behaved) function, + `x` is the evaluation point, `n` is the order, and `x_0` is + the reference point of integration (`m` is an arbitrary + parameter selected automatically). + + With `n = 1`, this is just the standard derivative `f'(x)`; with `n = 2`, + the second derivative `f''(x)`, etc. With `n = -1`, it gives + `\int_{x_0}^x f(t) dt`, with `n = -2` + it gives `\int_{x_0}^x \left( \int_{x_0}^t f(u) du \right) dt`, etc. + + As `n` is permitted to be any number, this operator generalizes + iterated differentiation and iterated integration to a single + operator with a continuous order parameter. + + **Examples** + + There is an exact formula for the fractional derivative of a + monomial `x^p`, which may be used as a reference. For example, + the following gives a half-derivative (order 0.5):: + + >>> from mpmath import (mp, mpf, differint, gamma, inf, exp, pi, + ... j, lower_gamma) + >>> mp.pretty = True + >>> x = mpf(3) + >>> p = 2 + >>> n = 0.5 + >>> differint(lambda t: t**p, x, n) + 7.81764019044672 + >>> gamma(p+1)/gamma(p-n+1) * x**(p-n) + 7.81764019044672 + + Another useful test function is the exponential function, whose + integration / differentiation formula easy generalizes + to arbitrary order. Here we first compute a third derivative, + and then a triply nested integral. (The reference point `x_0` + is set to `-\infty` to avoid nonzero endpoint terms.):: + + >>> differint(lambda x: exp(pi*x), -1.5, 3) + 0.278538406900792 + >>> exp(pi*-1.5) * pi**3 + 0.278538406900792 + >>> differint(lambda x: exp(pi*x), 3.5, -3, -inf) + 1922.50563031149 + >>> exp(pi*3.5) / pi**3 + 1922.50563031149 + + However, for noninteger `n`, the differentiation formula for the + exponential function must be modified to give the same result as the + Riemann-Liouville differintegral:: + + >>> x = mpf(3.5) + >>> c = pi + >>> n = 1+2*j + >>> differint(lambda x: exp(c*x), x, n) + (-123295.005390743 + 140955.117867654j) + >>> x**(-n) * exp(c)**x * (x*c)**n * lower_gamma(-n, x*c) / gamma(-n) + (-123295.005390743 + 140955.117867654j) + + + """ + m = max(int(ctx.ceil(ctx.re(n)))+1, 1) + r = m-n-1 + g = lambda x: ctx.quad(lambda t: (x-t)**r * f(t), [x0, x]) + return ctx.diff(g, x, m) / ctx.gamma(m-n) + +@defun +def diffun(ctx, f, n=1, **options): + r""" + Given a function `f`, returns a function `g(x)` that evaluates the nth + derivative `f^{(n)}(x)`:: + + >>> from mpmath import diffun, sin, cos, mp + >>> mp.pretty = True + >>> cos2 = diffun(sin) + >>> sin2 = diffun(sin, 4) + >>> cos(1.3), cos2(1.3) + (0.267498828624587, 0.267498828624587) + >>> sin(1.3), sin2(1.3) + (0.963558185417193, 0.963558185417193) + + The function `f` must support arbitrary precision evaluation. + See :func:`~mpmath.diff` for additional details and supported + keyword options. + """ + if n == 0: + return f + def g(x): + return ctx.diff(f, x, n, **options) + return g + +@defun +def taylor(ctx, f, x, n, **options): + r""" + Produces a degree-`n` Taylor polynomial around the point `x` of the + given function `f`. The coefficients are returned as a list. + + >>> from mpmath import mp, sin, nprint, chop, exp, polyval, taylor + >>> mp.pretty = True + >>> nprint(chop(taylor(sin, 0, 5))) + [0.0, 1.0, 0.0, -0.166667, 0.0, 0.00833333] + + The coefficients are computed using high-order numerical + differentiation. The function must be possible to evaluate + to arbitrary precision. See :func:`~mpmath.diff` for additional details + and supported keyword options. + + Note that to evaluate the Taylor polynomial as an approximation + of `f`, the point of the Taylor expansion must be subtracted from + the argument: + + >>> p = taylor(exp, 2.0, 10) + >>> polyval(p, 2.5 - 2.0) + 12.1824939606092 + >>> exp(2.5) + 12.1824939607035 + + """ + gen = enumerate(ctx.diffs(f, x, n, **options)) + if options.get("chop", True): + return [ctx.chop(d)/ctx.factorial(i) for i, d in gen] + else: + return [d/ctx.factorial(i) for i, d in gen] + +@defun +def pade(ctx, a, L, M): + r""" + Computes a Pade approximation of degree `(L, M)` to a function. + Given at least `L+M+1` Taylor coefficients `a` approximating + a function `A(x)`, :func:`~mpmath.pade` returns coefficients of + polynomials `P, Q` satisfying + + .. math :: + + P = \sum_{k=0}^L p_k x^k + + Q = \sum_{k=0}^M q_k x^k + + Q_0 = 1 + + A(x) Q(x) = P(x) + O(x^{L+M+1}) + + `P(x)/Q(x)` can provide a good approximation to an analytic function + beyond the radius of convergence of its Taylor series (example + from G.A. Baker 'Essentials of Pade Approximants' Academic Press, + Ch.1A):: + + >>> from mpmath import mp, mpf, sqrt, taylor, pade, polyval + >>> mp.pretty = True + >>> one = mpf(1) + >>> def f(x): + ... return sqrt((one + 2*x)/(one + x)) + ... + >>> a = taylor(f, 0, 6) + >>> p, q = pade(a, 3, 3) + >>> x = 10 + >>> polyval(p, x)/polyval(q, x) + 1.38169105566806 + >>> f(x) + 1.38169855941551 + + """ + # To determine L+1 coefficients of P and M coefficients of Q + # L+M+1 coefficients of A must be provided + if len(a) < L+M+1: + raise ValueError("L+M+1 Coefficients should be provided") + + if M == 0: + if L == 0: + return [ctx.one], [ctx.one] + else: + return a[:L+1], [ctx.one] + + # Solve first + # a[L]*q[1] + ... + a[L-M+1]*q[M] = -a[L+1] + # ... + # a[L+M-1]*q[1] + ... + a[L]*q[M] = -a[L+M] + A = ctx.matrix(M) + for j in range(M): + for i in range(min(M, L+j+1)): + A[j, i] = a[L+j-i] + v = -ctx.matrix(a[(L+1):(L+M+1)]) + x = ctx.lu_solve(A, v) + q = [ctx.one] + list(x) + # compute p + p = [0]*(L+1) + for i in range(L+1): + s = a[i] + for j in range(1, min(M,i) + 1): + s += q[j]*a[i-j] + p[i] = s + return p, q diff --git a/mpmath/calculus/extrapolation.py b/mpmath/calculus/extrapolation.py new file mode 100644 index 0000000..6fb8385 --- /dev/null +++ b/mpmath/calculus/extrapolation.py @@ -0,0 +1,2107 @@ +import itertools + +from .calculus import defun + + +@defun +def richardson(ctx, seq): + r""" + Given a list ``seq`` of the first `N` elements of a slowly convergent + infinite sequence, :func:`~mpmath.richardson` computes the `N`-term + Richardson extrapolate for the limit. + + :func:`~mpmath.richardson` returns `(v, c)` where `v` is the estimated + limit and `c` is the magnitude of the largest weight used during the + computation. The weight provides an estimate of the precision + lost to cancellation. Due to cancellation effects, the sequence must + be typically be computed at a much higher precision than the target + accuracy of the extrapolation. + + **Applicability and issues** + + The `N`-step Richardson extrapolation algorithm used by + :func:`~mpmath.richardson` is described in [1]. + + Richardson extrapolation only works for a specific type of sequence, + namely one converging like partial sums of + `P(1)/Q(1) + P(2)/Q(2) + \ldots` where `P` and `Q` are polynomials. + When the sequence does not convergence at such a rate + :func:`~mpmath.richardson` generally produces garbage. + + Richardson extrapolation has the advantage of being fast: the `N`-term + extrapolate requires only `O(N)` arithmetic operations, and usually + produces an estimate that is accurate to `O(N)` digits. Contrast with + the Shanks transformation (see :func:`~mpmath.shanks`), which requires + `O(N^2)` operations. + + :func:`~mpmath.richardson` is unable to produce an estimate for the + approximation error. One way to estimate the error is to perform + two extrapolations with slightly different `N` and comparing the + results. + + Richardson extrapolation does not work for oscillating sequences. + As a simple workaround, :func:`~mpmath.richardson` detects if the last + three elements do not differ monotonically, and in that case + applies extrapolation only to the even-index elements. + + **Example** + + Applying Richardson extrapolation to the Leibniz series for `\pi`:: + + >>> from mpmath import mp, mpf, richardson, nprint, pi + >>> mp.dps = 30 + >>> mp.pretty = True + >>> S = [4*sum(mpf(-1)**n/(2*n+1) for n in range(m)) + ... for m in range(1,30)] + >>> v, c = richardson(S[:10]) + >>> v + 3.2126984126984126984126984127 + >>> nprint([v-pi, c]) + [0.0711058, 2.0] + + >>> v, c = richardson(S[:30]) + >>> v + 3.14159265468624052829954206226 + >>> nprint([v-pi, c]) + [1.09645e-9, 20833.3] + + **References** + + 1. [BenderOrszag]_ pp. 375-376 + + """ + if len(seq) < 3: + raise ValueError("seq should be of minimum length 3") + if ctx.sign(seq[-1]-seq[-2]) != ctx.sign(seq[-2]-seq[-3]): + seq = seq[::2] + N = len(seq)//2-1 + s = ctx.zero + # The general weight is c[k] = (N+k)**N * (-1)**(k+N) / k! / (N-k)! + # To avoid repeated factorials, we simplify the quotient + # of successive weights to obtain a recurrence relation + c = (-1)**N * N**N / ctx.mpf(ctx._ifac(N)) + maxc = 1 + for k in range(N+1): + s += c * seq[N+k] + maxc = max(abs(c), maxc) + c *= (k-N)*ctx.mpf(k+N+1)**N + c /= ((1+k)*ctx.mpf(k+N)**N) + return s, maxc + +@defun +def shanks(ctx, seq, table=None, randomized=False): + r""" + Given a list ``seq`` of the first `N` elements of a slowly + convergent infinite sequence `(A_k)`, :func:`~mpmath.shanks` computes the iterated + Shanks transformation `S(A), S(S(A)), \ldots, S^{N/2}(A)`. The Shanks + transformation often provides strong convergence acceleration, + especially if the sequence is oscillating. + + The iterated Shanks transformation is computed using the Wynn + epsilon algorithm (see [1]). :func:`~mpmath.shanks` returns the full + epsilon table generated by Wynn's algorithm, which can be read + off as follows: + + * The table is a list of lists forming a lower triangular matrix, + where higher row and column indices correspond to more accurate + values. + * The columns with even index hold dummy entries (required for the + computation) and the columns with odd index hold the actual + extrapolates. + * The last element in the last row is typically the most + accurate estimate of the limit. + * The difference to the third last element in the last row + provides an estimate of the approximation error. + * The magnitude of the second last element provides an estimate + of the numerical accuracy lost to cancellation. + + For convenience, so the extrapolation is stopped at an odd index + so that ``shanks(seq)[-1][-1]`` always gives an estimate of the + limit. + + Optionally, an existing table can be passed to :func:`~mpmath.shanks`. + This can be used to efficiently extend a previous computation after + new elements have been appended to the sequence. The table will + then be updated in-place. + + **The Shanks transformation** + + The Shanks transformation is defined as follows (see [2]): given + the input sequence `(A_0, A_1, \ldots)`, the transformed sequence is + given by + + .. math :: + + S(A_k) = \frac{A_{k+1}A_{k-1}-A_k^2}{A_{k+1}+A_{k-1}-2 A_k} + + The Shanks transformation gives the exact limit `A_{\infty}` in a + single step if `A_k = A + a q^k`. Note in particular that it + extrapolates the exact sum of a geometric series in a single step. + + Applying the Shanks transformation once often improves convergence + substantially for an arbitrary sequence, but the optimal effect is + obtained by applying it iteratively: + `S(S(A_k)), S(S(S(A_k))), \ldots`. + + Wynn's epsilon algorithm provides an efficient way to generate + the table of iterated Shanks transformations. It reduces the + computation of each element to essentially a single division, at + the cost of requiring dummy elements in the table. See [1] for + details. + + **Precision issues** + + Due to cancellation effects, the sequence must be typically be + computed at a much higher precision than the target accuracy + of the extrapolation. + + If the Shanks transformation converges to the exact limit (such + as if the sequence is a geometric series), then a division by + zero occurs. By default, :func:`~mpmath.shanks` handles this case by + terminating the iteration and returning the table it has + generated so far. With *randomized=True*, it will instead + replace the zero by a pseudorandom number close to zero. + (TODO: find a better solution to this problem.) + + **Examples** + + We illustrate by applying Shanks transformation to the Leibniz + series for `\pi`:: + + >>> from mpmath import shanks, mp, nprint, mpf, pi + >>> mp.dps = 50 + >>> S = [4*sum(mpf(-1)**n/(2*n+1) for n in range(m)) + ... for m in range(1,30)] + >>> + >>> T = shanks(S[:7]) + >>> for row in T: + ... nprint(row) + ... + [-0.75] + [1.25, 3.16667] + [-1.75, 3.13333, -28.75] + [2.25, 3.14524, 82.25, 3.14234] + [-2.75, 3.13968, -177.75, 3.14139, -969.937] + [3.25, 3.14271, 327.25, 3.14166, 3515.06, 3.14161] + + The extrapolated accuracy is about 4 digits, and about 4 digits + may have been lost due to cancellation:: + + >>> L = T[-1] + >>> nprint([abs(L[-1] - pi), abs(L[-1] - L[-3]), abs(L[-2])]) + [2.22532e-5, 4.78309e-5, 3515.06] + + Now we extend the computation:: + + >>> T = shanks(S[:25], T) + >>> L = T[-1] + >>> nprint([abs(L[-1] - pi), abs(L[-1] - L[-3]), abs(L[-2])]) + [3.75527e-19, 1.48478e-19, 2.96014e+17] + + The value for pi is now accurate to 18 digits. About 18 digits may + also have been lost to cancellation. + + Here is an example with a geometric series, where the convergence + is immediate (the sum is exactly 1):: + + >>> mp.dps = 15 + >>> for row in shanks([0.5, 0.75, 0.875, 0.9375, 0.96875]): + ... nprint(row) + [4.0] + [8.0, 1.0] + + **References** + + 1. [GravesMorris]_ + 2. [BenderOrszag]_ pp. 368-375 + + """ + if len(seq) < 2: + raise ValueError("seq should be of minimum length 2") + if table: + START = len(table) + else: + START = 0 + table = [] + STOP = len(seq) - 1 + if STOP & 1: + STOP -= 1 + one = ctx.one + eps = +ctx.eps + if randomized: + from random import Random + rnd = Random() + rnd.seed(START) + for i in range(START, STOP): + row = [] + for j in range(i+1): + if j == 0: + a, b = 0, seq[i+1]-seq[i] + else: + if j == 1: + a = seq[i] + else: + a = table[i-1][j-2] + b = row[j-1] - table[i-1][j-1] + if not b: + if randomized: + b = (1 + rnd.getrandbits(10))*eps + elif i & 1: + return table[:-1] + else: + return table + row.append(a + one/b) + table.append(row) + return table + + +class levin_class: + # levin: Copyright 2013 Timo Hartmann (thartmann15 at gmail.com) + r""" + This interface implements Levin's (nonlinear) sequence transformation for + convergence acceleration and summation of divergent series. It performs + better than the Shanks/Wynn-epsilon algorithm for logarithmic convergent + or alternating divergent series. + + Let *A* be the series we want to sum: + + .. math :: + + A = \sum_{k=0}^{\infty} a_k + + Attention: all `a_k` must be non-zero! + + Let `s_n` be the partial sums of this series: + + .. math :: + + s_n = \sum_{k=0}^n a_k. + + **Methods** + + Calling ``levin`` returns an object with the following methods. + + ``update(...)`` works with the list of individual terms `a_k` of *A*, and + ``update_step(...)`` works with the list of partial sums `s_k` of *A*: + + .. code :: + + v, e = ...update([a_0, a_1,..., a_k]) + v, e = ...update_psum([s_0, s_1,..., s_k]) + + ``step(...)`` works with the individual terms `a_k` and ``step_psum(...)`` + works with the partial sums `s_k`: + + .. code :: + + v, e = ...step(a_k) + v, e = ...step_psum(s_k) + + *v* is the current estimate for *A*, and *e* is an error estimate which is + simply the difference between the current estimate and the last estimate. + One should not mix ``update``, ``update_psum``, ``step`` and ``step_psum``. + + **A word of caution** + + One can only hope for good results (i.e. convergence acceleration or + resummation) if the `s_n` have some well defind asymptotic behavior for + large `n` and are not erratic or random. Furthermore one usually needs very + high working precision because of the numerical cancellation. If the working + precision is insufficient, levin may produce silently numerical garbage. + Furthermore even if the Levin-transformation converges, in the general case + there is no proof that the result is mathematically sound. Only for very + special classes of problems one can prove that the Levin-transformation + converges to the expected result (for example Stieltjes-type integrals). + Furthermore the Levin-transform is quite expensive (i.e. slow) in comparison + to Shanks/Wynn-epsilon, Richardson & co. + In summary one can say that the Levin-transformation is powerful but + unreliable and that it may need a copious amount of working precision. + + The Levin transform has several variants differing in the choice of weights. + Some variants are better suited for the possible flavours of convergence + behaviour of *A* than other variants: + + .. code :: + + convergence behaviour levin-u levin-t levin-v shanks/wynn-epsilon + + logarithmic + - + - + linear + + + + + alternating divergent + + + + + + "+" means the variant is suitable,"-" means the variant is not suitable; + for comparison the Shanks/Wynn-epsilon transform is listed, too. + + The variant is controlled though the variant keyword (i.e. ``variant="u"``, + ``variant="t"`` or ``variant="v"``). Overall "u" is probably the best choice. + + Finally it is possible to use the Sidi-S transform instead of the Levin transform + by using the keyword ``method='sidi'``. The Sidi-S transform works better than the + Levin transformation for some divergent series (see the examples). + + Parameters: + + .. code :: + + method "levin" or "sidi" chooses either the Levin or the Sidi-S transformation + variant "u","t" or "v" chooses the weight variant. + + The Levin transform is also accessible through the nsum interface. + ``method="l"`` or ``method="levin"`` select the normal Levin transform while + ``method="sidi"`` + selects the Sidi-S transform. The variant is in both cases selected through the + levin_variant keyword. The stepsize in :func:`~mpmath.nsum` must not be chosen too large, otherwise + it will miss the point where the Levin transform converges resulting in numerical + overflow/garbage. For highly divergent series a copious amount of working precision + must be chosen. + + **Examples** + + First we sum the zeta function:: + + >>> from mpmath import mp + >>> eps = mp.mpf(mp.eps) + >>> with mp.extraprec(2 * mp.prec): # levin needs a high working precision + ... L = mp.levin(method = "levin", variant = "u") + ... S, s, n = [], 0, 1 + ... while 1: + ... s += mp.one / (n * n) + ... n += 1 + ... S.append(s) + ... v, e = L.update_psum(S) + ... if e < eps: + ... break + ... if n > 1000: raise RuntimeError("iteration limit exceeded") + >>> print(mp.chop(v - mp.pi ** 2 / 6)) + 0.0 + >>> w = mp.nsum(lambda n: 1 / (n*n), [1, mp.inf], method = "levin", levin_variant = "u") + >>> print(mp.chop(v - w)) + 0.0 + + Now we sum the zeta function outside its range of convergence + (attention: This does not work at the negative integers!):: + + >>> eps = mp.mpf(mp.eps) + >>> with mp.extraprec(2 * mp.prec): # levin needs a high working precision + ... L = mp.levin(method = "levin", variant = "v") + ... A, n = [], 1 + ... while 1: + ... s = mp.mpf(n) ** (2 + 3j) + ... n += 1 + ... A.append(s) + ... v, e = L.update(A) + ... if e < eps: + ... break + ... if n > 1000: raise RuntimeError("iteration limit exceeded") + >>> print(mp.chop(v - mp.zeta(-2-3j))) + 0.0 + >>> w = mp.nsum(lambda n: n ** (2 + 3j), [1, mp.inf], method = "levin", levin_variant = "v") + >>> print(mp.chop(v - w)) + 0.0 + + Now we sum the divergent asymptotic expansion of an integral related to the + exponential integral (see also [2] p.373). The Sidi-S transform works best here:: + + >>> z = mp.mpf(10) + >>> exact = mp.quad(lambda x: mp.exp(-x)/(1+x/z),[0,mp.inf]) + >>> # exact = z * mp.exp(z) * mp.expint(1,z) # this is the symbolic expression for the integral + >>> eps = mp.mpf(mp.eps) + >>> with mp.extraprec(2 * mp.prec): # high working precisions are mandatory for divergent resummation + ... L = mp.levin(method = "sidi", variant = "t") + ... n = 0 + ... while 1: + ... s = (-1)**n * mp.fac(n) * z ** (-n) + ... v, e = L.step(s) + ... n += 1 + ... if e < eps: + ... break + ... if n > 1000: raise RuntimeError("iteration limit exceeded") + >>> print(mp.chop(v - exact)) + 0.0 + >>> w = mp.nsum(lambda n: (-1) ** n * mp.fac(n) * z ** (-n), [0, mp.inf], method = "sidi", levin_variant = "t") + >>> print(mp.chop(v - w)) + 0.0 + + Another highly divergent integral is also summable:: + + >>> z = mp.mpf(2) + >>> eps = mp.mpf(mp.eps) + >>> exact = mp.quad(lambda x: mp.exp( -x * x / 2 - z * x ** 4), [0,mp.inf]) * 2 / mp.sqrt(2 * mp.pi) + >>> # exact = mp.exp(mp.one / (32 * z)) * mp.besselk(mp.one / 4, mp.one / (32 * z)) / (4 * mp.sqrt(z * mp.pi)) # this is the symbolic expression for the integral + >>> with mp.extraprec(7 * mp.prec): # we need copious amount of precision to sum this highly divergent series + ... L = mp.levin(method = "levin", variant = "t") + ... n, s = 0, 0 + ... while 1: + ... s += (-z)**n * mp.fac(4 * n) / (mp.fac(n) * mp.fac(2 * n) * (4 ** n)) + ... n += 1 + ... v, e = L.step_psum(s) + ... if e < eps: + ... break + ... if n > 1000: raise RuntimeError("iteration limit exceeded") + >>> print(mp.chop(v - exact)) + 0.0 + >>> w = mp.nsum(lambda n: (-z)**n * mp.fac(4 * n) / (mp.fac(n) * mp.fac(2 * n) * (4 ** n)), + ... [0, mp.inf], method = "levin", levin_variant = "t", workprec = 8*mp.prec, steps = [2] + [1 for x in range(1000)]) + >>> print(mp.chop(v - w)) + 0.0 + + These examples run with 15-20 decimal digits precision. For higher precision the + working precision must be raised. + + **Examples for nsum** + + Here we calculate Euler's constant as the constant term in the Laurent + expansion of `\zeta(s)` at `s=1`. This sum converges extremly slowly because of + the logarithmic convergence behaviour of the Dirichlet series for zeta:: + + >>> mp.dps = 30 + >>> z = mp.mpf(10) ** (-10) + >>> a = mp.nsum(lambda n: n**(-(1+z)), [1, mp.inf], method = "l") - 1 / z + >>> print(mp.chop(a - mp.euler, tol = 1e-10)) + 0.0 + + The Sidi-S transform performs excellently for the alternating series of `\log(2)`:: + + >>> a = mp.nsum(lambda n: (-1)**(n-1) / n, [1, mp.inf], method = "sidi") + >>> print(mp.chop(a - mp.log(2))) + 0.0 + + Hypergeometric series can also be summed outside their range of convergence. + The stepsize in :func:`~mpmath.nsum` must not be chosen too large, otherwise it will miss the + point where the Levin transform converges resulting in numerical overflow/garbage:: + + >>> z = 2 + 1j + >>> exact = mp.hyp2f1(2 / mp.mpf(3), 4 / mp.mpf(3), 1 / mp.mpf(3), z) + >>> f = lambda n: mp.rf(2 / mp.mpf(3), n) * mp.rf(4 / mp.mpf(3), n) * z**n / (mp.rf(1 / mp.mpf(3), n) * mp.fac(n)) + >>> v = mp.nsum(f, [0, mp.inf], method = "levin", steps = [10 for x in range(1000)]) + >>> print(mp.chop(exact-v)) + 0.0 + + References: + + 1. [Weniger]_ + 2. [Sidi]_ + 3. [Homeier]_ + + """ + + def __init__(self, method = "levin", variant = "u"): + self.variant = variant + self.n = 0 + self.a0 = 0 + self.theta = 1 + self.A = [] + self.B = [] + self.last = 0 + self.last_s = False + + if method == "levin": + self.factor = self.factor_levin + elif method == "sidi": + self.factor = self.factor_sidi + else: + raise ValueError("levin: unknown method \"%s\"" % method) + + def factor_levin(self, i): + # original levin + # [1] p.50,e.7.5-7 (with n-j replaced by i) + return (self.theta + i) * (self.theta + self.n - 1) ** (self.n - i - 2) / self.ctx.mpf(self.theta + self.n) ** (self.n - i - 1) + + def factor_sidi(self, i): + # sidi analogon to levin (factorial series) + # [1] p.59,e.8.3-16 (with n-j replaced by i) + return (self.theta + self.n - 1) * (self.theta + self.n - 2) / self.ctx.mpf((self.theta + 2 * self.n - i - 2) * (self.theta + 2 * self.n - i - 3)) + + def run(self, s, a0, a1 = 0): + if self.variant=="t": + # levin t + w=a0 + elif self.variant=="u": + # levin u + w=a0*(self.theta+self.n) + elif self.variant=="v": + # levin v + w=a0*a1/(a0-a1) + else: + assert False, "unknown variant" + + if w==0: + raise ValueError("levin: zero weight") + + self.A.append(s/w) + self.B.append(1/w) + + for i in range(self.n-1,-1,-1): + if i==self.n-1: + f=1 + else: + f=self.factor(i) + + self.A[i]=self.A[i+1]-f*self.A[i] + self.B[i]=self.B[i+1]-f*self.B[i] + + self.n+=1 + + ########################################################################### + + def update_psum(self,S): + """ + This routine applies the convergence acceleration to the list of partial sums. + + A = sum(a_k, k = 0..infinity) + s_n = sum(a_k, k = 0..n) + + v, e = ...update_psum([s_0, s_1,..., s_k]) + + output: + v current estimate of the series A + e an error estimate which is simply the difference between the current + estimate and the last estimate. + """ + + if self.variant!="v": + if self.n==0: + self.run(S[0],S[0]) + while self.n>> from mpmath import mp + >>> AC = mp.cohen_alt() + >>> S, s, n = [], 0, 1 + >>> while 1: + ... s += -((-1) ** n) * mp.one / (n * n) + ... n += 1 + ... S.append(s) + ... v, e = AC.update_psum(S) + ... if e < mp.eps: + ... break + ... if n > 1000: raise RuntimeError("iteration limit exceeded") + >>> print(mp.chop(v - mp.pi ** 2 / 12)) + 0.0 + + Here we compute the product `\prod_{n=1}^{\infty} \Gamma(1+1/(2n-1)) / \Gamma(1+1/(2n))`:: + + >>> A = [] + >>> AC = mp.cohen_alt() + >>> n = 1 + >>> while 1: + ... A.append( mp.loggamma(1 + mp.one / (2 * n - 1))) + ... A.append(-mp.loggamma(1 + mp.one / (2 * n))) + ... n += 1 + ... v, e = AC.update(A) + ... if e < mp.eps: + ... break + ... if n > 1000: raise RuntimeError("iteration limit exceeded") + >>> v = mp.exp(v) + >>> print(mp.chop(v - 1.06215090557106, tol = 1e-12)) + 0.0 + + ``cohen_alt`` is also accessible through the :func:`~mpmath.nsum` interface:: + + >>> v = mp.nsum(lambda n: (-1)**(n-1) / n, [1, mp.inf], method = "a") + >>> print(mp.chop(v - mp.log(2))) + 0.0 + >>> v = mp.nsum(lambda n: (-1)**n / (2 * n + 1), [0, mp.inf], method = "a") + >>> print(mp.chop(v - mp.pi / 4)) + 0.0 + >>> v = mp.nsum(lambda n: (-1)**n * mp.log(n) * n, [1, mp.inf], method = "a") + >>> print(mp.chop(v - mp.diff(lambda s: mp.altzeta(s), -1))) + 0.0 + + """ + + def __init__(self): + self.last=0 + + def update(self, A): + """ + This routine applies the convergence acceleration to the list of individual terms. + + A = sum(a_k, k = 0..infinity) + + v, e = ...update([a_0, a_1,..., a_k]) + + output: + v current estimate of the series A + e an error estimate which is simply the difference between the current + estimate and the last estimate. + """ + + n = len(A) + d = (3 + self.ctx.sqrt(8)) ** n + d = (d + 1 / d) / 2 + b = -self.ctx.one + c = -d + s = 0 + + for k in range(n): + c = b - c + if k % 2 == 0: + s = s + c * A[k] + else: + s = s - c * A[k] + b = 2 * (k + n) * (k - n) * b / ((2 * k + 1) * (k + self.ctx.one)) + + value = s / d + + err = abs(value - self.last) + self.last = value + + return value, err + + def update_psum(self, S): + """ + This routine applies the convergence acceleration to the list of partial sums. + + A = sum(a_k, k = 0..infinity) + s_n = sum(a_k ,k = 0..n) + + v, e = ...update_psum([s_0, s_1,..., s_k]) + + output: + v current estimate of the series A + e an error estimate which is simply the difference between the current + estimate and the last estimate. + """ + + n = len(S) + d = (3 + self.ctx.sqrt(8)) ** n + d = (d + 1 / d) / 2 + b = self.ctx.one + s = 0 + + for k in range(n): + b = 2 * (n + k) * (n - k) * b / ((2 * k + 1) * (k + self.ctx.one)) + s += b * S[k] + + value = s / d + + err = abs(value - self.last) + self.last = value + + return value, err + +def cohen_alt(ctx): + L = cohen_alt_class() + L.ctx = ctx + return L + +cohen_alt.__doc__ = cohen_alt_class.__doc__ +defun(cohen_alt) + + +@defun +def sumap(ctx, f, interval, integral=None, error=False): + r""" + Evaluates an infinite series of an analytic summand *f* using the + Abel-Plana formula + + .. math :: + + \sum_{k=0}^{\infty} f(k) = \int_0^{\infty} f(t) dt + \frac{1}{2} f(0) + + i \int_0^{\infty} \frac{f(it)-f(-it)}{e^{2\pi t}-1} dt. + + Unlike the Euler-Maclaurin formula (see :func:`~mpmath.sumem`), + the Abel-Plana formula does not require derivatives. However, + it only works when `|f(it)-f(-it)|` does not + increase too rapidly with `t`. + + **Examples** + + The Abel-Plana formula is particularly useful when the summand + decreases like a power of `k`; for example when the sum is a pure + zeta function:: + + >>> from mpmath import (mp, sumap, zeta, inf, chop, expint, log, + ... polylog) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> sumap(lambda k: 1/k**2.5, [1,inf]) + 1.34148725725091717975677 + >>> zeta(2.5) + 1.34148725725091717975677 + >>> sumap(lambda k: 1/(k+1j)**(2.5+2.5j), [1,inf]) + (-3.385361068546473342286084 - 0.7432082105196321803869551j) + >>> zeta(2.5+2.5j, 1+1j) + (-3.385361068546473342286084 - 0.7432082105196321803869551j) + + If the series is alternating, numerical quadrature along the real + line is likely to give poor results, so it is better to evaluate + the first term symbolically whenever possible: + + >>> n=3 + >>> z=-0.75 + >>> I = expint(n,-log(z)) + >>> chop(sumap(lambda k: z**k / k**n, [1,inf], integral=I)) + -0.6917036036904594510141448 + >>> polylog(n,z) + -0.6917036036904594510141448 + + """ + prec = ctx.prec + try: + ctx.prec += 10 + a, b = interval + if b != ctx.inf: + raise ValueError("b should be equal to ctx.inf") + g = lambda x: f(x+a) + if integral is None: + i1, err1 = ctx.quad(g, [0,ctx.inf], error=True) + else: + i1, err1 = integral, 0 + j = ctx.j + p = ctx.pi * 2 + if ctx._is_real_type(i1): + h = lambda t: -2 * ctx.im(g(j*t)) / ctx.expm1(p*t) + else: + h = lambda t: j*(g(j*t)-g(-j*t)) / ctx.expm1(p*t) + i2, err2 = ctx.quad(h, [0,ctx.inf], error=True) + err = err1+err2 + v = i1+i2+0.5*g(ctx.mpf(0)) + finally: + ctx.prec = prec + if error: + return +v, err + return +v + + +@defun +def sumem(ctx, f, interval, tol=None, reject=10, integral=None, + adiffs=None, bdiffs=None, verbose=False, error=False, + _fast_abort=False): + r""" + Uses the Euler-Maclaurin formula to compute an approximation accurate + to within ``tol`` (which defaults to the present epsilon) of the sum + + .. math :: + + S = \sum_{k=a}^b f(k) + + where `(a,b)` are given by ``interval`` and `a` or `b` may be + infinite. The approximation is + + .. math :: + + S \sim \int_a^b f(x) \,dx + \frac{f(a)+f(b)}{2} + + \sum_{k=1}^{\infty} \frac{B_{2k}}{(2k)!} + \left(f^{(2k-1)}(b)-f^{(2k-1)}(a)\right). + + The last sum in the Euler-Maclaurin formula is not generally + convergent (a notable exception is if `f` is a polynomial, in + which case Euler-Maclaurin actually gives an exact result). + + The summation is stopped as soon as the quotient between two + consecutive terms falls below *reject*. That is, by default + (*reject* = 10), the summation is continued as long as each + term adds at least one decimal. + + Although not convergent, convergence to a given tolerance can + often be "forced" if `b = \infty` by summing up to `a+N` and then + applying the Euler-Maclaurin formula to the sum over the range + `(a+N+1, \ldots, \infty)`. This procedure is implemented by + :func:`~mpmath.nsum`. + + By default numerical quadrature and differentiation is used. + If the symbolic values of the integral and endpoint derivatives + are known, it is more efficient to pass the value of the + integral explicitly as ``integral`` and the derivatives + explicitly as ``adiffs`` and ``bdiffs``. The derivatives + should be given as iterables that yield + `f(a), f'(a), f''(a), \ldots` (and the equivalent for `b`). + + **Examples** + + Summation of an infinite series, with automatic and symbolic + integral and derivative values (the second should be much faster):: + + >>> from mpmath import mp, sumem, inf, fac, mpf + >>> mp.dps = 50 + >>> mp.pretty = True + >>> sumem(lambda n: 1/n**2, [32, inf]) + 0.03174336652030209012658168043874142714132886413417 + >>> I = mpf(1)/32 + >>> D = adiffs=((-1)**n*fac(n+1)*32**(-2-n) for n in range(999)) + >>> sumem(lambda n: 1/n**2, [32, inf], integral=I, adiffs=D) + 0.03174336652030209012658168043874142714132886413417 + + An exact evaluation of a finite polynomial sum:: + + >>> sumem(lambda n: n**5-12*n**2+3*n, [-100000, 200000]) + 10500155000624963999742499550000.0 + >>> print(sum(n**5-12*n**2+3*n for n in range(-100000, 200001))) + 10500155000624963999742499550000 + + """ + tol = tol or +ctx.eps + interval = ctx._as_points(interval) + a = ctx.convert(interval[0]) + b = ctx.convert(interval[-1]) + err = ctx.zero + prev = 0 + M = 10000 + if a == ctx.ninf: adiffs = (0 for n in range(M)) + else: adiffs = adiffs or ctx.diffs(f, a) + if b == ctx.inf: bdiffs = (0 for n in range(M)) + else: bdiffs = bdiffs or ctx.diffs(f, b) + orig = ctx.prec + #verbose = 1 + try: + ctx.prec += 10 + s = ctx.zero + for k, (da, db) in enumerate(zip(adiffs, bdiffs)): + if k & 1: + term = (db-da) * ctx.bernoulli(k+1) / ctx.factorial(k+1) + mag = abs(term) + if verbose: + print("term", k, "magnitude =", ctx.nstr(mag)) + if k > 4 and mag < tol: + s += term + break + elif k > 4 and abs(prev) / mag < reject: + err += mag + if _fast_abort: + return [s, (s, err)][error] + if verbose: + print("Failed to converge") + break + else: + s += term + prev = term + # Endpoint correction + if a != ctx.ninf: s += f(a)/2 + if b != ctx.inf: s += f(b)/2 + # Tail integral + if verbose: + print("Integrating f(x) from x = %s to %s" % (ctx.nstr(a), ctx.nstr(b))) + if integral: + s += integral + else: + integral, ierr = ctx.quad(f, interval, error=True) + if verbose: + print("Integration error:", ierr) + s += integral + err += ierr + finally: + ctx.prec = orig + if error: + return s, err + else: + return s + +@defun +def adaptive_extrapolation(ctx, update, emfun, kwargs): + option = kwargs.get + if ctx._fixed_precision: + tol = option('tol', ctx.eps*2**10) + else: + tol = option('tol', ctx.eps/2**10) + verbose = option('verbose', False) + maxterms = option('maxterms', ctx.dps*10) + method = set(option('method', 'r+s').split('+')) + skip = option('skip', 0) + steps = iter(option('steps', range(10, 10**9, 10))) + strict = option('strict') + #steps = (10 for i in range(1000)) + summer=[] + if 'd' in method or 'direct' in method: + TRY_RICHARDSON = TRY_SHANKS = TRY_EULER_MACLAURIN = False + else: + TRY_RICHARDSON = ('r' in method) or ('richardson' in method) + TRY_SHANKS = ('s' in method) or ('shanks' in method) + TRY_EULER_MACLAURIN = ('e' in method) or \ + ('euler-maclaurin' in method) + + def init_levin(m): + variant = kwargs.get("levin_variant", "u") + if isinstance(variant, str): + if variant == "all": + variant = ["u", "v", "t"] + else: + variant = [variant] + for s in variant: + L = levin_class(method = m, variant = s) + L.ctx = ctx + L.name = m + "(" + s + ")" + summer.append(L) + + if ('l' in method) or ('levin' in method): + init_levin("levin") + + if ('sidi' in method): + init_levin("sidi") + + if ('a' in method) or ('alternating' in method): + L = cohen_alt_class() + L.ctx = ctx + L.name = "alternating" + summer.append(L) + + last_richardson_value = 0 + shanks_table = [] + index = 0 + step = 10 + partial = [] + best = ctx.zero + orig = ctx.prec + try: + if 'workprec' in kwargs: + ctx.prec = kwargs['workprec'] + elif TRY_RICHARDSON or TRY_SHANKS or len(summer)!=0: + ctx.prec = (ctx.prec+10) * 4 + else: + ctx.prec += 30 + while 1: + if index >= maxterms: + break + + # Get new batch of terms + try: + step = next(steps) + except StopIteration: + pass + if verbose: + print("-"*70) + print("Adding terms #%i-#%i" % (index, index+step)) + update(partial, range(index, index+step)) + index += step + + # Check direct error + best = partial[-1] + error = abs(best - partial[-2]) + if verbose: + print("Direct error: %s" % ctx.nstr(error)) + if error <= tol: + return best + + # Check each extrapolation method + if TRY_RICHARDSON: + value, maxc = ctx.richardson(partial) + # Convergence + richardson_error = abs(value - last_richardson_value) + if verbose: + print("Richardson error: %s" % ctx.nstr(richardson_error)) + # Convergence + if richardson_error <= tol: + return value + last_richardson_value = value + # Unreliable due to cancellation + if ctx.eps*maxc > tol: + if verbose: + print("Ran out of precision for Richardson") + TRY_RICHARDSON = False + if richardson_error < error: + error = richardson_error + best = value + if TRY_SHANKS: + shanks_table = ctx.shanks(partial, shanks_table, randomized=True) + row = shanks_table[-1] + if len(row) == 2: + est1 = row[-1] + shanks_error = 0 + else: + est1, maxc, est2 = row[-1], abs(row[-2]), row[-3] + shanks_error = abs(est1-est2) + if verbose: + print("Shanks error: %s" % ctx.nstr(shanks_error)) + if shanks_error <= tol: + return est1 + if ctx.eps*maxc > tol: + if verbose: + print("Ran out of precision for Shanks") + TRY_SHANKS = False + if shanks_error < error: + error = shanks_error + best = est1 + for L in summer: + est, lerror = L.update_psum(partial) + if verbose: + print("%s error: %s" % (L.name, ctx.nstr(lerror))) + if lerror <= tol: + return est + if lerror < error: + error = lerror + best = est + if TRY_EULER_MACLAURIN: + if ctx.almosteq(ctx.mpc(ctx.sign(partial[-1]) / ctx.sign(partial[-2])), -1): + if verbose: + print ("NOT using Euler-Maclaurin: the series appears" + " to be alternating, so numerical\n quadrature" + " will most likely fail") + TRY_EULER_MACLAURIN = False + else: + value, em_error = emfun(index, tol) + value += partial[-1] + if verbose: + print("Euler-Maclaurin error: %s" % ctx.nstr(em_error)) + if em_error <= tol: + return value + if em_error < error: + best = value + finally: + ctx.prec = orig + if strict: + raise ctx.NoConvergence + if verbose: + print("Warning: failed to converge to target accuracy") + return best + +@defun +def nsum(ctx, f, *intervals, **options): + r""" + Computes the sum + + .. math :: S = \sum_{k=a}^b f(k) + + where `(a, b)` = *interval*, and where `a = -\infty` and/or + `b = \infty` are allowed, or more generally + + .. math :: S = \sum_{k_1=a_1}^{b_1} \cdots + \sum_{k_n=a_n}^{b_n} f(k_1,\ldots,k_n) + + if multiple intervals are given. + + Two examples of infinite series that can be summed by :func:`~mpmath.nsum`, + where the first converges rapidly and the second converges slowly, + are:: + + >>> from mpmath import (mp, fac, inf, nsum, sin, cos, zeta, mpf, pi, + ... log, sqrt, diff, ln2, altzeta, sech) + >>> mp.pretty = True + >>> nsum(lambda n: 1/fac(n), [0, inf]) + 2.71828182845905 + >>> nsum(lambda n: 1/n**2, [1, inf]) + 1.64493406684823 + + When appropriate, :func:`~mpmath.nsum` applies convergence acceleration to + accurately estimate the sums of slowly convergent series. If the series is + finite, :func:`~mpmath.nsum` currently does not attempt to perform any + extrapolation, and simply calls :func:`~mpmath.fsum`. + + Multidimensional infinite series are reduced to a single-dimensional + series over expanding hypercubes; if both infinite and finite dimensions + are present, the finite ranges are moved innermost. For more advanced + control over the summation order, use nested calls to :func:`~mpmath.nsum`, + or manually rewrite the sum as a single-dimensional series. + + **Options** + + *tol* + Desired maximum final error. Defaults roughly to the + epsilon of the working precision. + + *method* + Which summation algorithm to use (described below). + Default: ``'richardson+shanks'``. + + *maxterms* + Cancel after at most this many terms. Default: 10*dps. + + *steps* + An iterable giving the number of terms to add between + each extrapolation attempt. The default sequence is + [10, 20, 30, 40, ...]. For example, if you know that + approximately 100 terms will be required, efficiency might be + improved by setting this to [100, 10]. Then the first + extrapolation will be performed after 100 terms, the second + after 110, etc. + + *verbose* + Print details about progress. + + *ignore* + If enabled, any term that raises ``ArithmeticError`` + or ``ValueError`` (e.g. through division by zero) is replaced + by a zero. This is convenient for lattice sums with + a singular term near the origin. + + **Methods** + + Unfortunately, an algorithm that can efficiently sum any infinite + series does not exist. :func:`~mpmath.nsum` implements several different + algorithms that each work well in different cases. The *method* + keyword argument selects a method. + + The default method is ``'r+s'``, i.e. both Richardson extrapolation + and Shanks transformation is attempted. A slower method that + handles more cases is ``'r+s+e'``. For very high precision + summation, or if the summation needs to be fast (for example if + multiple sums need to be evaluated), it is a good idea to + investigate which one method works best and only use that. + + ``'richardson'`` / ``'r'``: + Uses Richardson extrapolation. Provides useful extrapolation + when `f(k) \sim P(k)/Q(k)` or when `f(k) \sim (-1)^k P(k)/Q(k)` + for polynomials `P` and `Q`. See :func:`~mpmath.richardson` for + additional information. + + ``'shanks'`` / ``'s'``: + Uses Shanks transformation. Typically provides useful + extrapolation when `f(k) \sim c^k` or when successive terms + alternate signs. Is able to sum some divergent series. + See :func:`~mpmath.shanks` for additional information. + + ``'levin'`` / ``'l'``: + Uses the Levin transformation. It performs better than the Shanks + transformation for logarithmic convergent or alternating divergent + series. The ``'levin_variant'``-keyword selects the variant. Valid + choices are "u", "t", "v" and "all" whereby "all" uses all three + u,t and v simultanously (This is good for performance comparison in + conjunction with "verbose=True"). Instead of the Levin transform one can + also use the Sidi-S transform by selecting the method ``'sidi'``. + See :func:`~mpmath.levin` for additional details. + + ``'alternating'`` / ``'a'``: + This is the convergence acceleration of alternating series developped + by Cohen, Villegras and Zagier. + See :func:`~mpmath.cohen_alt` for additional details. + + ``'euler-maclaurin'`` / ``'e'``: + Uses the Euler-Maclaurin summation formula to approximate + the remainder sum by an integral. This requires high-order + numerical derivatives and numerical integration. The advantage + of this algorithm is that it works regardless of the + decay rate of `f`, as long as `f` is sufficiently smooth. + See :func:`~mpmath.sumem` for additional information. + + ``'direct'`` / ``'d'``: + Does not perform any extrapolation. This can be used + (and should only be used for) rapidly convergent series. + The summation automatically stops when the terms + decrease below the target tolerance. + + **Basic examples** + + A finite sum:: + + >>> nsum(lambda k: 1/k, [1, 6]) + 2.45 + + Summation of a series going to negative infinity and a doubly + infinite series:: + + >>> nsum(lambda k: 1/k**2, [-inf, -1]) + 1.64493406684823 + >>> nsum(lambda k: 1/(1+k**2), [-inf, inf]) + 3.15334809493716 + + :func:`~mpmath.nsum` handles sums of complex numbers:: + + >>> nsum(lambda k: (0.5+0.25j)**k, [0, inf]) + (1.6 + 0.8j) + + The following sum converges very rapidly, so it is most + efficient to sum it by disabling convergence acceleration:: + + >>> mp.dps = 1000 + >>> a = nsum(lambda k: -(-1)**k * k**2 / fac(2*k), [1, inf], + ... method='direct') + >>> b = (cos(1)+sin(1))/4 + >>> abs(a-b) < mpf('1e-998') + True + + **Examples with Richardson extrapolation** + + Richardson extrapolation works well for sums over rational + functions, as well as their alternating counterparts:: + + >>> mp.dps = 50 + >>> nsum(lambda k: 1 / k**3, [1, inf], + ... method='richardson') + 1.2020569031595942853997381615114499907649862923405 + >>> zeta(3) + 1.2020569031595942853997381615114499907649862923405 + + >>> nsum(lambda n: (n + 3)/(n**3 + n**2), [1, inf], + ... method='richardson') + 2.9348022005446793094172454999380755676568497036204 + >>> pi**2/2-2 + 2.9348022005446793094172454999380755676568497036204 + + >>> nsum(lambda k: (-1)**k / k**3, [1, inf], + ... method='richardson') + -0.90154267736969571404980362113358749307373971925537 + >>> -3*zeta(3)/4 + -0.90154267736969571404980362113358749307373971925538 + + **Examples with Shanks transformation** + + The Shanks transformation works well for geometric series + and typically provides excellent acceleration for Taylor + series near the border of their disk of convergence. + Here we apply it to a series for `\log(2)`, which can be + seen as the Taylor series for `\log(1+x)` with `x = 1`:: + + >>> nsum(lambda k: -(-1)**k/k, [1, inf], + ... method='shanks') + 0.69314718055994530941723212145817656807550013436025 + >>> log(2) + 0.69314718055994530941723212145817656807550013436025 + + Here we apply it to a slowly convergent geometric series:: + + >>> nsum(lambda k: mpf('0.995')**k, [0, inf], + ... method='shanks') + 200.0 + + Finally, Shanks' method works very well for alternating series + where `f(k) = (-1)^k g(k)`, and often does so regardless of + the exact decay rate of `g(k)`:: + + >>> mp.dps = 15 + >>> nsum(lambda k: (-1)**(k+1) / k**1.5, [1, inf], + ... method='shanks') + 0.765147024625408 + >>> (2-sqrt(2))*zeta(1.5)/2 + 0.765147024625408 + + The following slowly convergent alternating series has no known + closed-form value. Evaluating the sum a second time at higher + precision indicates that the value is probably correct:: + + >>> nsum(lambda k: (-1)**k / log(k), [2, inf], + ... method='shanks') + 0.924299897222939 + >>> mp.dps = 30 + >>> nsum(lambda k: (-1)**k / log(k), [2, inf], + ... method='shanks') + 0.92429989722293885595957018136 + + **Examples with Levin transformation** + + The following example calculates Euler's constant as the constant term in + the Laurent expansion of zeta(s) at s=1. This sum converges extremly slow + because of the logarithmic convergence behaviour of the Dirichlet series + for zeta. + + >>> mp.dps = 30 + >>> z = mp.mpf(10) ** (-10) + >>> a = mp.nsum(lambda n: n**(-(1+z)), [1, mp.inf], method = "levin") - 1 / z + >>> print(mp.chop(a - mp.euler, tol = 1e-10)) + 0.0 + + Now we sum the zeta function outside its range of convergence + (attention: This does not work at the negative integers!): + + >>> mp.dps = 15 + >>> w = mp.nsum(lambda n: n ** (2 + 3j), [1, mp.inf], method = "levin", levin_variant = "v") + >>> print(mp.chop(w - mp.zeta(-2-3j))) + 0.0 + + The next example resummates an asymptotic series expansion of an integral + related to the exponential integral. + + >>> mp.dps = 15 + >>> z = mp.mpf(10) + >>> # exact = mp.quad(lambda x: mp.exp(-x)/(1+x/z),[0,mp.inf]) + >>> exact = z * mp.exp(z) * mp.expint(1,z) # this is the symbolic expression for the integral + >>> w = mp.nsum(lambda n: (-1) ** n * mp.fac(n) * z ** (-n), [0, mp.inf], method = "sidi", levin_variant = "t") + >>> print(mp.chop(w - exact)) + 0.0 + + Following highly divergent asymptotic expansion needs some care. Firstly we + need copious amount of working precision. Secondly the stepsize must not be + chosen to large, otherwise nsum may miss the point where the Levin transform + converges and reach the point where only numerical garbage is produced due to + numerical cancellation. + + >>> mp.dps = 15 + >>> z = mp.mpf(2) + >>> # exact = mp.quad(lambda x: mp.exp( -x * x / 2 - z * x ** 4), [0,mp.inf]) * 2 / mp.sqrt(2 * mp.pi) + >>> exact = mp.exp(mp.one / (32 * z)) * mp.besselk(mp.one / 4, mp.one / (32 * z)) / (4 * mp.sqrt(z * mp.pi)) # this is the symbolic expression for the integral + >>> w = mp.nsum(lambda n: (-z)**n * mp.fac(4 * n) / (mp.fac(n) * mp.fac(2 * n) * (4 ** n)), + ... [0, mp.inf], method = "levin", levin_variant = "t", workprec = 8*mp.prec, steps = [2] + [1 for x in range(1000)]) + >>> print(mp.chop(w - exact)) + 0.0 + + The hypergeoemtric function can also be summed outside its range of convergence: + + >>> mp.dps = 15 + >>> z = 2 + 1j + >>> exact = mp.hyp2f1(2 / mp.mpf(3), 4 / mp.mpf(3), 1 / mp.mpf(3), z) + >>> f = lambda n: mp.rf(2 / mp.mpf(3), n) * mp.rf(4 / mp.mpf(3), n) * z**n / (mp.rf(1 / mp.mpf(3), n) * mp.fac(n)) + >>> v = mp.nsum(f, [0, mp.inf], method = "levin", steps = [10 for x in range(1000)]) + >>> print(mp.chop(exact-v)) + 0.0 + + **Examples with Cohen's alternating series resummation** + + The next example sums the alternating zeta function: + + >>> v = mp.nsum(lambda n: (-1)**(n-1) / n, [1, mp.inf], method = "a") + >>> print(mp.chop(v - mp.log(2))) + 0.0 + + The derivate of the alternating zeta function outside its range of + convergence: + + >>> v = mp.nsum(lambda n: (-1)**n * mp.log(n) * n, [1, mp.inf], method = "a") + >>> print(mp.chop(v - mp.diff(lambda s: mp.altzeta(s), -1))) + 0.0 + + **Examples with Euler-Maclaurin summation** + + The sum in the following example has the wrong rate of convergence + for either Richardson or Shanks to be effective. + + >>> f = lambda k: log(k)/k**2.5 + >>> mp.dps = 15 + >>> nsum(f, [1, inf], method='euler-maclaurin') + 0.38734195032621 + >>> -diff(zeta, 2.5) + 0.38734195032621 + + Increasing ``steps`` improves speed at higher precision:: + + >>> mp.dps = 50 + >>> nsum(f, [1, inf], method='euler-maclaurin', steps=[250]) + 0.38734195032620997271199237593105101319948228874688 + >>> -diff(zeta, 2.5) + 0.38734195032620997271199237593105101319948228874688 + + **Divergent series** + + The Shanks transformation is able to sum some *divergent* + series. In particular, it is often able to sum Taylor series + beyond their radius of convergence (this is due to a relation + between the Shanks transformation and Pade approximations; + see :func:`~mpmath.pade` for an alternative way to evaluate divergent + Taylor series). Furthermore the Levin-transform examples above + contain some divergent series resummation. + + Here we apply it to `\log(1+x)` far outside the region of + convergence:: + + >>> mp.dps = 50 + >>> nsum(lambda k: -(-9)**k/k, [1, inf], + ... method='shanks') + 2.3025850929940456840179914546843642076011014886288 + >>> log(10) + 2.3025850929940456840179914546843642076011014886288 + + A particular type of divergent series that can be summed + using the Shanks transformation is geometric series. + The result is the same as using the closed-form formula + for an infinite geometric series:: + + >>> mp.dps = 15 + >>> for n in range(-8, 8): + ... if n == 1: + ... continue + ... print("%s %s %s" % (mpf(n), mpf(1)/(1-n), + ... nsum(lambda k: n**k, [0, inf], method='shanks'))) + ... + -8.0 0.111111111111111 0.111111111111111 + -7.0 0.125 0.125 + -6.0 0.142857142857143 0.142857142857143 + -5.0 0.166666666666667 0.166666666666667 + -4.0 0.2 0.2 + -3.0 0.25 0.25 + -2.0 0.333333333333333 0.333333333333333 + -1.0 0.5 0.5 + 0.0 1.0 1.0 + 2.0 -1.0 -1.0 + 3.0 -0.5 -0.5 + 4.0 -0.333333333333333 -0.333333333333333 + 5.0 -0.25 -0.25 + 6.0 -0.2 -0.2 + 7.0 -0.166666666666667 -0.166666666666667 + + **Multidimensional sums** + + Any combination of finite and infinite ranges is allowed for the + summation indices:: + + >>> mp.dps = 15 + >>> nsum(lambda x,y: x+y, [2,3], [4,5]) + 28.0 + >>> nsum(lambda x,y: x/2**y, [1,3], [1,inf]) + 6.0 + >>> nsum(lambda x,y: y/2**x, [1,inf], [1,3]) + 6.0 + >>> nsum(lambda x,y,z: z/(2**x*2**y), [1,inf], [1,inf], [3,4]) + 7.0 + >>> nsum(lambda x,y,z: y/(2**x*2**z), [1,inf], [3,4], [1,inf]) + 7.0 + >>> nsum(lambda x,y,z: x/(2**z*2**y), [3,4], [1,inf], [1,inf]) + 7.0 + + Some nice examples of double series with analytic solutions or + reductions to single-dimensional series (see [1]):: + + >>> nsum(lambda m, n: 1/2**(m*n), [1,inf], [1,inf]) + 1.60669515241529 + >>> nsum(lambda n: 1/(2**n-1), [1,inf]) + 1.60669515241529 + + >>> nsum(lambda i,j: (-1)**(i+j)/(i**2+j**2), [1,inf], [1,inf]) + 0.278070510848213 + >>> pi*(pi-3*ln2)/12 + 0.278070510848213 + + >>> nsum(lambda i,j: (-1)**(i+j)/(i+j)**2, [1,inf], [1,inf]) + 0.129319852864168 + >>> altzeta(2) - altzeta(1) + 0.129319852864168 + + >>> nsum(lambda i,j: (-1)**(i+j)/(i+j)**3, [1,inf], [1,inf]) + 0.0790756439455825 + >>> altzeta(3) - altzeta(2) + 0.0790756439455825 + + >>> nsum(lambda m,n: m**2*n/(3**m*(n*3**m+m*3**n)), + ... [1,inf], [1,inf]) + 0.28125 + >>> mpf(9)/32 + 0.28125 + + >>> nsum(lambda i,j: fac(i-1)*fac(j-1)/fac(i+j), + ... [1,inf], [1,inf], workprec=400) + 1.64493406684823 + >>> zeta(2) + 1.64493406684823 + + A hard example of a multidimensional sum is the Madelung constant + in three dimensions (see [2]). The defining sum converges very + slowly and only conditionally, so :func:`~mpmath.nsum` is lucky to + obtain an accurate value through convergence acceleration. The + second evaluation below uses a much more efficient, rapidly + convergent 2D sum:: + + >>> nsum(lambda x,y,z: (-1)**(x+y+z)/(x*x+y*y+z*z)**0.5, + ... [-inf,inf], [-inf,inf], [-inf,inf], ignore=True) + -1.74756459463318 + >>> nsum(lambda x,y: -12*pi*sech(0.5*pi * \ + ... sqrt((2*x+1)**2+(2*y+1)**2))**2, [0,inf], [0,inf]) + -1.74756459463318 + + Another example of a lattice sum in 2D:: + + >>> nsum(lambda x,y: (-1)**(x+y) / (x**2+y**2), [-inf,inf], + ... [-inf,inf], ignore=True) + -2.1775860903036 + >>> -pi*ln2 + -2.1775860903036 + + An example of an Eisenstein series:: + + >>> nsum(lambda m,n: (m+n*1j)**(-4), [-inf,inf], [-inf,inf], + ... ignore=True) + (3.1512120021539 + 0.0j) + + **References** + + 1. [Weisstein]_ http://mathworld.wolfram.com/DoubleSeries.html, + 2. [Weisstein]_ http://mathworld.wolfram.com/MadelungConstants.html + + """ + infinite, g = standardize(ctx, f, intervals, options) + if not infinite: + return +g() + + def update(partial_sums, indices): + if partial_sums: + psum = partial_sums[-1] + else: + psum = ctx.zero + for k in indices: + psum = psum + g(ctx.mpf(k)) + partial_sums.append(psum) + + prec = ctx.prec + + def emfun(point, tol): + workprec = ctx.prec + ctx.prec = prec + 10 + v = ctx.sumem(g, [point, ctx.inf], tol, error=1) + ctx.prec = workprec + return v + + return +ctx.adaptive_extrapolation(update, emfun, options) + + +def wrapsafe(f): + def g(*args): + try: + return f(*args) + except (ArithmeticError, ValueError): + return 0 + return g + +def standardize(ctx, f, intervals, options): + if options.get("ignore"): + f = wrapsafe(f) + finite = [] + infinite = [] + for k, points in enumerate(intervals): + a, b = ctx._as_points(points) + if b < a: + return False, (lambda: ctx.zero) + if a == ctx.ninf or b == ctx.inf: + infinite.append((k, (a,b))) + else: + finite.append((k, (int(a), int(b)))) + if finite: + f = fold_finite(ctx, f, finite) + if not infinite: + return False, lambda: f(*([0]*len(intervals))) + if infinite: + f = standardize_infinite(ctx, f, infinite) + f = fold_infinite(ctx, f, infinite) + args = [0] * len(intervals) + d = infinite[0][0] + def g(k): + args[d] = k + return f(*args) + return True, g + +def fold_finite(ctx, f, intervals): + if not intervals: + return f + indices = [v[0] for v in intervals] + points = [v[1] for v in intervals] + ranges = [range(a, b+1) for (a,b) in points] + def g(*args): + args = list(args) + s = ctx.zero + for xs in itertools.product(*ranges): + for dim, x in zip(indices, xs): + args[dim] = ctx.mpf(x) + s += f(*args) + return s + #print "Folded finite", indices + return g + +# Standardize each interval to [0,inf] +def standardize_infinite(ctx, f, intervals): + if not intervals: + return f + dim, [a,b] = intervals[-1] + if a == ctx.ninf: + if b == ctx.inf: + def g(*args): + args = list(args) + k = args[dim] + if k: + s = f(*args) + args[dim] = -k + s += f(*args) + return s + else: + return f(*args) + else: + def g(*args): + args = list(args) + args[dim] = b - args[dim] + return f(*args) + else: + def g(*args): + args = list(args) + args[dim] += a + return f(*args) + #print "Standardized infinity along dimension", dim, a, b + return standardize_infinite(ctx, g, intervals[:-1]) + +def fold_infinite(ctx, f, intervals): + if len(intervals) < 2: + return f + dim1 = intervals[-2][0] + dim2 = intervals[-1][0] + # Assume intervals are [0,inf] x [0,inf] x ... + def g(*args): + args = list(args) + #args.insert(dim2, None) + n = int(args[dim1]) + s = ctx.zero + #y = ctx.mpf(n) + args[dim2] = ctx.mpf(n) #y + for x in range(n+1): + args[dim1] = ctx.mpf(x) + s += f(*args) + args[dim1] = ctx.mpf(n) #ctx.mpf(n) + for y in range(n): + args[dim2] = ctx.mpf(y) + s += f(*args) + return s + #print "Folded infinite from", len(intervals), "to", (len(intervals)-1) + return fold_infinite(ctx, g, intervals[:-1]) + +@defun +def nprod(ctx, f, interval, nsum=False, **kwargs): + r""" + Computes the product + + .. math :: + + P = \prod_{k=a}^b f(k) + + where `(a, b)` = *interval*, and where `a = -\infty` and/or + `b = \infty` are allowed. + + By default, :func:`~mpmath.nprod` uses the same extrapolation methods as + :func:`~mpmath.nsum`, except applied to the partial products rather than + partial sums, and the same keyword options as for :func:`~mpmath.nsum` are + supported. If ``nsum=True``, the product is instead computed via + :func:`~mpmath.nsum` as + + .. math :: + + P = \exp\left( \sum_{k=a}^b \log(f(k)) \right). + + This is slower, but can sometimes yield better results. It is + also required (and used automatically) when Euler-Maclaurin + summation is requested. + + **Examples** + + A simple finite product:: + + >>> from mpmath import (mp, nprod, inf, csch, cosh, exp, pi, sinh, + ... sqrt, exp, euler, cos, tanh, log, jtheta) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> nprod(lambda k: k, [1, 4]) + 24.0 + + A large number of infinite products have known exact values, + and can therefore be used as a reference. Most of the following + examples are taken from MathWorld [1]. + + A few infinite products with simple values are:: + + >>> 2*nprod(lambda k: (4*k**2)/(4*k**2-1), [1, inf]) + 3.141592653589793238462643 + >>> nprod(lambda k: (1+1/k)**2/(1+2/k), [1, inf]) + 2.0 + >>> nprod(lambda k: (k**3-1)/(k**3+1), [2, inf]) + 0.6666666666666666666666667 + >>> nprod(lambda k: (1-1/k**2), [2, inf]) + 0.5 + + Next, several more infinite products with more complicated + values:: + + >>> nprod(lambda k: exp(1/k**2), [1, inf]) + 5.180668317897115748416626 + >>> exp(pi**2/6) + 5.180668317897115748416626 + + >>> nprod(lambda k: (k**2-1)/(k**2+1), [2, inf]) + 0.2720290549821331629502366 + >>> pi*csch(pi) + 0.2720290549821331629502366 + + >>> nprod(lambda k: (k**4-1)/(k**4+1), [2, inf]) + 0.8480540493529003921296502 + >>> pi*sinh(pi)/(cosh(sqrt(2)*pi)-cos(sqrt(2)*pi)) + 0.8480540493529003921296502 + + >>> nprod(lambda k: (1+1/k+1/k**2)**2/(1+2/k+3/k**2), [1, inf]) + 1.848936182858244485224927 + >>> 3*sqrt(2)*cosh(pi*sqrt(3)/2)**2*csch(pi*sqrt(2))/pi + 1.848936182858244485224927 + + >>> nprod(lambda k: (1-1/k**4), [2, inf]) + 0.9190194775937444301739244 + >>> sinh(pi)/(4*pi) + 0.9190194775937444301739244 + + >>> nprod(lambda k: (1-1/k**6), [2, inf]) + 0.9826842777421925183244759 + >>> (1+cosh(pi*sqrt(3)))/(12*pi**2) + 0.9826842777421925183244759 + + >>> nprod(lambda k: (1+1/k**2), [2, inf]) + 1.838038955187488860347849 + >>> sinh(pi)/(2*pi) + 1.838038955187488860347849 + + >>> nprod(lambda n: (1+1/n)**n * exp(1/(2*n)-1), [1, inf]) + 1.447255926890365298959138 + >>> exp(1+euler/2)/sqrt(2*pi) + 1.447255926890365298959138 + + The following two products are equivalent and can be evaluated in + terms of a Jacobi theta function. Pi can be replaced by any value + (as long as convergence is preserved):: + + >>> nprod(lambda k: (1-pi**-k)/(1+pi**-k), [1, inf]) + 0.3838451207481672404778686 + >>> nprod(lambda k: tanh(k*log(pi)/2), [1, inf]) + 0.3838451207481672404778686 + >>> jtheta(4,0,1/pi) + 0.3838451207481672404778686 + + This product does not have a known closed form value:: + + >>> nprod(lambda k: (1-1/2**k), [1, inf]) + 0.2887880950866024212788997 + + A product taken from `-\infty`:: + + >>> nprod(lambda k: 1-k**(-3), [-inf,-2]) + 0.8093965973662901095786805 + >>> cosh(pi*sqrt(3)/2)/(3*pi) + 0.8093965973662901095786805 + + A doubly infinite product:: + + >>> nprod(lambda k: exp(1/(1+k**2)), [-inf, inf]) + 23.41432688231864337420035 + >>> exp(pi/tanh(pi)) + 23.41432688231864337420035 + + A product requiring the use of Euler-Maclaurin summation to compute + an accurate value:: + + >>> nprod(lambda k: (1-1/k**2.5), [2, inf], method='e') + 0.696155111336231052898125 + + **References** + + 1. [Weisstein]_ http://mathworld.wolfram.com/InfiniteProduct.html + + """ + if nsum or ('e' in kwargs.get('method', '')): + orig = ctx.prec + try: + # TODO: we are evaluating log(1+eps) -> eps, which is + # inaccurate. This currently works because nsum greatly + # increases the working precision. But we should be + # more intelligent and handle the precision here. + ctx.prec += 10 + v = ctx.nsum(lambda n: ctx.ln(f(n)), interval, **kwargs) + finally: + ctx.prec = orig + return +ctx.exp(v) + + a, b = ctx._as_points(interval) + if a == ctx.ninf: + if b == ctx.inf: + return f(0) * ctx.nprod(lambda k: f(-k) * f(k), [1, ctx.inf], **kwargs) + return ctx.nprod(f, [-b, ctx.inf], **kwargs) + elif b != ctx.inf: + return ctx.fprod(f(ctx.mpf(k)) for k in range(int(a), int(b)+1)) + + a = int(a) + + def update(partial_products, indices): + if partial_products: + pprod = partial_products[-1] + else: + pprod = ctx.one + for k in indices: + pprod = pprod * f(a + ctx.mpf(k)) + partial_products.append(pprod) + + return +ctx.adaptive_extrapolation(update, None, kwargs) + + +@defun +def limit(ctx, f, x, direction=1, exp=False, **kwargs): + r""" + Computes an estimate of the limit + + .. math :: + + \lim_{t \to x} f(t) + + where `x` may be finite or infinite. + + For finite `x`, :func:`~mpmath.limit` evaluates `f(x + d/n)` for + consecutive integer values of `n`, where the approach direction + `d` may be specified using the *direction* keyword argument. + For infinite `x`, :func:`~mpmath.limit` evaluates values of + `f(\mathrm{sign}(x) \cdot n)`. + + If the approach to the limit is not sufficiently fast to give + an accurate estimate directly, :func:`~mpmath.limit` attempts to find + the limit using Richardson extrapolation or the Shanks + transformation. You can select between these methods using + the *method* keyword (see documentation of :func:`~mpmath.nsum` for + more information). + + **Options** + + The following options are available with essentially the + same meaning as for :func:`~mpmath.nsum`: *tol*, *method*, *maxterms*, + *steps*, *verbose*. + + If the option *exp=True* is set, `f` will be + sampled at exponentially spaced points `n = 2^1, 2^2, 2^3, \ldots` + instead of the linearly spaced points `n = 1, 2, 3, \ldots`. + This can sometimes improve the rate of convergence so that + :func:`~mpmath.limit` may return a more accurate answer (and faster). + However, do note that this can only be used if `f` + supports fast and accurate evaluation for arguments that + are extremely close to the limit point (or if infinite, + very large arguments). + + **Examples** + + A basic evaluation of a removable singularity:: + + >>> from mpmath import (limit, mp, sin, inf, exp, fac, sqrt, pi, e, + ... mpf, log, euler) + >>> mp.dps = 30 + >>> mp.pretty = True + >>> limit(lambda x: (x-sin(x))/x**3, 0) + 0.166666666666666666666666666667 + + Computing the exponential function using its limit definition:: + + >>> limit(lambda n: (1+3/n)**n, inf) + 20.0855369231876677409285296546 + >>> exp(3) + 20.0855369231876677409285296546 + + A limit for `\pi`:: + + >>> f = lambda n: 2**(4*n+1)*fac(n)**4/(2*n+1)/fac(2*n)**2 + >>> limit(f, inf) + 3.14159265358979323846264338328 + + Calculating the coefficient in Stirling's formula:: + + >>> limit(lambda n: fac(n) / (sqrt(n)*(n/e)**n), inf) + 2.50662827463100050241576528481 + >>> sqrt(2*pi) + 2.50662827463100050241576528481 + + Evaluating Euler's constant `\gamma` using the limit representation + + .. math :: + + \gamma = \lim_{n \rightarrow \infty } \left[ \left( + \sum_{k=1}^n \frac{1}{k} \right) - \log(n) \right] + + (which converges notoriously slowly):: + + >>> f = lambda n: sum([mpf(1)/k for k in range(1,int(n)+1)]) - log(n) + >>> limit(f, inf) + 0.577215664901532860606512090082 + >>> +euler + 0.577215664901532860606512090082 + + With default settings, the following limit converges too slowly + to be evaluated accurately. Changing to exponential sampling + however gives a perfect result:: + + >>> f = lambda x: sqrt(x**3+x**2)/(sqrt(x**3)+x) + >>> limit(f, inf) + 0.992831158558330281129249686491 + >>> limit(f, inf, exp=True) + 1.0 + + """ + + if ctx.isinf(x): + direction = ctx.sign(x) + g = lambda k: f(ctx.mpf(k+1)*direction) + else: + direction *= ctx.one + g = lambda k: f(x + direction/(k+1)) + if exp: + h = g + g = lambda k: h(2**k) + + def update(values, indices): + for k in indices: + values.append(g(k+1)) + + # XXX: steps used by nsum don't work well + if 'steps' not in kwargs: + kwargs['steps'] = [10] + + return +ctx.adaptive_extrapolation(update, None, kwargs) diff --git a/mpmath/calculus/inverselaplace.py b/mpmath/calculus/inverselaplace.py new file mode 100644 index 0000000..610d2af --- /dev/null +++ b/mpmath/calculus/inverselaplace.py @@ -0,0 +1,947 @@ +# contributed to mpmath by Kristopher L. Kuhlman, February 2017 +# contributed to mpmath by Guillermo Navas-Palencia, February 2022 + +class InverseLaplaceTransform: + r""" + Inverse Laplace transform methods are implemented using this + class, in order to simplify the code and provide a common + infrastructure. + + Implement a custom inverse Laplace transform algorithm by + subclassing :class:`InverseLaplaceTransform` and implementing the + appropriate methods. The subclass can then be used by + :func:`~mpmath.invertlaplace` by passing it as the *method* + argument. + """ + + def __init__(self, ctx): + self.ctx = ctx + + def calc_laplace_parameter(self, t, **kwargs): + r""" + Determine the vector of Laplace parameter values needed for an + algorithm, this will depend on the choice of algorithm (de + Hoog is default), the algorithm-specific parameters passed (or + default ones), and desired time. + """ + raise NotImplementedError + + def calc_time_domain_solution(self, fp): + r""" + Compute the time domain solution, after computing the + Laplace-space function evaluations at the abscissa required + for the algorithm. Abscissa computed for one algorithm are + typically not useful for another algorithm. + """ + raise NotImplementedError + + +class FixedTalbot(InverseLaplaceTransform): + + def calc_laplace_parameter(self, t, **kwargs): + r"""The "fixed" Talbot method deforms the Bromwich contour towards + `-\infty` in the shape of a parabola. Traditionally the Talbot + algorithm has adjustable parameters, but the "fixed" version + does not. The `r` parameter could be passed in as a parameter, + if you want to override the default given by (Abate & Valko, + 2004). + + The Laplace parameter is sampled along a parabola opening + along the negative imaginary axis, with the base of the + parabola along the real axis at + `p=\frac{r}{t_\mathrm{max}}`. As the number of terms used in + the approximation (degree) grows, the abscissa required for + function evaluation tend towards `-\infty`, requiring high + precision to prevent overflow. If any poles, branch cuts or + other singularities exist such that the deformed Bromwich + contour lies to the left of the singularity, the method will + fail. + + **Optional arguments** + + :class:`~mpmath.calculus.inverselaplace.FixedTalbot.calc_laplace_parameter` + recognizes the following keywords + + *tmax* + maximum time associated with vector of times + (typically just the time requested) + *degree* + integer order of approximation (M = number of terms) + *r* + abscissa for `p_0` (otherwise computed using rule + of thumb `2M/5`) + + The working precision will be increased according to a rule of + thumb. If 'degree' is not specified, the working precision and + degree are chosen to hopefully achieve the dps of the calling + context. If 'degree' is specified, the working precision is + chosen to achieve maximum resulting precision for the + specified degree. + + .. math :: + + p_0=\frac{r}{t} + + .. math :: + + p_i=\frac{i r \pi}{Mt_\mathrm{max}}\left[\cot\left( + \frac{i\pi}{M}\right) + j \right] \qquad 1\le i 0: + self.degree += 1 + + M = self.degree + + # this is adjusting the dps of the calling context + # hopefully the caller doesn't monkey around with it + # between calling this routine and calc_time_domain_solution() + self.dps_orig = self.ctx.dps + self.ctx.dps = self.dps_goal + + self.V = self._coeff() + self.p = self.ctx.matrix(self.ctx.arange(1, M+1))*self.ctx.ln2/self.t + + # NB: p is real (mpf) + + def _coeff(self): + r"""Salzer summation weights (aka, "Stehfest coefficients") + only depend on the approximation order (M) and the precision""" + + M = self.degree + M2 = int(M/2) # checked earlier that M is even + + V = self.ctx.matrix(M, 1) + + # Salzer summation weights + # get very large in magnitude and oscillate in sign, + # if the precision is not high enough, there will be + # catastrophic cancellation + for k in range(1, M+1): + z = self.ctx.matrix(min(k, M2)+1, 1) + for j in range(int((k+1)/2), min(k, M2)+1): + z[j] = (self.ctx.power(j, M2)*self.ctx.fac(2*j)/ + (self.ctx.fac(M2-j)*self.ctx.fac(j)* + self.ctx.fac(j-1)*self.ctx.fac(k-j)* + self.ctx.fac(2*j-k))) + V[k-1] = self.ctx.power(-1, k+M2)*self.ctx.fsum(z) + + return V + + def calc_time_domain_solution(self, fp, t, manual_prec=False): + r"""Compute time-domain Stehfest algorithm solution. + + .. math :: + + f(t,M) = \frac{\log 2}{t} \sum_{k=1}^{M} V_k \bar{f}\left( + p_k \right) + + where + + .. math :: + + V_k = (-1)^{k + N/2} \sum^{\min(k,N/2)}_{i=\lfloor(k+1)/2 \rfloor} + \frac{i^{\frac{N}{2}}(2i)!}{\left(\frac{N}{2}-i \right)! \, i! \, + \left(i-1 \right)! \, \left(k-i\right)! \, \left(2i-k \right)!} + + As the degree increases, the abscissa (`p_k`) only increase + linearly towards `\infty`, but the Stehfest coefficients + (`V_k`) alternate in sign and increase rapidly in sign, + requiring high precision to prevent overflow or loss of + significance when evaluating the sum. + + **References** + + 1. [Widder]_ + 2. [Stehfest]_ + + """ + + # required + self.t = self.ctx.convert(t) + + # assume fp was computed from p matrix returned from + # calc_laplace_parameter(), so is already + # a list or matrix of mpmath 'mpf' types + + result = self.ctx.fdot(self.V, fp)*self.ctx.ln2/self.t + + # setting dps back to value when calc_laplace_parameter was called + if not manual_prec: + self.ctx.dps = self.dps_orig + + # ignore any small imaginary part + return result.real + + +# **************************************** + +class deHoog(InverseLaplaceTransform): + + def calc_laplace_parameter(self, t, **kwargs): + r"""the de Hoog, Knight & Stokes algorithm is an + accelerated form of the Fourier series numerical + inverse Laplace transform algorithms. + + .. math :: + + p_k = \gamma + \frac{jk}{T} \qquad 0 \le k < 2M+1 + + where + + .. math :: + + \gamma = \alpha - \frac{\log \mathrm{tol}}{2T}, + + `j=\sqrt{-1}`, `T = 2t_\mathrm{max}` is a scaled time, + `\alpha=10^{-\mathrm{dps\_goal}}` is the real part of the + rightmost pole or singularity, which is chosen based on the + desired accuracy (assuming the rightmost singularity is 0), + and `\mathrm{tol}=10\alpha` is the desired tolerance, which is + chosen in relation to `\alpha`.` + + When increasing the degree, the abscissa increase towards + `j\infty`, but more slowly than the fixed Talbot + algorithm. The de Hoog et al. algorithm typically does better + with oscillatory functions of time, and less well-behaved + functions. The method tends to be slower than the Talbot and + Stehfest algorithsm, especially so at very high precision + (e.g., `>500` digits precision). + + """ + + # required + # ------------------------------ + self.t = self.ctx.convert(t) + + # optional + # ------------------------------ + self.tmax = kwargs.get('tmax', self.t) + + # empirical relationships used here based on a linear fit of + # requested and delivered dps for exponentially decaying time + # functions for requested dps up to 512. + + if 'degree' in kwargs: + self.degree = kwargs['degree'] + self.dps_goal = int(1.38*self.degree) + else: + self.dps_goal = int(self.ctx.dps*1.36) + self.degree = max(10, self.dps_goal) + + # 2*M+1 terms in approximation + M = self.degree + + # adjust alpha component of abscissa of convergence for higher + # precision + tmp = self.ctx.power(10.0, -self.dps_goal) + self.alpha = self.ctx.convert(kwargs.get('alpha', tmp)) + + # desired tolerance (here simply related to alpha) + self.tol = self.ctx.convert(kwargs.get('tol', self.alpha*10.0)) + self.np = 2*self.degree+1 # number of terms in approximation + + # this is adjusting the dps of the calling context + # hopefully the caller doesn't monkey around with it + # between calling this routine and calc_time_domain_solution() + self.dps_orig = self.ctx.dps + self.ctx.dps = self.dps_goal + + # scaling factor (likely tun-able, but 2 is typical) + self.scale = kwargs.get('scale', 2) + self.T = self.ctx.convert(kwargs.get('T', self.scale*self.tmax)) + + self.p = self.ctx.matrix(2*M+1, 1) + self.gamma = self.alpha - self.ctx.log(self.tol)/(self.scale*self.T) + self.p = (self.gamma + self.ctx.pi* + self.ctx.matrix(self.ctx.arange(self.np))/self.T*1j) + + # NB: p is complex (mpc) + + def calc_time_domain_solution(self, fp, t, manual_prec=False): + r"""Calculate time-domain solution for + de Hoog, Knight & Stokes algorithm. + + The un-accelerated Fourier series approach is: + + .. math :: + + f(t,2M+1) = \frac{e^{\gamma t}}{T} \sum_{k=0}^{2M}{}^{'} + \Re\left[\bar{f}\left( p_k \right) + e^{i\pi t/T} \right], + + where the prime on the summation indicates the first term is halved. + + This simplistic approach requires so many function evaluations + that it is not practical. Non-linear acceleration is + accomplished via Pade-approximation and an analytic expression + for the remainder of the continued fraction. See the original + paper (reference 2 below) a detailed description of the + numerical approach. + + **References** + + 1. [Davies]_ + 2. [Hoog]_ + + """ + + M = self.degree + np = self.np + T = self.T + + self.t = self.ctx.convert(t) + + # would it be useful to try re-using + # space between e&q and A&B? + e = self.ctx.zeros(np, M+1) + q = self.ctx.matrix(2*M, M) + d = self.ctx.matrix(np, 1) + A = self.ctx.zeros(np+1, 1) + B = self.ctx.ones(np+1, 1) + + # initialize Q-D table + e[:, 0] = 0.0 + 0j + q[0, 0] = fp[1]/(fp[0]/2) + for i in range(1, 2*M): + q[i, 0] = fp[i+1]/fp[i] + + # rhombus rule for filling triangular Q-D table (e & q) + for r in range(1, M+1): + # start with e, column 1, 0:2*M-2 + mr = 2*(M-r) + 1 + e[0:mr, r] = q[1:mr+1, r-1] - q[0:mr, r-1] + e[1:mr+1, r-1] + if not r == M: + rq = r+1 + mr = 2*(M-rq)+1 + 2 + for i in range(mr): + q[i, rq-1] = q[i+1, rq-2]*e[i+1, rq-1]/e[i, rq-1] + + # build up continued fraction coefficients (d) + d[0] = fp[0]/2 + for r in range(1, M+1): + d[2*r-1] = -q[0, r-1] # even terms + d[2*r] = -e[0, r] # odd terms + + # seed A and B for recurrence + A[0] = 0.0 + 0.0j + A[1] = d[0] + B[0:2] = 1.0 + 0.0j + + # base of the power series + z = self.ctx.expjpi(self.t/T) # i*pi is already in fcn + + # coefficients of Pade approximation (A & B) + # using recurrence for all but last term + for i in range(1, 2*M): + A[i+1] = A[i] + d[i]*A[i-1]*z + B[i+1] = B[i] + d[i]*B[i-1]*z + + # "improved remainder" to continued fraction + brem = (1 + (d[2*M-1] - d[2*M])*z)/2 + # powm1(x,y) computes x^y - 1 more accurately near zero + rem = brem*self.ctx.powm1(1 + d[2*M]*z/brem, + self.ctx.fraction(1, 2)) + + # last term of recurrence using new remainder + A[np] = A[2*M] + rem*A[2*M-1] + B[np] = B[2*M] + rem*B[2*M-1] + + # diagonal Pade approximation + # F=A/B represents accelerated trapezoid rule + result = self.ctx.exp(self.gamma*self.t)/T*(A[np]/B[np]).real + + # setting dps back to value when calc_laplace_parameter was called + if not manual_prec: + self.ctx.dps = self.dps_orig + + return result + + +# **************************************** + +class Cohen(InverseLaplaceTransform): + + def calc_laplace_parameter(self, t, **kwargs): + r"""The Cohen algorithm accelerates the convergence of the nearly + alternating series resulting from the application of the trapezoidal + rule to the Bromwich contour inversion integral. + + .. math :: + + p_k = \frac{\gamma}{2 t} + \frac{\pi i k}{t} \qquad 0 \le k < M + + where + + .. math :: + + \gamma = \frac{2}{3} (d + \log(10) + \log(2 t)), + + `d = \mathrm{dps\_goal}`, which is chosen based on the desired + accuracy using the method developed in [1] to improve numerical + stability. The Cohen algorithm shows robustness similar to the de Hoog + et al. algorithm, but it is faster than the fixed Talbot algorithm. + + **Optional arguments** + + *degree* + integer order of the approximation (M = number of terms) + *alpha* + abscissa for `p_0` (controls the discretization error) + + The working precision will be increased according to a rule of + thumb. If 'degree' is not specified, the working precision and + degree are chosen to hopefully achieve the dps of the calling + context. If 'degree' is specified, the working precision is + chosen to achieve maximum resulting precision for the + specified degree. + + **References** + + 1. [Glasserman]_ + + """ + self.t = self.ctx.convert(t) + + if 'degree' in kwargs: + self.degree = kwargs['degree'] + self.dps_goal = int(1.5 * self.degree) + else: + self.dps_goal = int(self.ctx.dps * 1.74) + self.degree = max(22, int(1.31 * self.dps_goal)) + + M = self.degree + 1 + + # this is adjusting the dps of the calling context hopefully + # the caller doesn't monkey around with it between calling + # this routine and calc_time_domain_solution() + self.dps_orig = self.ctx.dps + self.ctx.dps = self.dps_goal + + ttwo = 2 * self.t + tmp = self.ctx.dps * self.ctx.log(10) + self.ctx.log(ttwo) + tmp = self.ctx.fraction(2, 3) * tmp + self.alpha = self.ctx.convert(kwargs.get('alpha', tmp)) + + # all but time-dependent part of p + a_t = self.alpha / ttwo + p_t = self.ctx.pi * 1j / self.t + + self.p = self.ctx.matrix(M, 1) + self.p[0] = a_t + + for i in range(1, M): + self.p[i] = a_t + i * p_t + + def calc_time_domain_solution(self, fp, t, manual_prec=False): + r"""Calculate time-domain solution for Cohen algorithm. + + The accelerated nearly alternating series is: + + .. math :: + + f(t, M) = \frac{e^{\gamma / 2}}{t} \left[\frac{1}{2} + \Re\left(\bar{f}\left(\frac{\gamma}{2t}\right) \right) - + \sum_{k=0}^{M-1}\frac{c_{M,k}}{d_M}\Re\left(\bar{f} + \left(\frac{\gamma + 2(k+1) \pi i}{2t}\right)\right)\right], + + where coefficients `\frac{c_{M, k}}{d_M}` are described in [1]. + + 1. H. Cohen, F. Rodriguez Villegas, D. Zagier (2000). Convergence + acceleration of alternating series. *Experiment. Math* 9(1):3-12 + + """ + self.t = self.ctx.convert(t) + + n = self.degree + M = n + 1 + + A = self.ctx.matrix(M, 1) + for i in range(M): + A[i] = fp[i].real + + d = (3 + self.ctx.sqrt(8)) ** n + d = (d + 1 / d) / 2 + b = -self.ctx.one + c = -d + s = 0 + + for k in range(n): + c = b - c + s = s + c * A[k + 1] + b = 2 * (k + n) * (k - n) * b / ((2 * k + 1) * (k + self.ctx.one)) + + result = self.ctx.exp(self.alpha / 2) / self.t * (A[0] / 2 - s / d) + + # setting dps back to value when calc_laplace_parameter was + # called, unless flag is set. + if not manual_prec: + self.ctx.dps = self.dps_orig + + return result + + +# **************************************** + +class LaplaceTransformInversionMethods: + def __init__(ctx, *args, **kwargs): + ctx._fixed_talbot = FixedTalbot(ctx) + ctx._stehfest = Stehfest(ctx) + ctx._de_hoog = deHoog(ctx) + ctx._cohen = Cohen(ctx) + + def invertlaplace(ctx, f, t, **kwargs): + r"""Computes the numerical inverse Laplace transform for a + Laplace-space function at a given time. The function being + evaluated is assumed to be a real-valued function of time. + + The user must supply a Laplace-space function `\bar{f}(p)`, + and a desired time at which to estimate the time-domain + solution `f(t)`. + + A few basic examples of Laplace-space functions with known + inverses (see references [1,2]) : + + .. math :: + + \mathcal{L}\left\lbrace f(t) \right\rbrace=\bar{f}(p) + + .. math :: + + \mathcal{L}^{-1}\left\lbrace \bar{f}(p) \right\rbrace = f(t) + + .. math :: + + \bar{f}(p) = \frac{1}{(p+1)^2} + + .. math :: + + f(t) = t e^{-t} + + >>> from mpmath import (besselj, euler, exp, invertlaplace, log, + ... mp, nstr, sinh, sqrt) + >>> mp.pretty = True + >>> tt = [0.001, 0.01, 0.1, 1, 10] + >>> fp = lambda p: 1/(p+1)**2 + >>> ft = lambda t: t*exp(-t) + >>> ft(tt[0]),ft(tt[0])-invertlaplace(fp,tt[0],method='talbot') + (0.000999000499833375, 8.57923043561212e-20) + >>> ft(tt[1]),ft(tt[1])-invertlaplace(fp,tt[1],method='talbot') + (0.00990049833749168, 3.27007646698047e-19) + >>> ft(tt[2]),ft(tt[2])-invertlaplace(fp,tt[2],method='talbot') + (0.090483741803596, -1.75215800052168e-18) + >>> ft(tt[3]),ft(tt[3])-invertlaplace(fp,tt[3],method='talbot') + (0.367879441171442, 1.2428864009344e-17) + >>> ft(tt[4]),ft(tt[4])-invertlaplace(fp,tt[4],method='talbot') + (0.000453999297624849, 4.04513489306658e-20) + + The methods also work for higher precision: + + >>> mp.dps = 100 + >>> mp.pretty = True + >>> nstr(ft(tt[0]),15),nstr(ft(tt[0])-invertlaplace(fp,tt[0],method='talbot'),15) + ('0.000999000499833375', '-4.96868310693356e-105') + >>> nstr(ft(tt[1]),15),nstr(ft(tt[1])-invertlaplace(fp,tt[1],method='talbot'),15) + ('0.00990049833749168', '1.23032291513122e-104') + + .. math :: + + \bar{f}(p) = \frac{1}{p^2+1} + + .. math :: + + f(t) = \mathrm{J}_0(t) + + >>> mp.dps = 15 + >>> mp.pretty = True + >>> fp = lambda p: 1/sqrt(p*p + 1) + >>> ft = lambda t: besselj(0,t) + >>> ft(tt[0]),ft(tt[0])-invertlaplace(fp,tt[0],method='dehoog') + (0.999999750000016, -6.09717765032273e-18) + >>> ft(tt[1]),ft(tt[1])-invertlaplace(fp,tt[1],method='dehoog') + (0.99997500015625, -5.61756281076169e-17) + + .. math :: + + \bar{f}(p) = \frac{\log p}{p} + + .. math :: + + f(t) = -\gamma -\log t + + >>> mp.dps = 15 + >>> mp.pretty = True + >>> fp = lambda p: log(p)/p + >>> ft = lambda t: -euler-log(t) + >>> ft(tt[0]),ft(tt[0])-invertlaplace(fp,tt[0],method='stehfest') + (6.3305396140806, -1.92126634837863e-16) + >>> ft(tt[1]),ft(tt[1])-invertlaplace(fp,tt[1],method='stehfest') + (4.02795452108656, -4.81486093200704e-16) + + **Options** + + :func:`~mpmath.invertlaplace` recognizes the following optional + keywords valid for all methods: + + *method* + Chooses numerical inverse Laplace transform algorithm + (described below). + *degree* + Number of terms used in the approximation + + **Algorithms** + + Mpmath implements four numerical inverse Laplace transform + algorithms, attributed to: Talbot, Stehfest, and de Hoog, + Knight and Stokes. These can be selected by using + *method='talbot'*, *method='stehfest'*, *method='dehoog'* or + *method='cohen'* or by passing the classes *method=FixedTalbot*, + *method=Stehfest*, *method=deHoog*, or *method=Cohen*. The functions + ``invlaptalbot()``, ``invlapstehfest()``, + ``invlapdehoog()``, and ``invlapcohen()`` + are also available as shortcuts. + + All four algorithms implement a heuristic balance between the + requested precision and the precision used internally for the + calculations. This has been tuned for a typical exponentially + decaying function and precision up to few hundred decimal + digits. + + The Laplace transform converts the variable time (i.e., along + a line) into a parameter given by the right half of the + complex `p`-plane. Singularities, poles, and branch cuts in + the complex `p`-plane contain all the information regarding + the time behavior of the corresponding function. Any numerical + method must therefore sample `p`-plane "close enough" to the + singularities to accurately characterize them, while not + getting too close to have catastrophic cancellation, overflow, + or underflow issues. Most significantly, if one or more of the + singularities in the `p`-plane is not on the left side of the + Bromwich contour, its effects will be left out of the computed + solution, and the answer will be completely wrong. + + *Talbot* + + The fixed Talbot method is high accuracy and fast, but the + method can catastrophically fail for certain classes of time-domain + behavior, including a Heaviside step function for positive + time (e.g., `H(t-2)`), or some oscillatory behaviors. The + Talbot method usually has adjustable parameters, but the + "fixed" variety implemented here does not. This method + deforms the Bromwich integral contour in the shape of a + parabola towards `-\infty`, which leads to problems + when the solution has a decaying exponential in it (e.g., a + Heaviside step function is equivalent to multiplying by a + decaying exponential in Laplace space). + + *Stehfest* + + The Stehfest algorithm only uses abscissa along the real axis + of the complex `p`-plane to estimate the time-domain + function. Oscillatory time-domain functions have poles away + from the real axis, so this method does not work well with + oscillatory functions, especially high-frequency ones. This + method also depends on summation of terms in a series that + grows very large, and will have catastrophic cancellation + during summation if the working precision is too low. + + *de Hoog et al.* + + The de Hoog, Knight, and Stokes method is essentially a + Fourier-series quadrature-type approximation to the Bromwich + contour integral, with non-linear series acceleration and an + analytical expression for the remainder term. This method is + typically one of the most robust. This method also involves the + greatest amount of overhead, so it is typically the slowest of the + four methods at high precision. + + *Cohen* + + The Cohen method is a trapezoidal rule approximation to the Bromwich + contour integral, with linear acceleration for alternating + series. This method is as robust as the de Hoog et al method and the + fastest of the four methods at high precision, and is therefore the + default method. + + **Singularities** + + All numerical inverse Laplace transform methods have problems + at large time when the Laplace-space function has poles, + singularities, or branch cuts to the right of the origin in + the complex plane. For simple poles in `\bar{f}(p)` at the + `p`-plane origin, the time function is constant in time (e.g., + `\mathcal{L}\left\lbrace 1 \right\rbrace=1/p` has a pole at + `p=0`). A pole in `\bar{f}(p)` to the left of the origin is a + decreasing function of time (e.g., `\mathcal{L}\left\lbrace + e^{-t/2} \right\rbrace=1/(p+1/2)` has a pole at `p=-1/2`), and + a pole to the right of the origin leads to an increasing + function in time (e.g., `\mathcal{L}\left\lbrace t e^{t/4} + \right\rbrace = 1/(p-1/4)^2` has a pole at `p=1/4`). When + singularities occur off the real `p` axis, the time-domain + function is oscillatory. For example `\mathcal{L}\left\lbrace + \mathrm{J}_0(t) \right\rbrace=1/\sqrt{p^2+1}` has a branch cut + starting at `p=j=\sqrt{-1}` and is a decaying oscillatory + function, This range of behaviors is illustrated in Duffy [3] + Figure 4.10.4, p. 228. + + In general as `p \rightarrow \infty` `t \rightarrow 0` and + vice-versa. All numerical inverse Laplace transform methods + require their abscissa to shift closer to the origin for + larger times. If the abscissa shift left of the rightmost + singularity in the Laplace domain, the answer will be + completely wrong (the effect of singularities to the right of + the Bromwich contour are not included in the results). + + For example, the following exponentially growing function has + a pole at `p=3`: + + .. math :: + + \bar{f}(p)=\frac{1}{p^2-9} + + .. math :: + + f(t)=\frac{1}{3}\sinh 3t + + >>> mp.dps = 15 + >>> mp.pretty = True + >>> fp = lambda p: 1/(p*p-9) + >>> ft = lambda t: sinh(3*t)/3 + >>> tt = [0.01,0.1,1.0,10.0] + >>> ft(tt[0]),invertlaplace(fp,tt[0],method='talbot') + (0.0100015000675014, 0.0100015000675014) + >>> ft(tt[1]),invertlaplace(fp,tt[1],method='talbot') + (0.101506764482381, 0.101506764482381) + >>> ft(tt[2]),invertlaplace(fp,tt[2],method='talbot') + (3.33929164246997, 3.33929164246997) + >>> ft(tt[3]),invertlaplace(fp,tt[3],method='talbot') + (1781079096920.74, -1.61331069624091e-14) + + **References** + + 1. [DLMF]_ section 1.14 (http://dlmf.nist.gov/1.14T4) + 2. [Cohen]_ + 3. [Duffy98]_ + + **Numerical Inverse Laplace Transform Reviews** + + 1. [Bellman]_ + 2. [Davies79]_ + 3. [Duffy93]_ + 4. [Kuhlman]_ + + """ + + rule = kwargs.get('method', 'cohen') + if type(rule) is str: + lrule = rule.lower() + if lrule == 'talbot': + rule = ctx._fixed_talbot + elif lrule == 'stehfest': + rule = ctx._stehfest + elif lrule == 'dehoog': + rule = ctx._de_hoog + elif rule == 'cohen': + rule = ctx._cohen + else: + raise ValueError("unknown invlap algorithm: %s" % rule) + else: + rule = rule(ctx) + + # determine the vector of Laplace-space parameter + # needed for the requested method and desired time + rule.calc_laplace_parameter(t, **kwargs) + + # compute the Laplace-space function evalutations + # at the required abscissa. + fp = [f(p) for p in rule.p] + + # compute the time-domain solution from the + # Laplace-space function evaluations + return rule.calc_time_domain_solution(fp, t) + + # shortcuts for the above function for specific methods + def invlaptalbot(ctx, *args, **kwargs): + kwargs['method'] = 'talbot' + return ctx.invertlaplace(*args, **kwargs) + + def invlapstehfest(ctx, *args, **kwargs): + kwargs['method'] = 'stehfest' + return ctx.invertlaplace(*args, **kwargs) + + def invlapdehoog(ctx, *args, **kwargs): + kwargs['method'] = 'dehoog' + return ctx.invertlaplace(*args, **kwargs) + + def invlapcohen(ctx, *args, **kwargs): + kwargs['method'] = 'cohen' + return ctx.invertlaplace(*args, **kwargs) diff --git a/mpmath/calculus/odes.py b/mpmath/calculus/odes.py new file mode 100644 index 0000000..73b3a57 --- /dev/null +++ b/mpmath/calculus/odes.py @@ -0,0 +1,285 @@ +from bisect import bisect + + +class ODEMethods: + pass + +def ode_taylor(ctx, derivs, x0, y0, tol_prec, n): + h = tol = ctx.ldexp(1, -tol_prec) + dim = len(y0) + xs = [x0] + ys = [y0] + x = x0 + y = y0 + orig = ctx.prec + try: + ctx.prec = orig*(1+n) + # Use n steps with Euler's method to get + # evaluation points for derivatives + for i in range(n): + fxy = derivs(x, y) + y = [y[i]+h*fxy[i] for i in range(len(y))] + x += h + xs.append(x) + ys.append(y) + # Compute derivatives + ser = [[] for d in range(dim)] + for j in range(n+1): + s = [0]*dim + b = (-1) ** (j & 1) + k = 1 + for i in range(j+1): + for d in range(dim): + s[d] += b * ys[i][d] + b = (b * (j-k+1)) // (-k) + k += 1 + scale = h**(-j) / ctx.fac(j) + for d in range(dim): + s[d] = s[d] * scale + ser[d].append(s[d]) + finally: + ctx.prec = orig + # Estimate radius for which we can get full accuracy. + # XXX: do this right for zeros + radius = ctx.one + for ts in ser: + if ts[-1]: + radius = min(radius, ctx.nthroot(tol/abs(ts[-1]), n)) + radius /= 2 # XXX + return ser, x0+radius + +def odefun(ctx, F, x0, y0, tol=None, degree=None, method='taylor', verbose=False): + r""" + Returns a function `y(x) = [y_0(x), y_1(x), \ldots, y_n(x)]` + that is a numerical solution of the `n+1`-dimensional first-order + ordinary differential equation (ODE) system + + .. math :: + + y_0'(x) = F_0(x, [y_0(x), y_1(x), \ldots, y_n(x)]) + + y_1'(x) = F_1(x, [y_0(x), y_1(x), \ldots, y_n(x)]) + + \vdots + + y_n'(x) = F_n(x, [y_0(x), y_1(x), \ldots, y_n(x)]) + + The derivatives are specified by the vector-valued function + *F* that evaluates + `[y_0', \ldots, y_n'] = F(x, [y_0, \ldots, y_n])`. + The initial point `x_0` is specified by the scalar argument *x0*, + and the initial value `y(x_0) = [y_0(x_0), \ldots, y_n(x_0)]` is + specified by the vector argument *y0*. + + For convenience, if the system is one-dimensional, you may optionally + provide just a scalar value for *y0*. In this case, *F* should accept + a scalar *y* argument and return a scalar. The solution function + *y* will return scalar values instead of length-1 vectors. + + Evaluation of the solution function `y(x)` is permitted + for any `x \ge x_0`. + + A high-order ODE can be solved by transforming it into first-order + vector form. This transformation is described in standard texts + on ODEs. Examples will also be given below. + + **Options, speed and accuracy** + + By default, :func:`~mpmath.odefun` uses a high-order Taylor series + method. For reasonably well-behaved problems, the solution will + be fully accurate to within the working precision. Note that + *F* must be possible to evaluate to very high precision + for the generation of Taylor series to work. + + To get a faster but less accurate solution, you can set a large + value for *tol* (which defaults roughly to *eps*). If you just + want to plot the solution or perform a basic simulation, + *tol = 0.01* is likely sufficient. + + The *degree* argument controls the degree of the solver (with + *method='taylor'*, this is the degree of the Taylor series + expansion). A higher degree means that a longer step can be taken + before a new local solution must be generated from *F*, + meaning that fewer steps are required to get from `x_0` to a given + `x_1`. On the other hand, a higher degree also means that each + local solution becomes more expensive (i.e., more evaluations of + *F* are required per step, and at higher precision). + + The optimal setting therefore involves a tradeoff. Generally, + decreasing the *degree* for Taylor series is likely to give faster + solution at low precision, while increasing is likely to be better + at higher precision. + + The function + object returned by :func:`~mpmath.odefun` caches the solutions at all step + points and uses polynomial interpolation between step points. + Therefore, once `y(x_1)` has been evaluated for some `x_1`, + `y(x)` can be evaluated very quickly for any `x_0 \le x \le x_1`. + and continuing the evaluation up to `x_2 > x_1` is also fast. + + **Examples of first-order ODEs** + + We will solve the standard test problem `y'(x) = y(x), y(0) = 1` + which has explicit solution `y(x) = \exp(x)`:: + + >>> from mpmath import (atan, cos, exp, mp, mpf, nprint, odefun, + ... pi, sin, quad) + >>> mp.pretty = True + >>> f = odefun(lambda x, y: y, 0, 1) + >>> for x in [0, 1, 2.5]: + ... print((f(x), exp(x))) + ... + (1.0, 1.0) + (2.71828182845905, 2.71828182845905) + (12.1824939607035, 12.1824939607035) + + The solution with high precision:: + + >>> mp.dps = 50 + >>> f = odefun(lambda x, y: y, 0, 1) + >>> f(1) + 2.7182818284590452353602874713526624977572470937 + >>> exp(1) + 2.7182818284590452353602874713526624977572470937 + + Using the more general vectorized form, the test problem + can be input as (note that *f* returns a 1-element vector):: + + >>> mp.dps = 15 + >>> f = odefun(lambda x, y: [y[0]], 0, [1]) + >>> f(1) + [2.71828182845905] + + :func:`~mpmath.odefun` can solve nonlinear ODEs, which are generally + impossible (and at best difficult) to solve analytically. As + an example of a nonlinear ODE, we will solve `y'(x) = x \sin(y(x))` + for `y(0) = \pi/2`. An exact solution happens to be known + for this problem, and is given by + `y(x) = 2 \tan^{-1}\left(\exp\left(x^2/2\right)\right)`:: + + >>> f = odefun(lambda x, y: x*sin(y), 0, pi/2) + >>> for x in [2, 5, 10]: + ... print((f(x), 2*atan(exp(mpf(x)**2/2)))) + ... + (2.87255666284091, 2.87255666284091) + (3.14158520028345, 3.14158520028345) + (3.14159265358979, 3.14159265358979) + + If `F` is independent of `y`, an ODE can be solved using direct + integration. We can therefore obtain a reference solution with + :func:`~mpmath.quad`:: + + >>> f = lambda x: (1+x**2)/(1+x**3) + >>> g = odefun(lambda x, y: f(x), pi, 0) + >>> g(2*pi) + 0.72128263801696 + >>> quad(f, [pi, 2*pi]) + 0.72128263801696 + + **Examples of second-order ODEs** + + We will solve the harmonic oscillator equation `y''(x) + y(x) = 0`. + To do this, we introduce the helper functions `y_0 = y, y_1 = y_0'` + whereby the original equation can be written as `y_1' + y_0' = 0`. Put + together, we get the first-order, two-dimensional vector ODE + + .. math :: + + \begin{cases} + y_0' = y_1 \\ + y_1' = -y_0 + \end{cases} + + To get a well-defined IVP, we need two initial values. With + `y(0) = y_0(0) = 1` and `-y'(0) = y_1(0) = 0`, the problem will of + course be solved by `y(x) = y_0(x) = \cos(x)` and + `y'(x) = y_1(x) = -y_0(x) = -\sin(x)`. We check this:: + + >>> f = odefun(lambda x, y: [y[1], -y[0]], 0, [1, 0]) + >>> for x in [0, 1, 2.5, 10]: + ... nprint(f(x), 15) + ... nprint([cos(x), -sin(x)], 15) + ... print("---") + ... + [1.0, 0.0] + [1.0, 0.0] + --- + [0.54030230586814, -0.841470984807897] + [0.54030230586814, -0.841470984807897] + --- + [-0.801143615546934, -0.598472144103957] + [-0.801143615546934, -0.598472144103957] + --- + [-0.839071529076452, 0.54402111088937] + [-0.839071529076452, 0.54402111088937] + --- + + Note that we get both the sine and the cosine solutions + simultaneously. + + **TODO** + + * Better automatic choice of degree and step size + * Make determination of Taylor series convergence radius + more robust + * Allow solution for `x < x_0` + * Allow solution for complex `x` + * Test for difficult (ill-conditioned) problems + * Implement Runge-Kutta and other algorithms + + """ + if tol: + tol_prec = int(-ctx.log(tol, 2))+10 + else: + tol_prec = ctx.prec+10 + degree = degree or (3 + int(3*ctx.dps/2.)) + workprec = ctx.prec + 40 + try: + len(y0) + return_vector = True + except TypeError: + F_ = F + F = lambda x, y: [F_(x, y[0])] + y0 = [y0] + return_vector = False + ser, xb = ode_taylor(ctx, F, x0, y0, tol_prec, degree) + series_boundaries = [x0, xb] + series_data = [(ser, x0, xb)] + # We will be working with vectors of Taylor series + def mpolyval(ser, a): + return [ctx.polyval(s, a) for s in ser] + # Find nearest expansion point; compute if necessary + def get_series(x): + if x < x0: + raise ValueError + n = bisect(series_boundaries, x) + if n < len(series_boundaries): + return series_data[n-1] + while 1: + ser, xa, xb = series_data[-1] + if verbose: + print("Computing Taylor series for [%f, %f]" % (xa, xb)) + y = mpolyval(ser, xb-xa) + xa = xb + ser, xb = ode_taylor(ctx, F, xb, y, tol_prec, degree) + series_boundaries.append(xb) + series_data.append((ser, xa, xb)) + if x <= xb: + return series_data[-1] + # Evaluation function + def interpolant(x): + x = ctx.convert(x) + orig = ctx.prec + try: + ctx.prec = workprec + ser, xa, xb = get_series(x) + y = mpolyval(ser, x-xa) + finally: + ctx.prec = orig + if return_vector: + return [+yk for yk in y] + else: + return +y[0] + return interpolant + +ODEMethods.odefun = odefun diff --git a/mpmath/calculus/optimization.py b/mpmath/calculus/optimization.py new file mode 100644 index 0000000..139a63b --- /dev/null +++ b/mpmath/calculus/optimization.py @@ -0,0 +1,1311 @@ +from copy import copy + + +class OptimizationMethods: + def __init__(ctx): + pass + +############## +# 1D-SOLVERS # +############## + +class Newton: + """ + 1d-solver generating pairs of approximative root and error. + + Needs starting points x0 close to the root. + + Pro: + + * converges fast + * sometimes more robust than secant with bad second starting point + + Contra: + + * converges slowly for multiple roots + * needs first derivative + * 2 function evaluations per iteration + """ + maxsteps = 20 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + if len(x0) == 1: + self.x0 = x0[0] + else: + raise ValueError('expected 1 starting point, got %i' % len(x0)) + self.f = f + if 'df' not in kwargs: + def df(x): + return self.ctx.diff(f, x) + else: + df = kwargs['df'] + self.df = df + + def __iter__(self): + f = self.f + df = self.df + x0 = self.x0 + while True: + x1 = x0 - f(x0) / df(x0) + error = abs(x1 - x0) + x0 = x1 + yield (x1, error) + +class Secant: + """ + 1d-solver generating pairs of approximative root and error. + + Needs starting points x0 and x1 close to the root. + x1 defaults to x0 + 0.25. + + Pro: + + * converges fast + + Contra: + + * converges slowly for multiple roots + """ + maxsteps = 30 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + if len(x0) == 1: + self.x0 = x0[0] + self.x1 = self.x0 + 0.25 + elif len(x0) == 2: + self.x0 = x0[0] + self.x1 = x0[1] + else: + raise ValueError('expected 1 or 2 starting points, got %i' % len(x0)) + self.f = f + + def __iter__(self): + f = self.f + x0 = self.x0 + x1 = self.x1 + f0 = f(x0) + while True: + f1 = f(x1) + l = x1 - x0 + if not l: + break + s = (f1 - f0) / l + if not s: + break + x0, x1 = x1, x1 - f1/s + f0 = f1 + yield x1, abs(l) + +class MNewton: + """ + 1d-solver generating pairs of approximative root and error. + + Needs starting point x0 close to the root. + Uses modified Newton's method that converges fast regardless of the + multiplicity of the root. + + Pro: + + * converges fast for multiple roots + + Contra: + + * needs first and second derivative of f + * 3 function evaluations per iteration + """ + maxsteps = 20 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + if not len(x0) == 1: + raise ValueError('expected 1 starting point, got %i' % len(x0)) + self.x0 = x0[0] + self.f = f + if 'df' not in kwargs: + def df(x): + return self.ctx.diff(f, x) + else: + df = kwargs['df'] + self.df = df + if 'd2f' not in kwargs: + def d2f(x): + return self.ctx.diff(df, x) + else: + d2f = kwargs['df'] + self.d2f = d2f + + def __iter__(self): + x = self.x0 + f = self.f + df = self.df + d2f = self.d2f + while True: + prevx = x + fx = f(x) + if fx == 0: + break + dfx = df(x) + d2fx = d2f(x) + # x = x - F(x)/F'(x) with F(x) = f(x)/f'(x) + x -= fx / (dfx - fx * d2fx / dfx) + error = abs(x - prevx) + yield x, error + +class Halley: + """ + 1d-solver generating pairs of approximative root and error. + + Needs a starting point x0 close to the root. + Uses Halley's method with cubic convergence rate. + + Pro: + + * converges even faster the Newton's method + * useful when computing with *many* digits + + Contra: + + * needs first and second derivative of f + * 3 function evaluations per iteration + * converges slowly for multiple roots + """ + + maxsteps = 20 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + if not len(x0) == 1: + raise ValueError('expected 1 starting point, got %i' % len(x0)) + self.x0 = x0[0] + self.f = f + if 'df' not in kwargs: + def df(x): + return self.ctx.diff(f, x) + else: + df = kwargs['df'] + self.df = df + if 'd2f' not in kwargs: + def d2f(x): + return self.ctx.diff(df, x) + else: + d2f = kwargs['df'] + self.d2f = d2f + + def __iter__(self): + x = self.x0 + f = self.f + df = self.df + d2f = self.d2f + while True: + prevx = x + fx = f(x) + dfx = df(x) + d2fx = d2f(x) + x -= 2*fx*dfx / (2*dfx**2 - fx*d2fx) + error = abs(x - prevx) + yield x, error + +class Muller: + """ + 1d-solver generating pairs of approximative root and error. + + Needs starting points x0, x1 and x2 close to the root. + x1 defaults to x0 + 0.25; x2 to x1 + 0.25. + Uses Muller's method that converges towards complex roots. + + Pro: + + * converges fast (somewhat faster than secant) + * can find complex roots + + Contra: + + * converges slowly for multiple roots + * may have complex values for real starting points and real roots + + http://en.wikipedia.org/wiki/Muller's_method + """ + maxsteps = 30 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + if len(x0) == 1: + self.x0 = x0[0] + self.x1 = self.x0 + 0.25 + self.x2 = self.x1 + 0.25 + elif len(x0) == 2: + self.x0 = x0[0] + self.x1 = x0[1] + self.x2 = self.x1 + 0.25 + elif len(x0) == 3: + self.x0 = x0[0] + self.x1 = x0[1] + self.x2 = x0[2] + else: + raise ValueError('expected 1, 2 or 3 starting points, got %i' + % len(x0)) + self.f = f + self.verbose = kwargs['verbose'] + + def __iter__(self): + f = self.f + x0 = self.x0 + x1 = self.x1 + x2 = self.x2 + fx0 = f(x0) + fx1 = f(x1) + fx2 = f(x2) + while True: + # TODO: maybe refactoring with function for divided differences + # calculate divided differences + fx2x1 = (fx1 - fx2) / (x1 - x2) + fx2x0 = (fx0 - fx2) / (x0 - x2) + fx1x0 = (fx0 - fx1) / (x0 - x1) + w = fx2x1 + fx2x0 - fx1x0 + fx2x1x0 = (fx1x0 - fx2x1) / (x0 - x2) + if w == 0 and fx2x1x0 == 0: + if self.verbose: + print('canceled with') + print('x0 =', x0, ', x1 =', x1, 'and x2 =', x2) + break + x0 = x1 + fx0 = fx1 + x1 = x2 + fx1 = fx2 + # denominator should be as large as possible => choose sign + r = self.ctx.sqrt(w**2 - 4*fx2*fx2x1x0) + if abs(w - r) > abs(w + r): + r = -r + x2 -= 2*fx2 / (w + r) + fx2 = f(x2) + error = abs(x2 - x1) + yield x2, error + +class Bisection: + """ + 1d-solver generating pairs of approximative root and error. + + Uses bisection method to find a root of f in [a, b]. + Might fail for multiple roots (needs sign change). + + Pro: + + * robust and reliable + + Contra: + + * converges slowly + * needs sign change + """ + maxsteps = 100 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + if len(x0) != 2: + raise ValueError('expected interval of 2 points, got %i' % len(x0)) + self.f = f + self.a, self.b = x0 + self.maxsteps = 2*ctx.prec + ctx.ceil(ctx.log2(abs(self.a - self.b))) + + def __iter__(self): + ctx = self.ctx + f = self.f + a = self.a + b = self.b + l = b - a + fa = f(a) + fb = f(b) + + if fa*fb > 0: + raise ValueError("Function must have opposite signs at interval boundaries.") + + while True: + m = ctx.ldexp(a + b, -1) + fm = f(m) + sign = fm * fb + if sign < 0: + a = m + elif sign > 0: + b = m + fb = fm + else: + yield m, ctx.zero + l /= 2 + yield (a + b)/2, abs(l) + +def _getm(method): + """ + Return a function to calculate m for Illinois-like methods. + """ + if method == 'illinois': + def getm(fz, fb): + return 0.5 + elif method == 'pegasus': + def getm(fz, fb): + return fb/(fb + fz) + elif method == 'anderson': + def getm(fz, fb): + m = 1 - fz/fb + if m > 0: + return m + else: + return 0.5 + else: + raise ValueError("method '%s' not recognized" % method) + return getm + +class Illinois: + """ + 1d-solver generating pairs of approximative root and error. + + Uses Illinois method or similar to find a root of f in [a, b]. + Might fail for multiple roots (needs sign change). + Combines bisect with secant (improved regula falsi). + + The only difference between the methods is the scaling factor m, which is + used to ensure convergence (you can choose one using the 'method' keyword): + + Illinois method ('illinois'): + m = 0.5 + + Pegasus method ('pegasus'): + m = fb/(fb + fz) + + Anderson-Bjoerk method ('anderson'): + m = 1 - fz/fb if positive else 0.5 + + Pro: + + * converges very fast + + Contra: + + * has problems with multiple roots + * needs sign change + """ + maxsteps = 30 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + if len(x0) != 2: + raise ValueError('expected interval of 2 points, got %i' % len(x0)) + self.a = x0[0] + self.b = x0[1] + self.f = f + self.tol = kwargs['tol'] + self.verbose = kwargs['verbose'] + self.method = kwargs.get('method', 'illinois') + self.getm = _getm(self.method) + if self.verbose: + print('using %s method' % self.method) + + def __iter__(self): + method = self.method + f = self.f + a = self.a + b = self.b + fa = f(a) + fb = f(b) + m = None + while True: + l = b - a + if l == 0: + break + s = (fb - fa) / l + z = a - fa/s + fz = f(z) + if abs(fz) < self.tol: + # TODO: better condition (when f is very flat) + if self.verbose: + print('canceled with z =', z) + yield z, l + break + if fz * fb < 0: # root in [z, b] + a = b + fa = fb + b = z + fb = fz + else: # root in [a, z] + m = self.getm(fz, fb) + b = z + fb = fz + fa = m*fa # scale down to ensure convergence + if self.verbose and m and not method == 'illinois': + print('m:', m) + yield (a + b)/2, abs(l) + +def Pegasus(*args, **kwargs): + """ + 1d-solver generating pairs of approximative root and error. + + Uses Pegasus method to find a root of f in [a, b]. + Wrapper for illinois to use method='pegasus'. + """ + kwargs['method'] = 'pegasus' + return Illinois(*args, **kwargs) + +def Anderson(*args, **kwargs): + """ + 1d-solver generating pairs of approximative root and error. + + Uses Anderson-Bjoerk method to find a root of f in [a, b]. + Wrapper for illinois to use method='anderson'. + """ + kwargs['method'] = 'anderson' + return Illinois(*args, **kwargs) + +# TODO: check whether it's possible to combine it with Illinois stuff +class Ridder: + """ + 1d-solver generating pairs of approximative root and error. + + Ridders' method to find a root of f in [a, b]. + Is told to perform as well as Brent's method while being simpler. + + Pro: + + * very fast + * simpler than Brent's method + + Contra: + + * two function evaluations per step + * has problems with multiple roots + * needs sign change + + http://en.wikipedia.org/wiki/Ridders'_method + """ + maxsteps = 30 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + self.f = f + if len(x0) != 2: + raise ValueError('expected interval of 2 points, got %i' % len(x0)) + self.x1 = x0[0] + self.x2 = x0[1] + self.verbose = kwargs['verbose'] + self.tol = kwargs['tol'] + + def __iter__(self): + ctx = self.ctx + f = self.f + x1 = self.x1 + fx1 = f(x1) + x2 = self.x2 + fx2 = f(x2) + while True: + x3 = 0.5*(x1 + x2) + fx3 = f(x3) + x4 = x3 + (x3 - x1) * ctx.sign(fx1 - fx2) * fx3 / ctx.sqrt(fx3**2 - fx1*fx2) + fx4 = f(x4) + if abs(fx4) < self.tol: + # TODO: better condition (when f is very flat) + if self.verbose: + print('canceled with f(x4) =', fx4) + yield x4, abs(x1 - x2) + break + if fx3 * fx4 < 0: # root in [x4, x3] + x1, x2 = x4, x3 + fx1, fx2 = fx4, fx3 + elif fx4 * fx1 < 0: # in [x1, x4] + x2 = x4 + fx2 = fx4 + else: # in [x4, x2] + x1 = x4 + fx1 = fx4 + error = abs(x1 - x2) + yield (x1 + x2)/2, error + +class ANewton: + """ + EXPERIMENTAL 1d-solver generating pairs of approximative root and error. + + Uses Newton's method modified to use Steffensens method when convergence is + slow. (I.e. for multiple roots.) + """ + maxsteps = 20 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + if not len(x0) == 1: + raise ValueError('expected 1 starting point, got %i' % len(x0)) + self.x0 = x0[0] + self.f = f + if 'df' not in kwargs: + def df(x): + return self.ctx.diff(f, x) + else: + df = kwargs['df'] + self.df = df + def phi(x): + return x - f(x) / df(x) + self.phi = phi + self.verbose = kwargs['verbose'] + + def __iter__(self): + x0 = self.x0 + f = self.f + df = self.df + phi = self.phi + error = 0 + counter = 0 + while True: + prevx = x0 + try: + x0 = phi(x0) + except ZeroDivisionError: + if self.verbose: + print('ZeroDivisionError: canceled with x =', x0) + break + preverror = error + error = abs(prevx - x0) + # TODO: decide not to use convergence acceleration + if error and abs(error - preverror) / error < 1: + if self.verbose: + print('converging slowly') + counter += 1 + if counter >= 3: + # accelerate convergence + phi = steffensen(phi) + counter = 0 + if self.verbose: + print('accelerating convergence') + yield x0, error + +class Brent: + """ + 1d-solver generating pairs of approximative root and error. + + Uses Brent's method to find a root of f in [a, b]. It combines + Bisection, the Secant method, and Inverse Quadratic Interpolation (IQI) + for robust and superlinear convergence. + + Pro: + * Guaranteed to converge if a root is bracketed (like Bisection). + * Can converge much faster than Bisection on smooth functions. + + Contra: + * Needs an initial sign-changing bracket. + + http://en.wikipedia.org/wiki/Brent%27s_method + """ + maxsteps = 100 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + if len(x0) != 2: + raise ValueError('expected interval of 2 points, got %i' % len(x0)) + + self.f = f + self.a, self.b = x0 + self.tol = kwargs['tol'] + + def __iter__(self): + ctx = self.ctx + f = self.f + + a = self.a + b = self.b + fa = f(a) + fb = f(b) + + if fa*fb > 0: + raise ValueError("Function must have opposite signs at interval boundaries.") + + if abs(fa) < abs(fb): + a, b = b, a + fa, fb = fb, fa + + c = a + fc = fa + d = c # will be assigned properly on the first interation + mflag = True + + while True: + + yield b, abs(b - a) + + if fa != fc and fb != fc: + # Inverse Quadratic Interpolation formula + s = (a * fb * fc) / ((fa - fb) * (fa - fc)) + \ + (b * fa * fc) / ((fb - fa) * (fb - fc)) + \ + (c * fa * fb) / ((fc - fa) * (fc - fb)) + else: + # standard Secant + s = b - fb * (b - a) / (fb - fa) + + # Define conditions matching Brent's bounds + bound_lower = (3 * a + b) / 4 + is_between = (bound_lower <= s <= b) or (b <= s <= bound_lower) + + delta = ctx.eps * max(ctx.one, ctx.fabs(b)) + + cond1 = not is_between + cond2 = mflag and (abs(s - b) >= abs(b - c) / 2) + cond3 = (not mflag) and (abs(s - b) >= abs(c - d) / 2) + cond4 = mflag and (abs(b - c) < delta) + cond5 = (not mflag) and (abs(c - d) < delta) + + if cond1 or cond2 or cond3 or cond4 or cond5: + s = ctx.ldexp(a + b, -1) + mflag = True + else: + mflag = False + + fs = f(s) + + d = c + c = b + fc = fb + + if fa*fs < 0: + b = s + fb = fs + else: + a = s + fa = fs + + if abs(fa) < abs(fb): + a, b = b, a + fa, fb = fb, fa + +class ModAB: + """ + 1d-solver generating pairs of approximative root and error. + + Uses the Modified Anderson-Björck (modAB) hybrid method to find + a root of f in [a, b]. It dynamically switches between Bisection + and False Position (Secant) while correcting for stagnant endpoints. + + Pro: + * Robust and guaranteed to converge (like Bisection) + * Fast convergence on smooth functions (like Secant) + + Contra: + * Needs an initial sign change bracket + + https://doi.org/10.3390/a19050332 + """ + maxsteps = 200 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + if len(x0) != 2: + raise ValueError('expected interval of 2 points, got %i' % len(x0)) + + self.f = f + + # Enforce ordering: self.a as lower bound, self.b as upper bound + self.a, self.b = x0 + if self.a > self.b: + self.a, self.b = self.b, self.a + + def __iter__(self): + ctx = self.ctx + f = self.f + + a = self.a + b = self.b + fa = f(a) + fb = f(b) + + # Check for initial bracketing + if fa*fb > 0: + raise ValueError("Function must have opposite signs at interval boundaries.") + + bisection = True + side = 0 # -1 for left moved last, 1 for right, 0 for none + threshold = b - a + C = ctx.mpf(16) # Safety factor threshold scaling constant + + while True: + if bisection: + x3 = ctx.ldexp(a + b, -1) + else: + x3 = (a * fb - b * fa) / (fb - fa) + + # Yield the current best guess and the remaining interval length (error) + yield x3, abs(b - a) + + # Evaluate function or handle out-of-bounds secant calculations + if bisection: + fx3 = f(x3) + ym = ctx.ldexp(fa + fb, -1) + + # Check linearity to see if we can switch to secant + r = ctx.one - ctx.fabs(ym / (fb - fa)) # Symmetry factor + k = r * r # Deviation factor + + if ctx.fabs(ym - fx3) < k * (ctx.fabs(fx3) + ctx.fabs(ym)): + bisection = False + threshold = (b - a) * C + else: + # Clamp secant point safely within the bounds to handle floating-point rounding + if x3 <= a: + x3, fx3 = a, fa + elif x3 >= b: + x3, fx3 = b, fb + else: + fx3 = f(x3) + + threshold *= 0.5 + + # Check for exact root convergence + if fx3 == ctx.zero: + yield x3, ctx.zero + + # Update the interval and apply Anderson-Björck adjustments + if fa*fx3 > 0: + if side == 1: + m = ctx.one - (fx3 / fa) + fb *= ctx.ldexp(ctx.one, -1) if m <= 0 else m + elif not bisection: + side = 1 + a, fa = x3, fx3 + else: + if side == -1: + m = ctx.one - (fx3 / fb) + fa *= ctx.ldexp(ctx.one, -1) if m <= 0 else m + elif not bisection: + side = -1 + b, fb = x3, fx3 + + # Fallback check: If progress is too slow, force a bisection step next time + if (b - a) > threshold: + bisection = True + side = 0 + +############################ +# MULTIDIMENSIONAL SOLVERS # +############################ + +def jacobian(ctx, f, x): + """ + Calculate the Jacobian matrix of a function at the point x0. + + This is the first derivative of a vectorial function: + + f : R^m -> R^n with m >= n + """ + x = ctx.matrix(x) + h = ctx.sqrt(ctx.eps) + fx = ctx.matrix(f(*x)) + m = len(fx) + n = len(x) + J = ctx.matrix(m, n) + for j in range(n): + xj = x.copy() + xj[j] += h + Jj = (ctx.matrix(f(*xj)) - fx) / h + for i in range(m): + J[i,j] = Jj[i] + return J + +# TODO: test with user-specified jacobian matrix +class MDNewton: + """ + Find the root of a vector function numerically using Newton's method. + + f is a vector function representing a nonlinear equation system. + + x0 is the starting point close to the root. + + J is a function returning the Jacobian matrix for a point. + + Supports overdetermined systems. + + Use the 'norm' keyword to specify which norm to use. Defaults to max-norm. + The function to calculate the Jacobian matrix can be given using the + keyword 'J'. Otherwise it will be calculated numerically. + + Please note that this method converges only locally. Especially for high- + dimensional systems it is not trivial to find a good starting point being + close enough to the root. + + It is recommended to use a faster, low-precision solver from SciPy [1] or + OpenOpt [2] to get an initial guess. Afterwards you can use this method for + root-polishing to any precision. + + [1] http://scipy.org + + [2] http://openopt.org/Welcome + """ + maxsteps = 10 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + self.f = f + if isinstance(x0, (tuple, list)): + x0 = ctx.matrix(x0) + assert x0.cols == 1, 'need a vector' + self.x0 = x0 + if 'J' in kwargs: + self.J = kwargs['J'] + else: + def J(*x): + return ctx.jacobian(f, x) + self.J = J + self.norm = kwargs['norm'] + self.verbose = kwargs['verbose'] + + def __iter__(self): + f = self.f + x0 = self.x0 + norm = self.norm + J = self.J + fx = self.ctx.matrix(f(*x0)) + fxnorm = norm(fx) + cancel = False + while not cancel: + # get direction of descent + fxn = -fx + Jx = J(*x0) + s = self.ctx.lu_solve(Jx, fxn) + if self.verbose: + print('Jx:') + print(Jx) + print('s:', s) + # damping step size TODO: better strategy (hard task) + l = self.ctx.one + x1 = x0 + s + while True: + if x1 == x0: + if self.verbose: + print("canceled, won't get more exact") + cancel = True + break + fx = self.ctx.matrix(f(*x1)) + newnorm = norm(fx) + if newnorm < fxnorm: + # new x accepted + fxnorm = newnorm + x0 = x1 + break + l /= 2 + x1 = x0 + l*s + yield (x0, fxnorm) + +############# +# UTILITIES # +############# + +str2solver = {'newton':Newton, 'secant':Secant, 'mnewton':MNewton, + 'halley':Halley, 'muller':Muller, 'bisect':Bisection, + 'illinois':Illinois, 'pegasus':Pegasus, 'anderson':Anderson, + 'ridder':Ridder, 'anewton':ANewton, 'mdnewton':MDNewton, 'modAB':ModAB, 'brent':Brent} + +def findroot(ctx, f, x0, solver='secant', tol=None, verbose=False, verify=True, **kwargs): + r""" + Find an approximate solution to `f(x) = 0`, using *x0* as starting point or + interval for *x*. + + Multidimensional overdetermined systems are supported. + You can specify them using a function or a list of functions. + + Mathematically speaking, this function returns `x` such that + `|f(x)|^2 \leq \mathrm{tol}` is true within the current working precision. + If the computed value does not meet this criterion, an exception is raised. + This exception can be disabled with *verify=False*. + + For interval arithmetic (``iv.findroot()``), please note that + the returned interval ``x`` is not guaranteed to contain `f(x)=0`! + It is only some `x` for which `|f(x)|^2 \leq \mathrm{tol}` certainly holds + regardless of numerical error. This may be improved in the future. + + **Arguments** + + *f* + one dimensional function + *x0* + starting point, several starting points or interval (depends on solver) + *tol* + the returned solution has an error smaller than this, with + the defailt value ``2**10`` times epsilon of working precision + *verbose* + print additional information for each iteration if true + *verify* + verify the solution and raise a ValueError if `|f(x)|^2 > \mathrm{tol}` + *solver* + a generator for *f* and *x0* returning approximative solution and error + *maxsteps* + the solver will cancel at least after that number of iterations + *df* + first derivative of *f* (used by some solvers) + *d2f* + second derivative of *f* (used by some solvers) + *multidimensional* + force multidimensional solving + *J* + Jacobian matrix of *f* (used by multidimensional solvers) + *norm* + used vector norm (used by multidimensional solvers) + + solver has to be callable with ``(f, x0, **kwargs)`` and return an generator + yielding pairs of approximative solution and estimated error (which is + expected to be positive). + You can use the following string aliases: + 'secant', 'mnewton', 'halley', 'muller', 'illinois', 'pegasus', 'anderson', + 'ridder', 'anewton', 'bisect', 'modAB' + + See mpmath.calculus.optimization for their documentation. + + **Examples** + + The function :func:`~mpmath.findroot` locates a root of a given function using the + secant method by default. A simple example use of the secant method is to + compute `\pi` as the root of `\sin x` closest to `x_0 = 3`:: + + >>> from mpmath import (diff, gamma, findroot, sin, zeta, exp, log, + ... lambertw, mp, j) + >>> mp.dps = 30 + >>> mp.pretty = True + >>> findroot(sin, 3) + 3.14159265358979323846264338328 + + The secant method can be used to find complex roots of analytic functions, + although it must in that case generally be given a nonreal starting value + (or else it will never leave the real line):: + + >>> mp.dps = 15 + >>> findroot(lambda x: x**3 + 2*x + 1, j) + (0.226698825758202 + 1.46771150871022j) + + A nice application is to compute nontrivial roots of the Riemann zeta + function with many digits (good initial values are needed for convergence):: + + >>> mp.dps = 30 + >>> findroot(zeta, 0.5+14j) + (0.5 + 14.1347251417346937904572519836j) + + The secant method can also be used as an optimization algorithm, by passing + it a derivative of a function. The following example locates the positive + minimum of the gamma function:: + + >>> mp.dps = 20 + >>> findroot(lambda x: diff(gamma, x), 1) + 1.4616321449683623413 + + Finally, a useful application is to compute inverse functions, such as the + Lambert W function which is the inverse of `w e^w`, given the first + term of the solution's asymptotic expansion as the initial value. In basic + cases, this gives identical results to mpmath's built-in ``lambertw`` + function:: + + >>> def lambert(x): + ... return findroot(lambda w: w*exp(w) - x, log(1+x)) + ... + >>> mp.dps = 15 + >>> lambert(1) + 0.567143290409784 + >>> lambertw(1) + 0.567143290409784 + >>> lambert(1000) + 5.2496028524016 + >>> lambertw(1000) + 5.2496028524016 + + Multidimensional functions are also supported:: + + >>> f = [lambda x1, x2: x1**2 + x2, + ... lambda x1, x2: 5*x1**2 - 3*x1 + 2*x2 - 3] + >>> findroot(f, (0, 0)) + [-0.618033988749895] + [-0.381966011250105] + >>> findroot(f, (10, 10)) + [ 1.61803398874989] + [-2.61803398874989] + + You can verify this by solving the system manually. + + Please note that the following (more general) syntax also works:: + + >>> def f(x1, x2): + ... return x1**2 + x2, 5*x1**2 - 3*x1 + 2*x2 - 3 + ... + >>> findroot(f, (0, 0)) + [-0.618033988749895] + [-0.381966011250105] + + + **Multiple roots** + + For multiple roots all methods of the Newtonian family (including secant) + converge slowly. Consider this example:: + + >>> f = lambda x: (x - 1)**99 + >>> findroot(f, 0.9) + 0.918073542444929 + + Even for a very close starting point the secant method converges very + slowly. Use ``verbose=True`` to illustrate this. + + It is possible to modify Newton's method to make it converge regardless of + the root's multiplicity:: + + >>> findroot(f, -10, solver='mnewton') + 1.0 + + This variant uses the first and second derivative of the function, which is + not very efficient. + + Alternatively you can use an experimental Newtonian solver that keeps track + of the speed of convergence and accelerates it using Steffensen's method if + necessary:: + + >>> findroot(f, -10, solver='anewton', verbose=True) + x: -9.88888888888888888889 + error: 0.111111111111111111111 + converging slowly + x: -9.77890011223344556678 + error: 0.10998877665544332211 + converging slowly + x: -9.67002233332199662166 + error: 0.108877778911448945119 + converging slowly + accelerating convergence + x: -9.5622443299551077669 + error: 0.107778003366888854764 + converging slowly + x: 0.99999999999999999214 + error: 10.562244329955107759 + x: 1.0 + error: 7.8598304758094664213e-18 + ZeroDivisionError: canceled with x = 1.0 + 1.0 + + **Complex roots** + + For complex roots it's recommended to use Muller's method as it converges + even for real starting points very fast:: + + >>> findroot(lambda x: x**4 + x + 1, (0, 1, 2), solver='muller') + (0.727136084491197 + 0.934099289460529j) + + + **Intersection methods** + + When you need to find a root in a known interval, it's highly recommended to + use an intersection-based solver like ```'modAB'``` or ``'anderson'`` or ``'ridder'``. + Usually they converge faster and more reliable. They have however problems + with multiple roots and usually need a sign change to find a root:: + + >>> findroot(lambda x: x**3, (-1, 1), solver='anderson') + 0.0 + + Be careful with symmetric functions:: + + >>> findroot(lambda x: x**2, (-1, 1), solver='anderson') + Traceback (most recent call last): + ... + ZeroDivisionError + + It fails even for better starting points, because there is no sign change:: + + >>> findroot(lambda x: x**2, (-1, .5), solver='anderson') + Traceback (most recent call last): + ... + ValueError: Could not find root within given tolerance. (1.0 > 2.16840434497100886801e-19) + Try another starting point or tweak arguments. + + """ + prec = ctx.prec + trap_complex = getattr(ctx, 'trap_complex', None) + try: + ctx.prec += 20 + + # initialize arguments + if tol is None: + tol = ctx.eps * 2**10 + + kwargs['verbose'] = kwargs.get('verbose', verbose) + + if 'd1f' in kwargs: + kwargs['df'] = kwargs['d1f'] + + kwargs['tol'] = tol + if isinstance(x0, (list, tuple)): + x0 = [ctx.convert(x) for x in x0] + else: + x0 = [ctx.convert(x0)] + + if isinstance(solver, str): + try: + solver = str2solver[solver] + except KeyError: + raise ValueError('could not recognize solver') + + # accept list of functions + if isinstance(f, (list, tuple)): + f2 = copy(f) + def tmp(*args): + return [fn(*args) for fn in f2] + f = tmp + + # detect multidimensional functions + try: + fx = f(*x0) + multidimensional = isinstance(fx, (list, tuple, ctx.matrix)) + except TypeError: + fx = f(x0[0]) + multidimensional = False + if 'multidimensional' in kwargs: + multidimensional = kwargs['multidimensional'] + if multidimensional: + # only one multidimensional solver available at the moment + solver = MDNewton + if 'norm' not in kwargs: + norm = lambda x: ctx.norm(x, 'inf') + kwargs['norm'] = norm + else: + norm = kwargs['norm'] + ctx.trap_complex = True # MDNewton assume real input + else: + norm = abs + + # happily return starting point if it's a root + if norm(fx) == 0: + if multidimensional: + return ctx.matrix(x0) + else: + return x0[0] + + # use solver + iterations = solver(ctx, f, x0, **kwargs) + maxsteps = kwargs.get('maxsteps', iterations.maxsteps) + i = 0 + for x, error in iterations: + if verbose: + print('x: ', x) + print('error:', error) + i += 1 + if error < tol * max(1, norm(x)) or i >= maxsteps: + break + else: + if not i: + raise ValueError('Could not find root using the given solver.\n' + 'Try another starting point or tweak arguments.') + if not isinstance(x, (list, tuple, ctx.matrix)): + xl = [x] + else: + xl = x + if verify and norm(f(*xl))**2 > tol: # TODO: better condition? + raise ValueError('Could not find root within given tolerance. ' + '(%s > %s)\n' + 'Try another starting point or tweak arguments.' + % (norm(f(*xl))**2, tol)) + return x + finally: + ctx.prec = prec + if trap_complex is not None: + ctx.trap_complex = trap_complex + + +def multiplicity(ctx, f, root, tol=None, maxsteps=10, **kwargs): + """ + Return the multiplicity of a given root of f. + + Internally, numerical derivatives are used. This might be inefficient for + higher order derviatives. Due to this, ``multiplicity`` cancels after + evaluating 10 derivatives by default. You can be specify the n-th derivative + using the dnf keyword. + + >>> from mpmath import multiplicity, pi, sin + >>> multiplicity(lambda x: sin(x) - 1, pi/2) + 2 + + """ + if tol is None: + tol = ctx.eps ** 0.8 + kwargs['d0f'] = f + for i in range(maxsteps): + dfstr = 'd' + str(i) + 'f' + if dfstr in kwargs: + df = kwargs[dfstr] + else: + df = lambda x: ctx.diff(f, x, i) + if not abs(df(root)) < tol: + break + return i + +def steffensen(f): + """ + linear convergent function -> quadratic convergent function + + Steffensen's method for quadratic convergence of a linear converging + sequence. + Don not use it for higher rates of convergence. + It may even work for divergent sequences. + + Definition: + F(x) = (x*f(f(x)) - f(x)**2) / (f(f(x)) - 2*f(x) + x) + + Example + ....... + + You can use Steffensen's method to accelerate a fixpoint iteration of linear + (or less) convergence. + + x* is a fixpoint of the iteration x_{k+1} = phi(x_k) if x* = phi(x*). For + phi(x) = x**2 there are two fixpoints: 0 and 1. + + Let's try Steffensen's method: + + >>> f = lambda x: x**2 + >>> from mpmath import mp + >>> from mpmath.calculus.optimization import steffensen + >>> F = steffensen(f) + >>> for x in [0.5, 0.9, 2.0]: + ... fx = Fx = mp.mpf(x) + ... for i in range(9): + ... try: + ... fx = f(fx) + ... except OverflowError: + ... pass + ... try: + ... Fx = F(Fx) + ... except ZeroDivisionError: + ... pass + ... print(f'{fx:20g} {Fx:20g}') + 0.25 -0.5 + 0.0625 0.1 + 0.00390625 -0.0011236 + 1.52588e-05 1.41691e-09 + 2.32831e-10 -2.84465e-27 + 5.42101e-20 2.30189e-80 + 2.93874e-39 -1.21971e-239 + 8.63617e-78 1.81455e-717 + 7.45834e-155 -5.97459e-2151 + 0.81 1.02676 + 0.6561 1.00134 + 0.430467 1 + 0.185302 1 + 0.0343368 1 + 0.00117902 1 + 1.39008e-06 1 + 1.93233e-12 1 + 3.73392e-24 1 + 4 1.6 + 16 1.2962 + 256 1.10194 + 65536 1.01659 + 4.29497e+09 1.00053 + 1.84467e+19 1 + 3.40282e+38 1 + 1.15792e+77 1 + 1.34078e+154 1 + + Unmodified, the iteration converges only towards 0. Modified it converges + not only much faster, it converges even to the repelling fixpoint 1. + """ + def F(x): + fx = f(x) + ffx = f(fx) + return (x*ffx - fx**2) / (ffx - 2*fx + x) + return F + +OptimizationMethods.jacobian = jacobian +OptimizationMethods.findroot = findroot +OptimizationMethods.multiplicity = multiplicity diff --git a/mpmath/calculus/polynomials.py b/mpmath/calculus/polynomials.py new file mode 100644 index 0000000..94abc6c --- /dev/null +++ b/mpmath/calculus/polynomials.py @@ -0,0 +1,223 @@ +from .calculus import defun + + +#----------------------------------------------------------------------------# +# Polynomials # +#----------------------------------------------------------------------------# + +# XXX: extra precision +@defun +def polyval(ctx, coeffs, x, derivative=False, asc=True): + r""" + Given coefficients `[c_0, c_1, c_2, \ldots, c_n]` and a number `x`, + :func:`~mpmath.polyval` evaluates the polynomial + + .. math :: + + P(x) = c_0 + c_1 x + c_2 x^2 \ldots c_n x^n + + If *derivative=True* is set, :func:`~mpmath.polyval` simultaneously + evaluates `P(x)` with the derivative, `P'(x)`, and returns the + tuple `(P(x), P'(x))`. + + >>> from mpmath import mp, polyval + >>> mp.pretty = True + >>> polyval([2, 0, 3], 0.5) + 2.75 + >>> polyval([2, 0, 3], 0.5, derivative=True) + (2.75, 3.0) + + If *asc=False*, descending order of coefficients is used (the term + of largest degree - first). + + The coefficients and the evaluation point may be any combination + of real or complex numbers. + """ + if not coeffs: + return ctx.zero + if not asc: + coeffs = coeffs[::-1] + p = ctx.convert(coeffs[-1]) + q = ctx.zero + for c in reversed(coeffs[:-1]): + if derivative: + q = p + x*q + p = c + x*p + if derivative: + return p, q + else: + return p + +@defun +def polyroots(ctx, coeffs, maxsteps=50, cleanup=True, extraprec=10, + error=False, roots_init=None, asc=True): + """ + Computes all roots (real or complex) of a given polynomial. + + The roots are returned as a sorted list, where real roots appear first + followed by complex conjugate roots as adjacent elements. The polynomial + should be given as a list of coefficients, in the format used by + :func:`~mpmath.polyval`. The leading coefficient must be nonzero. + + With *error=True*, :func:`~mpmath.polyroots` returns a tuple *(roots, err)* + where *err* is an estimate of the maximum error among the computed roots. + + If *asc=False*, descending order of coefficients is used (the term + of largest degree - first). + + **Examples** + + Finding the three real roots of `x^3 - x^2 - 14x + 24`:: + + >>> from mpmath import mp, polyroots, nprint, sqrt, polyval + >>> mp.pretty = True + >>> nprint(polyroots([24,-14,-1,1]), 4) + [-4.0, 2.0, 3.0] + + Finding the two complex conjugate roots of `4x^2 + 3x + 2`, with an + error estimate:: + + >>> roots, err = polyroots([2,3,4], error=True) + >>> for r in roots: + ... print(r) + ... + (-0.375 + 0.59947894041409j) + (-0.375 - 0.59947894041409j) + >>> + >>> err + 2.22044604925031e-16 + >>> + >>> polyval([2,3,4], roots[0]) + (2.22044604925031e-16 + 0.0j) + >>> polyval([2,3,4], roots[1]) + (2.22044604925031e-16 + 0.0j) + + The following example computes all the 5th roots of unity; that is, + the roots of `x^5 - 1`:: + + >>> mp.dps = 20 + >>> for r in polyroots([-1, 0, 0, 0, 0, 1]): + ... print(r) + ... + 1.0 + (-0.8090169943749474241 + 0.58778525229247312917j) + (-0.8090169943749474241 - 0.58778525229247312917j) + (0.3090169943749474241 + 0.95105651629515357212j) + (0.3090169943749474241 - 0.95105651629515357212j) + + **Precision and conditioning** + + The roots are computed to the current working precision accuracy. If this + accuracy cannot be achieved in ``maxsteps`` steps, then a + ``NoConvergence`` exception is raised. The algorithm internally is using + the current working precision extended by ``extraprec``. If + ``NoConvergence`` was raised, that is caused either by not having enough + extra precision to achieve convergence (in which case increasing + ``extraprec`` should fix the problem) or too low ``maxsteps`` (in which + case increasing ``maxsteps`` should fix the problem), or a combination of + both. + + The user should always do a convergence study with regards to + ``extraprec`` to ensure accurate results. It is possible to get + convergence to a wrong answer with too low ``extraprec``. + + Provided there are no repeated roots, :func:`~mpmath.polyroots` can + typically compute all roots of an arbitrary polynomial to high precision:: + + >>> mp.dps = 60 + >>> for r in polyroots([1, 0, -10, 0, 1]): + ... print(r) + ... + -3.14626436994197234232913506571557044551247712918732870123249 + -0.317837245195782244725757617296174288373133378433432554879127 + 0.317837245195782244725757617296174288373133378433432554879127 + 3.14626436994197234232913506571557044551247712918732870123249 + >>> + >>> sqrt(3) + sqrt(2) + 3.14626436994197234232913506571557044551247712918732870123249 + >>> sqrt(3) - sqrt(2) + 0.317837245195782244725757617296174288373133378433432554879127 + + **Algorithm** + + :func:`~mpmath.polyroots` implements the Durand-Kerner method [1], which + uses complex arithmetic to locate all roots simultaneously. + The Durand-Kerner method can be viewed as approximately performing + simultaneous Newton iteration for all the roots. In particular, + the convergence to simple roots is quadratic, just like Newton's + method. + + Although all roots are internally calculated using complex arithmetic, any + root found to have an imaginary part smaller than the estimated numerical + error is truncated to a real number (small real parts are also chopped). + Real roots are placed first in the returned list, sorted by value. The + remaining complex roots are sorted by their real parts so that conjugate + roots end up next to each other. + + **References** + + 1. [Wikipedia]_ https://en.wikipedia.org/wiki/Durand-Kerner_method + + """ + if len(coeffs) <= 1: + if not coeffs or not coeffs[0]: + raise ValueError("Input to polyroots must not be the zero polynomial") + # Constant polynomial with no roots + return [] + if not asc: + coeffs = coeffs[::-1] + + orig = ctx.prec + tol = +ctx.eps + with ctx.extraprec(extraprec): + deg = len(coeffs) - 1 + # Must be monic + lead = ctx.convert(coeffs[-1]) + if lead == 1: + coeffs = [ctx.convert(c) for c in coeffs] + else: + coeffs = [c/lead for c in coeffs] + f = lambda x: ctx.polyval(coeffs, x) + if roots_init is None: + roots = [ctx.mpc((0.4+0.9j)**n) for n in range(deg)] + else: + roots = [None]*deg + deg_init = min(deg, len(roots_init)) + roots[:deg_init] = list(roots_init[:deg_init]) + roots[deg_init:] = [ctx.mpc((0.4+0.9j)**n) for n + in range(deg_init,deg)] + err = [ctx.one for n in range(deg)] + # Durand-Kerner iteration until convergence + for step in range(maxsteps): + if abs(max(err)) < tol: + break + for i in range(deg): + p = roots[i] + x = f(p) + for j in range(deg): + if i != j: + try: + x /= (p-roots[j]) + except ZeroDivisionError: + continue + roots[i] = p - x + err[i] = abs(x) + if abs(max(err)) >= tol: + raise ctx.NoConvergence("Didn't converge in maxsteps=%d steps." \ + % maxsteps) + # Remove small real or imaginary parts + if cleanup: + for i in range(deg): + if abs(roots[i]) < tol: + roots[i] = ctx.zero + elif abs(ctx._im(roots[i])) < tol: + roots[i] = roots[i].real + elif abs(ctx._re(roots[i])) < tol: + roots[i] = roots[i].imag * 1j + roots.sort(key=lambda x: (abs(ctx._im(x)), ctx._re(x))) + if error: + err = max(err) + err = max(err, ctx.ldexp(1, -orig+1)) + return [+r for r in roots], +err + else: + return [+r for r in roots] diff --git a/mpmath/calculus/quadrature.py b/mpmath/calculus/quadrature.py new file mode 100644 index 0000000..f06a5bf --- /dev/null +++ b/mpmath/calculus/quadrature.py @@ -0,0 +1,1110 @@ +import math + + +class QuadratureRule: + """ + Quadrature rules are implemented using this class, in order to + simplify the code and provide a common infrastructure + for tasks such as error estimation and node caching. + + You can implement a custom quadrature rule by subclassing + :class:`QuadratureRule` and implementing the appropriate + methods. The subclass can then be used by :func:`~mpmath.quad` by + passing it as the *method* argument. + + :class:`QuadratureRule` instances are supposed to be singletons. + """ + + def __init__(self, ctx): + self.ctx = ctx + self.transformed_cache = {} + + def clear(self): + """ + Delete cached node data. + """ + self.transformed_cache = {} + + def calc_nodes(self, degree, prec, verbose=False): + r""" + Compute nodes for the standard interval `[-1, 1]`. Subclasses + should probably implement only this method, and use + :func:`~mpmath.calculus.quadrature.QuadratureRule.get_nodes` + method to retrieve the nodes. + """ + raise NotImplementedError + + def get_nodes(self, a, b, degree, prec, verbose=False): + """ + Return nodes for given interval, degree and precision. The + nodes are retrieved from a cache if already computed; + otherwise they are computed by calling + :func:`~mpmath.calculus.quadrature.QuadratureRule.calc_nodes` + and are then cached. + + Subclasses should probably not implement this method, but just + implement :func:`~mpmath.calculus.quadrature.QuadratureRule.calc_nodes` + for the actual node computation. + """ + key = (a, b, degree, prec) + if key in self.transformed_cache: + return self.transformed_cache[key] + orig = self.ctx.prec + try: + self.ctx.prec = prec+20 + # Get nodes on standard interval + stdkey = (-1, 1, degree, prec) + if stdkey in self.transformed_cache: + nodes = self.transformed_cache[stdkey] + else: + nodes = self.calc_nodes(degree, prec, verbose) + self.transformed_cache[stdkey] = nodes + # Transform to general interval + nodes = self.transform_nodes(nodes, a, b, verbose) + if key not in self.transformed_cache: + self.transformed_cache[key] = nodes + finally: + self.ctx.prec = orig + return nodes + + def transform_nodes(self, nodes, a, b, verbose=False): + r""" + Rescale standardized nodes (for `[-1, 1]`) to a general + interval `[a, b]`. For a finite interval, a simple linear + change of variables is used. Otherwise, the following + transformations are used: + + .. math :: + + \lbrack a, \infty \rbrack : t = \frac{1}{x} + (a-1) + + \lbrack -\infty, b \rbrack : t = (b+1) - \frac{1}{x} + + \lbrack -\infty, \infty \rbrack : t = \frac{x}{\sqrt{1-x^2}} + + """ + ctx = self.ctx + a = ctx.convert(a) + b = ctx.convert(b) + one = ctx.one + if (a, b) == (-one, one): + return nodes + half = ctx.mpf(0.5) + new_nodes = [] + if ctx.isinf(a) or ctx.isinf(b): + if (a, b) == (ctx.ninf, ctx.inf): + p05 = -half + for x, w in nodes: + x2 = x*x + px1 = one-x2 + spx1 = px1**p05 + x = x*spx1 + w *= spx1/px1 + new_nodes.append((x, w)) + elif a == ctx.ninf: + b1 = b+1 + for x, w in nodes: + u = 2/(x+one) + x = b1-u + w *= half*u**2 + new_nodes.append((x, w)) + elif b == ctx.inf: + a1 = a-1 + for x, w in nodes: + u = 2/(x+one) + x = a1+u + w *= half*u**2 + new_nodes.append((x, w)) + elif a == ctx.inf or b == ctx.ninf: + return [(x,-w) for (x,w) in self.transform_nodes(nodes, b, a, verbose)] + else: + raise NotImplementedError + else: + # Simple linear change of variables + C = (b-a)/2 + D = (b+a)/2 + for x, w in nodes: + new_nodes.append((D+C*x, C*w)) + return new_nodes + + def guess_degree(self, prec): + """ + Given a desired precision `p` in bits, estimate the degree `m` + of the quadrature required to accomplish full accuracy for + typical integrals. By default, :func:`~mpmath.quad` will perform up + to `m` iterations. The value of `m` should be a slight + overestimate, so that "slightly bad" integrals can be dealt + with automatically using a few extra iterations. On the + other hand, it should not be too big, so :func:`~mpmath.quad` can + quit within a reasonable amount of time when it is given + an "unsolvable" integral. + + The default formula used by :func:`~mpmath.calculus.quadrature.QuadratureRule.guess_degree` is tuned + for both :class:`TanhSinh` and :class:`GaussLegendre`. + The output is roughly as follows: + + +---------+---------+ + | `p` | `m` | + +=========+=========+ + | 50 | 6 | + +---------+---------+ + | 100 | 7 | + +---------+---------+ + | 500 | 10 | + +---------+---------+ + | 3000 | 12 | + +---------+---------+ + + This formula is based purely on a limited amount of + experimentation and will sometimes be wrong. + """ + # Expected degree + # XXX: use mag + g = int(4 + max(0, self.ctx.log(prec/30.0, 2))) + # Reasonable "worst case" + g += 2 + return g + + def estimate_error(self, results, prec, epsilon): + r""" + Given results from integrations `[I_1, I_2, \ldots, I_k]` done + with a quadrature of rule of degree `1, 2, \ldots, k`, estimate + the error of `I_k`. + + For `k = 2`, we estimate `|I_{\infty}-I_2|` as `|I_2-I_1|`. + + For `k > 2`, we extrapolate `|I_{\infty}-I_k| \approx |I_{k+1}-I_k|` + from `|I_k-I_{k-1}|` and `|I_k-I_{k-2}|` under the assumption + that each degree increment roughly doubles the accuracy of + the quadrature rule (this is true for both :class:`TanhSinh` + and :class:`GaussLegendre`). The extrapolation formula is given + by Borwein, Bailey & Girgensohn. Although not very conservative, + this method seems to be very robust in practice. + """ + if len(results) == 2: + return abs(results[0]-results[1]) + try: + if results[-1] == results[-2] == results[-3]: + return self.ctx.zero + D1 = self.ctx.log(abs(results[-1]-results[-2]), 10) + D2 = self.ctx.log(abs(results[-1]-results[-3]), 10) + except ValueError: + return epsilon + D3 = -prec + D4 = min(0, max(D1**2/D2, 2*D1, D3)) + return self.ctx.mpf(10) ** int(D4) + + def summation(self, f, points, prec, epsilon, max_degree, verbose=False): + """ + Main integration function. Computes the 1D integral over + the interval specified by *points*. For each subinterval, + performs quadrature of degree from 1 up to *max_degree* + until :func:`~mpmath.calculus.quadrature.QuadratureRule.estimate_error` signals convergence. + + :func:`~mpmath.calculus.quadrature.QuadratureRule.summation` transforms each subintegration to + the standard interval and then calls :func:`~mpmath.calculus.quadrature.QuadratureRule.sum_next`. + """ + ctx = self.ctx + I = total_err = ctx.zero + for i in range(len(points)-1): + a, b = points[i], points[i+1] + if a == b: + continue + # XXX: we could use a single variable transformation, + # but this is not good in practice. We get better accuracy + # by having 0 as an endpoint. + if (a, b) == (ctx.ninf, ctx.inf): + _f = f + f = lambda x: _f(-x) + _f(x) + a, b = (ctx.zero, ctx.inf) + results = [] + err = ctx.zero + for degree in range(1, max_degree+1): + nodes = self.get_nodes(a, b, degree, prec, verbose) + if verbose: + print("Integrating from %s to %s (degree %s of %s)" % \ + (ctx.nstr(a), ctx.nstr(b), degree, max_degree)) + result = self.sum_next(f, nodes, degree, prec, results, verbose) + results.append(result) + if degree > 1: + err = self.estimate_error(results, prec, epsilon) + if verbose: + print("Estimated error:", ctx.nstr(err), " epsilon:", ctx.nstr(epsilon), " result: ", ctx.nstr(result)) + if err <= epsilon: + break + I += results[-1] + total_err += err + if total_err > epsilon: + if verbose: + print("Failed to reach full accuracy. Estimated error:", ctx.nstr(total_err)) + return I, total_err + + def sum_next(self, f, nodes, degree, prec, previous, verbose=False): + r""" + Evaluates the step sum `\sum w_k f(x_k)` where the *nodes* list + contains the `(w_k, x_k)` pairs. + + :func:`~mpmath.calculus.quadrature.QuadratureRule.summation` will supply the list *results* of + values computed by :func:`~mpmath.calculus.quadrature.QuadratureRule.sum_next` at previous degrees, in + case the quadrature rule is able to reuse them. + """ + return self.ctx.fdot((w, f(x)) for (x,w) in nodes) + + +class TanhSinh(QuadratureRule): + r""" + This class implements "tanh-sinh" or "doubly exponential" + quadrature. This quadrature rule is based on the Euler-Maclaurin + integral formula. By performing a change of variables involving + nested exponentials / hyperbolic functions (hence the name), the + derivatives at the endpoints vanish rapidly. Since the error term + in the Euler-Maclaurin formula depends on the derivatives at the + endpoints, a simple step sum becomes extremely accurate. In + practice, this means that doubling the number of evaluation + points roughly doubles the number of accurate digits. + + Comparison to Gauss-Legendre: + * Initial computation of nodes is usually faster + * Handles endpoint singularities better + * Handles infinite integration intervals better + * Is slower for smooth integrands once nodes have been computed + + The implementation of the tanh-sinh algorithm is based on the + description given in Borwein, Bailey & Girgensohn, "Experimentation + in Mathematics - Computational Paths to Discovery", A K Peters, + 2003, pages 312-313. In the present implementation, a few + improvements have been made: + + * A more efficient scheme is used to compute nodes (exploiting + recurrence for the exponential function) + * The nodes are computed successively instead of all at once + + **References** + + * [Bailey]_ + * [BorweinTanhSinh]_ + + """ + + def sum_next(self, f, nodes, degree, prec, previous, verbose=False): + """ + Step sum for tanh-sinh quadrature of degree `m`. We exploit the + fact that half of the abscissas at degree `m` are precisely the + abscissas from degree `m-1`. Thus reusing the result from + the previous level allows a 2x speedup. + """ + h = self.ctx.mpf(2)**(-degree) + # Abscissas overlap, so reusing saves half of the time + if previous: + S = previous[-1]/(h*2) + else: + S = self.ctx.zero + S += self.ctx.fdot((w,f(x)) for (x,w) in nodes) + return h*S + + def calc_nodes(self, degree, prec, verbose=False): + r""" + The abscissas and weights for tanh-sinh quadrature of degree + `m` are given by + + .. math:: + + x_k = \tanh(\pi/2 \sinh(t_k)) + + w_k = \pi/2 \cosh(t_k) / \cosh(\pi/2 \sinh(t_k))^2 + + where `t_k = t_0 + hk` for a step length `h \sim 2^{-m}`. The + list of nodes is actually infinite, but the weights die off so + rapidly that only a few are needed. + """ + ctx = self.ctx + nodes = [] + + extra = 20 + ctx.prec += extra + tol = ctx.ldexp(1, -prec-10) + pi4 = ctx.pi/4 + + # For simplicity, we work in steps h = 1/2^n, with the first point + # offset so that we can reuse the sum from the previous degree + + # We define degree 1 to include the "degree 0" steps, including + # the point x = 0. (It doesn't work well otherwise; not sure why.) + t0 = ctx.ldexp(1, -degree) + if degree == 1: + #nodes.append((mpf(0), pi4)) + #nodes.append((-mpf(0), pi4)) + nodes.append((ctx.zero, ctx.pi/2)) + h = t0 + else: + h = t0*2 + + # Since h is fixed, we can compute the next exponential + # by simply multiplying by exp(h) + expt0 = ctx.exp(t0) + a = pi4 * expt0 + b = pi4 / expt0 + udelta = ctx.exp(h) + urdelta = 1/udelta + + for k in range(0, 20*2**degree+1): + # Reference implementation: + # t = t0 + k*h + # x = tanh(pi/2 * sinh(t)) + # w = pi/2 * cosh(t) / cosh(pi/2 * sinh(t))**2 + + # Fast implementation. Note that c = exp(pi/2 * sinh(t)) + c = ctx.exp(a-b) + d = 1/c + co = (c+d)/2 + si = (c-d)/2 + x = si / co + w = (a+b) / co**2 + diff = abs(x-1) + if diff <= tol: + break + + nodes.append((x, w)) + nodes.append((-x, w)) + + a *= udelta + b *= urdelta + + if verbose and k % 300 == 150: + # Note: the number displayed is rather arbitrary. Should + # figure out how to print something that looks more like a + # percentage + print("Calculating nodes:", ctx.nstr(-ctx.log(diff, 10) / prec)) + + ctx.prec -= extra + return nodes + + +class GaussLegendre(QuadratureRule): + r""" + This class implements Gauss-Legendre quadrature, which is + exceptionally efficient for polynomials and polynomial-like (i.e. + very smooth) integrands. + + The abscissas and weights are given by roots and values of + Legendre polynomials, which are the orthogonal polynomials + on `[-1, 1]` with respect to the unit weight + (see :func:`~mpmath.legendre`). + + In this implementation, we take the "degree" `m` of the quadrature + to denote a Gauss-Legendre rule of degree `3 \cdot 2^m` (following + Borwein, Bailey & Girgensohn). This way we get quadratic, rather + than linear, convergence as the degree is incremented. + + Comparison to tanh-sinh quadrature: + * Is faster for smooth integrands once nodes have been computed + * Initial computation of nodes is usually slower + * Handles endpoint singularities worse + * Handles infinite integration intervals worse + + """ + + def calc_nodes(self, degree, prec, verbose=False): + r""" + Calculates the abscissas and weights for Gauss-Legendre + quadrature of degree of given degree (actually `3 \cdot 2^m`). + """ + ctx = self.ctx + # It is important that the epsilon is set lower than the + # "real" epsilon + epsilon = ctx.ldexp(1, -prec-8) + # Fairly high precision might be required for accurate + # evaluation of the roots + orig = ctx.prec + ctx.prec = int(prec*1.5) + if degree == 1: + x = ctx.sqrt(ctx.mpf(3)/5) + w = ctx.mpf(5)/9 + nodes = [(-x,w),(ctx.zero,ctx.mpf(8)/9),(x,w)] + ctx.prec = orig + return nodes + nodes = [] + n = 3*2**(degree-1) + upto = n//2 + 1 + for j in range(1, upto): + # Asymptotic formula for the roots + r = ctx.mpf(math.cos(math.pi*(j-0.25)/(n+0.5))) + # Newton iteration + while 1: + t1, t2 = 1, 0 + # Evaluates the Legendre polynomial using its defining + # recurrence relation + for j1 in range(1,n+1): + t3, t2, t1 = t2, t1, ((2*j1-1)*r*t1 - (j1-1)*t2)/j1 + t4 = n*(r*t1-t2)/(r**2-1) + a = t1/t4 + r = r - a + if abs(a) < epsilon: + break + x = r + w = 2/((1-r**2)*t4**2) + if verbose and j % 30 == 15: + print("Computing nodes (%i of %i)" % (j, upto)) + nodes.append((x, w)) + nodes.append((-x, w)) + ctx.prec = orig + return nodes + +class QuadratureMethods: + + def __init__(ctx, *args, **kwargs): + ctx._gauss_legendre = GaussLegendre(ctx) + ctx._tanh_sinh = TanhSinh(ctx) + + def quad(ctx, f, *points, **kwargs): + r""" + Computes a single, double or triple integral over a given + 1D interval, 2D rectangle, or 3D cuboid. A basic example:: + + >>> from mpmath import (mp, quad, cos, pi, exp, inf, sqrt, + ... chop, sin, j, log, euler, e, linspace) + >>> mp.pretty = True + >>> quad(sin, [0, pi]) + 2.0 + + A basic 2D integral:: + + >>> f = lambda x, y: cos(x+y/2) + >>> quad(f, [-pi/2, pi/2], [0, pi]) + 4.0 + + **Interval format** + + The integration range for each dimension may be specified + using a list or tuple. Arguments are interpreted as follows: + + ``quad(f, [x1, x2])`` -- calculates + `\int_{x_1}^{x_2} f(x) \, dx` + + ``quad(f, [x1, x2], [y1, y2])`` -- calculates + `\int_{x_1}^{x_2} \int_{y_1}^{y_2} f(x,y) \, dy \, dx` + + ``quad(f, [x1, x2], [y1, y2], [z1, z2])`` -- calculates + `\int_{x_1}^{x_2} \int_{y_1}^{y_2} \int_{z_1}^{z_2} f(x,y,z) + \, dz \, dy \, dx` + + Endpoints may be finite or infinite. An interval descriptor + may also contain more than two points. In this + case, the integration is split into subintervals, between + each pair of consecutive points. This is useful for + dealing with mid-interval discontinuities, or integrating + over large intervals where the function is irregular or + oscillates. + + **Options** + + :func:`~mpmath.quad` recognizes the following keyword arguments: + + *method* + Chooses integration algorithm (described below). + *error* + If set to true, :func:`~mpmath.quad` returns `(v, e)` where `v` is the + integral and `e` is the estimated error. + *maxdegree* + Maximum degree of the quadrature rule to try before + quitting. + *verbose* + Print details about progress. + + **Algorithms** + + Mpmath presently implements two integration algorithms: tanh-sinh + quadrature and Gauss-Legendre quadrature. These can be selected + using *method='tanh-sinh'* or *method='gauss-legendre'* or by + passing the classes *method=TanhSinh*, *method=GaussLegendre*. + The functions ``quadts()`` and ``quadgl()`` are also available + as shortcuts. + + Both algorithms have the property that doubling the number of + evaluation points roughly doubles the accuracy, so both are ideal + for high precision quadrature (hundreds or thousands of digits). + + At high precision, computing the nodes and weights for the + integration can be expensive (more expensive than computing the + function values). To make repeated integrations fast, nodes + are automatically cached. + + The advantages of the tanh-sinh algorithm are that it tends to + handle endpoint singularities well, and that the nodes are cheap + to compute on the first run. For these reasons, it is used by + :func:`~mpmath.quad` as the default algorithm. + + Gauss-Legendre quadrature often requires fewer function + evaluations, and is therefore often faster for repeated use, but + the algorithm does not handle endpoint singularities as well and + the nodes are more expensive to compute. Gauss-Legendre quadrature + can be a better choice if the integrand is smooth and repeated + integrations are required (e.g. for multiple integrals). + + See the documentation for :class:`~mpmath.calculus.quadrature.TanhSinh` and + :class:`~mpmath.calculus.quadrature.GaussLegendre` for additional details. + + **Examples of 1D integrals** + + Intervals may be infinite or half-infinite. The following two + examples evaluate the limits of the inverse tangent function + (`\int 1/(1+x^2) = \tan^{-1} x`), and the Gaussian integral + `\int_{\infty}^{\infty} \exp(-x^2)\,dx = \sqrt{\pi}`:: + + >>> quad(lambda x: 2/(x**2+1), [0, inf]) + 3.14159265358979 + >>> quad(lambda x: exp(-x**2), [-inf, inf])**2 + 3.14159265358979 + + Integrals can typically be resolved to high precision. + The following computes 50 digits of `\pi` by integrating the + area of the half-circle defined by `x^2 + y^2 \le 1`, + `-1 \le x \le 1`, `y \ge 0`:: + + >>> mp.dps = 50 + >>> 2*quad(lambda x: sqrt(1-x**2), [-1, 1]) + 3.1415926535897932384626433832795028841971693993751 + + One can just as well compute 1000 digits (output truncated):: + + >>> mp.dps = 1000 + >>> 2*quad(lambda x: sqrt(1-x**2), [-1, 1]) + 3.141592653589793238462643383279502884...216420199 + + Complex integrals are supported. The following computes + a residue at `z = 0` by integrating counterclockwise along the + diamond-shaped path from `1` to `+i` to `-1` to `-i` to `1`:: + + >>> mp.dps = 15 + >>> chop(quad(lambda z: 1/z, [1,j,-1,-j,1])) + (0.0 + 6.28318530717959j) + + **Examples of 2D and 3D integrals** + + Here are several nice examples of analytically solvable + 2D integrals (taken from MathWorld [1]) that can be evaluated + to high precision fairly rapidly by :func:`~mpmath.quad`:: + + >>> mp.dps = 30 + >>> f = lambda x, y: (x-1)/((1-x*y)*log(x*y)) + >>> quad(f, [0, 1], [0, 1]) + 0.577215664901532860606512090082 + >>> +euler + 0.577215664901532860606512090082 + + >>> f = lambda x, y: 1/sqrt(1+x**2+y**2) + >>> quad(f, [-1, 1], [-1, 1]) + 3.17343648530607134219175646705 + >>> 4*log(2+sqrt(3))-2*pi/3 + 3.17343648530607134219175646705 + + >>> f = lambda x, y: 1/(1-x**2 * y**2) + >>> quad(f, [0, 1], [0, 1]) + 1.23370055013616982735431137498 + >>> pi**2 / 8 + 1.23370055013616982735431137498 + + >>> quad(lambda x, y: 1/(1-x*y), [0, 1], [0, 1]) + 1.64493406684822643647241516665 + >>> pi**2 / 6 + 1.64493406684822643647241516665 + + Multiple integrals may be done over infinite ranges:: + + >>> mp.dps = 15 + >>> print(quad(lambda x,y: exp(-x-y), [0, inf], [1, inf])) + 0.367879441171442 + >>> print(1/e) + 0.367879441171442 + + For nonrectangular areas, one can call :func:`~mpmath.quad` recursively. + For example, we can replicate the earlier example of calculating + `\pi` by integrating over the unit-circle, and actually use double + quadrature to actually measure the area circle:: + + >>> f = lambda x: quad(lambda y: 1, [-sqrt(1-x**2), sqrt(1-x**2)]) + >>> quad(f, [-1, 1]) + 3.14159265358979 + + Here is a simple triple integral:: + + >>> mp.dps = 15 + >>> f = lambda x,y,z: x*y/(1+z) + >>> quad(f, [0,1], [0,1], [1,2], method='gauss-legendre') + 0.101366277027041 + >>> (log(3)-log(2))/4 + 0.101366277027041 + + **Singularities** + + Both tanh-sinh and Gauss-Legendre quadrature are designed to + integrate smooth (infinitely differentiable) functions. Neither + algorithm copes well with mid-interval singularities (such as + mid-interval discontinuities in `f(x)` or `f'(x)`). + The best solution is to split the integral into parts:: + + >>> mp.dps = 15 + >>> quad(lambda x: abs(sin(x)), [0, 2*pi]) # Bad + 3.99900894176779 + >>> quad(lambda x: abs(sin(x)), [0, pi, 2*pi]) # Good + 4.0 + + The tanh-sinh rule often works well for integrands having a + singularity at one or both endpoints:: + + >>> mp.dps = 15 + >>> quad(log, [0, 1], method='tanh-sinh') # Good + -1.0 + >>> quad(log, [0, 1], method='gauss-legendre') # Bad + -0.999932197413801 + + However, the result may still be inaccurate for some functions:: + + >>> quad(lambda x: 1/sqrt(x), [0, 1], method='tanh-sinh') + 1.99999999946942 + + This problem is not due to the quadrature rule per se, but to + numerical amplification of errors in the nodes. The problem can be + circumvented by temporarily increasing the precision:: + + >>> mp.dps = 30 + >>> a = quad(lambda x: 1/sqrt(x), [0, 1], method='tanh-sinh') + >>> mp.dps = 15 + >>> +a + 2.0 + + **Highly variable functions** + + For functions that are smooth (in the sense of being infinitely + differentiable) but contain sharp mid-interval peaks or many + "bumps", :func:`~mpmath.quad` may fail to provide full accuracy. For + example, with default settings, :func:`~mpmath.quad` is able to integrate + `\sin(x)` accurately over an interval of length 100 but not over + length 1000:: + + >>> quad(sin, [0, 100]) # Good + 0.137681127712316 + >>> 1-cos(100) + 0.137681127712316 + >>> quad(sin, [0, 1000]) # Bad + -37.8587612408485 + >>> 1-cos(1000) + 0.437620923709297 + + One solution is to break the integration into 10 intervals of + length 100:: + + >>> quad(sin, linspace(0, 1000, 10)) # Good + 0.437620923709297 + + Another is to increase the degree of the quadrature:: + + >>> quad(sin, [0, 1000], maxdegree=10) # Also good + 0.437620923709297 + + Whether splitting the interval or increasing the degree is + more efficient differs from case to case. Another example is the + function `1/(1+x^2)`, which has a sharp peak centered around + `x = 0`:: + + >>> f = lambda x: 1/(1+x**2) + >>> quad(f, [-100, 100]) # Bad + 3.64804647105268 + >>> quad(f, [-100, 100], maxdegree=10) # Good + 3.12159332021646 + >>> quad(f, [-100, 0, 100]) # Also good + 3.12159332021646 + + **References** + + 1. [Weisstein]_ http://mathworld.wolfram.com/DoubleIntegral.html + + """ + rule = kwargs.get('method', 'tanh-sinh') + if type(rule) is str: + if rule == 'tanh-sinh': + rule = ctx._tanh_sinh + elif rule == 'gauss-legendre': + rule = ctx._gauss_legendre + else: + raise ValueError("unknown quadrature rule: %s" % rule) + else: + rule = rule(ctx) + verbose = kwargs.get('verbose') + dim = len(points) + orig = prec = ctx.prec + epsilon = ctx.eps/8 + m = kwargs.get('maxdegree') or rule.guess_degree(prec) + points = [ctx._as_points(p) for p in points] + try: + ctx.prec += 20 + if dim == 1: + v, err = rule.summation(f, points[0], prec, epsilon, m, verbose) + elif dim == 2: + v, err = rule.summation(lambda x: \ + rule.summation(lambda y: f(x,y), \ + points[1], prec, epsilon, m)[0], + points[0], prec, epsilon, m, verbose) + elif dim == 3: + v, err = rule.summation(lambda x: \ + rule.summation(lambda y: \ + rule.summation(lambda z: f(x,y,z), \ + points[2], prec, epsilon, m)[0], + points[1], prec, epsilon, m)[0], + points[0], prec, epsilon, m, verbose) + else: + raise NotImplementedError("quadrature must have dim 1, 2 or 3") + finally: + ctx.prec = orig + if kwargs.get("error"): + return +v, err + return +v + + def quadts(ctx, *args, **kwargs): + """ + Performs tanh-sinh quadrature. The call + + quadts(func, *points, ...) + + is simply a shortcut for: + + quad(func, *points, ..., method=TanhSinh) + + For example, a single integral and a double integral: + + quadts(lambda x: exp(cos(x)), [0, 1]) + quadts(lambda x, y: exp(cos(x+y)), [0, 1], [0, 1]) + + See the documentation for quad for information about how points + arguments and keyword arguments are parsed. + + See documentation for TanhSinh for algorithmic information about + tanh-sinh quadrature. + """ + kwargs['method'] = 'tanh-sinh' + return ctx.quad(*args, **kwargs) + + def quadgl(ctx, *args, **kwargs): + """ + Performs Gauss-Legendre quadrature. The call + + quadgl(func, *points, ...) + + is simply a shortcut for: + + quad(func, *points, ..., method=GaussLegendre) + + For example, a single integral and a double integral: + + quadgl(lambda x: exp(cos(x)), [0, 1]) + quadgl(lambda x, y: exp(cos(x+y)), [0, 1], [0, 1]) + + See the documentation for quad for information about how points + arguments and keyword arguments are parsed. + + See documentation for TanhSinh for algorithmic information about + tanh-sinh quadrature. + """ + kwargs['method'] = 'gauss-legendre' + return ctx.quad(*args, **kwargs) + + def quadosc(ctx, f, interval, omega=None, period=None, zeros=None): + r""" + Calculates + + .. math :: + + I = \int_a^b f(x) dx + + where at least one of `a` and `b` is infinite and where + `f(x) = g(x) \cos(\omega x + \phi)` for some slowly + decreasing function `g(x)`. With proper input, :func:`~mpmath.quadosc` + can also handle oscillatory integrals where the oscillation + rate is different from a pure sine or cosine wave. + + In the standard case when `|a| < \infty, b = \infty`, + :func:`~mpmath.quadosc` works by evaluating the infinite series + + .. math :: + + I = \int_a^{x_1} f(x) dx + + \sum_{k=1}^{\infty} \int_{x_k}^{x_{k+1}} f(x) dx + + where `x_k` are consecutive zeros (alternatively + some other periodic reference point) of `f(x)`. + Accordingly, :func:`~mpmath.quadosc` requires information about the + zeros of `f(x)`. For a periodic function, you can specify + the zeros by either providing the angular frequency `\omega` + (*omega*) or the *period* `2 \pi/\omega`. In general, you can + specify the `n`-th zero by providing the *zeros* arguments. + Below is an example of each:: + + >>> from mpmath import (mp, sin, quadosc, pi, ei, cos, inf, j0, + ... j1, sqrt, findroot, exp, e, si, ci, j, + ... quad, log) + >>> mp.pretty = True + >>> f = lambda x: sin(3*x)/(x**2+1) + >>> quadosc(f, [0,inf], omega=3) + 0.37833007080198 + >>> quadosc(f, [0,inf], period=2*pi/3) + 0.37833007080198 + >>> quadosc(f, [0,inf], zeros=lambda n: pi*n/3) + 0.37833007080198 + >>> (ei(3)*exp(-3)-exp(3)*ei(-3))/2 # Computed by Mathematica + 0.37833007080198 + + Note that *zeros* was specified to multiply `n` by the + *half-period*, not the full period. In theory, it does not matter + whether each partial integral is done over a half period or a full + period. However, if done over half-periods, the infinite series + passed to :func:`~mpmath.nsum` becomes an *alternating series* and this + typically makes the extrapolation much more efficient. + + Here is an example of an integration over the entire real line, + and a half-infinite integration starting at `-\infty`:: + + >>> quadosc(lambda x: cos(x)/(1+x**2), [-inf, inf], omega=1) + 1.15572734979092 + >>> pi/e + 1.15572734979092 + >>> quadosc(lambda x: cos(x)/x**2, [-inf, -1], period=2*pi) + -0.0844109505595739 + >>> cos(1)+si(1)-pi/2 + -0.0844109505595738 + + Of course, the integrand may contain a complex exponential just as + well as a real sine or cosine:: + + >>> quadosc(lambda x: exp(3*j*x)/(1+x**2), [-inf,inf], omega=3) + (0.156410688228254 + 0.0j) + >>> pi/e**3 + 0.156410688228254 + >>> quadosc(lambda x: exp(3*j*x)/(2+x+x**2), [-inf,inf], omega=3) + (0.00317486988463794 - 0.0447701735209082j) + >>> 2*pi/sqrt(7)/exp(3*(j+sqrt(7))/2) + (0.00317486988463794 - 0.0447701735209082j) + + **Non-periodic functions** + + If `f(x) = g(x) h(x)` for some function `h(x)` that is not + strictly periodic, *omega* or *period* might not work, and it might + be necessary to use *zeros*. + + A notable exception can be made for Bessel functions which, though not + periodic, are "asymptotically periodic" in a sufficiently strong sense + that the sum extrapolation will work out:: + + >>> quadosc(j0, [0, inf], period=2*pi) + 1.0 + >>> quadosc(j1, [0, inf], period=2*pi) + 1.0 + + More properly, one should provide the exact Bessel function zeros:: + + >>> j0zero = lambda n: findroot(j0, pi*(n-0.25)) + >>> quadosc(j0, [0, inf], zeros=j0zero) + 1.0 + + For an example where *zeros* becomes necessary, consider the + complete Fresnel integrals + + .. math :: + + \int_0^{\infty} \cos x^2\,dx = \int_0^{\infty} \sin x^2\,dx + = \sqrt{\frac{\pi}{8}}. + + Although the integrands do not decrease in magnitude as + `x \to \infty`, the integrals are convergent since the oscillation + rate increases (causing consecutive periods to asymptotically + cancel out). These integrals are virtually impossible to calculate + to any kind of accuracy using standard quadrature rules. However, + if one provides the correct asymptotic distribution of zeros + (`x_n \sim \sqrt{n}`), :func:`~mpmath.quadosc` works:: + + >>> mp.dps = 30 + >>> f = lambda x: cos(x**2) + >>> quadosc(f, [0,inf], zeros=lambda n:sqrt(pi*n)) + 0.626657068657750125603941321203 + >>> f = lambda x: sin(x**2) + >>> quadosc(f, [0,inf], zeros=lambda n:sqrt(pi*n)) + 0.626657068657750125603941321203 + >>> sqrt(pi/8) + 0.626657068657750125603941321203 + + (Interestingly, these integrals can still be evaluated if one + places some other constant than `\pi` in the square root sign.) + + In general, if `f(x) \sim g(x) \cos(h(x))`, the zeros follow + the inverse-function distribution `h^{-1}(x)`:: + + >>> mp.dps = 15 + >>> f = lambda x: sin(exp(x)) + >>> quadosc(f, [1,inf], zeros=lambda n: log(n)) + -0.25024394235267 + >>> pi/2-si(e) + -0.250243942352671 + + **Non-alternating functions** + + If the integrand oscillates around a positive value, without + alternating signs, the extrapolation might fail. A simple trick + that sometimes works is to multiply or divide the frequency by 2:: + + >>> f = lambda x: 1/x**2+sin(x)/x**4 + >>> quadosc(f, [1,inf], omega=1) # Bad + 1.28642190869861 + >>> quadosc(f, [1,inf], omega=0.5) # Perfect + 1.28652953559617 + >>> 1+(cos(1)+ci(1)+sin(1))/6 + 1.28652953559617 + + **Fast decay** + + :func:`~mpmath.quadosc` is primarily useful for slowly decaying + integrands. If the integrand decreases exponentially or faster, + :func:`~mpmath.quad` will likely handle it without trouble (and generally be + much faster than :func:`~mpmath.quadosc`):: + + >>> quadosc(lambda x: cos(x)/exp(x), [0, inf], omega=1) + 0.5 + >>> quad(lambda x: cos(x)/exp(x), [0, inf]) + 0.5 + + """ + a, b = ctx._as_points(interval) + a = ctx.convert(a) + b = ctx.convert(b) + if [omega, period, zeros].count(None) != 2: + raise ValueError( \ + "must specify exactly one of omega, period, zeros") + if a == ctx.ninf and b == ctx.inf: + s1 = ctx.quadosc(f, [a, 0], omega=omega, zeros=zeros, period=period) + s2 = ctx.quadosc(f, [0, b], omega=omega, zeros=zeros, period=period) + return s1 + s2 + if a == ctx.ninf: + if zeros: + return ctx.quadosc(lambda x:f(-x), [-b,-a], zeros=lambda n: zeros(-n)) + else: + return ctx.quadosc(lambda x:f(-x), [-b,-a], omega=omega, period=period) + if b != ctx.inf: + raise ValueError("quadosc requires an infinite integration interval") + if not zeros: + if omega: + period = 2*ctx.pi/omega + zeros = lambda n: n*period/2 + #for n in range(1,10): + # p = zeros(n) + # if p > a: + # break + #if n >= 9: + # raise ValueError("zeros do not appear to be correctly indexed") + n = 1 + s = ctx.quadgl(f, [a, zeros(n)]) + def term(k): + return ctx.quadgl(f, [zeros(k), zeros(k+1)]) + s += ctx.nsum(term, [n, ctx.inf]) + return s + + def quadsubdiv(ctx, f, interval, tol=None, maxintervals=None, **kwargs): + """ + Computes the integral of *f* over the interval or path specified + by *interval*, using :func:`~mpmath.quad` together with adaptive + subdivision of the interval. + + This function gives an accurate answer for some integrals where + :func:`~mpmath.quad` fails:: + + >>> from mpmath import (mp, sin, pi, quad, quadsubdiv, ceil, exp, + ... sech, linspace, fp, ci) + >>> mp.pretty = True + >>> quad(lambda x: abs(sin(x)), [0, 2*pi]) + 3.99900894176779 + >>> quadsubdiv(lambda x: abs(sin(x)), [0, 2*pi]) + 4.0 + >>> quadsubdiv(sin, [0, 1000]) + 0.437620923709297 + >>> quadsubdiv(lambda x: 1/(1+x**2), [-100, 100]) + 3.12159332021646 + >>> quadsubdiv(lambda x: ceil(x), [0, 100]) + 5050.0 + >>> quadsubdiv(lambda x: sin(x+exp(x)), [0,8]) + 0.347400172657248 + + The argument *maxintervals* can be set to limit the permissible + subdivision:: + + >>> quadsubdiv(lambda x: sin(x**2), [0,100], maxintervals=5, error=True) + (-5.40487904307774, 5.011) + >>> quadsubdiv(lambda x: sin(x**2), [0,100], maxintervals=100, error=True) + (0.631417921866934, 1.10101120134116e-17) + + Subdivision does not guarantee a correct answer since, the error + estimate on subintervals may be inaccurate:: + + >>> quadsubdiv(lambda x: sech(10*x-2)**2 + sech(100*x-40)**4 + sech(1000*x-600)**6, [0,1], error=True) + (0.210802735500549, 1.0001111101e-17) + >>> mp.dps = 20 + >>> quadsubdiv(lambda x: sech(10*x-2)**2 + sech(100*x-40)**4 + sech(1000*x-600)**6, [0,1], error=True) + (0.21080273550054927738, 2.200000001e-24) + + The second answer is correct. We can get an accurate result at lower + precision by forcing a finer initial subdivision:: + + >>> mp.dps = 15 + >>> quadsubdiv(lambda x: sech(10*x-2)**2 + sech(100*x-40)**4 + sech(1000*x-600)**6, linspace(0,1,5)) + 0.210802735500549 + + The following integral is too oscillatory for convergence, but we can get a + reasonable estimate:: + + >>> v, err = fp.quadsubdiv(lambda x: fp.sin(1/x), [0,1], error=True) + >>> round(v, 6), round(err, 6) + (0.504067, 1e-06) + >>> sin(1) - ci(1) + 0.504067061906928 + + """ + queue = [] + for i in range(len(interval)-1): + queue.append((interval[i], interval[i+1])) + total = ctx.zero + total_error = ctx.zero + if maxintervals is None: + maxintervals = 10 * ctx.prec + count = 0 + quad_args = kwargs.copy() + quad_args["verbose"] = False + quad_args["error"] = True + if tol is None: + tol = +ctx.eps + orig = ctx.prec + try: + ctx.prec += 5 + while queue: + a, b = queue.pop() + s, err = ctx.quad(f, [a, b], **quad_args) + if kwargs.get("verbose"): + print("subinterval", count, a, b, err) + if err < tol or count > maxintervals: + total += s + total_error += err + else: + count += 1 + if count == maxintervals and kwargs.get("verbose"): + print("warning: number of intervals exceeded maxintervals") + if a == -ctx.inf and b == ctx.inf: + m = 0 + elif a == -ctx.inf: + m = min(b-1, 2*b) + elif b == ctx.inf: + m = max(a+1, 2*a) + else: + m = a + (b - a) / 2 + queue.append((a, m)) + queue.append((m, b)) + finally: + ctx.prec = orig + if kwargs.get("error"): + return +total, +total_error + else: + return +total diff --git a/mpmath/ctx_base.py b/mpmath/ctx_base.py new file mode 100644 index 0000000..6990c32 --- /dev/null +++ b/mpmath/ctx_base.py @@ -0,0 +1,517 @@ +import random +from operator import gt, lt + +from . import libmp +from .calculus.calculus import CalculusMethods +from .calculus.inverselaplace import LaplaceTransformInversionMethods +from .calculus.odes import ODEMethods +from .calculus.optimization import OptimizationMethods +from .calculus.quadrature import QuadratureMethods +from .functions.functions import SpecialFunctions +from .functions.rszeta import RSCache +from .identification import IdentificationMethods +from .matrices.calculus import MatrixCalculusMethods +from .matrices.eigen import Eigen +from .matrices.linalg import LinearAlgebraMethods +from .matrices.matrices import MatrixMethods +from .visualization import VisualizationMethods + + +class Context: + pass + +class StandardBaseContext(Context, + SpecialFunctions, + RSCache, + QuadratureMethods, + LaplaceTransformInversionMethods, + CalculusMethods, + MatrixMethods, + MatrixCalculusMethods, + LinearAlgebraMethods, + Eigen, + IdentificationMethods, + OptimizationMethods, + ODEMethods, + VisualizationMethods): + + NoConvergence = libmp.NoConvergence + ComplexResult = libmp.ComplexResult + + def __init__(ctx): + ctx._aliases = {} + # Call those that need preinitialization (e.g. for wrappers) + SpecialFunctions.__init__(ctx) + RSCache.__init__(ctx) + QuadratureMethods.__init__(ctx) + LaplaceTransformInversionMethods.__init__(ctx) + CalculusMethods.__init__(ctx) + MatrixMethods.__init__(ctx) + + def _init_aliases(ctx): + for alias, value in ctx._aliases.items(): + try: + setattr(ctx, alias, getattr(ctx, value)) + except AttributeError: + pass + + _fixed_precision = False + + # XXX + verbose = False + + def warn(ctx, msg): + print("Warning:", msg) + + def bad_domain(ctx, msg): + raise ValueError(msg) + + def _re(ctx, x): + if hasattr(x, "real"): + return x.real + return x + + def _im(ctx, x): + if hasattr(x, "imag"): + return x.imag + return ctx.zero + + def _as_points(ctx, x): + return x + + def fneg(ctx, x, **kwargs): + return -ctx.convert(x) + + def fadd(ctx, x, y, **kwargs): + return ctx.convert(x)+ctx.convert(y) + + def fsub(ctx, x, y, **kwargs): + return ctx.convert(x)-ctx.convert(y) + + def fmul(ctx, x, y, **kwargs): + return ctx.convert(x)*ctx.convert(y) + + def fdiv(ctx, x, y, **kwargs): + return ctx.convert(x)/ctx.convert(y) + + def fsum(ctx, args, absolute=False, squared=False): + if absolute: + if squared: + return sum((abs(x)**2 for x in args), ctx.zero) + return sum((abs(x) for x in args), ctx.zero) + if squared: + return sum((x**2 for x in args), ctx.zero) + return sum(args, ctx.zero) + + def fdot(ctx, xs, ys=None, conjugate=False): + if ys is not None: + xs = zip(xs, ys) + if conjugate: + cf = ctx.conj + return sum((x*cf(y) for (x,y) in xs), ctx.zero) + else: + return sum((x*y for (x,y) in xs), ctx.zero) + + def fprod(ctx, args): + prod = ctx.one + for arg in args: + prod *= arg + return prod + + def nprint(ctx, x, n=6, **kwargs): + """ + Equivalent to ``print(nstr(x, n))``. + """ + print(ctx.nstr(x, n, **kwargs)) + + def chop(ctx, x, tol=None): + """ + Chops off small real or imaginary parts, or converts + numbers close to zero to exact zeros. The input can be a + single number or an iterable:: + + >>> from mpmath import chop, nprint + >>> chop(5+1e-10j, tol=1e-9) + mpf('5.0') + >>> nprint(chop([1.0, 1e-20, 3+1e-18j, -4, 2])) + [1.0, 0.0, 3.0, -4.0, 2.0] + + The tolerance defaults to ``100*eps``. + """ + if tol is None: + tol = 100*ctx.eps + try: + x = ctx.convert(x) + absx = abs(x) + if abs(x) < tol: + return ctx.zero + if ctx._is_complex_type(x): + #part_tol = min(tol, absx*tol) + part_tol = max(tol, absx*tol) + if abs(x.imag) < part_tol: + return x.real + if abs(x.real) < part_tol: + return ctx.mpc(0, x.imag) + except TypeError: + if isinstance(x, ctx.matrix): + return x.apply(lambda a: ctx.chop(a, tol)) + if hasattr(x, "__iter__"): + return [ctx.chop(a, tol) for a in x] + return x + + def almosteq(ctx, s, t, rel_eps=None, abs_eps=None): + r""" + Determine whether the difference between `s` and `t` is smaller + than a given epsilon, either relatively or absolutely. + + Both a maximum relative difference and a maximum difference + ('epsilons') may be specified. The absolute difference is + defined as `|s-t|` and the relative difference is defined + as `|s-t|/\max(|s|, |t|)`. + + If only one epsilon is given, both are set to the same value. + If none is given, both epsilons are set to `2^{-p+m}` where + `p` is the current working precision and `m` is a small + integer. The default setting typically allows :func:`~mpmath.almosteq` + to be used to check for mathematical equality + in the presence of small rounding errors. + + **Examples** + + >>> from mpmath import almosteq + >>> almosteq(3.141592653589793, 3.141592653589790) + True + >>> almosteq(3.141592653589793, 3.141592653589700) + False + >>> almosteq(3.141592653589793, 3.141592653589700, 1e-10) + True + >>> almosteq(1e-20, 2e-20) + True + >>> almosteq(1e-20, 2e-20, rel_eps=0, abs_eps=0) + False + + """ + s = ctx.convert(s) + t = ctx.convert(t) + if any(ctx.isinf(_) or ctx.isnan(_) for _ in [s, t]): + return s == t + if abs_eps is None and rel_eps is None: + rel_eps = abs_eps = ctx.ldexp(1, -ctx.prec+4) + if abs_eps is None: + abs_eps = rel_eps + elif rel_eps is None: + rel_eps = abs_eps + diff = abs(s-t) + if diff <= abs_eps: + return True + abss = abs(s) + abst = abs(t) + if abss < abst: + err = diff/abst + else: + err = diff/abss + return err <= rel_eps + + def arange(ctx, *args): + r""" + This is a generalized version of Python's :class:`range` function + that accepts fractional endpoints and step sizes and + returns a list of ``mpf`` instances. Like :class:`range`, + :func:`~mpmath.arange` can be called with 1, 2 or 3 arguments: + + ``arange(b)`` + `[0, 1, 2, \ldots, x]` + ``arange(a, b)`` + `[a, a+1, a+2, \ldots, x]` + ``arange(a, b, h)`` + `[a, a+h, a+h, \ldots, x]` + + where `b-1 \le x < b` (in the third case, `b-h \le x < b`). + + Like Python's :class:`range`, the endpoint is not included. To + produce ranges where the endpoint is included, :func:`~mpmath.linspace` + is more convenient. + + **Examples** + + >>> from mpmath import arange + >>> arange(4) + [mpf('0.0'), mpf('1.0'), mpf('2.0'), mpf('3.0')] + >>> arange(1, 2, 0.25) + [mpf('1.0'), mpf('1.25'), mpf('1.5'), mpf('1.75')] + >>> arange(1, -1, -0.75) + [mpf('1.0'), mpf('0.25'), mpf('-0.5')] + + """ + if not len(args) <= 3: + raise TypeError('arange expected at most 3 arguments, got %i' + % len(args)) + if not len(args) >= 1: + raise TypeError('arange expected at least 1 argument, got %i' + % len(args)) + # set default + a = 0 + dt = 1 + # interpret arguments + if len(args) == 1: + b = args[0] + elif len(args) >= 2: + a = args[0] + b = args[1] + if len(args) == 3: + dt = args[2] + a, b, dt = ctx.mpf(a), ctx.mpf(b), ctx.mpf(dt) + assert a + dt != a, 'dt is too small and would cause an infinite loop' + # adapt code for sign of dt + if a > b: + if dt > 0: + return [] + op = gt + else: + if dt < 0: + return [] + op = lt + # create list + result = [] + i = 0 + t = a + while 1: + t = a + dt*i + i += 1 + if op(t, b): + result.append(t) + else: + break + return result + + def linspace(ctx, *args, **kwargs): + """ + ``linspace(a, b, n)`` returns a list of `n` evenly spaced + samples from `a` to `b`. The syntax ``linspace(mpi(a,b), n)`` + is also valid. + + This function is often more convenient than :func:`~mpmath.arange` + for partitioning an interval into subintervals, since + the endpoint is included:: + + >>> from mpmath import linspace + >>> linspace(1, 4, 4) + [mpf('1.0'), mpf('2.0'), mpf('3.0'), mpf('4.0')] + + You may also provide the keyword argument ``endpoint=False``:: + + >>> linspace(1, 4, 4, endpoint=False) + [mpf('1.0'), mpf('1.75'), mpf('2.5'), mpf('3.25')] + + """ + if len(args) == 3: + a = ctx.mpf(args[0]) + b = ctx.mpf(args[1]) + n = int(args[2]) + elif len(args) == 2: + assert hasattr(args[0], '_mpi_') + a = args[0].a + b = args[0].b + n = int(args[1]) + else: + raise TypeError('linspace expected 2 or 3 arguments, got %i' \ + % len(args)) + if n < 1: + raise ValueError('n must be greater than 0') + if 'endpoint' not in kwargs or kwargs['endpoint']: + if n == 1: + return [ctx.mpf(a)] + step = (b - a) / ctx.mpf(n - 1) + y = [i*step + a for i in range(n)] + y[-1] = b + else: + step = (b - a) / ctx.mpf(n) + y = [i*step + a for i in range(n)] + return y + + def cos_sin(ctx, z, **kwargs): + return ctx.cos(z, **kwargs), ctx.sin(z, **kwargs) + + def cospi_sinpi(ctx, z, **kwargs): + return ctx.cospi(z, **kwargs), ctx.sinpi(z, **kwargs) + + def _default_hyper_maxprec(ctx, p): + return int(1000 * p**0.25 + 4*p) + + _gcd = staticmethod(libmp.libintmath.gcd) + list_primes = staticmethod(libmp.libintmath.list_primes) + isprime = staticmethod(libmp.libintmath.isprime) + bernfrac = staticmethod(libmp.gammazeta.bernfrac) + moebius = staticmethod(libmp.libintmath.moebius) + _ifac = staticmethod(libmp.ifac) + _eulernum = staticmethod(libmp.libintmath.eulernum) + _stirling1 = staticmethod(libmp.libintmath.stirling1) + _stirling2 = staticmethod(libmp.libintmath.stirling2) + + def sum_accurately(ctx, terms, check_step=1): + prec = ctx.prec + try: + extraprec = 10 + while 1: + ctx.prec = prec + extraprec + 5 + max_mag = ctx.ninf + sum_mag = ctx.zero + s = ctx.zero + k = 0 + for term in terms(): + s += term + if (not k % check_step) and term: + term_mag = ctx.mag(term) + max_mag = max(max_mag, term_mag) + sum_mag = ctx.mag(s) + if sum_mag - term_mag > ctx.prec: + break + k += 1 + cancellation = max_mag - sum_mag + if cancellation != cancellation: + break + if cancellation < extraprec or ctx._fixed_precision: + break + extraprec += min(ctx.prec, cancellation) + return s + finally: + ctx.prec = prec + + def mul_accurately(ctx, factors, check_step=1): + prec = ctx.prec + try: + extraprec = 10 + while 1: + ctx.prec = prec + extraprec + 5 + max_mag = ctx.ninf + one = ctx.one + s = one + k = 0 + for factor in factors(): + s *= factor + term = factor - one + if (not k % check_step): + term_mag = ctx.mag(term) + max_mag = max(max_mag, term_mag) + sum_mag = ctx.mag(s-one) + #if sum_mag - term_mag > ctx.prec: + # break + if -term_mag > ctx.prec: + break + k += 1 + cancellation = max_mag - sum_mag + if cancellation != cancellation: + break + if cancellation < extraprec or ctx._fixed_precision: + break + extraprec += min(ctx.prec, cancellation) + return s + finally: + ctx.prec = prec + + def power(ctx, x, y): + r"""Converts `x` and `y` to mpmath numbers and evaluates + the principal value of `\exp(y \log(x))`:: + + >>> from mpmath import mp, power + >>> mp.dps = 30 + >>> mp.pretty = True + >>> power(2, 0.5) + 1.41421356237309504880168872421 + + This shows the leading few digits of a large Mersenne prime + (performing the exact calculation ``2**43112609-1`` and + displaying the result in Python would be very slow):: + + >>> power(2, 43112609)-1 + 3.16470269330255923143453723949e+12978188 + + **See Also** + + :func:`~mpmath.root` + """ + return ctx.convert(x) ** ctx.convert(y) + + def _zeta_int(ctx, n): + return ctx.zeta(n) + + def maxcalls(ctx, f, N): + """ + Return a wrapped copy of *f* that raises ``NoConvergence`` when *f* + has been called more than *N* times:: + + >>> from mpmath import maxcalls, sin + >>> f = maxcalls(sin, 10) + >>> print(sum(f(n) for n in range(10))) + 1.95520948210738 + >>> f(10) + Traceback (most recent call last): + ... + NoConvergence: maxcalls: function evaluated 10 times + + """ + counter = [0] + def f_maxcalls_wrapped(*args, **kwargs): + counter[0] += 1 + if counter[0] > N: + raise ctx.NoConvergence("maxcalls: function evaluated %i times" % N) + return f(*args, **kwargs) + return f_maxcalls_wrapped + + def rand(ctx): + """ + Get a random number in the range ``[0.0, 1.0)`` with (almost) uniform distribution. + + This method is a replacement for ``random.random()``. It is roughly equal + to ``random.randint(0, 2 ** prec - 1) / (2 ** prec)``, where ``prec`` + is the current resolution in bits. + + Just like ``random.random()`` and most other floating-point random number + generators, the distribution is not perfect: + + Some float values within ``[0,1)`` will never be returned, for example, + ``2 ** -(prec + 1)`` is impossible. See + http://mumble.net/~campbell/2014/04/28/uniform-random-float + for a lengthy discussion. + """ + # slow default implementation of rand() that works for arbitrary precision. + return ctx.convert(random.getrandbits(ctx.prec)) * (ctx.convert(2) ** (-ctx.prec)) + + + def memoize(ctx, f): + """ + Return a wrapped copy of *f* that caches computed values, i.e. + a memoized copy of *f*. Values are only reused if the cached precision + is equal to or higher than the working precision:: + + >>> from mpmath import memoize, maxcalls, mp, sin + >>> mp.pretty = True + >>> f = memoize(maxcalls(sin, 1)) + >>> f(2) + 0.909297426825682 + >>> f(2) + 0.909297426825682 + >>> mp.dps = 25 + >>> f(2) + Traceback (most recent call last): + ... + NoConvergence: maxcalls: function evaluated 1 times + + """ + f_cache = {} + def f_cached(*args, **kwargs): + if kwargs: + key = args, tuple(kwargs.items()) + else: + key = args + prec = ctx.prec + if key in f_cache: + cprec, cvalue = f_cache[key] + if cprec >= prec: + return +cvalue + value = f(*args, **kwargs) + f_cache[key] = (prec, value) + return value + f_cached.__name__ = f.__name__ + f_cached.__doc__ = f.__doc__ + return f_cached diff --git a/mpmath/ctx_fp.py b/mpmath/ctx_fp.py new file mode 100644 index 0000000..dd7535f --- /dev/null +++ b/mpmath/ctx_fp.py @@ -0,0 +1,283 @@ +import cmath +import functools +import inspect +import math +import sys + +from . import function_docs, libfp, libmp +from .ctx_base import StandardBaseContext +from .libmp import int_types, mpf_bernoulli, to_float + + +class FPContext(StandardBaseContext): + """ + Context for fast low-precision arithmetic (usually, 53-bit precision, + giving at most about 15 decimal digits), using Python's builtin float and + complex types. + """ + + def __init__(ctx): + super().__init__() + ctx.pretty = False + ctx._init_aliases() + + NoConvergence = libmp.NoConvergence + + @property + def prec(ctx): + return sys.float_info.mant_dig + + @prec.setter + def prec(ctx, p): + return + + @property + def dps(ctx): + return sys.float_info.dig + + @dps.setter + def dps(ctx, p): + return + + _fixed_precision = True + + zero = 0.0 + one = 1.0 + eps = sys.float_info.epsilon + inf = libfp.INF + ninf = -math.inf + nan = math.nan + j = 1j + + # Called by SpecialFunctions.__init__() + @classmethod + def _wrap_specfun(cls, name, f, wrap): + if wrap: + def f_wrapped(ctx, *args, **kwargs): + convert = ctx.convert + args = [convert(a) for a in args] + return f(ctx, *args, **kwargs) + else: + f_wrapped = f + f_wrapped.__doc__ = function_docs.__dict__.get(name, f.__doc__) + try: + f_wrapped.__signature__ = inspect.signature(f) + except ValueError: # pragma: no cover + pass + f_wrapped.__name__ = f.__name__ + setattr(cls, name, f_wrapped) + + @functools.lru_cache + def bernoulli(ctx, n, plus=False): + return to_float(mpf_bernoulli(n, ctx.prec, 'n', plus=plus), strict=True) + + pi = libfp.pi + e = math.e + euler = libfp.euler + sqrt2 = 1.4142135623730950488 + sqrt5 = 2.2360679774997896964 + phi = 1.6180339887498948482 + ln2 = 0.69314718055994530942 + ln10 = 2.302585092994045684 + euler = libfp.euler + catalan = 0.91596559417721901505 + khinchin = 2.6854520010653064453 + apery = 1.2020569031595942854 + glaisher = 1.2824271291006226369 + + absmin = absmax = abs + + def isnan(ctx, x): + return x != x + + def isinf(ctx, x): + return abs(x) == libfp.INF + + def isfinite(ctx, x): + if type(x) is complex: + return all(map(math.isfinite, [x.real, x.imag])) + return math.isfinite(x) + + def isnormal(ctx, x): + if type(x) is complex: + return ctx.isnormal(abs(x)) + # XXX: can use math.isnormal() on Python 3.15+ + return bool(x) and math.isfinite(x) and abs(x) >= sys.float_info.min + + def isnpint(ctx, x): + if type(x) is complex: + if x.imag: + return False + x = x.real + return math.isfinite(x) and x <= 0.0 and round(x) == x + + mpf = float + mpc = complex + + def convert(ctx, x): + try: + return float(x) + except: + return complex(x) + + power = staticmethod(libfp.pow) + sqrt = staticmethod(libfp.sqrt) + exp = staticmethod(libfp.exp) + ln = log = staticmethod(libfp.log) + cos = staticmethod(libfp.cos) + sin = staticmethod(libfp.sin) + tan = staticmethod(libfp.tan) + cos_sin = staticmethod(libfp.cos_sin) + acos = staticmethod(libfp.acos) + asin = staticmethod(libfp.asin) + atan = staticmethod(libfp.atan) + cosh = staticmethod(libfp.cosh) + sinh = staticmethod(libfp.sinh) + tanh = staticmethod(libfp.tanh) + acosh = staticmethod(libfp.acosh) + asinh = staticmethod(libfp.asinh) + atanh = staticmethod(libfp.atanh) + gamma = staticmethod(libfp.gamma) + rgamma = staticmethod(libfp.rgamma) + fac = factorial = staticmethod(libfp.factorial) + floor = staticmethod(libfp.floor) + ceil = staticmethod(libfp.ceil) + cospi = staticmethod(libfp.cospi) + sinpi = staticmethod(libfp.sinpi) + cbrt = staticmethod(libfp.cbrt) + _nthroot = staticmethod(libfp.nthroot) + _ei = staticmethod(libfp.ei) + _e1 = staticmethod(libfp.e1) + _zeta = _zeta_int = staticmethod(libfp.zeta) + arg = staticmethod(cmath.phase) + loggamma = staticmethod(libfp.loggamma) + + def expj(ctx, x): + return ctx.exp(ctx.j*x) + + def expjpi(ctx, x): + return ctx.exp(ctx.j*ctx.pi*x) + + ldexp = math.ldexp + frexp = math.frexp + hypot = math.hypot + + def mag(ctx, z): + if z: + n, e = ctx.frexp(abs(z)) + if e: + return e + return ctx.convert(n) + return ctx.ninf + + def isint(ctx, z): + if z.imag: + return False + z = z.real + try: + return z == int(z) + except: + return False + + def nint_distance(ctx, z): + n = round(z.real) + if n == z: + return n, ctx.ninf + return n, ctx.mag(abs(z-n)) + + def _convert_param(ctx, z): + if type(z) is tuple: + p, q = z + return ctx.mpf(p / q), 'R' + intz = int(z.real) + if z == intz: + return intz, 'Z' + if not z.imag: + return ctx.mpf(z), 'R' + return ctx.mpc(z), 'C' + + def _is_real_type(ctx, z): + return isinstance(z, float) or isinstance(z, int_types) + + def _is_complex_type(ctx, z): + return isinstance(z, complex) + + def hypsum(ctx, p, q, flags, coeffs, z, maxterms=6000, **kwargs): + for i, c in enumerate(coeffs[p:], start=p): + if flags[i] == 'Z': + if c <= 0: + ok = False + for ii, cc in enumerate(coeffs[:p]): + # Note: c <= cc or c < cc, depending on convention + if flags[ii] == 'Z' and cc <= 0 and c <= cc: + ok = True + if not ok: + raise ZeroDivisionError("pole in hypergeometric series") + num = range(p) + den = range(p,p+q) + if ctx.isinf(z): + n = max(((n, c) for n, c in enumerate(coeffs[:p]) + if flags[n] == 'Z' and c < 0), default=(-1, 0), + key=lambda x: x[1])[0] + if n >= 0: + n = -coeffs[n] + t = z**n + for k in range(n): + for i in num: t *= (coeffs[i]+k) + for i in den: t /= (coeffs[i]+k) + t /= (k+1) + return t + tol = ctx.eps + s = t = 1.0 + k = 0 + while 1: + for i in num: t *= (coeffs[i]+k) + try: + for i in den: t /= (coeffs[i]+k) + except ZeroDivisionError: + assert not t # poles are handled above + return s + k += 1; t /= k; t *= z; s += t + if abs(t) < tol: + return s + if k > maxterms: + raise ctx.NoConvergence + + atan2 = staticmethod(math.atan2) + + def psi(ctx, m, z): + m = int(m) + if m == 0: + return ctx.digamma(z) + return (-1)**(m+1) * ctx.fac(m) * ctx.zeta(m+1, z) + + digamma = staticmethod(libfp.digamma) + + def harmonic(ctx, x): + x = ctx.convert(x) + if x == 0 or x == 1: + return x + return ctx.digamma(x+1) + ctx.euler + + nstr = str + + def to_fixed(ctx, x, prec): + return int(math.ldexp(x, prec)) + + def rand(ctx): + import random + return random.random() + + _erf = staticmethod(math.erf) + _erfc = staticmethod(math.erfc) + + def sum_accurately(ctx, terms, check_step=1): + s = ctx.zero + k = 0 + for term in terms(): + s += term + if (not k % check_step) and term: + if abs(term) <= 1e-18*abs(s): + break + k += 1 + return s diff --git a/mpmath/ctx_iv.py b/mpmath/ctx_iv.py new file mode 100644 index 0000000..ca4f9ba --- /dev/null +++ b/mpmath/ctx_iv.py @@ -0,0 +1,545 @@ +import inspect +import numbers +import sys + +from . import function_docs, libmp +from .libmp import (MPZ_ONE, ComplexResult, dps_to_prec, finf, fnan, fninf, + from_float, from_int, from_str, fzero, int_types, mpf_le, + mpf_neg, prec_to_dps, repr_dps, round_ceiling, round_floor) +from .libmp.libmpc import mpc_hash +from .libmp.libmpf import mpf_hash, mpf_pos +from .libmp.libmpi import (mpci_abs, mpci_add, mpci_div, mpci_mul, mpci_neg, + mpci_pos, mpci_pow, mpci_sub, mpi_abs, mpi_add, + mpi_delta, mpi_div, mpi_from_str, mpi_mid, mpi_mul, + mpi_neg, mpi_pos, mpi_pow, mpi_str, mpi_sub) +from .matrices.matrices import _matrix + + +mpi_zero = (fzero, fzero) + +from .ctx_base import StandardBaseContext + + +new = object.__new__ + +def convert_mpf_(x, prec, rounding): + if hasattr(x, "_mpf_"): return x._mpf_ + if isinstance(x, int_types): return from_int(x, prec, rounding) + if isinstance(x, float): return from_float(x, prec, rounding) + if isinstance(x, str): return from_str(x, prec, rounding) + if isinstance(x, tuple): return mpf_pos(x, prec, rounding) + raise NotImplementedError + +# pickling support +def _make_mpf(x): + from mpmath import iv + return iv.mpf(x) + + +class ivmpf: + """ + Interval arithmetic class. Precision is controlled by iv.prec. + """ + + def __new__(cls, x=0): + return cls.ctx.convert(x) + + def cast(self, cls, f_convert): + a, b = self._mpi_ + if a == b: + return cls(f_convert(a)) + raise ValueError + + def __int__(self): + return self.cast(int, libmp.to_int) + + def __float__(self): + return self.cast(float, libmp.to_float) + + def __complex__(self): + return self.cast(complex, libmp.to_float) + + def __hash__(self): + a, b = self._mpi_ + if a == b: + return mpf_hash(a) + else: + return hash(self._mpi_) + + @property + def real(self): return self + + @property + def imag(self): return self.ctx.zero + + def conjugate(self): return self + + @property + def a(self): + a, b = self._mpi_ + return self.ctx.make_mpf((a, a)) + + @property + def b(self): + a, b = self._mpi_ + return self.ctx.make_mpf((b, b)) + + @property + def mid(self): + ctx = self.ctx + v = mpi_mid(self._mpi_, ctx.prec) + return ctx.make_mpf((v, v)) + + @property + def delta(self): + ctx = self.ctx + v = mpi_delta(self._mpi_, ctx.prec) + return ctx.make_mpf((v,v)) + + @property + def _mpci_(self): + return self._mpi_, mpi_zero + + def _compare(*args): + raise TypeError("no ordering relation is defined for intervals") + + __gt__ = _compare + __le__ = _compare + __gt__ = _compare + __ge__ = _compare + + def __contains__(self, t): + t = self.ctx.mpf(t) + return (self.a <= t.a) and (t.b <= self.b) + + def __str__(self): + return mpi_str(self._mpi_, self.ctx.prec) + + def __repr__(self): + if self.ctx.pretty: + return str(self) + a, b = self._mpi_ + n = repr_dps(self.ctx.prec) + a = libmp.to_str(a, n) + b = libmp.to_str(b, n) + return "mpi(%r, %r)" % (a, b) + + def __reduce__(self): + return _make_mpf, (self._mpi_,) + + def _compare(s, t, cmpfun): + if not hasattr(t, "_mpi_"): + try: + t = s.ctx.convert(t) + except: + return NotImplemented + return cmpfun(s._mpi_, t._mpi_) + + def __eq__(s, t): return s._compare(t, libmp.libmpi.mpi_eq) + def __ne__(s, t): return s._compare(t, libmp.libmpi.mpi_ne) + def __lt__(s, t): return s._compare(t, libmp.libmpi.mpi_lt) + def __le__(s, t): return s._compare(t, libmp.libmpi.mpi_le) + def __gt__(s, t): return s._compare(t, libmp.libmpi.mpi_gt) + def __ge__(s, t): return s._compare(t, libmp.libmpi.mpi_ge) + + def __abs__(self): + return self.ctx.make_mpf(mpi_abs(self._mpi_, self.ctx.prec)) + def __pos__(self): + return self.ctx.make_mpf(mpi_pos(self._mpi_, self.ctx.prec)) + def __neg__(self): + return self.ctx.make_mpf(mpi_neg(self._mpi_, self.ctx.prec)) + + def ae(s, t, rel_eps=None, abs_eps=None): + return s.ctx.almosteq(s, t, rel_eps, abs_eps) + +class ivmpc: + + def __new__(cls, re=0, im=0): + re = cls.ctx.convert(re) + im = cls.ctx.convert(im) + y = new(cls) + y._mpci_ = re._mpi_, im._mpi_ + return y + + def __hash__(self): + (a, b), (c,d) = self._mpci_ + if a == b and c == d: + return mpc_hash((a, c)) + else: + return hash(self._mpci_) + + def __repr__(s): + if s.ctx.pretty: + return str(s) + return "iv.mpc(%s, %s)" % (repr(s.real), repr(s.imag)) + + def __str__(s): + return "(%s + %s*j)" % (str(s.real), str(s.imag)) + + @property + def a(self): + (a, b), (c,d) = self._mpci_ + return self.ctx.make_mpf((a, a)) + + @property + def b(self): + (a, b), (c,d) = self._mpci_ + return self.ctx.make_mpf((b, b)) + + @property + def c(self): + (a, b), (c,d) = self._mpci_ + return self.ctx.make_mpf((c, c)) + + @property + def d(self): + (a, b), (c,d) = self._mpci_ + return self.ctx.make_mpf((d, d)) + + @property + def real(s): + return s.ctx.make_mpf(s._mpci_[0]) + + @property + def imag(s): + return s.ctx.make_mpf(s._mpci_[1]) + + def conjugate(s): + a, b = s._mpci_ + return s.ctx.make_mpc((a, mpf_neg(b))) + + def overlap(s, t): + t = s.ctx.convert(t) + real_overlap = (s.a <= t.a <= s.b) or (s.a <= t.b <= s.b) or (t.a <= s.a <= t.b) or (t.a <= s.b <= t.b) + imag_overlap = (s.c <= t.c <= s.d) or (s.c <= t.d <= s.d) or (t.c <= s.c <= t.d) or (t.c <= s.d <= t.d) + return real_overlap and imag_overlap + + def __contains__(s, t): + t = s.ctx.convert(t) + return t.real in s.real and t.imag in s.imag + + def _compare(s, t, ne=False): + if not isinstance(t, s.ctx._types): + try: + t = s.ctx.convert(t) + except: + return NotImplemented + if hasattr(t, '_mpi_'): + tval = t._mpi_, mpi_zero + elif hasattr(t, '_mpci_'): + tval = t._mpci_ + if ne: + return s._mpci_ != tval + return s._mpci_ == tval + + def __eq__(s, t): return s._compare(t) + def __ne__(s, t): return s._compare(t, True) + + def __lt__(s, t): raise TypeError("complex intervals cannot be ordered") + __le__ = __gt__ = __ge__ = __lt__ + + def __neg__(s): return s.ctx.make_mpc(mpci_neg(s._mpci_, s.ctx.prec)) + def __pos__(s): return s.ctx.make_mpc(mpci_pos(s._mpci_, s.ctx.prec)) + def __abs__(s): return s.ctx.make_mpf(mpci_abs(s._mpci_, s.ctx.prec)) + + def ae(s, t, rel_eps=None, abs_eps=None): + return s.ctx.almosteq(s, t, rel_eps, abs_eps) + +def _binary_op(f_real, f_complex): + def g_complex(ctx, sval, tval): + return ctx.make_mpc(f_complex(sval, tval, ctx.prec)) + def g_real(ctx, sval, tval): + try: + return ctx.make_mpf(f_real(sval, tval, ctx.prec)) + except ComplexResult: + sval = (sval, mpi_zero) + tval = (tval, mpi_zero) + return g_complex(ctx, sval, tval) + def lop_real(s, t): + if isinstance(t, _matrix): return NotImplemented + ctx = s.ctx + if not isinstance(t, ctx._types): t = ctx.convert(t) + if hasattr(t, "_mpi_"): return g_real(ctx, s._mpi_, t._mpi_) + if hasattr(t, "_mpci_"): return g_complex(ctx, (s._mpi_, mpi_zero), t._mpci_) + return NotImplemented + def rop_real(s, t): + ctx = s.ctx + if not isinstance(t, ctx._types): t = ctx.convert(t) + if hasattr(t, "_mpi_"): return g_real(ctx, t._mpi_, s._mpi_) + if hasattr(t, "_mpci_"): return g_complex(ctx, t._mpci_, (s._mpi_, mpi_zero)) + return NotImplemented + def lop_complex(s, t): + if isinstance(t, _matrix): return NotImplemented + ctx = s.ctx + if not isinstance(t, s.ctx._types): + try: + t = s.ctx.convert(t) + except (ValueError, TypeError): + return NotImplemented + return g_complex(ctx, s._mpci_, t._mpci_) + def rop_complex(s, t): + ctx = s.ctx + if not isinstance(t, s.ctx._types): + t = s.ctx.convert(t) + return g_complex(ctx, t._mpci_, s._mpci_) + return lop_real, rop_real, lop_complex, rop_complex + +ivmpf.__add__, ivmpf.__radd__, ivmpc.__add__, ivmpc.__radd__ = _binary_op(mpi_add, mpci_add) +ivmpf.__sub__, ivmpf.__rsub__, ivmpc.__sub__, ivmpc.__rsub__ = _binary_op(mpi_sub, mpci_sub) +ivmpf.__mul__, ivmpf.__rmul__, ivmpc.__mul__, ivmpc.__rmul__ = _binary_op(mpi_mul, mpci_mul) +ivmpf.__pow__, ivmpf.__rpow__, ivmpc.__pow__, ivmpc.__rpow__ = _binary_op(mpi_pow, mpci_pow) + +ivmpf.__truediv__, ivmpf.__rtruediv__, ivmpc.__truediv__, ivmpc.__rtruediv__ = _binary_op(mpi_div, mpci_div) + +class ivmpf_constant(ivmpf): + def __new__(cls, f): + self = new(cls) + self._f = f + return self + def _get_mpi_(self): + prec = self.ctx._prec[0] + a = self._f(prec, round_floor) + b = self._f(prec, round_ceiling) + return a, b + _mpi_ = property(_get_mpi_) + +class MPIntervalContext(StandardBaseContext): + + def __init__(ctx): + ctx.mpf = type('ivmpf', (ivmpf,), {}) + ctx.mpc = type('ivmpc', (ivmpc,), {}) + ctx._types = (ctx.mpf, ctx.mpc) + ctx._constant = type('ivmpf_constant', (ivmpf_constant,), {}) + ctx._prec = [sys.float_info.mant_dig] + ctx._set_prec(ctx._prec[0]) + ctx._constant._ctxdata = ctx.mpf._ctxdata = ctx.mpc._ctxdata = [ctx.mpf, new, ctx._prec] + ctx._constant.ctx = ctx.mpf.ctx = ctx.mpc.ctx = ctx + ctx.pretty = False + StandardBaseContext.__init__(ctx) + ctx._init_builtins() + + def _mpi(ctx, a, b=None): + if b is None: + return ctx.mpf(a) + return ctx.mpf((a,b)) + + def _init_builtins(ctx): + ctx.one = ctx.mpf(1) + ctx.zero = ctx.mpf(0) + ctx.inf = ctx.mpf('inf') + ctx.ninf = -ctx.inf + ctx.nan = ctx.mpf('nan') + ctx.j = ctx.mpc(0,1) + ctx.exp = ctx._wrap_mpi_function(libmp.libmpi.mpi_exp, libmp.libmpi.mpci_exp) + ctx.sqrt = ctx._wrap_mpi_function(libmp.libmpi.mpi_sqrt) + ctx.ln = ctx._wrap_mpi_function(libmp.libmpi.mpi_log, libmp.libmpi.mpci_log) + ctx.cos = ctx._wrap_mpi_function(libmp.libmpi.mpi_cos, libmp.libmpi.mpci_cos) + ctx.sin = ctx._wrap_mpi_function(libmp.libmpi.mpi_sin, libmp.libmpi.mpci_sin) + ctx.tan = ctx._wrap_mpi_function(libmp.libmpi.mpi_tan) + ctx.gamma = ctx._wrap_mpi_function(libmp.libmpi.mpi_gamma, libmp.libmpi.mpci_gamma) + ctx.loggamma = ctx._wrap_mpi_function(libmp.libmpi.mpi_loggamma, libmp.libmpi.mpci_loggamma) + ctx.rgamma = ctx._wrap_mpi_function(libmp.libmpi.mpi_rgamma, libmp.libmpi.mpci_rgamma) + ctx.factorial = ctx._wrap_mpi_function(libmp.libmpi.mpi_factorial, libmp.libmpi.mpci_factorial) + ctx.fac = ctx.factorial + + ctx.eps = ctx._constant(lambda prec, rnd: (0, MPZ_ONE, 1-prec, 1)) + ctx.pi = ctx._constant(libmp.mpf_pi) + ctx.e = ctx._constant(libmp.mpf_e) + ctx.ln2 = ctx._constant(libmp.libelefun.mpf_ln2) + ctx.ln10 = ctx._constant(libmp.libelefun.mpf_ln10) + ctx.phi = ctx._constant(libmp.libelefun.mpf_phi) + ctx.euler = ctx._constant(libmp.gammazeta.mpf_euler) + ctx.catalan = ctx._constant(libmp.gammazeta.mpf_catalan) + ctx.glaisher = ctx._constant(libmp.gammazeta.mpf_glaisher) + ctx.khinchin = ctx._constant(libmp.gammazeta.mpf_khinchin) + ctx.twinprime = ctx._constant(libmp.gammazeta.mpf_twinprime) + + def _wrap_mpi_function(ctx, f_real, f_complex=None): + def g(x, **kwargs): + if kwargs: + prec = kwargs.get('prec', ctx._prec[0]) + else: + prec = ctx._prec[0] + x = ctx.convert(x) + if hasattr(x, "_mpi_"): + return ctx.make_mpf(f_real(x._mpi_, prec)) + if hasattr(x, "_mpci_"): + return ctx.make_mpc(f_complex(x._mpci_, prec)) + raise ValueError + return g + + @classmethod + def _wrap_specfun(cls, name, f, wrap): + if wrap: + def f_wrapped(ctx, *args, **kwargs): + convert = ctx.convert + args = [convert(a) for a in args] + prec = ctx.prec + try: + ctx.prec += 10 + retval = f(ctx, *args, **kwargs) + finally: + ctx.prec = prec + return +retval + else: + f_wrapped = f + f_wrapped.__doc__ = function_docs.__dict__.get(name, f.__doc__) + f_wrapped.__signature__ = inspect.signature(f) + f_wrapped.__name__ = f.__name__ + setattr(cls, name, f_wrapped) + + def _set_prec(ctx, n): + ctx._prec[0] = max(1, int(n)) + ctx._dps = prec_to_dps(n) + + def _set_dps(ctx, n): + ctx._prec[0] = dps_to_prec(n) + ctx._dps = max(1, int(n)) + + prec = property(lambda ctx: ctx._prec[0], _set_prec) + dps = property(lambda ctx: ctx._dps, _set_dps) + + def make_mpf(ctx, v): + a = new(ctx.mpf) + a._mpi_ = v + return a + + def make_mpc(ctx, v): + a = new(ctx.mpc) + a._mpci_ = v + return a + + def convert(ctx, x): + if isinstance(x, (ctx.mpf, ctx.mpc)): + return x + if isinstance(x, ctx._constant): + return +x + if isinstance(x, complex) or hasattr(x, "_mpc_"): + re = ctx.convert(x.real) + im = ctx.convert(x.imag) + return ctx.mpc(re,im) + if isinstance(x, str): + v = mpi_from_str(x, ctx.prec) + return ctx.make_mpf(v) + if hasattr(x, "_mpi_"): + a, b = x._mpi_ + else: + try: + a, b = x + except (TypeError, ValueError): + a = b = x + if hasattr(a, "_mpi_"): + a = a._mpi_[0] + else: + a = convert_mpf_(a, ctx.prec, round_floor) + if hasattr(b, "_mpi_"): + b = b._mpi_[1] + else: + b = convert_mpf_(b, ctx.prec, round_ceiling) + if a == fnan or b == fnan: + a = fninf + b = finf + assert mpf_le(a, b), "endpoints must be properly ordered" + return ctx.make_mpf((a, b)) + + def nstr(ctx, x, n=5, **kwargs): + x = ctx.convert(x) + if hasattr(x, "_mpi_"): + return libmp.libmpi.mpi_to_str(x._mpi_, n, **kwargs) + if hasattr(x, "_mpci_"): + re = libmp.libmpi.mpi_to_str(x._mpci_[0], n, **kwargs) + im = libmp.libmpi.mpi_to_str(x._mpci_[1], n, **kwargs) + return "(%s + %s*j)" % (re, im) + + def mag(ctx, x): + x = ctx.convert(x) + if isinstance(x, ctx.mpc): + return max(ctx.mag(x.real), ctx.mag(x.imag)) + 1 + a, b = libmp.libmpi.mpi_abs(x._mpi_) + sign, man, exp, bc = b + if man: + return exp+bc + if b == fzero: + return ctx.ninf + if b == fnan: + return ctx.nan + return ctx.inf + + def isnan(ctx, x): + return False + + def isinf(ctx, x): + return x == ctx.inf + + def isint(ctx, x): + x = ctx.convert(x) + a, b = x._mpi_ + if a == b: + sign, man, exp, bc = a + if man: + return exp >= 0 + return a == fzero + return None + + def ldexp(ctx, x, n): + a, b = ctx.convert(x)._mpi_ + a = libmp.mpf_shift(a, n) + b = libmp.mpf_shift(b, n) + return ctx.make_mpf((a,b)) + + def absmin(ctx, x): + return abs(ctx.convert(x)).a + + def absmax(ctx, x): + return abs(ctx.convert(x)).b + + def atan2(ctx, y, x): + y = ctx.convert(y)._mpi_ + x = ctx.convert(x)._mpi_ + return ctx.make_mpf(libmp.libmpi.mpi_atan2(y,x,ctx.prec)) + + def _convert_param(ctx, x): + if isinstance(x, libmp.int_types): + return x, 'Z' + if isinstance(x, tuple): + p, q = x + return (ctx.mpf(p) / ctx.mpf(q), 'R') + x = ctx.convert(x) + if isinstance(x, ctx.mpf): + return x, 'R' + if isinstance(x, ctx.mpc): + return x, 'C' + raise ValueError + + def _is_real_type(ctx, z): + return isinstance(z, ctx.mpf) or isinstance(z, int_types) + + def _is_complex_type(ctx, z): + return isinstance(z, ctx.mpc) + + def hypsum(ctx, p, q, types, coeffs, z, maxterms=6000, **kwargs): + coeffs = list(coeffs) + num = range(p) + den = range(p,p+q) + #tol = ctx.eps + s = t = ctx.one + k = 0 + while 1: + for i in num: t *= (coeffs[i]+k) + for i in den: t /= (coeffs[i]+k) + k += 1; t /= k; t *= z; s += t + if t == 0: + return s + #if abs(t) < tol: + # return s + if k > maxterms: + raise ctx.NoConvergence + + +# Register with "numbers" ABC +# We do not subclass, hence we do not use the @abstractmethod checks. While +# this is less invasive it may turn out that we do not actually support +# parts of the expected interfaces. See +# https://docs.python.org/3/library/numbers.html for list of abstract methods. +numbers.Complex.register(ivmpc) +numbers.Real.register(ivmpf) diff --git a/mpmath/ctx_mp.py b/mpmath/ctx_mp.py new file mode 100644 index 0000000..63fc955 --- /dev/null +++ b/mpmath/ctx_mp.py @@ -0,0 +1,1338 @@ +""" +This module defines the mpf, mpc classes, and standard functions for +operating with them. +""" + +import functools +import re +import sys + +from . import function_docs, libmp +from .ctx_base import StandardBaseContext +from .ctx_mp_python import PythonMPContext as BaseMPContext +from .libmp import (MPZ_ONE, ComplexResult, dps_to_prec, finf, fnan, fninf, + fone, from_rational, fzero, int_types, mpf_add, mpf_div, + mpf_mul, mpf_neg, mpf_sub, repr_dps, round_nearest, + to_man_exp, to_str) +from .libmp.backend import MPQ +from .libmp.gammazeta import (mpf_apery, mpf_catalan, mpf_euler, mpf_glaisher, + mpf_khinchin, mpf_mertens, mpf_twinprime) +from .libmp.libelefun import (mpf_degree, mpf_e, mpf_ln2, mpf_ln10, mpf_phi, + mpf_pi) +from .libmp.libmpc import (mpc_add, mpc_add_mpf, mpc_div, mpc_div_mpf, + mpc_mpf_div, mpc_mpf_sub, mpc_mul, mpc_mul_mpf, + mpc_neg, mpc_sub, mpc_sub_mpf, mpc_to_str) +from .libmp.libmpf import mpf_rand + + +get_complex = re.compile(r""" + \(? + (?P[+-]?(\d*(\.\d*)?(e[+-]?\d+)?|\d+/\d+))?? + (?P[+-]?(\d*(\.\d*)?(e[+-]?\d+)?|\d+/\d+)\*?[ji])? + \)?$ +""", re.VERBOSE | re.IGNORECASE) + + +class MPContext(BaseMPContext, StandardBaseContext): + """ + Context for multiple precision floatng-point arithmetic. + + **Arguments** + + *prec* + precision in bits, default is 53 + *rounding* + rounding mode, default is round to nearest + *trap_complex* + enable complex answers, where real aren't possible, default is False + + """ + + def __init__(ctx, prec=sys.float_info.mant_dig, + rounding=round_nearest, trap_complex=False): + BaseMPContext.__init__(ctx) + ctx.pretty = False + ctx.types = [ctx.mpf, ctx.mpc, ctx.constant] + ctx.default() + ctx._set_prec(prec) + ctx._set_rounding(rounding) + ctx.trap_complex = trap_complex + StandardBaseContext.__init__(ctx) + + ctx.init_builtins() + + ctx.hyp_summators = {} + + ctx._init_aliases() + + ctx.bernoulli.__func__.__doc__ = function_docs.bernoulli + ctx.primepi.__func__.__doc__ = function_docs.primepi + ctx.psi.__func__.__doc__ = function_docs.psi + ctx.atan2.__func__.__doc__ = function_docs.atan2 + + ctx.digamma.__doc__ = function_docs.digamma + ctx.cospi.__doc__ = function_docs.cospi + ctx.sinpi.__doc__ = function_docs.sinpi + ctx.sinpi.__name__ = 'sinpi' + ctx.cospi.__name__ = 'cospi' + + def init_builtins(ctx): + # Exact constants + ctx.one = ctx.make_mpf(fone) + ctx.zero = ctx.make_mpf(fzero) + ctx.j = ctx.make_mpc((fzero,fone)) + ctx.inf = ctx.make_mpf(finf) + ctx.ninf = ctx.make_mpf(fninf) + ctx.nan = ctx.make_mpf(fnan) + + ctx.eps = ctx.constant(lambda prec, rnd: (0, MPZ_ONE, 1-prec, 1), + "epsilon of working precision", "eps", + lambda: ctx.dps) + + # Approximate constants + ctx.pi = ctx.constant(mpf_pi, "pi", "pi") + ctx.ln2 = ctx.constant(mpf_ln2, "ln(2)", "ln2") + ctx.ln10 = ctx.constant(mpf_ln10, "ln(10)", "ln10") + ctx.phi = ctx.constant(mpf_phi, "Golden ratio phi", "phi") + ctx.e = ctx.constant(mpf_e, "e = exp(1)", "e") + ctx.euler = ctx.constant(mpf_euler, "Euler's constant", "euler") + ctx.catalan = ctx.constant(mpf_catalan, "Catalan's constant", "catalan") + ctx.khinchin = ctx.constant(mpf_khinchin, "Khinchin's constant", "khinchin") + ctx.glaisher = ctx.constant(mpf_glaisher, "Glaisher's constant", "glaisher") + ctx.apery = ctx.constant(mpf_apery, "Apery's constant", "apery") + ctx.degree = ctx.constant(mpf_degree, "1 deg = pi / 180", "degree") + ctx.twinprime = ctx.constant(mpf_twinprime, "Twin prime constant", "twinprime") + ctx.mertens = ctx.constant(mpf_mertens, "Mertens' constant", "mertens") + + # Standard functions + ctx.sqrt = ctx._wrap_libmp_function(libmp.mpf_sqrt, libmp.mpc_sqrt) + ctx.cbrt = ctx._wrap_libmp_function(libmp.libelefun.mpf_cbrt, libmp.libmpc.mpc_cbrt) + ctx.ln = ctx._wrap_libmp_function(libmp.libelefun.mpf_ln, libmp.libmpc.mpc_ln) + ctx.atan = ctx._wrap_libmp_function(libmp.mpf_atan, libmp.libmpc.mpc_atan) + ctx.exp = ctx._wrap_libmp_function(libmp.mpf_exp, libmp.mpc_exp) + ctx.expj = ctx._wrap_libmp_function(libmp.libmpc.mpf_expj, libmp.libmpc.mpc_expj) + ctx.expjpi = ctx._wrap_libmp_function(libmp.libmpc.mpf_expjpi, libmp.libmpc.mpc_expjpi) + ctx.sin = ctx._wrap_libmp_function(libmp.mpf_sin, libmp.libmpc.mpc_sin) + ctx.cos = ctx._wrap_libmp_function(libmp.mpf_cos, libmp.libmpc.mpc_cos) + ctx.tan = ctx._wrap_libmp_function(libmp.mpf_tan, libmp.libmpc.mpc_tan) + ctx.sinh = ctx._wrap_libmp_function(libmp.libelefun.mpf_sinh, libmp.libmpc.mpc_sinh) + ctx.cosh = ctx._wrap_libmp_function(libmp.libelefun.mpf_cosh, libmp.libmpc.mpc_cosh) + ctx.tanh = ctx._wrap_libmp_function(libmp.libelefun.mpf_tanh, libmp.libmpc.mpc_tanh) + ctx.asin = ctx._wrap_libmp_function(libmp.libelefun.mpf_asin, libmp.libmpc.mpc_asin) + ctx.acos = ctx._wrap_libmp_function(libmp.libelefun.mpf_acos, libmp.libmpc.mpc_acos) + ctx.atan = ctx._wrap_libmp_function(libmp.mpf_atan, libmp.libmpc.mpc_atan) + ctx.asinh = ctx._wrap_libmp_function(libmp.libelefun.mpf_asinh, libmp.libmpc.mpc_asinh) + ctx.acosh = ctx._wrap_libmp_function(libmp.libelefun.mpf_acosh, libmp.libmpc.mpc_acosh) + ctx.atanh = ctx._wrap_libmp_function(libmp.libelefun.mpf_atanh, libmp.libmpc.mpc_atanh) + ctx.sinpi = ctx._wrap_libmp_function(libmp.libelefun.mpf_sin_pi, libmp.libmpc.mpc_sin_pi) + ctx.cospi = ctx._wrap_libmp_function(libmp.libelefun.mpf_cos_pi, libmp.libmpc.mpc_cos_pi) + ctx.floor = ctx._wrap_libmp_function(libmp.mpf_floor, libmp.libmpc.mpc_floor) + ctx.ceil = ctx._wrap_libmp_function(libmp.mpf_ceil, libmp.libmpc.mpc_ceil) + ctx.nint = ctx._wrap_libmp_function(libmp.libmpf.mpf_nint, libmp.libmpc.mpc_nint) + ctx.frac = ctx._wrap_libmp_function(libmp.libmpf.mpf_frac, libmp.libmpc.mpc_frac) + ctx.fib = ctx.fibonacci = ctx._wrap_libmp_function(libmp.libelefun.mpf_fibonacci, libmp.libmpc.mpc_fibonacci) + + ctx.gamma = ctx._wrap_libmp_function(libmp.gammazeta.mpf_gamma, libmp.gammazeta.mpc_gamma) + ctx.rgamma = ctx._wrap_libmp_function(libmp.gammazeta.mpf_rgamma, libmp.gammazeta.mpc_rgamma) + ctx.loggamma = ctx._wrap_libmp_function(libmp.gammazeta.mpf_loggamma, libmp.gammazeta.mpc_loggamma) + ctx.fac = ctx.factorial = ctx._wrap_libmp_function(libmp.gammazeta.mpf_factorial, libmp.gammazeta.mpc_factorial) + + ctx.digamma = ctx._wrap_libmp_function(libmp.gammazeta.mpf_psi0, libmp.gammazeta.mpc_psi0) + ctx.harmonic = ctx._wrap_libmp_function(libmp.gammazeta.mpf_harmonic, libmp.gammazeta.mpc_harmonic) + ctx.ei = ctx._wrap_libmp_function(libmp.libhyper.mpf_ei, libmp.libhyper.mpc_ei) + ctx.e1 = ctx._wrap_libmp_function(libmp.libhyper.mpf_e1, libmp.libhyper.mpc_e1) + ctx._ci = ctx._wrap_libmp_function(libmp.libhyper.mpf_ci, libmp.libhyper.mpc_ci) + ctx._si = ctx._wrap_libmp_function(libmp.libhyper.mpf_si, libmp.libhyper.mpc_si) + ctx.ellipk = ctx._wrap_libmp_function(libmp.libhyper.mpf_ellipk, libmp.libhyper.mpc_ellipk) + ctx._ellipe = ctx._wrap_libmp_function(libmp.libhyper.mpf_ellipe, libmp.libhyper.mpc_ellipe) + ctx.agm1 = ctx._wrap_libmp_function(libmp.libhyper.mpf_agm1, libmp.libhyper.mpc_agm1) + ctx._erf = ctx._wrap_libmp_function(libmp.libhyper.mpf_erf, None) + ctx._erfc = ctx._wrap_libmp_function(libmp.libhyper.mpf_erfc, None) + ctx._zeta = ctx._wrap_libmp_function(libmp.gammazeta.mpf_zeta, libmp.gammazeta.mpc_zeta) + ctx._altzeta = ctx._wrap_libmp_function(libmp.gammazeta.mpf_altzeta, libmp.gammazeta.mpc_altzeta) + + def to_fixed(ctx, x, prec): + return x.to_fixed(prec) + + def hypot(ctx, x, y): + r""" + Computes the Euclidean norm of the vector `(x, y)`, equal + to `\sqrt{x^2 + y^2}`. Both `x` and `y` must be real.""" + x = ctx.convert(x) + y = ctx.convert(y) + return ctx.make_mpf(libmp.libmpf.mpf_hypot(x._mpf_, y._mpf_, *ctx._prec_rounding)) + + def _gamma_upper_int(ctx, n, z): + n = int(ctx._re(n)) + if n == 0: + return ctx.e1(z) + if not hasattr(z, '_mpf_'): + raise NotImplementedError + prec, rounding = ctx._prec_rounding + real, imag = libmp.libhyper.mpf_expint(n, z._mpf_, prec, rounding, gamma=True) + if imag is None: + return ctx.make_mpf(real) + else: + return ctx.make_mpc((real, imag)) + + def _expint_int(ctx, n, z): + n = int(n) + if n == 1: + return ctx.e1(z) + if not hasattr(z, '_mpf_'): + raise NotImplementedError + prec, rounding = ctx._prec_rounding + real, imag = libmp.libhyper.mpf_expint(n, z._mpf_, prec, rounding) + if imag is None: + return ctx.make_mpf(real) + else: + return ctx.make_mpc((real, imag)) + + def _nthroot(ctx, x, n): + if hasattr(x, '_mpf_'): + try: + return ctx.make_mpf(libmp.libelefun.mpf_nthroot(x._mpf_, n, *ctx._prec_rounding)) + except ComplexResult: + if ctx.trap_complex: + raise + x = (x._mpf_, libmp.fzero) + else: + x = x._mpc_ + return ctx.make_mpc(libmp.libmpc.mpc_nthroot(x, n, *ctx._prec_rounding)) + + def _besselj(ctx, n, z): + prec, rounding = ctx._prec_rounding + if hasattr(z, '_mpf_'): + return ctx.make_mpf(libmp.libhyper.mpf_besseljn(n, z._mpf_, prec, rounding)) + elif hasattr(z, '_mpc_'): + return ctx.make_mpc(libmp.libhyper.mpc_besseljn(n, z._mpc_, prec, rounding)) + + def _agm(ctx, a, b=1): + prec, rounding = ctx._prec_rounding + if hasattr(a, '_mpf_') and hasattr(b, '_mpf_'): + try: + v = libmp.libhyper.mpf_agm(a._mpf_, b._mpf_, prec, rounding) + return ctx.make_mpf(v) + except ComplexResult: + pass + if hasattr(a, '_mpf_'): a = (a._mpf_, libmp.fzero) + else: a = a._mpc_ + if hasattr(b, '_mpf_'): b = (b._mpf_, libmp.fzero) + else: b = b._mpc_ + return ctx.make_mpc(libmp.libhyper.mpc_agm(a, b, prec, rounding)) + + def bernoulli(ctx, n, plus=False): + return ctx.make_mpf(libmp.mpf_bernoulli(int(n), *ctx._prec_rounding, plus=plus)) + + def _zeta_int(ctx, n): + return ctx.make_mpf(libmp.gammazeta.mpf_zeta_int(int(n), *ctx._prec_rounding)) + + def atan2(ctx, y, x): + x = ctx.convert(x) + y = ctx.convert(y) + return ctx.make_mpf(libmp.mpf_atan2(y._mpf_, x._mpf_, *ctx._prec_rounding)) + + def psi(ctx, m, z): + z = ctx.convert(z) + m = int(m) + if ctx._is_real_type(z): + return ctx.make_mpf(libmp.gammazeta.mpf_psi(m, z._mpf_, *ctx._prec_rounding)) + else: + return ctx.make_mpc(libmp.gammazeta.mpc_psi(m, z._mpc_, *ctx._prec_rounding)) + + def cos_sin(ctx, x, **kwargs): + if type(x) not in ctx.types: + x = ctx.convert(x) + prec, rounding = ctx._parse_prec(kwargs) + if hasattr(x, '_mpf_'): + c, s = libmp.libelefun.mpf_cos_sin(x._mpf_, prec, rounding) + return ctx.make_mpf(c), ctx.make_mpf(s) + elif hasattr(x, '_mpc_'): + c, s = libmp.libmpc.mpc_cos_sin(x._mpc_, prec, rounding) + return ctx.make_mpc(c), ctx.make_mpc(s) + else: + return ctx.cos(x, **kwargs), ctx.sin(x, **kwargs) + + def cospi_sinpi(ctx, x, **kwargs): + if type(x) not in ctx.types: + x = ctx.convert(x) + prec, rounding = ctx._parse_prec(kwargs) + if hasattr(x, '_mpf_'): + c, s = libmp.libelefun.mpf_cos_sin_pi(x._mpf_, prec, rounding) + return ctx.make_mpf(c), ctx.make_mpf(s) + elif hasattr(x, '_mpc_'): + c, s = libmp.libmpc.mpc_cos_sin_pi(x._mpc_, prec, rounding) + return ctx.make_mpc(c), ctx.make_mpc(s) + else: + return ctx.cos(x, **kwargs), ctx.sin(x, **kwargs) + + def clone(ctx): + """ + Create a copy of the context, with the same working precision. + """ + a = ctx.__class__() + a.prec = ctx.prec + return a + + # Several helper methods + # TODO: add more of these, make consistent, write docstrings, ... + + def _is_real_type(ctx, x): + if hasattr(x, '_mpc_') or type(x) is complex: + return False + return True + + def _is_complex_type(ctx, x): + if hasattr(x, '_mpc_') or type(x) is complex: + return True + return False + + def isnan(ctx, x): + """ + Return *True* if *x* is a NaN (not-a-number), or for a complex + number, whether either the real or complex part is NaN; + otherwise return *False*:: + + >>> from mpmath import isnan, nan, mpc + >>> isnan(3.14) + False + >>> isnan(nan) + True + >>> isnan(mpc(3.14,2.72)) + False + >>> isnan(mpc(3.14,nan)) + True + + """ + if hasattr(x, "_mpf_"): + return x._mpf_ == fnan + if hasattr(x, "_mpc_"): + return fnan in x._mpc_ + if isinstance(x, int_types) or isinstance(x, MPQ): + return False + x = ctx.convert(x) + if hasattr(x, '_mpf_') or hasattr(x, '_mpc_'): + return ctx.isnan(x) + raise TypeError("isnan() needs a number as input") + + def isfinite(ctx, x): + """ + Return *True* if *x* is a finite number, i.e. neither + an infinity or a NaN. + + >>> from mpmath import isfinite, inf, nan, mpc + >>> isfinite(inf) + False + >>> isfinite(-inf) + False + >>> isfinite(3) + True + >>> isfinite(nan) + False + >>> isfinite(3+4j) + True + >>> isfinite(mpc(3,inf)) + False + >>> isfinite(mpc(nan,3)) + False + + """ + if ctx.isinf(x) or ctx.isnan(x): + return False + return True + + def isnpint(ctx, x): + """ + Determine if *x* is a nonpositive integer. + """ + if not x: + return True + if hasattr(x, '_mpf_'): + if ctx.isfinite(x): + man, exp = to_man_exp(x._mpf_) + return man < 0 and exp >= 0 + return False + if hasattr(x, '_mpc_'): + return not x.imag and ctx.isnpint(x.real) + if type(x) in int_types: + return x <= 0 + if isinstance(x, MPQ): + p, q = x.numerator, x.denominator + if not p: + return True + return q == 1 and p <= 0 + return ctx.isnpint(ctx.convert(x)) + + def __str__(ctx): + lines = ["Mpmath settings:", + (" mp.prec = %s" % ctx.prec).ljust(30) + f"[default: {sys.float_info.mant_dig}]", + (" mp.dps = %s" % ctx.dps).ljust(30) + f"[default: {sys.float_info.dig}]", + (" mp.rounding = '%s'" % ctx.rounding).ljust(30) + f"[default: 'n']", + (" mp.trap_complex = %s" % ctx.trap_complex).ljust(30) + "[default: False]", + ] + return "\n".join(lines) + + @property + def _repr_digits(ctx): + return repr_dps(ctx._prec) + + @property + def _str_digits(ctx): + return ctx._dps + + def extraprec(ctx, n, normalize_output=False): + """ + The block + + with extraprec(n): + + + increases the precision n bits, executes , and then + restores the precision. + + extraprec(n)(f) returns a decorated version of the function f + that increases the working precision by n bits before execution, + and restores the parent precision afterwards. With + normalize_output=True, it rounds the return value to the parent + precision. + """ + return PrecisionManager(ctx, lambda p: p + n, None, normalize_output) + + def extradps(ctx, n, normalize_output=False): + """ + This function is analogous to extraprec (see documentation) + but changes the decimal precision instead of the number of bits. + """ + return PrecisionManager(ctx, None, lambda d: d + n, normalize_output) + + def workprec(ctx, n, normalize_output=False): + """ + The block + + with workprec(n): + + + sets the precision to n bits, executes , and then restores + the precision. + + workprec(n)(f) returns a decorated version of the function f + that sets the precision to n bits before execution, + and restores the precision afterwards. With normalize_output=True, + it rounds the return value to the parent precision. + """ + return PrecisionManager(ctx, lambda p: n, None, normalize_output) + + def workdps(ctx, n, normalize_output=False): + """ + This function is analogous to workprec (see documentation) + but changes the decimal precision instead of the number of bits. + """ + return PrecisionManager(ctx, None, lambda d: n, normalize_output) + + def autoprec(ctx, f, maxprec=None, catch=(), verbose=False): + r""" + Return a wrapped copy of *f* that repeatedly evaluates *f* + with increasing precision until the result converges to the + full precision used at the point of the call. + + This heuristically protects against rounding errors, at the cost of + roughly a 2x slowdown compared to manually setting the optimal + precision. This method can, however, easily be fooled if the results + from *f* depend "discontinuously" on the precision, for instance + if catastrophic cancellation can occur. Therefore, :func:`~mpmath.autoprec` + should be used judiciously. + + **Examples** + + Many functions are sensitive to perturbations of the input arguments. + If the arguments are decimal numbers, they may have to be converted + to binary at a much higher precision. If the amount of required + extra precision is unknown, :func:`~mpmath.autoprec` is convenient:: + + >>> from mpmath import mp, besselj, autoprec, sin, pi, exp, expm1 + >>> mp.pretty = True + >>> besselj(5, 125 * 10**28) # Exact input + -8.03284785591801e-17 + >>> besselj(5, '1.25e30') # Bad + 7.12954868316652e-16 + >>> autoprec(besselj)(5, '1.25e30') # Good + -8.03284785591801e-17 + + The following fails to converge because `\sin(\pi) = 0` whereas all + finite-precision approximations of `\pi` give nonzero values:: + + >>> autoprec(sin)(pi) + Traceback (most recent call last): + ... + NoConvergence: autoprec: prec increased to 2910 without convergence + + As the following example shows, :func:`~mpmath.autoprec` can protect against + cancellation, but is fooled by too severe cancellation:: + + >>> x = 1e-10 + >>> exp(x)-1 + 1.00000008274037e-10 + >>> expm1(x) + 1.00000000005e-10 + >>> autoprec(lambda t: exp(t)-1)(x) + 1.00000000005e-10 + >>> x = 1e-50 + >>> exp(x)-1 + 0.0 + >>> expm1(x) + 1.0e-50 + >>> autoprec(lambda t: exp(t)-1)(x) + 0.0 + + With *catch*, an exception or list of exceptions to intercept + may be specified. The raised exception is interpreted + as signaling insufficient precision. This permits, for example, + evaluating a function where a too low precision results in a + division by zero:: + + >>> f = lambda x: 1/(exp(x)-1) + >>> f(1e-30) + Traceback (most recent call last): + ... + ZeroDivisionError + >>> autoprec(f, catch=ZeroDivisionError)(1e-30) + 1.0e+30 + + + """ + def f_autoprec_wrapped(*args, **kwargs): + prec = ctx.prec + if maxprec is None: + maxprec2 = ctx._default_hyper_maxprec(prec) + else: + maxprec2 = maxprec + try: + ctx.prec = prec + 10 + try: + v1 = f(*args, **kwargs) + except catch: + v1 = ctx.nan + prec2 = prec + 20 + while 1: + ctx.prec = prec2 + try: + v2 = f(*args, **kwargs) + except catch: + v2 = ctx.nan + if v1 == v2: + break + err = ctx.mag(v2-v1) - ctx.mag(v2) + if err < (-prec): + break + if verbose: + print("autoprec: target=%s, prec=%s, accuracy=%s" \ + % (prec, prec2, -err)) + v1 = v2 + if prec2 >= maxprec2: + raise ctx.NoConvergence(\ + "autoprec: prec increased to %i without convergence"\ + % prec2) + prec2 += int(prec2*2) + prec2 = min(prec2, maxprec2) + finally: + ctx.prec = prec + return +v2 + return f_autoprec_wrapped + + def nstr(ctx, x, n=6, **kwargs): + """ + Convert an ``mpf`` or ``mpc`` to a decimal string literal with *n* + significant digits. The small default value for *n* is chosen to + make this function useful for printing collections of numbers + (lists, matrices, etc). + + If *x* is a list or tuple, :func:`~mpmath.nstr` is applied recursively + to each element. For unrecognized classes, :func:`~mpmath.nstr` + simply returns ``str(x)``. + + The companion function :func:`~mpmath.nprint` prints the result + instead of returning it. + + The keyword arguments *strip_zeros*, *min_fixed*, *max_fixed* + and *show_zero_exponent* are forwarded to ``mpmath.libmp.to_str()``. + + The number will be printed in fixed-point format if the position + of the leading digit is strictly between min_fixed + (default = min(-dps/3,-5)) and max_fixed (default = dps). + + To force fixed-point format always, set min_fixed = -inf, + max_fixed = +inf. To force floating-point format, set + min_fixed >= max_fixed. + + >>> from mpmath import nstr, ldexp, mpf, pi, nprint + >>> nstr([+pi, ldexp(1,-500)]) + '[3.14159, 3.05494e-151]' + >>> nprint([+pi, ldexp(1,-500)]) + [3.14159, 3.05494e-151] + >>> nstr(mpf("5e-10"), 5) + '5.0e-10' + >>> nstr(mpf("5e-10"), 5, strip_zeros=False) + '5.0000e-10' + >>> nstr(mpf("5e-10"), 5, strip_zeros=False, min_fixed=-11) + '0.00000000050000' + >>> nstr(mpf(0), 5, show_zero_exponent=True) + '0.0e+0' + + """ + if isinstance(x, list): + return "[%s]" % (", ".join(ctx.nstr(c, n, **kwargs) for c in x)) + if isinstance(x, tuple): + return "(%s)" % (", ".join(ctx.nstr(c, n, **kwargs) for c in x)) + if hasattr(x, '_mpf_'): + return to_str(x._mpf_, n, **kwargs) + if hasattr(x, '_mpc_'): + return "(" + mpc_to_str(x._mpc_, n, **kwargs) + ")" + if isinstance(x, str): + return repr(x) + if isinstance(x, ctx.matrix): + return x.__nstr__(n, **kwargs) + return str(x) + + def _convert_fallback(ctx, x, strings): + if strings and isinstance(x, str): + match = get_complex.match(x.replace(' ', '')) + if match: + re = match.group('re') + if not re: + re = 0 + im = match.group('im').rstrip('jiJI*') + return ctx.mpc(ctx.convert(re), ctx.convert(im)) + if hasattr(x, "_mpi_"): + a, b = x._mpi_ + if a == b: + return ctx.make_mpf(a) + else: + raise ValueError("can only create mpf from zero-width interval") + raise TypeError("cannot create mpf from " + repr(x)) + + def mpmathify(ctx, *args, **kwargs): + return ctx.convert(*args, **kwargs) + + _MPFR_rounding_map = {'N': 'n', + 'D': 'f', + 'U': 'c', + 'Y': 'u', + 'Z': 'd', + 'n': 'n', + 'f': 'f', + 'c': 'c', + 'u': 'u', + 'd': 'd'} + + def _parse_prec(ctx, kwargs): + if kwargs: + if kwargs.get('exact'): + return 0, 'f' + prec, rounding = ctx._prec_rounding + if 'rounding' in kwargs: + rounding = ctx._MPFR_rounding_map[kwargs['rounding']] + if 'prec' in kwargs: + prec = kwargs['prec'] + if prec == ctx.inf: + return 0, 'f' + else: + prec = int(prec) + elif 'dps' in kwargs: + dps = kwargs['dps'] + if dps == ctx.inf: + return 0, 'f' + prec = dps_to_prec(dps) + return prec, rounding + return ctx._prec_rounding + + _exact_overflow_msg = "the exact result does not fit in memory" + + _hypsum_msg = """hypsum() failed to converge to the requested %i bits of accuracy +using a working precision of %i bits. Try with a higher maxprec, +maxterms, or set zeroprec.""" + + def hypsum(ctx, p, q, flags, coeffs, z, accurate_small=True, **kwargs): + if hasattr(z, "_mpf_"): + key = p, q, flags, 'R' + v = z._mpf_ + elif hasattr(z, "_mpc_"): + key = p, q, flags, 'C' + v = z._mpc_ + for i, c in enumerate(coeffs[p:], start=p): + if flags[i] == 'Z': + if c <= 0: + ok = False + for ii, cc in enumerate(coeffs[:p]): + # Note: c <= cc or c < cc, depending on convention + if flags[ii] == 'Z' and cc <= 0 and c <= cc: + ok = True + if not ok: + raise ZeroDivisionError("pole in hypergeometric series") + num = range(p) + den = range(p,p+q) + if ctx.isinf(z): + n = max(((n, c) for n, c in enumerate(coeffs[:p]) + if flags[n] == 'Z' and c < 0), default=(-1, 0), + key=lambda x: x[1])[0] + if n >= 0: + n = -coeffs[n] + t = z**n + for k in range(n): + for i in num: t *= (coeffs[i]+k) + for i in den: t /= (coeffs[i]+k) + t /= (k+1) + return t + if key not in ctx.hyp_summators: + ctx.hyp_summators[key] = libmp.libhyper.make_hyp_summator(key)[1] + summator = ctx.hyp_summators[key] + prec = ctx.prec + maxprec = kwargs.get('maxprec', ctx._default_hyper_maxprec(prec)) + extraprec = 50 + epsshift = 25 + # Jumps in magnitude occur when parameters are close to negative + # integers. We must ensure that these terms are included in + # the sum and added accurately + magnitude_check = {} + max_total_jump = 0 + for i, c in enumerate(coeffs): + if flags[i] == 'Z': + continue + n, d = ctx.nint_distance(c) + n = -int(n) + d = -d + if i >= p and n >= 0 and d > 4: + if n in magnitude_check: + magnitude_check[n] += d + else: + magnitude_check[n] = d + extraprec = max(extraprec, d - prec + 60) + max_total_jump += abs(d) + while 1: + if extraprec > maxprec: + raise ctx.NoConvergence(ctx._hypsum_msg % (prec, prec+extraprec)) + wp = prec + extraprec + if magnitude_check: + mag_dict = dict((n,None) for n in magnitude_check) + else: + mag_dict = {} + zv, have_complex, magnitude = summator(coeffs, v, prec, wp, \ + epsshift, mag_dict, **kwargs) + cancel = -magnitude + jumps_resolved = True + if extraprec < max_total_jump: + for n in mag_dict.values(): + if (n is None) or (n < prec): + jumps_resolved = False + break + accurate = (cancel < extraprec-25-5 or not accurate_small) + if jumps_resolved: + if accurate: + break + # zero? + zeroprec = kwargs.get('zeroprec') + if zeroprec is not None: + if cancel > zeroprec: + if have_complex: + return ctx.mpc(0) + else: + return ctx.zero + + # Some near-singularities were not included, so increase + # precision and repeat until they are + extraprec *= 2 + # Possible workaround for bad roundoff in fixed-point arithmetic + epsshift += 5 + extraprec += 5 + + if type(zv) is tuple: + if have_complex: + return ctx.make_mpc(zv) + else: + return ctx.make_mpf(zv) + else: + return zv + + def ldexp(ctx, x, n): + r""" + Computes `x 2^n` efficiently. No rounding is performed. + The argument `x` must be a real floating-point number (or + possible to convert into one) and `n` must be a Python ``int``. + + >>> from mpmath import ldexp + >>> ldexp(1, 10) + mpf('1024.0') + >>> ldexp(1, -3) + mpf('0.125') + + """ + x = ctx.convert(x) + return ctx.make_mpf(libmp.mpf_shift(x._mpf_, n)) + + def frexp(ctx, x): + r""" + Given a real number `x`, returns `(y, n)` with `y \in [0.5, 1)`, + `n` a Python integer, and such that `x = y 2^n`. No rounding is + performed. + + >>> from mpmath import frexp + >>> frexp(7.5) + (mpf('0.9375'), 3) + + """ + x = ctx.convert(x) + y, n = libmp.libmpf.mpf_frexp(x._mpf_) + return ctx.make_mpf(y), n + + def fneg(ctx, x, **kwargs): + """ + Negates the number *x*, giving a floating-point result, optionally + using a custom precision and rounding mode. + + See the documentation of :func:`~mpmath.fadd` for a detailed description + of how to specify precision and rounding. + + **Examples** + + An mpmath number is returned:: + + >>> from mpmath import fneg, fadd, mpf, log, inf + >>> fneg(2.5) + mpf('-2.5') + >>> fneg(-5+2j) + mpc(real='5.0', imag='-2.0') + + Precise control over rounding is possible:: + + >>> x = fadd(2, 1e-100, exact=True) + >>> fneg(x) + mpf('-2.0') + >>> fneg(x, rounding='f') + mpf('-2.0000000000000004') + + Negating with and without roundoff:: + + >>> n = 200000000000000000000001 + >>> print(int(-mpf(n))) + -200000000000000016777216 + >>> print(int(fneg(n))) + -200000000000000016777216 + >>> print(int(fneg(n, prec=log(n,2)+1))) + -200000000000000000000001 + >>> print(int(fneg(n, dps=log(n,10)+1))) + -200000000000000000000001 + >>> print(int(fneg(n, prec=inf))) + -200000000000000000000001 + >>> print(int(fneg(n, dps=inf))) + -200000000000000000000001 + >>> print(int(fneg(n, exact=True))) + -200000000000000000000001 + + """ + prec, rounding = ctx._parse_prec(kwargs) + x = ctx.convert(x) + if hasattr(x, '_mpf_'): + return ctx.make_mpf(mpf_neg(x._mpf_, prec, rounding)) + if hasattr(x, '_mpc_'): + return ctx.make_mpc(mpc_neg(x._mpc_, prec, rounding)) + raise ValueError("Arguments need to be mpf or mpc compatible numbers") + + def fadd(ctx, x, y, **kwargs): + """ + Adds the numbers *x* and *y*, giving a floating-point result, + optionally using a custom precision and rounding mode. + + The default precision is the working precision of the context. + You can specify a custom precision in bits by passing the *prec* keyword + argument, or by providing an equivalent decimal precision with the *dps* + keyword argument. If the precision is set to ``+inf``, or if the flag + *exact=True* is passed, an exact addition with no rounding is performed. + + When the precision is finite, the optional *rounding* keyword argument + specifies the direction of rounding. Valid options are: + + * ``'f'`` (alias ``'D'``) for floor, towards minus infinity + * ``'c'`` (alias ``'U'``) for ceiling, towards plus infinity + * ``'d'`` (alias ``'Z'``) for down, towards zero + * ``'u'`` (alias ``'Y'``) for up, away from zero + * ``'n'`` (alias ``'N'``) for rounding to nearest (default) + + **Examples** + + Using :func:`~mpmath.fadd` with precision and rounding control:: + + >>> from mpmath import fadd, nprint, mpf, inf + >>> fadd(2, 1e-20) + mpf('2.0') + >>> fadd(2, 1e-20, rounding='u') + mpf('2.0000000000000004') + >>> nprint(fadd(2, 1e-20, prec=100), 25) + 2.00000000000000000001 + >>> nprint(fadd(2, 1e-20, dps=15), 25) + 2.0 + >>> nprint(fadd(2, 1e-20, dps=25), 25) + 2.00000000000000000001 + >>> nprint(fadd(2, 1e-20, exact=True), 25) + 2.00000000000000000001 + + Exact addition avoids cancellation errors, enforcing familiar laws + of numbers such as `x+y-x = y`, which don't hold in floating-point + arithmetic with finite precision:: + + >>> x, y = mpf(2), mpf('1e-1000') + >>> print(x + y - x) + 0.0 + >>> print(fadd(x, y, prec=inf) - x) + 1.0e-1000 + >>> print(fadd(x, y, exact=True) - x) + 1.0e-1000 + + Exact addition can be inefficient and may be impossible to perform + with large magnitude differences:: + + >>> fadd(1, '1e-100000000000000000000', prec=inf) + Traceback (most recent call last): + ... + OverflowError: the exact result does not fit in memory + + """ + prec, rounding = ctx._parse_prec(kwargs) + x = ctx.convert(x) + y = ctx.convert(y) + try: + if hasattr(x, '_mpf_'): + if hasattr(y, '_mpf_'): + return ctx.make_mpf(mpf_add(x._mpf_, y._mpf_, prec, rounding)) + if hasattr(y, '_mpc_'): + return ctx.make_mpc(mpc_add_mpf(y._mpc_, x._mpf_, prec, rounding)) + if hasattr(x, '_mpc_'): + if hasattr(y, '_mpf_'): + return ctx.make_mpc(mpc_add_mpf(x._mpc_, y._mpf_, prec, rounding)) + if hasattr(y, '_mpc_'): + return ctx.make_mpc(mpc_add(x._mpc_, y._mpc_, prec, rounding)) + except (ValueError, OverflowError): + raise OverflowError(ctx._exact_overflow_msg) + raise ValueError("Arguments need to be mpf or mpc compatible numbers") + + def fsub(ctx, x, y, **kwargs): + """ + Subtracts the numbers *x* and *y*, giving a floating-point result, + optionally using a custom precision and rounding mode. + + See the documentation of :func:`~mpmath.fadd` for a detailed description + of how to specify precision and rounding. + + **Examples** + + Using :func:`~mpmath.fsub` with precision and rounding control:: + + >>> from mpmath import fsub, nprint, mpf, inf + >>> fsub(2, 1e-20) + mpf('2.0') + >>> fsub(2, 1e-20, rounding='d') + mpf('1.9999999999999998') + >>> nprint(fsub(2, 1e-20, prec=100), 25) + 1.99999999999999999999 + >>> nprint(fsub(2, 1e-20, dps=15), 25) + 2.0 + >>> nprint(fsub(2, 1e-20, dps=25), 25) + 1.99999999999999999999 + >>> nprint(fsub(2, 1e-20, exact=True), 25) + 1.99999999999999999999 + + Exact subtraction avoids cancellation errors, enforcing familiar laws + of numbers such as `x-y+y = x`, which don't hold in floating-point + arithmetic with finite precision:: + + >>> x, y = mpf(2), mpf('1e1000') + >>> print(x - y + y) + 0.0 + >>> print(fsub(x, y, prec=inf) + y) + 2.0 + >>> print(fsub(x, y, exact=True) + y) + 2.0 + + Exact subtraction can be inefficient and may be impossible to perform + with large magnitude differences:: + + >>> fsub(1, '1e-100000000000000000000', prec=inf) + Traceback (most recent call last): + ... + OverflowError: the exact result does not fit in memory + + """ + prec, rounding = ctx._parse_prec(kwargs) + x = ctx.convert(x) + y = ctx.convert(y) + try: + if hasattr(x, '_mpf_'): + if hasattr(y, '_mpf_'): + return ctx.make_mpf(mpf_sub(x._mpf_, y._mpf_, prec, rounding)) + if hasattr(y, '_mpc_'): + return ctx.make_mpc(mpc_mpf_sub(x._mpf_, y._mpc_, prec, rounding)) + if hasattr(x, '_mpc_'): + if hasattr(y, '_mpf_'): + return ctx.make_mpc(mpc_sub_mpf(x._mpc_, y._mpf_, prec, rounding)) + if hasattr(y, '_mpc_'): + return ctx.make_mpc(mpc_sub(x._mpc_, y._mpc_, prec, rounding)) + except (ValueError, OverflowError): + raise OverflowError(ctx._exact_overflow_msg) + raise ValueError("Arguments need to be mpf or mpc compatible numbers") + + def fmul(ctx, x, y, **kwargs): + """ + Multiplies the numbers *x* and *y*, giving a floating-point result, + optionally using a custom precision and rounding mode. + + See the documentation of :func:`~mpmath.fadd` for a detailed description + of how to specify precision and rounding. + + **Examples** + + The result is an mpmath number:: + + >>> from mpmath import fmul, mpf, mpc + >>> fmul(2, 5.0) + mpf('10.0') + >>> fmul(0.5j, 0.5) + mpc(real='0.0', imag='0.25') + + Avoiding roundoff:: + + >>> x, y = 10**10+1, 10**15+1 + >>> print(x*y) + 10000000001000010000000001 + >>> print(mpf(x) * mpf(y)) + 1.0000000001e+25 + >>> print(int(mpf(x) * mpf(y))) + 10000000001000011026399232 + >>> print(int(fmul(x, y))) + 10000000001000011026399232 + >>> print(int(fmul(x, y, dps=25))) + 10000000001000010000000001 + >>> print(int(fmul(x, y, exact=True))) + 10000000001000010000000001 + + Exact multiplication with complex numbers can be inefficient and may + be impossible to perform with large magnitude differences between + real and imaginary parts:: + + >>> x = 1+2j + >>> y = mpc(2, '1e-100000000000000000000') + >>> fmul(x, y) + mpc(real='2.0', imag='4.0') + >>> fmul(x, y, rounding='u') + mpc(real='2.0', imag='4.0000000000000009') + >>> fmul(x, y, exact=True) + Traceback (most recent call last): + ... + OverflowError: the exact result does not fit in memory + + """ + prec, rounding = ctx._parse_prec(kwargs) + x = ctx.convert(x) + y = ctx.convert(y) + try: + if hasattr(x, '_mpf_'): + if hasattr(y, '_mpf_'): + return ctx.make_mpf(mpf_mul(x._mpf_, y._mpf_, prec, rounding)) + if hasattr(y, '_mpc_'): + return ctx.make_mpc(mpc_mul_mpf(y._mpc_, x._mpf_, prec, rounding)) + if hasattr(x, '_mpc_'): + if hasattr(y, '_mpf_'): + return ctx.make_mpc(mpc_mul_mpf(x._mpc_, y._mpf_, prec, rounding)) + if hasattr(y, '_mpc_'): + return ctx.make_mpc(mpc_mul(x._mpc_, y._mpc_, prec, rounding)) + except (ValueError, OverflowError): + raise OverflowError(ctx._exact_overflow_msg) + raise ValueError("Arguments need to be mpf or mpc compatible numbers") + + def fdiv(ctx, x, y, **kwargs): + """ + Divides the numbers *x* and *y*, giving a floating-point result, + optionally using a custom precision and rounding mode. + + See the documentation of :func:`~mpmath.fadd` for a detailed description + of how to specify precision and rounding. + + **Examples** + + The result is an mpmath number:: + + >>> from mpmath import fdiv + >>> fdiv(3, 2) + mpf('1.5') + >>> fdiv(2, 3) + mpf('0.66666666666666663') + >>> fdiv(2+4j, 0.5) + mpc(real='4.0', imag='8.0') + + The rounding direction and precision can be controlled:: + + >>> fdiv(2, 3, dps=3) # Should be accurate to at least 3 digits + mpf('0.6666259765625') + >>> fdiv(2, 3, rounding='d') + mpf('0.66666666666666663') + >>> fdiv(2, 3, prec=60) + mpf('0.66666666666666667') + >>> fdiv(2, 3, rounding='u') + mpf('0.66666666666666674') + + Checking the error of a division by performing it at higher precision:: + + >>> fdiv(2, 3) - fdiv(2, 3, prec=100) + mpf('-3.7007434154172148e-17') + + Unlike :func:`~mpmath.fadd`, :func:`~mpmath.fmul`, etc., exact division is not + allowed since the quotient of two floating-point numbers generally + does not have an exact floating-point representation. (In the + future this might be changed to allow the case where the division + is actually exact.) + + >>> fdiv(2, 3, exact=True) + Traceback (most recent call last): + ... + ValueError: division is not an exact operation + + """ + prec, rounding = ctx._parse_prec(kwargs) + if not prec: + raise ValueError("division is not an exact operation") + x = ctx.convert(x) + y = ctx.convert(y) + if hasattr(x, '_mpf_'): + if hasattr(y, '_mpf_'): + return ctx.make_mpf(mpf_div(x._mpf_, y._mpf_, prec, rounding)) + if hasattr(y, '_mpc_'): + return ctx.make_mpc(mpc_mpf_div(x._mpf_, y._mpc_, prec, rounding)) + if hasattr(x, '_mpc_'): + if hasattr(y, '_mpf_'): + return ctx.make_mpc(mpc_div_mpf(x._mpc_, y._mpf_, prec, rounding)) + if hasattr(y, '_mpc_'): + return ctx.make_mpc(mpc_div(x._mpc_, y._mpc_, prec, rounding)) + raise ValueError("Arguments need to be mpf or mpc compatible numbers") + + def nint_distance(ctx, x): + r""" + Return `(n,d)` where `n` is the nearest integer to `x` and `d` is + an estimate of `\log_2(|x-n|)`. If `d < 0`, `-d` gives the precision + (measured in bits) lost to cancellation when computing `x-n`. + + >>> from mpmath import nint_distance, mpf, mpc + >>> n, d = nint_distance(5) + >>> print(n) + 5 + >>> print(d) + -inf + >>> n, d = nint_distance(mpf(5)) + >>> print(n) + 5 + >>> print(d) + -inf + >>> n, d = nint_distance(mpf(5.00000001)) + >>> print(n) + 5 + >>> print(d) + -26 + >>> n, d = nint_distance(mpf(4.99999999)) + >>> print(n) + 5 + >>> print(d) + -26 + >>> n, d = nint_distance(mpc(5,10)) + >>> print(n) + 5 + >>> print(d) + 4 + >>> n, d = nint_distance(mpc(5,0.000001)) + >>> print(n) + 5 + >>> print(d) + -19 + + """ + typx = type(x) + if typx in int_types: + return int(x), ctx.ninf + elif typx is MPQ: + p, q = x.numerator, x.denominator + n, r = divmod(p, q) + if 2*r >= q: + n += 1 + elif not r: + return n, ctx.ninf + # log(p/q-n) = log((p-nq)/q) = log(p-nq) - log(q) + d = (p-n*q).bit_length() - q.bit_length() + return n, d + if hasattr(x, "_mpf_"): + re = x._mpf_ + im_dist = ctx.ninf + elif hasattr(x, "_mpc_"): + re, im = x._mpc_ + iman, iexp = to_man_exp(im) + if iman: + im_dist = iexp + iman.bit_length() + else: + im_dist = ctx.ninf + else: + x = ctx.convert(x) + if hasattr(x, "_mpf_") or hasattr(x, "_mpc_"): + return ctx.nint_distance(x) + else: + raise TypeError("requires an mpf/mpc") + man, exp = to_man_exp(re) + mag = exp+man.bit_length() + # |x| < 0.5 + if mag < 0: + n = 0 + re_dist = mag + elif man: + sign = man < 0 + man = abs(man) + # exact integer + if exp >= 0: + n = man << exp + re_dist = ctx.ninf + # exact half-integer + elif exp == -1: + n = (man>>1)+1 + re_dist = 0 + else: + d = (-exp-1) + t = man >> d + if t & 1: + t += 1 + man = (t<>1 # int(t)>>1 + re_dist = exp+man.bit_length() + if sign: + n = -n + else: + re_dist = ctx.ninf + n = 0 + return n, max(re_dist, im_dist) + + def fprod(ctx, factors): + r""" + Calculates a product containing a finite number of factors (for + infinite products, see :func:`~mpmath.nprod`). The factors will be + converted to mpmath numbers. + + >>> from mpmath import fprod + >>> fprod([1, 2, 0.5, 7]) + mpf('7.0') + + """ + orig = ctx.prec + try: + v = ctx.one + for p in factors: + v *= p + finally: + ctx.prec = orig + return +v + + def rand(ctx): + """ + Returns an ``mpf`` with value chosen randomly from `[0, 1)`. + The number of randomly generated bits in the mantissa is equal + to the working precision. + """ + return ctx.make_mpf(mpf_rand(ctx._prec)) + + def fraction(ctx, p, q): + """ + Given Python integers `(p, q)`, returns a lazy ``mpf`` representing + the fraction `p/q`. The value is updated with the precision. + + >>> from mpmath import fraction, mpf, mp + >>> a = fraction(1,100) + >>> b = mpf(1)/100 + >>> print(a) + 0.01 + >>> print(b) + 0.01 + >>> mp.dps = 30 + >>> print(a) # a will be accurate + 0.01 + >>> print(b) + 0.0100000000000000002081668171172 + """ + return ctx.constant(lambda prec, rnd: from_rational(p, q, prec, rnd), + '%s/%s' % (p, q)) + + def absmin(ctx, x): + return abs(ctx.convert(x)) + + def absmax(ctx, x): + return abs(ctx.convert(x)) + + def _as_points(ctx, x): + # XXX: remove this? + if hasattr(x, '_mpi_'): + a, b = x._mpi_ + return [ctx.make_mpf(a), ctx.make_mpf(b)] + return x + + ''' + def _zetasum(ctx, s, a, b): + """ + Computes sum of k^(-s) for k = a, a+1, ..., b with a, b both small + integers. + """ + a = int(a) + b = int(b) + s = ctx.convert(s) + prec, rounding = ctx._prec_rounding + if hasattr(s, '_mpf_'): + v = ctx.make_mpf(libmp.gammazeta.mpf_zetasum(s._mpf_, a, b, prec)) + elif hasattr(s, '_mpc_'): + v = ctx.make_mpc(libmp.gammazeta.mpc_zetasum(s._mpc_, a, b, prec)) + return v + ''' + + def _zetasum_fast(ctx, s, a, n, derivatives=[0], reflect=False): + if not (ctx.isint(a) and hasattr(s, "_mpc_")): + raise NotImplementedError + a = int(a) + prec = ctx._prec + xs, ys = libmp.gammazeta.mpc_zetasum(s._mpc_, a, n, derivatives, reflect, prec) + xs = [ctx.make_mpc(x) for x in xs] + ys = [ctx.make_mpc(y) for y in ys] + return xs, ys + +class PrecisionManager: + def __init__(self, ctx, precfun, dpsfun, normalize_output=False): + self.ctx = ctx + self.precfun = precfun + self.dpsfun = dpsfun + self.normalize_output = normalize_output + def __call__(self, f): + @functools.wraps(f) + def g(*args, **kwargs): + orig = self.ctx.prec + try: + if self.precfun: + self.ctx.prec = self.precfun(self.ctx.prec) + else: + self.ctx.dps = self.dpsfun(self.ctx.dps) + if self.normalize_output: + v = f(*args, **kwargs) + if type(v) is tuple: + return tuple([+a for a in v]) + return +v + else: + return f(*args, **kwargs) + finally: + self.ctx.prec = orig + return g + def __enter__(self): + self.origp = self.ctx.prec + if self.precfun: + self.ctx.prec = self.precfun(self.ctx.prec) + else: + self.ctx.dps = self.dpsfun(self.ctx.dps) + def __exit__(self, exc_type, exc_val, exc_tb): + self.ctx.prec = self.origp + return False diff --git a/mpmath/ctx_mp_python.py b/mpmath/ctx_mp_python.py new file mode 100644 index 0000000..f542c1e --- /dev/null +++ b/mpmath/ctx_mp_python.py @@ -0,0 +1,1306 @@ +import inspect +import numbers +import sys + +from . import function_docs +from .libmp import (MPZ, ComplexResult, dps_to_prec, finf, fnan, fninf, + from_float, from_int, from_man_exp, from_rational, + from_str, fzero, int_types, mpc_abs, mpc_pow, mpc_pow_int, + mpc_pow_mpf, mpf_abs, mpf_add, mpf_div, mpf_eq, mpf_ge, + mpf_gt, mpf_le, mpf_lt, mpf_mod, mpf_mul, mpf_neg, mpf_pow, + mpf_sub, prec_to_dps, round_nearest, to_float, to_int, + to_man_exp, to_rational, to_str) +from .libmp.backend import MPQ +from .libmp.libmpc import (mpc_add, mpc_add_mpf, mpc_conjugate, mpc_div, + mpc_div_mpf, mpc_hash, mpc_is_inf, mpc_is_nonzero, + mpc_mpf_div, mpc_mpf_sub, mpc_mul, mpc_mul_int, + mpc_mul_mpf, mpc_neg, mpc_pos, mpc_sub, mpc_sub_mpf, + mpc_to_complex, mpc_to_str) +from .libmp.libmpf import (format_mpc, format_mpf, from_Decimal, from_npfloat, + mpf_hash, mpf_pos, mpf_sum, to_fixed) + + +new = object.__new__ + +class mpnumeric: + """Base class for mpf and mpc.""" + +# pickling support +def _make_mpf(x): + from mpmath import mp + return mp.mpf(x) + +def _make_mpc(x, y): + from mpmath import mp + return mp.mpc(x, y) + + +class _mpf(mpnumeric): + """ + An mpf instance holds a real-valued floating-point number. mpf:s + work analogously to Python floats, but support arbitrary-precision + arithmetic. + """ + __slots__ = ['_mpf_', 'context'] + + def __new__(cls, val=fzero, **kwargs): + """A new mpf can be created from a Python float, an int, a + or a decimal string representing a number in floating-point + format.""" + ctx = cls.context + prec, rounding = ctx._prec_rounding + base = 0 + if kwargs: + prec = kwargs.get('prec', prec) + if 'dps' in kwargs: + prec = dps_to_prec(kwargs['dps']) + rounding = kwargs.get('rounding', rounding) + base = kwargs.get('base', base) + v = new(cls) + if type(val) is cls: + val = val._mpf_ + elif type(val) is tuple: + if len(val) == 4: + val = val[0], MPZ(val[1]), *val[2:] + elif len(val) == 2: + v._mpf_ = from_man_exp(val[0], val[1], prec, rounding) + return v + else: + raise ValueError + elif isinstance(val, str): + val = from_str(val, prec, rounding, base) + else: + val = cls.mpf_convert_arg(val, prec, rounding) + v._mpf_ = mpf_pos(val, prec, rounding) + return v + + @classmethod + def mpf_convert_arg(cls, x, prec, rounding): + if isinstance(x, int_types): return from_int(x) + if isinstance(x, float): return from_float(x) + ctx = cls.context + if isinstance(x, ctx.constant): return x.func(prec, rounding) + if hasattr(x, '_mpf_'): return x._mpf_ + if hasattr(x, '_mpmath_'): + t = ctx.convert(x._mpmath_(prec, rounding)) + if hasattr(t, '_mpf_'): + return t._mpf_ + if hasattr(x, '_mpi_'): + a, b = x._mpi_ + if a == b: + return a + raise ValueError("can only create mpf from zero-width interval") + if isinstance(x, numbers.Rational): return from_rational(x.numerator, + x.denominator, + prec, rounding) + if type(x).__module__ == 'decimal': + return from_Decimal(x, prec, rounding) + raise TypeError("cannot create mpf from " + repr(x)) + + @classmethod + def mpf_convert_rhs(cls, x): + try: + ctx = cls.context + r = ctx.convert(x, strings=False) + if hasattr(r, '_mpf_'): + r = r._mpf_ + return r + except (ValueError, TypeError): + return NotImplemented + + @classmethod + def mpf_convert_lhs(cls, x): + x = cls.mpf_convert_rhs(x) + if type(x) is tuple: + ctx = cls.context + return ctx.make_mpf(x) + return x + + man_exp = property(lambda self: to_man_exp(self._mpf_, signed=False)) + man = property(lambda self: self.man_exp[0]) + exp = property(lambda self: self.man_exp[1]) + bc = property(lambda self: self.man.bit_length()) + + real = property(lambda self: self) + imag = property(lambda self: self.context.zero) + + conjugate = lambda self: self + + def as_integer_ratio(self): + return to_rational(self._mpf_) + + def __reduce__(self): return _make_mpf, (self._mpf_,) + + def __repr__(self): + ctx = self.context + rounding = ctx._prec_rounding[1] + if ctx.pretty: + ndigits = (ctx._repr_digits + if ctx._pretty_repr_dps else ctx._str_digits) + return to_str(self._mpf_, ndigits, rnd=rounding) + return f"mpf({to_str(self._mpf_, ctx._repr_digits, rnd=rounding)!r})" + + def __str__(self): + ctx = self.context + rounding = ctx._prec_rounding[1] + return to_str(self._mpf_, ctx._str_digits, rnd=rounding) + + def __hash__(self): return mpf_hash(self._mpf_) + def __int__(self): return int(to_int(self._mpf_)) + + def __float__(self): + ctx = self.context + rounding = ctx._prec_rounding[1] + return to_float(self._mpf_, rnd=rounding) + + def __bool__(self): return self._mpf_ != fzero + + def __abs__(self): + mpf, new, (prec, rounding) = self._ctxdata + v = new(mpf) + v._mpf_ = mpf_abs(self._mpf_, prec, rounding) + return v + + def __pos__(self): + mpf, new, (prec, rounding) = self._ctxdata + v = new(mpf) + v._mpf_ = mpf_pos(self._mpf_, prec, rounding) + return v + + def __neg__(self): + mpf, new, (prec, rounding) = self._ctxdata + v = new(mpf) + v._mpf_ = mpf_neg(self._mpf_, prec, rounding) + return v + + def _cmp(self, other, func): + if hasattr(other, '_mpf_'): + other = other._mpf_ + else: + other = self.mpf_convert_rhs(other) + if other is NotImplemented: + return other + return func(self._mpf_, other) + + def __lt__(self, other): return self._cmp(other, mpf_lt) + def __gt__(self, other): return self._cmp(other, mpf_gt) + def __le__(self, other): return self._cmp(other, mpf_le) + def __ge__(self, other): return self._cmp(other, mpf_ge) + + def __eq__(self, other): + mpf, new, (prec, rounding) = self._ctxdata + sval = self._mpf_ + if hasattr(other, '_mpf_'): + oval = other._mpf_ + return mpf_eq(sval, oval) + if hasattr(other, '_mpc_'): + oval = other._mpc_ + return (oval[1] == fzero) and mpf_eq(oval[0], sval) + try: + ctx = mpf.context + other = ctx.convert(other, strings=False) + except TypeError: + return NotImplemented + return self.__eq__(other) + + def __add__(self, other): + mpf, new, (prec, rounding) = self._ctxdata + sval = self._mpf_ + if hasattr(other, '_mpf_'): + oval = other._mpf_ + val = mpf_add(sval, oval, prec, rounding) + obj = new(mpf) + obj._mpf_ = val + return obj + if hasattr(other, '_mpc_'): + oval = other._mpc_ + mpc = type(other) + val = mpc_add_mpf(oval, sval, prec, rounding) + obj = new(mpc) + obj._mpc_ = val + return obj + try: + ctx = mpf.context + other = ctx.convert(other, strings=False) + except TypeError: + return NotImplemented + return self.__add__(other) + __radd__ = __add__ + + def __sub__(self, other): + mpf, new, (prec, rounding) = self._ctxdata + sval = self._mpf_ + if hasattr(other, '_mpf_'): + oval = other._mpf_ + val = mpf_sub(sval, oval, prec, rounding) + obj = new(mpf) + obj._mpf_ = val + return obj + if hasattr(other, '_mpc_'): + oval = other._mpc_ + mpc = type(other) + val = mpc_mpf_sub(sval, oval, prec, rounding) + obj = new(mpc) + obj._mpc_ = val + return obj + try: + ctx = mpf.context + other = ctx.convert(other, strings=False) + except TypeError: + return NotImplemented + return self.__sub__(other) + + def __rsub__(self, other): + other = self.mpf_convert_lhs(other) + if other is NotImplemented: + return other + return other - self + + def __mul__(self, other): + mpf, new, (prec, rounding) = self._ctxdata + sval = self._mpf_ + if hasattr(other, '_mpf_'): + oval = other._mpf_ + val = mpf_mul(sval, oval, prec, rounding) + obj = new(mpf) + obj._mpf_ = val + return obj + if hasattr(other, '_mpc_'): + oval = other._mpc_ + mpc = type(other) + val = mpc_mul_mpf(oval, sval, prec, rounding) + obj = new(mpc) + obj._mpc_ = val + return obj + try: + ctx = mpf.context + other = ctx.convert(other, strings=False) + except TypeError: + return NotImplemented + return self.__mul__(other) + __rmul__ = __mul__ + + def __truediv__(self, other): + mpf, new, (prec, rounding) = self._ctxdata + sval = self._mpf_ + if hasattr(other, '_mpf_'): + oval = other._mpf_ + val = mpf_div(sval, oval, prec, rounding) + obj = new(mpf) + obj._mpf_ = val + return obj + if hasattr(other, '_mpc_'): + oval = other._mpc_ + mpc = type(other) + val = mpc_mpf_div(sval, oval, prec, rounding) + obj = new(mpc) + obj._mpc_ = val + return obj + try: + ctx = mpf.context + other = ctx.convert(other, strings=False) + except TypeError: + return NotImplemented + return self.__truediv__(other) + + def __rtruediv__(self, other): + other = self.mpf_convert_lhs(other) + if other is NotImplemented: + return other + return other / self + + def __mod__(self, other): + mpf, new, (prec, rounding) = self._ctxdata + sval = self._mpf_ + if hasattr(other, '_mpf_'): + oval = other._mpf_ + val = mpf_mod(sval, oval, prec, rounding) + obj = new(mpf) + obj._mpf_ = val + return obj + if hasattr(other, '_mpc_'): + return NotImplemented + try: + ctx = mpf.context + other = ctx.convert(other, strings=False) + except TypeError: + return NotImplemented + return self.__mod__(other) + + def __rmod__(self, other): + other = self.mpf_convert_lhs(other) + if other is NotImplemented: + return other + return other % self + + def __floordiv__(self, other): + return (self - (self % other)) / other + + def __divmod__(self, other): + mod = self % other + return (self - mod) / other, mod + + def __pow__(self, other): + mpf, new, (prec, rounding) = self._ctxdata + ctx = mpf.context + sval = self._mpf_ + if hasattr(other, '_mpf_'): + oval = other._mpf_ + try: + val = mpf_pow(sval, oval, prec, rounding) + obj = new(mpf) + obj._mpf_ = val + return obj + except ComplexResult: + if ctx.trap_complex: + raise + mpc = ctx.mpc + val = mpc_pow_mpf((sval, fzero), oval, prec, rounding) + obj = new(mpc) + obj._mpc_ = val + return obj + if hasattr(other, '_mpc_'): + oval = other._mpc_ + mpc = ctx.mpc + val = mpc_pow((sval, fzero), oval, prec, rounding) + obj = new(mpc) + obj._mpc_ = val + return obj + try: + other = ctx.convert(other, strings=False) + except TypeError: + return NotImplemented + return self.__pow__(other) + + def __rpow__(self, other): + other = self.mpf_convert_lhs(other) + if other is NotImplemented: + return other + return other ** self + + def __format__(self, format_spec): + """ + ``mpf`` objects allow for formatting similar to Python floats: + + >>> from mpmath import fp, mp, pi + >>> mp.dps = 50 + >>> format(pi, '*^60.50f') + '****3.14159265358979323846264338327950288419716939937511****' + >>> f'{10*pi:.20e}' + '3.14159265358979323846e+01' + + The format specification adopts the same general form as Python's + :external:ref:`formatspec`. All of Python's format types are + supported, with the exception of ``'n'``. + + If precision is left as default, the resulting string is exactly the + same as if printing a regular :external:class:`float`: + + >>> mp.dps = fp.dps + >>> f"{mp.mpf('1.22'):.25f}" + '1.2199999999999999733546474' + >>> f'{1.22:.25f}' + '1.2199999999999999733546474' + >>> mp.dps = 50 + >>> f"{mp.mpf('1.22'):.25f}" + '1.2200000000000000000000000' + + In addition to the normal Python features, four different kinds of + rounding are supported: + + * ``'U'``: rounding towards plus infinity + * ``'D'``: rounding towards minus infinity + * ``'Y'``: rounding away from zero + * ``'Z'``: rounding towards zero + * ``'N'``: rounding to nearest (default) + + If it's not specified, the context's rounding mode is used. + + The rounding option must be set right before the presentation type: + + >>> x = mp.mpf('-1.2345678') + >>> f'{x:.5Uf}' + '-1.23456' + >>> f'{x:.5Df}' + '-1.23457' + + Format types ``'a'`` and ``'A'`` (use uppercase digits) allow to + represent floating-point number as a C99-style hexadecimal string + ``[±][0x]h[.hhh]p±d``, where there is one hexadecimal digit before the + dot and the fractional part either is exact or the number of its + hexadecimal digits is equal to the specified precision. The exponent + ``d`` is written in decimal, it always contains at least one digit, + and it gives the power of 2 by which to multiply the coefficient. If + no digits follow the decimal point, the decimal point is also removed + unless the ``'#'`` option is specified. + + >>> f'{x:a}' + '-0x1.3c0ca2a5b1d5d0818d3359c99ff1a26f2b31063249p+0' + >>> f'{x:.10a}' + '-0x1.3c0ca2a5b2p+0' + + Format type ``'b'`` allows format number in binary: + + >>> f'{x:.15b}' + '-1.001111000000110p+0' + + Alternate form (``'#'`` option) works like for ``'a'`` type. + """ + + _, _, (prec, rounding) = self._ctxdata + ctx = self.context + return format_mpf(self._mpf_, format_spec, prec, rounding, + ctx._pretty_repr_dps) + + def sqrt(self): + ctx = self.context + return ctx.sqrt(self) + + def ae(self, other, rel_eps=None, abs_eps=None): + ctx = self.context + return ctx.almosteq(self, other, rel_eps, abs_eps) + + def to_fixed(self, prec): + return to_fixed(self._mpf_, prec) + + def __round__(self, ndigits=None): + ctx = self.context + if ctx.isfinite(self): + frac = MPQ(*self.as_integer_ratio()) + res = round(frac, ndigits) + res = ctx.convert(res) + else: + res = self + if ndigits is None: + res = int(res) + return res + + +class _constant(_mpf): + """Represents a mathematical constant with dynamic precision. + When printed or used in an arithmetic operation, a constant + is converted to a regular mpf at the working precision. A + regular mpf can also be obtained using the operation +x.""" + + def __new__(cls, func, name, docname='', _reprdps_getter=lambda: 15): + a = object.__new__(cls) + a.name = name + a.func = func + a._reprdps_getter = _reprdps_getter + a.__doc__ = getattr(function_docs, docname, '') + return a + + def __call__(self, prec=None, dps=None, rounding=None): + prec2, rounding2 = self.context._prec_rounding + if not prec: prec = prec2 + if not rounding: rounding = rounding2 + if dps: prec = dps_to_prec(dps) + return self.context.make_mpf(self.func(prec, rounding)) + + @property + def _mpf_(self): + prec, rounding = self.context._prec_rounding + return self.func(prec, rounding) + + def __repr__(self): + return "<%s: %s~>" % (self.name, self.context.nstr(self(dps=self._reprdps_getter()))) + + +class _mpc(mpnumeric): + """ + An mpc represents a complex number using a pair of mpf's (one + for the real part and another for the imaginary part.) The mpc + class behaves fairly similarly to Python's complex type. + """ + + __slots__ = ['_mpc_'] + + def __new__(cls, real=0, imag=0): + ctx = cls.context + s = object.__new__(cls) + if isinstance(real, str): + real = ctx.convert(real) + if isinstance(real, complex_types): + r_real, r_imag = real.real, real.imag + elif hasattr(real, '_mpc_'): + r_real, r_imag = real._mpc_ + else: + r_real, r_imag = real, 0 + if isinstance(imag, complex_types): + i_real, i_imag = imag.real, imag.imag + elif hasattr(imag, '_mpc_'): + i_real, i_imag = imag._mpc_ + else: + i_real, i_imag = imag, 0 + r_real, r_imag = map(ctx.mpf, [r_real, r_imag]) + i_real, i_imag = map(ctx.mpf, [i_real, i_imag]) + real = r_real - i_imag + imag = r_imag + i_real + s._mpc_ = (real._mpf_, imag._mpf_) + return s + + real = property(lambda self: self.context.make_mpf(self._mpc_[0])) + imag = property(lambda self: self.context.make_mpf(self._mpc_[1])) + + def __reduce__(self): return _make_mpc, self._mpc_ + + def __repr__(self): + ctx = self.context + if ctx.pretty: + ndigits = (ctx._repr_digits + if ctx._pretty_repr_dps else ctx._str_digits) + return f"({mpc_to_str(self._mpc_, ndigits)})" + r = repr(self.real)[4:-1] + i = repr(self.imag)[4:-1] + return f"{type(self).__name__}(real={r}, imag={i})" + + def __str__(self): + ctx = self.context + return f"({mpc_to_str(self._mpc_, ctx._str_digits)})" + + def __complex__(self): + ctx = self.context + return mpc_to_complex(self._mpc_, rnd=ctx._prec_rounding[1]) + + def __pos__(self): + mpc, new, (prec, rounding) = self._ctxdata + v = new(mpc) + v._mpc_ = mpc_pos(self._mpc_, prec, rounding) + return v + + def __abs__(self): + ctx = self.context + mpf = ctx.mpf + _, new, (prec, rounding) = self._ctxdata + v = new(mpf) + v._mpf_ = mpc_abs(self._mpc_, prec, rounding) + return v + + def __neg__(self): + mpc, new, (prec, rounding) = self._ctxdata + v = new(mpc) + v._mpc_ = mpc_neg(self._mpc_, prec, rounding) + return v + + def conjugate(self): + mpc, new, (prec, rounding) = self._ctxdata + v = new(mpc) + v._mpc_ = mpc_conjugate(self._mpc_, prec, rounding) + return v + + def __bool__(self): + return mpc_is_nonzero(self._mpc_) + + def __hash__(self): + return mpc_hash(self._mpc_) + + @classmethod + def mpc_convert_lhs(cls, x): + ctx = cls.context + try: + return ctx.convert(x, strings=False) + except (TypeError, ValueError): + return NotImplemented + + def __eq__(self, other): + if not hasattr(other, '_mpc_'): + if isinstance(other, str): + return False + other = self.mpc_convert_lhs(other) + if other is NotImplemented: + return other + return self.real == other.real and self.imag == other.imag + + def __add__(self, other): + mpc, new, (prec, rounding) = self._ctxdata + sval = self._mpc_ + if not hasattr(other, '_mpc_'): + other = self.mpc_convert_lhs(other) + if other is NotImplemented: + return other + if hasattr(other, '_mpf_'): + oval = other._mpf_ + val = mpc_add_mpf(sval, oval, prec, rounding) + obj = new(mpc) + obj._mpc_ = val + return obj + oval = other._mpc_ + val = mpc_add(sval, oval, prec, rounding) + obj = new(mpc) + obj._mpc_ = val + return obj + __radd__ = __add__ + + def __sub__(self, other): + mpc, new, (prec, rounding) = self._ctxdata + sval = self._mpc_ + if not hasattr(other, '_mpc_'): + other = self.mpc_convert_lhs(other) + if other is NotImplemented: + return other + if hasattr(other, '_mpf_'): + oval = other._mpf_ + val = mpc_sub_mpf(sval, other._mpf_, prec, rounding) + obj = new(mpc) + obj._mpc_ = val + return obj + oval = other._mpc_ + val = mpc_sub(sval, oval, prec, rounding) + obj = new(mpc) + obj._mpc_ = val + return obj + + def __rsub__(self, other): + other = self.mpc_convert_lhs(other) + if other is NotImplemented: + return other + return other - self + + def __mul__(self, other): + mpc, new, (prec, rounding) = self._ctxdata + sval = self._mpc_ + if not hasattr(other, '_mpc_'): + if isinstance(other, int_types): + val = mpc_mul_int(sval, other, prec, rounding) + obj = new(mpc) + obj._mpc_ = val + return obj + other = self.mpc_convert_lhs(other) + if other is NotImplemented: + return other + if hasattr(other, '_mpf_'): + oval = other._mpf_ + val = mpc_mul_mpf(sval, oval, prec, rounding) + obj = new(mpc) + obj._mpc_ = val + return obj + oval = other._mpc_ + val = mpc_mul(sval, oval, prec, rounding) + obj = new(mpc) + obj._mpc_ = val + return obj + + def __rmul__(self, other): + mpc, new, (prec, rounding) = self._ctxdata + if isinstance(other, int_types): + sval = self._mpc_ + val = mpc_mul_int(sval, other, prec, rounding) + obj = new(mpc) + obj._mpc_ = val + return obj + other = self.mpc_convert_lhs(other) + if other is NotImplemented: + return other + return other * self + + def __truediv__(self, other): + mpc, new, (prec, rounding) = self._ctxdata + sval = self._mpc_ + if not hasattr(other, '_mpc_'): + other = self.mpc_convert_lhs(other) + if other is NotImplemented: + return other + if hasattr(other, '_mpf_'): + oval = other._mpf_ + val = mpc_div_mpf(sval, oval, prec, rounding) + obj = new(mpc) + obj._mpc_ = val + return obj + oval = other._mpc_ + val = mpc_div(sval, oval, prec, rounding) + obj = new(mpc) + obj._mpc_ = val + return obj + + def __rtruediv__(self, other): + other = self.mpc_convert_lhs(other) + if other is NotImplemented: + return other + return other / self + + def __pow__(self, other): + mpc, new, (prec, rounding) = self._ctxdata + sval = self._mpc_ + if isinstance(other, int_types): + val = mpc_pow_int(sval, other, prec, rounding) + obj = new(mpc) + obj._mpc_ = val + return obj + other = self.mpc_convert_lhs(other) + if other is NotImplemented: + return other + if hasattr(other, '_mpf_'): + oval = other._mpf_ + val = mpc_pow_mpf(sval, oval, prec, rounding) + else: + oval = other._mpc_ + val = mpc_pow(sval, oval, prec, rounding) + obj = new(mpc) + obj._mpc_ = val + return obj + + def __rpow__(self, other): + other = self.mpc_convert_lhs(other) + if other is NotImplemented: + return other + return other ** self + + def ae(self, other, rel_eps=None, abs_eps=None): + ctx = self.context + return ctx.almosteq(self, other, rel_eps, abs_eps) + + def __format__(self, format_spec): + """ + ``mpc`` objects allow for formatting similar to Python + :external:class:`complex`, specified in :external:ref:`formatspec`. + + All ``mpf``'s format types and options are supported, with + the exception for ``'%'`` format type, ``'='`` alignment and + zero padding. + """ + ctx = self.context + _, _, (prec, rounding) = self._ctxdata + return format_mpc(self._mpc_, format_spec, prec, rounding, + ctx._pretty_repr_dps) + + +complex_types = (complex, _mpc) + + +class PythonMPContext: + def __init__(ctx): + ctx._prec_rounding = [sys.float_info.mant_dig, round_nearest] + ctx._pretty_repr_dps = False + ctx.mpf = type('mpf', (_mpf,), {}) + ctx.mpf._ctxdata = [ctx.mpf, new, ctx._prec_rounding] + ctx.mpf.context = ctx + ctx.mpc = type('mpc', (_mpc,), {}) + ctx.mpc._ctxdata = [ctx.mpc, new, ctx._prec_rounding] + ctx.mpc.context = ctx + ctx.constant = type('constant', (_constant,), {}) + ctx.constant._ctxdata = [ctx.mpf, new, ctx._prec_rounding] + ctx.constant.context = ctx + + def make_mpf(ctx, v): + a = new(ctx.mpf) + a._mpf_ = v + return a + + def make_mpc(ctx, v): + a = new(ctx.mpc) + a._mpc_ = v + return a + + def default(ctx): + ctx._prec = ctx._prec_rounding[0] = sys.float_info.mant_dig + ctx._dps = sys.float_info.dig + ctx.trap_complex = False + + def _set_prec(ctx, n): + ctx._prec = ctx._prec_rounding[0] = max(1, int(n)) + ctx._dps = prec_to_dps(n) + + def _set_dps(ctx, n): + ctx._prec = ctx._prec_rounding[0] = dps_to_prec(n) + ctx._dps = max(1, int(n)) + + def _set_rounding(ctx, r): + try: + ctx._prec_rounding[1] = ctx._parse_prec({'rounding': r})[1] + except KeyError: + raise ValueError('invalid rounding mode') + + prec = property(lambda ctx: ctx._prec, _set_prec) + dps = property(lambda ctx: ctx._dps, _set_dps) + rounding = property(lambda ctx: ctx._prec_rounding[1], _set_rounding) + + def _set_pretty_dps(ctx, v): + ctx._pretty_repr_dps = True if v == 'repr' else False + + def _get_pretty_dps(ctx): + return 'repr' if ctx._pretty_repr_dps else 'str' + + pretty_dps = property(_get_pretty_dps, _set_pretty_dps) + + def convert(ctx, x, strings=True): + """ + Converts *x* to an ``mpf`` or ``mpc``. If *x* is of type ``mpf``, + ``mpc``, ``int``, ``float``, ``complex``, the conversion + will be performed losslessly. + + If *x* is a string, the result will be rounded to the present + working precision. Strings representing fractions or complex + numbers are permitted. + + >>> from mpmath import mpmathify + >>> mpmathify(3.5) + mpf('3.5') + >>> mpmathify('2.1') + mpf('2.1000000000000001') + >>> mpmathify('3/4') + mpf('0.75') + >>> mpmathify('2+3j') + mpc(real='2.0', imag='3.0') + + """ + if type(x) in ctx.types: return x + if isinstance(x, int_types): return ctx.make_mpf(from_int(x)) + if isinstance(x, float): return ctx.make_mpf(from_float(x)) + if isinstance(x, complex): + return ctx.make_mpc((from_float(x.real), from_float(x.imag))) + if type(x).__module__ == 'numpy': return ctx.npconvert(x) + prec, rounding = ctx._prec_rounding + if hasattr(x, '_mpf_'): return ctx.make_mpf(x._mpf_) + if hasattr(x, '_mpc_'): return ctx.make_mpc(x._mpc_) + if hasattr(x, '_mpmath_'): + return ctx.convert(x._mpmath_(prec, rounding)) + if isinstance(x, numbers.Rational): + p, q = x.numerator, x.denominator + return ctx.make_mpf(from_rational(p, q, prec, rounding)) + if strings and isinstance(x, str): + try: + _mpf_ = from_str(x, prec, rounding) + return ctx.make_mpf(_mpf_) + except ValueError: + pass + if type(x).__module__ == 'decimal': + return ctx.make_mpf(from_Decimal(x, prec, rounding)) + return ctx._convert_fallback(x, strings) + + def npconvert(ctx, x): + """ + Converts *x* to an ``mpf`` or ``mpc``. *x* should be a numpy + scalar. + """ + import numpy as np + if isinstance(x, np.ndarray) and x.ndim == 0: x = x.item() + if isinstance(x, (np.integer, int)): return ctx.make_mpf(from_int(int(x))) + if isinstance(x, (np.floating, float)): return ctx.mpf(from_npfloat(x)) + if isinstance(x, (np.complexfloating, complex)): + return ctx.make_mpc((from_npfloat(x.real), from_npfloat(x.imag))) + raise TypeError("cannot create mpf from " + repr(x)) + + def isinf(ctx, x): + """ + Return *True* if the absolute value of *x* is infinite; + otherwise return *False*:: + + >>> from mpmath import isinf, inf, mpc + >>> isinf(inf) + True + >>> isinf(-inf) + True + >>> isinf(3) + False + >>> isinf(3+4j) + False + >>> isinf(mpc(3,inf)) + True + >>> isinf(mpc(inf,3)) + True + + """ + if hasattr(x, "_mpf_"): + return x._mpf_ in (finf, fninf) + if hasattr(x, "_mpc_"): + return mpc_is_inf(x._mpc_) + if isinstance(x, int_types) or isinstance(x, MPQ): + return False + x = ctx.convert(x) + return ctx.isinf(x) + + def isnormal(ctx, x): + """ + Determine whether *x* is "normal" in the sense of floating-point + representation; that is, return *False* if *x* is zero, an + infinity or NaN; otherwise return *True*. By extension, a + complex number *x* is considered "normal" if its magnitude is + normal:: + + >>> from mpmath import isnormal, inf, nan, mpc + >>> isnormal(3) + True + >>> isnormal(0) + False + >>> isnormal(inf); isnormal(-inf); isnormal(nan) + False + False + False + >>> isnormal(0+0j) + False + >>> isnormal(0+3j) + True + >>> isnormal(mpc(2,nan)) + False + """ + if hasattr(x, "_mpf_"): + if ctx.isfinite(x): + return bool(to_man_exp(x._mpf_)[0]) + return False + if hasattr(x, "_mpc_"): + re, im = x._mpc_ + re_normal = bool(re[1]) + im_normal = bool(im[1]) + if re == fzero: return im_normal + if im == fzero: return re_normal + return re_normal and im_normal + if isinstance(x, int_types) or isinstance(x, MPQ): + return bool(x) + x = ctx.convert(x) + return ctx.isnormal(x) + + def isint(ctx, x, gaussian=False): + """ + Return *True* if *x* is integer-valued; otherwise return + *False*:: + + >>> from mpmath import isint, mpf, inf + >>> isint(3) + True + >>> isint(mpf(3)) + True + >>> isint(3.2) + False + >>> isint(inf) + False + + Optionally, Gaussian integers can be checked for:: + + >>> isint(3+0j) + True + >>> isint(3+2j) + False + >>> isint(3+2j, gaussian=True) + True + + """ + if isinstance(x, int_types): + return True + if hasattr(x, "_mpf_"): + if ctx.isfinite(x): + man, exp = to_man_exp(x._mpf_) + return bool((man and exp >= 0) or x._mpf_ == fzero) + return False + if hasattr(x, "_mpc_"): + re, im = x._mpc_ + if ctx.isfinite(x): + man, exp = to_man_exp(re) + re_isint = bool((man and exp >= 0) or re == fzero) + man, exp = to_man_exp(im) + im_isint = bool((man and exp >= 0) or im == fzero) + else: + return False + if gaussian: + return re_isint and im_isint + return re_isint and im == fzero + if isinstance(x, MPQ): + p, q = x.numerator, x.denominator + return p % q == 0 + x = ctx.convert(x) + return ctx.isint(x, gaussian) + + def fsum(ctx, terms, absolute=False, squared=False): + """ + Calculates a sum containing a finite number of terms (for infinite + series, see :func:`~mpmath.nsum`). The terms will be converted to + mpmath numbers. For len(terms) > 2, this function is generally + faster and produces more accurate results than the builtin + Python function :func:`sum`. + + >>> from mpmath import fsum + >>> fsum([1, 2, 0.5, 7]) + mpf('10.5') + + With squared=True each term is squared, and with absolute=True + the absolute value of each term is used. + """ + prec, rnd = ctx._prec_rounding + real = [] + imag = [] + for term in terms: + reval = imval = 0 + if hasattr(term, "_mpf_"): + reval = term._mpf_ + elif hasattr(term, "_mpc_"): + reval, imval = term._mpc_ + else: + term = ctx.convert(term) + if hasattr(term, "_mpf_"): + reval = term._mpf_ + elif hasattr(term, "_mpc_"): + reval, imval = term._mpc_ + else: + raise NotImplementedError + if imval: + if squared: + if absolute: + real.append(mpf_mul(reval,reval)) + real.append(mpf_mul(imval,imval)) + else: + reval, imval = mpc_pow_int((reval,imval),2,prec+10) + real.append(reval) + imag.append(imval) + elif absolute: + real.append(mpc_abs((reval,imval), prec)) + else: + real.append(reval) + imag.append(imval) + else: + if squared: + reval = mpf_mul(reval, reval) + elif absolute: + reval = mpf_abs(reval) + real.append(reval) + s = mpf_sum(real, prec, rnd, absolute) + if imag: + s = ctx.make_mpc((s, mpf_sum(imag, prec, rnd))) + else: + s = ctx.make_mpf(s) + return s + + def fdot(ctx, A, B=None, conjugate=False): + r""" + Computes the dot product of the iterables `A` and `B`, + + .. math :: + + \sum_{k=0} A_k B_k. + + Alternatively, :func:`~mpmath.fdot` accepts a single iterable of pairs. + In other words, ``fdot(A,B)`` and ``fdot(zip(A,B))`` are equivalent. + The elements are automatically converted to mpmath numbers. + + With ``conjugate=True``, the elements in the second vector + will be conjugated: + + .. math :: + + \sum_{k=0} A_k \overline{B_k} + + **Examples** + + >>> from mpmath import fdot, j + >>> A = [2, 1.5, 3] + >>> B = [1, -1, 2] + >>> fdot(A, B) + mpf('6.5') + >>> list(zip(A, B)) + [(2, 1), (1.5, -1), (3, 2)] + >>> fdot(_) + mpf('6.5') + >>> A = [2, 1.5, 3j] + >>> B = [1+j, 3, -1-j] + >>> fdot(A, B) + mpc(real='9.5', imag='-1.0') + >>> fdot(A, B, conjugate=True) + mpc(real='3.5', imag='-5.0') + + """ + if B is not None: + A = zip(A, B) + prec, rnd = ctx._prec_rounding + real = [] + imag = [] + hasattr_ = hasattr + types = (ctx.mpf, ctx.mpc) + for a, b in A: + if type(a) not in types: a = ctx.convert(a) + if type(b) not in types: b = ctx.convert(b) + a_real = hasattr_(a, "_mpf_") + b_real = hasattr_(b, "_mpf_") + if a_real and b_real: + real.append(mpf_mul(a._mpf_, b._mpf_)) + continue + a_complex = hasattr_(a, "_mpc_") + b_complex = hasattr_(b, "_mpc_") + if a_real and b_complex: + aval = a._mpf_ + bre, bim = b._mpc_ + if conjugate: + bim = mpf_neg(bim) + real.append(mpf_mul(aval, bre)) + imag.append(mpf_mul(aval, bim)) + elif b_real and a_complex: + are, aim = a._mpc_ + bval = b._mpf_ + real.append(mpf_mul(are, bval)) + imag.append(mpf_mul(aim, bval)) + elif a_complex and b_complex: + #re, im = mpc_mul(a._mpc_, b._mpc_, prec+20) + are, aim = a._mpc_ + bre, bim = b._mpc_ + if conjugate: + bim = mpf_neg(bim) + real.append(mpf_mul(are, bre)) + real.append(mpf_neg(mpf_mul(aim, bim))) + imag.append(mpf_mul(are, bim)) + imag.append(mpf_mul(aim, bre)) + else: + raise NotImplementedError + s = mpf_sum(real, prec, rnd) + if imag: + s = ctx.make_mpc((s, mpf_sum(imag, prec, rnd))) + else: + s = ctx.make_mpf(s) + return s + + def _wrap_libmp_function(ctx, mpf_f, mpc_f=None, mpi_f=None, doc=""): + """ + Given a low-level mpf_ function, and optionally similar functions + for mpc_ and mpi_, defines the function as a context method. + + It is assumed that the return type is the same as that of + the input; the exception is that propagation from mpf to mpc is possible + by raising ComplexResult. + + """ + def f(x, *, prec=None, dps=None, rounding=None): + if type(x) not in ctx.types: + x = ctx.convert(x) + ctx_prec, ctx_rounding = ctx._prec_rounding + if prec and dps: + raise ValueError("both prec and dps can't be specified") + if dps: + prec = dps_to_prec(dps) + if prec is None: + prec = ctx_prec + if rounding is None: + rounding = ctx_rounding + if hasattr(x, '_mpf_'): + try: + return ctx.make_mpf(mpf_f(x._mpf_, prec, rounding)) + except ComplexResult: + # Handle propagation to complex + if ctx.trap_complex: + raise + return ctx.make_mpc(mpc_f((x._mpf_, fzero), prec, rounding)) + elif hasattr(x, '_mpc_'): + return ctx.make_mpc(mpc_f(x._mpc_, prec, rounding)) + raise NotImplementedError("%s of a %s" % (name, type(x))) + name = mpf_f.__name__[4:] + f.__doc__ = function_docs.__dict__.get(name, "Computes the %s of x" % doc) + f.__name__ = name + return f + + # Called by SpecialFunctions.__init__() + @classmethod + def _wrap_specfun(cls, name, f, wrap): + if wrap: + def f_wrapped(ctx, *args, **kwargs): + convert = ctx.convert + args = [convert(a) for a in args] + prec = ctx.prec + try: + ctx.prec += 10 + retval = f(ctx, *args, **kwargs) + finally: + ctx.prec = prec + return +retval + else: + f_wrapped = f + f_wrapped.__doc__ = function_docs.__dict__.get(name, f.__doc__) + f_wrapped.__signature__ = inspect.signature(f) + f_wrapped.__name__ = f.__name__ + setattr(cls, name, f_wrapped) + + def _convert_param(ctx, x): + if hasattr(x, "_mpc_"): + v, im = x._mpc_ + if im != fzero: + return x, 'C' + elif hasattr(x, "_mpf_"): + v = x._mpf_ + else: + if type(x) in int_types: + return int(x), 'Z' + p = None + if isinstance(x, tuple): + p, q = x + elif isinstance(x, str) and '/' in x: + p, q = x.split('/') + p = int(p) + q = int(q) + if p is not None: + if not p % q: + return p // q, 'Z' + return MPQ(p,q), 'Q' + x = ctx.convert(x) + if hasattr(x, "_mpc_"): + v, im = x._mpc_ + if im != fzero: + return x, 'C' + elif hasattr(x, "_mpf_"): + v = x._mpf_ + else: + raise NotImplementedError + man, exp = to_man_exp(v) + if man: + if exp >= -4: + if exp >= 0: + return int(man) << exp, 'Z' + p, q = int(man), (1<<(-exp)) + return MPQ(p,q), 'Q' + x = ctx.make_mpf(v) + return x, 'R' + if not exp: + return 0, 'Z' + raise NotImplementedError + + def _mpf_mag(ctx, x): + if x == fzero: + return ctx.ninf + if x in (finf, fninf, fnan): + return ctx.make_mpf(mpf_abs(x)) + man, exp = to_man_exp(x) + return exp+man.bit_length() + + def mag(ctx, x): + """ + Quick logarithmic magnitude estimate of a number. Returns an + integer or infinity `m` such that `|x| <= 2^m`. It is not + guaranteed that `m` is an optimal bound, but it will never + be too large by more than 2 (and probably not more than 1). + + **Examples** + + >>> from mpmath import mp, mag, ceil, mpf, log, inf, nan + >>> mp.pretty = True + >>> mag(10), mag(10.0), mag(mpf(10)), int(ceil(log(10,2))) + (4, 4, 4, 4) + >>> mag(10j), mag(10+10j) + (4, 5) + >>> mag(0.01), int(ceil(log(0.01,2))) + (-6, -6) + >>> mag(0), mag(inf), mag(-inf), mag(nan) + (-inf, inf, inf, nan) + + """ + if hasattr(x, "_mpf_"): + return ctx._mpf_mag(x._mpf_) + if hasattr(x, "_mpc_"): + r, i = x._mpc_ + if r == fzero: + return ctx._mpf_mag(i) + if i == fzero: + return ctx._mpf_mag(r) + return 1+max(ctx._mpf_mag(r), ctx._mpf_mag(i)) + if isinstance(x, int_types): + if x: + return x.bit_length() + return ctx.ninf + if isinstance(x, MPQ): + p, q = x.numerator, x.denominator + if p: + return 1 + p.bit_length() - q.bit_length() + return ctx.ninf + x = ctx.convert(x) + return ctx.mag(x) + + +# Register with "numbers" ABC +# We do not subclass, hence we do not use the @abstractmethod checks. While +# this is less invasive it may turn out that we do not actually support +# parts of the expected interfaces. See +# https://docs.python.org/3/library/numbers.html for list of abstract methods. +numbers.Complex.register(_mpc) +numbers.Real.register(_mpf) diff --git a/mpmath/function_docs.py b/mpmath/function_docs.py new file mode 100644 index 0000000..2550d4b --- /dev/null +++ b/mpmath/function_docs.py @@ -0,0 +1,10636 @@ +""" +Extended docstrings for functions.py +""" + + +pi = r""" +`\pi`, roughly equal to 3.141592654, represents the area of the unit +circle, the half-period of trigonometric functions, and many other +things in mathematics. + +Mpmath can evaluate `\pi` to arbitrary precision:: + + >>> from mpmath import mp, pi, sin, sinpi + >>> mp.dps = 50 + >>> mp.pretty = True + >>> +pi + 3.1415926535897932384626433832795028841971693993751 + +This shows digits 99991-100000 of `\pi` (the last digit is actually +a 4 when the decimal expansion is truncated, but here the nearest +rounding is used):: + + >>> mp.dps = 100000 + >>> str(pi)[-10:] + '5549362465' + +**Possible issues** + +:data:`pi` always rounds to the nearest floating-point +number when used. This means that exact mathematical identities +involving `\pi` will generally not be preserved in floating-point +arithmetic. In particular, multiples of :data:`pi` (except for +the trivial case ``0*pi``) are *not* the exact roots of +:func:`~mpmath.sin`, but differ roughly by the current epsilon:: + + >>> mp.dps = 15 + >>> sin(pi) + 1.22464679914735e-16 + +One solution is to use the :func:`~mpmath.sinpi` function instead:: + + >>> sinpi(1) + 0.0 + +See the documentation of trigonometric functions for additional +details. + +**References** + +* [BorweinBorwein]_ + +""" + +degree = r""" +Represents one degree of angle, `1^{\circ} = \pi/180`, or +about 0.01745329. This constant may be evaluated to arbitrary +precision:: + + >>> from mpmath import mp, degree, sin + >>> mp.dps = 50 + >>> mp.pretty = True + >>> +degree + 0.017453292519943295769236907684886127134428718885417 + +The :data:`degree` object is convenient for conversion +to radians:: + + >>> sin(30 * degree) + 0.5 + >>> asin(0.5) / degree + 30.0 +""" + +e = r""" +The transcendental number `e` = 2.718281828... is the base of the +natural logarithm (:func:`~mpmath.ln`) and of the exponential function +(:func:`~mpmath.exp`). + +Mpmath can be evaluate `e` to arbitrary precision:: + + >>> from mpmath import mp, e + >>> mp.dps = 50 + >>> mp.pretty = True + >>> +e + 2.7182818284590452353602874713526624977572470937 + +This shows digits 99991-100000 of `e` (the last digit is actually +a 5 when the decimal expansion is truncated, but here the nearest +rounding is used):: + + >>> mp.dps = 100000 + >>> str(e)[-10:] + '2100427166' + +**Possible issues** + +:data:`e` always rounds to the nearest floating-point number +when used, and mathematical identities involving `e` may not +hold in floating-point arithmetic. For example, ``ln(e)`` +might not evaluate exactly to 1. + +In particular, don't use ``e**x`` to compute the exponential +function. Use ``exp(x)`` instead; this is both faster and more +accurate. +""" + +phi = r""" +Represents the golden ratio `\phi = (1+\sqrt 5)/2`, +approximately equal to 1.6180339887. To high precision, +its value is:: + + >>> from mpmath import mp, phi, sqrt, findroot, fib, inf, limit + >>> mp.dps = 50 + >>> mp.pretty = True + >>> +phi + 1.6180339887498948482045868343656381177203091798058 + +Formulas for the golden ratio include the following:: + + >>> (1+sqrt(5))/2 + 1.6180339887498948482045868343656381177203091798058 + >>> findroot(lambda x: x**2-x-1, 1) + 1.6180339887498948482045868343656381177203091798058 + >>> limit(lambda n: fib(n+1)/fib(n), inf) + 1.6180339887498948482045868343656381177203091798058 +""" + +euler = r""" +Euler's constant or the Euler-Mascheroni constant `\gamma` += 0.57721566... is a number of central importance to +number theory and special functions. It is defined as the limit + +.. math :: + + \gamma = \lim_{n\to\infty} H_n - \log n + +where `H_n = 1 + \frac{1}{2} + \ldots + \frac{1}{n}` is a harmonic +number (see :func:`~mpmath.harmonic`). + +Evaluation of `\gamma` is supported at arbitrary precision:: + + >>> from mpmath import (mp, euler, harmonic, limit, log, inf, exp, + ... zeta, gamma, nsum, diff, nprod) + >>> mp.dps = 50 + >>> mp.pretty = True + >>> +euler + 0.57721566490153286060651209008240243104215933593992 + +We can also compute `\gamma` directly from the definition, +although this is less efficient:: + + >>> limit(lambda n: harmonic(n)-log(n), inf) + 0.57721566490153286060651209008240243104215933593992 + +This shows digits 9991-10000 of `\gamma` (the last digit is actually +a 5 when the decimal expansion is truncated, but here the nearest +rounding is used):: + + >>> mp.dps = 10000 + >>> str(euler)[-10:] + '4679858166' + +Integrals, series, and representations for `\gamma` in terms of +special functions include the following (there are many others):: + + >>> mp.dps = 25 + >>> -quad(lambda x: exp(-x)*log(x), [0,inf]) + 0.5772156649015328606065121 + >>> quad(lambda x,y: (x-1)/(1-x*y)/log(x*y), [0,1], [0,1]) + 0.5772156649015328606065121 + >>> nsum(lambda k: 1/k-log(1+1/k), [1,inf]) + 0.5772156649015328606065121 + >>> nsum(lambda k: (-1)**k*zeta(k)/k, [2,inf]) + 0.5772156649015328606065121 + >>> -diff(gamma, 1) + 0.5772156649015328606065121 + >>> limit(lambda x: 1/x-gamma(x), 0) + 0.5772156649015328606065121 + >>> limit(lambda x: zeta(x)-1/(x-1), 1) + 0.5772156649015328606065121 + >>> (log(2*pi*nprod(lambda n: + ... exp(-2+2/n)*(1+2/n)**n, [1,inf]))-3)/2 + 0.5772156649015328606065121 + +For generalizations of the identities `\gamma = -\Gamma'(1)` +and `\gamma = \lim_{x\to1} \zeta(x)-1/(x-1)`, see +:func:`~mpmath.psi` and :func:`~mpmath.stieltjes` respectively. + +**References** + +* [BorweinBailey]_ +* [Gourdon]_ + +""" + +catalan = r""" +Catalan's constant `K` = 0.91596559... is given by the infinite +series + +.. math :: + + K = \sum_{k=0}^{\infty} \frac{(-1)^k}{(2k+1)^2}. + +Mpmath can evaluate it to arbitrary precision:: + + >>> from mpmath import (mp, catalan, nsum, inf, quad, log, atan, + ... ellipk, pi, zeta) + >>> mp.dps = 50 + >>> mp.pretty = True + >>> +catalan + 0.91596559417721901505460351493238411077414937428167 + +One can also compute `K` directly from the definition, although +this is significantly less efficient:: + + >>> nsum(lambda k: (-1)**k/(2*k+1)**2, [0, inf]) + 0.91596559417721901505460351493238411077414937428167 + +This shows digits 9991-10000 of `K` (the last digit is actually +a 3 when the decimal expansion is truncated, but here the nearest +rounding is used):: + + >>> mp.dps = 10000 + >>> str(catalan)[-10:] + '9537871504' + +Catalan's constant has numerous integral representations:: + + >>> mp.dps = 50 + >>> quad(lambda x: -log(x)/(1+x**2), [0, 1]) + 0.91596559417721901505460351493238411077414937428167 + >>> quad(lambda x: atan(x)/x, [0, 1]) + 0.91596559417721901505460351493238411077414937428167 + >>> quad(lambda x: ellipk(x**2)/2, [0, 1]) + 0.91596559417721901505460351493238411077414937428167 + >>> quad(lambda x,y: 1/(1+(x*y)**2), [0, 1], [0, 1]) + 0.91596559417721901505460351493238411077414937428167 + +As well as series representations:: + + >>> pi*log(sqrt(3)+2)/8 + 3*nsum(lambda n: + ... (fac(n)/(2*n+1))**2/fac(2*n), [0, inf])/8 + 0.91596559417721901505460351493238411077414937428167 + >>> 1-nsum(lambda n: n*zeta(2*n+1)/16**n, [1,inf]) + 0.91596559417721901505460351493238411077414937428167 +""" + +khinchin = r""" +Khinchin's constant `K` = 2.68542... is a number that +appears in the theory of continued fractions. Mpmath can evaluate +it to arbitrary precision:: + + >>> from mpmath import mp, khinchin, log, quad, sincpi, exp, nsum, mpf + >>> mp.dps = 50 + >>> mp.pretty = True + >>> +khinchin + 2.6854520010653064453097148354817956938203822939945 + +An integral representation is:: + + >>> I = quad(lambda x: log((1-x**2)/sincpi(x))/x/(1+x), [0, 1]) + >>> 2*exp(1/log(2)*I) + 2.6854520010653064453097148354817956938203822939945 + +The computation of ``khinchin`` is based on an efficient +implementation of the following series:: + + >>> f = lambda n: (zeta(2*n)-1)/n*sum((-1)**(k+1)/mpf(k) + ... for k in range(1,2*int(n))) + >>> exp(nsum(f, [1,inf])/log(2)) + 2.6854520010653064453097148354817956938203822939945 +""" + +glaisher = r""" +Glaisher's constant `A`, also known as the Glaisher-Kinkelin +constant, is a number approximately equal to 1.282427129 that +sometimes appears in formulas related to gamma and zeta functions. +It is also related to the Barnes G-function (see :func:`~mpmath.barnesg`). + +The constant is defined as `A = \exp(1/12-\zeta'(-1))` where +`\zeta'(s)` denotes the derivative of the Riemann zeta function +(see :func:`~mpmath.zeta`). + +Mpmath can evaluate Glaisher's constant to arbitrary precision: + + >>> from mpmath import mp, glaisher, quad, log, gamma, pi, mpf, zeta + >>> mp.dps = 50 + >>> mp.pretty = True + >>> +glaisher + 1.282427129100622636875342568869791727767688927325 + +We can verify that the value computed by :data:`glaisher` is +correct using mpmath's facilities for numerical +differentiation and arbitrary evaluation of the zeta function: + + >>> exp(mpf(1)/12 - diff(zeta, -1)) + 1.282427129100622636875342568869791727767688927325 + +Here is an example of an integral that can be evaluated in +terms of Glaisher's constant: + + >>> mp.dps = 15 + >>> quad(lambda x: log(gamma(x)), [1, 1.5]) + -0.0428537406502909 + >>> -0.5 - 7*log(2)/24 + log(pi)/4 + 3*log(glaisher)/2 + -0.042853740650291 + +Mpmath computes Glaisher's constant by applying Euler-Maclaurin +summation to a slowly convergent series. The implementation is +reasonably efficient up to about 10,000 digits. See the source +code for additional details. + +References: +[Weisstein]_ http://mathworld.wolfram.com/Glaisher-KinkelinConstant.html +""" + +apery = r""" +Represents Apery's constant, which is the irrational number +approximately equal to 1.2020569 given by + +.. math :: + + \zeta(3) = \sum_{k=1}^\infty\frac{1}{k^3}. + +The calculation is based on an efficient hypergeometric +series. To 50 decimal places, the value is given by:: + + >>> from mpmath import mp, apery, zeta, psi, nsum, inf, exp, pi + >>> mp.dps = 50 + >>> mp.pretty = True + >>> +apery + 1.2020569031595942853997381615114499907649862923405 + +Other ways to evaluate Apery's constant using mpmath +include:: + + >>> zeta(3) + 1.2020569031595942853997381615114499907649862923405 + >>> -psi(2,1)/2 + 1.2020569031595942853997381615114499907649862923405 + >>> 8*nsum(lambda k: 1/(2*k+1)**3, [0,inf])/7 + 1.2020569031595942853997381615114499907649862923405 + >>> f = lambda k: 2/k**3/(exp(2*pi*k)-1) + >>> 7*pi**3/180 - nsum(f, [1,inf]) + 1.2020569031595942853997381615114499907649862923405 + +This shows digits 9991-10000 of Apery's constant:: + + >>> mp.dps = 10000 + >>> str(apery)[-10:] + '3189504235' +""" + +mertens = r""" +Represents the Mertens or Meissel-Mertens constant, which is the +prime number analog of Euler's constant: + +.. math :: + + B_1 = \lim_{N\to\infty} + \left(\sum_{p_k \le N} \frac{1}{p_k} - \log \log N \right) + +Here `p_k` denotes the `k`-th prime number. Other names for this +constant include the Hadamard-de la Vallee-Poussin constant or +the prime reciprocal constant. + +The following gives the Mertens constant to 50 digits:: + + >>> from mpmath import mp, mertens + >>> mp.dps = 50 + >>> mp.pretty = True + >>> +mertens + 0.2614972128476427837554268386086958590515666482612 + +References: +[Weisstein]_ http://mathworld.wolfram.com/MertensConstant.html +""" + +twinprime = r""" +Represents the twin prime constant, which is the factor `C_2` +featuring in the Hardy-Littlewood conjecture for the growth of the +twin prime counting function, + +.. math :: + + \pi_2(n) \sim 2 C_2 \frac{n}{\log^2 n}. + +It is given by the product over primes + +.. math :: + + C_2 = \prod_{p\ge3} \frac{p(p-2)}{(p-1)^2} \approx 0.66016 + +Computing `C_2` to 50 digits:: + + >>> from mpmath import mp, twinprime + >>> mp.dps = 50 + >>> mp.pretty = True + >>> +twinprime + 0.66016181584686957392781211001455577843262336028473 + +References: +[Weisstein]_ http://mathworld.wolfram.com/TwinPrimesConstant.html +""" + +ln = r""" +Computes the natural logarithm of `x`, `\ln x`. +See :func:`~mpmath.log` for additional documentation.""" + +sqrt = r""" +``sqrt(x)`` gives the principal square root of `x`, `\sqrt x`. +For positive real numbers, the principal root is simply the +positive square root. For arbitrary complex numbers, the principal +square root is defined to satisfy `\sqrt x = \exp(\log(x)/2)`. +The function thus has a branch cut along the negative half real axis. + +For all mpmath numbers ``x``, calling ``sqrt(x)`` is equivalent to +performing ``x**0.5``. + +**Examples** + +Basic examples and limits:: + + >>> from mpmath import mp, sqrt, inf, iv + >>> mp.pretty = True + >>> sqrt(10) + 3.16227766016838 + >>> sqrt(100) + 10.0 + >>> sqrt(-4) + (0.0 + 2.0j) + >>> sqrt(1+1j) + (1.09868411346781 + 0.455089860562227j) + >>> sqrt(inf) + inf + +Square root evaluation is fast at huge precision:: + + >>> mp.dps = 50000 + >>> a = sqrt(3) + >>> str(a)[-10:] + '9329332815' + +``mpmath.iv.sqrt()`` supports interval arguments:: + + >>> iv.pretty = True + >>> iv.sqrt([16,100]) + [4.0, 10.0] + >>> iv.sqrt(2) + [1.4142135623730949234, 1.4142135623730951455] + >>> iv.sqrt(2) ** 2 + [1.9999999999999995559, 2.0000000000000004441] + +""" + +cbrt = r""" +``cbrt(x)`` computes the cube root of `x`, `x^{1/3}`. This +function is faster and more accurate than raising to a floating-point +fraction:: + + >>> from mpmath import mpf, cbrt, mp + >>> 125**(mpf(1)/3) + mpf('4.9999999999999991') + >>> cbrt(125) + mpf('5.0') + +Every nonzero complex number has three cube roots. This function +returns the cube root defined by `\exp(\log(x)/3)` where the +principal branch of the natural logarithm is used. Note that this +does not give a real cube root for negative real numbers:: + + >>> mp.pretty = True + >>> cbrt(-1) + (0.5 + 0.866025403784439j) +""" + +exp = r""" +Computes the exponential function, + +.. math :: + + \exp(x) = e^x = \sum_{k=0}^{\infty} \frac{x^k}{k!}. + +For complex numbers, the exponential function also satisfies + +.. math :: + + \exp(x+yi) = e^x (\cos y + i \sin y). + +**Basic examples** + +Some values of the exponential function:: + + >>> from mpmath import (mp, exp, inf, iv, pi, j, chop, nprint, taylor, + ... diff, quad, limit, odefun, fac, nsum, cosh, sinh) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> exp(0) + 1.0 + >>> exp(1) + 2.718281828459045235360287 + >>> exp(-1) + 0.3678794411714423215955238 + >>> exp(inf) + inf + >>> exp(-inf) + 0.0 + +Arguments can be arbitrarily large:: + + >>> exp(10000) + 8.806818225662921587261496e+4342 + >>> exp(-10000) + 1.135483865314736098540939e-4343 + +Evaluation is supported for interval arguments via +``mpmath.iv.exp()``:: + + >>> iv.dps = 25 + >>> iv.pretty = True + >>> iv.exp([-inf,0]) + [0.0, 1.0] + >>> iv.exp([0,1]) + [1.0, 2.71828182845904523536028749558] + +The exponential function can be evaluated efficiently to arbitrary +precision:: + + >>> mp.dps = 10000 + >>> exp(pi) + 23.140692632779269005729...8984304016040616 + +**Functional properties** + +Numerical verification of Euler's identity for the complex +exponential function:: + + >>> mp.dps = 15 + >>> exp(j*pi)+1 + (0.0 + 1.22464679914735e-16j) + >>> chop(exp(j*pi)+1) + 0.0 + +This recovers the coefficients (reciprocal factorials) in the +Maclaurin series expansion of exp:: + + >>> nprint(taylor(exp, 0, 5)) + [1.0, 1.0, 0.5, 0.166667, 0.0416667, 0.00833333] + +The exponential function is its own derivative and antiderivative:: + + >>> exp(pi) + 23.1406926327793 + >>> diff(exp, pi) + 23.1406926327793 + >>> quad(exp, [-inf, pi]) + 23.1406926327793 + +The exponential function can be evaluated using various methods, +including direct summation of the series, limits, and solving +the defining differential equation:: + + >>> nsum(lambda k: pi**k/fac(k), [0,inf]) + 23.1406926327793 + >>> limit(lambda k: (1+pi/k)**k, inf) + 23.1406926327793 + >>> odefun(lambda t, x: x, 0, 1)(pi) + 23.1406926327793 +""" + +cosh = r""" +Computes the hyperbolic cosine of `x`, +`\cosh(x) = (e^x + e^{-x})/2`. Values and limits include:: + + >>> from mpmath import mp, cosh, inf, chop, taylor, nprint, cos + >>> mp.dps = 25 + >>> mp.pretty = True + >>> cosh(0) + 1.0 + >>> cosh(1) + 1.543080634815243778477906 + >>> cosh(-inf), cosh(+inf) + (inf, inf) + +The hyperbolic cosine is an even, convex function with +a global minimum at `x = 0`, having a Maclaurin series +that starts:: + + >>> nprint(chop(taylor(cosh, 0, 5))) + [1.0, 0.0, 0.5, 0.0, 0.0416667, 0.0] + +Generalized to complex numbers, the hyperbolic cosine is +equivalent to a cosine with the argument rotated +in the imaginary direction, or `\cosh x = \cos ix`:: + + >>> cosh(2+3j) + (-3.724545504915322565473971 + 0.5118225699873846088344638j) + >>> cos(3-2j) + (-3.724545504915322565473971 + 0.5118225699873846088344638j) +""" + +sinh = r""" +Computes the hyperbolic sine of `x`, +`\sinh(x) = (e^x - e^{-x})/2`. Values and limits include:: + + >>> from mpmath import mp, sinh, inf, chop, taylor, nprint, j + >>> mp.dps = 25 + >>> mp.pretty = True + >>> sinh(0) + 0.0 + >>> sinh(1) + 1.175201193643801456882382 + >>> sinh(-inf), sinh(+inf) + (-inf, inf) + +The hyperbolic sine is an odd function, with a Maclaurin +series that starts:: + + >>> nprint(chop(taylor(sinh, 0, 5))) + [0.0, 1.0, 0.0, 0.166667, 0.0, 0.00833333] + +Generalized to complex numbers, the hyperbolic sine is +essentially a sine with a rotation `i` applied to +the argument; more precisely, `\sinh x = -i \sin ix`:: + + >>> sinh(2+3j) + (-3.590564589985779952012565 + 0.5309210862485198052670401j) + >>> j*sin(3-2j) + (-3.590564589985779952012565 + 0.5309210862485198052670401j) +""" + +tanh = r""" +Computes the hyperbolic tangent of `x`, +`\tanh(x) = \sinh(x)/\cosh(x)`. Values and limits include:: + + >>> from mpmath import mp, tanh, inf, nprint, chop, taylor, j + >>> mp.dps = 25 + >>> mp.pretty = True + >>> tanh(0) + 0.0 + >>> tanh(1) + 0.7615941559557648881194583 + >>> tanh(-inf), tanh(inf) + (-1.0, 1.0) + +The hyperbolic tangent is an odd, sigmoidal function, similar +to the inverse tangent and error function. Its Maclaurin +series is:: + + >>> nprint(chop(taylor(tanh, 0, 5))) + [0.0, 1.0, 0.0, -0.333333, 0.0, 0.133333] + +Generalized to complex numbers, the hyperbolic tangent is +essentially a tangent with a rotation `i` applied to +the argument; more precisely, `\tanh x = -i \tan ix`:: + + >>> tanh(2+3j) + (0.9653858790221331242784803 - 0.009884375038322493720314034j) + >>> j*tan(3-2j) + (0.9653858790221331242784803 - 0.009884375038322493720314034j) +""" + +cos = r""" +Computes the cosine of `x`, `\cos(x)`. + + >>> from mpmath import mp, cos, pi, inf, nprint, chop, taylor, iv + >>> mp.dps = 25 + >>> mp.pretty = True + >>> cos(pi/3) + 0.5 + >>> cos(100000001) + -0.9802850113244713353133243 + >>> cos(2+3j) + (-4.189625690968807230132555 - 9.109227893755336597979197j) + >>> cos(inf) + nan + >>> nprint(chop(taylor(cos, 0, 6))) + [1.0, 0.0, -0.5, 0.0, 0.0416667, 0.0, -0.00138889] + +Intervals are supported via ``mpmath.iv.cos()``:: + + >>> iv.dps = 25 + >>> iv.pretty = True + >>> iv.cos([0,1]) + [0.540302305868139717400936602301, 1.0] + >>> iv.cos([0,2]) + [-0.41614683654714238699756823214, 1.0] +""" + +sin = r""" +Computes the sine of `x`, `\sin(x)`. + + >>> from mpmath import mp, sin, inf, nprint, chop, taylor, iv + >>> mp.dps = 25 + >>> mp.pretty = True + >>> sin(pi/3) + 0.8660254037844386467637232 + >>> sin(100000001) + 0.1975887055794968911438743 + >>> sin(2+3j) + (9.1544991469114295734673 - 4.168906959966564350754813j) + >>> sin(inf) + nan + >>> nprint(chop(taylor(sin, 0, 6))) + [0.0, 1.0, 0.0, -0.166667, 0.0, 0.00833333, 0.0] + +Intervals are supported via ``mpmath.iv.sin()``:: + + >>> iv.dps = 25 + >>> iv.pretty = True + >>> iv.sin([0,1]) + [0.0, 0.841470984807896506652502331201] + >>> iv.sin([0,2]) + [0.0, 1.0] +""" + +tan = r""" +Computes the tangent of `x`, `\tan(x) = \frac{\sin(x)}{\cos(x)}`. +The tangent function is singular at `x = (n+1/2)\pi`, but +``tan(x)`` always returns a finite result since `(n+1/2)\pi` +cannot be represented exactly using floating-point arithmetic. + + >>> from mpmath import mp, iv, tan, pi, inf, nprint, chop, taylor + >>> mp.dps = 25 + >>> mp.pretty = True + >>> tan(pi/3) + 1.732050807568877293527446 + >>> tan(100000001) + -0.2015625081449864533091058 + >>> tan(2+3j) + (-0.003764025641504248292751221 + 1.003238627353609801446359j) + >>> tan(inf) + nan + >>> nprint(chop(taylor(tan, 0, 6))) + [0.0, 1.0, 0.0, 0.333333, 0.0, 0.133333, 0.0] + +Intervals are supported via ``mpmath.iv.tan()``:: + + >>> iv.dps = 25 + >>> iv.pretty = True + >>> iv.tan([0,1]) + [0.0, 1.55740772465490223050697482944] + >>> iv.tan([0,2]) # Interval includes a singularity + [-inf, inf] +""" + +sec = r""" +Computes the secant of `x`, `\mathrm{sec}(x) = \frac{1}{\cos(x)}`. +The secant function is singular at `x = (n+1/2)\pi`, but +``sec(x)`` always returns a finite result since `(n+1/2)\pi` +cannot be represented exactly using floating-point arithmetic. + + >>> from mpmath import mp, pi, sec, inf, nprint, chop, taylor, iv + >>> mp.dps = 25 + >>> mp.pretty = True + >>> sec(pi/3) + 2.0 + >>> sec(10000001) + -1.184723164360392819100265 + >>> sec(2+3j) + (-0.04167496441114427004834991 + 0.0906111371962375965296612j) + >>> sec(inf) + nan + >>> nprint(chop(taylor(sec, 0, 6))) + [1.0, 0.0, 0.5, 0.0, 0.208333, 0.0, 0.0847222] + +Intervals are supported via ``mpmath.iv.sec()``:: + + >>> iv.dps = 25 + >>> iv.pretty = True + >>> iv.sec([0,1]) + [1.0, 1.85081571768092561791175326276] + >>> iv.sec([0,2]) # Interval includes a singularity + [-inf, inf] +""" + +csc = r""" +Computes the cosecant of `x`, `\mathrm{csc}(x) = \frac{1}{\sin(x)}`. +This cosecant function is singular at `x = n \pi`, but with the +exception of the point `x = 0`, ``csc(x)`` returns a finite result +since `n \pi` cannot be represented exactly using floating-point +arithmetic. + + >>> from mpmath import mp, csc, inf, iv, pi + >>> mp.dps = 25 + >>> mp.pretty = True + >>> csc(pi/3) + 1.154700538379251529018298 + >>> csc(10000001) + -1.864910497503629858938891 + >>> csc(2+3j) + (0.09047320975320743980579048 + 0.04120098628857412646300981j) + >>> csc(inf) + nan + +Intervals are supported via ``mpmath.iv.csc()``:: + + >>> iv.dps = 25 + >>> iv.pretty = True + >>> iv.csc([0,1]) # Interval includes a singularity + [1.18839510577812121626159943988, inf] + >>> iv.csc([0,2]) + [1.0, inf] +""" + +cot = r""" +Computes the cotangent of `x`, +`\mathrm{cot}(x) = \frac{1}{\tan(x)} = \frac{\cos(x)}{\sin(x)}`. +This cotangent function is singular at `x = n \pi`, but with the +exception of the point `x = 0`, ``cot(x)`` returns a finite result +since `n \pi` cannot be represented exactly using floating-point +arithmetic. + + >>> from mpmath import mp, cot, pi, inf, iv + >>> mp.dps = 25 + >>> mp.pretty = True + >>> cot(pi/3) + 0.5773502691896257645091488 + >>> cot(10000001) + 1.574131876209625656003562 + >>> cot(2+3j) + (-0.003739710376336956660117409 - 0.9967577965693583104609688j) + >>> cot(inf) + nan + +Intervals are supported via ``mpmath.iv.cot()``:: + + >>> iv.dps = 25 + >>> iv.pretty = True + >>> iv.cot([0,1]) # Interval includes a singularity + [0.642092615934330703006419974862, inf] + >>> iv.cot([1,2]) + [-inf, inf] +""" + +acos = r""" +Computes the inverse cosine or arccosine of `x`, `\cos^{-1}(x)`. +Since `-1 \le \cos(x) \le 1` for real `x`, the inverse +cosine is real-valued only for `-1 \le x \le 1`. On this interval, +:func:`~mpmath.acos` is defined to be a monotonically decreasing +function assuming values between `+\pi` and `0`. + +Basic values are:: + + >>> from mpmath import mp, acos, nprint, chop, taylort, cos + >>> mp.dps = 25 + >>> mp.pretty = True + >>> acos(-1) + 3.141592653589793238462643 + >>> acos(0) + 1.570796326794896619231322 + >>> acos(1) + 0.0 + >>> nprint(chop(taylor(acos, 0, 6))) + [1.5708, -1.0, 0.0, -0.166667, 0.0, -0.075, 0.0] + +:func:`~mpmath.acos` is defined so as to be a proper inverse function of +`\cos(\theta)` for `0 \le \theta < \pi`. +We have `\cos(\cos^{-1}(x)) = x` for all `x`, but +`\cos^{-1}(\cos(x)) = x` only for `0 \le \Re[x] < \pi`:: + + >>> for x in [1, 10, -1, 2+3j, 10+3j]: + ... print("%s %s" % (cos(acos(x)), acos(cos(x)))) + ... + 1.0 1.0 + (10.0 + 0.0j) 2.566370614359172953850574 + -1.0 1.0 + (2.0 + 3.0j) (2.0 + 3.0j) + (10.0 + 3.0j) (2.566370614359172953850574 - 3.0j) + +The inverse cosine has two branch points: `x = \pm 1`. :func:`~mpmath.acos` +places the branch cuts along the line segments `(-\infty, -1)` and +`(+1, +\infty)`. In general, + +.. math :: + + \cos^{-1}(x) = \frac{\pi}{2} + i \log\left(ix + \sqrt{1-x^2} \right) + +where the principal-branch log and square root are implied. +""" + +asin = r""" +Computes the inverse sine or arcsine of `x`, `\sin^{-1}(x)`. +Since `-1 \le \sin(x) \le 1` for real `x`, the inverse +sine is real-valued only for `-1 \le x \le 1`. +On this interval, it is defined to be a monotonically increasing +function assuming values between `-\pi/2` and `\pi/2`. + +Basic values are:: + + >>> from mpmath import mp, asin, nprint, chop, taylor, sin + >>> mp.dps = 25 + >>> mp.pretty = True + >>> asin(-1) + -1.570796326794896619231322 + >>> asin(0) + 0.0 + >>> asin(1) + 1.570796326794896619231322 + >>> nprint(chop(taylor(asin, 0, 6))) + [0.0, 1.0, 0.0, 0.166667, 0.0, 0.075, 0.0] + +:func:`~mpmath.asin` is defined so as to be a proper inverse function of +`\sin(\theta)` for `-\pi/2 < \theta < \pi/2`. +We have `\sin(\sin^{-1}(x)) = x` for all `x`, but +`\sin^{-1}(\sin(x)) = x` only for `-\pi/2 < \Re[x] < \pi/2`:: + + >>> for x in [1, 10, -1, 1+3j, -2+3j]: + ... print("%s %s" % (chop(sin(asin(x))), asin(sin(x)))) + ... + 1.0 1.0 + 10.0 -0.5752220392306202846120698 + -1.0 -1.0 + (1.0 + 3.0j) (1.0 + 3.0j) + (-2.0 + 3.0j) (-1.141592653589793238462643 - 3.0j) + +The inverse sine has two branch points: `x = \pm 1`. :func:`~mpmath.asin` +places the branch cuts along the line segments `(-\infty, -1)` and +`(+1, +\infty)`. In general, + +.. math :: + + \sin^{-1}(x) = -i \log\left(ix + \sqrt{1-x^2} \right) + +where the principal-branch log and square root are implied. +""" + +atan = r""" +Computes the inverse tangent or arctangent of `x`, `\tan^{-1}(x)`. +This is a real-valued function for all real `x`, with range +`(-\pi/2, \pi/2)`. + +Basic values are:: + + >>> from mpmath import mp, inf, atan, nprint, chop + >>> mp.dps = 25 + >>> mp.pretty = True + >>> atan(-inf) + -1.570796326794896619231322 + >>> atan(-1) + -0.7853981633974483096156609 + >>> atan(0) + 0.0 + >>> atan(1) + 0.7853981633974483096156609 + >>> atan(inf) + 1.570796326794896619231322 + >>> nprint(chop(taylor(atan, 0, 6))) + [0.0, 1.0, 0.0, -0.333333, 0.0, 0.2, 0.0] + +The inverse tangent is often used to compute angles. However, +the atan2 function is often better for this as it preserves sign +(see :func:`~mpmath.atan2`). + +:func:`~mpmath.atan` is defined so as to be a proper inverse function of +`\tan(\theta)` for `-\pi/2 < \theta < \pi/2`. +We have `\tan(\tan^{-1}(x)) = x` for all `x`, but +`\tan^{-1}(\tan(x)) = x` only for `-\pi/2 < \Re[x] < \pi/2`:: + + >>> mp.dps = 25 + >>> for x in [1, 10, -1, 1+3j, -2+3j]: + ... print("%s %s" % (tan(atan(x)), atan(tan(x)))) + ... + 1.0 1.0 + 10.0 0.5752220392306202846120698 + -1.0 -1.0 + (1.0 + 3.0j) (1.000000000000000000000001 + 3.0j) + (-2.0 + 3.0j) (1.141592653589793238462644 + 3.0j) + +The inverse tangent has two branch points: `x = \pm i`. :func:`~mpmath.atan` +places the branch cuts along the line segments `(-i \infty, -i)` and +`(+i, +i \infty)`. In general, + +.. math :: + + \tan^{-1}(x) = \frac{i}{2}\left(\log(1-ix)-\log(1+ix)\right) + +where the principal-branch log is implied. +""" + +acot = r"""Computes the inverse cotangent of `x`, +`\mathrm{cot}^{-1}(x) = \tan^{-1}(1/x)`.""" + +asec = r"""Computes the inverse secant of `x`, +`\mathrm{sec}^{-1}(x) = \cos^{-1}(1/x)`.""" + +acsc = r"""Computes the inverse cosecant of `x`, +`\mathrm{csc}^{-1}(x) = \sin^{-1}(1/x)`.""" + +coth = r"""Computes the hyperbolic cotangent of `x`, +`\mathrm{coth}(x) = \frac{\cosh(x)}{\sinh(x)}`. +""" + +sech = r"""Computes the hyperbolic secant of `x`, +`\mathrm{sech}(x) = \frac{1}{\cosh(x)}`. +""" + +csch = r"""Computes the hyperbolic cosecant of `x`, +`\mathrm{csch}(x) = \frac{1}{\sinh(x)}`. +""" + +acosh = r"""Computes the inverse hyperbolic cosine of `x`, +`\mathrm{cosh}^{-1}(x) = \log(x+\sqrt{x+1}\sqrt{x-1})`. +""" + +asinh = r"""Computes the inverse hyperbolic sine of `x`, +`\mathrm{sinh}^{-1}(x) = \log(x+\sqrt{1+x^2})`. +""" + +atanh = r"""Computes the inverse hyperbolic tangent of `x`, +`\mathrm{tanh}^{-1}(x) = \frac{1}{2}\left(\log(1+x)-\log(1-x)\right)`. +""" + +acoth = r"""Computes the inverse hyperbolic cotangent of `x`, +`\mathrm{coth}^{-1}(x) = \tanh^{-1}(1/x)`.""" + +asech = r"""Computes the inverse hyperbolic secant of `x`, +`\mathrm{sech}^{-1}(x) = \cosh^{-1}(1/x)`.""" + +acsch = r"""Computes the inverse hyperbolic cosecant of `x`, +`\mathrm{csch}^{-1}(x) = \sinh^{-1}(1/x)`.""" + + + +sinpi = r""" +Computes `\sin(\pi x)`, more accurately than the expression +``sin(pi*x)``:: + + >>> from mpmath import mp, sinpi, pi, sin + >>> mp.pretty = True + >>> sinpi(10**10), sin(pi*(10**10)) + (0.0, -2.23936276195592e-6) + >>> sinpi(10**10+0.5), sin(pi*(10**10+0.5)) + (1.0, 0.999999999998721) +""" + +cospi = r""" +Computes `\cos(\pi x)`, more accurately than the expression +``cos(pi*x)``:: + + >>> from mpmath import mp, cospi, cos, pi + >>> mp.pretty = True + >>> cospi(10**10), cos(pi*(10**10)) + (1.0, 0.999999999997493) + >>> cospi(10**10+0.5), cos(pi*(10**10+0.5)) + (0.0, 1.59960492420134e-6) +""" + +sinc = r""" +``sinc(x)`` computes the unnormalized sinc function, defined as + +.. math :: + + \mathrm{sinc}(x) = \begin{cases} + \sin(x)/x, & \mbox{if } x \ne 0 \\ + 1, & \mbox{if } x = 0. + \end{cases} + +See :func:`~mpmath.sincpi` for the normalized sinc function. + +Simple values and limits include:: + + >>> from mpmath import mp, sinc, inf, quad, si + >>> mp.pretty = True + >>> sinc(0) + 1.0 + >>> sinc(1) + 0.841470984807897 + >>> sinc(inf) + 0.0 + +The integral of the sinc function is the sine integral Si:: + + >>> quad(sinc, [0, 1]) + 0.946083070367183 + >>> si(1) + 0.946083070367183 +""" + +sincpi = r""" +``sincpi(x)`` computes the normalized sinc function, defined as + +.. math :: + + \mathrm{sinc}_{\pi}(x) = \begin{cases} + \sin(\pi x)/(\pi x), & \mbox{if } x \ne 0 \\ + 1, & \mbox{if } x = 0. + \end{cases} + +Equivalently, we have +`\mathrm{sinc}_{\pi}(x) = \mathrm{sinc}(\pi x)`. + +The normalization entails that the function integrates +to unity over the entire real line:: + + >>> from mpmath import mp, inf, quadosc, sincpi + >>> mp.pretty = True + >>> quadosc(sincpi, [-inf, inf], period=2.0) + 1.0 + +Like, :func:`~mpmath.sinpi`, :func:`~mpmath.sincpi` is evaluated accurately +at its roots:: + + >>> sincpi(10) + 0.0 +""" + +expj = r""" +Convenience function for computing `e^{ix}`:: + + >>> from mpmath import mp, expj, j + >>> mp.dps = 25 + >>> mp.pretty = True + >>> expj(0) + (1.0 + 0.0j) + >>> expj(-1) + (0.5403023058681397174009366 - 0.8414709848078965066525023j) + >>> expj(j) + (0.3678794411714423215955238 + 0.0j) + >>> expj(1+j) + (0.1987661103464129406288032 + 0.3095598756531121984439128j) +""" + +expjpi = r""" +Convenience function for computing `e^{i \pi x}`. +Evaluation is accurate near zeros (see also :func:`~mpmath.cospi`, +:func:`~mpmath.sinpi`):: + + >>> from mpmath import mp, expjpi, j + >>> mp.dps = 25 + >>> mp.pretty = True + >>> expjpi(0) + (1.0 + 0.0j) + >>> expjpi(1) + (-1.0 + 0.0j) + >>> expjpi(0.5) + (0.0 + 1.0j) + >>> expjpi(-1) + (-1.0 + 0.0j) + >>> expjpi(j) + (0.04321391826377224977441774 + 0.0j) + >>> expjpi(1+j) + (-0.04321391826377224977441774 + 0.0j) +""" + +floor = r""" +Computes the floor of `x`, `\lfloor x \rfloor`, defined as +the largest integer less than or equal to `x`:: + + >>> from mpmath import floor + >>> floor(3.5) + mpf('3.0') + +.. note :: + + :func:`~mpmath.floor`, :func:`~mpmath.ceil` and :func:`~mpmath.nint` return a + floating-point number, not a Python ``int``. If `\lfloor x \rfloor` is + too large to be represented exactly at the present working precision, + the result will be rounded, not necessarily in the direction + implied by the mathematical definition of the function. + +To avoid rounding, use *prec=0*:: + + >>> print(int(floor(10**30+1))) + 1000000000000000019884624838656 + >>> print(int(floor(10**30+1, prec=0))) + 1000000000000000000000000000001 + +The floor function is defined for complex numbers and +acts on the real and imaginary parts separately:: + + >>> floor(3.25+4.75j) + mpc(real='3.0', imag='4.0') +""" + +ceil = r""" +Computes the ceiling of `x`, `\lceil x \rceil`, defined as +the smallest integer greater than or equal to `x`:: + + >>> from mpmath import ceil + >>> ceil(3.5) + mpf('4.0') + +The ceiling function is defined for complex numbers and +acts on the real and imaginary parts separately:: + + >>> ceil(3.25+4.75j) + mpc(real='4.0', imag='5.0') + +See notes about rounding for :func:`~mpmath.floor`. +""" + +nint = r""" +Evaluates the nearest integer function, `\mathrm{nint}(x)`. +This gives the nearest integer to `x`; on a tie, it +gives the nearest even integer:: + + >>> from mpmath import nint + >>> nint(3.2) + mpf('3.0') + >>> nint(3.8) + mpf('4.0') + >>> nint(3.5) + mpf('4.0') + >>> nint(4.5) + mpf('4.0') + +The nearest integer function is defined for complex numbers and +acts on the real and imaginary parts separately:: + + >>> nint(3.25+4.75j) + mpc(real='3.0', imag='5.0') + +See notes about rounding for :func:`~mpmath.floor`. +""" + +frac = r""" +Gives the fractional part of `x`, defined as +`\mathrm{frac}(x) = x - \lfloor x \rfloor` (see :func:`~mpmath.floor`). +In effect, this computes `x` modulo 1, or `x+n` where +`n \in \mathbb{Z}` is such that `x+n \in [0,1)`:: + + >>> from mpmath import frac, nprint, fourier, pi + >>> frac(1.25) + mpf('0.25') + >>> frac(3) + mpf('0.0') + >>> frac(-1.25) + mpf('0.75') + +For a complex number, the fractional part function applies to +the real and imaginary parts separately:: + + >>> frac(2.25+3.75j) + mpc(real='0.25', imag='0.75') + +Plotted, the fractional part function gives a sawtooth +wave. The Fourier series coefficients have a simple +form:: + + >>> nprint(fourier(lambda x: frac(x)-0.5, [0,1], 4)) + ([0.0, 0.0, 0.0, 0.0, 0.0], [0.0, -0.31831, -0.159155, -0.106103, -0.0795775]) + >>> nprint([-1/(pi*k) for k in range(1,5)]) + [-0.31831, -0.159155, -0.106103, -0.0795775] + +.. note:: + + The fractional part is sometimes defined as a symmetric + function, i.e. returning `-\mathrm{frac}(-x)` if `x < 0`. + This convention is used, for instance, by Mathematica's + ``FractionalPart``. + +""" + +sign = r""" +Returns the sign of `x`, defined as `\mathrm{sign}(x) = x / |x|` +(with the special case `\mathrm{sign}(0) = 0`):: + + >>> from mpmath import sign, mp, j + >>> sign(10) + mpf('1.0') + >>> sign(-10) + mpf('-1.0') + >>> sign(0) + mpf('0.0') + +Note that the sign function is also defined for complex numbers, +for which it gives the projection onto the unit circle:: + + >>> mp.pretty = True + >>> sign(1+j) + (0.707106781186547 + 0.707106781186547j) + +""" + +arg = r""" +Computes the complex argument (phase) of `x`, defined as the +signed angle between the positive real axis and `x` in the +complex plane:: + + >>> from mpmath import arg, mp + >>> mp.pretty = True + >>> arg(3) + 0.0 + >>> arg(3+3j) + 0.785398163397448 + >>> arg(3j) + 1.5707963267949 + >>> arg(-3) + 3.14159265358979 + >>> arg(-3j) + -1.5707963267949 + +The angle is defined to satisfy `-\pi < \arg(x) \le \pi` and +with the sign convention that a nonnegative imaginary part +results in a nonnegative argument. + +The value returned by :func:`~mpmath.arg` is an ``mpf`` instance. +""" + +fabs = r""" +Returns the absolute value of `x`, `|x|`. Unlike :func:`abs`, +:func:`~mpmath.fabs` converts non-mpmath numbers (such as ``int``) +into mpmath numbers:: + + >>> from mpmath import fabs + >>> fabs(3) + mpf('3.0') + >>> fabs(-3) + mpf('3.0') + >>> fabs(3+4j) + mpf('5.0') +""" + +re = r""" +Returns the real part of `x`, `\Re(x)`. :func:`~mpmath.re` +converts a non-mpmath number to an mpmath number:: + + >>> from mpmath import re + >>> re(3) + mpf('3.0') + >>> re(-1+4j) + mpf('-1.0') +""" + +im = r""" +Returns the imaginary part of `x`, `\Im(x)`. :func:`~mpmath.im` +converts a non-mpmath number to an mpmath number:: + + >>> from mpmath import im + >>> im(3) + mpf('0.0') + >>> im(-1+4j) + mpf('4.0') +""" + +conj = r""" +Returns the complex conjugate of `x`, `\overline{x}`. Unlike +``x.conjugate()``, :func:`~mpmath.conj` converts `x` to a mpmath number:: + + >>> from mpmath import conj + >>> conj(3) + mpf('3.0') + >>> conj(-1+4j) + mpc(real='-1.0', imag='-4.0') +""" + +polar = r""" +Returns the polar representation of the complex number `z` +as a pair `(r, \phi)` such that `z = r e^{i \phi}`:: + + >>> from mpmath import polar, mp + >>> mp.pretty = True + >>> polar(-2) + (2.0, 3.14159265358979) + >>> polar(3-4j) + (5.0, -0.927295218001612) +""" + +rect = r""" +Returns the complex number represented by polar +coordinates `(r, \phi)`:: + + >>> from mpmath import rect, mp, pi, chop, sqrt + >>> mp.pretty = True + >>> chop(rect(2, pi)) + -2.0 + >>> rect(sqrt(2), -pi/4) + (1.0 - 1.0j) +""" + +expm1 = r""" +Computes `e^x - 1`, accurately for small `x`. + +Unlike the expression ``exp(x) - 1``, ``expm1(x)`` does not suffer from +potentially catastrophic cancellation:: + + >>> from mpmath import mp, exp, expm1 + >>> mp.pretty = True + >>> exp(1e-10)-1 + 1.00000008274037e-10 + >>> print(expm1(1e-10)) + 1.00000000005e-10 + >>> exp(1e-20)-1 + 0.0 + >>> print(expm1(1e-20)) + 1.0e-20 + >>> 1/(exp(1e-20)-1) + Traceback (most recent call last): + ... + ZeroDivisionError + >>> 1/expm1(1e-20) + 1.0e+20 + +Evaluation works for extremely tiny values:: + + >>> expm1(0) + 0.0 + >>> expm1('1e-10000000') + 1.0e-10000000 + +""" + +log1p = r""" +Computes `\log(1+x)`, accurately for small `x`. + + >>> from mpmath import mp, log, log1p + >>> mp.pretty = True + >>> log(1+1e-10) + 1.00000008269037e-10 + >>> print(mp.log1p(1e-10)) + 9.9999999995e-11 + >>> mp.log1p(1e-100j) + (5.0e-201 + 1.0e-100j) + >>> mp.log1p(0) + 0.0 + +""" + + +powm1 = r""" +Computes `x^y - 1`, accurately when `x^y` is very close to 1. + +This avoids potentially catastrophic cancellation:: + + >>> from mpmath import mp, power, powm1, j, fadd + >>> mp.pretty = True + >>> power(0.99999995, 1e-10) - 1 + 0.0 + >>> powm1(0.99999995, 1e-10) + -5.00000012791934e-18 + +Powers exactly equal to 1, and only those powers, yield 0 exactly:: + + >>> powm1(-j, 4) + (0.0 + 0.0j) + >>> powm1(3, 0) + 0.0 + >>> powm1(fadd(-1, 1e-100, exact=True), 4) + -4.0e-100 + +Evaluation works for extremely tiny `y`:: + + >>> powm1(2, '1e-100000') + 6.93147180559945e-100001 + >>> powm1(j, '1e-1000') + (-1.23370055013617e-2000 + 1.5707963267949e-1000j) + +""" + +root = r""" +``root(z, n, k=0)`` computes an `n`-th root of `z`, i.e. returns a number +`r` that (up to possible approximation error) satisfies `r^n = z`. +(``nthroot()`` is available as an alias for :func:`~mpmath.root`.) + +Every complex number `z \ne 0` has `n` distinct `n`-th roots, which are +equidistant points on a circle with radius `|z|^{1/n}`, centered around the +origin. A specific root may be selected using the optional index +`k`. The roots are indexed counterclockwise, starting with `k = 0` for the root +closest to the positive real half-axis. + +The `k = 0` root is the so-called principal `n`-th root, often denoted by +`\sqrt[n]{z}` or `z^{1/n}`, and also given by `\exp(\log(z) / n)`. If `z` is +a positive real number, the principal root is just the unique positive +`n`-th root of `z`. Under some circumstances, non-principal real roots exist: +for positive real `z`, `n` even, there is a negative root given by `k = n/2`; +for negative real `z`, `n` odd, there is a negative root given by `k = (n-1)/2`. + +To obtain all roots with a simple expression, use +``[root(z,n,k) for k in range(n)]``. + +An important special case, ``root(1, n, k)`` returns the `k`-th `n`-th root of +unity, `\zeta_k = e^{2 \pi i k / n}`. Alternatively, :func:`~mpmath.unitroots` +provides a slightly more convenient way to obtain the roots of unity, +including the option to compute only the primitive roots of unity. + +Both `k` and `n` should be integers; `k` outside of ``range(n)`` will be +reduced modulo `n`. If `n` is negative, `x^{-1/n} = 1/x^{1/n}` (or +the equivalent reciprocal for a non-principal root with `k \ne 0`) is computed. + +:func:`~mpmath.root` is implemented to use Newton's method for small +`n`. At high precision, this makes `x^{1/n}` not much more +expensive than the regular exponentiation, `x^n`. For very large +`n`, :func:`~mpmath.root` falls back to use the exponential function. + +**Examples** + +``nthroot()``/:func:`~mpmath.root` is faster and more accurate than raising to a +floating-point fraction:: + + >>> from mpmath import root, mpf, nthroot, mp + >>> 16807 ** (mpf(1)/5) + mpf('7.0000000000000009') + >>> root(16807, 5) + mpf('7.0') + >>> nthroot(16807, 5) # Alias + mpf('7.0') + +A high-precision root:: + + >>> mp.dps = 50 + >>> mp.pretty = True + >>> nthroot(10, 5) + 1.584893192461113485202101373391507013269442133825 + >>> nthroot(10, 5) ** 5 + 10.0 + +Computing principal and non-principal square and cube roots:: + + >>> mp.dps = 15 + >>> root(10, 2) + 3.16227766016838 + >>> root(10, 2, 1) + -3.16227766016838 + >>> root(-10, 3) + (1.07721734501594 + 1.86579517236206j) + >>> root(-10, 3, 1) + -2.15443469003188 + >>> root(-10, 3, 2) + (1.07721734501594 - 1.86579517236206j) + +All the 7th roots of a complex number:: + + >>> for r in [root(3+4j, 7, k) for k in range(7)]: + ... print("%s %s" % (r, r**7)) + ... + (1.24747270589553 + 0.166227124177353j) (3.0 + 4.0j) + (0.647824911301003 + 1.07895435170559j) (3.0 + 4.0j) + (-0.439648254723098 + 1.17920694574172j) (3.0 + 4.0j) + (-1.19605731775069 + 0.391492658196305j) (3.0 + 4.0j) + (-1.05181082538903 - 0.691023585965793j) (3.0 + 4.0j) + (-0.115529328478668 - 1.25318497558335j) (3.0 + 4.0j) + (0.907748109144957 - 0.871672518271819j) (3.0 + 4.0j) + +Cube roots of unity:: + + >>> for k in range(3): print(root(1, 3, k)) + ... + 1.0 + (-0.5 + 0.866025403784439j) + (-0.5 - 0.866025403784439j) + +Some exact high order roots:: + + >>> root(75**210, 105) + 5625.0 + >>> root(1, 128, 96) + (0.0 - 1.0j) + >>> root(4**128, 128, 96) + (0.0 - 4.0j) + +""" + +unitroots = r""" +``unitroots(n)`` returns `\zeta_0, \zeta_1, \ldots, \zeta_{n-1}`, +all the distinct `n`-th roots of unity, as a list. If the option +*primitive=True* is passed, only the primitive roots are returned. + +Every `n`-th root of unity satisfies `(\zeta_k)^n = 1`. There are `n` distinct +roots for each `n` (`\zeta_k` and `\zeta_j` are the same when +`k = j \pmod n`), which form a regular polygon with vertices on the unit +circle. They are ordered counterclockwise with increasing `k`, starting +with `\zeta_0 = 1`. + +**Examples** + +The roots of unity up to `n = 4`:: + + >>> from mpmath import mp, nprint, unitroots, chop, fsum, nprint + >>> mp.pretty = True + >>> nprint(unitroots(1)) + [1.0] + >>> nprint(unitroots(2)) + [1.0, -1.0] + >>> nprint(unitroots(3)) + [1.0, (-0.5 + 0.866025j), (-0.5 - 0.866025j)] + >>> nprint(unitroots(4)) + [1.0, (0.0 + 1.0j), -1.0, (0.0 - 1.0j)] + +Roots of unity form a geometric series that sums to 0:: + + >>> mp.dps = 50 + >>> chop(fsum(unitroots(25))) + 0.0 + +Primitive roots up to `n = 4`:: + + >>> mp.dps = 15 + >>> nprint(unitroots(1, primitive=True)) + [1.0] + >>> nprint(unitroots(2, primitive=True)) + [-1.0] + >>> nprint(unitroots(3, primitive=True)) + [(-0.5 + 0.866025j), (-0.5 - 0.866025j)] + >>> nprint(unitroots(4, primitive=True)) + [(0.0 + 1.0j), (0.0 - 1.0j)] + +There are only four primitive 12th roots:: + + >>> nprint(unitroots(12, primitive=True)) + [(0.866025 + 0.5j), (-0.866025 + 0.5j), (-0.866025 - 0.5j), (0.866025 - 0.5j)] + +The `n`-th roots of unity form a group, the cyclic group of order `n`. +Any primitive root `r` is a generator for this group, meaning that +`r^0, r^1, \ldots, r^{n-1}` gives the whole set of unit roots (in +some permuted order):: + + >>> for r in unitroots(6): print(r) + ... + 1.0 + (0.5 + 0.866025403784439j) + (-0.5 + 0.866025403784439j) + -1.0 + (-0.5 - 0.866025403784439j) + (0.5 - 0.866025403784439j) + >>> r = unitroots(6, primitive=True)[1] + >>> for k in range(6): print(chop(r**k)) + ... + 1.0 + (0.5 - 0.866025403784439j) + (-0.5 - 0.866025403784439j) + -1.0 + (-0.5 + 0.866025403784438j) + (0.5 + 0.866025403784438j) + +The number of primitive roots equals the Euler totient function `\phi(n)`:: + + >>> [len(unitroots(n, primitive=True)) for n in range(1,20)] + [1, 1, 2, 2, 4, 2, 6, 4, 6, 4, 10, 4, 12, 6, 8, 8, 16, 6, 18] + +""" + + +log = r""" +Computes the base-`b` logarithm of `x`, `\log_b(x)`. If `b` is +unspecified, :func:`~mpmath.log` computes the natural (base `e`) logarithm +and is equivalent to :func:`~mpmath.ln`. In general, the base `b` logarithm +is defined in terms of the natural logarithm as +`\log_b(x) = \ln(x)/\ln(b)`. + +By convention, we take `\log(0) = -\infty`. + +The natural logarithm is real if `x > 0` and complex if `x < 0` or if +`x` is complex. The principal branch of the complex logarithm is +used, meaning that `\Im(\ln(x)) = -\pi < \arg(x) \le \pi`. + +**Examples** + +Some basic values and limits:: + + >>> from mpmath import mp, log, inf, quad, diff, nprint, taylor, j, pi + >>> mp.pretty = True + >>> log(1) + 0.0 + >>> log(2) + 0.693147180559945 + >>> log(1000,10) + 3.0 + >>> log(4, 16) + 0.5 + >>> log(j) + (0.0 + 1.5707963267949j) + >>> log(-1) + (0.0 + 3.14159265358979j) + >>> log(0) + -inf + >>> log(inf) + inf + +The natural logarithm is the antiderivative of `1/x`:: + + >>> quad(lambda x: 1/x, [1, 5]) + 1.6094379124341 + >>> log(5) + 1.6094379124341 + >>> diff(log, 10) + 0.1 + +The Taylor series expansion of the natural logarithm around +`x = 1` has coefficients `(-1)^{n+1}/n`:: + + >>> nprint(taylor(log, 1, 7)) + [0.0, 1.0, -0.5, 0.333333, -0.25, 0.2, -0.166667, 0.142857] + +:func:`~mpmath.log` supports arbitrary precision evaluation:: + + >>> mp.dps = 50 + >>> log(pi) + 1.1447298858494001741434273513530587116472948129153 + >>> log(pi, pi**3) + 0.33333333333333333333333333333333333333333333333333 + >>> mp.dps = 25 + >>> log(3+4j) + (1.609437912434100374600759 + 0.9272952180016122324285125j) +""" + +log10 = r""" +Computes the base-10 logarithm of `x`, `\log_{10}(x)`. ``log10(x)`` +is equivalent to ``log(x, 10)``. +""" + +log2 = r""" +Computes the base-2 logarithm of `x`, `\log_{2}(x)`. ``log2(x)`` +is equivalent to ``log(x, 2)``. +""" + +exp2 = """ +Computes 2 raised to the power `x`. +""" + +fmod = r""" +Converts `x` and `y` to mpmath numbers and returns `x \mod y`. +For mpmath numbers, this is equivalent to ``x % y``. + + >>> from mpmath import mp, pi, fmod + >>> mp.pretty = True + >>> fmod(100, pi) + 2.61062773871641 + +You can use :func:`~mpmath.fmod` to compute fractional parts of numbers:: + + >>> fmod(10.25, 1) + 0.25 + +""" + +radians = r""" +Converts the degree angle `x` to radians:: + + >>> from mpmath import mp, radians + >>> mp.pretty = True + >>> radians(60) + 1.0471975511966 +""" + +degrees = r""" +Converts the radian angle `x` to a degree angle:: + + >>> from mpmath import mp, degrees, pi + >>> mp.pretty = True + >>> degrees(pi/3) + 60.0 +""" + +atan2 = r""" +Computes the two-argument arctangent, `\mathrm{atan2}(y, x)`, +giving the signed angle between the positive `x`-axis and the +point `(x, y)` in the 2D plane. This function is defined for +real `x` and `y` only. + +The two-argument arctangent essentially computes +`\mathrm{atan}(y/x)`, but accounts for the signs of both +`x` and `y` to give the angle for the correct quadrant. The +following examples illustrate the difference:: + + >>> from mpmath import mp, atan2, atan + >>> mp.pretty = True + >>> atan2(1,1), atan(1/1.) + (0.785398163397448, 0.785398163397448) + >>> atan2(1,-1), atan(1/-1.) + (2.35619449019234, -0.785398163397448) + >>> atan2(-1,1), atan(-1/1.) + (-0.785398163397448, -0.785398163397448) + >>> atan2(-1,-1), atan(-1/-1.) + (-2.35619449019234, 0.785398163397448) + +The angle convention is the same as that used for the complex +argument; see :func:`~mpmath.arg`. +""" + +fibonacci = r""" +``fibonacci(n)`` computes the `n`-th Fibonacci number, `F(n)`. The +Fibonacci numbers are defined by the recurrence `F(n) = F(n-1) + F(n-2)` +with the initial values `F(0) = 0`, `F(1) = 1`. :func:`~mpmath.fibonacci` +extends this definition to arbitrary real and complex arguments +using the formula + +.. math :: + + F(z) = \frac{\phi^z - \cos(\pi z) \phi^{-z}}{\sqrt 5} + +where `\phi` is the golden ratio. :func:`~mpmath.fibonacci` also uses this +continuous formula to compute `F(n)` for extremely large `n`, where +calculating the exact integer would be wasteful. + +For convenience, ``fib()`` is available as an alias for +:func:`~mpmath.fibonacci`. + +**Basic examples** + +Some small Fibonacci numbers are:: + + >>> from mpmath import mp, fibonacci, fib, pi, findroot, nsum, sqrt, inf + >>> mp.pretty = True + >>> for i in range(10): + ... print(fibonacci(i)) + ... + 0.0 + 1.0 + 1.0 + 2.0 + 3.0 + 5.0 + 8.0 + 13.0 + 21.0 + 34.0 + >>> fibonacci(50) + 12586269025.0 + +The recurrence for `F(n)` extends backwards to negative `n`:: + + >>> for i in range(10): + ... print(fibonacci(-i)) + ... + 0.0 + 1.0 + -1.0 + 2.0 + -3.0 + 5.0 + -8.0 + 13.0 + -21.0 + 34.0 + +Large Fibonacci numbers will be computed approximately unless +the precision is set high enough:: + + >>> fib(200) + 2.8057117299251e+41 + >>> mp.dps = 45 + >>> fib(200) + 280571172992510140037611932413038677189525.0 + +:func:`~mpmath.fibonacci` can compute approximate Fibonacci numbers +of stupendous size:: + + >>> mp.dps = 15 + >>> fibonacci(10**25) + 3.49052338550226e+2089876402499787337692720 + +**Real and complex arguments** + +The extended Fibonacci function is an analytic function. The +property `F(z) = F(z-1) + F(z-2)` holds for arbitrary `z`:: + + >>> mp.dps = 15 + >>> fib(pi) + 2.1170270579161 + >>> fib(pi-1) + fib(pi-2) + 2.1170270579161 + >>> fib(3+4j) + (-5248.51130728372 - 14195.962288353j) + >>> fib(2+4j) + fib(1+4j) + (-5248.51130728372 - 14195.962288353j) + +The Fibonacci function has infinitely many roots on the +negative half-real axis. The first root is at 0, the second is +close to -0.18, and then there are infinitely many roots that +asymptotically approach `-n+1/2`:: + + >>> findroot(fib, -0.2) + -0.183802359692956 + >>> findroot(fib, -2) + -1.57077646820395 + >>> findroot(fib, -17) + -16.4999999596115 + >>> findroot(fib, -24) + -23.5000000000479 + +**Mathematical relationships** + +For large `n`, `F(n+1)/F(n)` approaches the golden ratio:: + + >>> mp.dps = 50 + >>> fibonacci(101)/fibonacci(100) + 1.6180339887498948482045868343656381177203127439638 + >>> +phi + 1.6180339887498948482045868343656381177203091798058 + +The sum of reciprocal Fibonacci numbers converges to an irrational +number for which no closed form expression is known:: + + >>> mp.dps = 15 + >>> nsum(lambda n: 1/fib(n), [1, inf]) + 3.35988566624318 + +Amazingly, however, the sum of odd-index reciprocal Fibonacci +numbers can be expressed in terms of a Jacobi theta function:: + + >>> nsum(lambda n: 1/fib(2*n+1), [0, inf]) + 1.82451515740692 + >>> sqrt(5)*jtheta(2,0,(3-sqrt(5))/2)**2/4 + 1.82451515740692 + +Some related sums can be done in closed form:: + + >>> nsum(lambda k: 1/(1+fib(2*k+1)), [0, inf]) + 1.11803398874989 + >>> phi - 0.5 + 1.11803398874989 + >>> f = lambda k:(-1)**(k+1) / sum(fib(n)**2 for n in range(1,int(k+1))) + >>> nsum(f, [1, inf]) + 0.618033988749895 + >>> phi-1 + 0.618033988749895 + +**References** + +1. [Weisstein]_ http://mathworld.wolfram.com/FibonacciNumber.html +""" + +altzeta = r""" +Gives the Dirichlet eta function, `\eta(s)`, also known as the +alternating zeta function. This function is defined in analogy +with the Riemann zeta function as providing the sum of the +alternating series + +.. math :: + + \eta(s) = \sum_{k=1}^{\infty} \frac{(-1)^{k-1}}{k^s} + = 1-\frac{1}{2^s}+\frac{1}{3^s}-\frac{1}{4^s}+\ldots + +The eta function, unlike the Riemann zeta function, is an entire +function, having a finite value for all complex `s`. The special case +`\eta(1) = \log(2)` gives the value of the alternating harmonic series. + +The alternating zeta function may expressed using the Riemann zeta function +as `\eta(s) = (1 - 2^{1-s}) \zeta(s)`. It can also be expressed +in terms of the Hurwitz zeta function, for example using +:func:`~mpmath.dirichlet` (see documentation for that function). + +**Examples** + +Some special values are:: + + >>> from mpmath import mp, altzeta, mpf, pi, inf + >>> mp.pretty = True + >>> altzeta(1) + 0.693147180559945 + >>> altzeta(0) + 0.5 + >>> altzeta(-1) + 0.25 + >>> altzeta(-2) + 0.0 + +An example of a sum that can be computed more accurately and +efficiently via :func:`~mpmath.altzeta` than via numerical summation:: + + >>> sum(-(-1)**n / mpf(n)**2.5 for n in range(1, 100)) + 0.867204951503984 + >>> altzeta(2.5) + 0.867199889012184 + +At positive even integers, the Dirichlet eta function +evaluates to a rational multiple of a power of `\pi`:: + + >>> altzeta(2) + 0.822467033424113 + >>> pi**2/12 + 0.822467033424113 + +Like the Riemann zeta function, `\eta(s)`, approaches 1 +as `s` approaches positive infinity, although it does +so from below rather than from above:: + + >>> altzeta(30) + 0.999999999068682 + >>> altzeta(inf) + 1.0 + >>> mp.pretty = False + >>> altzeta(1000, rounding='d') + mpf('0.99999999999999989') + >>> altzeta(1000, rounding='u') + mpf('1.0') + +**References** + +1. [Weisstein]_ http://mathworld.wolfram.com/DirichletEtaFunction.html + +2. [Wikipedia]_ http://en.wikipedia.org/wiki/Dirichlet_eta_function +""" + +factorial = r""" +Computes the factorial, `x!`. For integers `n \ge 0`, we have +`n! = 1 \cdot 2 \cdots (n-1) \cdot n` and more generally the factorial +is defined for real or complex `x` by `x! = \Gamma(x+1)`. + +**Examples** + +Basic values and limits:: + + >>> from mpmath import mp, fac, sqrt, inf, pi, exp, nsum + >>> mp.pretty = True + >>> for k in range(6): + ... print("%s %s" % (k, fac(k))) + ... + 0 1.0 + 1 1.0 + 2 2.0 + 3 6.0 + 4 24.0 + 5 120.0 + >>> fac(inf) + inf + >>> fac(0.5), sqrt(pi)/2 + (0.886226925452758, 0.886226925452758) + +For large positive `x`, `x!` can be approximated by +Stirling's formula:: + + >>> x = 10**10 + >>> fac(x) + 2.32579620567308e+95657055186 + >>> sqrt(2*pi*x)*(x/e)**x + 2.32579597597705e+95657055186 + +:func:`~mpmath.factorial` supports evaluation for astronomically large values:: + + >>> fac(10**30) + 6.22311232304258e+29565705518096748172348871081098 + +Reciprocal factorials appear in the Taylor series of the +exponential function (among many other contexts):: + + >>> nsum(lambda k: 1/fac(k), [0, inf]), exp(1) + (2.71828182845905, 2.71828182845905) + >>> nsum(lambda k: pi**k/fac(k), [0, inf]), exp(pi) + (23.1406926327793, 23.1406926327793) + +""" + +gamma = r""" +Computes the gamma function, `\Gamma(x)`. The gamma function is a +shifted version of the ordinary factorial, satisfying +`\Gamma(n) = (n-1)!` for integers `n > 0`. More generally, it +is defined by + +.. math :: + + \Gamma(x) = \int_0^{\infty} t^{x-1} e^{-t}\, dt + +for any real or complex `x` with `\Re(x) > 0` and for `\Re(x) < 0` +by analytic continuation. + +**Examples** + +Basic values and limits:: + + >>> from mpmath import mp, gamma, inf, sqrt, pi, exp, quad + >>> mp.pretty = True + >>> for k in range(1, 6): + ... print("%s %s" % (k, gamma(k))) + ... + 1 1.0 + 2 1.0 + 3 2.0 + 4 6.0 + 5 24.0 + >>> gamma(inf) + inf + >>> gamma(0) + Traceback (most recent call last): + ... + ValueError: gamma function pole + +The gamma function of a half-integer is a rational multiple of +`\sqrt{\pi}`:: + + >>> gamma(0.5), sqrt(pi) + (1.77245385090552, 1.77245385090552) + >>> gamma(1.5), sqrt(pi)/2 + (0.886226925452758, 0.886226925452758) + +We can check the integral definition:: + + >>> gamma(3.5) + 3.32335097044784 + >>> quad(lambda t: t**2.5*exp(-t), [0,inf]) + 3.32335097044784 + +:func:`~mpmath.gamma` supports arbitrary-precision evaluation and +complex arguments:: + + >>> mp.dps = 50 + >>> gamma(sqrt(3)) + 0.91510229697308632046045539308226554038315280564184 + >>> mp.dps = 25 + >>> gamma(2j) + (0.009902440080927490985955066 - 0.07595200133501806872408048j) + +Arguments can also be large. Note that the gamma function grows +very quickly:: + + >>> mp.dps = 15 + >>> gamma(10**20) + 1.9328495143101e+1956570551809674817225 + +**References** + +* [Spouge]_ + +""" + +psi = r""" +Gives the polygamma function of order `m` of `z`, `\psi^{(m)}(z)`. +Special cases are known as the *digamma function* (`\psi^{(0)}(z)`), +the *trigamma function* (`\psi^{(1)}(z)`), etc. The polygamma +functions are defined as the logarithmic derivatives of the gamma +function: + +.. math :: + + \psi^{(m)}(z) = \left(\frac{d}{dz}\right)^{m+1} \log \Gamma(z) + +In particular, `\psi^{(0)}(z) = \Gamma'(z)/\Gamma(z)`. In the +present implementation of :func:`~mpmath.psi`, the order `m` must be a +nonnegative integer, while the argument `z` may be an arbitrary +complex number (with exception for the polygamma function's poles +at `z = 0, -1, -2, \ldots`). + +**Examples** + +For various rational arguments, the polygamma function reduces to +a combination of standard mathematical constants:: + + >>> from mpmath import (mp, psi, euler, catalan, pi, apery, quad, diff, + ... sqrt, nsum, inf, j, nprint, polyroots) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> psi(0, 1), -euler + (-0.5772156649015328606065121, -0.5772156649015328606065121) + >>> psi(1, '1/4'), pi**2+8*catalan + (17.19732915450711073927132, 17.19732915450711073927132) + >>> psi(2, '1/2'), -14*apery + (-16.82879664423431999559633, -16.82879664423431999559633) + +The polygamma functions are derivatives of each other:: + + >>> diff(lambda x: psi(3, x), pi), psi(4, pi) + (-0.1105749312578862734526952, -0.1105749312578862734526952) + >>> quad(lambda x: psi(4, x), [2, 3]), psi(3,3)-psi(3,2) + (-0.375, -0.375) + +The digamma function diverges logarithmically as `z \to \infty`, +while higher orders tend to zero:: + + >>> psi(0,inf), psi(1,inf), psi(2,inf) + (inf, 0.0, 0.0) + +Evaluation for a complex argument:: + + >>> psi(2, -1-2j) + (0.03902435405364952654838445 + 0.1574325240413029954685366j) + +Evaluation is supported for large orders `m` and/or large +arguments `z`:: + + >>> psi(3, 10**100) + 2.0e-300 + >>> psi(250, 10**30+10**20*j) + (-1.293142504363642687204865e-7010 + 3.232856260909107391513108e-7018j) + +**Application to infinite series** + +Any infinite series where the summand is a rational function of +the index `k` can be evaluated in closed form in terms of polygamma +functions of the roots and poles of the summand:: + + >>> a = sqrt(2) + >>> b = sqrt(3) + >>> nsum(lambda k: 1/((k+a)**2*(k+b)), [0, inf]) + 0.4049668927517857061917531 + >>> (psi(0,a)-psi(0,b)-a*psi(1,a)+b*psi(1,a))/(a-b)**2 + 0.4049668927517857061917531 + +This follows from the series representation (`m > 0`) + +.. math :: + + \psi^{(m)}(z) = (-1)^{m+1} m! \sum_{k=0}^{\infty} + \frac{1}{(z+k)^{m+1}}. + +Since the roots of a polynomial may be complex, it is sometimes +necessary to use the complex polygamma function to evaluate +an entirely real-valued sum:: + + >>> nsum(lambda k: 1/(k**2-2*k+3), [0, inf]) + 1.694361433907061256154665 + >>> nprint(polyroots([3,-2,1])) + [(1.0 - 1.41421j), (1.0 + 1.41421j)] + >>> r1 = 1-sqrt(2)*j + >>> r2 = r1.conjugate() + >>> (psi(0,-r2)-psi(0,-r1))/(r1-r2) + (1.694361433907061256154665 + 0.0j) + +""" + +digamma = r""" +Shortcut for ``psi(0,z)``. +""" + +harmonic = r""" +If `n` is an integer, ``harmonic(n)`` gives a floating-point +approximation of the `n`-th harmonic number `H(n)`, defined as + +.. math :: + + H(n) = 1 + \frac{1}{2} + \frac{1}{3} + \ldots + \frac{1}{n} + +The first few harmonic numbers are:: + + >>> from mpmath import mp, harmonic, inf, pi, findroot, ceil + >>> mp.pretty = True + >>> for n in range(8): + ... print("%s %s" % (n, harmonic(n))) + ... + 0 0.0 + 1 1.0 + 2 1.5 + 3 1.83333333333333 + 4 2.08333333333333 + 5 2.28333333333333 + 6 2.45 + 7 2.59285714285714 + +The infinite harmonic series `1 + 1/2 + 1/3 + \ldots` diverges:: + + >>> harmonic(inf) + inf + +:func:`~mpmath.harmonic` is evaluated using the digamma function rather +than by summing the harmonic series term by term. It can therefore +be computed quickly for arbitrarily large `n`, and even for +nonintegral arguments:: + + >>> harmonic(10**100) + 230.835724964306 + >>> harmonic(0.5) + 0.613705638880109 + >>> harmonic(3+4j) + (2.24757548223494 + 0.850502209186044j) + +:func:`~mpmath.harmonic` supports arbitrary precision evaluation:: + + >>> mp.dps = 50 + >>> harmonic(11) + 3.0198773448773448773448773448773448773448773448773 + >>> harmonic(pi) + 1.8727388590273302654363491032336134987519132374152 + +The harmonic series diverges, but at a glacial pace. It is possible +to calculate the exact number of terms required before the sum +exceeds a given amount, say 100:: + + >>> mp.dps = 50 + >>> v = 10**findroot(lambda x: harmonic(10**x) - 100, 10) + >>> v + 15092688622113788323693563264538101449859496.864101 + >>> v = int(ceil(v)) + >>> print(v) + 15092688622113788323693563264538101449859497 + >>> harmonic(v-1) + 99.999999999999999999999999999999999999999999942747 + >>> harmonic(v) + 100.000000000000000000000000000000000000000000009 + +""" + +bernoulli = r""" +Computes the nth Bernoulli number, `B_n`, for any integer `n \ge 0`. + +The Bernoulli numbers are rational numbers, but this function +returns a floating-point approximation. To obtain an exact +fraction, use :func:`~mpmath.bernfrac` instead. + +Optional ``plus`` flag (default: False) control the sign choice of +the `B_1` value (default: `-0.5`). + +**Examples** + +Numerical values of the first few Bernoulli numbers:: + + >>> from mpmath import mp, bernoulli, pi, fac, zeta + >>> mp.pretty = True + >>> for n in range(15): + ... print("%s %s" % (n, bernoulli(n))) + ... + 0 1.0 + 1 -0.5 + 2 0.166666666666667 + 3 0.0 + 4 -0.0333333333333333 + 5 0.0 + 6 0.0238095238095238 + 7 0.0 + 8 -0.0333333333333333 + 9 0.0 + 10 0.0757575757575758 + 11 0.0 + 12 -0.253113553113553 + 13 0.0 + 14 1.16666666666667 + +Bernoulli numbers can be approximated with arbitrary precision:: + + >>> mp.dps = 50 + >>> bernoulli(100) + -2.8382249570693706959264156336481764738284680928013e+78 + +Arbitrarily large `n` are supported:: + + >>> mp.dps = 15 + >>> bernoulli(10**20 + 2) + 3.09136296657021e+1876752564973863312327 + +The Bernoulli numbers are related to the Riemann zeta function +at integer arguments:: + + >>> -bernoulli(8) * (2*pi)**8 / (2*fac(8)) + 1.00407735619794 + >>> zeta(8) + 1.00407735619794 + +**Algorithm** + +For small `n` (`n < 3000`) :func:`~mpmath.bernoulli` uses a recurrence +formula due to Ramanujan. All results in this range are cached, +so sequential computation of small Bernoulli numbers is +guaranteed to be fast. + +For larger `n`, `B_n` is evaluated in terms of the Riemann zeta +function. + +**References** + +1. [Wikipedia]_ https://en.wikipedia.org/wiki/Bernoulli_number + +""" + +stieltjes = r""" +For a nonnegative integer `n`, ``stieltjes(n)`` computes the +`n`-th Stieltjes constant `\gamma_n`, defined as the +`n`-th coefficient in the Laurent series expansion of the +Riemann zeta function around the pole at `s = 1`. That is, +we have: + +.. math :: + + \zeta(s) = \frac{1}{s-1} \sum_{n=0}^{\infty} + \frac{(-1)^n}{n!} \gamma_n (s-1)^n + +More generally, ``stieltjes(n, a)`` gives the corresponding +coefficient `\gamma_n(a)` for the Hurwitz zeta function +`\zeta(s,a)` (with `\gamma_n = \gamma_n(1)`). + +**Examples** + +The zeroth Stieltjes constant is just Euler's constant `\gamma`:: + + >>> from mpmath import mp, stieltjes, extradps, zeta, diff + >>> mp.pretty = True + >>> stieltjes(0) + 0.577215664901533 + +Some more values are:: + + >>> stieltjes(1) + -0.0728158454836767 + >>> stieltjes(10) + 0.000205332814909065 + >>> stieltjes(30) + 0.00355772885557316 + >>> stieltjes(1000) + -1.57095384420474e+486 + >>> stieltjes(2000) + 2.680424678918e+1109 + >>> stieltjes(1, 2.5) + -0.23747539175716 + +An alternative way to compute `\gamma_1`:: + + >>> diff(extradps(15)(lambda x: 1/(x-1) - zeta(x)), 1) + -0.0728158454836767 + +:func:`~mpmath.stieltjes` supports arbitrary precision evaluation:: + + >>> mp.dps = 50 + >>> stieltjes(2) + -0.0096903631928723184845303860352125293590658061013408 + +**Algorithm** + +:func:`~mpmath.stieltjes` numerically evaluates the integral in +the following representation due to Ainsworth, Howell and +Coffey [1], [2]: + +.. math :: + + \gamma_n(a) = \frac{\log^n a}{2a} - \frac{\log^{n+1}(a)}{n+1} + + \frac{2}{a} \Re \int_0^{\infty} + \frac{(x/a-i)\log^n(a-ix)}{(1+x^2/a^2)(e^{2\pi x}-1)} dx. + +For some reference values with `a = 1`, see e.g. [4]. + +**References** + +1. [Ainsworth]_ + +2. [Coffey]_ + +3. [Weisstein]_ http://mathworld.wolfram.com/StieltjesConstants.html + +4. https://web.archive.org/web/20110722205305/http://pi.lacim.uqam.ca/piDATA/stieltjesgamma.txt + +""" + +gammaprod = r""" +Given iterables `a` and `b`, ``gammaprod(a, b)`` computes the +product / quotient of gamma functions: + +.. math :: + + \frac{\Gamma(a_0) \Gamma(a_1) \cdots \Gamma(a_p)} + {\Gamma(b_0) \Gamma(b_1) \cdots \Gamma(b_q)} + +Unlike direct calls to :func:`~mpmath.gamma`, :func:`~mpmath.gammaprod` considers +the entire product as a limit and evaluates this limit properly if +any of the numerator or denominator arguments are nonpositive +integers such that poles of the gamma function are encountered. +That is, :func:`~mpmath.gammaprod` evaluates + +.. math :: + + \lim_{\epsilon \to 0} + \frac{\Gamma(a_0+\epsilon) \Gamma(a_1+\epsilon) \cdots + \Gamma(a_p+\epsilon)} + {\Gamma(b_0+\epsilon) \Gamma(b_1+\epsilon) \cdots + \Gamma(b_q+\epsilon)} + +In particular: + +* If there are equally many poles in the numerator and the + denominator, the limit is a rational number times the remaining, + regular part of the product. + +* If there are more poles in the numerator, :func:`~mpmath.gammaprod` + returns ``+inf``. + +* If there are more poles in the denominator, :func:`~mpmath.gammaprod` + returns 0. + +**Examples** + +The reciprocal gamma function `1/\Gamma(x)` evaluated at `x = 0`:: + + >>> from mpmath import mp, gammaprod, limit, gamma + >>> mp.pretty = True + >>> gammaprod([], [0]) + 0.0 + +A limit:: + + >>> gammaprod([-4], [-3]) + -0.25 + >>> limit(lambda x: gamma(x-1)/gamma(x), -3, direction=1) + -0.25 + >>> limit(lambda x: gamma(x-1)/gamma(x), -3, direction=-1) + -0.25 + +""" + +beta = r""" +Computes the beta function, +`B(x,y) = \Gamma(x) \Gamma(y) / \Gamma(x+y)`. +The beta function is also commonly defined by the integral +representation + +.. math :: + + B(x,y) = \int_0^1 t^{x-1} (1-t)^{y-1} \, dt + +**Examples** + +For integer and half-integer arguments where all three gamma +functions are finite, the beta function becomes either rational +number or a rational multiple of `\pi`:: + + >>> from mpmath import mp, beta, inf, j, pi, e, quad, sqrt, sin, cos + >>> mp.pretty = True + >>> beta(5, 2) + 0.0333333333333333 + >>> beta(1.5, 2) + 0.266666666666667 + >>> 16*beta(2.5, 1.5) + 3.14159265358979 + +Where appropriate, :func:`~mpmath.beta` evaluates limits. A pole +of the beta function is taken to result in ``+inf``:: + + >>> beta(-0.5, 0.5) + 0.0 + >>> beta(-3, 3) + -0.333333333333333 + >>> beta(-2, 3) + inf + >>> beta(inf, 1) + 0.0 + >>> beta(inf, 0) + nan + +:func:`~mpmath.beta` supports complex numbers and arbitrary precision +evaluation:: + + >>> beta(1, 2+j) + (0.4 - 0.2j) + >>> mp.dps = 25 + >>> beta(j,0.5) + (1.079424249270925780135675 - 1.410032405664160838288752j) + >>> mp.dps = 50 + >>> beta(pi, e) + 0.037890298781212201348153837138927165984170287886464 + +Various integrals can be computed by means of the +beta function:: + + >>> mp.dps = 15 + >>> quad(lambda t: t**2.5*(1-t)**2, [0, 1]) + 0.0230880230880231 + >>> beta(3.5, 3) + 0.0230880230880231 + >>> quad(lambda t: sin(t)**4 * sqrt(cos(t)), [0, pi/2]) + 0.319504062596158 + >>> beta(2.5, 0.75)/2 + 0.319504062596158 + +""" + +betainc = r""" +``betainc(a, b, x1=0, x2=1, regularized=False)`` gives the generalized +incomplete beta function, + +.. math :: + + I_{x_1}^{x_2}(a,b) = \int_{x_1}^{x_2} t^{a-1} (1-t)^{b-1} dt. + +When `x_1 = 0, x_2 = 1`, this reduces to the ordinary (complete) +beta function `B(a,b)`; see :func:`~mpmath.beta`. + +With the keyword argument ``regularized=True``, :func:`~mpmath.betainc` +computes the regularized incomplete beta function +`I_{x_1}^{x_2}(a,b) / B(a,b)`. This is the cumulative distribution of the +beta distribution with parameters `a`, `b`. + +.. note : + + Implementations of the incomplete beta function in some other + software uses a different argument order. For example, Mathematica uses the + reversed argument order ``Beta[x1,x2,a,b]``. For the equivalent of SciPy's + three-argument incomplete beta integral (implicitly with `x1 = 0`), use + ``betainc(a,b,0,x2,regularized=True)``. + +**Examples** + +Verifying that :func:`~mpmath.betainc` computes the integral in the +definition:: + + >>> from mpmath import mp, betainc, quad, identify, chop, pi, e + >>> mp.dps = 25 + >>> mp.pretty = True + >>> x,y,a,b = 3, 4, 0, 6 + >>> betainc(x, y, a, b) + -4010.4 + >>> quad(lambda t: t**(x-1) * (1-t)**(y-1), [a, b]) + -4010.4 + +The arguments may be arbitrary complex numbers:: + + >>> betainc(0.75, 1-4j, 0, 2+3j) + (0.2241657956955709603655887 + 0.3619619242700451992411724j) + +With regularization:: + + >>> betainc(1, 2, 0, 0.25, regularized=True) + 0.4375 + >>> betainc(pi, e, 0, 1, regularized=True) # Complete + 1.0 + +The beta integral satisfies some simple argument transformation +symmetries:: + + >>> mp.dps = 15 + >>> betainc(2,3,4,5), -betainc(2,3,5,4), betainc(3,2,1-5,1-4) + (56.0833333333333, 56.0833333333333, 56.0833333333333) + +The beta integral can often be evaluated analytically. For integer and +rational arguments, the incomplete beta function typically reduces to a +simple algebraic-logarithmic expression:: + + >>> mp.dps = 25 + >>> identify(chop(betainc(0, 0, 3, 4))) + '-(log((9/8)))' + >>> identify(betainc(2, 3, 4, 5)) + '(673/12)' + >>> identify(betainc(1.5, 1, 1, 2)) + '((-12+sqrt(1152))/18)' + +""" + +binomial = r""" +Computes the binomial coefficient + +.. math :: + + {n \choose k} = \frac{n!}{k!(n-k)!}. + +The binomial coefficient gives the number of ways that `k` items +can be chosen from a set of `n` items. More generally, the binomial +coefficient is a well-defined function of arbitrary real or +complex `n` and `k`, via the gamma function. + +**Examples** + +Generate Pascal's triangle:: + + >>> from mpmath import mp, binomial, nprint, exp, taylor, j, chop, quad, pi + >>> mp.pretty = True + >>> for n in range(5): + ... nprint([binomial(n,k) for k in range(n+1)]) + ... + [1.0] + [1.0, 1.0] + [1.0, 2.0, 1.0] + [1.0, 3.0, 3.0, 1.0] + [1.0, 4.0, 6.0, 4.0, 1.0] + +There is 1 way to select 0 items from the empty set, and 0 ways to +select 1 item from the empty set:: + + >>> binomial(0, 0) + 1.0 + >>> binomial(0, 1) + 0.0 + +:func:`~mpmath.binomial` supports large arguments:: + + >>> binomial(10**20, 10**20-5) + 8.33333333333333e+97 + >>> binomial(10**20, 10**10) + 2.60784095465201e+104342944813 + +Nonintegral binomial coefficients find use in series +expansions:: + + >>> nprint(taylor(lambda x: (1+x)**0.25, 0, 4)) + [1.0, 0.25, -0.09375, 0.0546875, -0.0375977] + >>> nprint([binomial(0.25, k) for k in range(5)]) + [1.0, 0.25, -0.09375, 0.0546875, -0.0375977] + +An integral representation:: + + >>> n, k = 5, 3 + >>> f = lambda t: exp(-j*k*t)*(1+exp(j*t))**n + >>> chop(quad(f, [-pi,pi])/(2*pi)) + 10.0 + >>> binomial(n,k) + 10.0 + +""" + +rf = r""" +Computes the rising factorial or Pochhammer symbol, + +.. math :: + + x^{(n)} = x (x+1) \cdots (x+n-1) = \frac{\Gamma(x+n)}{\Gamma(x)} + +where the rightmost expression is valid for nonintegral `n`. + +**Examples** + +For integral `n`, the rising factorial is a polynomial:: + + >>> from mpmath import rf, mp, nprint, taylor + >>> mp.pretty = True + >>> for n in range(5): + ... nprint(taylor(lambda x: rf(x,n), 0, n)) + ... + [1.0] + [0.0, 1.0] + [0.0, 1.0, 1.0] + [0.0, 2.0, 3.0, 1.0] + [0.0, 6.0, 11.0, 6.0, 1.0] + +Evaluation is supported for arbitrary arguments:: + + >>> rf(2+3j, 5.5) + (-7202.03920483347 - 3777.58810701527j) +""" + +ff = r""" +Computes the falling factorial, + +.. math :: + + (x)_n = x (x-1) \cdots (x-n+1) = \frac{\Gamma(x+1)}{\Gamma(x-n+1)} + +where the rightmost expression is valid for nonintegral `n`. + +**Examples** + +For integral `n`, the falling factorial is a polynomial:: + + >>> from mpmath import mp, ff, nprint, taylor + >>> mp.pretty = True + >>> for n in range(5): + ... nprint(taylor(lambda x: ff(x,n), 0, n)) + ... + [1.0] + [0.0, 1.0] + [0.0, -1.0, 1.0] + [0.0, 2.0, -3.0, 1.0] + [0.0, -6.0, 11.0, -6.0, 1.0] + +Evaluation is supported for arbitrary arguments:: + + >>> ff(2+3j, 5.5) + (-720.41085888203 + 316.101124983878j) +""" + +fac2 = r""" +Computes the double factorial `x!!`, defined for integers +`x > 0` by + +.. math :: + + x!! = \begin{cases} + 1 \cdot 3 \cdots (x-2) \cdot x & x \;\mathrm{odd} \\ + 2 \cdot 4 \cdots (x-2) \cdot x & x \;\mathrm{even} + \end{cases} + +and more generally by [1] + +.. math :: + + x!! = 2^{x/2} \left(\frac{\pi}{2}\right)^{(\cos(\pi x)-1)/4} + \Gamma\left(\frac{x}{2}+1\right). + +**Examples** + +The integer sequence of double factorials begins:: + + >>> from mpmath import (mp, fac2, nprint, mpf, exp, pi, nsum, sqrt, e, + ... gamma, inf, erf, fac) + >>> mp.pretty = True + >>> nprint([fac2(n) for n in range(10)]) + [1.0, 1.0, 2.0, 3.0, 8.0, 15.0, 48.0, 105.0, 384.0, 945.0] + +For large `x`, double factorials follow a Stirling-like asymptotic +approximation:: + + >>> x = mpf(10000) + >>> fac2(x) + 5.97272691416282e+17830 + >>> sqrt(pi)*x**((x+1)/2)*exp(-x/2) + 5.97262736954392e+17830 + +The recurrence formula `x!! = x (x-2)!!` can be reversed to +define the double factorial of negative odd integers (but +not negative even integers):: + + >>> fac2(-1), fac2(-3), fac2(-5), fac2(-7) + (1.0, -1.0, 0.333333333333333, -0.0666666666666667) + >>> fac2(-2) + Traceback (most recent call last): + ... + ValueError: gamma function pole + +With the exception of the poles at negative even integers, +:func:`~mpmath.fac2` supports evaluation for arbitrary complex arguments. +The recurrence formula is valid generally:: + + >>> fac2(pi+2j) + (-1.3697207890154e-12 + 3.93665300979176e-12j) + >>> (pi+2j)*fac2(pi-2+2j) + (-1.3697207890154e-12 + 3.93665300979176e-12j) + +Double factorials should not be confused with nested factorials, +which are immensely larger:: + + >>> fac(fac(20)) + 5.13805976125208e+43675043585825292774 + >>> fac2(20) + 3715891200.0 + +Double factorials appear, among other things, in series expansions +of Gaussian functions and the error function. Infinite series +include:: + + >>> nsum(lambda k: 1/fac2(k), [0, inf]) + 3.05940740534258 + >>> sqrt(e)*(1+sqrt(pi/2)*erf(sqrt(2)/2)) + 3.05940740534258 + >>> nsum(lambda k: 2**k/fac2(2*k-1), [1, inf]) + 4.06015693855741 + >>> e * erf(1) * sqrt(pi) + 4.06015693855741 + +A beautiful Ramanujan sum:: + + >>> nsum(lambda k: (-1)**k*(fac2(2*k-1)/fac2(2*k))**3, [0,inf]) + 0.90917279454693 + >>> (gamma('9/8')/gamma('5/4')/gamma('7/8'))**2 + 0.90917279454693 + +**References** + +1. [WolframFunctions]_ http://functions.wolfram.com/GammaBetaErf/Factorial2/27/01/0002/ + +2. [Weisstein]_ http://mathworld.wolfram.com/DoubleFactorial.html + +""" + +hyper = r""" +Evaluates the generalized hypergeometric function + +.. math :: + + \,_pF_q(a_1,\ldots,a_p; b_1,\ldots,b_q; z) = + \sum_{n=0}^\infty \frac{(a_1)_n (a_2)_n \ldots (a_p)_n} + {(b_1)_n(b_2)_n\ldots(b_q)_n} \frac{z^n}{n!} + +where `(x)_n` denotes the rising factorial (see :func:`~mpmath.rf`). + +The parameters lists ``a_s`` and ``b_s`` may contain integers, +real numbers, complex numbers, as well as exact fractions given in +the form of tuples `(p, q)`. :func:`~mpmath.hyper` is optimized to handle +integers and fractions more efficiently than arbitrary +floating-point parameters (since rational parameters are by +far the most common). + +**Examples** + +Verifying that :func:`~mpmath.hyper` gives the sum in the definition, by +comparison with :func:`~mpmath.nsum`:: + + >>> from mpmath import (mp, hyper, rf, fac, nsum, inf, mpf, sqrt, pi, + ... exp, identify, extradps) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> a,b,c,d = 2,3,4,5 + >>> x = 0.25 + >>> hyper([a,b],[c,d],x) + 1.078903941164934876086237 + >>> fn = lambda n: rf(a,n)*rf(b,n)/rf(c,n)/rf(d,n)*x**n/fac(n) + >>> nsum(fn, [0, inf]) + 1.078903941164934876086237 + +The parameters can be any combination of integers, fractions, +floats and complex numbers:: + + >>> a, b, c, d, e = 1, (-1,2), pi, 3+4j, (2,3) + >>> x = 0.2j + >>> hyper([a,b],[c,d,e],x) + (0.9923571616434024810831887 - 0.005753848733883879742993122j) + >>> b, e = -0.5, mpf(2)/3 + >>> fn = lambda n: rf(a,n)*rf(b,n)/rf(c,n)/rf(d,n)/rf(e,n)*x**n/fac(n) + >>> nsum(fn, [0, inf]) + (0.9923571616434024810831887 - 0.005753848733883879742993122j) + +The `\,_0F_0` and `\,_1F_0` series are just elementary functions:: + + >>> a, z = sqrt(2), +pi + >>> hyper([],[],z) + 23.14069263277926900572909 + >>> exp(z) + 23.14069263277926900572909 + >>> hyper([a],[],z) + (-0.09069132879922920160334114 + 0.3283224323946162083579656j) + >>> (1-z)**(-a) + (-0.09069132879922920160334114 + 0.3283224323946162083579656j) + +If any `a_k` coefficient is a nonpositive integer, the series terminates +into a finite polynomial:: + + >>> hyper([1,1,1,-3],[2,5],1) + 0.7904761904761904761904762 + >>> identify(_) + '(83/105)' + +If any `b_k` is a nonpositive integer, the function is undefined (unless the +series terminates before the division by zero occurs):: + + >>> hyper([1,1,1,-3],[-2,5],1) + Traceback (most recent call last): + ... + ZeroDivisionError: pole in hypergeometric series + >>> hyper([1,1,1,-1],[-2,5],1) + 1.1 + +Except for polynomial cases, the radius of convergence `R` of the hypergeometric +series is either `R = \infty` (if `p \le q`), `R = 1` (if `p = q+1`), or +`R = 0` (if `p > q+1`). + +The analytic continuations of the functions with `p = q+1`, i.e. `\,_2F_1`, +`\,_3F_2`, `\,_4F_3`, etc, are all implemented and therefore these functions +can be evaluated for `|z| \ge 1`. The shortcuts :func:`~mpmath.hyp2f1`, :func:`~mpmath.hyp3f2` +are available to handle the most common cases (see their documentation), +but functions of higher degree are also supported via :func:`~mpmath.hyper`:: + + >>> hyper([1,2,3,4], [5,6,7], 1) # 4F3 at finite-valued branch point + 1.141783505526870731311423 + >>> hyper([4,5,6,7], [1,2,3], 1) # 4F3 at pole + inf + >>> hyper([1,2,3,4,5], [6,7,8,9], 10) # 5F4 + (1.543998916527972259717257 - 0.5876309929580408028816365j) + >>> hyper([1,2,3,4,5,6], [7,8,9,10,11], 1j) # 6F5 + (0.9996565821853579063502466 + 0.0129721075905630604445669j) + +Near `z = 1` with noninteger parameters:: + + >>> hyper(['1/3',1,'3/2',2], ['1/5','11/6','41/8'], 1) + 2.219433352235586121250027 + >>> hyper(['1/3',1,'3/2',2], ['1/5','11/6','5/4'], 1) + inf + >>> eps1 = extradps(6)(lambda: 1 - mpf('1e-6'))() + >>> hyper(['1/3',1,'3/2',2], ['1/5','11/6','5/4'], eps1) + 2923978034.412973409330956 + +Please note that, as currently implemented, evaluation of `\,_pF_{p-1}` +with `p \ge 3` may be slow or inaccurate when `|z-1|` is small, +for some parameter values. + +Evaluation may be aborted if convergence appears to be too slow. +The optional ``maxterms`` (limiting the number of series terms) and ``maxprec`` +(limiting the internal precision) keyword arguments can be used +to control evaluation:: + + >>> hyper([1,2,3], [4,5,6], 10000) + Traceback (most recent call last): + ... + NoConvergence: Hypergeometric series converges too slowly. Try increasing maxterms. + >>> hyper([1,2,3], [4,5,6], 10000, maxterms=10**6) + 7.622806053177969474396918e+4310 + +Additional options include ``force_series`` (which forces direct use of +a hypergeometric series even if another evaluation method might work better) +and ``asymp_tol`` which controls the target tolerance for using +asymptotic series. + +When `p > q+1`, ``hyper`` computes the (iterated) Borel sum of the divergent +series. For `\,_2F_0` the Borel sum has an analytic solution and can be +computed efficiently (see :func:`~mpmath.hyp2f0`). For higher degrees, the functions +is evaluated first by attempting to sum it directly as an asymptotic +series (this only works for tiny `|z|`), and then by evaluating the Borel +regularized sum using numerical integration. Except for +special parameter combinations, this can be extremely slow. + + >>> hyper([1,1], [], 0.5) # regularization of 2F0 + (1.340965419580146562086448 + 0.8503366631752726568782447j) + >>> hyper([1,1,1,1], [1], 0.5) # regularization of 4F1 + (1.108287213689475145830699 + 0.5327107430640678181200491j) + +With the following magnitude of argument, the asymptotic series for `\,_3F_1` +gives only a few digits. Using Borel summation, ``hyper`` can produce +a value with full accuracy:: + + >>> mp.dps = 15 + >>> hyper([2,0.5,4], [5.25], '0.08', force_series=True) + Traceback (most recent call last): + ... + NoConvergence: Hypergeometric series converges too slowly. Try increasing maxterms. + >>> hyper([2,0.5,4], [5.25], '0.08', asymp_tol=1e-4) + 1.0725535790737 + >>> hyper([2,0.5,4], [5.25], '0.08') + (1.07269542893559 + 5.54668863216891e-5j) + >>> hyper([2,0.5,4], [5.25], '-0.08', asymp_tol=1e-4) + 0.946344925484879 + >>> hyper([2,0.5,4], [5.25], '-0.08') + 0.946312503737771 + >>> mp.dps = 25 + >>> hyper([2,0.5,4], [5.25], '-0.08') + 0.9463125037377662296700858 + +Note that with the positive `z` value, there is a complex part in the +correct result, which falls below the tolerance of the asymptotic series. + +By default, a parameter that appears in both ``a_s`` and ``b_s`` will be removed +unless it is a nonpositive integer. This generally speeds up evaluation +by producing a hypergeometric function of lower order. +This optimization can be disabled by passing ``eliminate=False``. + + >>> hyper([1,2,3], [4,5,3], 10000) + 1.268943190440206905892212e+4321 + >>> hyper([1,2,3], [4,5,3], 10000, eliminate=False) + Traceback (most recent call last): + ... + NoConvergence: Hypergeometric series converges too slowly. Try increasing maxterms. + >>> hyper([1,2,3], [4,5,3], 10000, eliminate=False, maxterms=10**6) + 1.268943190440206905892212e+4321 + +If a nonpositive integer `-n` appears in both ``a_s`` and ``b_s``, this parameter +cannot be unambiguously removed since it creates a term 0 / 0. +In this case the hypergeometric series is understood to terminate before +the division by zero occurs. This convention is consistent with Mathematica. +An alternative convention of eliminating the parameters can be toggled +with ``eliminate_all=True``: + + >>> hyper([2,-1], [-1], 3) + 7.0 + >>> hyper([2,-1], [-1], 3, eliminate_all=True) + 0.25 + >>> hyper([2], [], 3) + 0.25 + +**References** + +* [Buhring]_ + +""" + +hypercomb = r""" +Computes a weighted combination of hypergeometric functions + +.. math :: + + \sum_{r=1}^N \left[ \prod_{k=1}^{l_r} {w_{r,k}}^{c_{r,k}} + \frac{\prod_{k=1}^{m_r} \Gamma(\alpha_{r,k})}{\prod_{k=1}^{n_r} + \Gamma(\beta_{r,k})} + \,_{p_r}F_{q_r}(a_{r,1},\ldots,a_{r,p}; b_{r,1}, + \ldots, b_{r,q}; z_r)\right]. + +Typically the parameters are linear combinations of a small set of base +parameters; :func:`~mpmath.hypercomb` permits computing a correct value in +the case that some of the `\alpha`, `\beta`, `b` turn out to be +nonpositive integers, or if division by zero occurs for some `w^c`, +assuming that there are opposing singularities that cancel out. +The limit is computed by evaluating the function with the base +parameters perturbed, at a higher working precision. + +The first argument should be a function that takes the perturbable +base parameters ``params`` as input and returns `N` tuples +``(w, c, alpha, beta, a, b, z)``, where the coefficients ``w``, ``c``, +gamma factors ``alpha``, ``beta``, and hypergeometric coefficients +``a``, ``b`` each should be lists of numbers, and ``z`` should be a single +number. + +**Examples** + +The following evaluates + +.. math :: + + (a-1) \frac{\Gamma(a-3)}{\Gamma(a-4)} \,_1F_1(a,a-1,z) = e^z(a-4)(a+z-1) + +with `a=1, z=3`. There is a zero factor, two gamma function poles, and +the 1F1 function is singular; all singularities cancel out to give a finite +value:: + + >>> from mpmath import mp, hypercomb, exp + >>> mp.pretty = True + >>> hypercomb(lambda a: [([a-1],[1],[a-3],[a-4],[a],[a-1],3)], [1]) + -180.769832308689 + >>> -9*exp(3) + -180.769832308689 + +""" + +hyp0f1 = r""" +Gives the hypergeometric function `\,_0F_1`, sometimes known as the +confluent limit function, defined as + +.. math :: + + \,_0F_1(a,z) = \sum_{k=0}^{\infty} \frac{1}{(a)_k} \frac{z^k}{k!}. + +This function satisfies the differential equation `z f''(z) + a f'(z) = f(z)`, +and is related to the Bessel function of the first kind (see :func:`~mpmath.besselj`). + +``hyp0f1(a,z)`` is equivalent to ``hyper([],[a],z)``; see documentation for +:func:`~mpmath.hyper` for more information. + +**Examples** + +Evaluation for arbitrary arguments:: + + >>> from mpmath import mp, hyp0f1, chop, diff + >>> mp.dps = 25 + >>> mp.pretty = True + >>> hyp0f1(2, 0.25) + 1.130318207984970054415392 + >>> hyp0f1((1,2), 1234567) + 6.27287187546220705604627e+964 + >>> hyp0f1(3+4j, 1000000j) + (3.905169561300910030267132e+606 + 3.807708544441684513934213e+606j) + +Evaluation is supported for arbitrarily large values of `z`, +using asymptotic expansions:: + + >>> hyp0f1(1, 10**50) + 2.131705322874965310390701e+8685889638065036553022565 + >>> hyp0f1(1, -10**50) + 1.115945364792025420300208e-13 + +Verifying the differential equation:: + + >>> a = 2.5 + >>> f = lambda z: hyp0f1(a,z) + >>> for z in [0, 10, 3+4j]: + ... chop(z*diff(f,z,2) + a*diff(f,z) - f(z)) + ... + 0.0 + 0.0 + 0.0 + +""" + +hyp1f1 = r""" +Gives the confluent hypergeometric function of the first kind, + +.. math :: + + \,_1F_1(a,b,z) = \sum_{k=0}^{\infty} \frac{(a)_k}{(b)_k} \frac{z^k}{k!}, + +also known as Kummer's function and sometimes denoted by `M(a,b,z)`. This +function gives one solution to the confluent (Kummer's) differential equation + +.. math :: + + z f''(z) + (b-z) f'(z) - af(z) = 0. + +A second solution is given by the `U` function; see :func:`~mpmath.hyperu`. +Solutions are also given in an alternate form by the Whittaker +functions (:func:`~mpmath.whitm`, :func:`~mpmath.whitw`). + +``hyp1f1(a,b,z)`` is equivalent +to ``hyper([a],[b],z)``; see documentation for :func:`~mpmath.hyper` for more +information. + +**Examples** + +Evaluation for real and complex values of the argument `z`, with +fixed parameters `a = 2, b = -1/3`:: + + >>> from mpmath import mp, hyp1f1, j, chop, diff, exp, quad, gammaprod + >>> mp.dps = 25 + >>> mp.pretty = True + >>> hyp1f1(2, (-1,3), 3.25) + -2815.956856924817275640248 + >>> hyp1f1(2, (-1,3), -3.25) + -1.145036502407444445553107 + >>> hyp1f1(2, (-1,3), 1000) + -8.021799872770764149793693e+441 + >>> hyp1f1(2, (-1,3), -1000) + 0.000003131987633006813594535331 + >>> hyp1f1(2, (-1,3), 100+100j) + (-3.189190365227034385898282e+48 - 1.106169926814270418999315e+49j) + +Parameters may be complex:: + + >>> hyp1f1(2+3j, -1+j, 10j) + (261.8977905181045142673351 + 160.8930312845682213562172j) + +Arbitrarily large values of `z` are supported:: + + >>> hyp1f1(3, 4, 10**20) + 3.890569218254486878220752e+43429448190325182745 + >>> hyp1f1(3, 4, -10**20) + 6.0e-60 + >>> hyp1f1(3, 4, 10**20*j) + (-1.935753855797342532571597e-20 - 2.291911213325184901239155e-20j) + +Verifying the differential equation:: + + >>> a, b = 1.5, 2 + >>> f = lambda z: hyp1f1(a,b,z) + >>> for z in [0, -10, 3, 3+4j]: + ... chop(z*diff(f,z,2) + (b-z)*diff(f,z) - a*f(z)) + ... + 0.0 + 0.0 + 0.0 + 0.0 + +An integral representation:: + + >>> a, b = 1.5, 3 + >>> z = 1.5 + >>> hyp1f1(a,b,z) + 2.269381460919952778587441 + >>> g = lambda t: exp(z*t)*t**(a-1)*(1-t)**(b-a-1) + >>> gammaprod([b],[a,b-a])*quad(g, [0,1]) + 2.269381460919952778587441 + + +""" + +hyp1f2 = r""" +Gives the hypergeometric function `\,_1F_2(a_1,a_2;b_1,b_2; z)`. +The call ``hyp1f2(a1,b1,b2,z)`` is equivalent to +``hyper([a1],[b1,b2],z)``. + +Evaluation works for complex and arbitrarily large arguments:: + + >>> from mpmath import mp, hyp1f2, j + >>> mp.dps = 25 + >>> mp.pretty = True + >>> a, b, c = 1.5, (-1,3), 2.25 + >>> hyp1f2(a, b, c, 10**20) + -1.159388148811981535941434e+8685889639 + >>> hyp1f2(a, b, c, -10**20) + -12.60262607892655945795907 + >>> hyp1f2(a, b, c, 10**20*j) + (4.237220401382240876065501e+6141851464 - 2.950930337531768015892987e+6141851464j) + >>> hyp1f2(2+3j, -2j, 0.5j, 10-20j) + (135881.9905586966432662004 - 86681.95885418079535738828j) + +""" + +hyp2f2 = r""" +Gives the hypergeometric function `\,_2F_2(a_1,a_2;b_1,b_2; z)`. +The call ``hyp2f2(a1,a2,b1,b2,z)`` is equivalent to +``hyper([a1,a2],[b1,b2],z)``. + +Evaluation works for complex and arbitrarily large arguments:: + + >>> from mpmath import mp, hyp2f2, j + >>> mp.dps = 25 + >>> mp.pretty = True + >>> a, b, c, d = 1.5, (-1,3), 2.25, 4 + >>> hyp2f2(a, b, c, d, 10**20) + -5.275758229007902299823821e+43429448190325182663 + >>> hyp2f2(a, b, c, d, -10**20) + 2561445.079983207701073448 + >>> hyp2f2(a, b, c, d, 10**20*j) + (2218276.509664121194836667 - 1280722.539991603850462856j) + >>> hyp2f2(2+3j, -2j, 0.5j, 4j, 10-20j) + (80500.68321405666957342788 - 20346.82752982813540993502j) + +""" + +hyp2f3 = r""" +Gives the hypergeometric function `\,_2F_3(a_1,a_2;b_1,b_2,b_3; z)`. +The call ``hyp2f3(a1,a2,b1,b2,b3,z)`` is equivalent to +``hyper([a1,a2],[b1,b2,b3],z)``. + +Evaluation works for arbitrarily large arguments:: + + >>> from mpmath import mp, hyp2f3, j + >>> mp.dps = 25 + >>> mp.pretty = True + >>> a1,a2,b1,b2,b3 = 1.5, (-1,3), 2.25, 4, (1,5) + >>> hyp2f3(a1,a2,b1,b2,b3,10**20) + -4.169178177065714963568963e+8685889590 + >>> hyp2f3(a1,a2,b1,b2,b3,-10**20) + 7064472.587757755088178629 + >>> hyp2f3(a1,a2,b1,b2,b3,10**20*j) + (-5.163368465314934589818543e+6141851415 + 1.783578125755972803440364e+6141851416j) + >>> hyp2f3(2+3j, -2j, 0.5j, 4j, -1-j, 10-20j) + (-2280.938956687033150740228 + 13620.97336609573659199632j) + >>> hyp2f3(2+3j, -2j, 0.5j, 4j, -1-j, 10000000-20000000j) + (4.849835186175096516193e+3504 - 3.365981529122220091353633e+3504j) + +""" + +hyp2f1 = r""" +Gives the Gauss hypergeometric function `\,_2F_1` (often simply referred to as +*the* hypergeometric function), defined for `|z| < 1` as + +.. math :: + + \,_2F_1(a,b,c,z) = \sum_{k=0}^{\infty} + \frac{(a)_k (b)_k}{(c)_k} \frac{z^k}{k!}. + +and for `|z| \ge 1` by analytic continuation, with a branch cut on `(1, \infty)` +when necessary. + +Special cases of this function include many of the orthogonal polynomials as +well as the incomplete beta function and other functions. Properties of the +Gauss hypergeometric function are documented comprehensively in many references, +for example Abramowitz & Stegun, section 15. + +The implementation supports the analytic continuation as well as evaluation +close to the unit circle where `|z| \approx 1`. The syntax ``hyp2f1(a,b,c,z)`` +is equivalent to ``hyper([a,b],[c],z)``. + +**Examples** + +Evaluation with `z` inside, outside and on the unit circle, for +fixed parameters:: + + >>> from mpmath import mp, hyp2f1, j, gammaprod, quad, diff, chop + >>> mp.dps = 25 + >>> mp.pretty = True + >>> hyp2f1(2, (1,2), 4, 0.75) + 1.303703703703703703703704 + >>> hyp2f1(2, (1,2), 4, -1.75) + 0.7431290566046919177853916 + >>> hyp2f1(2, (1,2), 4, 1.75) + (1.418075801749271137026239 - 1.114976146679907015775102j) + >>> hyp2f1(2, (1,2), 4, 1) + 1.6 + >>> hyp2f1(2, (1,2), 4, -1) + 0.8235498012182875315037882 + >>> hyp2f1(2, (1,2), 4, j) + (0.9144026291433065674259078 + 0.2050415770437884900574923j) + >>> hyp2f1(2, (1,2), 4, 2+j) + (0.9274013540258103029011549 + 0.7455257875808100868984496j) + >>> hyp2f1(2, (1,2), 4, 0.25j) + (0.9931169055799728251931672 + 0.06154836525312066938147793j) + +Evaluation with complex parameter values:: + + >>> hyp2f1(1+j, 0.75, 10j, 1+5j) + (0.8834833319713479923389638 + 0.7053886880648105068343509j) + +Evaluation with `z = 1`:: + + >>> hyp2f1(-2.5, 3.5, 1.5, 1) + 0.0 + >>> hyp2f1(-2.5, 3, 4, 1) + 0.06926406926406926406926407 + >>> hyp2f1(2, 3, 4, 1) + inf + +Evaluation for huge arguments:: + + >>> hyp2f1((-1,3), 1.75, 4, '1e100') + (7.883714220959876246415651e+32 + 1.365499358305579597618785e+33j) + >>> hyp2f1((-1,3), 1.75, 4, '1e1000000') + (7.883714220959876246415651e+333332 + 1.365499358305579597618785e+333333j) + >>> hyp2f1((-1,3), 1.75, 4, '1e1000000j') + (1.365499358305579597618785e+333333 - 7.883714220959876246415651e+333332j) + +An integral representation:: + + >>> a,b,c,z = -0.5, 1, 2.5, 0.25 + >>> g = lambda t: t**(b-1) * (1-t)**(c-b-1) * (1-t*z)**(-a) + >>> gammaprod([c],[b,c-b]) * quad(g, [0,1]) + 0.9480458814362824478852618 + >>> hyp2f1(a,b,c,z) + 0.9480458814362824478852618 + +Verifying the hypergeometric differential equation:: + + >>> f = lambda z: hyp2f1(a,b,c,z) + >>> chop(z*(1-z)*diff(f,z,2) + (c-(a+b+1)*z)*diff(f,z) - a*b*f(z)) + 0.0 + +""" + +hyp3f2 = r""" +Gives the generalized hypergeometric function `\,_3F_2`, defined for `|z| < 1` +as + +.. math :: + + \,_3F_2(a_1,a_2,a_3,b_1,b_2,z) = \sum_{k=0}^{\infty} + \frac{(a_1)_k (a_2)_k (a_3)_k}{(b_1)_k (b_2)_k} \frac{z^k}{k!}. + +and for `|z| \ge 1` by analytic continuation. The analytic structure of this +function is similar to that of `\,_2F_1`, generally with a singularity at +`z = 1` and a branch cut on `(1, \infty)`. + +Evaluation is supported inside, on, and outside +the circle of convergence `|z| = 1`:: + + >>> from mpmath import mp, hyp3f2, sqrt, j, pi, ln2, ellipe, hyp2f1 + >>> mp.dps = 25 + >>> mp.pretty = True + >>> hyp3f2(1,2,3,4,5,0.25) + 1.083533123380934241548707 + >>> hyp3f2(1,2+2j,3,4,5,-10+10j) + (0.1574651066006004632914361 - 0.03194209021885226400892963j) + >>> hyp3f2(1,2,3,4,5,-10) + 0.3071141169208772603266489 + >>> hyp3f2(1,2,3,4,5,10) + (-0.4857045320523947050581423 - 0.5988311440454888436888028j) + >>> hyp3f2(0.25,1,1,2,1.5,1) + 1.157370995096772047567631 + >>> (8-pi-2*ln2)/3 + 1.157370995096772047567631 + >>> hyp3f2(1+j,0.5j,2,1,-2j,-1) + (1.74518490615029486475959 + 0.1454701525056682297614029j) + >>> hyp3f2(1+j,0.5j,2,1,-2j,sqrt(j)) + (0.9829816481834277511138055 - 0.4059040020276937085081127j) + >>> hyp3f2(-3,2,1,-5,4,1) + 1.41 + >>> hyp3f2(-3,2,1,-5,4,2) + 2.12 + +Evaluation very close to the unit circle:: + + >>> hyp3f2(1,2,3,4,5,'1.0001') + (1.564877796743282766872279 - 3.76821518787438186031973e-11j) + >>> hyp3f2(1,2,3,4,5,'1+0.0001j') + (1.564747153061671573212831 + 0.0001305757570366084557648482j) + >>> hyp3f2(1,2,3,4,5,'0.9999') + 1.564616644881686134983664 + >>> hyp3f2(1,2,3,4,5,'-0.9999') + 0.7823896253461678060196207 + +.. note :: + + Evaluation for `|z-1|` small can currently be inaccurate or slow + for some parameter combinations. + +For various parameter combinations, `\,_3F_2` admits representation in terms +of hypergeometric functions of lower degree, or in terms of +simpler functions:: + + >>> for a, b, z in [(1,2,-1), (2,0.5,1)]: + ... hyp2f1(a,b,a+b+0.5,z)**2 + ... hyp3f2(2*a,a+b,2*b,a+b+0.5,2*a+2*b,z) + ... + 0.4246104461966439006086308 + 0.4246104461966439006086308 + 7.111111111111111111111111 + 7.111111111111111111111111 + + >>> z = 2+3j + >>> hyp3f2(0.5,1,1.5,2,2,z) + (0.7621440939243342419729144 + 0.4249117735058037649915723j) + >>> 4*(pi-2*ellipe(z))/(pi*z) + (0.7621440939243342419729144 + 0.4249117735058037649915723j) + +""" + +hyperu = r""" +Gives the Tricomi confluent hypergeometric function `U`, also known as +the Kummer or confluent hypergeometric function of the second kind. This +function gives a second linearly independent solution to the confluent +hypergeometric differential equation (the first is provided by `\,_1F_1` -- +see :func:`~mpmath.hyp1f1`). + +**Examples** + +Evaluation for arbitrary complex arguments:: + + >>> from mpmath import mp, hyperu, chop, diff, quad, gamma, exp, inf + >>> mp.dps = 25 + >>> mp.pretty = True + >>> hyperu(2,3,4) + 0.0625 + >>> hyperu(0.25, 5, 1000) + 0.1779949416140579573763523 + >>> hyperu(0.25, 5, -1000) + (0.1256256609322773150118907 - 0.1256256609322773150118907j) + +The `U` function may be singular at `z = 0`:: + + >>> hyperu(1.5, 2, 0) + inf + >>> hyperu(1.5, -2, 0) + 0.1719434921288400112603671 + +Verifying the differential equation:: + + >>> a, b = 1.5, 2 + >>> f = lambda z: hyperu(a,b,z) + >>> for z in [-10, 3, 3+4j]: + ... chop(z*diff(f,z,2) + (b-z)*diff(f,z) - a*f(z)) + ... + 0.0 + 0.0 + 0.0 + +An integral representation:: + + >>> a,b,z = 2, 3.5, 4.25 + >>> hyperu(a,b,z) + 0.06674960718150520648014567 + >>> quad(lambda t: exp(-z*t)*t**(a-1)*(1+t)**(b-a-1),[0,inf]) / gamma(a) + 0.06674960718150520648014567 + + +[1] http://people.math.sfu.ca/~cbm/aands/page_504.htm +""" + +hyp2f0 = r""" +Gives the hypergeometric function `\,_2F_0`, defined formally by the +series + +.. math :: + + \,_2F_0(a,b;;z) = \sum_{n=0}^{\infty} (a)_n (b)_n \frac{z^n}{n!}. + +This series usually does not converge. For small enough `z`, it can be viewed +as an asymptotic series that may be summed directly with an appropriate +truncation. When this is not the case, :func:`~mpmath.hyp2f0` gives a regularized sum, +or equivalently, it uses a representation in terms of the +hypergeometric U function [1]. The series also converges when either `a` or `b` +is a nonpositive integer, as it then terminates into a polynomial +after `-a` or `-b` terms. + +**Examples** + +Evaluation is supported for arbitrary complex arguments:: + + >>> from mpmath import mp, hyp2f0, j, nprint, identify, taylor + >>> mp.dps = 25 + >>> mp.pretty = True + >>> hyp2f0((2,3), 1.25, -100) + 0.07095851870980052763312791 + >>> hyp2f0((2,3), 1.25, 100) + (-0.03254379032170590665041131 + 0.07269254613282301012735797j) + >>> hyp2f0(-0.75, 1-j, 4j) + (-0.3579987031082732264862155 - 3.052951783922142735255881j) + +Even with real arguments, the regularized value of 2F0 is often complex-valued, +but the imaginary part decreases exponentially as `z \to 0`. In the following +example, the first call uses complex evaluation while the second has a small +enough `z` to evaluate using the direct series and thus the returned value +is strictly real (this should be taken to indicate that the imaginary +part is less than ``eps``):: + + >>> mp.dps = 15 + >>> hyp2f0(1.5, 0.5, 0.05) + (1.04166637647907 + 8.34584913683906e-8j) + >>> hyp2f0(1.5, 0.5, 0.0005) + 1.00037535207621 + +The imaginary part can be retrieved by increasing the working precision:: + + >>> mp.dps = 80 + >>> nprint(hyp2f0(1.5, 0.5, 0.009).imag) + 1.23828e-46 + +In the polynomial case (the series terminating), 2F0 can evaluate exactly:: + + >>> mp.dps = 15 + >>> hyp2f0(-6,-6,2) + 291793.0 + >>> identify(hyp2f0(-2,1,0.25)) + '(5/8)' + +The coefficients of the polynomials can be recovered using Taylor expansion:: + + >>> nprint(taylor(lambda x: hyp2f0(-3,0.5,x), 0, 10)) + [1.0, -1.5, 2.25, -1.875, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + >>> nprint(taylor(lambda x: hyp2f0(-4,0.5,x), 0, 10)) + [1.0, -2.0, 4.5, -7.5, 6.5625, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + + +[1] http://people.math.sfu.ca/~cbm/aands/page_504.htm +""" + + +gammainc = r""" +``gammainc(z, a=0, b=inf)`` computes the (generalized) incomplete +gamma function with integration limits `[a, b]`: + +.. math :: + + \Gamma(z,a,b) = \int_a^b t^{z-1} e^{-t} \, dt + +The generalized incomplete gamma function reduces to the +following special cases when one or both endpoints are fixed: + +* `\Gamma(z,0,\infty)` is the standard ("complete") + gamma function, `\Gamma(z)` (available directly + as the mpmath function :func:`~mpmath.gamma`) +* `\Gamma(z,a,\infty)` is the "upper" incomplete gamma + function, `\Gamma(z,a)` +* `\Gamma(z,0,b)` is the "lower" incomplete gamma + function, `\gamma(z,b)`. + +Of course, we have +`\Gamma(z,0,x) + \Gamma(z,x,\infty) = \Gamma(z)` +for all `z` and `x`. + +Note however that some authors reverse the order of the +arguments when defining the lower and upper incomplete +gamma function, so one should be careful to get the correct +definition. + +If also given the keyword argument ``regularized=True``, +:func:`~mpmath.gammainc` computes the "regularized" incomplete gamma +function + +.. math :: + + P(z,a,b) = \frac{\Gamma(z,a,b)}{\Gamma(z)}. + +**Examples** + +We can compare with numerical quadrature to verify that +:func:`~mpmath.gammainc` computes the integral in the definition:: + + >>> from mpmath import (mp, gammainc, quad, exp, findroot, mpf, sqrt, + ... erf, pi, identify, ei, lower_gamma, upper_gamma) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> gammainc(2+3j, 4, 10) + (0.00977212668627705160602312 - 0.0770637306312989892451977j) + >>> quad(lambda t: t**(2+3j-1) * exp(-t), [4, 10]) + (0.00977212668627705160602312 - 0.0770637306312989892451977j) + +Argument symmetries follow directly from the integral definition:: + + >>> gammainc(3, 4, 5) + gammainc(3, 5, 4) + 0.0 + >>> lower_gamma(3,2) + gammainc(3,2,4) + 1.523793388892911312363331 + >>> lower_gamma(3,4) + 1.523793388892911312363331 + >>> findroot(lambda z: gammainc(2,z,3), 1) + 3.0 + +Evaluation for arbitrarily large arguments:: + + >>> upper_gamma(10, 100) + 4.083660630910611272288592e-26 + >>> upper_gamma(10, 10000000000000000) + 5.290402449901174752972486e-4342944819032375 + >>> upper_gamma(3+4j, 1000000+1000000j) + (-1.257913707524362408877881e-434284 + 2.556691003883483531962095e-434284j) + +Evaluation of a generalized incomplete gamma function automatically chooses +the representation that gives a more accurate result, depending on which +parameter is larger:: + + >>> upper_gamma(10000000, 3) - upper_gamma(10000000, 2) # Bad + 0.0 + >>> gammainc(10000000, 2, 3) # Good + 1.755146243738946045873491e+4771204 + >>> lower_gamma(2, 100000001) - lower_gamma(2, 100000000) # Bad + 0.0 + >>> gammainc(2, 100000000, 100000001) # Good + 4.078258353474186729184421e-43429441 + +The incomplete gamma functions satisfy simple recurrence +relations:: + + >>> mp.dps = 25 + >>> z, a = mpf(3.5), mpf(2) + >>> upper_gamma(z+1, a) + 10.60130296933533459267329 + >>> z*upper_gamma(z,a) + a**z*exp(-a) + 10.60130296933533459267329 + >>> lower_gamma(z+1,a) + 1.030425427232114336470932 + >>> z*lower_gamma(z,a) - a**z*exp(-a) + 1.030425427232114336470932 + +Evaluation at integers and poles:: + + >>> gammainc(-3, -4, -5) + (-0.2214577048967798566234192 + 0.0j) + >>> lower_gamma(-3, 5) + inf + +If `z` is an integer, the recurrence reduces the incomplete gamma +function to `P(a) \exp(-a) + Q(b) \exp(-b)` where `P` and +`Q` are polynomials:: + + >>> upper_gamma(1, 2) + 0.1353352832366126918939995 + >>> exp(-2) + 0.1353352832366126918939995 + >>> mp.dps = 50 + >>> identify(gammainc(6, 1, 2), ['exp(-1)', 'exp(-2)']) + '(326*exp(-1) + (-872)*exp(-2))' + +The incomplete gamma functions reduce to functions such as +the exponential integral Ei and the error function for special +arguments:: + + >>> mp.dps = 25 + >>> upper_gamma(0, 4) + 0.00377935240984890647887486 + >>> -ei(-4) + 0.00377935240984890647887486 + >>> lower_gamma(0.5, 2) + 1.691806732945198336509541 + >>> sqrt(pi)*erf(sqrt(2)) + 1.691806732945198336509541 + +**Related functions** + +See also :func:`~mpmath.lower_gamma` and :func:`~mpmath.upper_gamma`. + +""" + +lower_gamma = r""" +``lower_gamma(z, b)`` is the "lower" incomplete gamma function. + +.. math :: + + \Gamma(z,0,b) = \int_0^b t^{z-1} e^{-t} \, dt + +See also :func:`~mpmath.gammainc`. +""" + +upper_gamma = r""" +``upper_gamma(z, a)`` is the "upper" incomplete gamma function. + +.. math :: + + \Gamma(z,a,\infty) = \int_a^{\infty} t^{z-1} e^{-t} \, dt + +See also :func:`~mpmath.gammainc`. + +""" + +erf = r""" +Computes the error function, `\mathrm{erf}(x)`. The error +function is the normalized antiderivative of the Gaussian function +`\exp(-t^2)`. More precisely, + +.. math:: + + \mathrm{erf}(x) = \frac{2}{\sqrt \pi} \int_0^x \exp(-t^2) \,dt + +**Basic examples** + +Simple values and limits include:: + + >>> from mpmath import mp, erf, inf, nprint, chop, taylor, j + >>> mp.pretty = True + >>> erf(0) + 0.0 + >>> erf(1) + 0.842700792949715 + >>> erf(-1) + -0.842700792949715 + >>> erf(inf) + 1.0 + >>> erf(-inf) + -1.0 + +For large real `x`, `\mathrm{erf}(x)` approaches 1 very +rapidly:: + + >>> erf(3) + 0.999977909503001 + >>> erf(5) + 0.999999999998463 + +The error function is an odd function:: + + >>> nprint(chop(taylor(erf, 0, 5))) + [0.0, 1.12838, 0.0, -0.376126, 0.0, 0.112838] + +:func:`~mpmath.erf` implements arbitrary-precision evaluation and +supports complex numbers:: + + >>> mp.dps = 50 + >>> erf(0.5) + 0.52049987781304653768274665389196452873645157575796 + >>> mp.dps = 25 + >>> erf(1+j) + (1.316151281697947644880271 + 0.1904534692378346862841089j) + +Evaluation is supported for large arguments:: + + >>> mp.dps = 25 + >>> erf('1e1000') + 1.0 + >>> erf('-1e1000') + -1.0 + >>> erf('1e-1000') + 1.128379167095512573896159e-1000 + >>> erf('1e7j') + (0.0 + 8.593897639029319267398803e+43429448190317j) + >>> erf('1e7+1e7j') + (0.9999999858172446172631323 + 3.728805278735270407053139e-8j) + +**Related functions** + +See also :func:`~mpmath.erfc`, which is more accurate for large `x`, +and :func:`~mpmath.erfi` which gives the antiderivative of +`\exp(t^2)`. + +The Fresnel integrals :func:`~mpmath.fresnels` and :func:`~mpmath.fresnelc` +are also related to the error function. +""" + +erfc = r""" +Computes the complementary error function, +`\mathrm{erfc}(x) = 1-\mathrm{erf}(x)`. +This function avoids cancellation that occurs when naively +computing the complementary error function as ``1-erf(x)``:: + + >>> from mpmath import mp, erf, erfc + >>> mp.pretty = True + >>> 1 - erf(10) + 0.0 + >>> erfc(10) + 2.08848758376254e-45 + +:func:`~mpmath.erfc` works accurately even for ludicrously large +arguments:: + + >>> erfc(10**10) + 4.3504398860243e-43429448190325182776 + +Complex arguments are supported:: + + >>> erfc(500+50j) + (1.19739830969552e-107492 + 1.46072418957528e-107491j) + +""" + + +erfi = r""" +Computes the imaginary error function, `\mathrm{erfi}(x)`. +The imaginary error function is defined in analogy with the +error function, but with a positive sign in the integrand: + +.. math :: + + \mathrm{erfi}(x) = \frac{2}{\sqrt \pi} \int_0^x \exp(t^2) \,dt + +Whereas the error function rapidly converges to 1 as `x` grows, +the imaginary error function rapidly diverges to infinity. +The functions are related as +`\mathrm{erfi}(x) = -i\,\mathrm{erf}(ix)` for all complex +numbers `x`. + +**Examples** + +Basic values and limits:: + + >>> from mpmath import mp, erfi, inf, erf + >>> mp.pretty = True + >>> erfi(0) + 0.0 + >>> erfi(1) + 1.65042575879754 + >>> erfi(-1) + -1.65042575879754 + >>> erfi(inf) + inf + >>> erfi(-inf) + -inf + +Note the symmetry between erf and erfi:: + + >>> erfi(3j) + (0.0 + 0.999977909503001j) + >>> erf(3) + 0.999977909503001 + >>> erf(1+2j) + (-0.536643565778565 - 5.04914370344703j) + >>> erfi(2+1j) + (-5.04914370344703 - 0.536643565778565j) + +Large arguments are supported:: + + >>> erfi(1000) + 1.71130938718796e+434291 + >>> erfi(10**10) + 7.3167287567024e+43429448190325182754 + >>> erfi(-10**10) + -7.3167287567024e+43429448190325182754 + >>> erfi(1000-500j) + (2.49895233563961e+325717 + 2.6846779342253e+325717j) + >>> erfi(100000j) + (0.0 + 1.0j) + >>> erfi(-100000j) + (0.0 - 1.0j) + + +""" + +erfinv = r""" +Computes the inverse error function, satisfying + +.. math :: + + \mathrm{erf}(\mathrm{erfinv}(x)) = + \mathrm{erfinv}(\mathrm{erf}(x)) = x. + +This function is defined only for `-1 \le x \le 1`. + +**Examples** + +Special values include:: + + >>> from mpmath import mp, erfinv, erf, quad, sqrt, pi + >>> mp.pretty = True + >>> erfinv(0) + 0.0 + >>> erfinv(1) + inf + >>> erfinv(-1) + -inf + +The domain is limited to the standard interval:: + + >>> erfinv(2) + Traceback (most recent call last): + ... + ValueError: erfinv(x) is defined only for -1 <= x <= 1 + +It is simple to check that :func:`~mpmath.erfinv` computes inverse values of +:func:`~mpmath.erf` as promised:: + + >>> erf(erfinv(0.75)) + 0.75 + >>> erf(erfinv(-0.995)) + -0.995 + +:func:`~mpmath.erfinv` supports arbitrary-precision evaluation:: + + >>> mp.dps = 50 + >>> x = erf(2) + >>> x + 0.99532226501895273416206925636725292861089179704006 + >>> erfinv(x) + 2.0 + +A definite integral involving the inverse error function:: + + >>> mp.dps = 15 + >>> quad(erfinv, [0, 1]) + 0.564189583547756 + >>> 1/sqrt(pi) + 0.564189583547756 + +The inverse error function can be used to generate random numbers +with a Gaussian distribution (although this is a relatively +inefficient algorithm):: + + >>> nprint([erfinv(2*rand()-1) for n in range(6)]) # doctest: +SKIP + [-0.586747, 1.10233, -0.376796, 0.926037, -0.708142, -0.732012] + +""" + +npdf = r""" +``npdf(x, mu=0, sigma=1)`` evaluates the probability density +function of a normal distribution with mean value `\mu` +and variance `\sigma^2`. + +Elementary properties of the probability distribution can +be verified using numerical integration:: + + >>> from mpmath import mp, quad, inf, npdf + >>> mp.pretty = True + >>> quad(npdf, [-inf, inf]) + 1.0 + >>> quad(lambda x: npdf(x, 3), [3, inf]) + 0.5 + >>> quad(lambda x: npdf(x, 3, 2), [3, inf]) + 0.5 + +See also :func:`~mpmath.ncdf`, which gives the cumulative +distribution. +""" + +ncdf = r""" +``ncdf(x, mu=0, sigma=1)`` evaluates the cumulative distribution +function of a normal distribution with mean value `\mu` +and variance `\sigma^2`. + +See also :func:`~mpmath.npdf`, which gives the probability density. + +Elementary properties include:: + + >>> from mpmath import mp, ncdf, pi, inf, diff, npdf + >>> mp.pretty = True + >>> ncdf(pi, mu=pi) + 0.5 + >>> ncdf(-inf) + 0.0 + >>> ncdf(+inf) + 1.0 + +The cumulative distribution is the integral of the density +function having identical mu and sigma:: + + >>> mp.dps = 15 + >>> diff(ncdf, 2) + 0.053990966513188 + >>> npdf(2) + 0.053990966513188 + >>> diff(lambda x: ncdf(x, 1, 0.5), 0) + 0.107981933026376 + >>> npdf(0, 1, 0.5) + 0.107981933026376 +""" + +expint = r""" +:func:`~mpmath.expint` gives the generalized exponential integral +or En-function, + +.. math :: + + \mathrm{E}_n(z) = \int_1^{\infty} \frac{e^{-zt}}{t^n} dt, + +where `n` and `z` may both be complex numbers. The case with `n = 1` is +also given by :func:`~mpmath.e1`. + +**Examples** + +Evaluation at real and complex arguments:: + + >>> from mpmath import mp, expint, fac, exp, pi + >>> mp.dps = 25 + >>> mp.pretty = True + >>> expint(1, 6.25) + 0.0002704758872637179088496194 + >>> expint(-3, 2+3j) + (0.00299658467335472929656159 + 0.06100816202125885450319632j) + >>> expint(2+3j, 4-5j) + (0.001803529474663565056945248 - 0.002235061547756185403349091j) + +At negative integer values of `n`, `E_n(z)` reduces to a +rational-exponential function:: + + >>> f = lambda n, z: fac(n)*sum(z**k/fac(k-1) for k in range(1,n+2))/\ + ... exp(z)/z**(n+2) + >>> n = 3 + >>> z = 1/pi + >>> expint(-n,z) + 584.2604820613019908668219 + >>> f(n,z) + 584.2604820613019908668219 + >>> n = 5 + >>> expint(-n,z) + 115366.5762594725451811138 + >>> f(n,z) + 115366.5762594725451811138 +""" + +e1 = r""" +Computes the exponential integral `\mathrm{E}_1(z)`, given by + +.. math :: + + \mathrm{E}_1(z) = \int_z^{\infty} \frac{e^{-t}}{t} dt. + +This is equivalent to :func:`~mpmath.expint` with `n = 1`. + +**Examples** + +Two ways to evaluate this function:: + + >>> from mpmath import mp, e1, expint, ei + >>> mp.dps = 25 + >>> mp.pretty = True + >>> e1(6.25) + 0.0002704758872637179088496194 + >>> expint(1,6.25) + 0.0002704758872637179088496194 + +The E1-function is essentially the same as the Ei-function (:func:`~mpmath.ei`) +with negated argument, except for an imaginary branch cut term:: + + >>> e1(2.5) + 0.02491491787026973549562801 + >>> -ei(-2.5) + 0.02491491787026973549562801 + >>> e1(-2.5) + (-7.073765894578600711923552 - 3.141592653589793238462643j) + >>> -ei(2.5) + -7.073765894578600711923552 + +""" + +ei = r""" +Computes the exponential integral or Ei-function, `\mathrm{Ei}(x)`. +The exponential integral is defined as + +.. math :: + + \mathrm{Ei}(x) = \int_{-\infty\,}^x \frac{e^t}{t} \, dt. + +When the integration range includes `t = 0`, the exponential +integral is interpreted as providing the Cauchy principal value. + +For real `x`, the Ei-function behaves roughly like +`\mathrm{Ei}(x) \approx \exp(x) + \log(|x|)`. + +The Ei-function is related to the more general family of exponential +integral functions denoted by `E_n`, which are available as :func:`~mpmath.expint`. + +**Basic examples** + +Some basic values and limits are:: + + >>> from mpmath import (mp, ei, inf, quad, exp, chop, si, pi, j, chi, + ... shi, hyper, euler, ln, ci) + >>> mp.pretty = True + >>> ei(0) + -inf + >>> ei(1) + 1.89511781635594 + >>> ei(inf) + inf + >>> ei(-inf) + 0.0 + +For `x < 0`, the defining integral can be evaluated +numerically as a reference:: + + >>> ei(-4) + -0.00377935240984891 + >>> quad(lambda t: exp(t)/t, [-inf, -4]) + -0.00377935240984891 + +:func:`~mpmath.ei` supports complex arguments and arbitrary +precision evaluation:: + + >>> mp.dps = 50 + >>> ei(pi) + 10.928374389331410348638445906907535171566338835056 + >>> mp.dps = 25 + >>> ei(3+4j) + (-4.154091651642689822535359 + 4.294418620024357476985535j) + +**Related functions** + +The exponential integral is closely related to the logarithmic +integral. See :func:`~mpmath.li` for additional information. + +The exponential integral is related to the hyperbolic +and trigonometric integrals (see :func:`~mpmath.chi`, :func:`~mpmath.shi`, +:func:`~mpmath.ci`, :func:`~mpmath.si`) similarly to how the ordinary +exponential function is related to the hyperbolic and +trigonometric functions:: + + >>> mp.dps = 15 + >>> ei(3) + 9.93383257062542 + >>> chi(3) + shi(3) + 9.93383257062542 + >>> chop(ci(3j) - j*si(3j) - pi*j/2) + 9.93383257062542 + +Beware that logarithmic corrections, as in the last example +above, are required to obtain the correct branch in general. +For details, see [1]. + +The exponential integral is also a special case of the +hypergeometric function `\,_2F_2`:: + + >>> z = 0.6 + >>> z*hyper([1,1],[2,2],z) + (ln(z)-ln(1/z))/2 + euler + 0.769881289937359 + >>> ei(z) + 0.769881289937359 + +**References** + +1. [WolframFunctions]_ http://functions.wolfram.com/GammaBetaErf/ExpIntegralEi/27/01/ + +2. [AbramowitzStegun]_, section 5: + +3. [Weisstein]_ http://mathworld.wolfram.com/En-Function.html +""" + +li = r""" +Computes the logarithmic integral or li-function +`\mathrm{li}(x)`, defined by + +.. math :: + + \mathrm{li}(x) = \int_0^x \frac{1}{\log t} \, dt + +The logarithmic integral has a singularity at `x = 1`. + +Alternatively, ``li(x, offset=True)`` computes the offset +logarithmic integral (used in number theory) + +.. math :: + + \mathrm{Li}(x) = \int_2^x \frac{1}{\log t} \, dt. + +These two functions are related via the simple identity +`\mathrm{Li}(x) = \mathrm{li}(x) - \mathrm{li}(2)`. + +The logarithmic integral should also not be confused with +the polylogarithm (also denoted by Li), which is implemented +as :func:`~mpmath.polylog`. + +**Examples** + +Some basic values and limits:: + + >>> from mpmath import mp, li, findroot, ei, log, quad, inf, ln + >>> mp.dps = 30 + >>> mp.pretty = True + >>> li(0) + 0.0 + >>> li(1) + -inf + >>> li(1) + -inf + >>> li(2) + 1.04516378011749278484458888919 + >>> findroot(li, 2) + 1.45136923488338105028396848589 + >>> li(inf) + inf + >>> li(2, offset=True) + 0.0 + >>> li(1, offset=True) + -inf + >>> li(0, offset=True) + -1.04516378011749278484458888919 + >>> li(10, offset=True) + 5.12043572466980515267839286347 + +The logarithmic integral can be evaluated for arbitrary +complex arguments:: + + >>> mp.dps = 20 + >>> li(3+4j) + (3.1343755504645775265 + 2.6769247817778742392j) + +The logarithmic integral is related to the exponential integral:: + + >>> ei(log(3)) + 2.1635885946671919729 + >>> li(3) + 2.1635885946671919729 + +The logarithmic integral grows like `O(x/\log(x))`:: + + >>> mp.dps = 15 + >>> x = 10**100 + >>> x/log(x) + 4.34294481903252e+97 + >>> li(x) + 4.3619719871407e+97 + +The prime number theorem states that the number of primes less +than `x` is asymptotic to `\mathrm{Li}(x)` (equivalently +`\mathrm{li}(x)`). For example, it is known that there are +exactly 1,925,320,391,606,803,968,923 prime numbers less than +`10^{23}` [1]. The logarithmic integral provides a very +accurate estimate:: + + >>> li(10**23, offset=True) + 1.92532039161405e+21 + +A definite integral is:: + + >>> quad(li, [0, 1]) + -0.693147180559945 + >>> -ln(2) + -0.693147180559945 + +**References** + +1. [Weisstein]_ http://mathworld.wolfram.com/PrimeCountingFunction.html + +2. [Weisstein]_ http://mathworld.wolfram.com/LogarithmicIntegral.html + +""" + +ci = r""" +Computes the cosine integral, + +.. math :: + + \mathrm{Ci}(x) = -\int_x^{\infty} \frac{\cos t}{t}\,dt + = \gamma + \log x + \int_0^x \frac{\cos t - 1}{t}\,dt + +**Examples** + +Some values and limits:: + + >>> from mpmath import (mp, ci, pi, inf, chop, sinc, limit, findroot, + ... cos, quadosc, fac, nsum, euler, j) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> ci(0) + -inf + >>> ci(1) + 0.3374039229009681346626462 + >>> ci(pi) + 0.07366791204642548599010096 + >>> ci(inf) + 0.0 + >>> ci(-inf) + (0.0 + 3.141592653589793238462643j) + >>> ci(2+3j) + (1.408292501520849518759125 - 2.983617742029605093121118j) + +The cosine integral behaves roughly like the sinc function +(see :func:`~mpmath.sinc`) for large real `x`:: + + >>> ci(10**10) + -4.875060251748226537857298e-11 + >>> sinc(10**10) + -4.875060250875106915277943e-11 + >>> chop(limit(ci, inf)) + 0.0 + +It has infinitely many roots on the positive real axis:: + + >>> findroot(ci, 1) + 0.6165054856207162337971104 + >>> findroot(ci, 2) + 3.384180422551186426397851 + +Evaluation is supported for `z` anywhere in the complex plane:: + + >>> ci(10**6*(1+j)) + (4.449410587611035724984376e+434287 + 9.75744874290013526417059e+434287j) + +We can evaluate the defining integral as a reference:: + + >>> mp.dps = 15 + >>> -quadosc(lambda t: cos(t)/t, [5, inf], omega=1) + -0.190029749656644 + >>> ci(5) + -0.190029749656644 + +Some infinite series can be evaluated using the +cosine integral:: + + >>> nsum(lambda k: (-1)**k/(fac(2*k)*(2*k)), [1,inf]) + -0.239811742000565 + >>> ci(1) - euler + -0.239811742000565 + +""" + +si = r""" +Computes the sine integral, + +.. math :: + + \mathrm{Si}(x) = \int_0^x \frac{\sin t}{t}\,dt. + +The sine integral is thus the antiderivative of the sinc +function (see :func:`~mpmath.sinc`). + +**Examples** + +Some values and limits:: + + >>> from mpmath import si, mp, pi, inf, j, quad, sinc, nsum, fac + >>> mp.dps = 25 + >>> mp.pretty = True + >>> si(0) + 0.0 + >>> si(1) + 0.9460830703671830149413533 + >>> si(-1) + -0.9460830703671830149413533 + >>> si(pi) + 1.851937051982466170361053 + >>> si(inf) + 1.570796326794896619231322 + >>> si(-inf) + -1.570796326794896619231322 + >>> si(2+3j) + (4.547513889562289219853204 + 1.399196580646054789459839j) + +The sine integral approaches `\pi/2` for large real `x`:: + + >>> si(10**10) + 1.570796326707584656968511 + >>> pi/2 + 1.570796326794896619231322 + +Evaluation is supported for `z` anywhere in the complex plane:: + + >>> si(10**6*(1+j)) + (-9.75744874290013526417059e+434287 + 4.449410587611035724984376e+434287j) + +We can evaluate the defining integral as a reference:: + + >>> mp.dps = 15 + >>> quad(sinc, [0, 5]) + 1.54993124494467 + >>> si(5) + 1.54993124494467 + +Some infinite series can be evaluated using the +sine integral:: + + >>> nsum(lambda k: (-1)**k/(fac(2*k+1)*(2*k+1)), [0,inf]) + 0.946083070367183 + >>> si(1) + 0.946083070367183 + +""" + +chi = r""" +Computes the hyperbolic cosine integral, defined +in analogy with the cosine integral (see :func:`~mpmath.ci`) as + +.. math :: + + \mathrm{Chi}(x) = -\int_x^{\infty} \frac{\cosh t}{t}\,dt + = \gamma + \log x + \int_0^x \frac{\cosh t - 1}{t}\,dt + +Some values and limits:: + + >>> from mpmath import mp, chi, inf, findroot, j + >>> mp.dps = 25 + >>> mp.pretty = True + >>> chi(0) + -inf + >>> chi(1) + 0.8378669409802082408946786 + >>> chi(inf) + inf + >>> findroot(chi, 0.5) + 0.5238225713898644064509583 + >>> chi(2+3j) + (-0.1683628683277204662429321 + 2.625115880451325002151688j) + +Evaluation is supported for `z` anywhere in the complex plane:: + + >>> chi(10**6*(1+j)) + (4.449410587611035724984376e+434287 - 9.75744874290013526417059e+434287j) + +""" + +shi = r""" +Computes the hyperbolic sine integral, defined +in analogy with the sine integral (see :func:`~mpmath.si`) as + +.. math :: + + \mathrm{Shi}(x) = \int_0^x \frac{\sinh t}{t}\,dt. + +Some values and limits:: + + >>> from mpmath import mp, shi, inf, j + >>> mp.dps = 25 + >>> mp.pretty = True + >>> shi(0) + 0.0 + >>> shi(1) + 1.057250875375728514571842 + >>> shi(-1) + -1.057250875375728514571842 + >>> shi(inf) + inf + >>> shi(2+3j) + (-0.1931890762719198291678095 + 2.645432555362369624818525j) + +Evaluation is supported for `z` anywhere in the complex plane:: + + >>> shi(10**6*(1+j)) + (4.449410587611035724984376e+434287 - 9.75744874290013526417059e+434287j) + +""" + +fresnels = r""" +Computes the Fresnel sine integral + +.. math :: + + S(x) = \int_0^x \sin\left(\frac{\pi t^2}{2}\right) \,dt + +Note that some sources define this function +without the normalization factor `\pi/2`. + +**Examples** + +Some basic values and limits:: + + >>> from mpmath import mp, fresnels, inf, quad, sin, pi + >>> mp.dps = 25 + >>> mp.pretty = True + >>> fresnels(0) + 0.0 + >>> fresnels(inf) + 0.5 + >>> fresnels(-inf) + -0.5 + >>> fresnels(1) + 0.4382591473903547660767567 + >>> fresnels(1+2j) + (36.72546488399143842838788 + 15.58775110440458732748279j) + +Comparing with the definition:: + + >>> fresnels(3) + 0.4963129989673750360976123 + >>> quad(lambda t: sin(pi*t**2/2), [0,3]) + 0.4963129989673750360976123 +""" + +fresnelc = r""" +Computes the Fresnel cosine integral + +.. math :: + + C(x) = \int_0^x \cos\left(\frac{\pi t^2}{2}\right) \,dt + +Note that some sources define this function +without the normalization factor `\pi/2`. + +**Examples** + +Some basic values and limits:: + + >>> from mpmath import mp, fresnelc, inf, quad, cos, pi + >>> mp.dps = 25 + >>> mp.pretty = True + >>> fresnelc(0) + 0.0 + >>> fresnelc(inf) + 0.5 + >>> fresnelc(-inf) + -0.5 + >>> fresnelc(1) + 0.7798934003768228294742064 + >>> fresnelc(1+2j) + (16.08787137412548041729489 - 36.22568799288165021578758j) + +Comparing with the definition:: + + >>> fresnelc(3) + 0.6057207892976856295561611 + >>> quad(lambda t: cos(pi*t**2/2), [0,3]) + 0.6057207892976856295561611 +""" + +airyai = r""" +Computes the Airy function `\operatorname{Ai}(z)`, which is +the solution of the Airy differential equation `f''(z) - z f(z) = 0` +with initial conditions + +.. math :: + + \operatorname{Ai}(0) = + \frac{1}{3^{2/3}\Gamma\left(\frac{2}{3}\right)} + + \operatorname{Ai}'(0) = + -\frac{1}{3^{1/3}\Gamma\left(\frac{1}{3}\right)}. + +Other common ways of defining the Ai-function include +integrals such as + +.. math :: + + \operatorname{Ai}(x) = \frac{1}{\pi} + \int_0^{\infty} \cos\left(\frac{1}{3}t^3+xt\right) dt + \qquad x \in \mathbb{R} + + \operatorname{Ai}(z) = \frac{\sqrt{3}}{2\pi} + \int_0^{\infty} + \exp\left(-\frac{t^3}{3}-\frac{z^3}{3t^3}\right) dt. + +The Ai-function is an entire function with a turning point, +behaving roughly like a slowly decaying sine wave for `z < 0` and +like a rapidly decreasing exponential for `z > 0`. +A second solution of the Airy differential equation +is given by `\operatorname{Bi}(z)` (see :func:`~mpmath.airybi`). + +Optionally, with *derivative=alpha*, :func:`airyai` can compute the +`\alpha`-th order fractional derivative with respect to `z`. +For `\alpha = n = 1,2,3,\ldots` this gives the derivative +`\operatorname{Ai}^{(n)}(z)`, and for `\alpha = -n = -1,-2,-3,\ldots` +this gives the `n`-fold iterated integral + +.. math :: + + f_0(z) = \operatorname{Ai}(z) + + f_n(z) = \int_0^z f_{n-1}(t) dt. + +The Ai-function has infinitely many zeros, all located along the +negative half of the real axis. They can be computed with +:func:`~mpmath.airyaizero`. + +**Plots** + +.. literalinclude :: /plots/ai.py +.. image :: /plots/ai.png +.. literalinclude :: /plots/ai_c.py +.. image :: /plots/ai_c.png + +**Basic examples** + +Limits and values include:: + + >>> from mpmath import (mp, airyai, power, gamma, inf, j, findroot, + ... airyaizero, chop, airybi, besselj, nprint, + ... taylor, sqrt, diff, quad, pi, differint) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> airyai(0) + 0.3550280538878172392600632 + >>> 1/(power(3,'2/3')*gamma('2/3')) + 0.3550280538878172392600632 + >>> airyai(1) + 0.1352924163128814155241474 + >>> airyai(-1) + 0.5355608832923521187995166 + >>> airyai(inf) + 0.0 + >>> airyai(-inf) + 0.0 + +Evaluation is supported for large magnitudes of the argument:: + + >>> airyai(-100) + 0.1767533932395528780908311 + >>> airyai(100) + 2.634482152088184489550553e-291 + >>> airyai(50+50j) + (-5.31790195707456404099817e-68 - 1.163588003770709748720107e-67j) + >>> airyai(-50+50j) + (1.041242537363167632587245e+158 + 3.347525544923600321838281e+157j) + +Huge arguments are also fine:: + + >>> airyai(10**10) + 1.162235978298741779953693e-289529654602171 + >>> airyai(-10**10) + 0.0001736206448152818510510181 + >>> w = airyai(10**10*(1+j)) + >>> w.real + 5.711508683721355528322567e-186339621747698 + >>> w.imag + 1.867245506962312577848166e-186339621747697 + +The first root of the Ai-function is:: + + >>> findroot(airyai, -2) + -2.338107410459767038489197 + >>> airyaizero(1) + -2.338107410459767038489197 + +**Properties and relations** + +Verifying the Airy differential equation:: + + >>> for z in [-3.4, 0, 2.5, 1+2j]: + ... chop(airyai(z,2) - z*airyai(z)) + ... + 0.0 + 0.0 + 0.0 + 0.0 + +The first few terms of the Taylor series expansion around `z = 0` +(every third term is zero):: + + >>> nprint(taylor(airyai, 0, 5)) + [0.355028, -0.258819, 0.0, 0.0591713, -0.0215683, 0.0] + +The Airy functions satisfy the Wronskian relation +`\operatorname{Ai}(z) \operatorname{Bi}'(z) - +\operatorname{Ai}'(z) \operatorname{Bi}(z) = 1/\pi`:: + + >>> z = -0.5 + >>> airyai(z)*airybi(z,1) - airyai(z,1)*airybi(z) + 0.3183098861837906715377675 + >>> 1/pi + 0.3183098861837906715377675 + +The Airy functions can be expressed in terms of Bessel +functions of order `\pm 1/3`. For `\Re[z] \le 0`, we have:: + + >>> z = -3 + >>> airyai(z) + -0.3788142936776580743472439 + >>> y = 2*power(-z,'3/2')/3 + >>> (sqrt(-z) * (besselj('1/3',y) + besselj('-1/3',y)))/3 + -0.3788142936776580743472439 + +**Derivatives and integrals** + +Derivatives of the Ai-function (directly and using :func:`~mpmath.diff`):: + + >>> airyai(-3,1) + 0.3145837692165988136507873 + >>> diff(airyai,-3) + 0.3145837692165988136507873 + >>> airyai(-3,2) + 1.136442881032974223041732 + >>> diff(airyai,-3,2) + 1.136442881032974223041732 + >>> airyai(1000,1) + -2.943133917910336090459748e-9156 + >>> diff(airyai,1000) + -2.943133917910336090459748e-9156 + +Several derivatives at `z = 0`:: + + >>> airyai(0,0) + 0.3550280538878172392600632 + >>> airyai(0,1) + -0.2588194037928067984051836 + >>> airyai(0,2) + 0.0 + >>> airyai(0,3) + 0.3550280538878172392600632 + >>> airyai(0,4) + -0.5176388075856135968103671 + >>> airyai(0,5) + 0.0 + >>> airyai(0,15) + 1292.30211615165475090663 + >>> airyai(0,16) + -3188.655054727379756351861 + >>> airyai(0,17) + 0.0 + +The integral of the Ai-function:: + + >>> airyai(3,-1) + 0.3299203760070217725002701 + >>> quad(airyai, [0,3]) + 0.3299203760070217725002701 + >>> airyai(-10,-1) + -0.765698403134212917425148 + >>> quad(airyai, [0,-10]) + -0.765698403134212917425148 + +Integrals of high or fractional order:: + + >>> airyai(-2,0.5) + (0.0 + 0.2453596101351438273844725j) + >>> differint(airyai,-2,0.5,0) + (0.0 + 0.2453596101351438273844725j) + >>> airyai(-2,-4) + 0.2939176441636809580339365 + >>> differint(airyai,-2,-4,0) + 0.2939176441636809580339365 + >>> airyai(0,-1) + 0.0 + >>> airyai(0,-2) + 0.0 + >>> airyai(0,-3) + 0.0 + +Integrals of the Ai-function can be evaluated at limit points:: + + >>> airyai(-1000000,-1) + -0.6666843728311539978751512 + >>> airyai(-inf,-1) + -0.6666666666666666666666667 + >>> airyai(10,-1) + 0.3333333332991690159427932 + >>> airyai(+inf,-1) + 0.3333333333333333333333333 + >>> airyai(+inf,-2) + inf + >>> airyai(+inf,-3) + inf + >>> airyai(-1000000,-2) + 666666.4078472650651209742 + >>> airyai(-inf,-2) + inf + >>> airyai(-1000000,-3) + -333333074513.7520264995733 + >>> airyai(-inf,-3) + -inf + +**References** + +1. [DLMF]_ Chapter 9: Airy and Related Functions +2. [WolframFunctions]_ section: Bessel-Type Functions + +""" + +airybi = r""" +Computes the Airy function `\operatorname{Bi}(z)`, which is +the solution of the Airy differential equation `f''(z) - z f(z) = 0` +with initial conditions + +.. math :: + + \operatorname{Bi}(0) = + \frac{1}{3^{1/6}\Gamma\left(\frac{2}{3}\right)} + + \operatorname{Bi}'(0) = + \frac{3^{1/6}}{\Gamma\left(\frac{1}{3}\right)}. + +Like the Ai-function (see :func:`~mpmath.airyai`), the Bi-function +is oscillatory for `z < 0`, but it grows rather than decreases +for `z > 0`. + +Optionally, as for :func:`~mpmath.airyai`, derivatives, integrals +and fractional derivatives can be computed with the *derivative* +parameter. + +The Bi-function has infinitely many zeros along the negative +half-axis, as well as complex zeros, which can all be computed +with :func:`~mpmath.airybizero`. + +**Plots** + +.. literalinclude :: /plots/bi.py +.. image :: /plots/bi.png +.. literalinclude :: /plots/bi_c.py +.. image :: /plots/bi_c.png + +**Basic examples** + +Limits and values include:: + + >>> from mpmath import (mp, airybi, power, gamma, inf, airybizero, findroot, + ... quad, nprint, taylor, mpf, sqrt, besselj, chop, diff, + ... differint, pi, j) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> airybi(0) + 0.6149266274460007351509224 + >>> 1/(power(3,'1/6')*gamma('2/3')) + 0.6149266274460007351509224 + >>> airybi(1) + 1.207423594952871259436379 + >>> airybi(-1) + 0.10399738949694461188869 + >>> airybi(inf) + inf + >>> airybi(-inf) + 0.0 + +Evaluation is supported for large magnitudes of the argument:: + + >>> airybi(-100) + 0.02427388768016013160566747 + >>> airybi(100) + 6.041223996670201399005265e+288 + >>> airybi(50+50j) + (-5.322076267321435669290334e+63 + 1.478450291165243789749427e+65j) + >>> airybi(-50+50j) + (-3.347525544923600321838281e+157 + 1.041242537363167632587245e+158j) + +Huge arguments:: + + >>> airybi(10**10) + 1.369385787943539818688433e+289529654602165 + >>> airybi(-10**10) + 0.001775656141692932747610973 + >>> w = airybi(10**10*(1+j)) + >>> w.real + -6.559955931096196875845858e+186339621747689 + >>> w.imag + -6.822462726981357180929024e+186339621747690 + +The first real root of the Bi-function is:: + + >>> findroot(airybi, -1) + -1.17371322270912792491998 + >>> airybizero(1) + -1.17371322270912792491998 + +**Properties and relations** + +Verifying the Airy differential equation:: + + >>> for z in [-3.4, 0, 2.5, 1+2j]: + ... chop(airybi(z,2) - z*airybi(z)) + ... + 0.0 + 0.0 + 0.0 + 0.0 + +The first few terms of the Taylor series expansion around `z = 0` +(every third term is zero):: + + >>> nprint(taylor(airybi, 0, 5)) + [0.614927, 0.448288, 0.0, 0.102488, 0.0373574, 0.0] + +The Airy functions can be expressed in terms of Bessel +functions of order `\pm 1/3`. For `\Re[z] \le 0`, we have:: + + >>> z = -3 + >>> airybi(z) + -0.1982896263749265432206449 + >>> p = 2*power(-z,'3/2')/3 + >>> sqrt(-mpf(z)/3)*(besselj('-1/3',p) - besselj('1/3',p)) + -0.1982896263749265432206449 + +**Derivatives and integrals** + +Derivatives of the Bi-function (directly and using :func:`~mpmath.diff`):: + + >>> airybi(-3,1) + -0.675611222685258537668032 + >>> diff(airybi,-3) + -0.675611222685258537668032 + >>> airybi(-3,2) + 0.5948688791247796296619346 + >>> diff(airybi,-3,2) + 0.5948688791247796296619346 + >>> airybi(1000,1) + 1.710055114624614989262335e+9156 + >>> diff(airybi,1000) + 1.710055114624614989262335e+9156 + +Several derivatives at `z = 0`:: + + >>> airybi(0,0) + 0.6149266274460007351509224 + >>> airybi(0,1) + 0.4482883573538263579148237 + >>> airybi(0,2) + 0.0 + >>> airybi(0,3) + 0.6149266274460007351509224 + >>> airybi(0,4) + 0.8965767147076527158296474 + >>> airybi(0,5) + 0.0 + >>> airybi(0,15) + 2238.332923903442675949357 + >>> airybi(0,16) + 5522.912562599140729510628 + >>> airybi(0,17) + 0.0 + +The integral of the Bi-function:: + + >>> airybi(3,-1) + 10.06200303130620056316655 + >>> quad(airybi, [0,3]) + 10.06200303130620056316655 + >>> airybi(-10,-1) + -0.01504042480614002045135483 + >>> quad(airybi, [0,-10]) + -0.01504042480614002045135483 + +Integrals of high or fractional order:: + + >>> airybi(-2,0.5) + (0.0 + 0.5019859055341699223453257j) + >>> differint(airybi, -2, 0.5, 0) + (0.0 + 0.5019859055341699223453257j) + >>> airybi(-2,-4) + 0.2809314599922447252139092 + >>> differint(airybi,-2,-4,0) + 0.2809314599922447252139092 + >>> airybi(0,-1) + 0.0 + >>> airybi(0,-2) + 0.0 + >>> airybi(0,-3) + 0.0 + +Integrals of the Bi-function can be evaluated at limit points:: + + >>> airybi(-1000000,-1) + 0.000002191261128063434047966873 + >>> airybi(-inf,-1) + 0.0 + >>> airybi(10,-1) + 147809803.1074067161675853 + >>> airybi(+inf,-1) + inf + >>> airybi(+inf,-2) + inf + >>> airybi(+inf,-3) + inf + >>> airybi(-1000000,-2) + 0.4482883750599908479851085 + >>> airybi(-inf,-2) + 0.4482883573538263579148237 + >>> gamma('2/3')*power(3,'2/3')/(2*pi) + 0.4482883573538263579148237 + >>> airybi(-100000,-3) + -44828.52827206932872493133 + >>> airybi(-inf,-3) + -inf + >>> airybi(-100000,-4) + 2241411040.437759489540248 + >>> airybi(-inf,-4) + inf + +""" + +airyaizero = r""" +Gives the `k`-th zero of the Airy Ai-function, +i.e. the `k`-th number `a_k` ordered by magnitude for which +`\operatorname{Ai}(a_k) = 0`. + +Optionally, with *derivative=1*, the corresponding +zero `a'_k` of the derivative function, i.e. +`\operatorname{Ai}'(a'_k) = 0`, is computed. + +**Examples** + +Some values of `a_k`:: + + >>> from mpmath import mp, airyaizero, airyai, chop + >>> mp.dps = 25 + >>> mp.pretty = True + >>> airyaizero(1) + -2.338107410459767038489197 + >>> airyaizero(2) + -4.087949444130970616636989 + >>> airyaizero(3) + -5.520559828095551059129856 + >>> airyaizero(1000) + -281.0315196125215528353364 + +Some values of `a'_k`:: + + >>> airyaizero(1,1) + -1.018792971647471089017325 + >>> airyaizero(2,1) + -3.248197582179836537875424 + >>> airyaizero(3,1) + -4.820099211178735639400616 + >>> airyaizero(1000,1) + -280.9378080358935070607097 + +Verification:: + + >>> chop(airyai(airyaizero(1))) + 0.0 + >>> chop(airyai(airyaizero(1,1),1)) + 0.0 + +""" + +airybizero = r""" +With *complex=False*, gives the `k`-th real zero of the Airy Bi-function, +i.e. the `k`-th number `b_k` ordered by magnitude for which +`\operatorname{Bi}(b_k) = 0`. + +With *complex=True*, gives the `k`-th complex zero in the upper +half plane `\beta_k`. Also the conjugate `\overline{\beta_k}` +is a zero. + +Optionally, with *derivative=1*, the corresponding +zero `b'_k` or `\beta'_k` of the derivative function, i.e. +`\operatorname{Bi}'(b'_k) = 0` or `\operatorname{Bi}'(\beta'_k) = 0`, +is computed. + +**Examples** + +Some values of `b_k`:: + + >>> from mpmath import mp, airybizero, airybi, chop, conj, pi, arg + >>> mp.dps = 25 + >>> mp.pretty = True + >>> airybizero(1) + -1.17371322270912792491998 + >>> airybizero(2) + -3.271093302836352715680228 + >>> airybizero(3) + -4.830737841662015932667709 + >>> airybizero(1000) + -280.9378112034152401578834 + +Some values of `b_k`:: + + >>> airybizero(1,1) + -2.294439682614123246622459 + >>> airybizero(2,1) + -4.073155089071828215552369 + >>> airybizero(3,1) + -5.512395729663599496259593 + >>> airybizero(1000,1) + -281.0315164471118527161362 + +Some values of `\beta_k`:: + + >>> airybizero(1,complex=True) + (0.9775448867316206859469927 + 2.141290706038744575749139j) + >>> airybizero(2,complex=True) + (1.896775013895336346627217 + 3.627291764358919410440499j) + >>> airybizero(3,complex=True) + (2.633157739354946595708019 + 4.855468179979844983174628j) + >>> airybizero(1000,complex=True) + (140.4978560578493018899793 + 243.3907724215792121244867j) + +Some values of `\beta'_k`:: + + >>> airybizero(1,1,complex=True) + (0.2149470745374305676088329 + 1.100600143302797880647194j) + >>> airybizero(2,1,complex=True) + (1.458168309223507392028211 + 2.912249367458445419235083j) + >>> airybizero(3,1,complex=True) + (2.273760763013482299792362 + 4.254528549217097862167015j) + >>> airybizero(1000,1,complex=True) + (140.4509972835270559730423 + 243.3096175398562811896208j) + +Verification:: + + >>> chop(airybi(airybizero(1))) + 0.0 + >>> chop(airybi(airybizero(1,1),1)) + 0.0 + >>> u = airybizero(1,complex=True) + >>> chop(airybi(u)) + 0.0 + >>> chop(airybi(conj(u))) + 0.0 + +The complex zeros (in the upper and lower half-planes respectively) +asymptotically approach the rays `z = R \exp(\pm i \pi /3)`:: + + >>> arg(airybizero(1,complex=True)) + 1.142532510286334022305364 + >>> arg(airybizero(1000,complex=True)) + 1.047271114786212061583917 + >>> arg(airybizero(1000000,complex=True)) + 1.047197624741816183341355 + >>> pi/3 + 1.047197551196597746154214 + +""" + + +ellipk = r""" +Evaluates the complete elliptic integral of the first kind, +`K(m)`, defined by + +.. math :: + + K(m) = \int_0^{\pi/2} \frac{dt}{\sqrt{1-m \sin^2 t}} \, = \, + \frac{\pi}{2} \,_2F_1\left(\frac{1}{2}, \frac{1}{2}, 1, m\right). + +Note that the argument is the parameter `m = k^2`, +not the modulus `k` which is sometimes used. + +**Plots** + +.. literalinclude :: /plots/ellipk.py +.. image :: /plots/ellipk.png + +**Examples** + +Values and limits include:: + + >>> from mpmath import mp, ellipk, inf, sin, quad, pi, hyp2f1, quad + >>> mp.dps = 25 + >>> mp.pretty = True + >>> ellipk(0) + 1.570796326794896619231322 + >>> ellipk(inf) + (0.0 + 0.0j) + >>> ellipk(-inf) + 0.0 + >>> ellipk(1) + inf + >>> ellipk(-1) + 1.31102877714605990523242 + >>> ellipk(2) + (1.31102877714605990523242 - 1.31102877714605990523242j) + +Verifying the defining integral and hypergeometric +representation:: + + >>> ellipk(0.5) + 1.85407467730137191843385 + >>> quad(lambda t: (1-0.5*sin(t)**2)**-0.5, [0, pi/2]) + 1.85407467730137191843385 + >>> pi/2*hyp2f1(0.5,0.5,1,0.5) + 1.85407467730137191843385 + +Evaluation is supported for arbitrary complex `m`:: + + >>> ellipk(3+4j) + (0.9111955638049650086562171 + 0.6313342832413452438845091j) + +A definite integral:: + + >>> quad(ellipk, [0, 1]) + 2.0 +""" + +agm = r""" +``agm(a, b)`` computes the arithmetic-geometric mean of `a` and +`b`, defined as the limit of the following iteration: + +.. math :: + + a_0 = a + + b_0 = b + + a_{n+1} = \frac{a_n+b_n}{2} + + b_{n+1} = \sqrt{a_n b_n} + +This function can be called with a single argument, computing +`\mathrm{agm}(a,1) = \mathrm{agm}(1,a)`. + +**Examples** + +It is a well-known theorem that the geometric mean of +two distinct positive numbers is less than the arithmetic +mean. It follows that the arithmetic-geometric mean lies +between the two means:: + + >>> from mpmath import (mp, mpf, agm, pi, log, j, quad, cos, sin, + ... gamma, sqrt, e, inf) + >>> mp.pretty = True + >>> a = mpf(3) + >>> b = mpf(4) + >>> sqrt(a*b) + 3.46410161513775 + >>> agm(a,b) + 3.48202767635957 + >>> (a+b)/2 + 3.5 + +The arithmetic-geometric mean is scale-invariant:: + + >>> agm(10*e, 10*pi) + 29.261085515723 + >>> 10*agm(e, pi) + 29.261085515723 + +As an order-of-magnitude estimate, `\mathrm{agm}(1,x) \approx x` +for large `x`:: + + >>> agm(10**10) + 643448704.760133 + >>> agm(10**50) + 1.34814309345871e+48 + +For tiny `x`, `\mathrm{agm}(1,x) \approx -\pi/(2 \log(x/4))`:: + + >>> agm('0.01') + 0.262166887202249 + >>> -pi/2/log('0.0025') + 0.262172347753122 + +The arithmetic-geometric mean can also be computed for complex +numbers:: + + >>> agm(3, 2+j) + (2.51055133276184 + 0.547394054060638j) + +The AGM iteration converges very quickly (each step doubles +the number of correct digits), so :func:`~mpmath.agm` supports efficient +high-precision evaluation:: + + >>> mp.dps = 10000 + >>> a = agm(1,2) + >>> str(a)[-10:] + '1679581912' + +**Mathematical relations** + +The arithmetic-geometric mean may be used to evaluate the +following two parametric definite integrals: + +.. math :: + + I_1 = \int_0^{\infty} + \frac{1}{\sqrt{(x^2+a^2)(x^2+b^2)}} \,dx + + I_2 = \int_0^{\pi/2} + \frac{1}{\sqrt{a^2 \cos^2(x) + b^2 \sin^2(x)}} \,dx + +We have:: + + >>> mp.dps = 15 + >>> a = 3 + >>> b = 4 + >>> f1 = lambda x: ((x**2+a**2)*(x**2+b**2))**-0.5 + >>> f2 = lambda x: ((a*cos(x))**2 + (b*sin(x))**2)**-0.5 + >>> quad(f1, [0, inf]) + 0.451115405388492 + >>> quad(f2, [0, pi/2]) + 0.451115405388492 + >>> pi/(2*agm(a,b)) + 0.451115405388492 + +A formula for `\Gamma(1/4)`:: + + >>> gamma(0.25) + 3.62560990822191 + >>> sqrt(2*sqrt(2*pi**3)/agm(1,sqrt(2))) + 3.62560990822191 + +**Possible issues** + +The branch cut chosen for complex `a` and `b` is somewhat +arbitrary. + +""" + +gegenbauer = r""" +Evaluates the Gegenbauer function, a generalization of Gegenbauer (or +ultraspherical) polynomials. + +.. math :: + + C_n^{(a)}(z) = \frac{\Gamma\left(n+2a\right)} + {\Gamma\left(2a\right)\Gamma\left(n+1\right)} + \,_2F_1\left(-n, n+2a;a+\frac{1}{2}; \frac{1}{2}(1-z)\right). + +When `n` is a nonnegative integer, this formula gives a polynomial +in `z` of degree `n`, but all parameters are permitted to be +complex numbers. With `a = 1/2`, the Gegenbauer polynomial +reduces to a Legendre polynomial. + +**Examples** + +Evaluation for arbitrary arguments:: + + >>> from mpmath import mp, gegenbauer, chop, diff, taylor, quad + >>> mp.dps = 25 + >>> mp.pretty = True + >>> gegenbauer(3, 0.5, -10) + -2485.0 + >>> gegenbauer(1000, 10, 100) + 3.012757178975667428359374e+2322 + >>> gegenbauer(2+3j, -0.75, -1000j) + (-5038991.358609026523401901 + 9414549.285447104177860806j) + +Evaluation at negative integer orders:: + + >>> gegenbauer(-4, 2, 1.75) + -1.0 + >>> gegenbauer(-4, 3, 1.75) + 0.0 + >>> gegenbauer(-4, 2j, 1.75) + 0.0 + >>> gegenbauer(-7, 0.5, 3) + 8989.0 + +The Gegenbauer polynomials solve the differential equation:: + + >>> n, a = 4.5, 1+2j + >>> f = lambda z: gegenbauer(n, a, z) + >>> for z in [0, 0.75, -0.5j]: + ... chop((1-z**2)*diff(f,z,2) - (2*a+1)*z*diff(f,z) + n*(n+2*a)*f(z)) + ... + 0.0 + 0.0 + 0.0 + +The Gegenbauer polynomials have generating function +`(1-2zt+t^2)^{-a}`:: + + >>> a, z = 2.5, 1 + >>> taylor(lambda t: (1-2*z*t+t**2)**(-a), 0, 3) + [1.0, 5.0, 15.0, 35.0] + >>> [gegenbauer(n,a,z) for n in range(4)] + [1.0, 5.0, 15.0, 35.0] + +The Gegenbauer polynomials are orthogonal on `[-1, 1]` with respect +to the weight `(1-z^2)^{a-\frac{1}{2}}`:: + + >>> a, n, m = 2.5, 4, 5 + >>> Cn = lambda z: gegenbauer(n, a, z, zeroprec=1000) + >>> Cm = lambda z: gegenbauer(m, a, z, zeroprec=1000) + >>> chop(quad(lambda z: Cn(z)*Cm(z)*(1-z**2)*(a-0.5), [-1, 1])) + 0.0 +""" + +laguerre = r""" +Gives the generalized (associated) Laguerre polynomial, defined by + +.. math :: + + L_n^a(z) = \frac{\Gamma(n+b+1)}{\Gamma(b+1) \Gamma(n+1)} + \,_1F_1(-n, a+1, z). + +With `a = 0` and `n` a nonnegative integer, this reduces to an ordinary +Laguerre polynomial, the sequence of which begins +`L_0(z) = 1, L_1(z) = 1-z, L_2(z) = z^2-2z+1, \ldots`. + +The Laguerre polynomials are orthogonal with respect to the weight +`z^a e^{-z}` on `[0, \infty)`. + +**Plots** + +.. literalinclude :: /plots/laguerre.py +.. image :: /plots/laguerre.png + +**Examples** + +Evaluation for arbitrary arguments:: + + >>> from mpmath import mp, laguerre, j, chop, fac, taylor, quad, exp, inf + >>> mp.dps = 25 + >>> mp.pretty = True + >>> laguerre(5, 0, 0.25) + 0.03726399739583333333333333 + >>> laguerre(1+j, 0.5, 2+3j) + (4.474921610704496808379097 - 11.02058050372068958069241j) + >>> laguerre(2, 0, 10000) + 49980001.0 + >>> laguerre(2.5, 0, 10000) + -9.327764910194842158583189e+4328 + +The first few Laguerre polynomials, normalized to have integer +coefficients:: + + >>> for n in range(7): + ... chop(taylor(lambda z: fac(n)*laguerre(n, 0, z), 0, n)) + ... + [1.0] + [1.0, -1.0] + [2.0, -4.0, 1.0] + [6.0, -18.0, 9.0, -1.0] + [24.0, -96.0, 72.0, -16.0, 1.0] + [120.0, -600.0, 600.0, -200.0, 25.0, -1.0] + [720.0, -4320.0, 5400.0, -2400.0, 450.0, -36.0, 1.0] + +Verifying orthogonality:: + + >>> Lm = lambda t: laguerre(m,a,t) + >>> Ln = lambda t: laguerre(n,a,t) + >>> a, n, m = 2.5, 2, 3 + >>> chop(quad(lambda t: exp(-t)*t**a*Lm(t)*Ln(t), [0,inf])) + 0.0 + + +""" + +hermite = r""" +Evaluates the Hermite polynomial `H_n(z)`, which may be defined using +the recurrence + +.. math :: + + H_0(z) = 1 + + H_1(z) = 2z + + H_{n+1} = 2z H_n(z) - 2n H_{n-1}(z). + +The Hermite polynomials are orthogonal on `(-\infty, \infty)` with +respect to the weight `e^{-z^2}`. More generally, allowing arbitrary complex +values of `n`, the Hermite function `H_n(z)` is defined as + +.. math :: + + H_n(z) = (2z)^n \,_2F_0\left(-\frac{n}{2}, \frac{1-n}{2}, + -\frac{1}{z^2}\right) + +for `\Re{z} > 0`, or generally + +.. math :: + + H_n(z) = 2^n \sqrt{\pi} \left( + \frac{1}{\Gamma\left(\frac{1-n}{2}\right)} + \,_1F_1\left(-\frac{n}{2}, \frac{1}{2}, z^2\right) - + \frac{2z}{\Gamma\left(-\frac{n}{2}\right)} + \,_1F_1\left(\frac{1-n}{2}, \frac{3}{2}, z^2\right) + \right). + +**Plots** + +.. literalinclude :: /plots/hermite.py +.. image :: /plots/hermite.png + +**Examples** + +Evaluation for arbitrary arguments:: + + >>> from mpmath import mp, hermite, chop, taylor, chop, diff, exp, inf, quad + >>> mp.dps = 25 + >>> mp.pretty = True + >>> hermite(0, 10) + 1.0 + >>> hermite(1, 10) + 20.0 + >>> hermite(2, 10) + 398.0 + >>> hermite(10000, 2) + 4.950440066552087387515653e+19334 + >>> hermite(3, -10**8) + -7999999999999998800000000.0 + >>> hermite(-3, -10**8) + 1.675159751729877682920301e+4342944819032534 + >>> hermite(2+3j, -1+2j) + (-0.07652130602993513389421901 - 0.1084662449961914580276007j) + +Coefficients of the first few Hermite polynomials are:: + + >>> for n in range(7): + ... chop(taylor(lambda z: hermite(n, z), 0, n)) + ... + [1.0] + [0.0, 2.0] + [-2.0, 0.0, 4.0] + [0.0, -12.0, 0.0, 8.0] + [12.0, 0.0, -48.0, 0.0, 16.0] + [0.0, 120.0, 0.0, -160.0, 0.0, 32.0] + [-120.0, 0.0, 720.0, 0.0, -480.0, 0.0, 64.0] + +Values at `z = 0`:: + + >>> for n in range(-5, 9): + ... hermite(n, 0) + ... + 0.02769459142039868792653387 + 0.08333333333333333333333333 + 0.2215567313631895034122709 + 0.5 + 0.8862269254527580136490837 + 1.0 + 0.0 + -2.0 + 0.0 + 12.0 + 0.0 + -120.0 + 0.0 + 1680.0 + +Hermite functions satisfy the differential equation:: + + >>> n = 4 + >>> f = lambda z: hermite(n, z) + >>> z = 1.5 + >>> chop(diff(f,z,2) - 2*z*diff(f,z) + 2*n*f(z)) + 0.0 + +Verifying orthogonality:: + + >>> chop(quad(lambda t: hermite(2,t)*hermite(4,t)*exp(-t**2), [-inf,inf])) + 0.0 + +""" + +jacobi = r""" +``jacobi(n, a, b, x)`` evaluates the Jacobi polynomial +`P_n^{(a,b)}(x)`. The Jacobi polynomials are a special +case of the hypergeometric function `\,_2F_1` given by: + +.. math :: + + P_n^{(a,b)}(x) = {n+a \choose n} + \,_2F_1\left(-n,1+a+b+n,a+1,\frac{1-x}{2}\right). + +Note that this definition generalizes to nonintegral values +of `n`. When `n` is an integer, the hypergeometric series +terminates after a finite number of terms, giving +a polynomial in `x`. + +**Evaluation of Jacobi polynomials** + +A special evaluation is `P_n^{(a,b)}(1) = {n+a \choose n}`:: + + >>> from mpmath import (mp, jacobi, binomial, nprint, taylor, chop, + ... quad, diff, pi) + >>> mp.pretty = True + >>> jacobi(4, 0.5, 0.25, 1) + 2.4609375 + >>> binomial(4+0.5, 4) + 2.4609375 + +A Jacobi polynomial of degree `n` is equal to its +Taylor polynomial of degree `n`. The explicit +coefficients of Jacobi polynomials can therefore +be recovered easily using :func:`~mpmath.taylor`:: + + >>> for n in range(5): + ... nprint(taylor(lambda x: jacobi(n,1,2,x), 0, n)) + ... + [1.0] + [-0.5, 2.5] + [-0.75, -1.5, 5.25] + [0.5, -3.5, -3.5, 10.5] + [0.625, 2.5, -11.25, -7.5, 20.625] + +For nonintegral `n`, the Jacobi "polynomial" is no longer +a polynomial:: + + >>> nprint(taylor(lambda x: jacobi(0.5,1,2,x), 0, 4)) + [0.309983, 1.84119, -1.26933, 1.26699, -1.34808] + +**Orthogonality** + +The Jacobi polynomials are orthogonal on the interval +`[-1, 1]` with respect to the weight function +`w(x) = (1-x)^a (1+x)^b`. That is, +`w(x) P_n^{(a,b)}(x) P_m^{(a,b)}(x)` integrates to +zero if `m \ne n` and to a nonzero number if `m = n`. + +The orthogonality is easy to verify using numerical +quadrature:: + + >>> P = jacobi + >>> f = lambda x: (1-x)**a * (1+x)**b * P(m,a,b,x) * P(n,a,b,x) + >>> a = 2 + >>> b = 3 + >>> m, n = 3, 4 + >>> chop(quad(f, [-1, 1]), 1) + 0.0 + >>> m, n = 4, 4 + >>> quad(f, [-1, 1]) + 1.9047619047619 + +**Differential equation** + +The Jacobi polynomials are solutions of the differential +equation + +.. math :: + + (1-x^2) y'' + (b-a-(a+b+2)x) y' + n (n+a+b+1) y = 0. + +We can verify that :func:`~mpmath.jacobi` approximately satisfies +this equation:: + + >>> from mpmath import mp, jacobi, diff, nprint, pi + >>> mp.dps = 15 + >>> a = 2.5 + >>> b = 4 + >>> n = 3 + >>> y = lambda x: jacobi(n,a,b,x) + >>> x = pi + >>> A0 = n*(n+a+b+1)*y(x) + >>> A1 = (b-a-(a+b+2)*x)*diff(y,x) + >>> A2 = (1-x**2)*diff(y,x,2) + >>> nprint(A2 + A1 + A0, 1) + 4.0e-12 + +The difference of order `10^{-12}` is as close to zero as +it could be at 15-digit working precision, since the terms +are large:: + + >>> A0, A1, A2 + (26560.2328981879, -21503.7641037294, -5056.46879445852) + +""" + +legendre = r""" +``legendre(n, x)`` evaluates the Legendre polynomial `P_n(x)`. +The Legendre polynomials are given by the formula + +.. math :: + + P_n(x) = \frac{1}{2^n n!} \frac{d^n}{dx^n} (x^2 -1)^n. + +Alternatively, they can be computed recursively using + +.. math :: + + P_0(x) = 1 + + P_1(x) = x + + (n+1) P_{n+1}(x) = (2n+1) x P_n(x) - n P_{n-1}(x). + +A third definition is in terms of the hypergeometric function +`\,_2F_1`, whereby they can be generalized to arbitrary `n`: + +.. math :: + + P_n(x) = \,_2F_1\left(-n, n+1, 1, \frac{1-x}{2}\right) + +**Plots** + +.. literalinclude :: /plots/legendre.py +.. image :: /plots/legendre.png + +**Basic evaluation** + +The Legendre polynomials assume fixed values at the points +`x = -1` and `x = 1`:: + + >>> from mpmath import (mp, legendre, nprint, chop, taylor, polyroots, + ... quad, diff) + >>> mp.pretty = True + >>> nprint([legendre(n, 1) for n in range(6)]) + [1.0, 1.0, 1.0, 1.0, 1.0, 1.0] + >>> nprint([legendre(n, -1) for n in range(6)]) + [1.0, -1.0, 1.0, -1.0, 1.0, -1.0] + +The coefficients of Legendre polynomials can be recovered +using degree-`n` Taylor expansion:: + + >>> for n in range(5): + ... nprint(chop(taylor(lambda x: legendre(n, x), 0, n))) + ... + [1.0] + [0.0, 1.0] + [-0.5, 0.0, 1.5] + [0.0, -1.5, 0.0, 2.5] + [0.375, 0.0, -3.75, 0.0, 4.375] + +The roots of Legendre polynomials are located symmetrically +on the interval `[-1, 1]`:: + + >>> for n in range(5): + ... nprint(polyroots(taylor(lambda x: legendre(n, x), 0, n))) + ... + [] + [0.0] + [-0.57735, 0.57735] + [-0.774597, 0.0, 0.774597] + [-0.861136, -0.339981, 0.339981, 0.861136] + +An example of an evaluation for arbitrary `n`:: + + >>> legendre(0.75, 2+4j) + (1.94952805264875 + 2.1071073099422j) + +**Orthogonality** + +The Legendre polynomials are orthogonal on `[-1, 1]` with respect +to the trivial weight `w(x) = 1`. That is, `P_m(x) P_n(x)` +integrates to zero if `m \ne n` and to `2/(2n+1)` if `m = n`:: + + >>> m, n = 3, 4 + >>> quad(lambda x: legendre(m,x)*legendre(n,x), [-1, 1]) + 0.0 + >>> m, n = 4, 4 + >>> quad(lambda x: legendre(m,x)*legendre(n,x), [-1, 1]) + 0.222222222222222 + +**Differential equation** + +The Legendre polynomials satisfy the differential equation + +.. math :: + + ((1-x^2) y')' + n(n+1) y' = 0. + +We can verify this numerically:: + + >>> n = 3.6 + >>> x = 0.73 + >>> P = legendre + >>> A = diff(lambda t: (1-t**2)*diff(lambda u: P(n,u), t), x) + >>> B = n*(n+1)*P(n,x) + >>> nprint(A+B,1) + 9.0e-16 + +""" + + +legenp = r""" +Calculates the (associated) Legendre function of the first kind of +degree *n* and order *m*, `P_n^m(z)`. Taking `m = 0` gives the ordinary +Legendre function of the first kind, `P_n(z)`. The parameters may be +complex numbers. + +In terms of the Gauss hypergeometric function, the (associated) Legendre +function is defined as + +.. math :: + + P_n^m(z) = \frac{1}{\Gamma(1-m)} \frac{(1+z)^{m/2}}{(1-z)^{m/2}} + \,_2F_1\left(-n, n+1, 1-m, \frac{1-z}{2}\right). + +With *type=3* instead of *type=2*, the alternative +definition + +.. math :: + + \hat{P}_n^m(z) = \frac{1}{\Gamma(1-m)} \frac{(z+1)^{m/2}}{(z-1)^{m/2}} + \,_2F_1\left(-n, n+1, 1-m, \frac{1-z}{2}\right). + +is used. These functions correspond respectively to ``LegendreP[n,m,2,z]`` +and ``LegendreP[n,m,3,z]`` in Mathematica. + +The general solution of the (associated) Legendre differential equation + +.. math :: + + (1-z^2) f''(z) - 2zf'(z) + \left(n(n+1)-\frac{m^2}{1-z^2}\right)f(z) = 0 + +is given by `C_1 P_n^m(z) + C_2 Q_n^m(z)` for arbitrary constants +`C_1`, `C_2`, where `Q_n^m(z)` is a Legendre function of the +second kind as implemented by :func:`~mpmath.legenq`. + +**Examples** + +Evaluation for arbitrary parameters and arguments:: + + >>> from mpmath import (mp, legenp, legendre, chop, legenq, diff, + ... mpmathify, j) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> legenp(2, 0, 10) + 149.5 + >>> legendre(2, 10) + 149.5 + >>> legenp(-2, 0.5, 2.5) + (1.972260393822275434196053 - 1.972260393822275434196053j) + >>> legenp(2+3j, 1-j, -0.5+4j) + (-3.335677248386698208736542 - 5.663270217461022307645625j) + >>> chop(legenp(3, 2, -1.5, type=2)) + 28.125 + >>> chop(legenp(3, 2, -1.5, type=3)) + -28.125 + +Verifying the associated Legendre differential equation:: + + >>> n, m = 2, -0.5 + >>> C1, C2 = 1, -3 + >>> f = lambda z: C1*legenp(n,m,z) + C2*legenq(n,m,z) + >>> deq = lambda z: (1-z**2)*diff(f,z,2) - 2*z*diff(f,z) + \ + ... (n*(n+1)-m**2/(1-z**2))*f(z) + >>> for z in [0, 2, -1.5, 0.5+2j]: + ... chop(deq(mpmathify(z))) + ... + 0.0 + 0.0 + 0.0 + 0.0 +""" + +legenq = r""" +Calculates the (associated) Legendre function of the second kind of +degree *n* and order *m*, `Q_n^m(z)`. Taking `m = 0` gives the ordinary +Legendre function of the second kind, `Q_n(z)`. The parameters may be +complex numbers. + +The Legendre functions of the second kind give a second set of +solutions to the (associated) Legendre differential equation. +(See :func:`~mpmath.legenp`.) +Unlike the Legendre functions of the first kind, they are not +polynomials of `z` for integer `n`, `m` but rational or logarithmic +functions with poles at `z = \pm 1`. + +There are various ways to define Legendre functions of +the second kind, giving rise to different complex structure. +A version can be selected using the *type* keyword argument. +The *type=2* and *type=3* functions are given respectively by + +.. math :: + + Q_n^m(z) = \frac{\pi}{2 \sin(\pi m)} + \left( \cos(\pi m) P_n^m(z) - + \frac{\Gamma(1+m+n)}{\Gamma(1-m+n)} P_n^{-m}(z)\right) + + \hat{Q}_n^m(z) = \frac{\pi}{2 \sin(\pi m)} e^{\pi i m} + \left( \hat{P}_n^m(z) - + \frac{\Gamma(1+m+n)}{\Gamma(1-m+n)} \hat{P}_n^{-m}(z)\right) + +where `P` and `\hat{P}` are the *type=2* and *type=3* Legendre functions +of the first kind. The formulas above should be understood as limits +when `m` is an integer. + +These functions correspond to ``LegendreQ[n,m,2,z]`` (or ``LegendreQ[n,m,z]``) +and ``LegendreQ[n,m,3,z]`` in Mathematica. The *type=3* function +is essentially the same as the function defined in +Abramowitz & Stegun (eq. 8.1.3) but with `(z+1)^{m/2}(z-1)^{m/2}` instead +of `(z^2-1)^{m/2}`, giving slightly different branches. + +**Examples** + +Evaluation for arbitrary parameters and arguments:: + + >>> from mpmath import mp, legenq, j, chop + >>> mp.dps = 25 + >>> mp.pretty = True + >>> legenq(2, 0, 0.5) + -0.8186632680417568557122028 + >>> legenq(-1.5, -2, 2.5) + (0.6655964618250228714288277 + 0.3937692045497259717762649j) + >>> legenq(2-j, 3+4j, -6+5j) + (-10001.95256487468541686564 - 6011.691337610097577791134j) + +Different versions of the function:: + + >>> legenq(2, 1, 0.5) + 0.7298060598018049369381857 + >>> legenq(2, 1, 1.5) + (-7.902916572420817192300921 + 0.1998650072605976600724502j) + >>> legenq(2, 1, 0.5, type=3) + (2.040524284763495081918338 - 0.7298060598018049369381857j) + >>> chop(legenq(2, 1, 1.5, type=3)) + -0.1998650072605976600724502 + +""" + +chebyt = r""" +``chebyt(n, x)`` evaluates the Chebyshev polynomial of the first +kind `T_n(x)`, defined by the identity + +.. math :: + + T_n(\cos x) = \cos(n x). + +The Chebyshev polynomials of the first kind are a special +case of the Jacobi polynomials, and by extension of the +hypergeometric function `\,_2F_1`. They can thus also be +evaluated for nonintegral `n`. + +**Plots** + +.. literalinclude :: /plots/chebyt.py +.. image :: /plots/chebyt.png + +**Basic evaluation** + +The coefficients of the `n`-th polynomial can be recovered +using using degree-`n` Taylor expansion:: + + >>> from mpmath import mp, nprint, chop, taylor, chebyt, quad, sqrt + >>> mp.pretty = True + >>> for n in range(5): + ... nprint(chop(taylor(lambda x: chebyt(n, x), 0, n))) + ... + [1.0] + [0.0, 1.0] + [-1.0, 0.0, 2.0] + [0.0, -3.0, 0.0, 4.0] + [1.0, 0.0, -8.0, 0.0, 8.0] + +**Orthogonality** + +The Chebyshev polynomials of the first kind are orthogonal +on the interval `[-1, 1]` with respect to the weight +function `w(x) = 1/\sqrt{1-x^2}`:: + + >>> f = lambda x: chebyt(m,x)*chebyt(n,x)/sqrt(1-x**2) + >>> m, n = 3, 4 + >>> nprint(quad(f, [-1, 1]),1) + 0.0 + >>> m, n = 4, 4 + >>> quad(f, [-1, 1]) + 1.57079632596448 + +""" + +chebyu = r""" +``chebyu(n, x)`` evaluates the Chebyshev polynomial of the second +kind `U_n(x)`, defined by the identity + +.. math :: + + U_n(\cos x) = \frac{\sin((n+1)x)}{\sin(x)}. + +The Chebyshev polynomials of the second kind are a special +case of the Jacobi polynomials, and by extension of the +hypergeometric function `\,_2F_1`. They can thus also be +evaluated for nonintegral `n`. + +**Plots** + +.. literalinclude :: /plots/chebyu.py +.. image :: /plots/chebyu.png + +**Basic evaluation** + +The coefficients of the `n`-th polynomial can be recovered +using using degree-`n` Taylor expansion:: + + >>> from mpmath import mp, nprint, taylor, chop, chebyu, sqrt, quad + >>> mp.pretty = True + >>> for n in range(5): + ... nprint(chop(taylor(lambda x: chebyu(n, x), 0, n))) + ... + [1.0] + [0.0, 2.0] + [-1.0, 0.0, 4.0] + [0.0, -4.0, 0.0, 8.0] + [1.0, 0.0, -12.0, 0.0, 16.0] + +**Orthogonality** + +The Chebyshev polynomials of the second kind are orthogonal +on the interval `[-1, 1]` with respect to the weight +function `w(x) = \sqrt{1-x^2}`:: + + >>> f = lambda x: chebyu(m,x)*chebyu(n,x)*sqrt(1-x**2) + >>> m, n = 3, 4 + >>> quad(f, [-1, 1]) + 0.0 + >>> m, n = 4, 4 + >>> quad(f, [-1, 1]) + 1.5707963267949 +""" + +besselj = r""" +``besselj(n, x, derivative=0)`` gives the Bessel function of the first kind +`J_n(x)`. Bessel functions of the first kind are defined as +solutions of the differential equation + +.. math :: + + x^2 y'' + x y' + (x^2 - n^2) y = 0 + +which appears, among other things, when solving the radial +part of Laplace's equation in cylindrical coordinates. This +equation has two solutions for given `n`, where the +`J_n`-function is the solution that is nonsingular at `x = 0`. +For positive integer `n`, `J_n(x)` behaves roughly like a sine +(odd `n`) or cosine (even `n`) multiplied by a magnitude factor +that decays slowly as `x \to \pm\infty`. + +Generally, `J_n` is a special case of the hypergeometric +function `\,_0F_1`: + +.. math :: + + J_n(x) = \frac{x^n}{2^n \Gamma(n+1)} + \,_0F_1\left(n+1,-\frac{x^2}{4}\right) + +With *derivative* = `m \ne 0`, the `m`-th derivative + +.. math :: + + \frac{d^m}{dx^m} J_n(x) + +is computed. + +**Plots** + +.. literalinclude :: /plots/besselj.py +.. image :: /plots/besselj.png +.. literalinclude :: /plots/besselj_c.py +.. image :: /plots/besselj_c.png + +**Examples** + +Evaluation is supported for arbitrary arguments, and at +arbitrary precision:: + + >>> from mpmath import (mp, besselj, pi, nprint, findroot, quadosc, sqrt, + ... pi, sin, cos, diff, j0, j1, quad, differint, j, inf) + >>> mp.pretty = True + >>> besselj(2, 1000) + -0.024777229528606 + >>> besselj(4, 0.75) + 0.000801070086542314 + >>> besselj(2, 1000j) + (-2.48071721019185e+432 + 6.41567059811949e-437j) + >>> mp.dps = 25 + >>> besselj(0.75j, 3+4j) + (-2.778118364828153309919653 - 1.5863603889018621585533j) + >>> mp.dps = 50 + >>> besselj(1, pi) + 0.28461534317975275734531059968613140570981118184947 + +Arguments may be large:: + + >>> mp.dps = 25 + >>> besselj(0, 10000) + -0.007096160353388801477265164 + >>> besselj(0, 10**10) + 0.000002175591750246891726859055 + >>> besselj(2, 10**100) + 7.337048736538615712436929e-51 + >>> besselj(2, 10**5*j) + (-3.540725411970948860173735e+43426 + 4.4949812409615803110051e-43433j) + +The Bessel functions of the first kind satisfy simple +symmetries around `x = 0`:: + + >>> mp.dps = 15 + >>> nprint([besselj(n,0) for n in range(5)]) + [1.0, 0.0, 0.0, 0.0, 0.0] + >>> nprint([besselj(n,pi) for n in range(5)]) + [-0.304242, 0.284615, 0.485434, 0.333458, 0.151425] + >>> nprint([besselj(n,-pi) for n in range(5)]) + [-0.304242, -0.284615, 0.485434, -0.333458, 0.151425] + +Roots of Bessel functions are often used:: + + >>> nprint([findroot(j0, k) for k in [2, 5, 8, 11, 14]]) + [2.40483, 5.52008, 8.65373, 11.7915, 14.9309] + >>> nprint([findroot(j1, k) for k in [3, 7, 10, 13, 16]]) + [3.83171, 7.01559, 10.1735, 13.3237, 16.4706] + +The roots are not periodic, but the distance between successive +roots asymptotically approaches `2 \pi`. Bessel functions of +the first kind have the following normalization:: + + >>> quadosc(j0, [0, inf], period=2*pi) + 1.0 + >>> quadosc(j1, [0, inf], period=2*pi) + 1.0 + +For `n = 1/2` or `n = -1/2`, the Bessel function reduces to a +trigonometric function:: + + >>> x = 10 + >>> besselj(0.5, x), sqrt(2/(pi*x))*sin(x) + (-0.13726373575505, -0.13726373575505) + >>> besselj(-0.5, x), sqrt(2/(pi*x))*cos(x) + (-0.211708866331398, -0.211708866331398) + +Derivatives of any order can be computed (negative orders +correspond to integration):: + + >>> mp.dps = 25 + >>> besselj(0, 7.5, 1) + -0.1352484275797055051822405 + >>> diff(lambda x: besselj(0,x), 7.5) + -0.1352484275797055051822405 + >>> besselj(0, 7.5, 10) + -0.1377811164763244890135677 + >>> diff(lambda x: besselj(0,x), 7.5, 10) + -0.1377811164763244890135677 + >>> besselj(0,7.5,-1) - besselj(0,3.5,-1) + -0.1241343240399987693521378 + >>> quad(j0, [3.5, 7.5]) + -0.1241343240399987693521378 + +Differentiation with a noninteger order gives the fractional derivative +in the sense of the Riemann-Liouville differintegral, as computed by +:func:`~mpmath.differint`:: + + >>> mp.dps = 15 + >>> besselj(1, 3.5, 0.75) + -0.385977722939384 + >>> differint(lambda x: besselj(1, x), 3.5, 0.75) + -0.385977722939384 + +""" + +besseli = r""" +``besseli(n, x, derivative=0)`` gives the modified Bessel function of the +first kind, + +.. math :: + + I_n(x) = i^{-n} J_n(ix). + +With *derivative* = `m \ne 0`, the `m`-th derivative + +.. math :: + + \frac{d^m}{dx^m} I_n(x) + +is computed. + +**Plots** + +.. literalinclude :: /plots/besseli.py +.. image :: /plots/besseli.png +.. literalinclude :: /plots/besseli_c.py +.. image :: /plots/besseli_c.png + +**Examples** + +Some values of `I_n(x)`:: + + >>> from mpmath import mp, besseli, exp, cos, pi, diff, quad + >>> mp.dps = 25 + >>> mp.pretty = True + >>> besseli(0,0) + 1.0 + >>> besseli(1,0) + 0.0 + >>> besseli(0,1) + 1.266065877752008335598245 + >>> besseli(3.5, 2+3j) + (-0.2904369752642538144289025 - 0.4469098397654815837307006j) + +Arguments may be large:: + + >>> besseli(2, 1000) + 2.480717210191852440616782e+432 + >>> besseli(2, 10**10) + 4.299602851624027900335391e+4342944813 + >>> besseli(2, 6000+10000j) + (-2.114650753239580827144204e+2603 + 4.385040221241629041351886e+2602j) + +For integers `n`, the following integral representation holds:: + + >>> mp.dps = 15 + >>> n = 3 + >>> x = 2.3 + >>> quad(lambda t: exp(x*cos(t))*cos(n*t), [0,pi])/pi + 0.349223221159309 + >>> besseli(n,x) + 0.349223221159309 + +Derivatives and antiderivatives of any order can be computed:: + + >>> mp.dps = 25 + >>> besseli(2, 7.5, 1) + 195.8229038931399062565883 + >>> diff(lambda x: besseli(2,x), 7.5) + 195.8229038931399062565883 + >>> besseli(2, 7.5, 10) + 153.3296508971734525525176 + >>> diff(lambda x: besseli(2,x), 7.5, 10) + 153.3296508971734525525176 + >>> besseli(2,7.5,-1) - besseli(2,3.5,-1) + 202.5043900051930141956876 + >>> quad(lambda x: besseli(2,x), [3.5, 7.5]) + 202.5043900051930141956876 + +""" + +bessely = r""" +``bessely(n, x, derivative=0)`` gives the Bessel function of the second kind, + +.. math :: + + Y_n(x) = \frac{J_n(x) \cos(\pi n) - J_{-n}(x)}{\sin(\pi n)}. + +For `n` an integer, this formula should be understood as a +limit. With *derivative* = `m \ne 0`, the `m`-th derivative + +.. math :: + + \frac{d^m}{dx^m} Y_n(x) + +is computed. + +**Plots** + +.. literalinclude :: /plots/bessely.py +.. image :: /plots/bessely.png +.. literalinclude :: /plots/bessely_c.py +.. image :: /plots/bessely_c.png + +**Examples** + +Some values of `Y_n(x)`:: + + >>> from mpmath import mp, bessely, pi, diff, quad + >>> mp.dps = 25 + >>> mp.pretty = True + >>> bessely(0,0), bessely(1,0), bessely(2,0) + (-inf, -inf, -inf) + >>> bessely(1, pi) + 0.3588729167767189594679827 + >>> bessely(0.5, 3+4j) + (9.242861436961450520325216 - 3.085042824915332562522402j) + +Arguments may be large:: + + >>> bessely(0, 10000) + 0.00364780555898660588668872 + >>> bessely(2.5, 10**50) + -4.8952500412050989295774e-26 + >>> bessely(2.5, -10**50) + (0.0 + 4.8952500412050989295774e-26j) + +Derivatives and antiderivatives of any order can be computed:: + + >>> bessely(2, 3.5, 1) + 0.3842618820422660066089231 + >>> diff(lambda x: bessely(2, x), 3.5) + 0.3842618820422660066089231 + >>> bessely(0.5, 3.5, 1) + -0.2066598304156764337900417 + >>> diff(lambda x: bessely(0.5, x), 3.5) + -0.2066598304156764337900417 + >>> diff(lambda x: bessely(2, x), 0.5, 10) + -208173867409.5547350101511 + >>> bessely(2, 0.5, 10) + -208173867409.5547350101511 + >>> bessely(2, 100.5, 100) + 0.02668487547301372334849043 + >>> quad(lambda x: bessely(2,x), [1,3]) + -1.377046859093181969213262 + >>> bessely(2,3,-1) - bessely(2,1,-1) + -1.377046859093181969213262 + +""" + +besselk = r""" +``besselk(n, x)`` gives the modified Bessel function of the +second kind, + +.. math :: + + K_n(x) = \frac{\pi}{2} \frac{I_{-n}(x)-I_{n}(x)}{\sin(\pi n)} + +For `n` an integer, this formula should be understood as a +limit. + +**Plots** + +.. literalinclude :: /plots/besselk.py +.. image :: /plots/besselk.png +.. literalinclude :: /plots/besselk_c.py +.. image :: /plots/besselk_c.png + +**Examples** + +Evaluation is supported for arbitrary complex arguments:: + + >>> from mpmath import mp, besselk, j, fmul + >>> mp.dps = 25 + >>> mp.pretty = True + >>> besselk(0,1) + 0.4210244382407083333356274 + >>> besselk(0, -1) + (0.4210244382407083333356274 - 3.97746326050642263725661j) + >>> besselk(3.5, 2+3j) + (-0.02090732889633760668464128 + 0.2464022641351420167819697j) + >>> besselk(2+3j, 0.5) + (0.9615816021726349402626083 + 0.1918250181801757416908224j) + +Arguments may be large:: + + >>> besselk(0, 100) + 4.656628229175902018939005e-45 + >>> besselk(1, 10**6) + 4.131967049321725588398296e-434298 + >>> besselk(1, 10**6*j) + (0.001140348428252385844876706 - 0.0005200017201681152909000961j) + >>> besselk(4.5, fmul(10**50, j, exact=True)) + (1.561034538142413947789221e-26 + 1.243554598118700063281496e-25j) + +The point `x = 0` is a singularity (logarithmic if `n = 0`):: + + >>> besselk(0,0) + inf + >>> besselk(1,0) + inf + >>> for n in range(-4, 5): + ... print(besselk(n, '1e-1000')) + ... + 4.8e+4001 + 8.0e+3000 + 2.0e+2000 + 1.0e+1000 + 2302.701024509704096466802 + 1.0e+1000 + 2.0e+2000 + 8.0e+3000 + 4.8e+4001 + +""" + +hankel1 = r""" +``hankel1(n,x)`` computes the Hankel function of the first kind, +which is the complex combination of Bessel functions given by + +.. math :: + + H_n^{(1)}(x) = J_n(x) + i Y_n(x). + +**Plots** + +.. literalinclude :: /plots/hankel1.py +.. image :: /plots/hankel1.png +.. literalinclude :: /plots/hankel1_c.py +.. image :: /plots/hankel1_c.png + +**Examples** + +The Hankel function is generally complex-valued:: + + >>> from mpmath import mp, pi, hankel1 + >>> mp.dps = 25 + >>> mp.pretty = True + >>> hankel1(2, pi) + (0.4854339326315091097054957 - 0.0999007139290278787734903j) + >>> hankel1(3.5, pi) + (0.2340002029630507922628888 - 0.6419643823412927142424049j) +""" + +hankel2 = r""" +``hankel2(n,x)`` computes the Hankel function of the second kind, +which is the complex combination of Bessel functions given by + +.. math :: + + H_n^{(2)}(x) = J_n(x) - i Y_n(x). + +**Plots** + +.. literalinclude :: /plots/hankel2.py +.. image :: /plots/hankel2.png +.. literalinclude :: /plots/hankel2_c.py +.. image :: /plots/hankel2_c.png + +**Examples** + +The Hankel function is generally complex-valued:: + + >>> from mpmath import mp, pi, hankel2 + >>> mp.dps = 25 + >>> mp.pretty = True + >>> hankel2(2, pi) + (0.4854339326315091097054957 + 0.0999007139290278787734903j) + >>> hankel2(3.5, pi) + (0.2340002029630507922628888 + 0.6419643823412927142424049j) +""" + +lambertw = r""" +The Lambert W function `W(z)` is defined as the inverse function +of `w \exp(w)`. 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)`. +All branches are supported by :func:`~mpmath.lambertw`: + +* ``lambertw(z)`` gives the principal solution (branch 0) + +* ``lambertw(z, k)`` gives the solution on branch `k` + +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`. + +The definition, implementation and choice of branches +is based on [Corless]_. + +**Plots** + +.. literalinclude :: /plots/lambertw.py +.. image :: /plots/lambertw.png +.. literalinclude :: /plots/lambertw_c.py +.. image :: /plots/lambertw_c.png + +**Basic examples** + +The Lambert W function is the inverse of `w \exp(w)`:: + + >>> from mpmath import (mp, lambertw, exp, chop, mpf, log, nprint, + ... taylor, inf, e, eps) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> w = lambertw(1) + >>> w + 0.5671432904097838729999687 + >>> w*exp(w) + 1.0 + +Any branch gives a valid inverse:: + + >>> w = lambertw(1, k=3) + >>> w + (-2.853581755409037807206819 + 17.11353553941214591260783j) + >>> w = lambertw(1, k=25) + >>> w + (-5.047020464221569709378686 + 155.4763860949415867162066j) + >>> chop(w*exp(w)) + 1.0 + +**Applications to equation-solving** + +The Lambert W function may be used to solve various kinds of +equations, such as finding the value of the infinite power +tower `z^{z^{z^{\ldots}}}`:: + + >>> def tower(z, n): + ... if n == 0: + ... return z + ... return z ** tower(z, n-1) + ... + >>> tower(mpf(0.5), 100) + 0.6411857445049859844862005 + >>> -lambertw(-log(0.5))/log(0.5) + 0.6411857445049859844862005 + +**Properties** + +The Lambert W function grows roughly like the natural logarithm +for large arguments:: + + >>> lambertw(1000) + 5.249602852401596227126056 + >>> log(1000) + 6.907755278982137052053974 + >>> lambertw(10**100) + 224.8431064451185015393731 + >>> log(10**100) + 230.2585092994045684017991 + +The principal branch of the Lambert W function has a rational +Taylor series expansion around `z = 0`:: + + >>> nprint(taylor(lambertw, 0, 6), 10) + [0.0, 1.0, -1.0, 1.5, -2.666666667, 5.208333333, -10.8] + +Some special values and limits are:: + + >>> lambertw(0) + 0.0 + >>> lambertw(1) + 0.5671432904097838729999687 + >>> lambertw(e) + 1.0 + >>> lambertw(inf) + inf + >>> lambertw(0, k=-1) + -inf + >>> lambertw(0, k=3) + -inf + >>> lambertw(inf, k=2) + (inf + 12.56637061435917295385057j) + >>> lambertw(inf, k=3) + (inf + 18.84955592153875943077586j) + >>> lambertw(-inf, k=3) + (inf + 21.9911485751285526692385j) + +The `k = 0` and `k = -1` branches join at `z = -1/e` where +`W(z) = -1` for both branches. Since `-1/e` can only be represented +approximately with binary floating-point numbers, evaluating the +Lambert W function at this point only gives `-1` approximately:: + + >>> lambertw(-1/e, 0) + -0.9999999999998371330228251 + >>> lambertw(-1/e, -1) + -1.000000000000162866977175 + +If `-1/e` happens to round in the negative direction, there might be +a small imaginary part:: + + >>> mp.dps = 15 + >>> lambertw(-1/e) + (-1.0 + 8.22007971483662e-9j) + >>> lambertw(-1/e+eps) + -0.999999966242188 + +**References** + +1. [Corless]_ +""" + +barnesg = r""" +Evaluates the Barnes G-function, which generalizes the +superfactorial (:func:`~mpmath.superfac`) and by extension also the +hyperfactorial (:func:`~mpmath.hyperfac`) to the complex numbers +in an analogous way to how the gamma function generalizes +the ordinary factorial. + +The Barnes G-function may be defined in terms of a Weierstrass +product: + +.. math :: + + G(z+1) = (2\pi)^{z/2} e^{-[z(z+1)+\gamma z^2]/2} + \prod_{n=1}^\infty + \left[\left(1+\frac{z}{n}\right)^ne^{-z+z^2/(2n)}\right] + +For positive integers `n`, we have have relation to superfactorials +`G(n) = \mathrm{sf}(n-2) = 0! \cdot 1! \cdots (n-2)!`. + +**Examples** + +Some elementary values and limits of the Barnes G-function:: + + >>> from mpmath import (mp, barnesg, sqrt, exp, log, glaisher, inf, + ... catalan, pi, nthroot, gamma, limit, mpf, psi, diff) + >>> mp.pretty = True + >>> barnesg(1), barnesg(2), barnesg(3) + (1.0, 1.0, 1.0) + >>> barnesg(4) + 2.0 + >>> barnesg(5) + 12.0 + >>> barnesg(6) + 288.0 + >>> barnesg(7) + 34560.0 + >>> barnesg(8) + 24883200.0 + >>> barnesg(inf) + inf + >>> barnesg(0), barnesg(-1), barnesg(-2) + (0.0, 0.0, 0.0) + +Closed-form values are known for some rational arguments:: + + >>> barnesg('1/2') + 0.603244281209446 + >>> sqrt(exp(0.25+log(2)/12)/sqrt(pi)/glaisher**3) + 0.603244281209446 + >>> barnesg('1/4') + 0.29375596533861 + >>> nthroot(exp('3/8')/exp(catalan/pi)/ + ... gamma(0.25)**3/sqrt(glaisher)**9, 4) + 0.29375596533861 + +The Barnes G-function satisfies the functional equation +`G(z+1) = \Gamma(z) G(z)`:: + + >>> z = pi + >>> barnesg(z+1) + 2.39292119327948 + >>> gamma(z)*barnesg(z) + 2.39292119327948 + +The asymptotic growth rate of the Barnes G-function is related to +the Glaisher-Kinkelin constant:: + + >>> limit(lambda n: barnesg(n+1)/(n**(n**2/2-mpf(1)/12)* + ... (2*pi)**(n/2)*exp(-3*n**2/4)), inf) + 0.847536694177301 + >>> exp('1/12')/glaisher + 0.847536694177301 + +The Barnes G-function can be differentiated in closed form:: + + >>> z = 3 + >>> diff(barnesg, z) + 0.264507203401607 + >>> barnesg(z)*((z-1)*psi(0,z)-z+(log(2*pi)+1)/2) + 0.264507203401607 + +Evaluation is supported for arbitrary arguments and at arbitrary +precision:: + + >>> barnesg(6.5) + 2548.7457695685 + >>> barnesg(-pi) + 0.00535976768353037 + >>> barnesg(3+4j) + (-0.000676375932234244 - 4.42236140124728e-5j) + >>> mp.dps = 50 + >>> barnesg(1/sqrt(2)) + 0.81305501090451340843586085064413533788206204124732 + >>> q = barnesg(10j) + >>> q.real + 0.000000000021852360840356557241543036724799812371995850552234 + >>> q.imag + -0.00000000000070035335320062304849020654215545839053210041457588 + >>> mp.dps = 15 + >>> barnesg(100) + 3.10361006263698e+6626 + >>> barnesg(-101) + 0.0 + >>> barnesg(-10.5) + 5.94463017605008e+25 + >>> barnesg(-10000.5) + -6.14322868174828e+167480422 + >>> barnesg(1000j) + (5.21133054865546e-1173597 + 4.27461836811016e-1173597j) + >>> barnesg(-1000+1000j) + (2.43114569750291e+1026623 + 2.24851410674842e+1026623j) + + +**References** + +1. [WhittakerWatson]_, p.264 +2. [Wikipedia]_ http://en.wikipedia.org/wiki/Barnes_G-function +3. [Weisstein]_ http://mathworld.wolfram.com/BarnesG-Function.html + +""" + +superfac = r""" +Computes the superfactorial, defined as the product of +consecutive factorials + +.. math :: + + \mathrm{sf}(n) = \prod_{k=1}^n k! + +For general complex `z`, `\mathrm{sf}(z)` is defined +in terms of the Barnes G-function (see :func:`~mpmath.barnesg`). + +**Examples** + +The first few superfactorials are (OEIS A000178):: + + >>> from mpmath import mp, superfac, pi, diff + >>> mp.pretty = True + >>> for n in range(10): + ... print("%s %s" % (n, superfac(n))) + ... + 0 1.0 + 1 1.0 + 2 2.0 + 3 12.0 + 4 288.0 + 5 34560.0 + 6 24883200.0 + 7 125411328000.0 + 8 5.05658474496e+15 + 9 1.83493347225108e+21 + +Superfactorials grow very rapidly:: + + >>> superfac(1000) + 3.24570818422368e+1177245 + >>> superfac(10**10) + 2.61398543581249e+467427913956904067453 + +Evaluation is supported for arbitrary arguments:: + + >>> mp.dps = 25 + >>> superfac(pi) + 17.20051550121297985285333 + >>> superfac(2+3j) + (-0.005915485633199789627466468 + 0.008156449464604044948738263j) + >>> diff(superfac, 1) + 0.2645072034016070205673056 + +**References** + +1. [OEIS]_ http://oeis.org/A000178 + +""" + + +hyperfac = r""" +Computes the hyperfactorial, defined for integers as the product + +.. math :: + + H(n) = \prod_{k=1}^n k^k. + + +The hyperfactorial satisfies the recurrence formula `H(z) = z^z H(z-1)`. +It can be defined more generally in terms of the Barnes G-function (see +:func:`~mpmath.barnesg`) and the gamma function by the formula + +.. math :: + + H(z) = \frac{\Gamma(z+1)^z}{G(z)}. + +The extension to complex numbers can also be done via +the integral representation + +.. math :: + + H(z) = (2\pi)^{-z/2} \exp \left[ + {z+1 \choose 2} + \int_0^z \log(t!)\,dt + \right]. + +**Examples** + +The rapidly-growing sequence of hyperfactorials begins +(OEIS A002109):: + + >>> from mpmath import (mp, hyperfac, diff, pi, mpf, chop, exp, quad, + ... binomial, loggamma, sqrt, j) + >>> mp.pretty = True + >>> for n in range(10): + ... print("%s %s" % (n, hyperfac(n))) + ... + 0 1.0 + 1 1.0 + 2 4.0 + 3 108.0 + 4 27648.0 + 5 86400000.0 + 6 4031078400000.0 + 7 3.3197663987712e+18 + 8 5.56964379417266e+25 + 9 2.15779412229419e+34 + +Some even larger hyperfactorials are:: + + >>> hyperfac(1000) + 5.46458120882585e+1392926 + >>> hyperfac(10**10) + 4.60408207642219e+489142638002418704309 + +The hyperfactorial can be evaluated for arbitrary arguments:: + + >>> hyperfac(0.5) + 0.880449235173423 + >>> diff(hyperfac, 1) + 0.581061466795327 + >>> hyperfac(pi) + 205.211134637462 + >>> hyperfac(-10+1j) + (3.01144471378225e+46 - 2.45285242480185e+46j) + +The recurrence property of the hyperfactorial holds +generally:: + + >>> z = 3-4*j + >>> hyperfac(z) + (-4.49795891462086e-7 - 6.33262283196162e-7j) + >>> z**z * hyperfac(z-1) + (-4.49795891462086e-7 - 6.33262283196162e-7j) + >>> z = mpf(-0.6) + >>> chop(z**z * hyperfac(z-1)) + 1.28170142849352 + >>> hyperfac(z) + 1.28170142849352 + +The hyperfactorial may also be computed using the integral +definition:: + + >>> z = 2.5 + >>> hyperfac(z) + 15.9842119922237 + >>> (2*pi)**(-z/2)*exp(binomial(z+1,2) + + ... quad(lambda t: loggamma(t+1), [0, z])) + 15.9842119922237 + +:func:`~mpmath.hyperfac` supports arbitrary-precision evaluation:: + + >>> mp.dps = 50 + >>> hyperfac(10) + 215779412229418562091680268288000000000000000.0 + >>> hyperfac(1/sqrt(2)) + 0.89404818005227001975423476035729076375705084390942 + +**References** + +1. [OEIS]_ http://oeis.org/A002109 +2. [Weisstein]_ http://mathworld.wolfram.com/Hyperfactorial.html + +""" + +rgamma = r""" +Computes the reciprocal of the gamma function, `1/\Gamma(z)`. This +function evaluates to zero at the poles +of the gamma function, `z = 0, -1, -2, \ldots`. + +**Examples** + +Basic examples:: + + >>> from mpmath import mp, rgamma, inf, pi, log, exp, quad, e + >>> mp.dps = 25 + >>> mp.pretty = True + >>> rgamma(1) + 1.0 + >>> rgamma(4) + 0.1666666666666666666666667 + >>> rgamma(0) + 0.0 + >>> rgamma(-1) + 0.0 + >>> rgamma(1000) + 2.485168143266784862783596e-2565 + >>> rgamma(inf) + 0.0 + +A definite integral that can be evaluated in terms of elementary +integrals:: + + >>> quad(rgamma, [0,inf]) + 2.807770242028519365221501 + >>> e + quad(lambda t: exp(-t)/(pi**2+log(t)**2), [0,inf]) + 2.807770242028519365221501 +""" + +loggamma = r""" +Computes the principal branch of the log-gamma function, +`\ln \Gamma(z)`. Unlike `\ln(\Gamma(z))`, which has infinitely many +complex branch cuts, the principal log-gamma function only has a single +branch cut along the negative half-axis. The principal branch +continuously matches the asymptotic Stirling expansion + +.. math :: + + \ln \Gamma(z) \sim \frac{\ln(2 \pi)}{2} + + \left(z-\frac{1}{2}\right) \ln(z) - z + O(z^{-1}). + +The real parts of both functions agree, but their imaginary +parts generally differ by `2 n \pi` for some `n \in \mathbb{Z}`. +They coincide for `z \in \mathbb{R}, z > 0`. + +Computationally, it is advantageous to use :func:`~mpmath.loggamma` +instead of :func:`~mpmath.gamma` for extremely large arguments. + +**Examples** + +Comparing with `\ln(\Gamma(z))`:: + + >>> from mpmath import (mp, log, loggamma, pi, j, ln2, sqrt, inf, quad, + ... diff, psi) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> loggamma('13.2') + 20.49400419456603678498394 + >>> log(gamma('13.2')) + 20.49400419456603678498394 + >>> loggamma(3+4j) + (-1.756626784603784110530604 + 4.742664438034657928194889j) + >>> log(gamma(3+4j)) + (-1.756626784603784110530604 - 1.540520869144928548730397j) + >>> log(gamma(3+4j)) + 2*pi*j + (-1.756626784603784110530604 + 4.742664438034657928194889j) + +Note the imaginary parts for negative arguments:: + + >>> loggamma(-0.5) + (1.265512123484645396488946 - 3.141592653589793238462643j) + >>> loggamma(-1.5) + (0.8600470153764810145109327 - 6.283185307179586476925287j) + >>> loggamma(-2.5) + (-0.05624371649767405067259453 - 9.42477796076937971538793j) + +Some special values:: + + >>> loggamma(1) + 0.0 + >>> loggamma(2) + 0.0 + >>> loggamma(3) + 0.6931471805599453094172321 + >>> +ln2 + 0.6931471805599453094172321 + >>> loggamma(3.5) + 1.200973602347074224816022 + >>> log(15*sqrt(pi)/8) + 1.200973602347074224816022 + >>> loggamma(inf) + inf + +Huge arguments are permitted:: + + >>> loggamma('1e30') + 6.807755278982137052053974e+31 + >>> loggamma('1e300') + 6.897755278982137052053974e+302 + >>> loggamma('1e3000') + 6.906755278982137052053974e+3003 + >>> loggamma('1e100000000000000000000') + 2.302585092994045684007991e+100000000000000000020 + >>> loggamma('1e30j') + (-1.570796326794896619231322e+30 + 6.807755278982137052053974e+31j) + >>> loggamma('1e300j') + (-1.570796326794896619231322e+300 + 6.897755278982137052053974e+302j) + >>> loggamma('1e3000j') + (-1.570796326794896619231322e+3000 + 6.906755278982137052053974e+3003j) + +The log-gamma function can be integrated analytically +on any interval of unit length:: + + >>> z = 0 + >>> quad(loggamma, [z,z+1]) + 0.9189385332046727417803297 + >>> log(2*pi)/2 + 0.9189385332046727417803297 + >>> z = 3+4j + >>> quad(loggamma, [z,z+1]) + (-0.9619286014994750641314421 + 5.219637303741238195688575j) + >>> (log(z)-1)*z + log(2*pi)/2 + (-0.9619286014994750641314421 + 5.219637303741238195688575j) + +The derivatives of the log-gamma function are given by the +polygamma function (:func:`~mpmath.psi`):: + + >>> diff(loggamma, -4+3j) + (1.688493531222971393607153 + 2.554898911356806978892748j) + >>> psi(0, -4+3j) + (1.688493531222971393607153 + 2.554898911356806978892748j) + >>> diff(loggamma, -4+3j, 2) + (-0.1539414829219882371561038 - 0.1020485197430267719746479j) + >>> psi(1, -4+3j) + (-0.1539414829219882371561038 - 0.1020485197430267719746479j) + +The log-gamma function satisfies an additive form of the +recurrence relation for the ordinary gamma function:: + + >>> z = 2+3j + >>> loggamma(z) + (-2.092851753092733349564189 + 2.302396543466867626153708j) + >>> loggamma(z+1) - log(z) + (-2.092851753092733349564189 + 2.302396543466867626153708j) + +""" + +siegeltheta = r""" +Computes the Riemann-Siegel theta function, + +.. math :: + + \theta(t) = \frac{ + \log\Gamma\left(\frac{1+2it}{4}\right) - + \log\Gamma\left(\frac{1-2it}{4}\right) + }{2i} - \frac{\log \pi}{2} t. + +The Riemann-Siegel theta function is important in +providing the phase factor for the Z-function +(see :func:`~mpmath.siegelz`). Evaluation is supported for real and +complex arguments:: + + >>> from mpmath import (mp, siegeltheta, inf, diff, nprint, chop, taylor, + ... findroot, diffun, log, pi, mpf) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> siegeltheta(0) + 0.0 + >>> siegeltheta(inf) + inf + >>> siegeltheta(-inf) + -inf + >>> siegeltheta(1) + -1.767547952812290388302216 + >>> siegeltheta(10+0.25j) + (-3.068638039426838572528867 + 0.05804937947429712998395177j) + +Arbitrary derivatives may be computed with derivative = k + + >>> siegeltheta(1234, derivative=2) + 0.0004051864079114053109473741 + >>> diff(siegeltheta, 1234, n=2) + 0.0004051864079114053109473741 + + +The Riemann-Siegel theta function has odd symmetry around `t = 0`, +two local extreme points and three real roots including 0 (located +symmetrically):: + + >>> nprint(chop(taylor(siegeltheta, 0, 5))) + [0.0, -2.68609, 0.0, 2.69433, 0.0, -6.40218] + >>> findroot(diffun(siegeltheta), 7) + 6.28983598883690277966509 + >>> findroot(siegeltheta, 20) + 17.84559954041086081682634 + +For large `t`, there is a famous asymptotic formula +for `\theta(t)`, to first order given by:: + + >>> t = mpf(10**6) + >>> siegeltheta(t) + 5488816.353078403444882823 + >>> -t*log(2*pi/t)/2-t/2 + 5488816.745777464310273645 +""" + +grampoint = r""" +Gives the `n`-th Gram point `g_n`, defined as the solution +to the equation `\theta(g_n) = \pi n` where `\theta(t)` +is the Riemann-Siegel theta function (:func:`~mpmath.siegeltheta`). + +The first few Gram points are:: + + >>> from mpmath import mp, grampoint, siegeltheta, pi, findroot + >>> mp.dps = 25 + >>> mp.pretty = True + >>> grampoint(0) + 17.84559954041086081682634 + >>> grampoint(1) + 23.17028270124630927899664 + >>> grampoint(2) + 27.67018221781633796093849 + >>> grampoint(3) + 31.71797995476405317955149 + +Checking the definition:: + + >>> siegeltheta(grampoint(3)) + 9.42477796076937971538793 + >>> 3*pi + 9.42477796076937971538793 + +A large Gram point:: + + >>> grampoint(10**10) + 3293531632.728335454561153 + +Gram points are useful when studying the Z-function +(:func:`~mpmath.siegelz`). See the documentation of that function +for additional examples. + +:func:`~mpmath.grampoint` can solve the defining equation for +nonintegral `n`. There is a fixed point where `g(x) = x`:: + + >>> findroot(lambda x: grampoint(x) - x, 10000) + 9146.698193171459265866198 + +**References** + +1. [Weisstein]_ http://mathworld.wolfram.com/GramPoint.html + +""" + +siegelz = r""" +Computes the Z-function, also known as the Riemann-Siegel Z function, + +.. math :: + + Z(t) = e^{i \theta(t)} \zeta(1/2+it) + +where `\zeta(s)` is the Riemann zeta function (:func:`~mpmath.zeta`) +and where `\theta(t)` denotes the Riemann-Siegel theta function +(see :func:`~mpmath.siegeltheta`). + +Evaluation is supported for real and complex arguments:: + + >>> from mpmath import (mp, siegelz, diff, nprint, chop, taylor, + ... findroot, zeta, grampoint) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> siegelz(1) + -0.7363054628673177346778998 + >>> siegelz(3+4j) + (-0.1852895764366314976003936 - 0.2773099198055652246992479j) + +The first four derivatives are supported, using the +optional *derivative* keyword argument:: + + >>> siegelz(1234567, derivative=3) + 56.89689348495089294249178 + >>> diff(siegelz, 1234567, n=3) + 56.89689348495089294249178 + + +The Z-function has a Maclaurin expansion:: + + >>> nprint(chop(taylor(siegelz, 0, 4))) + [-1.46035, 0.0, 2.73588, 0.0, -8.39357] + +The Z-function `Z(t)` is equal to `\pm |\zeta(s)|` on the +critical line `s = 1/2+it` (i.e. for real arguments `t` +to `Z`). Its zeros coincide with those of the Riemann zeta +function:: + + >>> findroot(siegelz, 14) + 14.13472514173469379045725 + >>> findroot(siegelz, 20) + 21.02203963877155499262848 + >>> findroot(zeta, 0.5+14j) + (0.5 + 14.13472514173469379045725j) + >>> findroot(zeta, 0.5+20j) + (0.5 + 21.02203963877155499262848j) + +Since the Z-function is real-valued on the critical line +(and unlike `|\zeta(s)|` analytic), it is useful for +investigating the zeros of the Riemann zeta function. +For example, one can use a root-finding algorithm based +on sign changes:: + + >>> findroot(siegelz, [176, 177], solver='bisect') + 176.4414342977104188888926 + +To locate roots, Gram points `g_n` which can be computed +by :func:`~mpmath.grampoint` are useful. If `(-1)^n Z(g_n)` is +positive for two consecutive `n`, then `Z(t)` must have +a zero between those points:: + + >>> g10 = grampoint(10) + >>> g11 = grampoint(11) + >>> (-1)**10 * siegelz(g10) > 0 + True + >>> (-1)**11 * siegelz(g11) > 0 + True + >>> findroot(siegelz, [g10, g11], solver='bisect') + 56.44624769706339480436776 + >>> g10, g11 + (54.67523744685325626632663, 57.54516517954725443703014) + +""" + +riemannr = r""" +Evaluates the Riemann R function, a smooth approximation of the +prime counting function `\pi(x)` (see :func:`~mpmath.primepi`). The Riemann +R function gives a fast numerical approximation useful e.g. to +roughly estimate the number of primes in a given interval. + +The Riemann R function is computed using the rapidly convergent Gram +series, + +.. math :: + + R(x) = 1 + \sum_{k=1}^{\infty} + \frac{\log^k x}{k k! \zeta(k+1)}. + +From the Gram series, one sees that the Riemann R function is a +well-defined analytic function (except for a branch cut along +the negative real half-axis); it can be evaluated for arbitrary +real or complex arguments. + +The Riemann R function gives a very accurate approximation +of the prime counting function. For example, it is wrong by at +most 2 for `x < 1000`, and for `x = 10^9` differs from the exact +value of `\pi(x)` by 79, or less than two parts in a million. +It is about 10 times more accurate than the logarithmic integral +estimate (see :func:`~mpmath.li`), which however is even faster to evaluate. +It is orders of magnitude more accurate than the extremely +fast `x/\log x` estimate. + +**Examples** + +For small arguments, the Riemann R function almost exactly +gives the prime counting function if rounded to the nearest +integer:: + + >>> from mpmath import mp, primepi, riemannr, nstr, li, diff, mpf + >>> mp.pretty = True + >>> primepi(50), riemannr(50) + (15, 14.9757023241462) + >>> max(abs(primepi(n)-round(riemannr(n))) for n in range(100)) + 1 + >>> max(abs(primepi(n)-round(riemannr(n))) for n in range(300)) + 2 + +The Riemann R function can be evaluated for arguments far too large +for exact determination of `\pi(x)` to be computationally +feasible with any presently known algorithm:: + + >>> riemannr(10**30) + 1.46923988977204e+28 + >>> riemannr(10**100) + 4.3619719871407e+97 + >>> riemannr(10**1000) + 4.3448325764012e+996 + +A comparison of the Riemann R function and logarithmic integral estimates +for `\pi(x)` using exact values of `\pi(10^n)` up to `n = 9`. +The fractional error is shown in parentheses:: + + >>> exact = [4,25,168,1229,9592,78498,664579,5761455,50847534] + >>> for n, p in enumerate(exact): + ... n += 1 + ... r, l = riemannr(10**n), li(10**n) + ... rerr, lerr = nstr((r-p)/p,3), nstr((l-p)/p,3) + ... print("%i %i %s(%s) %s(%s)" % (n, p, r, rerr, l, lerr)) + ... + 1 4 4.56458314100509(0.141) 6.1655995047873(0.541) + 2 25 25.6616332669242(0.0265) 30.1261415840796(0.205) + 3 168 168.359446281167(0.00214) 177.609657990152(0.0572) + 4 1229 1226.93121834343(-0.00168) 1246.13721589939(0.0139) + 5 9592 9587.43173884197(-0.000476) 9629.8090010508(0.00394) + 6 78498 78527.3994291277(0.000375) 78627.5491594622(0.00165) + 7 664579 664667.447564748(0.000133) 664918.405048569(0.000511) + 8 5761455 5761551.86732017(1.68e-5) 5762209.37544803(0.000131) + 9 50847534 50847455.4277214(-1.55e-6) 50849234.9570018(3.35e-5) + +The derivative of the Riemann R function gives the approximate +probability for a number of magnitude `x` to be prime:: + + >>> diff(riemannr, 1000) + 0.141903028110784 + >>> mpf(primepi(1050) - primepi(950)) / 100 + 0.15 + +Evaluation is supported for arbitrary arguments and at arbitrary +precision:: + + >>> mp.dps = 30 + >>> riemannr(7.5) + 3.72934743264966261918857135136 + >>> riemannr(-4+2j) + (-0.551002208155486427591793957644 + 2.16966398138119450043195899746j) + +""" + +primepi = r""" +Evaluates the prime counting function, `\pi(x)`, which gives +the number of primes less than or equal to `x`. The argument +`x` may be fractional. + +The prime counting function is very expensive to evaluate +precisely for large `x`, and the present implementation is +not optimized in any way. For numerical approximation of the +prime counting function, it is better to use :func:`~mpmath.primepi2` +or :func:`~mpmath.riemannr`. + +Some values of the prime counting function:: + + >>> from mpmath import primepi + >>> [primepi(k) for k in range(20)] + [0, 0, 1, 2, 2, 3, 3, 4, 4, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 8] + >>> primepi(3.5) + 2 + >>> primepi(100000) + 9592 + +""" + +primepi2 = r""" +Returns an interval (as an ``mpi`` instance) providing bounds +for the value of the prime counting function `\pi(x)`. For small +`x`, :func:`~mpmath.primepi2` returns an exact interval based on +the output of :func:`~mpmath.primepi`. For `x > 2656`, a loose interval +based on Schoenfeld's inequality + +.. math :: + + |\pi(x) - \mathrm{li}(x)| < \frac{\sqrt x \log x}{8 \pi} + +is returned. This estimate is rigorous assuming the truth of +the Riemann hypothesis, and can be computed very quickly. + +**Examples** + +Exact values of the prime counting function for small `x`:: + + >>> from mpmath import mp, iv, primepi2, primepi, mpf, riemannr + >>> mp.pretty = True + >>> iv.pretty = True + >>> primepi2(10) + [4.0, 4.0] + >>> primepi2(100) + [25.0, 25.0] + >>> primepi2(1000) + [168.0, 168.0] + +Loose intervals are generated for moderately large `x`: + + >>> primepi2(10000), primepi(10000) + ([1209.0, 1283.0], 1229) + >>> primepi2(50000), primepi(50000) + ([5070.0, 5263.0], 5133) + +As `x` increases, the absolute error gets worse while the relative +error improves. The exact value of `\pi(10^{23})` is +1925320391606803968923, and :func:`~mpmath.primepi2` gives 9 significant +digits:: + + >>> p = primepi2(10**23) + >>> p + [1.9253203909477020467e+21, 1.925320392280406229e+21] + >>> mpf(p.delta) / mpf(p.a) + 6.9219865355293e-10 + +A more precise, nonrigorous estimate for `\pi(x)` can be +obtained using the Riemann R function (:func:`~mpmath.riemannr`). +For large enough `x`, the value returned by :func:`~mpmath.primepi2` +essentially amounts to a small perturbation of the value returned by +:func:`~mpmath.riemannr`:: + + >>> primepi2(10**100) + [4.3619719871407024816e+97, 4.3619719871407032404e+97] + >>> riemannr(10**100) + 4.3619719871407e+97 +""" + +primezeta = r""" +Computes the prime zeta function, which is defined +in analogy with the Riemann zeta function (:func:`~mpmath.zeta`) +as + +.. math :: + + P(s) = \sum_p \frac{1}{p^s} + +where the sum is taken over all prime numbers `p`. Although +this sum only converges for `\mathrm{Re}(s) > 1`, the +function is defined by analytic continuation in the +half-plane `\mathrm{Re}(s) > 0`. + +**Examples** + +Arbitrary-precision evaluation for real and complex arguments is +supported:: + + >>> from mpmath import (mp, primezeta, extradps, log, eps, mertens, + ... euler, inf, mpf, pi) + >>> mp.dps = 30 + >>> mp.pretty = True + >>> primezeta(2) + 0.452247420041065498506543364832 + >>> primezeta(pi) + 0.15483752698840284272036497397 + >>> mp.dps = 50 + >>> primezeta(3) + 0.17476263929944353642311331466570670097541212192615 + >>> mp.dps = 20 + >>> primezeta(3+4j) + (-0.12085382601645763295 - 0.013370403397787023602j) + +The prime zeta function has a logarithmic pole at `s = 1`, +with residue equal to the difference of the Mertens and +Euler constants:: + + >>> primezeta(1) + inf + >>> extradps(25)(lambda x: primezeta(1+x)+log(x))(+eps) + -0.31571845205389007685 + >>> mertens-euler + -0.31571845205389007685 + +The analytic continuation to `0 < \mathrm{Re}(s) \le 1` +is implemented. In this strip the function exhibits +very complex behavior; on the unit interval, it has poles at +`1/n` for every squarefree integer `n`:: + + >>> primezeta(0.5) # Pole at s = 1/2 + (-inf + 3.1415926535897932385j) + >>> primezeta(0.25) + (-1.0416106801757269036 + 0.52359877559829887308j) + >>> primezeta(0.5+10j) + (0.54892423556409790529 + 0.45626803423487934264j) + +Although evaluation works in principle for any `\mathrm{Re}(s) > 0`, +it should be noted that the evaluation time increases exponentially +as `s` approaches the imaginary axis. + +For large `\mathrm{Re}(s)`, `P(s)` is asymptotic to `2^{-s}`:: + + >>> primezeta(inf) + 0.0 + >>> primezeta(10), mpf(2)**-10 + (0.00099360357443698021786, 0.0009765625) + >>> primezeta(1000) + 9.3326361850321887899e-302 + >>> primezeta(1000+1000j) + (-3.8565440833654995949e-302 - 8.4985390447553234305e-302j) + +**References** + +* [Froberg]_ + +""" + +bernpoly = r""" +Evaluates the Bernoulli polynomial `B_n(z)`. + +The first few Bernoulli polynomials are:: + + >>> from mpmath import mp, nprint, chop, taylor, bernpoly, bernoulli + >>> mp.pretty = True + >>> for n in range(6): + ... nprint(chop(taylor(lambda x: bernpoly(n,x), 0, n))) + ... + [1.0] + [-0.5, 1.0] + [0.166667, -1.0, 1.0] + [0.0, 0.5, -1.5, 1.0] + [-0.0333333, 0.0, 1.0, -2.0, 1.0] + [0.0, -0.166667, 0.0, 1.66667, -2.5, 1.0] + +At `z = 0`, the Bernoulli polynomial evaluates to a +Bernoulli number (see :func:`~mpmath.bernoulli`):: + + >>> bernpoly(12, 0), bernoulli(12) + (-0.253113553113553, -0.253113553113553) + >>> bernpoly(13, 0), bernoulli(13) + (0.0, 0.0) + +Evaluation is accurate for large `n` and small `z`:: + + >>> mp.dps = 25 + >>> bernpoly(100, 0.5) + 2.838224957069370695926416e+78 + >>> bernpoly(1000, 10.5) + 5.318704469415522036482914e+1769 + +""" + +polylog = r""" +Computes the polylogarithm, defined by the sum + +.. math :: + + \mathrm{Li}_s(z) = \sum_{k=1}^{\infty} \frac{z^k}{k^s}. + +This series is convergent only for `|z| < 1`, so elsewhere +the analytic continuation is implied. + +The polylogarithm should not be confused with the logarithmic +integral (also denoted by Li or li), which is implemented +as :func:`~mpmath.li`. + +**Examples** + +The polylogarithm satisfies a huge number of functional identities. +A sample of polylogarithm evaluations is shown below:: + + >>> from mpmath import (mp, polylog, log, pi, phi, zeta, j, catalan, exp, + ... zeta, quad, diff, taylor, altzeta, nsum, inf, nprint) + >>> mp.pretty = True + >>> polylog(1,0.5), log(2) + (0.693147180559945, 0.693147180559945) + >>> polylog(2,0.5), (pi**2-6*log(2)**2)/12 + (0.582240526465012, 0.582240526465012) + >>> polylog(2,-phi), -log(phi)**2-pi**2/10 + (-1.21852526068613, -1.21852526068613) + >>> polylog(3,0.5), 7*zeta(3)/8-pi**2*log(2)/12+log(2)**3/6 + (0.53721319360804, 0.53721319360804) + +:func:`~mpmath.polylog` can evaluate the analytic continuation of the +polylogarithm when `s` is an integer:: + + >>> polylog(2, 10) + (0.536301287357863 - 7.23378441241546j) + >>> polylog(2, -10) + -4.1982778868581 + >>> polylog(2, 10j) + (-3.05968879432873 + 3.71678149306807j) + >>> polylog(-2, 10) + -0.150891632373114 + >>> polylog(-2, -10) + 0.067618332081142 + >>> polylog(-2, 10j) + (0.0384353698579347 + 0.0912451798066779j) + +Some more examples, with arguments on the unit circle (note that +the series definition cannot be used for computation here):: + + >>> polylog(2,j) + (-0.205616758356028 + 0.915965594177219j) + >>> j*catalan-pi**2/48 + (-0.205616758356028 + 0.915965594177219j) + >>> polylog(3,exp(2*pi*j/3)) + (-0.534247512515375 + 0.765587078525922j) + >>> -4*zeta(3)/9 + 2*j*pi**3/81 + (-0.534247512515375 + 0.765587078525921j) + +Polylogarithms of different order are related by integration +and differentiation:: + + >>> s, z = 3, 0.5 + >>> polylog(s+1, z) + 0.517479061673899 + >>> quad(lambda t: polylog(s,t)/t, [0, z]) + 0.517479061673899 + >>> z*diff(lambda t: polylog(s+2,t), z) + 0.517479061673899 + +Taylor series expansions around `z = 0` are:: + + >>> for n in range(-3, 4): + ... nprint(taylor(lambda x: polylog(n,x), 0, 5)) + ... + [0.0, 1.0, 8.0, 27.0, 64.0, 125.0] + [0.0, 1.0, 4.0, 9.0, 16.0, 25.0] + [0.0, 1.0, 2.0, 3.0, 4.0, 5.0] + [0.0, 1.0, 1.0, 1.0, 1.0, 1.0] + [0.0, 1.0, 0.5, 0.333333, 0.25, 0.2] + [0.0, 1.0, 0.25, 0.111111, 0.0625, 0.04] + [0.0, 1.0, 0.125, 0.037037, 0.015625, 0.008] + +The series defining the polylogarithm is simultaneously +a Taylor series and an L-series. For certain values of `z`, the +polylogarithm reduces to a pure zeta function:: + + >>> polylog(pi, 1), zeta(pi) + (1.17624173838258, 1.17624173838258) + >>> polylog(pi, -1), -altzeta(pi) + (-0.909670702980385, -0.909670702980385) + +Evaluation for arbitrary, nonintegral `s` is supported +for `z` within the unit circle: + + >>> polylog(3+4j, 0.25) + (0.24258605789446 - 0.00222938275488344j) + >>> nsum(lambda k: 0.25**k / k**(3+4j), [1,inf]) + (0.24258605789446 - 0.00222938275488344j) + +It is also supported outside of the unit circle:: + + >>> polylog(1+j, 20+40j) + (-7.1421172179728 - 3.92726697721369j) + >>> polylog(1+j, 200+400j) + (-5.41934747194626 - 9.94037752563927j) + +**References** + +1. [Crandall]_ +2. [Wikipedia]_ http://en.wikipedia.org/wiki/Polylogarithm +3. [Weisstein]_ http://mathworld.wolfram.com/Polylogarithm.html + +""" + +bell = r""" +For `n` a nonnegative integer, ``bell(n,x)`` evaluates the Bell +polynomial `B_n(x)`, the first few of which are + +.. math :: + + B_0(x) = 1 + + B_1(x) = x + + B_2(x) = x^2+x + + B_3(x) = x^3+3x^2+x + +If `x = 1` or :func:`~mpmath.bell` is called with only one argument, it +gives the `n`-th Bell number `B_n`, which is the number of +partitions of a set with `n` elements. By setting the precision to +at least `\log_{10} B_n` digits, :func:`~mpmath.bell` provides fast +calculation of exact Bell numbers. + +In general, :func:`~mpmath.bell` computes + +.. math :: + + B_n(x) = e^{-x} \left(\mathrm{sinc}(\pi n) + E_n(x)\right) + +where `E_n(x)` is the generalized exponential function implemented +by :func:`~mpmath.polyexp`. This is an extension of Dobinski's formula [1], +where the modification is the sinc term ensuring that `B_n(x)` is +continuous in `n`; :func:`~mpmath.bell` can thus be evaluated, +differentiated, etc for arbitrary complex arguments. + +**Examples** + +Simple evaluations:: + + >>> from mpmath import mp, bell, nprint, taylor, det, superfac + >>> mp.dps = 25 + >>> mp.pretty = True + >>> bell(0, 2.5) + 1.0 + >>> bell(1, 2.5) + 2.5 + >>> bell(2, 2.5) + 8.75 + +Evaluation for arbitrary complex arguments:: + + >>> bell(5.75+1j, 2-3j) + (-10767.71345136587098445143 - 15449.55065599872579097221j) + +The first few Bell polynomials:: + + >>> for k in range(7): + ... nprint(taylor(lambda x: bell(k,x), 0, k)) + ... + [1.0] + [0.0, 1.0] + [0.0, 1.0, 1.0] + [0.0, 1.0, 3.0, 1.0] + [0.0, 1.0, 7.0, 6.0, 1.0] + [0.0, 1.0, 15.0, 25.0, 10.0, 1.0] + [0.0, 1.0, 31.0, 90.0, 65.0, 15.0, 1.0] + +The first few Bell numbers and complementary Bell numbers:: + + >>> [int(bell(k)) for k in range(10)] + [1, 1, 2, 5, 15, 52, 203, 877, 4140, 21147] + >>> [int(bell(k,-1)) for k in range(10)] + [1, -1, 0, 1, 1, -2, -9, -9, 50, 267] + +Large Bell numbers:: + + >>> mp.dps = 50 + >>> bell(50) + 185724268771078270438257767181908917499221852770.0 + >>> bell(50,-1) + -29113173035759403920216141265491160286912.0 + +Some even larger values:: + + >>> mp.dps = 25 + >>> bell(1000,-1) + -1.237132026969293954162816e+1869 + >>> bell(1000) + 2.989901335682408421480422e+1927 + >>> bell(1000,2) + 6.591553486811969380442171e+1987 + >>> bell(1000,100.5) + 9.101014101401543575679639e+2529 + +A determinant identity satisfied by Bell numbers:: + + >>> mp.dps = 15 + >>> N = 8 + >>> det([[bell(k+j) for j in range(N)] for k in range(N)]) + 125411328000.0 + >>> superfac(N-1) + 125411328000.0 + +**References** + +1. [Weisstein]_ http://mathworld.wolfram.com/DobinskisFormula.html + +""" + +polyexp = r""" +Evaluates the polyexponential function, defined for arbitrary +complex `s`, `z` by the series + +.. math :: + + E_s(z) = \sum_{k=1}^{\infty} \frac{k^s}{k!} z^k. + +`E_s(z)` is constructed from the exponential function analogously +to how the polylogarithm is constructed from the ordinary +logarithm; as a function of `s` (with `z` fixed), `E_s` is an L-series +It is an entire function of both `s` and `z`. + +The polyexponential function provides a generalization of the +Bell polynomials `B_n(x)` (see :func:`~mpmath.bell`) to noninteger orders `n`. +In terms of the Bell polynomials, + +.. math :: + + E_s(z) = e^z B_s(z) - \mathrm{sinc}(\pi s). + +Note that `B_n(x)` and `e^{-x} E_n(x)` are identical if `n` +is a nonzero integer, but not otherwise. In particular, they differ +at `n = 0`. + +**Examples** + +Evaluating a series:: + + >>> from mpmath import mp, nsum, sqrt, fac, inf, polyexp, pi, hyper + >>> mp.dps = 25 + >>> mp.pretty = True + >>> nsum(lambda k: sqrt(k)/fac(k), [1,inf]) + 2.101755547733791780315904 + >>> polyexp(0.5,1) + 2.101755547733791780315904 + +Evaluation for arbitrary arguments:: + + >>> polyexp(-3-4j, 2.5+2j) + (2.351660261190434618268706 + 1.202966666673054671364215j) + +Evaluation is accurate for tiny function values:: + + >>> polyexp(4, -100) + 3.499471750566824369520223e-36 + +If `n` is a nonpositive integer, `E_n` reduces to a special +instance of the hypergeometric function `\,_pF_q`:: + + >>> n = 3 + >>> x = pi + >>> polyexp(-n,x) + 4.042192318847986561771779 + >>> x*hyper([1]*(n+1), [2]*(n+1), x) + 4.042192318847986561771779 + +""" + +cyclotomic = r""" +Evaluates the cyclotomic polynomial `\Phi_n(x)`, defined by + +.. math :: + + \Phi_n(x) = \prod_{\zeta} (x - \zeta) + +where `\zeta` ranges over all primitive `n`-th roots of unity +(see :func:`~mpmath.unitroots`). An equivalent representation, used +for computation, is + +.. math :: + + \Phi_n(x) = \prod_{d\mid n}(x^d-1)^{\mu(n/d)} = \Phi_n(x) + +where `\mu(m)` denotes the Moebius function. The cyclotomic +polynomials are integer polynomials, the first of which can be +written explicitly as + +.. math :: + + \Phi_0(x) = 1 + + \Phi_1(x) = x - 1 + + \Phi_2(x) = x + 1 + + \Phi_3(x) = x^3 + x^2 + 1 + + \Phi_4(x) = x^2 + 1 + + \Phi_5(x) = x^4 + x^3 + x^2 + x + 1 + + \Phi_6(x) = x^2 - x + 1 + +**Examples** + +The coefficients of low-order cyclotomic polynomials can be recovered +using Taylor expansion:: + + >>> from mpmath import (mp, chop, taylor, cyclotomic, nstr, fprod, + ... unitroots, polyroots) + >>> mp.pretty = True + >>> for n in range(9): + ... p = chop(taylor(lambda x: cyclotomic(n,x), 0, 10)) + ... print("%s %s" % (n, nstr(p[:10+1-p[::-1].index(1)]))) + ... + 0 [1.0] + 1 [-1.0, 1.0] + 2 [1.0, 1.0] + 3 [1.0, 1.0, 1.0] + 4 [1.0, 0.0, 1.0] + 5 [1.0, 1.0, 1.0, 1.0, 1.0] + 6 [1.0, -1.0, 1.0] + 7 [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] + 8 [1.0, 0.0, 0.0, 0.0, 1.0] + +The definition as a product over primitive roots may be checked +by computing the product explicitly (for a real argument, this +method will generally introduce numerical noise in the imaginary +part):: + + >>> mp.dps = 25 + >>> z = 3+4j + >>> cyclotomic(10, z) + (-419.0 - 360.0j) + >>> fprod(z-r for r in unitroots(10, primitive=True)) + (-419.0 - 360.0j) + >>> z = 3 + >>> cyclotomic(10, z) + 61.0 + >>> fprod(z-r for r in unitroots(10, primitive=True)) + (61.0 - 3.146045605088568607055454e-25j) + +Up to permutation, the roots of a given cyclotomic polynomial +can be checked to agree with the list of primitive roots:: + + >>> p = taylor(lambda x: cyclotomic(6,x), 0, 6)[:3] + >>> for r in polyroots(p): + ... print(r) + ... + (0.5 - 0.8660254037844386467637232j) + (0.5 + 0.8660254037844386467637232j) + >>> + >>> for r in unitroots(6, primitive=True): + ... print(r) + ... + (0.5 + 0.8660254037844386467637232j) + (0.5 - 0.8660254037844386467637232j) + +""" + +meijerg = r""" +Evaluates the Meijer G-function, defined as + +.. math :: + + G^{m,n}_{p,q} \left( \left. \begin{matrix} + a_1, \dots, a_n ; a_{n+1} \dots a_p \\ + b_1, \dots, b_m ; b_{m+1} \dots b_q + \end{matrix}\; \right| \; z ; r \right) = + \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=n+1}^{p}\Gamma(a_j+s) \prod_{j=m+1}^q \Gamma(1-b_j-s)} + z^{-s/r} ds + +for an appropriate choice of the contour `L` (see references). + +There are `p` elements `a_j`. +The argument *a_s* should be a pair of lists, the first containing the +`n` elements `a_1, \ldots, a_n` and the second containing +the `p-n` elements `a_{n+1}, \ldots a_p`. + +There are `q` elements `b_j`. +The argument *b_s* should be a pair of lists, the first containing the +`m` elements `b_1, \ldots, b_m` and the second containing +the `q-m` elements `b_{m+1}, \ldots b_q`. + +The implicit tuple `(m, n, p, q)` constitutes the order or degree of the +Meijer G-function, and is determined by the lengths of the coefficient +vectors. Confusingly, the indices in this tuple appear in a different order +from the coefficients, but this notation is standard. The many examples +given below should hopefully clear up any potential confusion. + +**Algorithm** + +The Meijer G-function is evaluated as a combination of hypergeometric series. +There are two versions of the function, which can be selected with +the optional *series* argument. + +*series=1* uses a sum of `m` `\,_pF_{q-1}` functions of `z` + +*series=2* uses a sum of `n` `\,_qF_{p-1}` functions of `1/z` + +The default series is chosen based on the degree and `|z|` in order +to be consistent with Mathematica's. This definition of the Meijer G-function +has a discontinuity at `|z| = 1` for some orders, which can +be avoided by explicitly specifying a series. + +Keyword arguments are forwarded to :func:`~mpmath.hypercomb`. + +**Examples** + +Many standard functions are special cases of the Meijer G-function +(possibly rescaled and/or with branch cut corrections). We define +some test parameters:: + + >>> from mpmath import (mp, mpf, meijerg, exp, log, sin, cos, sqrt, pi, + ... besselj, bessely, chop, gamma, expint, besseli, + ... besselk, erfc) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> a = mpf(0.75) + >>> b = mpf(1.5) + >>> z = mpf(2.25) + +The exponential function: +`e^z = G^{1,0}_{0,1} \left( \left. \begin{matrix} - \\ 0 \end{matrix} \; +\right| \; -z \right)` + + >>> meijerg([[],[]], [[0],[]], -z) + 9.487735836358525720550369 + >>> exp(z) + 9.487735836358525720550369 + +The natural logarithm: +`\log(1+z) = G^{1,2}_{2,2} \left( \left. \begin{matrix} 1, 1 \\ 1, 0 +\end{matrix} \; \right| \; -z \right)` + + >>> meijerg([[1,1],[]], [[1],[0]], z) + 1.178654996341646117219023 + >>> log(1+z) + 1.178654996341646117219023 + +A rational function: +`\frac{z}{z+1} = G^{1,2}_{2,2} \left( \left. \begin{matrix} 1, 1 \\ 1, 1 +\end{matrix} \; \right| \; z \right)` + + >>> meijerg([[1,1],[]], [[1],[1]], z) + 0.6923076923076923076923077 + >>> z/(z+1) + 0.6923076923076923076923077 + +The sine and cosine functions: + +`\frac{1}{\sqrt \pi} \sin(2 \sqrt z) = G^{1,0}_{0,2} \left( \left. \begin{matrix} +- \\ \frac{1}{2}, 0 \end{matrix} \; \right| \; z \right)` + +`\frac{1}{\sqrt \pi} \cos(2 \sqrt z) = G^{1,0}_{0,2} \left( \left. \begin{matrix} +- \\ 0, \frac{1}{2} \end{matrix} \; \right| \; z \right)` + + >>> meijerg([[],[]], [[0.5],[0]], (z/2)**2) + 0.4389807929218676682296453 + >>> sin(z)/sqrt(pi) + 0.4389807929218676682296453 + >>> meijerg([[],[]], [[0],[0.5]], (z/2)**2) + -0.3544090145996275423331762 + >>> cos(z)/sqrt(pi) + -0.3544090145996275423331762 + +Bessel functions: + +`J_a(2 \sqrt z) = G^{1,0}_{0,2} \left( \left. +\begin{matrix} - \\ \frac{a}{2}, -\frac{a}{2} +\end{matrix} \; \right| \; z \right)` + +`Y_a(2 \sqrt z) = G^{2,0}_{1,3} \left( \left. +\begin{matrix} \frac{-a-1}{2} \\ \frac{a}{2}, -\frac{a}{2}, \frac{-a-1}{2} +\end{matrix} \; \right| \; z \right)` + +`(-z)^{a/2} z^{-a/2} I_a(2 \sqrt z) = G^{1,0}_{0,2} \left( \left. +\begin{matrix} - \\ \frac{a}{2}, -\frac{a}{2} +\end{matrix} \; \right| \; -z \right)` + +`2 K_a(2 \sqrt z) = G^{2,0}_{0,2} \left( \left. +\begin{matrix} - \\ \frac{a}{2}, -\frac{a}{2} +\end{matrix} \; \right| \; z \right)` + +As the example with the Bessel *I* function shows, a branch +factor is required for some arguments when inverting the square root. + + >>> meijerg([[],[]], [[a/2],[-a/2]], (z/2)**2) + 0.5059425789597154858527264 + >>> besselj(a,z) + 0.5059425789597154858527264 + >>> meijerg([[],[(-a-1)/2]], [[a/2,-a/2],[(-a-1)/2]], (z/2)**2) + 0.1853868950066556941442559 + >>> bessely(a, z) + 0.1853868950066556941442559 + >>> meijerg([[],[]], [[a/2],[-a/2]], -(z/2)**2) + (0.8685913322427653875717476 + 2.096964974460199200551738j) + >>> (-z)**(a/2) / z**(a/2) * besseli(a, z) + (0.8685913322427653875717476 + 2.096964974460199200551738j) + >>> 0.5*meijerg([[],[]], [[a/2,-a/2],[]], (z/2)**2) + 0.09334163695597828403796071 + >>> besselk(a,z) + 0.09334163695597828403796071 + +Error functions: + +`\sqrt{\pi} z^{2(a-1)} \mathrm{erfc}(z) = G^{2,0}_{1,2} \left( \left. +\begin{matrix} a \\ a-1, a-\frac{1}{2} +\end{matrix} \; \right| \; z, \frac{1}{2} \right)` + + >>> meijerg([[],[a]], [[a-1,a-0.5],[]], z, 0.5) + 0.00172839843123091957468712 + >>> sqrt(pi) * z**(2*a-2) * erfc(z) + 0.00172839843123091957468712 + +A Meijer G-function of higher degree, (1,1,2,3): + + >>> meijerg([[a],[b]], [[a],[b,a-1]], z) + 1.55984467443050210115617 + >>> sin((b-a)*pi)/pi*(exp(z)-1)*z**(a-1) + 1.55984467443050210115617 + +A Meijer G-function of still higher degree, (4,1,2,4), that can +be expanded as a messy combination of exponential integrals: + + >>> meijerg([[a],[2*b-a]], [[b,a,b-0.5,-1-a+2*b],[]], z) + 0.3323667133658557271898061 + >>> chop(4**(a-b+1)*sqrt(pi)*gamma(2*b-2*a)*z**a*\ + ... expint(2*b-2*a, -2*sqrt(-z))*expint(2*b-2*a, 2*sqrt(-z))) + 0.3323667133658557271898061 + +In the following case, different series give different values:: + + >>> chop(meijerg([[1],[0.25]],[[3],[0.5]],-2)) + -0.06417628097442437076207337 + >>> meijerg([[1],[0.25]],[[3],[0.5]],-2,series=1) + 0.1428699426155117511873047 + >>> chop(meijerg([[1],[0.25]],[[3],[0.5]],-2,series=2)) + -0.06417628097442437076207337 + +**References** + +1. [Wikipedia]_ http://en.wikipedia.org/wiki/Meijer_G-function + +2. [Weisstein]_ http://mathworld.wolfram.com/MeijerG-Function.html + +3. [WolframFunctions]_ http://functions.wolfram.com/HypergeometricFunctions/MeijerG/ + +4. [WolframFunctions]_ http://functions.wolfram.com/HypergeometricFunctions/MeijerG1/ + +""" + +foxh = r""" +Evaluates the Fox H-function, a generalization of Meijer G-function, defined as + +.. math :: + + & H^{m,n}_{p,q} \left( \left. \begin{matrix} + (a_1, A_1), \dots, (a_n, A_n) ; (a_{n+1}, A_{n+1}) \dots (a_p, A_p) \\ + (b_1, B_1), \dots, (b_m, B_m) ; (b_{m+1}, B_{m+1}) \dots (b_q, B_q) + \end{matrix}\; \right| \; z ; r \right) \\ = & + \frac{1}{2 \pi i} \int_L + \frac{\prod_{j=1}^m \Gamma(b_j+B_js) \prod_{j=1}^n\Gamma(1-a_j-A_js)} + {\prod_{j=n+1}^{p}\Gamma(a_j+A_js) \prod_{j=m+1}^q \Gamma(1-b_j-B_js)} + z^{-s/r} ds + +for an appropriate choice of the contour `L` (see references). + +There are `p` pairs `(a_j, A_j)`. +The argument *aA_s* should be a pair of lists, the first containing the +`n` pairs `(a_1, A_1), \ldots, (a_n, A_n)` and the second containing +the `p-n` elements `(a_{n+1}, A_{n+1}), \ldots, (a_p, A_p)`. + +There are `q` pairs `(b_j, B_j)`. +The argument *b_s* should be a pair of lists, the first containing the +`m` pairs `(b_1, B_1), \ldots, (b_m, B_m)` and the second containing +the `q-m` pairs `(b_{m+1}, B_{m+1}), \ldots, (b_q, B_q)`. + +This implementation supports only positive rational `A_j` and `B_j`. +When `A_j` (and `B_j`) are integers, user can directly write `(a_j, A_j)` (and `b_j, B_j`). +When `A_j` (and `B_j`) are positive rational numbers, user should write +`(a_j, (E_j, D_j))` (and `b_j, (F_j, D_j)`), where `D_j` is the denominator. + +Other descriptions follow the Meijer G-function. + +**Algorithm** + +Using the following identity rational `A_j` and `B_j` are converted to integer `E_j` and `F_j`: + +.. math :: + H^{m,n}_{p,q} \left( \left. \begin{matrix} + \left(a_j, \frac{E_j}{D}\right) \\ + \left(b_j, \frac{F_j}{D}\right) + \end{matrix}\; \right| \; z ; r \right) = + D \cdot H^{m,n}_{p,q} \left( \left. \begin{matrix} + \left(a_j, E_j\right) \\ + \left(b_j, F_j\right) + \end{matrix}\; \right| \; z ; r/D \right) + +by choosing `D` to be the L.C.M. of the denominators of all `A_j` and `B_j`. + +Then by the Gauss Multiplicatoin formula + +.. math :: + \Gamma(kz) = (2\pi)^{(1-z)/2}k^{kz-1/2}\prod_{j=0}^{k-1} \Gamma\left(z + \frac{j}{k}\right) + +For terms in Fox-H function, for example, `\Gamma(a_j + E_j s)`, we can +write it as + +.. math :: + \Gamma\left(E_j\left(\frac{a_j}{E_j} + s\right)\right) = + (2\pi)^{(1-E_j)/2} E_j^{a_j - 1/2} E_j^{E_j s} + \prod_{\ell=0}^{E_j-1} \Gamma\left(s + \frac{a_j + \ell}{E_j}\right) + +Notice that now `s` has coefficient of 1, so it coincides with the Meijer G-function, we have + +.. math :: + H^{m,n}_{p,q} \left( \left. \begin{matrix} + \left(a_j, E_j\right) \\ + \left(b_j, F_j\right) + \end{matrix}\; \right| \; z ; r \right) + = (2\pi)^{c^*-a^*/2}\cdot M\cdot + G^{\tilde{m},\tilde{n}}_{\tilde{p},\tilde{q}} \left( \left. \begin{matrix} + \left(\frac{a_j + \ell}{E_j}\right) \\ + \left(\frac{b_j + \ell}{F_j}\right) + \end{matrix}\; \right| \; \frac{z}{\beta^r} ; r \right) + +where + +.. math :: + c^* &= m + n - (p + q)/2\\ + a^* &= \sum_{j=1}^{n}E_j - \sum_{j=n+1}^{p}E_j + \sum_{j=1}^{m}F_j - \sum_{j=m+1}^{q}F_j\\ + M &= \frac{\prod_{j=1}^{q} F_j^{F_j}}{\prod_{j=1}^{p} E_j^{E_j}}\\ + \beta &= \frac{\prod_{j=1}^{q} F_j^{b_j-1/2}}{\prod_{j=1}^{p} E_j^{a_j-1/2}} + +and `\tilde{m} = \sum_{j=1}^{m} F_j`, `\tilde{n} = \sum_{j=1}^{n} E_j`, +`\tilde{p} = \sum_{j=1}^{p} E_j`, `\tilde{q} = \sum_{j=1}^{q} F_j`. + +Then it is evaluated using :func:`~mpmath.meijerg`. +Keyword arguments are forwarded accordingly. + +**Examples** + +The exponential function: +`\frac{1}{B}z^{b/B}\exp\left(-z^{1/B}\right) = +H^{1,0}_{0,1} \left( \left. \begin{matrix} +- \\ (b, B) \end{matrix} \; \right| \; z \right)` + + >>> from mpmath import mp, mpf, exp, foxh, meijerg, pi + >>> mp.dps = 25 + >>> mp.pretty = True + >>> b = 1; B = 2; z = mpf(0.2) + >>> mpf(1)/B * (z ** (mpf(b)/B)) * exp(-z ** (mpf(1)/B)) + 0.1429758230956905796188428 + >>> foxh([[],[]],[[(b,B)],[]],z) + 0.1429758230956905796188428 + >>> meijerg([[],[]],[[b],[]],z,r=B)/B + 0.1429758230956905796188428 + +Another example involving rational `A_j` and irrational `a_j`: + + >>> foxh([[(mpf('1/10'),(6,5)), (mpf('13/10'),1)],[(mpf('17/5'),2)]],[[(mpf('7/5'),2)],[(pi,1)]],mpf('0.2')) + 0.1436702548477872392572574 + +**References** + +1. [Wikipedia]_ http://en.wikipedia.org/wiki/Fox_H-function + +2. [Weisstein]_ http://mathworld.wolfram.com/FoxH-Function.html + +""" + +clsin = r""" +Computes the Clausen sine function, defined formally by the series + +.. math :: + + \mathrm{Cl}_s(z) = \sum_{k=1}^{\infty} \frac{\sin(kz)}{k^s}. + +The special case `\mathrm{Cl}_2(z)` (i.e. ``clsin(2,z)``) is the classical +"Clausen function". More generally, the Clausen function is defined for +complex `s` and `z`, even when the series does not converge. The +Clausen function is related to the polylogarithm (:func:`~mpmath.polylog`) as + +.. math :: + + \mathrm{Cl}_s(z) = \frac{1}{2i}\left(\mathrm{Li}_s\left(e^{iz}\right) - + \mathrm{Li}_s\left(e^{-iz}\right)\right) + + = \mathrm{Im}\left[\mathrm{Li}_s(e^{iz})\right] \quad (s, z \in \mathbb{R}), + +and this representation can be taken to provide the analytic continuation of the +series. The complementary function :func:`~mpmath.clcos` gives the corresponding +cosine sum. + +**Examples** + +Evaluation for arbitrarily chosen `s` and `z`:: + + >>> from mpmath import (mp, clsin, nsum, sin, inf, chop, log, exp, j, + ... ln, quad, pi, chop, cot, csc, extraprec, sqrt, + ... catalan) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> s, z = 3, 4 + >>> clsin(s, z) + -0.6533010136329338746275795 + >>> nsum(lambda k: sin(z*k)/k**s, [1,inf]) + -0.6533010136329338746275795 + +Using `z + \pi` instead of `z` gives an alternating series:: + + >>> clsin(s, z+pi) + 0.8860032351260589402871624 + >>> nsum(lambda k: (-1)**k*sin(z*k)/k**s, [1,inf]) + 0.8860032351260589402871624 + +With `s = 1`, the sum can be expressed in closed form +using elementary functions:: + + >>> z = 1 + sqrt(3) + >>> clsin(1, z) + 0.2047709230104579724675985 + >>> chop((log(1-exp(-j*z)) - log(1-exp(j*z)))/(2*j)) + 0.2047709230104579724675985 + >>> nsum(lambda k: sin(k*z)/k, [1,inf]) + 0.2047709230104579724675985 + +The classical Clausen function `\mathrm{Cl}_2(\theta)` gives the +value of the integral `\int_0^{\theta} -\ln(2\sin(x/2)) dx` for +`0 < \theta < 2 \pi`:: + + >>> cl2 = lambda t: clsin(2, t) + >>> cl2(3.5) + -0.2465045302347694216534255 + >>> -quad(lambda x: ln(2*sin(0.5*x)), [0, 3.5]) + -0.2465045302347694216534255 + +This function is symmetric about `\theta = \pi` with zeros and extreme +points:: + + >>> cl2(0) + 0.0 + >>> cl2(pi/3) + 1.014941606409653625021203 + >>> chop(cl2(pi)) + 0.0 + >>> cl2(5*pi/3) + -1.014941606409653625021203 + >>> chop(cl2(2*pi)) + 0.0 + +Catalan's constant is a special value:: + + >>> cl2(pi/2) + 0.9159655941772190150546035 + >>> +catalan + 0.9159655941772190150546035 + +The Clausen sine function can be expressed in closed form when +`s` is an odd integer (becoming zero when `s` < 0):: + + >>> z = 1 + sqrt(2) + >>> clsin(1, z) + 0.3636895456083490948304773 + >>> (pi-z)/2 + 0.3636895456083490948304773 + >>> clsin(3, z) + 0.5661751584451144991707161 + >>> pi**2/6*z - pi*z**2/4 + z**3/12 + 0.5661751584451144991707161 + >>> clsin(-1, z) + 0.0 + >>> clsin(-3, z) + 0.0 + +It can also be expressed in closed form for even integer `s \le 0`, +providing a finite sum for series such as +`\sin(z) + \sin(2z) + \sin(3z) + \ldots`:: + + >>> z = 1 + sqrt(2) + >>> clsin(0, z) + 0.1903105029507513881275865 + >>> cot(z/2)/2 + 0.1903105029507513881275865 + >>> clsin(-2, z) + -0.1089406163841548817581392 + >>> -cot(z/2)*csc(z/2)**2/4 + -0.1089406163841548817581392 + +Call with ``pi=True`` to multiply `z` by `\pi` exactly:: + + >>> clsin(3, 3*pi) + -8.892316224968072424732898e-26 + >>> clsin(3, 3, pi=True) + 0.0 + +Evaluation for complex `s`, `z` in a nonconvergent case:: + + >>> s, z = -1-j, 1+2j + >>> clsin(s, z) + (-0.593079480117379002516034 + 0.9038644233367868273362446j) + >>> extraprec(20)(nsum)(lambda k: sin(k*z)/k**s, [1,inf]) + (-0.593079480117379002516034 + 0.9038644233367868273362446j) + +""" + +clcos = r""" +Computes the Clausen cosine function, defined formally by the series + +.. math :: + + \mathrm{\widetilde{Cl}}_s(z) = \sum_{k=1}^{\infty} \frac{\cos(kz)}{k^s}. + +This function is complementary to the Clausen sine function +:func:`~mpmath.clsin`. In terms of the polylogarithm, + +.. math :: + + \mathrm{\widetilde{Cl}}_s(z) = + \frac{1}{2}\left(\mathrm{Li}_s\left(e^{iz}\right) + + \mathrm{Li}_s\left(e^{-iz}\right)\right) + + = \mathrm{Re}\left[\mathrm{Li}_s(e^{iz})\right] \quad (s, z \in \mathbb{R}). + +**Examples** + +Evaluation for arbitrarily chosen `s` and `z`:: + + >>> from mpmath import (mp, clcos, cos, inf, nsum, pi, sqrt, exp, log, j, + ... sin, chop, nsum, csc, zeta, altzeta, extraprec) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> s, z = 3, 4 + >>> clcos(s, z) + -0.6518926267198991308332759 + >>> nsum(lambda k: cos(z*k)/k**s, [1,inf]) + -0.6518926267198991308332759 + +Using `z + \pi` instead of `z` gives an alternating series:: + + >>> s, z = 3, 0.5 + >>> clcos(s, z+pi) + -0.8155530586502260817855618 + >>> nsum(lambda k: (-1)**k*cos(z*k)/k**s, [1,inf]) + -0.8155530586502260817855618 + +With `s = 1`, the sum can be expressed in closed form +using elementary functions:: + + >>> z = 1 + sqrt(3) + >>> clcos(1, z) + -0.6720334373369714849797918 + >>> chop(-0.5*(log(1-exp(j*z))+log(1-exp(-j*z)))) + -0.6720334373369714849797918 + >>> -log(abs(2*sin(0.5*z))) # Equivalent to above when z is real + -0.6720334373369714849797918 + >>> nsum(lambda k: cos(k*z)/k, [1,inf]) + -0.6720334373369714849797918 + +It can also be expressed in closed form when `s` is an even integer. +For example, + + >>> clcos(2,z) + -0.7805359025135583118863007 + >>> pi**2/6 - pi*z/2 + z**2/4 + -0.7805359025135583118863007 + +The case `s = 0` gives the renormalized sum of +`\cos(z) + \cos(2z) + \cos(3z) + \ldots` (which happens to be the same for +any value of `z`):: + + >>> clcos(0, z) + -0.5 + >>> nsum(lambda k: cos(k*z), [1,inf]) + -0.5 + +Also the sums + +.. math :: + + \cos(z) + 2\cos(2z) + 3\cos(3z) + \ldots + +and + +.. math :: + + \cos(z) + 2^n \cos(2z) + 3^n \cos(3z) + \ldots + +for higher integer powers `n = -s` can be done in closed form. They are zero +when `n` is positive and even (`s` negative and even):: + + >>> clcos(-1, z) + -0.2607829375240542480694126 + >>> 1/(2*cos(z)-2) + -0.2607829375240542480694126 + >>> clcos(-3, z) + 0.1472635054979944390848006 + >>> (2+cos(z))*csc(z/2)**4/8 + 0.1472635054979944390848006 + >>> clcos(-2, z) + 0.0 + >>> clcos(-4, z) + 0.0 + >>> clcos(-6, z) + 0.0 + +With `z = \pi`, the series reduces to that of the Riemann zeta function +(more generally, if `z = p \pi/q`, it is a finite sum over Hurwitz zeta +function values):: + + >>> clcos(2.5, 0) + 1.34148725725091717975677 + >>> zeta(2.5) + 1.34148725725091717975677 + >>> clcos(2.5, pi) + -0.8671998890121841381913472 + >>> -altzeta(2.5) + -0.8671998890121841381913472 + +Call with ``pi=True`` to multiply `z` by `\pi` exactly:: + + >>> clcos(-3, 2*pi) + 2.997921055881167659267063e+102 + >>> clcos(-3, 2, pi=True) + 0.008333333333333333333333333 + +Evaluation for complex `s`, `z` in a nonconvergent case:: + + >>> s, z = -1-j, 1+2j + >>> clcos(s, z) + (0.9407430121562251476136807 + 0.715826296033590204557054j) + >>> extraprec(20)(nsum)(lambda k: cos(k*z)/k**s, [1,inf]) + (0.9407430121562251476136807 + 0.715826296033590204557054j) + +""" + +whitm = r""" +Evaluates the Whittaker function `M(k,m,z)`, which gives a solution +to the Whittaker differential equation + +.. math :: + + \frac{d^2f}{dz^2} + \left(-\frac{1}{4}+\frac{k}{z}+ + \frac{(\frac{1}{4}-m^2)}{z^2}\right) f = 0. + +A second solution is given by :func:`~mpmath.whitw`. + +The Whittaker functions are defined in Abramowitz & Stegun, section 13.1. +They are alternate forms of the confluent hypergeometric functions +`\,_1F_1` and `U`: + +.. math :: + + M(k,m,z) = e^{-\frac{1}{2}z} z^{\frac{1}{2}+m} + \,_1F_1(\tfrac{1}{2}+m-k, 1+2m, z) + + W(k,m,z) = e^{-\frac{1}{2}z} z^{\frac{1}{2}+m} + U(\tfrac{1}{2}+m-k, 1+2m, z). + +**Examples** + +Evaluation for arbitrary real and complex arguments is supported:: + + >>> from mpmath import (mp, whitm, j, mpf, chop, diff, inf, sqrt, pi, + ... quad, exp, whitw) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> whitm(1, 1, 1) + 0.7302596799460411820509668 + >>> whitm(1, 1, -1) + (0.0 - 1.417977827655098025684246j) + >>> whitm(j, j/2, 2+3j) + (3.245477713363581112736478 - 0.822879187542699127327782j) + >>> whitm(2, 3, 100000) + 4.303985255686378497193063e+21707 + +Evaluation at zero:: + + >>> whitm(1,-1,0) + inf + >>> whitm(1,-0.5,0) + nan + >>> whitm(1,0,0) + 0.0 + +We can verify that :func:`~mpmath.whitm` numerically satisfies the +differential equation for arbitrarily chosen values:: + + >>> k = mpf(0.25) + >>> m = mpf(1.5) + >>> f = lambda z: whitm(k,m,z) + >>> for z in [-1, 2.5, 3, 1+2j]: + ... chop(diff(f,z,2) + (-0.25 + k/z + (0.25-m**2)/z**2)*f(z)) + ... + 0.0 + 0.0 + 0.0 + 0.0 + +An integral involving both :func:`~mpmath.whitm` and :func:`~mpmath.whitw`, +verifying evaluation along the real axis:: + + >>> quad(lambda x: exp(-x)*whitm(3,2,x)*whitw(1,-2,x), [0,inf]) + 3.438869842576800225207341 + >>> 128/(21*sqrt(pi)) + 3.438869842576800225207341 + +""" + +whitw = r""" +Evaluates the Whittaker function `W(k,m,z)`, which gives a second +solution to the Whittaker differential equation. (See :func:`~mpmath.whitm`.) + +**Examples** + +Evaluation for arbitrary real and complex arguments is supported:: + + >>> from mpmath import mp, whitw, j, mpf, chop, diff + >>> mp.dps = 25 + >>> mp.pretty = True + >>> whitw(1, 1, 1) + 1.19532063107581155661012 + >>> whitw(1, 1, -1) + (-0.9424875979222187313924639 - 0.2607738054097702293308689j) + >>> whitw(j, j/2, 2+3j) + (0.1782899315111033879430369 - 0.01609578360403649340169406j) + >>> whitw(2, 3, 100000) + 1.887705114889527446891274e-21705 + >>> whitw(-1, -1, 100) + 1.905250692824046162462058e-24 + +Evaluation at zero:: + + >>> for m in [-1, -0.5, 0, 0.5, 1]: + ... whitw(1, m, 0) + ... + inf + nan + 0.0 + nan + inf + +We can verify that :func:`~mpmath.whitw` numerically satisfies the +differential equation for arbitrarily chosen values:: + + >>> k = mpf(0.25) + >>> m = mpf(1.5) + >>> f = lambda z: whitw(k,m,z) + >>> for z in [-1, 2.5, 3, 1+2j]: + ... chop(diff(f,z,2) + (-0.25 + k/z + (0.25-m**2)/z**2)*f(z)) + ... + 0.0 + 0.0 + 0.0 + 0.0 + +""" + +ber = r""" +Computes the Kelvin function ber, which for real arguments gives the real part +of the Bessel J function of a rotated argument + +.. math :: + + J_n\left(x e^{3\pi i/4}\right) = \mathrm{ber}_n(x) + i \mathrm{bei}_n(x). + +The imaginary part is given by :func:`~mpmath.bei`. + +**Plots** + +.. literalinclude :: /plots/ber.py +.. image :: /plots/ber.png + +**Examples** + +Verifying the defining relation:: + + >>> from mpmath import mp, ber, besselj, root, j, bei + >>> mp.dps = 25 + >>> mp.pretty = True + >>> n, x = 2, 3.5 + >>> ber(n,x) + 1.442338852571888752631129 + >>> bei(n,x) + -0.948359035324558320217678 + >>> besselj(n, x*root(1,8,3)) + (1.442338852571888752631129 - 0.948359035324558320217678j) + +The ber and bei functions are also defined by analytic continuation +for complex arguments:: + + >>> ber(1+j, 2+3j) + (4.675445984756614424069563 - 15.84901771719130765656316j) + >>> bei(1+j, 2+3j) + (15.83886679193707699364398 + 4.684053288183046528703611j) + +""" + +bei = r""" +Computes the Kelvin function bei, which for real arguments gives the +imaginary part of the Bessel J function of a rotated argument. +See :func:`~mpmath.ber`. +""" + +ker = r""" +Computes the Kelvin function ker, which for real arguments gives the real part +of the (rescaled) Bessel K function of a rotated argument + +.. math :: + + e^{-\pi i/2} K_n\left(x e^{3\pi i/4}\right) = \mathrm{ker}_n(x) + i \mathrm{kei}_n(x). + +The imaginary part is given by :func:`~mpmath.kei`. + +**Plots** + +.. literalinclude :: /plots/ker.py +.. image :: /plots/ker.png + +**Examples** + +Verifying the defining relation:: + + >>> from mpmath import mp, ker, exp, kei, pi, j, besselk, root + >>> mp.dps = 25 + >>> mp.pretty = True + >>> n, x = 2, 4.5 + >>> ker(n,x) + 0.02542895201906369640249801 + >>> kei(n,x) + -0.02074960467222823237055351 + >>> exp(-n*pi*j/2) * besselk(n, x*root(1,8,1)) + (0.02542895201906369640249801 - 0.02074960467222823237055351j) + +The ker and kei functions are also defined by analytic continuation +for complex arguments:: + + >>> ker(1+j, 3+4j) + (1.586084268115490421090533 - 2.939717517906339193598719j) + >>> kei(1+j, 3+4j) + (-2.940403256319453402690132 - 1.585621643835618941044855j) + +""" + +kei = r""" +Computes the Kelvin function kei, which for real arguments gives the +imaginary part of the (rescaled) Bessel K function of a rotated argument. +See :func:`~mpmath.ker`. +""" + +struveh = r""" +Gives the Struve function + +.. math :: + + \,\mathbf{H}_n(z) = + \sum_{k=0}^\infty \frac{(-1)^k}{\Gamma(k+\frac{3}{2}) + \Gamma(k+n+\frac{3}{2})} {\left({\frac{z}{2}}\right)}^{2k+n+1} + +which is a solution to the Struve differential equation + +.. math :: + + z^2 f''(z) + z f'(z) + (z^2-n^2) f(z) = \frac{2 z^{n+1}}{\pi (2n-1)!!}. + +**Examples** + +Evaluation for arbitrary real and complex arguments:: + + >>> from mpmath import mp, struveh, j, sqrt, cos, pi, mpf, diff, fac2, pi + >>> mp.dps = 25 + >>> mp.pretty = True + >>> struveh(0, 3.5) + 0.3608207733778295024977797 + >>> struveh(-1, 10) + -0.255212719726956768034732 + >>> struveh(1, -100.5) + 0.5819566816797362287502246 + >>> struveh(2.5, 10000000000000) + 3153915652525200060.308937 + >>> struveh(2.5, -10000000000000) + (0.0 - 3153915652525200060.308937j) + >>> struveh(1+j, 1000000+4000000j) + (-3.066421087689197632388731e+1737173 - 1.596619701076529803290973e+1737173j) + +A Struve function of half-integer order is elementary; for example: + + >>> z = 3 + >>> struveh(0.5, 3) + 0.9167076867564138178671595 + >>> sqrt(2/(pi*z))*(1-cos(z)) + 0.9167076867564138178671595 + +Numerically verifying the differential equation:: + + >>> z = mpf(4.5) + >>> n = 3 + >>> f = lambda z: struveh(n,z) + >>> lhs = z**2*diff(f,z,2) + z*diff(f,z) + (z**2-n**2)*f(z) + >>> rhs = 2*z**(n+1)/fac2(2*n-1)/pi + >>> lhs + 17.40359302709875496632744 + >>> rhs + 17.40359302709875496632744 + +""" + +struvel = r""" +Gives the modified Struve function + +.. math :: + + \,\mathbf{L}_n(z) = -i e^{-n\pi i/2} \mathbf{H}_n(i z) + +which solves to the modified Struve differential equation + +.. math :: + + z^2 f''(z) + z f'(z) - (z^2+n^2) f(z) = \frac{2 z^{n+1}}{\pi (2n-1)!!}. + +**Examples** + +Evaluation for arbitrary real and complex arguments:: + + >>> from mpmath import mp, struvel, j, mpf, diff, fac2, pi + >>> mp.dps = 25 + >>> mp.pretty = True + >>> struvel(0, 3.5) + 7.180846515103737996249972 + >>> struvel(-1, 10) + 2670.994904980850550721511 + >>> struvel(1, -100.5) + 1.757089288053346261497686e+42 + >>> struvel(2.5, 10000000000000) + 4.160893281017115450519948e+4342944819025 + >>> struvel(2.5, -10000000000000) + (0.0 - 4.160893281017115450519948e+4342944819025j) + >>> struvel(1+j, 700j) + (-0.1721150049480079451246076 + 0.1240770953126831093464055j) + >>> struvel(1+j, 1000000+4000000j) + (-2.973341637511505389128708e+434290 - 5.164633059729968297147448e+434290j) + +Numerically verifying the differential equation:: + + >>> z = mpf(3.5) + >>> n = 3 + >>> f = lambda z: struvel(n,z) + >>> lhs = z**2*diff(f,z,2) + z*diff(f,z) - (z**2+n**2)*f(z) + >>> rhs = 2*z**(n+1)/fac2(2*n-1)/pi + >>> lhs + 6.368850306060678353018165 + >>> rhs + 6.368850306060678353018165 +""" + +appellf1 = r""" +Gives the Appell F1 hypergeometric function of two variables, + +.. 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!}. + +This series is only generally convergent when `|x| < 1` and `|y| < 1`, +although :func:`~mpmath.appellf1` can evaluate an analytic continuation +with respecto to either variable, and sometimes both. + +**Examples** + +Evaluation is supported for real and complex parameters:: + + >>> from mpmath import (mp, appellf1, hyp2f1, chop, diff, mpmathify, + ... quad, ellipe, re, pi, mpf, sin, sqrt, j) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> appellf1(1,0,0.5,1,0.5,0.25) + 1.154700538379251529018298 + >>> appellf1(1,1+j,0.5,1,0.5,0.5j) + (1.138403860350148085179415 + 1.510544741058517621110615j) + +For some integer parameters, the F1 series reduces to a polynomial:: + + >>> appellf1(2,-4,-3,1,2,5) + -816.0 + >>> appellf1(-5,1,2,1,4,5) + -20528.0 + +The analytic continuation with respect to either `x` or `y`, +and sometimes with respect to both, can be evaluated:: + + >>> appellf1(2,3,4,5,100,0.5) + (0.0006231042714165329279738662 + 0.0000005769149277148425774499857j) + >>> appellf1('1.1', '0.3', '0.2+2j', '0.4', '0.2', 1.5+3j) + (-0.1782604566893954897128702 + 0.002472407104546216117161499j) + >>> appellf1(1,2,3,4,10,12) + -0.07122993830066776374929313 + +For certain arguments, F1 reduces to an ordinary hypergeometric function:: + + >>> appellf1(1,2,3,5,0.5,0.25) + 1.547902270302684019335555 + >>> 4*hyp2f1(1,2,5,'1/3')/3 + 1.547902270302684019335555 + >>> appellf1(1,2,3,4,0,1.5) + (-1.717202506168937502740238 - 2.792526803190927323077905j) + >>> hyp2f1(1,3,4,1.5) + (-1.717202506168937502740238 - 2.792526803190927323077905j) + +The F1 function satisfies a system of partial differential equations:: + + >>> a,b1,b2,c,x,y = map(mpf, [1,0.5,0.25,1.125,0.25,-0.25]) + >>> F = lambda x,y: appellf1(a,b1,b2,c,x,y) + >>> chop(x*(1-x)*diff(F,(x,y),(2,0)) + + ... y*(1-x)*diff(F,(x,y),(1,1)) + + ... (c-(a+b1+1)*x)*diff(F,(x,y),(1,0)) - + ... b1*y*diff(F,(x,y),(0,1)) - + ... a*b1*F(x,y)) + 0.0 + >>> + >>> chop(y*(1-y)*diff(F,(x,y),(0,2)) + + ... x*(1-y)*diff(F,(x,y),(1,1)) + + ... (c-(a+b2+1)*y)*diff(F,(x,y),(0,1)) - + ... b2*x*diff(F,(x,y),(1,0)) - + ... a*b2*F(x,y)) + 0.0 + +The Appell F1 function allows for closed-form evaluation of various +integrals, such as any integral of the form +`\int x^r (x+a)^p (x+b)^q dx`:: + + >>> def integral(a,b,p,q,r,x1,x2): + ... a,b,p,q,r,x1,x2 = map(mpmathify, [a,b,p,q,r,x1,x2]) + ... f = lambda x: x**r * (x+a)**p * (x+b)**q + ... def F(x): + ... v = x**(r+1)/(r+1) * (a+x)**p * (b+x)**q + ... v *= (1+x/a)**(-p) + ... v *= (1+x/b)**(-q) + ... v *= appellf1(r+1,-p,-q,2+r,-x/a,-x/b) + ... return v + ... print("Num. quad: %s" % quad(f, [x1,x2])) + ... print("Appell F1: %s" % (F(x2)-F(x1))) + ... + >>> integral('1/5','4/3','-2','3','1/2',0,1) + Num. quad: 9.073335358785776206576981 + Appell F1: 9.073335358785776206576981 + >>> integral('3/2','4/3','-2','3','1/2',0,1) + Num. quad: 1.092829171999626454344678 + Appell F1: 1.092829171999626454344678 + >>> integral('3/2','4/3','-2','3','1/2',12,25) + Num. quad: 1106.323225040235116498927 + Appell F1: 1106.323225040235116498927 + +Also incomplete elliptic integrals fall into this category [1]:: + + >>> def E(z, m): + ... if (pi/2).ae(z): + ... return ellipe(m) + ... return 2*round(re(z)/pi)*ellipe(m) + mpf(-1)**round(re(z)/pi)*\ + ... sin(z)*appellf1(0.5,0.5,-0.5,1.5,sin(z)**2,m*sin(z)**2) + ... + >>> z, m = 1, 0.5 + >>> E(z,m) + 0.9273298836244400669659042 + >>> quad(lambda t: sqrt(1-m*sin(t)**2), [0,pi/4,3*pi/4,z]) + 0.9273298836244400669659042 + >>> z, m = 3, 2 + >>> E(z,m) + (1.057495752337234229715836 + 1.198140234735592207439922j) + >>> quad(lambda t: sqrt(1-m*sin(t)**2), [0,pi/4,3*pi/4,z]) + (1.057495752337234229715836 + 1.198140234735592207439922j) + +**References** + +1. [WolframFunctions]_ http://functions.wolfram.com/EllipticIntegrals/EllipticE2/26/01/ +2. [SrivastavaKarlsson]_ +3. [CabralRosetti]_ +4. [Vidunas]_ +5. [Slater]_ + +""" + +angerj = r""" +Gives the Anger function + +.. math :: + + \mathbf{J}_{\nu}(z) = \frac{1}{\pi} + \int_0^{\pi} \cos(\nu t - z \sin t) dt + +which is an entire function of both the parameter `\nu` and +the argument `z`. It solves the inhomogeneous Bessel differential +equation + +.. math :: + + f''(z) + \frac{1}{z}f'(z) + \left(1-\frac{\nu^2}{z^2}\right) f(z) + = \frac{(z-\nu)}{\pi z^2} \sin(\pi \nu). + +**Examples** + +Evaluation for real and complex parameter and argument:: + + >>> from mpmath import (mp, angerj, besselj, mpf, diff, sinpi, quad, + ... sin, cos, pi) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> angerj(2,3) + 0.4860912605858910769078311 + >>> angerj(-3+4j, 2+5j) + (-5033.358320403384472395612 + 585.8011892476145118551756j) + >>> angerj(3.25, 1e6j) + (4.630743639715893346570743e+434290 - 1.117960409887505906848456e+434291j) + >>> angerj(-1.5, 1e6) + 0.0002795719747073879393087011 + +The Anger function coincides with the Bessel J-function when `\nu` +is an integer:: + + >>> angerj(1,3) + 0.3390589585259364589255146 + >>> besselj(1,3) + 0.3390589585259364589255146 + >>> angerj(1.5,3) + 0.4088969848691080859328847 + >>> besselj(1.5,3) + 0.4777182150870917715515015 + +Verifying the differential equation:: + + >>> v,z = mpf(2.25), 0.75 + >>> f = lambda z: angerj(v,z) + >>> diff(f,z,2) + diff(f,z)/z + (1-(v/z)**2)*f(z) + -0.6002108774380707130367995 + >>> (z-v)/(pi*z**2) * sinpi(v) + -0.6002108774380707130367995 + +Verifying the integral representation:: + + >>> angerj(v,z) + 0.1145380759919333180900501 + >>> quad(lambda t: cos(v*t-z*sin(t))/pi, [0,pi]) + 0.1145380759919333180900501 + +**References** + +1. [DLMF]_ section 11.10: Anger-Weber Functions +""" + +webere = r""" +Gives the Weber function + +.. math :: + + \mathbf{E}_{\nu}(z) = \frac{1}{\pi} + \int_0^{\pi} \sin(\nu t - z \sin t) dt + +which is an entire function of both the parameter `\nu` and +the argument `z`. It solves the inhomogeneous Bessel differential +equation + +.. math :: + + f''(z) + \frac{1}{z}f'(z) + \left(1-\frac{\nu^2}{z^2}\right) f(z) + = -\frac{1}{\pi z^2} (z+\nu+(z-\nu)\cos(\pi \nu)). + +**Examples** + +Evaluation for real and complex parameter and argument:: + + >>> from mpmath import mp, webere, struveh, pi, diff, cospi, mpf, sin, quad + >>> mp.dps = 25 + >>> mp.pretty = True + >>> webere(2,3) + -0.1057668973099018425662646 + >>> webere(-3+4j, 2+5j) + (-585.8081418209852019290498 - 5033.314488899926921597203j) + >>> webere(3.25, 1e6j) + (-1.117960409887505906848456e+434291 - 4.630743639715893346570743e+434290j) + >>> webere(3.25, 1e6) + -0.00002812518265894315604914453 + +Up to addition of a rational function of `z`, the Weber function coincides +with the Struve H-function when `\nu` is an integer:: + + >>> webere(1,3) + -0.3834897968188690177372881 + >>> 2/pi-struveh(1,3) + -0.3834897968188690177372881 + >>> webere(5,3) + 0.2009680659308154011878075 + >>> 26/(35*pi)-struveh(5,3) + 0.2009680659308154011878075 + +Verifying the differential equation:: + + >>> v,z = mpf(2.25), 0.75 + >>> f = lambda z: webere(v,z) + >>> diff(f,z,2) + diff(f,z)/z + (1-(v/z)**2)*f(z) + -1.097441848875479535164627 + >>> -(z+v+(z-v)*cospi(v))/(pi*z**2) + -1.097441848875479535164627 + +Verifying the integral representation:: + + >>> webere(v,z) + 0.1486507351534283744485421 + >>> quad(lambda t: sin(v*t-z*sin(t))/pi, [0,pi]) + 0.1486507351534283744485421 + +**References** + +1. [DLMF]_ section 11.10: Anger-Weber Functions +""" + +lommels1 = r""" +Gives the Lommel function `s_{\mu,\nu}` or `s^{(1)}_{\mu,\nu}` + +.. math :: + + s_{\mu,\nu}(z) = \frac{z^{\mu+1}}{(\mu-\nu+1)(\mu+\nu+1)} + \,_1F_2\left(1; \frac{\mu-\nu+3}{2}, \frac{\mu+\nu+3}{2}; + -\frac{z^2}{4} \right) + +which solves the inhomogeneous Bessel equation + +.. math :: + + z^2 f''(z) + z f'(z) + (z^2-\nu^2) f(z) = z^{\mu+1}. + +A second solution is given by :func:`~mpmath.lommels2`. + +**Plots** + +.. literalinclude :: /plots/lommels1.py +.. image :: /plots/lommels1.png + +**Examples** + +An integral representation:: + + >>> from mpmath import (mp, mpf, lommels1, quad, bessely, besselj, pi, + ... gamma, sqrt, power, struveh, diff) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> u,v,z = 0.25, 0.125, mpf(0.75) + >>> lommels1(u,v,z) + 0.4276243877565150372999126 + >>> (bessely(v,z)*quad(lambda t: t**u*besselj(v,t), [0,z]) - \ + ... besselj(v,z)*quad(lambda t: t**u*bessely(v,t), [0,z]))*(pi/2) + 0.4276243877565150372999126 + +A special value:: + + >>> lommels1(v,v,z) + 0.5461221367746048054932553 + >>> gamma(v+0.5)*sqrt(pi)*power(2,v-1)*struveh(v,z) + 0.5461221367746048054932553 + +Verifying the differential equation:: + + >>> f = lambda z: lommels1(u,v,z) + >>> z**2*diff(f,z,2) + z*diff(f,z) + (z**2-v**2)*f(z) + 0.6979536443265746992059141 + >>> z**(u+1) + 0.6979536443265746992059141 + +**References** + +1. [GradshteynRyzhik]_ +2. [Weisstein]_ http://mathworld.wolfram.com/LommelFunction.html +""" + +lommels2 = r""" +Gives the second Lommel function `S_{\mu,\nu}` or `s^{(2)}_{\mu,\nu}` + +.. math :: + + S_{\mu,\nu}(z) = s_{\mu,\nu}(z) + 2^{\mu-1} + \Gamma\left(\tfrac{1}{2}(\mu-\nu+1)\right) + \Gamma\left(\tfrac{1}{2}(\mu+\nu+1)\right) \times + + \left[\sin(\tfrac{1}{2}(\mu-\nu)\pi) J_{\nu}(z) - + \cos(\tfrac{1}{2}(\mu-\nu)\pi) Y_{\nu}(z) + \right] + +which solves the same differential equation as +:func:`~mpmath.lommels1`. + +**Plots** + +.. literalinclude :: /plots/lommels2.py +.. image :: /plots/lommels2.png + +**Examples** + +For large `|z|`, `S_{\mu,\nu} \sim z^{\mu-1}`:: + + >>> from mpmath import (mp, lommels2, power, struveh, bessely, power, + ... sqrt, pi, gamma, diff, mpf) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> lommels2(10,2,30000) + 1.968299831601008419949804e+40 + >>> power(30000,9) + 1.9683e+40 + +A special value:: + + >>> u,v,z = 0.5, 0.125, mpf(0.75) + >>> lommels2(v,v,z) + 0.9589683199624672099969765 + >>> (struveh(v,z)-bessely(v,z))*power(2,v-1)*sqrt(pi)*gamma(v+0.5) + 0.9589683199624672099969765 + +Verifying the differential equation:: + + >>> f = lambda z: lommels2(u,v,z) + >>> z**2*diff(f,z,2) + z*diff(f,z) + (z**2-v**2)*f(z) + 0.6495190528383289850727924 + >>> z**(u+1) + 0.6495190528383289850727924 + +**References** + +1. [GradshteynRyzhik]_ +2. [Weisstein]_ http://mathworld.wolfram.com/LommelFunction.html +""" + +appellf2 = r""" +Gives the Appell F2 hypergeometric function of two variables + +.. math :: + + F_2(a,b_1,b_2,c_1,c_2,x,y) = \sum_{m=0}^{\infty} \sum_{n=0}^{\infty} + \frac{(a)_{m+n} (b_1)_m (b_2)_n}{(c_1)_m (c_2)_n} + \frac{x^m y^n}{m! n!}. + +The series is generally absolutely convergent for `|x| + |y| < 1`. + +**Examples** + +Evaluation for real and complex arguments:: + + >>> from mpmath import mp, appellf2, chop, mpf, j, diff + >>> mp.dps = 25 + >>> mp.pretty = True + >>> appellf2(1,2,3,4,5,0.25,0.125) + 1.257417193533135344785602 + >>> appellf2(1,-3,-4,2,3,2,3) + -42.8 + >>> appellf2(0.5,0.25,-0.25,2,3,0.25j,0.25) + (0.9880539519421899867041719 + 0.01497616165031102661476978j) + >>> chop(appellf2(1,1+j,1-j,3j,-3j,0.25,0.25)) + 1.201311219287411337955192 + >>> appellf2(1,1,1,4,6,0.125,16) + (-0.09455532250274744282125152 - 0.7647282253046207836769297j) + +A transformation formula:: + + >>> a,b1,b2,c1,c2,x,y = map(mpf, [1,2,0.5,0.25,1.625,-0.125,0.125]) + >>> appellf2(a,b1,b2,c1,c2,x,y) + 0.2299211717841180783309688 + >>> (1-x)**(-a)*appellf2(a,c1-b1,b2,c1,c2,x/(x-1),y/(1-x)) + 0.2299211717841180783309688 + +A system of partial differential equations satisfied by F2:: + + >>> a,b1,b2,c1,c2,x,y = map(mpf, [1,0.5,0.25,1.125,1.5,0.0625,-0.0625]) + >>> F = lambda x,y: appellf2(a,b1,b2,c1,c2,x,y) + >>> chop(x*(1-x)*diff(F,(x,y),(2,0)) - + ... x*y*diff(F,(x,y),(1,1)) + + ... (c1-(a+b1+1)*x)*diff(F,(x,y),(1,0)) - + ... b1*y*diff(F,(x,y),(0,1)) - + ... a*b1*F(x,y)) + 0.0 + >>> chop(y*(1-y)*diff(F,(x,y),(0,2)) - + ... x*y*diff(F,(x,y),(1,1)) + + ... (c2-(a+b2+1)*y)*diff(F,(x,y),(0,1)) - + ... b2*x*diff(F,(x,y),(1,0)) - + ... a*b2*F(x,y)) + 0.0 + +**References** + +See references for :func:`~mpmath.appellf1`. +""" + +appellf3 = r""" +Gives the Appell F3 hypergeometric function of two variables + +.. math :: + + F_3(a_1,a_2,b_1,b_2,c,x,y) = \sum_{m=0}^{\infty} \sum_{n=0}^{\infty} + \frac{(a_1)_m (a_2)_n (b_1)_m (b_2)_n}{(c)_{m+n}} + \frac{x^m y^n}{m! n!}. + +The series is generally absolutely convergent for `|x| < 1, |y| < 1`. + +**Examples** + +Evaluation for various parameters and variables:: + + >>> from mpmath import (mp, appellf3, hyp2f1, j, mpf, polylog, gammaprod, + ... hyp3f2, chop, diff) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> appellf3(1,2,3,4,5,0.5,0.25) + 2.221557778107438938158705 + >>> appellf3(1,2,3,4,5,6,0) + (-0.5189554589089861284537389 - 0.1454441043328607980769742j) + >>> hyp2f1(1,3,5,6) + (-0.5189554589089861284537389 - 0.1454441043328607980769742j) + >>> appellf3(1,-2,-3,1,1,4,6) + -17.4 + >>> appellf3(1,2,-3,1,1,4,6) + (17.7876136773677356641825 + 19.54768762233649126154534j) + >>> appellf3(1,2,-3,1,1,6,4) + (85.02054175067929402953645 + 148.4402528821177305173599j) + >>> chop(appellf3(1+j,2,1-j,2,3,0.25,0.25)) + 1.719992169545200286696007 + +Many transformations and evaluations for special combinations +of the parameters are possible, e.g.: + + >>> a,b,c,x,y = map(mpf, [0.5,0.25,0.125,0.125,-0.125]) + >>> appellf3(a,c-a,b,c-b,c,x,y) + 1.093432340896087107444363 + >>> (1-y)**(a+b-c)*hyp2f1(a,b,c,x+y-x*y) + 1.093432340896087107444363 + >>> x**2*appellf3(1,1,1,1,3,x,-x) + 0.01568646277445385390945083 + >>> polylog(2,x**2) + 0.01568646277445385390945083 + >>> a1,a2,b1,b2,c,x = map(mpf, [0.5,0.25,0.125,0.5,4.25,0.125]) + >>> appellf3(a1,a2,b1,b2,c,x,1) + 1.03947361709111140096947 + >>> gammaprod([c,c-a2-b2],[c-a2,c-b2])*hyp3f2(a1,b1,c-a2-b2,c-a2,c-b2,x) + 1.03947361709111140096947 + +The Appell F3 function satisfies a pair of partial +differential equations:: + + >>> a1,a2,b1,b2,c,x,y = map(mpf, [0.5,0.25,0.125,0.5,0.625,0.0625,-0.0625]) + >>> F = lambda x,y: appellf3(a1,a2,b1,b2,c,x,y) + >>> chop(x*(1-x)*diff(F,(x,y),(2,0)) + + ... y*diff(F,(x,y),(1,1)) + + ... (c-(a1+b1+1)*x)*diff(F,(x,y),(1,0)) - + ... a1*b1*F(x,y)) + 0.0 + >>> chop(y*(1-y)*diff(F,(x,y),(0,2)) + + ... x*diff(F,(x,y),(1,1)) + + ... (c-(a2+b2+1)*y)*diff(F,(x,y),(0,1)) - + ... a2*b2*F(x,y)) + 0.0 + +**References** + +See references for :func:`~mpmath.appellf1`. +""" + +appellf4 = r""" +Gives the Appell F4 hypergeometric function of two variables + +.. math :: + + F_4(a,b,c_1,c_2,x,y) = \sum_{m=0}^{\infty} \sum_{n=0}^{\infty} + \frac{(a)_{m+n} (b)_{m+n}}{(c_1)_m (c_2)_n} + \frac{x^m y^n}{m! n!}. + +The series is generally absolutely convergent for +`\sqrt{|x|} + \sqrt{|y|} < 1`. + +**Examples** + +Evaluation for various parameters and arguments:: + + >>> from mpmath import mp, appellf4, hyp2f1, diff, chop, mpf + >>> mp.dps = 25 + >>> mp.pretty = True + >>> appellf4(1,1,2,2,0.25,0.125) + 1.286182069079718313546608 + >>> appellf4(-2,-3,4,5,4,5) + 34.8 + >>> appellf4(5,4,2,3,0.25j,-0.125j) + (-0.2585967215437846642163352 + 2.436102233553582711818743j) + +Reduction to `\,_2F_1` in a special case:: + + >>> a,b,c,x,y = map(mpf, [0.5,0.25,0.125,0.125,-0.125]) + >>> appellf4(a,b,c,a+b-c+1,x*(1-y),y*(1-x)) + 1.129143488466850868248364 + >>> hyp2f1(a,b,c,x)*hyp2f1(a,b,a+b-c+1,y) + 1.129143488466850868248364 + +A system of partial differential equations satisfied by F4:: + + >>> a,b,c1,c2,x,y = map(mpf, [1,0.5,0.25,1.125,0.0625,-0.0625]) + >>> F = lambda x,y: appellf4(a,b,c1,c2,x,y) + >>> chop(x*(1-x)*diff(F,(x,y),(2,0)) - + ... y**2*diff(F,(x,y),(0,2)) - + ... 2*x*y*diff(F,(x,y),(1,1)) + + ... (c1-(a+b+1)*x)*diff(F,(x,y),(1,0)) - + ... ((a+b+1)*y)*diff(F,(x,y),(0,1)) - + ... a*b*F(x,y)) + 0.0 + >>> chop(y*(1-y)*diff(F,(x,y),(0,2)) - + ... x**2*diff(F,(x,y),(2,0)) - + ... 2*x*y*diff(F,(x,y),(1,1)) + + ... (c2-(a+b+1)*y)*diff(F,(x,y),(0,1)) - + ... ((a+b+1)*x)*diff(F,(x,y),(1,0)) - + ... a*b*F(x,y)) + 0.0 + +**References** + +See references for :func:`~mpmath.appellf1`. +""" + +zeta = r""" +Computes the Riemann zeta function + +.. math :: + + \zeta(s) = 1+\frac{1}{2^s}+\frac{1}{3^s}+\frac{1}{4^s}+\ldots + +or, with `a \ne 1`, the more general Hurwitz zeta function + +.. math :: + + \zeta(s,a) = \sum_{k=0}^\infty \frac{1}{(a+k)^s}. + +Optionally, ``zeta(s, a, n)`` computes the `n`-th derivative with +respect to `s`, + +.. math :: + + \zeta^{(n)}(s,a) = (-1)^n \sum_{k=0}^\infty \frac{\log^n(a+k)}{(a+k)^s}. + +Although these series only converge for `\Re(s) > 1`, the Riemann and Hurwitz +zeta functions are defined through analytic continuation for arbitrary +complex `s \ne 1` (`s = 1` is a pole). + +The implementation uses three algorithms: the Borwein algorithm for +the Riemann zeta function when `s` is close to the real line; +the Riemann-Siegel formula for the Riemann zeta function when `s` is +large imaginary, and Euler-Maclaurin summation in all other cases. +The reflection formula for `\Re(s) < 0` is implemented in some cases. +The algorithm can be chosen with ``method = 'borwein'``, +``method='riemann-siegel'`` or ``method = 'euler-maclaurin'``. + +The parameter `a` is usually a rational number `a = p/q`, and may be specified +as such by passing an integer tuple `(p, q)`. Evaluation is supported for +arbitrary complex `a`, but may be slow and/or inaccurate when `\Re(s) < 0` for +nonrational `a` or when computing derivatives. + +**Examples** + +Some values of the Riemann zeta function:: + + >>> from mpmath import (mp, zeta, pi, inf, euler, j, zetazero, findroot, + ... chop, catalan, psi, ln, loggamma, fac, mpf, diff, + ... nsum) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> zeta(2) + 1.644934066848226436472415 + >>> pi**2 / 6 + 1.644934066848226436472415 + >>> zeta(0) + -0.5 + >>> zeta(-1) + -0.08333333333333333333333333 + >>> zeta(-2) + 0.0 + +For large positive `s`, `\zeta(s)` rapidly approaches 1:: + + >>> zeta(50) + 1.000000000000000888178421 + >>> zeta(100) + 1.0 + >>> zeta(inf) + 1.0 + >>> 1-sum((zeta(k)-1)/k for k in range(2,85)) + 0.5772156649015328606065121 + >>> +euler + 0.5772156649015328606065121 + >>> nsum(lambda k: zeta(k)-1, [2, inf]) + 1.0 + +Evaluation is supported for complex `s` and `a`: + + >>> zeta(-3+4j) + (-0.03373057338827757067584698 + 0.2774499251557093745297677j) + >>> zeta(2+3j, -1+j) + (389.6841230140842816370741 + 295.2674610150305334025962j) + +The Riemann zeta function has so-called nontrivial zeros on +the critical line `s = 1/2 + it`:: + + >>> findroot(zeta, 0.5+14j) + (0.5 + 14.13472514173469379045725j) + >>> zetazero(1) + (0.5 + 14.13472514173469379045725j) + >>> findroot(zeta, 0.5+21j) + (0.5 + 21.02203963877155499262848j) + >>> zetazero(2) + (0.5 + 21.02203963877155499262848j) + >>> findroot(zeta, 0.5+25j) + (0.5 + 25.01085758014568876321379j) + >>> zetazero(3) + (0.5 + 25.01085758014568876321379j) + >>> chop(zeta(zetazero(10))) + 0.0 + +Evaluation on and near the critical line is supported for large +heights `t` by means of the Riemann-Siegel formula (currently +for `a = 1`, `n \le 4`):: + + >>> zeta(0.5+100000j) + (1.073032014857753132114076 + 5.780848544363503984261041j) + >>> zeta(0.75+1000000j) + (0.9535316058375145020351559 + 0.9525945894834273060175651j) + >>> zeta(0.5+10000000j) + (11.45804061057709254500227 - 8.643437226836021723818215j) + >>> zeta(0.5+100000000j, derivative=1) + (51.12433106710194942681869 + 43.87221167872304520599418j) + >>> zeta(0.5+100000000j, derivative=2) + (-444.2760822795430400549229 - 896.3789978119185981665403j) + >>> zeta(0.5+100000000j, derivative=3) + (3230.72682687670422215339 + 14374.36950073615897616781j) + >>> zeta(0.5+100000000j, derivative=4) + (-11967.35573095046402130602 - 218945.7817789262839266148j) + >>> zeta(1+10000000j) # off the line + (2.859846483332530337008882 + 0.491808047480981808903986j) + >>> zeta(1+10000000j, derivative=1) + (-4.333835494679647915673205 - 0.08405337962602933636096103j) + >>> zeta(1+10000000j, derivative=4) + (453.2764822702057701894278 - 581.963625832768189140995j) + +For investigation of the zeta function zeros, the Riemann-Siegel +Z-function is often more convenient than working with the Riemann +zeta function directly (see :func:`~mpmath.siegelz`). + +Some values of the Hurwitz zeta function:: + + >>> zeta(2, 3) + 0.3949340668482264364724152 + >>> -5./4 + pi**2/6 + 0.3949340668482264364724152 + >>> zeta(2, (3,4)) + 2.541879647671606498397663 + >>> pi**2 - 8*catalan + 2.541879647671606498397663 + +For positive integer values of `s`, the Hurwitz zeta function is +equivalent to a polygamma function (except for a normalizing factor):: + + >>> zeta(4, (1,5)) + 625.5408324774542966919938 + >>> psi(3, '1/5')/6 + 625.5408324774542966919938 + +Evaluation of derivatives:: + + >>> zeta(0, 3+4j, 1) + (-2.675565317808456852310934 + 4.742664438034657928194889j) + >>> loggamma(3+4j) - ln(2*pi)/2 + (-2.675565317808456852310934 + 4.742664438034657928194889j) + >>> zeta(2, 1, 20) + 2432902008176640000.000242 + >>> zeta(3+4j, 5.5+2j, 4) + (-0.140075548947797130681075 - 0.3109263360275413251313634j) + >>> zeta(0.5+100000j, 1, 4) + (-10407.16081931495861539236 + 13777.78669862804508537384j) + >>> zeta(-100+0.5j, (1,3), derivative=4) + (4.007180821099823942702249e+79 + 4.916117957092593868321778e+78j) + +Generating a Taylor series at `s = 2` using derivatives:: + + >>> for k in range(11): print("%s * (s-2)^%i" % (zeta(2,1,k)/fac(k), k)) + ... + 1.644934066848226436472415 * (s-2)^0 + -0.9375482543158437537025741 * (s-2)^1 + 0.9946401171494505117104293 * (s-2)^2 + -1.000024300473840810940657 * (s-2)^3 + 1.000061933072352565457512 * (s-2)^4 + -1.000006869443931806408941 * (s-2)^5 + 1.000000173233769531820592 * (s-2)^6 + -0.9999999569989868493432399 * (s-2)^7 + 0.9999999937218844508684206 * (s-2)^8 + -0.9999999996355013916608284 * (s-2)^9 + 1.000000000004610645020747 * (s-2)^10 + +Evaluation at zero and for negative integer `s`:: + + >>> zeta(0, 10) + -9.5 + >>> zeta(-2, (2,3)) + 0.01234567901234567901234568 + >>> mpf(1)/81 + 0.01234567901234567901234568 + >>> zeta(-3+4j, (5,4)) + (0.2899236037682695182085988 + 0.06561206166091757973112783j) + >>> zeta(-3.25, 1/pi) + -0.0005117269627574430494396877 + >>> zeta(-3.5, pi, 1) + 11.156360390440003294709 + >>> zeta(-100.5, (8,3)) + -4.68162300487989766727122e+77 + >>> zeta(-10.5, (-8,3)) + (-0.01521913704446246609237979 + 29907.72510874248161608216j) + >>> zeta(-1000.5, (-8,3)) + (1.031911949062334538202567e+1770 + 1.519555750556794218804724e+426j) + >>> zeta(-1+j, 3+4j) + (-16.32988355630802510888631 - 22.17706465801374033261383j) + >>> zeta(-1+j, 3+4j, 2) + (32.48985276392056641594055 - 51.11604466157397267043655j) + >>> diff(lambda s: zeta(s, 3+4j), -1+j, 2) + (32.48985276392056641594055 - 51.11604466157397267043655j) + +**References** + +1. [Weisstein]_ http://mathworld.wolfram.com/RiemannZetaFunction.html + +2. [Weisstein]_ http://mathworld.wolfram.com/HurwitzZetaFunction.html + +3. [BorweinZeta]_ + +""" + +dirichlet = r""" +Evaluates the Dirichlet L-function + +.. math :: + + L(s,\chi) = \sum_{k=1}^\infty \frac{\chi(k)}{k^s}. + +where `\chi` is a periodic sequence of length `q` which should be supplied +in the form of a list `[\chi(0), \chi(1), \ldots, \chi(q-1)]`. +Strictly, `\chi` should be a Dirichlet character, but any periodic +sequence will work. + +For example, ``dirichlet(s, [1])`` gives the ordinary +Riemann zeta function and ``dirichlet(s, [-1,1])`` gives +the alternating zeta function (Dirichlet eta function). + +Also the derivative with respect to `s` (currently only a first +derivative) can be evaluated. + +**Examples** + +The ordinary Riemann zeta function:: + + >>> from mpmath import (mp, zeta, dirichlet, ln, pi, catalan, diff, log, + ... gamma, log, sqrt, euler, nsum, inf, ln2) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> dirichlet(3, [1]) + 1.202056903159594285399738 + >>> zeta(3) + 1.202056903159594285399738 + >>> dirichlet(1, [1]) + inf + +The alternating zeta function:: + + >>> dirichlet(1, [-1,1]) + 0.6931471805599453094172321 + >>> ln(2) + 0.6931471805599453094172321 + +The following defines the Dirichlet beta function +`\beta(s) = \sum_{k=0}^\infty \frac{(-1)^k}{(2k+1)^s}` and verifies +several values of this function:: + + >>> B = lambda s, d=0: dirichlet(s, [0, 1, 0, -1], d) + >>> B(0) + 0.5 + >>> 1./2 + 0.5 + >>> B(1) + 0.7853981633974483096156609 + >>> pi/4 + 0.7853981633974483096156609 + >>> B(2) + 0.9159655941772190150546035 + >>> +catalan + 0.9159655941772190150546035 + >>> B(2,1) + 0.08158073611659279510291217 + >>> diff(B, 2) + 0.08158073611659279510291217 + >>> B(-1,1) + 0.5831218080616375602767689 + >>> 2*catalan/pi + 0.5831218080616375602767689 + >>> B(0,1) + 0.3915943927068367764719453 + >>> log(gamma(0.25)**2/(2*pi*sqrt(2))) + 0.3915943927068367764719454 + >>> B(1,1) + 0.1929013167969124293631898 + >>> 0.25*pi*(euler+2*ln2+3*ln(pi)-4*ln(gamma(0.25))) + 0.1929013167969124293631898 + +A custom L-series of period 3:: + + >>> dirichlet(2, [2,0,1]) + 0.7059715047839078092146831 + >>> 2*nsum(lambda k: (3*k)**-2, [1,inf]) + \ + ... nsum(lambda k: (3*k+2)**-2, [0,inf]) + 0.7059715047839078092146831 + +""" + +coulombf = r""" +Calculates the regular Coulomb wave function + +.. math :: + + F_l(\eta,z) = C_l(\eta) z^{l+1} e^{-iz} \,_1F_1(l+1-i\eta, 2l+2, 2iz) + +where the normalization constant `C_l(\eta)` is as calculated by +:func:`~mpmath.coulombc`. This function solves the differential equation + +.. math :: + + f''(z) + \left(1-\frac{2\eta}{z}-\frac{l(l+1)}{z^2}\right) f(z) = 0. + +A second linearly independent solution is given by the irregular +Coulomb wave function `G_l(\eta,z)` (see :func:`~mpmath.coulombg`) +and thus the general solution is +`f(z) = C_1 F_l(\eta,z) + C_2 G_l(\eta,z)` for arbitrary +constants `C_1`, `C_2`. +Physically, the Coulomb wave functions give the radial solution +to the Schrodinger equation for a point particle in a `1/z` potential; `z` is +then the radius and `l`, `\eta` are quantum numbers. + +The Coulomb wave functions with real parameters are defined +in Abramowitz & Stegun, section 14. However, all parameters are permitted +to be complex in this implementation (see references). + +**Plots** + +.. literalinclude :: /plots/coulombf.py +.. image :: /plots/coulombf.png +.. literalinclude :: /plots/coulombf_c.py +.. image :: /plots/coulombf_c.png + +**Examples** + +Evaluation is supported for arbitrary magnitudes of `z`:: + + >>> from mpmath import (mp, coulombf, mpf, chop, diff, coulombg, sqrt, + ... exp, j, quad, coulombc, fac, inf) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> coulombf(2, 1.5, 3.5) + 0.4080998961088761187426445 + >>> coulombf(-2, 1.5, 3.5) + 0.7103040849492536747533465 + >>> coulombf(2, 1.5, '1e-10') + 4.143324917492256448770769e-33 + >>> coulombf(2, 1.5, 1000) + 0.4482623140325567050716179 + >>> coulombf(2, 1.5, 10**10) + -0.066804196437694360046619 + +Verifying the differential equation:: + + >>> l, eta, z = 2, 3, mpf(2.75) + >>> A, B = 1, 2 + >>> f = lambda z: A*coulombf(l,eta,z) + B*coulombg(l,eta,z) + >>> chop(diff(f,z,2) + (1-2*eta/z - l*(l+1)/z**2)*f(z)) + 0.0 + +A Wronskian relation satisfied by the Coulomb wave functions:: + + >>> l = 2 + >>> eta = 1.5 + >>> F = lambda z: coulombf(l,eta,z) + >>> G = lambda z: coulombg(l,eta,z) + >>> for z in [3.5, -1, 2+3j]: + ... chop(diff(F,z)*G(z) - F(z)*diff(G,z)) + ... + 1.0 + 1.0 + 1.0 + +Another Wronskian relation:: + + >>> F = coulombf + >>> G = coulombg + >>> for z in [3.5, -1, 2+3j]: + ... chop(F(l-1,eta,z)*G(l,eta,z)-F(l,eta,z)*G(l-1,eta,z) - l/sqrt(l**2+eta**2)) + ... + 0.0 + 0.0 + 0.0 + +An integral identity connecting the regular and irregular wave functions:: + + >>> l, eta, z = 4+j, 2-j, 5+2j + >>> coulombf(l,eta,z) + j*coulombg(l,eta,z) + (0.7997977752284033239714479 + 0.9294486669502295512503127j) + >>> g = lambda t: exp(-t)*t**(l-j*eta)*(t+2*j*z)**(l+j*eta) + >>> j*exp(-j*z)*z**(-l)/fac(2*l+1)/coulombc(l,eta)*quad(g, [0,inf]) + (0.7997977752284033239714479 + 0.9294486669502295512503127j) + +Some test case with complex parameters, taken from Michel [2]:: + + >>> mp.dps = 15 + >>> coulombf(1+0.1j, 50+50j, 100.156) + (-1.02107292320897e+15 - 2.83675545731519e+15j) + >>> coulombg(1+0.1j, 50+50j, 100.156) + (2.83675545731519e+15 - 1.02107292320897e+15j) + >>> coulombf(1e-5j, 10+1e-5j, 0.1+1e-6j) + (4.30566371247811e-14 - 9.03347835361657e-19j) + >>> coulombg(1e-5j, 10+1e-5j, 0.1+1e-6j) + (778709182061.134 + 18418936.2660553j) + +The following reproduces a table in Abramowitz & Stegun, at twice +the precision:: + + >>> mp.dps = 10 + >>> eta = 2 + >>> z = 5 + >>> for l in [5, 4, 3, 2, 1, 0]: + ... print("%s %s %s" % (l, coulombf(l,eta,z), + ... diff(lambda z: coulombf(l,eta,z), z))) + ... + 5 0.09079533488 0.1042553261 + 4 0.2148205331 0.2029591779 + 3 0.4313159311 0.320534053 + 2 0.7212774133 0.3952408216 + 1 0.9935056752 0.3708676452 + 0 1.143337392 0.2937960375 + +**References** + +1. [Thompson]_ +2. [Michel]_ + +""" + +coulombg = r""" +Calculates the irregular Coulomb wave function + +.. math :: + + G_l(\eta,z) = \frac{F_l(\eta,z) \cos(\chi) - F_{-l-1}(\eta,z)}{\sin(\chi)} + +where `\chi = \sigma_l - \sigma_{-l-1} - (l+1/2) \pi` +and `\sigma_l(\eta) = (\ln \Gamma(1+l+i\eta)-\ln \Gamma(1+l-i\eta))/(2i)`. + +See :func:`~mpmath.coulombf` for additional information. + +**Plots** + +.. literalinclude :: /plots/coulombg.py +.. image :: /plots/coulombg.png +.. literalinclude :: /plots/coulombg_c.py +.. image :: /plots/coulombg_c.png + +**Examples** + +Evaluation is supported for arbitrary magnitudes of `z`:: + + >>> from mpmath import mp, coulombg, diff + >>> mp.dps = 25 + >>> mp.pretty = True + >>> coulombg(-2, 1.5, 3.5) + 1.380011900612186346255524 + >>> coulombg(2, 1.5, 3.5) + 1.919153700722748795245926 + >>> coulombg(-2, 1.5, '1e-10') + 201126715824.7329115106793 + >>> coulombg(-2, 1.5, 1000) + 0.1802071520691149410425512 + >>> coulombg(-2, 1.5, 10**10) + 0.652103020061678070929794 + +The following reproduces a table in Abramowitz & Stegun, +at twice the precision:: + + >>> mp.dps = 10 + >>> eta = 2 + >>> z = 5 + >>> for l in [1, 2, 3, 4, 5]: + ... print("%s %s %s" % (l, coulombg(l,eta,z), + ... -diff(lambda z: coulombg(l,eta,z), z))) + ... + 1 1.08148276 0.6028279961 + 2 1.496877075 0.5661803178 + 3 2.048694714 0.7959909551 + 4 3.09408669 1.731802374 + 5 5.629840456 4.549343289 + +Evaluation close to the singularity at `z = 0`:: + + >>> mp.dps = 15 + >>> coulombg(0,10,1) + 3088184933.67358 + >>> coulombg(0,10,'1e-10') + 5554866000719.8 + >>> coulombg(0,10,'1e-100') + 5554866221524.1 + +Evaluation with a half-integer value for `l`:: + + >>> coulombg(1.5, 1, 10) + 0.852320038297334 +""" + +coulombc = r""" +Gives the normalizing Gamow constant for Coulomb wave functions, + +.. math :: + + C_l(\eta) = 2^l \exp\left(-\pi \eta/2 + [\ln \Gamma(1+l+i\eta) + + \ln \Gamma(1+l-i\eta)]/2 - \ln \Gamma(2l+2)\right), + +where the log gamma function with continuous imaginary part +away from the negative half axis (see :func:`~mpmath.loggamma`) is implied. + +This function is used internally for the calculation of +Coulomb wave functions, and automatically cached to make multiple +evaluations with fixed `l`, `\eta` fast. +""" + +ellipfun = r""" +Computes any of the Jacobi elliptic functions, defined +in terms of Jacobi theta functions as + +.. math :: + + \mathrm{sn}(u,m) = \frac{\vartheta_3(0,q)}{\vartheta_2(0,q)} + \frac{\vartheta_1(t,q)}{\vartheta_4(t,q)} + + \mathrm{cn}(u,m) = \frac{\vartheta_4(0,q)}{\vartheta_2(0,q)} + \frac{\vartheta_2(t,q)}{\vartheta_4(t,q)} + + \mathrm{dn}(u,m) = \frac{\vartheta_4(0,q)}{\vartheta_3(0,q)} + \frac{\vartheta_3(t,q)}{\vartheta_4(t,q)}, + +or more generally computes a ratio of two such functions. Here +`t = u/\vartheta_3(0,q)^2`, and `q = q(m)` denotes the nome (see +``mpmath.functions.elliptic.nome()``). Optionally, you can specify the nome directly +instead of `m` by passing ``q=``, or you can directly +specify the elliptic parameter `k` with ``k=``. + +The first argument should be a two-character string specifying the +function using any combination of ``'s'``, ``'c'``, ``'d'``, ``'n'``. These +letters respectively denote the basic functions +`\mathrm{sn}(u,m)`, `\mathrm{cn}(u,m)`, `\mathrm{dn}(u,m)`, and `1`. +The identifier specifies the ratio of two such functions. +For example, ``'ns'`` identifies the function + +.. math :: + + \mathrm{ns}(u,m) = \frac{1}{\mathrm{sn}(u,m)} + +and ``'cd'`` identifies the function + +.. math :: + + \mathrm{cd}(u,m) = \frac{\mathrm{cn}(u,m)}{\mathrm{dn}(u,m)}. + +If called with only the first argument, a function object +evaluating the chosen function for given arguments is returned. + +**Examples** + +Basic evaluation:: + + >>> from mpmath import mp, ellipfun, ellipk, chop, j + >>> mp.dps = 25 + >>> mp.pretty = True + >>> ellipfun('cd', 3.5, 0.5) + -0.9891101840595543931308394 + >>> ellipfun('cd', 3.5, q=0.25) + 0.07111979240214668158441418 + +The sn-function is doubly periodic in the complex plane with periods +`4 K(m)` and `2 i K(1-m)` (see :func:`~mpmath.ellipk`):: + + >>> sn = ellipfun('sn') + >>> sn(2, 0.25) + 0.9628981775982774425751399 + >>> sn(2+4*ellipk(0.25), 0.25) + 0.9628981775982774425751399 + >>> chop(sn(2+2*j*ellipk(1-0.25), 0.25)) + 0.9628981775982774425751399 + +The cn-function is doubly periodic with periods `4 K(m)` and `2 K(m) + 2 i K(1-m)`:: + + >>> cn = ellipfun('cn') + >>> cn(2, 0.25) + -0.2698649654510865792581416 + >>> cn(2+4*ellipk(0.25), 0.25) + -0.2698649654510865792581416 + >>> chop(cn(2+2*ellipk(0.25)+2*j*ellipk(1-0.25), 0.25)) + -0.2698649654510865792581416 + +The dn-function is doubly periodic with periods `2 K(m)` and `4 i K(1-m)`:: + + >>> dn = ellipfun('dn') + >>> dn(2, 0.25) + 0.8764740583123262286931578 + >>> dn(2+2*ellipk(0.25), 0.25) + 0.8764740583123262286931578 + >>> chop(dn(2+4*j*ellipk(1-0.25), 0.25)) + 0.8764740583123262286931578 + +""" + + +jtheta = r""" +Computes the Jacobi theta function `\vartheta_n(z, q)`, where +`n = 1, 2, 3, 4`, defined by the infinite series: + +.. math :: + + \vartheta_1(z,q) = 2 q^{1/4} \sum_{n=0}^{\infty} + (-1)^n q^{n^2+n\,} \sin((2n+1)z) + + \vartheta_2(z,q) = 2 q^{1/4} \sum_{n=0}^{\infty} + q^{n^{2\,} + n} \cos((2n+1)z) + + \vartheta_3(z,q) = 1 + 2 \sum_{n=1}^{\infty} + q^{n^2\,} \cos(2 n z) + + \vartheta_4(z,q) = 1 + 2 \sum_{n=1}^{\infty} + (-q)^{n^2\,} \cos(2 n z) + +The theta functions are functions of two variables: + +* `z` is the *argument*, an arbitrary real or complex number + +* `q` is the *nome*, which must be a real or complex number + in the unit disk (i.e. `|q| < 1`). For `|q| \ll 1`, the + series converge very quickly, so the Jacobi theta functions + can efficiently be evaluated to high precision. + +The compact notations `\vartheta_n(q) = \vartheta_n(0,q)` +and `\vartheta_n = \vartheta_n(0,q)` are also frequently +encountered. Finally, Jacobi theta functions are frequently +considered as functions of the half-period ratio `\tau` +and then usually denoted by `\vartheta_n(z|\tau)`. + +Optionally, ``jtheta(n, z, q, derivative=d)`` with `d > 0` computes +a `d`-th derivative with respect to `z`. + +**Examples and basic properties** + +Considered as functions of `z`, the Jacobi theta functions may be +viewed as generalizations of the ordinary trigonometric functions +cos and sin. They are periodic functions:: + + >>> from mpmath import (mp, jtheta, pi, nprint, fourier, exp, j, mpf, + ... gamma, diff, sqrt) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> jtheta(1, 0.25, '0.2') + 0.2945120798627300045053104 + >>> jtheta(1, 0.25 + 2*pi, '0.2') + 0.2945120798627300045053104 + +Indeed, the series defining the theta functions are essentially +trigonometric Fourier series. The coefficients can be retrieved +using :func:`~mpmath.fourier`:: + + >>> mp.dps = 10 + >>> nprint(fourier(lambda x: jtheta(2, x, 0.5), [-pi, pi], 4)) + ([0.0, 1.68179, 0.0, 0.420448, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0]) + +The Jacobi theta functions are also so-called quasiperiodic +functions of `z` and `\tau`, meaning that for fixed `\tau`, +`\vartheta_n(z, q)` and `\vartheta_n(z+\pi \tau, q)` are the same +except for an exponential factor:: + + >>> mp.dps = 25 + >>> tau = 3*j/10 + >>> q = exp(pi*j*tau) + >>> z = 10 + >>> jtheta(4, z+tau*pi, q) + (-0.682420280786034687520568 + 1.526683999721399103332021j) + >>> -exp(-2*j*z)/q * jtheta(4, z, q) + (-0.682420280786034687520568 + 1.526683999721399103332021j) + +The Jacobi theta functions satisfy a huge number of other +functional equations, such as the following identity (valid for +any `q`):: + + >>> q = mpf(3)/10 + >>> jtheta(3,0,q)**4 + 6.823744089352763305137427 + >>> jtheta(2,0,q)**4 + jtheta(4,0,q)**4 + 6.823744089352763305137427 + +Extensive listings of identities satisfied by the Jacobi theta +functions can be found in standard reference works. + +The Jacobi theta functions are related to the gamma function +for special arguments:: + + >>> jtheta(3, 0, exp(-pi)) + 1.086434811213308014575316 + >>> pi**(1/4.) / gamma(3/4.) + 1.086434811213308014575316 + +:func:`~mpmath.jtheta` supports arbitrary precision evaluation and complex +arguments:: + + >>> mp.dps = 50 + >>> jtheta(4, sqrt(2), 0.5) + 2.0549510717571539127004115835148878097035750653737 + >>> mp.dps = 25 + >>> jtheta(4, 1+2j, (1+j)/5) + (7.180331760146805926356634 - 1.634292858119162417301683j) + +Evaluation of derivatives:: + + >>> mp.dps = 25 + >>> jtheta(1, 7, 0.25, 1) + 1.209857192844475388637236 + >>> diff(lambda z: jtheta(1, z, 0.25), 7) + 1.209857192844475388637236 + >>> jtheta(1, 7, 0.25, 2) + -0.2598718791650217206533052 + >>> diff(lambda z: jtheta(1, z, 0.25), 7, 2) + -0.2598718791650217206533052 + >>> jtheta(2, 7, 0.25, 1) + -1.150231437070259644461474 + >>> diff(lambda z: jtheta(2, z, 0.25), 7) + -1.150231437070259644461474 + >>> jtheta(2, 7, 0.25, 2) + -0.6226636990043777445898114 + >>> diff(lambda z: jtheta(2, z, 0.25), 7, 2) + -0.6226636990043777445898114 + >>> jtheta(3, 7, 0.25, 1) + -0.9990312046096634316587882 + >>> diff(lambda z: jtheta(3, z, 0.25), 7) + -0.9990312046096634316587882 + >>> jtheta(3, 7, 0.25, 2) + -0.1530388693066334936151174 + >>> diff(lambda z: jtheta(3, z, 0.25), 7, 2) + -0.1530388693066334936151174 + >>> jtheta(4, 7, 0.25, 1) + 0.9820995967262793943571139 + >>> diff(lambda z: jtheta(4, z, 0.25), 7) + 0.9820995967262793943571139 + >>> jtheta(4, 7, 0.25, 2) + 0.3936902850291437081667755 + >>> diff(lambda z: jtheta(4, z, 0.25), 7, 2) + 0.3936902850291437081667755 + +**Possible issues** + +For `|q| \ge 1` or `\Im(\tau) \le 0`, :func:`~mpmath.jtheta` raises +``ValueError``:: + + >>> jtheta(1, 10, 2) + Traceback (most recent call last): + ... + ValueError: abs(q) >= 1 + +""" + +eulernum = r""" +Gives the `n`-th Euler number, defined as the `n`-th derivative of +`\mathrm{sech}(t) = 1/\cosh(t)` evaluated at `t = 0`. Equivalently, the +Euler numbers give the coefficients of the Taylor series + +.. math :: + + \mathrm{sech}(t) = \sum_{n=0}^{\infty} \frac{E_n}{n!} t^n. + +The Euler numbers are closely related to Bernoulli numbers +and Bernoulli polynomials. They can also be evaluated in terms of +Euler polynomials (see :func:`~mpmath.eulerpoly`) as `E_n = 2^n E_n(1/2)`. + +**Examples** + +Computing the first few Euler numbers and verifying that they +agree with the Taylor series:: + + >>> from mpmath import mp, eulernum, chop, diffs, sech, sqrt, pi, e + >>> mp.dps = 25 + >>> mp.pretty = True + >>> [eulernum(n) for n in range(11)] + [1.0, 0.0, -1.0, 0.0, 5.0, 0.0, -61.0, 0.0, 1385.0, 0.0, -50521.0] + >>> chop(diffs(sech, 0, 10)) + [1.0, 0.0, -1.0, 0.0, 5.0, 0.0, -61.0, 0.0, 1385.0, 0.0, -50521.0] + +Euler numbers grow very rapidly. :func:`~mpmath.eulernum` efficiently +computes numerical approximations for large indices:: + + >>> eulernum(50) + -6.053285248188621896314384e+54 + >>> eulernum(1000) + 3.887561841253070615257336e+2371 + >>> eulernum(10**20) + 4.346791453661149089338186e+1936958564106659551331 + +Comparing with an asymptotic formula for the Euler numbers:: + + >>> n = 10**5 + >>> (-1)**(n//2) * 8 * sqrt(n/(2*pi)) * (2*n/(pi*e))**n + 3.69919063017432362805663e+436961 + >>> eulernum(n) + 3.699193712834466537941283e+436961 + +Pass ``exact=True`` to obtain exact values of Euler numbers as integers:: + + >>> print(eulernum(50, exact=True)) + -6053285248188621896314383785111649088103498225146815121 + >>> print(eulernum(200, exact=True) % 10**10) + 1925859625 + >>> eulernum(1001, exact=True) + 0 +""" + +eulerpoly = r""" +Evaluates the Euler polynomial `E_n(z)`, defined by the generating function +representation + +.. math :: + + \frac{2e^{zt}}{e^t+1} = \sum_{n=0}^\infty E_n(z) \frac{t^n}{n!}. + +The Euler polynomials may also be represented in terms of +Bernoulli polynomials (see :func:`~mpmath.bernpoly`) using various formulas, for +example + +.. math :: + + E_n(z) = \frac{2}{n+1} \left( + B_n(z)-2^{n+1}B_n\left(\frac{z}{2}\right) + \right). + +Special values include the Euler numbers `E_n = 2^n E_n(1/2)` (see +:func:`~mpmath.eulernum`). + +**Examples** + +Computing the coefficients of the first few Euler polynomials:: + + >>> from mpmath import mp, chop, taylor, eulerpoly, inf, eulernum + >>> mp.dps = 25 + >>> mp.pretty = True + >>> for n in range(6): + ... chop(taylor(lambda z: eulerpoly(n,z), 0, n)) + ... + [1.0] + [-0.5, 1.0] + [0.0, -1.0, 1.0] + [0.25, 0.0, -1.5, 1.0] + [0.0, 1.0, 0.0, -2.0, 1.0] + [-0.5, 0.0, 2.5, 0.0, -2.5, 1.0] + +Evaluation for arbitrary `z`:: + + >>> eulerpoly(2,3) + 6.0 + >>> eulerpoly(5,4) + 423.5 + >>> eulerpoly(35, 11111111112) + 3.994957561486776072734601e+351 + >>> eulerpoly(4, 10+20j) + (-47990.0 - 235980.0j) + >>> eulerpoly(2, '-3.5e-5') + 0.000035001225 + >>> eulerpoly(3, 0.5) + 0.0 + >>> eulerpoly(55, -10**80) + -1.0e+4400 + >>> eulerpoly(5, -inf) + -inf + >>> eulerpoly(6, -inf) + inf + +Computing Euler numbers:: + + >>> 2**26 * eulerpoly(26,0.5) + -4087072509293123892361.0 + >>> eulernum(26) + -4087072509293123892361.0 + +Evaluation is accurate for large `n` and small `z`:: + + >>> eulerpoly(100, 0.5) + 2.29047999988194114177943e+108 + >>> eulerpoly(1000, 10.5) + 3.628120031122876847764566e+2070 + >>> eulerpoly(10000, 10.5) + 1.149364285543783412210773e+30688 +""" + +spherharm = r""" +Evaluates the spherical harmonic `Y_l^m(\theta,\phi)`, + +.. math :: + + Y_l^m(\theta,\phi) = \sqrt{\frac{2l+1}{4\pi}\frac{(l-m)!}{(l+m)!}} + P_l^m(\cos \theta) e^{i m \phi} + +where `P_l^m` is an associated Legendre function (see :func:`~mpmath.legenp`). + +Here `\theta \in [0, \pi]` denotes the polar coordinate (ranging +from the north pole to the south pole) and `\phi \in [0, 2 \pi]` denotes the +azimuthal coordinate on a sphere. Care should be used since many different +conventions for spherical coordinate variables are used. + +Usually spherical harmonics are considered for `l \in \mathbb{N}`, +`m \in \mathbb{Z}`, `|m| \le l`. More generally, `l,m,\theta,\phi` +are permitted to be complex numbers. + +.. note :: + + :func:`~mpmath.spherharm` returns a complex number, even if the value is + purely real. + +**Plots** + +.. literalinclude :: /plots/spherharm40.py + +`Y_{4,0}`: + +.. image :: /plots/spherharm40.png + +`Y_{4,1}`: + +.. image :: /plots/spherharm41.png + +`Y_{4,2}`: + +.. image :: /plots/spherharm42.png + +`Y_{4,3}`: + +.. image :: /plots/spherharm43.png + +`Y_{4,4}`: + +.. image :: /plots/spherharm44.png + +**Examples** + +Some low-order spherical harmonics with reference values:: + + >>> from mpmath import mp, spherharm, pi, sqrt, expj, sin, fp, j, cos + >>> mp.dps = 25 + >>> mp.pretty = True + >>> theta = pi/4 + >>> phi = pi/3 + >>> spherharm(0,0,theta,phi) + (0.2820947917738781434740397 + 0.0j) + >>> 0.5*sqrt(1/pi)*expj(0) + (0.2820947917738781434740397 + 0.0j) + >>> spherharm(1,-1,theta,phi) + (0.1221506279757299803965962 - 0.2115710938304086076055298j) + >>> 0.5*sqrt(3/(2*pi))*expj(-phi)*sin(theta) + (0.1221506279757299803965962 - 0.2115710938304086076055298j) + >>> spherharm(1,0,theta,phi) + (0.3454941494713354792652446 + 0.0j) + >>> 0.5*sqrt(3/pi)*cos(theta)*expj(0) + (0.3454941494713354792652446 + 0.0j) + >>> spherharm(1,1,theta,phi) + (-0.1221506279757299803965962 - 0.2115710938304086076055298j) + >>> -0.5*sqrt(3/(2*pi))*expj(phi)*sin(theta) + (-0.1221506279757299803965962 - 0.2115710938304086076055298j) + +With the normalization convention used, the spherical harmonics are orthonormal +on the unit sphere:: + + >>> sphere = [0,pi], [0,2*pi] + >>> dS = lambda t,p: fp.sin(t) # differential element + >>> Y1 = lambda t,p: fp.spherharm(l1,m1,t,p) + >>> Y2 = lambda t,p: fp.conj(fp.spherharm(l2,m2,t,p)) + >>> l1 = l2 = 3 + >>> m1 = m2 = 2 + >>> fp.chop(fp.quad(lambda t,p: Y1(t,p)*Y2(t,p)*dS(t,p), *sphere)) + 1.000000000000000... + >>> m2 = 1 # m1 != m2 + >>> print(fp.chop(fp.quad(lambda t,p: Y1(t,p)*Y2(t,p)*dS(t,p), *sphere))) + 0.0 + +Evaluation is accurate for large orders:: + + >>> spherharm(1000,750,0.5,0.25) + (3.776445785304252879026585e-102 - 5.82441278771834794493484e-102j) + +Evaluation works with complex parameter values:: + + >>> spherharm(1+j, 2j, 2+3j, -0.5j) + (64.44922331113759992154992 + 1981.693919841408089681743j) +""" + +scorergi = r""" +Evaluates the Scorer function + +.. math :: + + \operatorname{Gi}(z) = + \operatorname{Ai}(z) \int_0^z \operatorname{Bi}(t) dt + + \operatorname{Bi}(z) \int_z^{\infty} \operatorname{Ai}(t) dt + +which gives a particular solution to the inhomogeneous Airy +differential equation `f''(z) - z f(z) = 1/\pi`. Another +particular solution is given by the Scorer Hi-function +(:func:`~mpmath.scorerhi`). The two functions are related as +`\operatorname{Gi}(z) + \operatorname{Hi}(z) = \operatorname{Bi}(z)`. + +**Plots** + +.. literalinclude :: /plots/gi.py +.. image :: /plots/gi.png +.. literalinclude :: /plots/gi_c.py +.. image :: /plots/gi_c.png + +**Examples** + +Some values and limits:: + + >>> from mpmath import (mp, scorergi, power, gamma, diff, inf, airybi, + ... scorerhi, chop, airyai, pi) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> scorergi(0) + 0.2049755424820002450503075 + >>> 1/(power(3,'7/6')*gamma('2/3')) + 0.2049755424820002450503075 + >>> diff(scorergi, 0) + 0.1494294524512754526382746 + >>> 1/(power(3,'5/6')*gamma('1/3')) + 0.1494294524512754526382746 + >>> scorergi(+inf) + 0.0 + >>> scorergi(-inf) + 0.0 + >>> scorergi(1) + 0.2352184398104379375986902 + >>> scorergi(-1) + -0.1166722172960152826494198 + +Evaluation for large arguments:: + + >>> scorergi(10) + 0.03189600510067958798062034 + >>> scorergi(100) + 0.003183105228162961476590531 + >>> scorergi(1000000) + 0.0000003183098861837906721743873 + >>> 1/(pi*1000000) + 0.0000003183098861837906715377675 + >>> scorergi(-1000) + -0.08358288400262780392338014 + >>> scorergi(-100000) + 0.02886866118619660226809581 + >>> scorergi(50+10j) + (0.0061214102799778578790984 - 0.001224335676457532180747917j) + >>> scorergi(-50-10j) + (5.236047850352252236372551e+29 - 3.08254224233701381482228e+29j) + >>> scorergi(100000j) + (-8.806659285336231052679025e+6474077 + 8.684731303500835514850962e+6474077j) + +Verifying the connection between Gi and Hi:: + + >>> z = 0.25 + >>> scorergi(z) + scorerhi(z) + 0.7287469039362150078694543 + >>> airybi(z) + 0.7287469039362150078694543 + +Verifying the differential equation:: + + >>> for z in [-3.4, 0, 2.5, 1+2j]: + ... chop(diff(scorergi,z,2) - z*scorergi(z)) + ... + -0.3183098861837906715377675 + -0.3183098861837906715377675 + -0.3183098861837906715377675 + -0.3183098861837906715377675 + +Verifying the integral representation:: + + >>> z = 0.5 + >>> scorergi(z) + 0.2447210432765581976910539 + >>> Ai,Bi = airyai,airybi + >>> Bi(z)*(Ai(inf,-1)-Ai(z,-1)) + Ai(z)*(Bi(z,-1)-Bi(0,-1)) + 0.2447210432765581976910539 + +**References** + +1. [DLMF]_ section 9.12: Scorer Functions + +""" + +scorerhi = r""" +Evaluates the second Scorer function + +.. math :: + + \operatorname{Hi}(z) = + \operatorname{Bi}(z) \int_{-\infty}^z \operatorname{Ai}(t) dt - + \operatorname{Ai}(z) \int_{-\infty}^z \operatorname{Bi}(t) dt + +which gives a particular solution to the inhomogeneous Airy +differential equation `f''(z) - z f(z) = 1/\pi`. See also +:func:`~mpmath.scorergi`. + +**Plots** + +.. literalinclude :: /plots/hi.py +.. image :: /plots/hi.png +.. literalinclude :: /plots/hi_c.py +.. image :: /plots/hi_c.png + +**Examples** + +Some values and limits:: + + >>> from mpmath import (mp, scorerhi, power, gamma, diff, inf, airyai, + ... airybi, chop) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> scorerhi(0) + 0.4099510849640004901006149 + >>> 2/(power(3,'7/6')*gamma('2/3')) + 0.4099510849640004901006149 + >>> diff(scorerhi,0) + 0.2988589049025509052765491 + >>> 2/(power(3,'5/6')*gamma('1/3')) + 0.2988589049025509052765491 + >>> scorerhi(+inf) + inf + >>> scorerhi(-inf) + 0.0 + >>> scorerhi(1) + 0.9722051551424333218376886 + >>> scorerhi(-1) + 0.2206696067929598945381098 + +Evaluation for large arguments:: + + >>> scorerhi(10) + 455641153.5163291358991077 + >>> scorerhi(100) + 6.041223996670201399005265e+288 + >>> scorerhi(1000000) + 7.138269638197858094311122e+289529652 + >>> scorerhi(-10) + 0.0317685352825022727415011 + >>> scorerhi(-100) + 0.003183092495767499864680483 + >>> scorerhi(100j) + (-6.366197716545672122983857e-9 + 0.003183098861710582761688475j) + >>> scorerhi(50+50j) + (-5.322076267321435669290334e+63 + 1.478450291165243789749427e+65j) + >>> scorerhi(-1000-1000j) + (0.0001591549432510502796565538 - 0.000159154943091895334973109j) + +Verifying the differential equation:: + + >>> for z in [-3.4, 0, 2, 1+2j]: + ... chop(diff(scorerhi,z,2) - z*scorerhi(z)) + ... + 0.3183098861837906715377675 + 0.3183098861837906715377675 + 0.3183098861837906715377675 + 0.3183098861837906715377675 + +Verifying the integral representation:: + + >>> z = 0.5 + >>> scorerhi(z) + 0.6095559998265972956089949 + >>> Ai,Bi = airyai,airybi + >>> Bi(z)*(Ai(z,-1)-Ai(-inf,-1)) - Ai(z)*(Bi(z,-1)-Bi(-inf,-1)) + 0.6095559998265972956089949 + +""" + + +stirling1 = r""" +Gives the Stirling number of the first kind `s(n,k)`, defined by + +.. math :: + + x(x-1)(x-2)\cdots(x-n+1) = \sum_{k=0}^n s(n,k) x^k. + +The value is computed using an integer recurrence. The implementation +is not optimized for approximating large values quickly. + +**Examples** + +Comparing with the generating function:: + + >>> from mpmath import mp, taylor, ff, stirling1, matrix, stirling2 + >>> mp.dps = 25 + >>> mp.pretty = True + >>> taylor(lambda x: ff(x, 5), 0, 5) + [0.0, 24.0, -50.0, 35.0, -10.0, 1.0] + >>> [stirling1(5, k) for k in range(6)] + [0.0, 24.0, -50.0, 35.0, -10.0, 1.0] + +Recurrence relation:: + + >>> n, k = 5, 3 + >>> stirling1(n+1,k) + n*stirling1(n,k) - stirling1(n,k-1) + 0.0 + +The matrices of Stirling numbers of first and second kind are inverses +of each other:: + + >>> A = matrix(5, 5) + >>> B = matrix(5, 5) + >>> for n in range(5): + ... for k in range(5): + ... A[n,k] = stirling1(n,k) + ... B[n,k] = stirling2(n,k) + ... + >>> A * B + [1.0 0.0 0.0 0.0 0.0] + [0.0 1.0 0.0 0.0 0.0] + [0.0 0.0 1.0 0.0 0.0] + [0.0 0.0 0.0 1.0 0.0] + [0.0 0.0 0.0 0.0 1.0] + +Pass ``exact=True`` to obtain exact values of Stirling numbers as integers:: + + >>> stirling1(42, 5) + -2.864498971768501633736628e+50 + >>> print(stirling1(42, 5, exact=True)) + -286449897176850163373662803014001546235808317440000 + +""" + +stirling2 = r""" +Gives the Stirling number of the second kind `S(n,k)`, defined by + +.. math :: + + x^n = \sum_{k=0}^n S(n,k) x(x-1)(x-2)\cdots(x-k+1) + +The value is computed using integer arithmetic to evaluate a power sum. +The implementation is not optimized for approximating large values quickly. + +**Examples** + +Comparing with the generating function:: + + >>> from mpmath import mp, stirling2, taylor, ff + >>> mp.dps = 25 + >>> mp.pretty = True + >>> taylor(lambda x: sum(stirling2(5,k) * ff(x,k) for k in range(6)), 0, 5) + [0.0, 0.0, 0.0, 0.0, 0.0, 1.0] + +Recurrence relation:: + + >>> n, k = 5, 3 + >>> stirling2(n+1,k) - k*stirling2(n,k) - stirling2(n,k-1) + 0.0 + +Pass ``exact=True`` to obtain exact values of Stirling numbers as integers:: + + >>> stirling2(52, 10) + 2.641822121003543906807485e+45 + >>> print(stirling2(52, 10, exact=True)) + 2641822121003543906807485307053638921722527655 + + +""" + +squarew = r""" +Computes the square wave function using the definition: + +.. math:: + x(t) = A(-1)^{\left\lfloor{2t / P}\right\rfloor} + +where `P` is the period of the wave and `A` is the amplitude. + +**Examples** + +Square wave with period = 2, amplitude = 1 :: + + >>> from mpmath import mp, squarew + >>> mp.dps = 25 + >>> mp.pretty = True + >>> squarew(0,1,2) + 1.0 + >>> squarew(0.5,1,2) + 1.0 + >>> squarew(1,1,2) + -1.0 + >>> squarew(1.5,1,2) + -1.0 + >>> squarew(2,1,2) + 1.0 +""" + +trianglew = r""" +Computes the triangle wave function using the definition: + +.. math:: + x(t) = 2A\left(\frac{1}{2}-\left|1-2 \operatorname{frac}\left(\frac{x}{P}+\frac{1}{4}\right)\right|\right) + +where :math:`\operatorname{frac}\left(\frac{t}{T}\right) = \frac{t}{T}-\left\lfloor{\frac{t}{T}}\right\rfloor` +, `P` is the period of the wave, and `A` is the amplitude. + +**Examples** + +Triangle wave with period = 2, amplitude = 1 :: + + >>> from mpmath import mp, trianglew + >>> mp.dps = 25 + >>> mp.pretty = True + >>> trianglew(0,1,2) + 0.0 + >>> trianglew(0.25,1,2) + 0.5 + >>> trianglew(0.5,1,2) + 1.0 + >>> trianglew(1,1,2) + 0.0 + >>> trianglew(1.5,1,2) + -1.0 + >>> trianglew(2,1,2) + 0.0 +""" + +sawtoothw = r""" +Computes the sawtooth wave function using the definition: + +.. math:: + x(t) = A\operatorname{frac}\left(\frac{t}{T}\right) + +where :math:`\operatorname{frac}\left(\frac{t}{T}\right) = \frac{t}{T}-\left\lfloor{\frac{t}{T}}\right\rfloor`, +`P` is the period of the wave, and `A` is the amplitude. + +**Examples** + +Sawtooth wave with period = 2, amplitude = 1 :: + + >>> from mpmath import mp, sawtoothw + >>> mp.dps = 25 + >>> mp.pretty = True + >>> sawtoothw(0,1,2) + 0.0 + >>> sawtoothw(0.5,1,2) + 0.25 + >>> sawtoothw(1,1,2) + 0.5 + >>> sawtoothw(1.5,1,2) + 0.75 + >>> sawtoothw(2,1,2) + 0.0 +""" + +unit_triangle = r""" +Computes the unit triangle using the definition: + +.. math:: + x(t) = A(-\left| t \right| + 1) + +where `A` is the amplitude. + +**Examples** + +Unit triangle with amplitude = 1 :: + + >>> from mpmath import mp, unit_triangle + >>> mp.dps = 25 + >>> mp.pretty = True + >>> unit_triangle(-1,1) + 0.0 + >>> unit_triangle(-0.5,1) + 0.5 + >>> unit_triangle(0,1) + 1.0 + >>> unit_triangle(0.5,1) + 0.5 + >>> unit_triangle(1,1) + 0.0 +""" + +sigmoid = r""" +Computes the sigmoid function using the definition: + +.. math:: + x(t) = \frac{A}{1 + e^{-t}} + +where `A` is the amplitude. + +**Examples** + +Sigmoid function with amplitude = 1 :: + + >>> from mpmath import mp, sigmoid + >>> mp.dps = 25 + >>> mp.pretty = True + >>> sigmoid(-1,1) + 0.2689414213699951207488408 + >>> sigmoid(-0.5,1) + 0.3775406687981454353610994 + >>> sigmoid(0,1) + 0.5 + >>> sigmoid(0.5,1) + 0.6224593312018545646389006 + >>> sigmoid(1,1) + 0.7310585786300048792511592 + +""" diff --git a/mpmath/functions/__init__.py b/mpmath/functions/__init__.py new file mode 100644 index 0000000..5896ed0 --- /dev/null +++ b/mpmath/functions/__init__.py @@ -0,0 +1,14 @@ +from . import functions +# Hack to update methods +from . import factorials +from . import hypergeometric +from . import expintegrals +from . import bessel +from . import orthogonal +from . import theta +from . import elliptic +from . import signals +from . import zeta +from . import rszeta +from . import zetazeros +from . import qfunctions diff --git a/mpmath/functions/bessel.py b/mpmath/functions/bessel.py new file mode 100644 index 0000000..f25d476 --- /dev/null +++ b/mpmath/functions/bessel.py @@ -0,0 +1,1281 @@ +from ..libmp.backend import MPQ +from .functions import defun, defun_wrapped + +@defun +def j0(ctx, x): + """Computes the Bessel function `J_0(x)`. See :func:`~mpmath.besselj`.""" + return ctx.besselj(0, x) + +@defun +def j1(ctx, x): + """Computes the Bessel function `J_1(x)`. See :func:`~mpmath.besselj`.""" + return ctx.besselj(1, x) + +@defun +def besselj(ctx, n, z, derivative=0, **kwargs): + if type(n) is int: + n_isint = True + else: + n = ctx.convert(n) + n_isint = ctx.isint(n) + if n_isint: + n = int(ctx._re(n)) + if n_isint and n < 0: + return (-1)**n * ctx.besselj(-n, z, derivative, **kwargs) + z = ctx.convert(z) + M = ctx.mag(z) + if derivative: + d = ctx.convert(derivative) + # TODO: the integer special-casing shouldn't be necessary. + # However, the hypergeometric series gets inaccurate for large d + # because of inaccurate pole cancellation at a pole far from + # zero (needs to be fixed in hypercomb or hypsum) + if ctx.isint(d) and d >= 0: + d = int(d) + orig = ctx.prec + try: + ctx.prec += 15 + v = ctx.fsum((-1)**k * ctx.binomial(d,k) * ctx.besselj(2*k+n-d,z) + for k in range(d+1)) + finally: + ctx.prec = orig + v *= ctx.mpf(2)**(-d) + else: + def h(n,d): + r = ctx.fmul(ctx.fmul(z, z, prec=ctx.prec+M), -0.25, exact=True) + B = [0.5*(n-d+1), 0.5*(n-d+2)] + T = [([2,ctx.pi,z],[d-2*n,0.5,n-d],[],B,[(n+1)*0.5,(n+2)*0.5],B+[n+1],r)] + return T + v = ctx.hypercomb(h, [n,d], **kwargs) + else: + # Fast case: J_n(x), n int, appropriate magnitude for fixed-point calculation + if (not derivative) and n_isint and abs(M) < 10 and abs(n) < 20: + try: + return ctx._besselj(n, z) + except NotImplementedError: + pass + if not z: + if not n: + v = ctx.one + n+z + elif ctx.re(n) > 0: + v = n*z + else: + v = ctx.inf + z + n + else: + #v = 0 + orig = ctx.prec + try: + # XXX: workaround for accuracy in low level hypergeometric series + # when alternating, large arguments + ctx.prec += min(3*abs(M), ctx.prec) + w = ctx.fmul(z, 0.5, exact=True) + def h(n): + r = ctx.fneg(ctx.fmul(w, w, prec=max(0,ctx.prec+M)), exact=True) + return [([w], [n], [], [n+1], [], [n+1], r)] + v = ctx.hypercomb(h, [n], **kwargs) + finally: + ctx.prec = orig + v = +v + return v + +@defun +def besseli(ctx, n, z, derivative=0, **kwargs): + n = ctx.convert(n) + z = ctx.convert(z) + if n and ctx.isnpint(n): + return ctx.besseli(-n, z, derivative, **kwargs) + if not z: + if derivative: + raise ValueError + if not n: + # I(0,0) = 1 + return 1+n+z + if ctx.isint(n): + return 0*(n+z) + r = ctx.re(n) + if r == 0: + return ctx.nan*(n+z) + elif r > 0: + return 0*(n+z) + else: + return ctx.inf+(n+z) + M = ctx.mag(z) + if derivative: + d = ctx.convert(derivative) + def h(n,d): + r = ctx.fmul(ctx.fmul(z, z, prec=ctx.prec+M), 0.25, exact=True) + B = [0.5*(n-d+1), 0.5*(n-d+2), n+1] + T = [([2,ctx.pi,z],[d-2*n,0.5,n-d],[n+1],B,[(n+1)*0.5,(n+2)*0.5],B,r)] + return T + v = ctx.hypercomb(h, [n,d], **kwargs) + else: + def h(n): + w = ctx.fmul(z, 0.5, exact=True) + r = ctx.fmul(w, w, prec=max(0,ctx.prec+M)) + return [([w], [n], [], [n+1], [], [n+1], r)] + v = ctx.hypercomb(h, [n], **kwargs) + return v + +@defun_wrapped +def bessely(ctx, n, z, derivative=0, **kwargs): + if not z: + if derivative: + # Not implemented + raise ValueError + if not n: + # ~ log(z/2) + return -ctx.inf + (n+z) + if ctx.im(n): + return ctx.nan * (n+z) + r = ctx.re(n) + q = n+0.5 + if ctx.isint(q): + if n > 0: + return -ctx.inf + (n+z) + else: + return 0 * (n+z) + if r < 0 and int(ctx.floor(q)) % 2: + return ctx.inf + (n+z) + else: + return ctx.ninf + (n+z) + # XXX: use hypercomb + ctx.prec += 10 + m, d = ctx.nint_distance(n) + if d < -ctx.prec: + h = +ctx.eps + ctx.prec *= 2 + n += h + elif d < 0: + ctx.prec -= d + # TODO: avoid cancellation for imaginary arguments + cos, sin = ctx.cospi_sinpi(n) + return (ctx.besselj(n,z,derivative,**kwargs)*cos - \ + ctx.besselj(-n,z,derivative,**kwargs))/sin + +@defun_wrapped +def besselk(ctx, n, z, derivative=0, **kwargs): + if derivative: + raise NotImplementedError + if not z: + return ctx.inf + M = ctx.mag(z) + if M < 1: + # Represent as limit definition + def h(n): + r = (z/2)**2 + T1 = [z, 2], [-n, n-1], [n], [], [], [1-n], r + T2 = [z, 2], [n, -n-1], [-n], [], [], [1+n], r + return T1, T2 + # We could use the limit definition always, but it leads + # to very bad cancellation (of exponentially large terms) + # for large real z + # Instead represent in terms of 2F0 + else: + if ctx.isfinite(M): + ctx.prec += M + def h(n): + return [([ctx.pi/2, z, ctx.exp(-z)], [0.5,-0.5,1], [], [], \ + [n+0.5, 0.5-n], [], -1/(2*z))] + return ctx.hypercomb(h, [n], **kwargs) + +@defun_wrapped +def hankel1(ctx,n,x,**kwargs): + def terms(): + return [ctx.besselj(n,x,**kwargs), + ctx.j*ctx.bessely(n,x,**kwargs)] + return ctx.sum_accurately(terms) + +@defun_wrapped +def hankel2(ctx,n,x,**kwargs): + def terms(): + return [ctx.besselj(n,x,**kwargs), + -ctx.j*ctx.bessely(n,x,**kwargs)] + return ctx.sum_accurately(terms) + +@defun +def spherical_jn(ctx, n, z): + r""" + Spherical Bessel function of the first kind. + + 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. + + **Examples** + + >>> from mpmath import spherical_jn + >>> spherical_jn(0, 1) + mpf('0.84147098480789639') + + """ + return ctx.besselj(n + ctx.one/2, z) * ctx.sqrt(ctx.pi/(2*z)) + +@defun +def spherical_yn(ctx, n, z): + r""" + Spherical Bessel function of the second kind. + + This function is another solution to the spherical Bessel equation, and + linearly independent from `j_n`. It can be defined as + + .. math :: + j_\nu(z) = \sqrt{\frac{\pi}{2z}} Y_{\nu + \frac{1}{2}}(z), + + where `Y_\nu(z)` is the Bessel function of the second kind. + + **Examples** + + >>> from mpmath import spherical_yn + >>> spherical_yn(0, 1) + mpf('-0.54030230586813965') + + """ + return ctx.bessely(n + ctx.one/2, z) * ctx.sqrt(ctx.pi/(2*z)) + +@defun +def spherical_in(ctx, n, z): + r""" + Modified spherical Bessel function of the first kind. + + This function is a solution to the spherical Bessel equation + (equation 10.47.2 of [DLMF]_): + + .. 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 :: + i_\nu(z) = \sqrt{\frac{\pi}{2z}} I_{\nu + \frac{1}{2}}(z), + + where `I_\nu(z)` is the modified Bessel function of the first kind. + + **References** + + 1. [DLMF]_ Chapter 10.47. + + **Examples** + + >>> from mpmath import spherical_in + + >>> spherical_in(0, 1) + mpf('1.1752011936438014') + >>> spherical_in(6, 0.5 + 3j) + mpc(real='-0.0027505520810430398', imag='0.0033767606983784665') + + """ + return ctx.besseli(n + ctx.one/2, z) * ctx.sqrt(ctx.pi/(2*z)) + + +@defun +def spherical_kn(ctx, n, z): + r""" + Modified spherical Bessel function of the second kind. + + This function is a solution to the spherical Bessel equation + (equation 10.47.2 of [DLMF]_): + + .. 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 :: + k_\nu(z) = \sqrt{\frac{\pi}{2z}} K_{\nu + \frac{1}{2}}(z), + + where `K_\nu(z)` is the modified Bessel function of the second kind. + + **References** + + 1. [DLMF]_ Chapter 10.47. + + **Examples** + + >>> from mpmath import spherical_kn + + >>> spherical_kn(0, 1) + mpf('0.57786367489546075') + >>> spherical_kn(6, 0.5 + 3j) + mpc(real='-8.6615736788078621', imag='5.5165801484422303') + + """ + return ctx.besselk(n + ctx.one/2, z) * ctx.sqrt(ctx.pi/(2*z)) + + +@defun_wrapped +def whitm(ctx,k,m,z,**kwargs): + if z == 0: + # M(k,m,z) = 0^(1/2+m) + if ctx.re(m) > -0.5: + return z + elif ctx.re(m) < -0.5: + return ctx.inf + z + else: + return ctx.nan * z + x = ctx.fmul(-0.5, z, exact=True) + y = 0.5+m + return ctx.exp(x) * z**y * ctx.hyp1f1(y-k, 1+2*m, z, **kwargs) + +@defun_wrapped +def whitw(ctx,k,m,z,**kwargs): + if z == 0: + g = abs(ctx.re(m)) + if g < 0.5: + return z + elif g > 0.5: + return ctx.inf + z + else: + return ctx.nan * z + x = ctx.fmul(-0.5, z, exact=True) + y = 0.5+m + return ctx.exp(x) * z**y * ctx.hyperu(y-k, 1+2*m, z, **kwargs) + +@defun +def hyperu(ctx, a, b, z, **kwargs): + a, atype = ctx._convert_param(a) + b, btype = ctx._convert_param(b) + z = ctx.convert(z) + if not z: + if ctx.re(b) <= 1: + return ctx.gammaprod([1-b],[a-b+1]) + else: + return ctx.inf + z + bb = 1+a-b + bb, bbtype = ctx._convert_param(bb) + try: + orig = ctx.prec + try: + ctx.prec += 10 + v = ctx.hypsum(2, 0, (atype, bbtype), [a, bb], -1/z, maxterms=ctx.prec) + return v / z**a + finally: + ctx.prec = orig + except ctx.NoConvergence: + pass + def h(a,b): + w = ctx.sinpi(b) + T1 = ([ctx.pi,w],[1,-1],[],[a-b+1,b],[a],[b],z) + T2 = ([-ctx.pi,w,z],[1,-1,1-b],[],[a,2-b],[a-b+1],[2-b],z) + return T1, T2 + return ctx.hypercomb(h, [a,b], **kwargs) + +@defun +def struveh(ctx,n,z, **kwargs): + n = ctx.convert(n) + z = ctx.convert(z) + # http://functions.wolfram.com/Bessel-TypeFunctions/StruveH/26/01/02/ + def h(n): + return [([z/2, 0.5*ctx.sqrt(ctx.pi)], [n+1, -1], [], [n+1.5], [1], [1.5, n+1.5], -(z/2)**2)] + return ctx.hypercomb(h, [n], **kwargs) + +@defun +def struvel(ctx,n,z, **kwargs): + n = ctx.convert(n) + z = ctx.convert(z) + # http://functions.wolfram.com/Bessel-TypeFunctions/StruveL/26/01/02/ + def h(n): + return [([z/2, 0.5*ctx.sqrt(ctx.pi)], [n+1, -1], [], [n+1.5], [1], [1.5, n+1.5], (z/2)**2)] + return ctx.hypercomb(h, [n], **kwargs) + +def _anger(ctx,which,v,z,**kwargs): + v = ctx._convert_param(v)[0] + z = ctx.convert(z) + def h(v): + b = MPQ(1,2) + u = v*b + m = b*3 + a1,a2,b1,b2 = m-u, m+u, 1-u, 1+u + c, s = ctx.cospi_sinpi(u) + if which == 0: + A, B = [b*z, s], [c] + if which == 1: + A, B = [b*z, -c], [s] + w = ctx.square_exp_arg(z, mult=-0.25) + T1 = A, [1, 1], [], [a1,a2], [1], [a1,a2], w + T2 = B, [1], [], [b1,b2], [1], [b1,b2], w + return T1, T2 + return ctx.hypercomb(h, [v], **kwargs) + +@defun +def angerj(ctx, v, z, **kwargs): + return _anger(ctx, 0, v, z, **kwargs) + +@defun +def webere(ctx, v, z, **kwargs): + return _anger(ctx, 1, v, z, **kwargs) + +@defun +def lommels1(ctx, u, v, z, **kwargs): + u = ctx._convert_param(u)[0] + v = ctx._convert_param(v)[0] + z = ctx.convert(z) + def h(u,v): + b = MPQ(1,2) + w = ctx.square_exp_arg(z, mult=-0.25) + return ([u-v+1, u+v+1, z], [-1, -1, u+1], [], [], [1], \ + [b*(u-v+3),b*(u+v+3)], w), + return ctx.hypercomb(h, [u,v], **kwargs) + +@defun +def lommels2(ctx, u, v, z, **kwargs): + u = ctx._convert_param(u)[0] + v = ctx._convert_param(v)[0] + z = ctx.convert(z) + # Asymptotic expansion (GR p. 947) -- need to be careful + # not to use for small arguments + # def h(u,v): + # b = MPQ(1,2) + # w = -(z/2)**(-2) + # return ([z], [u-1], [], [], [b*(1-u+v)], [b*(1-u-v)], w), + def h(u,v): + b = MPQ(1,2) + w = ctx.square_exp_arg(z, mult=-0.25) + T1 = [u-v+1, u+v+1, z], [-1, -1, u+1], [], [], [1], [b*(u-v+3),b*(u+v+3)], w + T2 = [2, z], [u+v-1, -v], [v, b*(u+v+1)], [b*(v-u+1)], [], [1-v], w + T3 = [2, z], [u-v-1, v], [-v, b*(u-v+1)], [b*(1-u-v)], [], [1+v], w + #c1 = ctx.cospi((u-v)*b) + #c2 = ctx.cospi((u+v)*b) + #s = ctx.sinpi(v) + #r1 = (u-v+1)*b + #r2 = (u+v+1)*b + #T2 = [c1, s, z, 2], [1, -1, -v, v], [], [-v+1], [], [-v+1], w + #T3 = [-c2, s, z, 2], [1, -1, v, -v], [], [v+1], [], [v+1], w + #T2 = [c1, s, z, 2], [1, -1, -v, v+u-1], [r1, r2], [-v+1], [], [-v+1], w + #T3 = [-c2, s, z, 2], [1, -1, v, -v+u-1], [r1, r2], [v+1], [], [v+1], w + return T1, T2, T3 + return ctx.hypercomb(h, [u,v], **kwargs) + +@defun +def ber(ctx, n, z, **kwargs): + n = ctx.convert(n) + z = ctx.convert(z) + # http://functions.wolfram.com/Bessel-TypeFunctions/KelvinBer2/26/01/02/0001/ + def h(n): + r = -(z/4)**4 + cos, sin = ctx.cospi_sinpi(-0.75*n) + T1 = [cos, z/2], [1, n], [], [n+1], [], [0.5, 0.5*(n+1), 0.5*n+1], r + T2 = [sin, z/2], [1, n+2], [], [n+2], [], [1.5, 0.5*(n+3), 0.5*n+1], r + return T1, T2 + return ctx.hypercomb(h, [n], **kwargs) + +@defun +def bei(ctx, n, z, **kwargs): + n = ctx.convert(n) + z = ctx.convert(z) + # http://functions.wolfram.com/Bessel-TypeFunctions/KelvinBei2/26/01/02/0001/ + def h(n): + r = -(z/4)**4 + cos, sin = ctx.cospi_sinpi(0.75*n) + T1 = [cos, z/2], [1, n+2], [], [n+2], [], [1.5, 0.5*(n+3), 0.5*n+1], r + T2 = [sin, z/2], [1, n], [], [n+1], [], [0.5, 0.5*(n+1), 0.5*n+1], r + return T1, T2 + return ctx.hypercomb(h, [n], **kwargs) + +@defun +def ker(ctx, n, z, **kwargs): + n = ctx.convert(n) + z = ctx.convert(z) + # http://functions.wolfram.com/Bessel-TypeFunctions/KelvinKer2/26/01/02/0001/ + def h(n): + r = -(z/4)**4 + cos1, sin1 = ctx.cospi_sinpi(0.25*n) + cos2, sin2 = ctx.cospi_sinpi(0.75*n) + T1 = [2, z, 4*cos1], [-n-3, n, 1], [-n], [], [], [0.5, 0.5*(1+n), 0.5*(n+2)], r + T2 = [2, z, -sin1], [-n-3, 2+n, 1], [-n-1], [], [], [1.5, 0.5*(3+n), 0.5*(n+2)], r + T3 = [2, z, 4*cos2], [n-3, -n, 1], [n], [], [], [0.5, 0.5*(1-n), 1-0.5*n], r + T4 = [2, z, -sin2], [n-3, 2-n, 1], [n-1], [], [], [1.5, 0.5*(3-n), 1-0.5*n], r + return T1, T2, T3, T4 + return ctx.hypercomb(h, [n], **kwargs) + +@defun +def kei(ctx, n, z, **kwargs): + n = ctx.convert(n) + z = ctx.convert(z) + # http://functions.wolfram.com/Bessel-TypeFunctions/KelvinKei2/26/01/02/0001/ + def h(n): + r = -(z/4)**4 + cos1, sin1 = ctx.cospi_sinpi(0.75*n) + cos2, sin2 = ctx.cospi_sinpi(0.25*n) + T1 = [-cos1, 2, z], [1, n-3, 2-n], [n-1], [], [], [1.5, 0.5*(3-n), 1-0.5*n], r + T2 = [-sin1, 2, z], [1, n-1, -n], [n], [], [], [0.5, 0.5*(1-n), 1-0.5*n], r + T3 = [-sin2, 2, z], [1, -n-1, n], [-n], [], [], [0.5, 0.5*(n+1), 0.5*(n+2)], r + T4 = [-cos2, 2, z], [1, -n-3, n+2], [-n-1], [], [], [1.5, 0.5*(n+3), 0.5*(n+2)], r + return T1, T2, T3, T4 + return ctx.hypercomb(h, [n], **kwargs) + +# TODO: do this more generically? +def c_memo(f): + name = f.__name__ + def f_wrapped(ctx): + cache = ctx._misc_const_cache + prec = ctx.prec + p,v = cache.get(name, (-1,0)) + if p >= prec: + return +v + else: + cache[name] = (prec, f(ctx)) + return cache[name][1] + return f_wrapped + +@c_memo +def _airyai_C1(ctx): + return 1 / (ctx.cbrt(9) * ctx.gamma(ctx.mpf(2)/3)) + +@c_memo +def _airyai_C2(ctx): + return -1 / (ctx.cbrt(3) * ctx.gamma(ctx.mpf(1)/3)) + +@c_memo +def _airybi_C1(ctx): + return 1 / (ctx.nthroot(3,6) * ctx.gamma(ctx.mpf(2)/3)) + +@c_memo +def _airybi_C2(ctx): + return ctx.nthroot(3,6) / ctx.gamma(ctx.mpf(1)/3) + +def _airybi_n2_inf(ctx): + prec = ctx.prec + try: + v = ctx.power(3,'2/3')*ctx.gamma('2/3')/(2*ctx.pi) + finally: + ctx.prec = prec + return +v + +# Derivatives at z = 0 +# TODO: could be expressed more elegantly using triple factorials +def _airyderiv_0(ctx, z, n, ntype, which): + if ntype == 'Z': + if n < 0: + return z + r = MPQ(1,3) + prec = ctx.prec + try: + ctx.prec += 10 + v = ctx.gamma((n+1)*r) * ctx.power(3,n*r) / ctx.pi + if which == 0: + v *= ctx.sinpi(2*(n+1)*r) + v /= ctx.power(3,'2/3') + else: + v *= abs(ctx.sinpi(2*(n+1)*r)) + v /= ctx.power(3,'1/6') + finally: + ctx.prec = prec + return +v + z + else: + # singular (does the limit exist?) + raise NotImplementedError + +@defun +def airyai(ctx, z, derivative=0, **kwargs): + z = ctx.convert(z) + if derivative: + n, ntype = ctx._convert_param(derivative) + else: + n = 0 + # Values at infinities + if not ctx.isnormal(z) and z: + if n and ntype == 'Z': + if n == -1: + if z == ctx.inf: + return ctx.mpf(1)/3 + 1/z + if z == ctx.ninf: + return ctx.mpf(-2)/3 + 1/z + if n < -1: + if z == ctx.inf: + return z + if z == ctx.ninf: + return (-1)**n * (-z) + if (not n) and z == ctx.inf or z == ctx.ninf: + return 1/z + # TODO: limits + raise ValueError("essential singularity of Ai(z)") + # Account for exponential scaling + if z: + extraprec = max(0, int(1.5*ctx.mag(z))) + else: + extraprec = 0 + if n: + if n == 1: + def h(): + # http://functions.wolfram.com/03.07.06.0005.01 + if ctx._re(z) > 4: + ctx.prec += extraprec + w = z**1.5; r = -0.75/w; u = -2*w/3 + ctx.prec -= extraprec + C = -ctx.exp(u)/(2*ctx.sqrt(ctx.pi))*ctx.nthroot(z,4) + return ([C],[1],[],[],[(-1,6),(7,6)],[],r), + # http://functions.wolfram.com/03.07.26.0001.01 + else: + ctx.prec += extraprec + w = z**3 / 9 + ctx.prec -= extraprec + C1 = _airyai_C1(ctx) * 0.5 + C2 = _airyai_C2(ctx) + T1 = [C1,z],[1,2],[],[],[],[MPQ(5,3)],w + T2 = [C2],[1],[],[],[],[MPQ(1,3)],w + return T1, T2 + return ctx.hypercomb(h, [], **kwargs) + else: + if z == 0: + return _airyderiv_0(ctx, z, n, ntype, 0) + # http://functions.wolfram.com/03.05.20.0004.01 + def h(n): + ctx.prec += extraprec + w = z**3/9 + ctx.prec -= extraprec + q13,q23,q43 = MPQ(1,3), MPQ(2,3), MPQ(4,3) + a1=q13; a2=1; b1=(1-n)*q13; b2=(2-n)*q13; b3=1-n*q13 + T1 = [3, z], [n-q23, -n], [a1], [b1,b2,b3], \ + [a1,a2], [b1,b2,b3], w + a1=q23; b1=(2-n)*q13; b2=1-n*q13; b3=(4-n)*q13 + T2 = [3, z, -z], [n-q43, -n, 1], [a1], [b1,b2,b3], \ + [a1,a2], [b1,b2,b3], w + return T1, T2 + v = ctx.hypercomb(h, [n], **kwargs) + if ctx._is_real_type(z) and ctx.isint(n): + v = ctx._re(v) + return v + else: + def h(): + if ctx._re(z) > 4: + # We could use 1F1, but it results in huge cancellation; + # the following expansion is better. + # TODO: asymptotic series for derivatives + ctx.prec += extraprec + w = z**1.5; r = -0.75/w; u = -2*w/3 + ctx.prec -= extraprec + C = ctx.exp(u)/(2*ctx.sqrt(ctx.pi)*ctx.nthroot(z,4)) + return ([C],[1],[],[],[(1,6),(5,6)],[],r), + else: + ctx.prec += extraprec + w = z**3 / 9 + ctx.prec -= extraprec + C1 = _airyai_C1(ctx) + C2 = _airyai_C2(ctx) + T1 = [C1],[1],[],[],[],[MPQ(2,3)],w + T2 = [z*C2],[1],[],[],[],[MPQ(4,3)],w + return T1, T2 + return ctx.hypercomb(h, [], **kwargs) + +@defun +def airybi(ctx, z, derivative=0, **kwargs): + z = ctx.convert(z) + if derivative: + n, ntype = ctx._convert_param(derivative) + else: + n = 0 + # Values at infinities + if not ctx.isnormal(z) and z: + if n and ntype == 'Z': + if z == ctx.inf: + return z + if z == ctx.ninf: + if n == -1: + return 1/z + if n == -2: + return _airybi_n2_inf(ctx) + if n < -2: + return (-1)**n * (-z) + if not n: + if z == ctx.inf: + return z + if z == ctx.ninf: + return 1/z + # TODO: limits + raise ValueError("essential singularity of Bi(z)") + if z: + extraprec = max(0, int(1.5*ctx.mag(z))) + else: + extraprec = 0 + if n: + if n == 1: + # http://functions.wolfram.com/03.08.26.0001.01 + def h(): + ctx.prec += extraprec + w = z**3 / 9 + ctx.prec -= extraprec + C1 = _airybi_C1(ctx)*0.5 + C2 = _airybi_C2(ctx) + T1 = [C1,z],[1,2],[],[],[],[MPQ(5,3)],w + T2 = [C2],[1],[],[],[],[MPQ(1,3)],w + return T1, T2 + return ctx.hypercomb(h, [], **kwargs) + else: + if z == 0: + return _airyderiv_0(ctx, z, n, ntype, 1) + def h(n): + ctx.prec += extraprec + w = z**3/9 + ctx.prec -= extraprec + q13,q23,q43 = MPQ(1,3), MPQ(2,3), MPQ(4,3) + q16 = MPQ(1,6) + q56 = MPQ(5,6) + a1=q13; a2=1; b1=(1-n)*q13; b2=(2-n)*q13; b3=1-n*q13 + T1 = [3, z], [n-q16, -n], [a1], [b1,b2,b3], \ + [a1,a2], [b1,b2,b3], w + a1=q23; b1=(2-n)*q13; b2=1-n*q13; b3=(4-n)*q13 + T2 = [3, z], [n-q56, 1-n], [a1], [b1,b2,b3], \ + [a1,a2], [b1,b2,b3], w + return T1, T2 + v = ctx.hypercomb(h, [n], **kwargs) + if ctx._is_real_type(z) and ctx.isint(n): + v = ctx._re(v) + return v + else: + def h(): + ctx.prec += extraprec + w = z**3 / 9 + ctx.prec -= extraprec + C1 = _airybi_C1(ctx) + C2 = _airybi_C2(ctx) + T1 = [C1],[1],[],[],[],[MPQ(2,3)],w + T2 = [z*C2],[1],[],[],[],[MPQ(4,3)],w + return T1, T2 + return ctx.hypercomb(h, [], **kwargs) + +def _airy_zero(ctx, which, k, derivative, complex=False): + # Asymptotic formulas are given in DLMF section 9.9 + def U(t): return t**(2/3.)*(1-7/(t**2*48)) + def T(t): return t**(2/3.)*(1+5/(t**2*48)) + k = int(k) + if k < 1: + raise ValueError("k cannot be less than 1") + if derivative not in (0, 1): + raise ValueError("Derivative should lie between 0 and 1") + if which == 0: + if derivative: + return ctx.findroot(lambda z: ctx.airyai(z,1), + -U(3*ctx.pi*(4*k-3)/8)) + return ctx.findroot(ctx.airyai, -T(3*ctx.pi*(4*k-1)/8)) + if which == 1 and complex is False: + if derivative: + return ctx.findroot(lambda z: ctx.airybi(z,1), + -U(3*ctx.pi*(4*k-1)/8)) + return ctx.findroot(ctx.airybi, -T(3*ctx.pi*(4*k-3)/8)) + if which == 1 and complex is True: + if derivative: + t = 3*ctx.pi*(4*k-3)/8 + 0.75j*ctx.ln2 + s = ctx.expjpi(ctx.mpf(1)/3) * T(t) + return ctx.findroot(lambda z: ctx.airybi(z,1), s) + t = 3*ctx.pi*(4*k-1)/8 + 0.75j*ctx.ln2 + s = ctx.expjpi(ctx.mpf(1)/3) * U(t) + return ctx.findroot(ctx.airybi, s) + +@defun +def airyaizero(ctx, k, derivative=0): + return _airy_zero(ctx, 0, k, derivative, False) + +@defun +def airybizero(ctx, k, derivative=0, complex=False): + return _airy_zero(ctx, 1, k, derivative, complex) + +def _scorer(ctx, z, which, kwargs): + z = ctx.convert(z) + if ctx.isinf(z): + if z == ctx.inf: + if which == 0: return 1/z + if which == 1: return z + if z == ctx.ninf: + return 1/z + raise ValueError("essential singularity") + if z: + extraprec = max(0, int(1.5*ctx.mag(z))) + else: + extraprec = 0 + if kwargs.get('derivative'): + raise NotImplementedError + # Direct asymptotic expansions, to avoid + # exponentially large cancellation + try: + if ctx.mag(z) > 3: + if which == 0 and abs(ctx.arg(z)) < ctx.pi/3 * 0.999: + def h(): + return (([ctx.pi,z],[-1,-1],[],[],[(1,3),(2,3),1],[],9/z**3),) + return ctx.hypercomb(h, [], maxterms=ctx.prec, force_series=True) + if which == 1 and abs(ctx.arg(-z)) < 2*ctx.pi/3 * 0.999: + def h(): + return (([-ctx.pi,z],[-1,-1],[],[],[(1,3),(2,3),1],[],9/z**3),) + return ctx.hypercomb(h, [], maxterms=ctx.prec, force_series=True) + except ctx.NoConvergence: + pass + def h(): + A = ctx.airybi(z, **kwargs)/3 + B = -2*ctx.pi + if which == 1: + A *= 2 + B *= -1 + ctx.prec += extraprec + w = z**3/9 + ctx.prec -= extraprec + T1 = [A], [1], [], [], [], [], 0 + T2 = [B,z], [-1,2], [], [], [1], [MPQ(4,3),MPQ(5,3)], w + return T1, T2 + return ctx.hypercomb(h, [], **kwargs) + +@defun +def scorergi(ctx, z, **kwargs): + return _scorer(ctx, z, 0, kwargs) + +@defun +def scorerhi(ctx, z, **kwargs): + return _scorer(ctx, z, 1, kwargs) + +@defun_wrapped +def coulombc(ctx, l, eta, _cache={}): + if (l, eta) in _cache and _cache[l,eta][0] >= ctx.prec: + return +_cache[l,eta][1] + G3 = ctx.loggamma(2*l+2) + G1 = ctx.loggamma(1+l+ctx.j*eta) + G2 = ctx.loggamma(1+l-ctx.j*eta) + v = 2**l * ctx.exp((-ctx.pi*eta+G1+G2)/2 - G3) + if not (ctx.im(l) or ctx.im(eta)): + v = ctx.re(v) + _cache[l,eta] = (ctx.prec, v) + return v + +@defun_wrapped +def coulombf(ctx, l, eta, z, w=1, chop=True, **kwargs): + # Regular Coulomb wave function + # Note: w can be either 1 or -1; the other may be better in some cases + # TODO: check that chop=True chops when and only when it should + #ctx.prec += 10 + def h(l, eta): + try: + jw = ctx.j*w + jwz = ctx.fmul(jw, z, exact=True) + jwz2 = ctx.fmul(jwz, -2, exact=True) + C = ctx.coulombc(l, eta) + T1 = [C, z, ctx.exp(jwz)], [1, l+1, 1], [], [], [1+l+jw*eta], \ + [2*l+2], jwz2 + except ValueError: + T1 = [0], [-1], [], [], [], [], 0 + return (T1,) + v = ctx.hypercomb(h, [l,eta], **kwargs) + if chop and (not ctx.im(l)) and (not ctx.im(eta)) and (not ctx.im(z)) and \ + (ctx.re(z) >= 0): + v = ctx.re(v) + return v + +@defun_wrapped +def _coulomb_chi(ctx, l, eta, _cache={}): + if (l, eta) in _cache and _cache[l,eta][0] >= ctx.prec: + return _cache[l,eta][1] + def terms(): + l2 = -l-1 + jeta = ctx.j*eta + return [ctx.loggamma(1+l+jeta) * (-0.5j), + ctx.loggamma(1+l-jeta) * (0.5j), + ctx.loggamma(1+l2+jeta) * (0.5j), + ctx.loggamma(1+l2-jeta) * (-0.5j), + -(l+0.5)*ctx.pi] + v = ctx.sum_accurately(terms, 1) + _cache[l,eta] = (ctx.prec, v) + return v + +@defun_wrapped +def coulombg(ctx, l, eta, z, w=1, chop=True, **kwargs): + # Irregular Coulomb wave function + # Note: w can be either 1 or -1; the other may be better in some cases + # TODO: check that chop=True chops when and only when it should + if not ctx._im(l): + l = ctx._re(l) # XXX: for isint + def h(l, eta): + # Force perturbation for integers and half-integers + if ctx.isint(l*2): + T1 = [0], [-1], [], [], [], [], 0 + return (T1,) + l2 = -l-1 + try: + chi = ctx._coulomb_chi(l, eta) + jw = ctx.j*w + s = ctx.sin(chi); c = ctx.cos(chi) + C1 = ctx.coulombc(l,eta) + C2 = ctx.coulombc(l2,eta) + u = ctx.exp(jw*z) + x = -2*jw*z + T1 = [s, C1, z, u, c], [-1, 1, l+1, 1, 1], [], [], \ + [1+l+jw*eta], [2*l+2], x + T2 = [-s, C2, z, u], [-1, 1, l2+1, 1], [], [], \ + [1+l2+jw*eta], [2*l2+2], x + return T1, T2 + except ValueError: + T1 = [0], [-1], [], [], [], [], 0 + return (T1,) + v = ctx.hypercomb(h, [l,eta], **kwargs) + if chop and (not ctx._im(l)) and (not ctx._im(eta)) and (not ctx._im(z)) and \ + (ctx._re(z) >= 0): + v = ctx._re(v) + return v + +def mcmahon(ctx,kind,prime,v,m): + """ + Computes an estimate for the location of the Bessel function zero + j_{v,m}, y_{v,m}, j'_{v,m} or y'_{v,m} using McMahon's asymptotic + expansion (Abramowitz & Stegun 9.5.12-13, DLMF 20.21(vi)). + + Returns (r,err) where r is the estimated location of the root + and err is a positive number estimating the error of the + asymptotic expansion. + """ + u = 4*v**2 + if kind == 1 and not prime: b = (4*m+2*v-1)*ctx.pi/4 + if kind == 2 and not prime: b = (4*m+2*v-3)*ctx.pi/4 + if kind == 1 and prime: b = (4*m+2*v-3)*ctx.pi/4 + if kind == 2 and prime: b = (4*m+2*v-1)*ctx.pi/4 + if not prime: + s1 = b + s2 = -(u-1)/(8*b) + s3 = -4*(u-1)*(7*u-31)/(3*(8*b)**3) + s4 = -32*(u-1)*(83*u**2-982*u+3779)/(15*(8*b)**5) + s5 = -64*(u-1)*(6949*u**3-153855*u**2+1585743*u-6277237)/(105*(8*b)**7) + if prime: + s1 = b + s2 = -(u+3)/(8*b) + s3 = -4*(7*u**2+82*u-9)/(3*(8*b)**3) + s4 = -32*(83*u**3+2075*u**2-3039*u+3537)/(15*(8*b)**5) + s5 = -64*(6949*u**4+296492*u**3-1248002*u**2+7414380*u-5853627)/(105*(8*b)**7) + terms = [s1,s2,s3,s4,s5] + s = s1 + err = 0.0 + for i in range(1,len(terms)): + if abs(terms[i]) < abs(terms[i-1]): + s += terms[i] + else: + err = abs(terms[i]) + if i == len(terms)-1: + err = abs(terms[-1]) + return s, err + +def generalized_bisection(ctx,f,a,b,n): + """ + Given f known to have exactly n simple roots within [a,b], + return a list of n intervals isolating the roots + and having opposite signs at the endpoints. + + TODO: this can be optimized, e.g. by reusing evaluation points. + """ + if n < 1: + raise ValueError("n cannot be less than 1") + N = n+1 + points = [] + signs = [] + while 1: + points = ctx.linspace(a,b,N) + signs = [ctx.sign(f(x)) for x in points] + ok_intervals = [(points[i],points[i+1]) for i in range(N-1) \ + if signs[i]*signs[i+1] == -1] + if len(ok_intervals) == n: + return ok_intervals + N = N*2 + +def find_in_interval(ctx, f, ab): + return ctx.findroot(f, ab, solver='illinois') + +def bessel_zero(ctx, kind, prime, v, m, isoltol=0.01, _interval_cache={}): + prec = ctx.prec + workprec = max(prec, ctx.mag(v), ctx.mag(m))+10 + try: + ctx.prec = workprec + v = ctx.mpf(v) + m = int(m) + prime = int(prime) + if v < 0: + raise ValueError("v cannot be negative") + if m < 1: + raise ValueError("m cannot be less than 1") + if prime not in (0, 1): + raise ValueError("prime should lie between 0 and 1") + if kind == 1: + if prime: f = lambda x: ctx.besselj(v,x,derivative=1) + else: f = lambda x: ctx.besselj(v,x) + if kind == 2: + if prime: f = lambda x: ctx.bessely(v,x,derivative=1) + else: f = lambda x: ctx.bessely(v,x) + # The first root of J' is very close to 0 for small + # orders, and this needs to be special-cased + if kind == 1 and prime and m == 1: + if v == 0: + return ctx.zero + if v <= 1: + # TODO: use v <= j'_{v,1} < y_{v,1}? + r = 2*ctx.sqrt(v*(1+v)/(v+2)) + return find_in_interval(ctx, f, (r/10, 2*r)) + if (kind,prime,v,m) in _interval_cache: + return find_in_interval(ctx, f, _interval_cache[kind,prime,v,m]) + r, err = mcmahon(ctx, kind, prime, v, m) + if err < isoltol: + return find_in_interval(ctx, f, (r-isoltol, r+isoltol)) + # An x such that 0 < x < r_{v,1} + if kind == 1 and not prime: low = 2.4 + if kind == 1 and prime: low = 1.8 + if kind == 2 and not prime: low = 0.8 + if kind == 2 and prime: low = 2.0 + n = m+1 + while 1: + r1, err = mcmahon(ctx, kind, prime, v, n) + if err < isoltol: + r2, err2 = mcmahon(ctx, kind, prime, v, n+1) + intervals = generalized_bisection(ctx, f, low, 0.5*(r1+r2), n) + for k, ab in enumerate(intervals): + _interval_cache[kind,prime,v,k+1] = ab + return find_in_interval(ctx, f, intervals[m-1]) + else: + n = n*2 + finally: + ctx.prec = prec + +@defun +def besseljzero(ctx, v, m, derivative=0): + r""" + For a real order `\nu \ge 0` and a positive integer `m`, returns + `j_{\nu,m}`, the `m`-th positive zero of the Bessel function of the + first kind `J_{\nu}(z)` (see :func:`~mpmath.besselj`). Alternatively, + with *derivative=1*, gives the first nonnegative simple zero + `j'_{\nu,m}` of `J'_{\nu}(z)`. + + The indexing convention is that used by Abramowitz & Stegun + and the DLMF. Note the special case `j'_{0,1} = 0`, while all other + zeros are positive. In effect, only simple zeros are counted + (all zeros of Bessel functions are simple except possibly `z = 0`) + and `j_{\nu,m}` becomes a monotonic function of both `\nu` + and `m`. + + The zeros are interlaced according to the inequalities + + .. math :: + + j'_{\nu,k} < j_{\nu,k} < j'_{\nu,k+1} + + j_{\nu,1} < j_{\nu+1,2} < j_{\nu,2} < j_{\nu+1,2} < j_{\nu,3} < \cdots + + **Examples** + + Initial zeros of the Bessel functions `J_0(z), J_1(z), J_2(z)`:: + + >>> from mpmath import mp, besseljzero, mpf, gamma, nprod, inf, besselj + >>> mp.dps = 25 + >>> mp.pretty = True + >>> besseljzero(0,1) + 2.404825557695772768621632 + >>> besseljzero(0,2) + 5.520078110286310649596604 + >>> besseljzero(0,3) + 8.653727912911012216954199 + >>> besseljzero(1,1) + 3.831705970207512315614436 + >>> besseljzero(1,2) + 7.01558666981561875353705 + >>> besseljzero(1,3) + 10.17346813506272207718571 + >>> besseljzero(2,1) + 5.135622301840682556301402 + >>> besseljzero(2,2) + 8.417244140399864857783614 + >>> besseljzero(2,3) + 11.61984117214905942709415 + + Initial zeros of `J'_0(z), J'_1(z), J'_2(z)`:: + + 0.0 + 3.831705970207512315614436 + 7.01558666981561875353705 + >>> besseljzero(1,1,1) + 1.84118378134065930264363 + >>> besseljzero(1,2,1) + 5.331442773525032636884016 + >>> besseljzero(1,3,1) + 8.536316366346285834358961 + >>> besseljzero(2,1,1) + 3.054236928227140322755932 + >>> besseljzero(2,2,1) + 6.706133194158459146634394 + >>> besseljzero(2,3,1) + 9.969467823087595793179143 + + Zeros with large index:: + + >>> besseljzero(0,100) + 313.3742660775278447196902 + >>> besseljzero(0,1000) + 3140.807295225078628895545 + >>> besseljzero(0,10000) + 31415.14114171350798533666 + >>> besseljzero(5,100) + 321.1893195676003157339222 + >>> besseljzero(5,1000) + 3148.657306813047523500494 + >>> besseljzero(5,10000) + 31422.9947255486291798943 + >>> besseljzero(0,100,1) + 311.8018681873704508125112 + >>> besseljzero(0,1000,1) + 3139.236339643802482833973 + >>> besseljzero(0,10000,1) + 31413.57032947022399485808 + + Zeros of functions with large order:: + + >>> besseljzero(50,1) + 57.11689916011917411936228 + >>> besseljzero(50,2) + 62.80769876483536093435393 + >>> besseljzero(50,100) + 388.6936600656058834640981 + >>> besseljzero(50,1,1) + 52.99764038731665010944037 + >>> besseljzero(50,2,1) + 60.02631933279942589882363 + >>> besseljzero(50,100,1) + 387.1083151608726181086283 + + Zeros of functions with fractional order:: + + >>> besseljzero(0.5,1) + 3.141592653589793238462643 + >>> besseljzero(1.5,1) + 4.493409457909064175307881 + >>> besseljzero(2.25,4) + 15.15657692957458622921634 + + Both `J_{\nu}(z)` and `J'_{\nu}(z)` can be expressed as infinite + products over their zeros:: + + >>> v,z = 2, mpf(1) + >>> (z/2)**v/gamma(v+1) * \ + ... nprod(lambda k: 1-(z/besseljzero(v,k))**2, [1,inf]) + ... + 0.1149034849319004804696469 + >>> besselj(v,z) + 0.1149034849319004804696469 + >>> (z/2)**(v-1)/2/gamma(v) * \ + ... nprod(lambda k: 1-(z/besseljzero(v,k,1))**2, [1,inf]) + ... + 0.2102436158811325550203884 + >>> besselj(v,z,1) + 0.2102436158811325550203884 + + """ + return +bessel_zero(ctx, 1, derivative, v, m) + +@defun +def besselyzero(ctx, v, m, derivative=0): + r""" + For a real order `\nu \ge 0` and a positive integer `m`, returns + `y_{\nu,m}`, the `m`-th positive zero of the Bessel function of the + second kind `Y_{\nu}(z)` (see :func:`~mpmath.bessely`). Alternatively, + with *derivative=1*, gives the first positive zero `y'_{\nu,m}` of + `Y'_{\nu}(z)`. + + The zeros are interlaced according to the inequalities + + .. math :: + + y_{\nu,k} < y'_{\nu,k} < y_{\nu,k+1} + + y_{\nu,1} < y_{\nu+1,2} < y_{\nu,2} < y_{\nu+1,2} < y_{\nu,3} < \cdots + + **Examples** + + Initial zeros of the Bessel functions `Y_0(z), Y_1(z), Y_2(z)`:: + + >>> from mpmath import mp, besselyzero + >>> mp.dps = 25 + >>> mp.pretty = True + >>> besselyzero(0,1) + 0.8935769662791675215848871 + >>> besselyzero(0,2) + 3.957678419314857868375677 + >>> besselyzero(0,3) + 7.086051060301772697623625 + >>> besselyzero(1,1) + 2.197141326031017035149034 + >>> besselyzero(1,2) + 5.429681040794135132772005 + >>> besselyzero(1,3) + 8.596005868331168926429606 + >>> besselyzero(2,1) + 3.384241767149593472701426 + >>> besselyzero(2,2) + 6.793807513268267538291167 + >>> besselyzero(2,3) + 10.02347797936003797850539 + + Initial zeros of `Y'_0(z), Y'_1(z), Y'_2(z)`:: + + >>> besselyzero(0,1,1) + 2.197141326031017035149034 + >>> besselyzero(0,2,1) + 5.429681040794135132772005 + >>> besselyzero(0,3,1) + 8.596005868331168926429606 + >>> besselyzero(1,1,1) + 3.683022856585177699898967 + >>> besselyzero(1,2,1) + 6.941499953654175655751944 + >>> besselyzero(1,3,1) + 10.12340465543661307978775 + >>> besselyzero(2,1,1) + 5.002582931446063945200176 + >>> besselyzero(2,2,1) + 8.350724701413079526349714 + >>> besselyzero(2,3,1) + 11.57419546521764654624265 + + Zeros with large index:: + + >>> besselyzero(0,100) + 311.8034717601871549333419 + >>> besselyzero(0,1000) + 3139.236498918198006794026 + >>> besselyzero(0,10000) + 31413.57034538691205229188 + >>> besselyzero(5,100) + 319.6183338562782156235062 + >>> besselyzero(5,1000) + 3147.086508524556404473186 + >>> besselyzero(5,10000) + 31421.42392920214673402828 + >>> besselyzero(0,100,1) + 313.3726705426359345050449 + >>> besselyzero(0,1000,1) + 3140.807136030340213610065 + >>> besselyzero(0,10000,1) + 31415.14112579761578220175 + + Zeros of functions with large order:: + + >>> besselyzero(50,1) + 53.50285882040036394680237 + >>> besselyzero(50,2) + 60.11244442774058114686022 + >>> besselyzero(50,100) + 387.1096509824943957706835 + >>> besselyzero(50,1,1) + 56.96290427516751320063605 + >>> besselyzero(50,2,1) + 62.74888166945933944036623 + >>> besselyzero(50,100,1) + 388.6923300548309258355475 + + Zeros of functions with fractional order:: + + >>> besselyzero(0.5,1) + 1.570796326794896619231322 + >>> besselyzero(1.5,1) + 2.798386045783887136720249 + >>> besselyzero(2.25,4) + 13.56721208770735123376018 + + """ + return +bessel_zero(ctx, 2, derivative, v, m) diff --git a/mpmath/functions/elliptic.py b/mpmath/functions/elliptic.py new file mode 100644 index 0000000..2592462 --- /dev/null +++ b/mpmath/functions/elliptic.py @@ -0,0 +1,1977 @@ +r""" +Elliptic functions historically comprise the elliptic integrals +and their inverses, and originate from the problem of computing the +arc length of an ellipse. From a more modern point of view, +an elliptic function is defined as a doubly periodic function, i.e. +a function which satisfies + +.. math :: + + f(z + 2 \omega_1) = f(z + 2 \omega_2) = f(z) + +for some half-periods `\omega_1, \omega_2` with +`\mathrm{Im}[\omega_1 / \omega_2] > 0`. The canonical elliptic +functions are the Jacobi elliptic functions. More broadly, this section +includes quasi-doubly periodic functions (such as the Jacobi theta +functions) and other functions useful in the study of elliptic functions. + +Many different conventions for the arguments of +elliptic functions are in use. It is even standard to use +different parameterizations for different functions in the same +text or software (and mpmath is no exception). +The usual parameters are the elliptic nome `q`, which usually +must satisfy `|q| < 1`; the elliptic parameter `m` (an arbitrary +complex number); the elliptic modulus `k` (an arbitrary complex +number); and the half-period ratio `\tau`, which usually must +satisfy `\mathrm{Im}[\tau] > 0`. +These quantities can be expressed in terms of each other +using the following relations: + +.. math :: + + m = k^2 + +.. math :: + + \tau = i \frac{K(1-m)}{K(m)} + +.. math :: + + q = e^{i \pi \tau} + +.. math :: + + k = \frac{\vartheta_2^2(q)}{\vartheta_3^2(q)} + +In addition, an alternative definition is used for the nome in +number theory, which we here denote by q-bar: + +.. math :: + + \bar{q} = q^2 = e^{2 i \pi \tau} + +For convenience, mpmath provides functions to convert +between the various parameters (:func:`~mpmath.qfrom`, :func:`~mpmath.mfrom`, +:func:`~mpmath.kfrom`, :func:`~mpmath.taufrom`, :func:`~mpmath.qbarfrom`). + +**References** + +1. [AbramowitzStegun]_ + +2. [WhittakerWatson]_ + +""" + +from .functions import defun, defun_wrapped + +@defun_wrapped +def eta(ctx, tau): + r""" + Returns the Dedekind eta function of tau in the upper half-plane. + + >>> from mpmath import mp, eta, gamma, pi, sqrt, diff, chop, exp + >>> mp.dps = 25 + >>> mp.pretty = True + >>> eta(1j) + (0.7682254223260566590025942 + 0.0j) + >>> gamma(0.25) / (2*pi**0.75) + 0.7682254223260566590025942 + >>> tau = sqrt(2) + sqrt(5)*1j + >>> eta(-1/tau) + (0.9022859908439376463573294 + 0.07985093673948098408048575j) + >>> sqrt(-1j*tau) * eta(tau) + (0.9022859908439376463573295 + 0.07985093673948098408048575j) + >>> eta(tau+1) + (0.4493066139717553786223114 + 0.3290014793877986663915939j) + >>> exp(pi*1j/12) * eta(tau) + (0.4493066139717553786223114 + 0.3290014793877986663915939j) + >>> f = lambda z: diff(eta, z) / eta(z) + >>> chop(36*diff(f,tau)**2 - 24*diff(f,tau,2)*f(tau) + diff(f,tau,3)) + 0.0 + + """ + if ctx.im(tau) <= 0.0: + raise ValueError("eta is only defined in the upper half-plane") + q = ctx.expjpi(tau/12) + return q * ctx.qp(q**24) + +def nome(ctx, m): + m = ctx.convert(m) + if not m: + return m + if m == ctx.one: + return m + if ctx.isnan(m): + return m + if ctx.isinf(m): + if m == ctx.ninf: + return -ctx.one + else: + return ctx.mpc(-1) + a = ctx.ellipk(ctx.one-m) + b = ctx.ellipk(m) + v = ctx.exp(-ctx.pi*a/b) + if not ctx._im(m) and ctx._re(m) < 1: + if ctx._is_real_type(m): + return v.real + else: + return v.real + 0j + elif m == 2: + v = ctx.mpc(0, v.imag) + return v + +@defun_wrapped +def qfrom(ctx, q=None, m=None, k=None, tau=None, qbar=None): + r""" + Returns the elliptic nome `q`, given any of `q, m, k, \tau, \bar{q}`:: + + >>> from mpmath import mp, qfrom, mfrom, kfrom, taufrom, qbarfrom + >>> mp.dps = 25 + >>> mp.pretty = True + >>> qfrom(q=0.25) + 0.25 + >>> qfrom(m=mfrom(q=0.25)) + 0.25 + >>> qfrom(k=kfrom(q=0.25)) + 0.25 + >>> qfrom(tau=taufrom(q=0.25)) + (0.25 + 0.0j) + >>> qfrom(qbar=qbarfrom(q=0.25)) + 0.25 + + """ + if q is not None: + return ctx.convert(q) + if m is not None: + return nome(ctx, m) + if k is not None: + return nome(ctx, ctx.convert(k)**2) + if tau is not None: + return ctx.expjpi(tau) + if qbar is not None: + return ctx.sqrt(qbar) + +@defun_wrapped +def qbarfrom(ctx, q=None, m=None, k=None, tau=None, qbar=None): + r""" + Returns the number-theoretic nome `\bar q`, given any of + `q, m, k, \tau, \bar{q}`:: + + >>> from mpmath import (mp, qbarfrom, qfrom, extraprec, mfrom, + ... kfrom, taufrom) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> qbarfrom(qbar=0.25) + 0.25 + >>> qbarfrom(q=qfrom(qbar=0.25)) + 0.25 + >>> qbarfrom(m=extraprec(20)(mfrom)(qbar=0.25)) # ill-conditioned + 0.25 + >>> qbarfrom(k=extraprec(20)(kfrom)(qbar=0.25)) # ill-conditioned + 0.25 + >>> qbarfrom(tau=taufrom(qbar=0.25)) + (0.25 + 0.0j) + + """ + if qbar is not None: + return ctx.convert(qbar) + if q is not None: + return ctx.convert(q) ** 2 + if m is not None: + return nome(ctx, m) ** 2 + if k is not None: + return nome(ctx, ctx.convert(k)**2) ** 2 + if tau is not None: + return ctx.expjpi(2*tau) + +@defun_wrapped +def taufrom(ctx, q=None, m=None, k=None, tau=None, qbar=None): + r""" + Returns the elliptic half-period ratio `\tau`, given any of + `q, m, k, \tau, \bar{q}`:: + + >>> from mpmath import mp, taufrom, qfrom, mfrom, kfrom, qbarfrom + >>> mp.dps = 25 + >>> mp.pretty = True + >>> taufrom(tau=0.5j) + (0.0 + 0.5j) + >>> taufrom(q=qfrom(tau=0.5j)) + (0.0 + 0.5j) + >>> taufrom(m=mfrom(tau=0.5j)) + (0.0 + 0.5j) + >>> taufrom(k=kfrom(tau=0.5j)) + (0.0 + 0.5j) + >>> taufrom(qbar=qbarfrom(tau=0.5j)) + (0.0 + 0.5j) + + """ + if tau is not None: + return ctx.convert(tau) + if m is not None: + m = ctx.convert(m) + return ctx.j*ctx.ellipk(1-m)/ctx.ellipk(m) + if k is not None: + k = ctx.convert(k) + return ctx.j*ctx.ellipk(1-k**2)/ctx.ellipk(k**2) + if q is not None: + return ctx.log(q) / (ctx.pi*ctx.j) + if qbar is not None: + qbar = ctx.convert(qbar) + return ctx.log(qbar) / (2*ctx.pi*ctx.j) + +@defun_wrapped +def kfrom(ctx, q=None, m=None, k=None, tau=None, qbar=None): + r""" + Returns the elliptic modulus `k`, given any of + `q, m, k, \tau, \bar{q}`:: + + >>> from mpmath import mp, kfrom, mfrom, qfrom, taufrom, qbarfrom + >>> mp.dps = 25 + >>> mp.pretty = True + >>> kfrom(k=0.25) + 0.25 + >>> kfrom(m=mfrom(k=0.25)) + 0.25 + >>> kfrom(q=qfrom(k=0.25)) + 0.25 + >>> kfrom(tau=taufrom(k=0.25)) + (0.25 + 0.0j) + >>> kfrom(qbar=qbarfrom(k=0.25)) + 0.25 + + As `q \to 1` and `q \to -1`, `k` rapidly approaches + `1` and `i \infty` respectively:: + + >>> kfrom(q=0.75) + 0.9999999999999899166471767 + >>> kfrom(q=-0.75) + (0.0 + 7041781.096692038332790615j) + >>> kfrom(q=1) + 1 + >>> kfrom(q=-1) + (0.0 + infj) + """ + if k is not None: + return ctx.convert(k) + if m is not None: + return ctx.sqrt(m) + if tau is not None: + q = ctx.expjpi(tau) + if qbar is not None: + q = ctx.sqrt(qbar) + if q == 1: + return q + if q == -1: + return ctx.mpc(0,'inf') + return (ctx.jtheta(2,0,q)/ctx.jtheta(3,0,q))**2 + +@defun_wrapped +def mfrom(ctx, q=None, m=None, k=None, tau=None, qbar=None): + r""" + Returns the elliptic parameter `m`, given any of + `q, m, k, \tau, \bar{q}`:: + + >>> from mpmath import mp, mfrom, qfrom, kfrom, taufrom, qbarfrom, taylor + >>> mp.dps = 25 + >>> mp.pretty = True + >>> mfrom(m=0.25) + 0.25 + >>> mfrom(q=qfrom(m=0.25)) + 0.25 + >>> mfrom(k=kfrom(m=0.25)) + 0.25 + >>> mfrom(tau=taufrom(m=0.25)) + (0.25 + 0.0j) + >>> mfrom(qbar=qbarfrom(m=0.25)) + 0.25 + + As `q \to 1` and `q \to -1`, `m` rapidly approaches + `1` and `-\infty` respectively:: + + >>> mfrom(q=0.75) + 0.9999999999999798332943533 + >>> mfrom(q=-0.75) + -49586681013729.32611558353 + >>> mfrom(q=1) + 1.0 + >>> mfrom(q=-1) + -inf + + The inverse nome as a function of `q` has an integer + Taylor series expansion:: + + >>> taylor(lambda q: mfrom(q), 0, 7) + [0.0, 16.0, -128.0, 704.0, -3072.0, 11488.0, -38400.0, 117632.0] + + """ + if m is not None: + return m + if k is not None: + return k**2 + if tau is not None: + q = ctx.expjpi(tau) + if qbar is not None: + q = ctx.sqrt(qbar) + if q == 1: + return ctx.convert(q) + if q == -1: + return q*ctx.inf + v = (ctx.jtheta(2,0,q)/ctx.jtheta(3,0,q))**4 + if ctx._is_real_type(q) and q < 0: + v = v.real + return v + +jacobi_spec = { + 'sn' : ([3],[2],[1],[4], 'sin', 'tanh'), + 'cn' : ([4],[2],[2],[4], 'cos', 'sech'), + 'dn' : ([4],[3],[3],[4], '1', 'sech'), + 'ns' : ([2],[3],[4],[1], 'csc', 'coth'), + 'nc' : ([2],[4],[4],[2], 'sec', 'cosh'), + 'nd' : ([3],[4],[4],[3], '1', 'cosh'), + 'sc' : ([3],[4],[1],[2], 'tan', 'sinh'), + 'sd' : ([3,3],[2,4],[1],[3], 'sin', 'sinh'), + 'cd' : ([3],[2],[2],[3], 'cos', '1'), + 'cs' : ([4],[3],[2],[1], 'cot', 'csch'), + 'dc' : ([2],[3],[3],[2], 'sec', '1'), + 'ds' : ([2,4],[3,3],[3],[1], 'csc', 'csch'), + 'cc' : None, + 'ss' : None, + 'nn' : None, + 'dd' : None +} + +@defun +def ellipfun(ctx, kind, u=None, m=None, q=None, k=None, tau=None): + try: + S = jacobi_spec[kind] + except KeyError: + raise ValueError("First argument must be a two-character string " + "containing 's', 'c', 'd' or 'n', e.g.: 'sn'") + if u is None: + def f(*args, **kwargs): + return ctx.ellipfun(kind, *args, **kwargs) + f.__name__ = kind + return f + prec = ctx.prec + try: + ctx.prec += 10 + u = ctx.convert(u) + q = ctx.qfrom(m=m, q=q, k=k, tau=tau) + if S is None: + v = ctx.one + 0*q*u + elif q == ctx.zero: + if S[4] == '1': v = ctx.one + else: v = getattr(ctx, S[4])(u) + v += 0*q*u + elif q == ctx.one: + if S[5] == '1': v = ctx.one + else: v = getattr(ctx, S[5])(u) + v += 0*q*u + else: + t = u / ctx.jtheta(3, 0, q)**2 + v = ctx.one + for a in S[0]: v *= ctx.jtheta(a, 0, q) + for b in S[1]: v /= ctx.jtheta(b, 0, q) + for c in S[2]: v *= ctx.jtheta(c, t, q) + for d in S[3]: v /= ctx.jtheta(d, t, q) + finally: + ctx.prec = prec + return +v + +@defun_wrapped +def kleinj(ctx, tau=None, **kwargs): + r""" + Evaluates the Klein j-invariant, which is a modular function defined for + `\tau` in the upper half-plane as + + .. math :: + + J(\tau) = \frac{g_2^3(\tau)}{g_2^3(\tau) - 27 g_3^2(\tau)} + + where `g_2` and `g_3` are the modular invariants of the Weierstrass + elliptic function, + + .. math :: + + g_2(\tau) = 60 \sum_{(m,n) \in \mathbb{Z}^2 \setminus (0,0)} (m \tau+n)^{-4} + + g_3(\tau) = 140 \sum_{(m,n) \in \mathbb{Z}^2 \setminus (0,0)} (m \tau+n)^{-6}. + + An alternative, common notation is that of the j-function + `j(\tau) = 1728 J(\tau)`. + + **Plots** + + .. literalinclude :: /plots/kleinj.py + .. image :: /plots/kleinj.png + .. literalinclude :: /plots/kleinj2.py + .. image :: /plots/kleinj2.png + + **Examples** + + Verifying the functional equation `J(\tau) = J(\tau+1) = J(-\tau^{-1})`:: + + >>> from mpmath import (mp, j, kleinj, taylor, sqrt, extraprec, + ... chop, identify, cbrt) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> tau = 0.625+0.75*j + >>> tau = 0.625+0.75*j + >>> kleinj(tau) + (-0.1507492166511182267125242 + 0.07595948379084571927228948j) + >>> kleinj(tau+1) + (-0.1507492166511182267125242 + 0.07595948379084571927228948j) + >>> kleinj(-1/tau) + (-0.1507492166511182267125242 + 0.07595948379084571927228946j) + + The j-function has a famous Laurent series expansion in terms of the nome + `\bar{q}`, `j(\tau) = \bar{q}^{-1} + 744 + 196884\bar{q} + \ldots`:: + + >>> taylor(lambda q: 1728*q*kleinj(qbar=q), 0, 5, singular=True) + [1.0, 744.0, 196884.0, 21493760.0, 864299970.0, 20245856256.0] + + The j-function admits exact evaluation at special algebraic points + related to the Heegner numbers 1, 2, 3, 7, 11, 19, 43, 67, 163:: + + >>> @extraprec(10) + ... def h(n): + ... v = (1+sqrt(n)*j) + ... if n > 2: + ... v *= 0.5 + ... return v + ... + >>> mp.dps = 25 + >>> for n in [1,2,3,7,11,19,43,67,163]: + ... n, chop(1728*kleinj(h(n))) + ... + (1, 1728.0) + (2, 8000.0) + (3, 0.0) + (7, -3375.0) + (11, -32768.0) + (19, -884736.0) + (43, -884736000.0) + (67, -147197952000.0) + (163, -262537412640768000.0) + + Also at other special points, the j-function assumes explicit + algebraic values, e.g.:: + + >>> chop(1728*kleinj(j*sqrt(5))) + 1264538.909475140509320227 + >>> identify(cbrt(_)) # note: not simplified + '((100+sqrt(13520))/2)' + >>> (50+26*sqrt(5))**3 + 1264538.909475140509320227 + + """ + q = ctx.qfrom(tau=tau, **kwargs) + t2 = ctx.jtheta(2,0,q) + t3 = ctx.jtheta(3,0,q) + t4 = ctx.jtheta(4,0,q) + P = (t2**8 + t3**8 + t4**8)**3 + Q = 54*(t2*t3*t4)**8 + return P/Q + + +def RF_calc(ctx, x, y, z, r): + if y == z: return RC_calc(ctx, x, y, r) + if x == z: return RC_calc(ctx, y, x, r) + if x == y: return RC_calc(ctx, z, x, r) + if not (ctx.isnormal(x) and ctx.isnormal(y) and ctx.isnormal(z)): + if ctx.isnan(x) or ctx.isnan(y) or ctx.isnan(z): + return x*y*z + if ctx.isinf(x) or ctx.isinf(y) or ctx.isinf(z): + return ctx.zero + xm,ym,zm = x,y,z + A0 = Am = (x+y+z)/3 + Q = ctx.root(3*r, -6) * max(abs(A0-x),abs(A0-y),abs(A0-z)) + g = ctx.mpf(0.25) + pow4 = ctx.one + while 1: + xs = ctx.sqrt(xm) + ys = ctx.sqrt(ym) + zs = ctx.sqrt(zm) + lm = xs*ys + xs*zs + ys*zs + Am1 = (Am+lm)*g + xm, ym, zm = (xm+lm)*g, (ym+lm)*g, (zm+lm)*g + if pow4 * Q < abs(Am): + break + Am = Am1 + pow4 *= g + t = pow4/Am + X = (A0-x)*t + Y = (A0-y)*t + Z = -X-Y + E2 = X*Y-Z**2 + E3 = X*Y*Z + return ctx.power(Am,-0.5) * (9240-924*E2+385*E2**2+660*E3-630*E2*E3)/9240 + +def RC_calc(ctx, x, y, r, pv=True): + if not (ctx.isnormal(x) and ctx.isnormal(y)): + if ctx.isinf(x) or ctx.isinf(y): + return 1/(x*y) + if y == 0: + return ctx.inf + if x == 0: + return ctx.pi / ctx.sqrt(y) / 2 + raise ValueError + # Cauchy principal value + if pv and ctx._im(y) == 0 and ctx._re(y) < 0: + return ctx.sqrt(x/(x-y)) * RC_calc(ctx, x-y, -y, r) + if x == y: + return 1/ctx.sqrt(x) + extraprec = 2*max(0,-ctx.mag(x-y)+ctx.mag(x)) + ctx.prec += extraprec + if ctx._is_real_type(x) and ctx._is_real_type(y): + x = ctx._re(x) + y = ctx._re(y) + a = ctx.sqrt(x/y) + if x < y: + b = ctx.sqrt(y-x) + v = ctx.acos(a)/b + else: + b = ctx.sqrt(x-y) + v = ctx.acosh(a)/b + else: + sx = ctx.sqrt(x) + sy = ctx.sqrt(y) + v = ctx.acos(sx/sy)/(ctx.sqrt((1-x/y))*sy) + ctx.prec -= extraprec + return v + +def RJ_calc(ctx, x, y, z, p, r, integration): + """ + With integration == 0, computes RJ only using Carlson's algorithm + (may be wrong for some values). + With integration == 1, uses an initial integration to make sure + Carlson's algorithm is correct. + With integration == 2, uses only integration. + """ + if not (ctx.isnormal(x) and ctx.isnormal(y) and \ + ctx.isnormal(z) and ctx.isnormal(p)): + if ctx.isnan(x) or ctx.isnan(y) or ctx.isnan(z) or ctx.isnan(p): + return x*y*z*p + if ctx.isinf(x) or ctx.isinf(y) or ctx.isinf(z) or ctx.isinf(p): + return ctx.zero + if not p: + return ctx.inf + if (not x) + (not y) + (not z) > 1: + return ctx.inf + # Check conditions and fall back on integration for argument + # reduction if needed. The following conditions might be needlessly + # restrictive. + initial_integral = ctx.zero + if integration >= 1: + ok = (x.real >= 0 and y.real >= 0 and z.real >= 0 and p.real > 0) + if not ok: + if x == p or y == p or z == p: + ok = True + if not ok: + if p.imag != 0 or p.real >= 0: + if (x.imag == 0 and x.real >= 0 and ctx.conj(y) == z): + ok = True + if (y.imag == 0 and y.real >= 0 and ctx.conj(x) == z): + ok = True + if (z.imag == 0 and z.real >= 0 and ctx.conj(x) == y): + ok = True + if not ok or (integration == 2): + N = ctx.ceil(-min(x.real, y.real, z.real, p.real)) + 1 + # Integrate around any singularities + if all((t.imag >= 0 or t.real > 0) for t in [x, y, z, p]): + margin = ctx.j + elif all((t.imag < 0 or t.real > 0) for t in [x, y, z, p]): + margin = -ctx.j + else: + margin = 1 + # Go through the upper half-plane, but low enough that any + # parameter starting in the lower plane doesn't cross the + # branch cut + for t in [x, y, z, p]: + if t.imag >= 0 or t.real > 0: + continue + margin = min(margin, abs(t.imag) * 0.5) + margin *= ctx.j + N += margin + F = lambda t: 1/(ctx.sqrt(t+x)*ctx.sqrt(t+y)*ctx.sqrt(t+z)*(t+p)) + if integration == 2: + return 1.5 * ctx.quadsubdiv(F, [0, N, ctx.inf]) + initial_integral = 1.5 * ctx.quadsubdiv(F, [0, N]) + x += N; y += N; z += N; p += N + xm,ym,zm,pm = x,y,z,p + A0 = Am = (x + y + z + 2*p)/5 + delta = (p-x)*(p-y)*(p-z) + Q = ctx.root(0.25*r, -6) * max(abs(A0-x),abs(A0-y),abs(A0-z),abs(A0-p)) + g = ctx.mpf(0.25) + pow4 = ctx.one + S = 0 + while 1: + sx = ctx.sqrt(xm) + sy = ctx.sqrt(ym) + sz = ctx.sqrt(zm) + sp = ctx.sqrt(pm) + lm = sx*sy + sx*sz + sy*sz + Am1 = (Am+lm)*g + xm = (xm+lm)*g; ym = (ym+lm)*g; zm = (zm+lm)*g; pm = (pm+lm)*g + dm = (sp+sx) * (sp+sy) * (sp+sz) + em = delta * pow4**3 / dm**2 + if pow4 * Q < abs(Am): + break + T = RC_calc(ctx, ctx.one, ctx.one+em, r) * pow4 / dm + S += T + pow4 *= g + Am = Am1 + t = pow4 / Am + X = (A0-x)*t + Y = (A0-y)*t + Z = (A0-z)*t + P = (-X-Y-Z)/2 + E2 = X*Y + X*Z + Y*Z - 3*P**2 + E3 = X*Y*Z + 2*E2*P + 4*P**3 + E4 = (2*X*Y*Z + E2*P + 3*P**3)*P + E5 = X*Y*Z*P**2 + P = 24024 - 5148*E2 + 2457*E2**2 + 4004*E3 - 4158*E2*E3 - 3276*E4 + 2772*E5 + Q = 24024 + v1 = pow4 * ctx.power(Am, -1.5) * P/Q + v2 = 6*S + return initial_integral + v1 + v2 + +@defun +def elliprf(ctx, x, y, z): + r""" + Evaluates the Carlson symmetric elliptic integral of the first kind + + .. math :: + + R_F(x,y,z) = \frac{1}{2} + \int_0^{\infty} \frac{dt}{\sqrt{(t+x)(t+y)(t+z)}} + + which is defined for `x,y,z \notin (-\infty,0)`, and with + at most one of `x,y,z` being zero. + + For real `x,y,z \ge 0`, the principal square root is taken in the integrand. + For complex `x,y,z`, the principal square root is taken as `t \to \infty` + and as `t \to 0` non-principal branches are chosen as necessary so as to + make the integrand continuous. + + **Examples** + + Some basic values and limits:: + + >>> from mpmath import (mp, elliprf, pi, inf, ellipk, ellipe, + ... elliprd, mpf, quad, extradps, sqrt, j, gamma) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> elliprf(0,1,1) + 1.570796326794896619231322 + >>> pi/2 + 1.570796326794896619231322 + >>> elliprf(0,1,inf) + 0.0 + >>> elliprf(1,1,1) + 1.0 + >>> elliprf(2,2,2)**2 + 0.5 + >>> elliprf(1,0,0) + inf + >>> elliprf(0,0,1) + inf + >>> elliprf(0,1,0) + inf + >>> elliprf(0,0,0) + inf + + Representing complete elliptic integrals in terms of `R_F`:: + + >>> m = mpf(0.75) + >>> ellipk(m) + 2.156515647499643235438675 + >>> elliprf(0,1-m,1) + 2.156515647499643235438675 + >>> ellipe(m) + 1.211056027568459524803563 + >>> elliprf(0,1-m,1)-m*elliprd(0,1-m,1)/3 + 1.211056027568459524803563 + + Some symmetries and argument transformations:: + + >>> x,y,z = 2,3,4 + >>> elliprf(x,y,z) + 0.5840828416771517066928492 + >>> elliprf(y,x,z) + 0.5840828416771517066928492 + >>> elliprf(z,y,x) + 0.5840828416771517066928492 + >>> k = mpf(100000) + >>> elliprf(k*x,k*y,k*z) + 0.001847032121923321253219284 + >>> k**(-0.5) * elliprf(x,y,z) + 0.001847032121923321253219284 + >>> l = sqrt(x*y) + sqrt(y*z) + sqrt(z*x) + >>> elliprf(x,y,z) + 0.5840828416771517066928492 + >>> 2*elliprf(x+l,y+l,z+l) + 0.5840828416771517066928492 + >>> elliprf((x+l)/4,(y+l)/4,(z+l)/4) + 0.5840828416771517066928492 + + Comparing with numerical integration:: + + >>> x,y,z = 2,3,4 + >>> elliprf(x,y,z) + 0.5840828416771517066928492 + >>> f = lambda t: 0.5*((t+x)*(t+y)*(t+z))**(-0.5) + >>> q = extradps(25)(quad) + >>> q(f, [0,inf]) + 0.5840828416771517066928492 + + With the following arguments, the square root in the integrand becomes + discontinuous at `t = 1/2` if the principal branch is used. To obtain + the right value, `-\sqrt{r}` must be taken instead of `\sqrt{r}` + on `t \in (0, 1/2)`:: + + >>> x,y,z = j-1,j,0 + >>> elliprf(x,y,z) + (0.7961258658423391329305694 - 1.213856669836495986430094j) + >>> -q(f, [0,0.5]) + q(f, [0.5,inf]) + (0.7961258658423391329305694 - 1.213856669836495986430094j) + + The so-called *first lemniscate constant*, a transcendental number:: + + >>> elliprf(0,1,2) + 1.31102877714605990523242 + >>> extradps(25)(quad)(lambda t: 1/sqrt(1-t**4), [0,1]) + 1.31102877714605990523242 + >>> gamma('1/4')**2/(4*sqrt(2*pi)) + 1.31102877714605990523242 + + **References** + + 1. [Carlson]_ + 2. [DLMF]_ Chapter 19. Elliptic Integrals + + """ + x = ctx.convert(x) + y = ctx.convert(y) + z = ctx.convert(z) + prec = ctx.prec + try: + ctx.prec += 20 + tol = ctx.eps * 2**10 + v = RF_calc(ctx, x, y, z, tol) + finally: + ctx.prec = prec + return +v + +@defun +def elliprc(ctx, x, y, pv=True): + r""" + Evaluates the degenerate Carlson symmetric elliptic integral + of the first kind + + .. math :: + + R_C(x,y) = R_F(x,y,y) = + \frac{1}{2} \int_0^{\infty} \frac{dt}{(t+y) \sqrt{(t+x)}}. + + If `y \in (-\infty,0)`, either a value defined by continuity, + or with *pv=True* the Cauchy principal value, can be computed. + + If `x \ge 0, y > 0`, the value can be expressed in terms of + elementary functions as + + .. math :: + + R_C(x,y) = + \begin{cases} + \dfrac{1}{\sqrt{y-x}} + \cos^{-1}\left(\sqrt{\dfrac{x}{y}}\right), & x < y \\ + \dfrac{1}{\sqrt{y}}, & x = y \\ + \dfrac{1}{\sqrt{x-y}} + \cosh^{-1}\left(\sqrt{\dfrac{x}{y}}\right), & x > y \\ + \end{cases}. + + **Examples** + + Some special values and limits:: + + >>> from mpmath import (mp, elliprc, pi, acosh, sqrt, acos, + ... extradps, quad, inf, j) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> elliprc(1,2)*4 + 3.141592653589793238462643 + >>> elliprc(0,1)*2 + 3.141592653589793238462643 + >>> +pi + 3.141592653589793238462643 + >>> elliprc(1,0) + inf + >>> elliprc(5,5)**2 + 0.2 + >>> elliprc(1,inf) + 0.0 + >>> elliprc(inf,1) + 0.0 + >>> elliprc(inf,inf) + 0.0 + + Comparing with the elementary closed-form solution:: + + >>> elliprc('1/3', '1/5') + 2.041630778983498390751238 + >>> sqrt(7.5)*acosh(sqrt('5/3')) + 2.041630778983498390751238 + >>> elliprc('1/5', '1/3') + 1.875180765206547065111085 + >>> sqrt(7.5)*acos(sqrt('3/5')) + 1.875180765206547065111085 + + Comparing with numerical integration:: + + >>> q = extradps(25)(quad) + >>> elliprc(2, -3, pv=True) + 0.3333969101113672670749334 + >>> elliprc(2, -3, pv=False) + (0.3333969101113672670749334 + 0.7024814731040726393156375j) + >>> 0.5*q(lambda t: 1/(sqrt(t+2)*(t-3)), [0,3-j,6,inf]) + (0.3333969101113672670749334 + 0.7024814731040726393156375j) + + """ + x = ctx.convert(x) + y = ctx.convert(y) + prec = ctx.prec + try: + ctx.prec += 20 + tol = ctx.eps * 2**10 + v = RC_calc(ctx, x, y, tol, pv) + finally: + ctx.prec = prec + return +v + +@defun +def elliprj(ctx, x, y, z, p, integration=1): + r""" + Evaluates the Carlson symmetric elliptic integral of the third kind + + .. math :: + + R_J(x,y,z,p) = \frac{3}{2} + \int_0^{\infty} \frac{dt}{(t+p)\sqrt{(t+x)(t+y)(t+z)}}. + + Like :func:`~mpmath.elliprf`, the branch of the square root in the integrand + is defined so as to be continuous along the path of integration for + complex values of the arguments. + + **Examples** + + Some values and limits:: + + >>> from mpmath import (mp, elliprj, sqrt, gamma, pi, chop, mpf, + ... quad, inf, j) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> elliprj(1,1,1,1) + 1.0 + >>> elliprj(2,2,2,2) + 0.3535533905932737622004222 + >>> 1/(2*sqrt(2)) + 0.3535533905932737622004222 + >>> elliprj(0,1,2,2) + 1.067937989667395702268688 + >>> 3*(2*gamma('5/4')**2-pi**2/gamma('1/4')**2)/(sqrt(2*pi)) + 1.067937989667395702268688 + >>> elliprj(0,1,1,2) + 1.380226776765915172432054 + >>> 3*pi*(2-sqrt(2))/4 + 1.380226776765915172432054 + >>> elliprj(1,3,2,0) + inf + >>> elliprj(0,1,1,0) + inf + >>> elliprj(0,0,0,0) + inf + >>> elliprj(1,inf,1,0) + 0.0 + >>> elliprj(1,1,1,inf) + 0.0 + >>> chop(elliprj(1+j, 1-j, 1, 1)) + 0.8505007163686739432927844 + + Scale transformation:: + + >>> x,y,z,p = 2,3,4,5 + >>> k = mpf(100000) + >>> elliprj(k*x,k*y,k*z,k*p) + 4.521291677592745527851168e-9 + >>> k**(-1.5)*elliprj(x,y,z,p) + 4.521291677592745527851168e-9 + + Comparing with numerical integration:: + + >>> elliprj(1,2,3,4) + 0.2398480997495677621758617 + >>> f = lambda t: 1/((t+4)*sqrt((t+1)*(t+2)*(t+3))) + >>> 1.5*quad(f, [0,inf]) + 0.2398480997495677621758617 + >>> elliprj(1,2+1j,3,4-2j) + (0.216888906014633498739952 + 0.04081912627366673332369512j) + >>> f = lambda t: 1/((t+4-2j)*sqrt((t+1)*(t+2+1j)*(t+3))) + >>> 1.5*quad(f, [0,inf]) + (0.216888906014633498739952 + 0.04081912627366673332369511j) + + """ + x = ctx.convert(x) + y = ctx.convert(y) + z = ctx.convert(z) + p = ctx.convert(p) + prec = ctx.prec + try: + ctx.prec += 20 + tol = ctx.eps * 2**10 + v = RJ_calc(ctx, x, y, z, p, tol, integration) + finally: + ctx.prec = prec + return +v + +@defun +def elliprd(ctx, x, y, z): + r""" + Evaluates the degenerate Carlson symmetric elliptic integral + of the third kind or Carlson elliptic integral of the + second kind `R_D(x,y,z) = R_J(x,y,z,z)`. + + See :func:`~mpmath.elliprj` for additional information. + + **Examples** + + >>> from mpmath import (mp, elliprd, elliprj, extradps, quad, sqrt, + ... gamma, pi) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> elliprd(1,2,3) + 0.2904602810289906442326534 + >>> elliprj(1,2,3,3) + 0.2904602810289906442326534 + + The so-called *second lemniscate constant*, a transcendental number:: + + >>> elliprd(0,2,1)/3 + 0.5990701173677961037199612 + >>> extradps(25)(quad)(lambda t: t**2/sqrt(1-t**4), [0,1]) + 0.5990701173677961037199612 + >>> gamma('3/4')**2/sqrt(2*pi) + 0.5990701173677961037199612 + + """ + return ctx.elliprj(x,y,z,z) + +@defun +def elliprg(ctx, x, y, z): + r""" + Evaluates the Carlson completely symmetric elliptic integral + of the second kind + + .. math :: + + R_G(x,y,z) = \frac{1}{4} \int_0^{\infty} + \frac{t}{\sqrt{(t+x)(t+y)(t+z)}} + \left( \frac{x}{t+x} + \frac{y}{t+y} + \frac{z}{t+z}\right) dt. + + **Examples** + + Evaluation for real and complex arguments:: + + >>> from mpmath import mp, pi, elliprg, chop, fp, nprint, mpf, j + >>> mp.dps = 25 + >>> mp.pretty = True + >>> elliprg(0,1,1)*4 + 3.141592653589793238462643 + >>> +pi + 3.141592653589793238462643 + >>> elliprg(0,0.5,1) + 0.6753219405238377512600874 + >>> chop(elliprg(1+j, 1-j, 2)) + 1.172431327676416604532822 + + A double integral that can be evaluated in terms of `R_G`:: + + >>> x,y,z = 2,3,4 + >>> def f(t,u): + ... st = fp.sin(t); ct = fp.cos(t) + ... su = fp.sin(u); cu = fp.cos(u) + ... return (x*(st*cu)**2 + y*(st*su)**2 + z*ct**2)**0.5 * st + ... + >>> nprint(mpf(fp.quad(f, [0,fp.pi], [0,2*fp.pi])/(4*fp.pi)), 13) + 1.725503028069 + >>> nprint(elliprg(x,y,z), 13) + 1.725503028069 + + """ + x = ctx.convert(x) + y = ctx.convert(y) + z = ctx.convert(z) + zeros = (not x) + (not y) + (not z) + if zeros == 3: + return (x+y+z)*0 + if zeros == 2: + if x: return 0.5*ctx.sqrt(x) + if y: return 0.5*ctx.sqrt(y) + return 0.5*ctx.sqrt(z) + if zeros == 1: + if not z: + x, z = z, x + def terms(): + T1 = 0.5*z*ctx.elliprf(x,y,z) + T2 = -0.5*(x-z)*(y-z)*ctx.elliprd(x,y,z)/3 + T3 = 0.5*ctx.sqrt(x)*ctx.sqrt(y)/ctx.sqrt(z) + return T1,T2,T3 + return ctx.sum_accurately(terms) + + +@defun_wrapped +def ellipf(ctx, phi, m): + r""" + Evaluates the Legendre incomplete elliptic integral of the first kind + + .. math :: + + F(\phi,m) = \int_0^{\phi} \frac{dt}{\sqrt{1-m \sin^2 t}} + + or equivalently + + .. math :: + + F(\phi,m) = \int_0^{\sin \phi} + \frac{dt}{\left(\sqrt{1-t^2}\right)\left(\sqrt{1-mt^2}\right)}. + + The function reduces to a complete elliptic integral of the first kind + (see :func:`~mpmath.ellipk`) when `\phi = \frac{\pi}{2}`; that is, + + .. math :: + + F\left(\frac{\pi}{2}, m\right) = K(m). + + In the defining integral, it is assumed that the principal branch + of the square root is taken and that the path of integration avoids + crossing any branch cuts. Outside `-\pi/2 \le \Re(\phi) \le \pi/2`, + the function extends quasi-periodically as + + .. math :: + + F(\phi + n \pi, m) = 2 n K(m) + F(\phi,m), n \in \mathbb{Z}. + + **Plots** + + .. literalinclude :: /plots/ellipf.py + .. image :: /plots/ellipf.png + + **Examples** + + Basic values and limits:: + + >>> from mpmath import (mp, ellipf, log, sec, tan, pi, eps, ellipk, + ... sin, appellf1, quad) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> ellipf(0,1) + 0.0 + >>> ellipf(0,0) + 0.0 + >>> ellipf(1,0) + 1.0 + >>> ellipf(2+3j,0) + (2.0 + 3.0j) + >>> ellipf(1,1) + 1.226191170883517070813061 + >>> log(sec(1)+tan(1)) + 1.226191170883517070813061 + >>> ellipf(pi/2, -0.5) + 1.415737208425956198892166 + >>> ellipk(-0.5) + 1.415737208425956198892166 + >>> ellipf(pi/2+eps, 1) + inf + >>> ellipf(-pi/2-eps, 1) + inf + >>> ellipf(1.5, 1) + 3.340677542798311003320813 + + Comparing with numerical integration:: + + >>> z,m = 0.5, 1.25 + >>> ellipf(z,m) + 0.5287219202206327872978255 + >>> quad(lambda t: (1-m*sin(t)**2)**(-0.5), [0,z]) + 0.5287219202206327872978255 + + The arguments may be complex numbers:: + + >>> ellipf(3j, 0.5) + (0.0 + 1.713602407841590234804143j) + >>> ellipf(3+4j, 5-6j) + (1.269131241950351323305741 - 0.3561052815014558335412538j) + >>> z,m = 2+3j, 1.25 + >>> k = 1011 + >>> ellipf(z+pi*k,m) + (4086.184383622179764082821 - 3003.003538923749396546871j) + >>> ellipf(z,m) + 2*k*ellipk(m) + (4086.184383622179764082821 - 3003.003538923749396546871j) + + For `|\Re(z)| < \pi/2`, the function can be expressed as a + hypergeometric series of two variables + (see :func:`~mpmath.appellf1`):: + + >>> z,m = 0.5, 0.25 + >>> ellipf(z,m) + 0.5050887275786480788831083 + >>> sin(z)*appellf1(0.5,0.5,0.5,1.5,sin(z)**2,m*sin(z)**2) + 0.5050887275786480788831083 + + """ + z = phi + if not (ctx.isnormal(z) and ctx.isnormal(m)): + if m == 0: + return z + m + if z == 0: + return z * m + if m == ctx.inf or m == ctx.ninf: return z/m + raise ValueError + x = z.real + ctx.prec += max(0, ctx.mag(x)) + pi = +ctx.pi + away = abs(x) > pi/2 + if m == 1: + if away: + return ctx.inf + if away: + d = ctx.nint(x/pi) + z = z-pi*d + P = 2*d*ctx.ellipk(m) + else: + P = 0 + c, s = ctx.cos_sin(z) + return s * ctx.elliprf(c**2, 1-m*s**2, 1) + P + +@defun_wrapped +def ellipe(ctx, *args): + r""" + Called with a single argument `m`, evaluates the Legendre complete + elliptic integral of the second kind, `E(m)`, defined by + + .. math :: E(m) = \int_0^{\pi/2} \sqrt{1-m \sin^2 t} \, dt \,=\, + \frac{\pi}{2} + \,_2F_1\left(\frac{1}{2}, -\frac{1}{2}, 1, m\right). + + Called with two arguments `\phi, m`, evaluates the incomplete elliptic + integral of the second kind + + .. math :: + + E(\phi,m) = \int_0^{\phi} \sqrt{1-m \sin^2 t} \, dt = + \int_0^{\sin z} + \frac{\sqrt{1-mt^2}}{\sqrt{1-t^2}} \, dt. + + The incomplete integral reduces to a complete integral when + `\phi = \frac{\pi}{2}`; that is, + + .. math :: + + E\left(\frac{\pi}{2}, m\right) = E(m). + + In the defining integral, it is assumed that the principal branch + of the square root is taken and that the path of integration avoids + crossing any branch cuts. Outside `-\pi/2 \le \Re(z) \le \pi/2`, + the function extends quasi-periodically as + + .. math :: + + E(\phi + n \pi, m) = 2 n E(m) + E(\phi,m), n \in \mathbb{Z}. + + **Plots** + + .. literalinclude :: /plots/ellipe.py + .. image :: /plots/ellipe.png + + **Examples for the complete integral** + + Basic values and limits:: + + >>> from mpmath import (mp, ellipe, inf, quad, sqrt, sin, pi, + ... hyp2f1, appellf1) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> ellipe(0) + 1.570796326794896619231322 + >>> ellipe(1) + 1.0 + >>> ellipe(-1) + 1.910098894513856008952381 + >>> ellipe(2) + (0.5990701173677961037199612 + 0.5990701173677961037199612j) + >>> ellipe(inf) + (0.0 + infj) + >>> ellipe(-inf) + inf + + Verifying the defining integral and hypergeometric + representation:: + + >>> ellipe(0.5) + 1.350643881047675502520175 + >>> quad(lambda t: sqrt(1-0.5*sin(t)**2), [0, pi/2]) + 1.350643881047675502520175 + >>> pi/2*hyp2f1(0.5,-0.5,1,0.5) + 1.350643881047675502520175 + + Evaluation is supported for arbitrary complex `m`:: + + >>> ellipe(0.5+0.25j) + (1.360868682163129682716687 - 0.1238733442561786843557315j) + >>> ellipe(3+4j) + (1.499553520933346954333612 - 1.577879007912758274533309j) + + A definite integral:: + + >>> quad(ellipe, [0,1]) + 1.333333333333333333333333 + + **Examples for the incomplete integral** + + Basic values and limits:: + + >>> ellipe(0,1) + 0.0 + >>> ellipe(0,0) + 0.0 + >>> ellipe(1,0) + 1.0 + >>> ellipe(2+3j,0) + (2.0 + 3.0j) + >>> ellipe(1,1) + 0.8414709848078965066525023 + >>> sin(1) + 0.8414709848078965066525023 + >>> ellipe(pi/2, -0.5) + 1.751771275694817862026502 + >>> ellipe(-0.5) + 1.751771275694817862026502 + >>> ellipe(pi/2, 1) + 1.0 + >>> ellipe(-pi/2, 1) + -1.0 + >>> ellipe(1.5, 1) + 0.9974949866040544309417234 + + Comparing with numerical integration:: + + >>> z,m = 0.5, 1.25 + >>> ellipe(z,m) + 0.4740152182652628394264449 + >>> quad(lambda t: sqrt(1-m*sin(t)**2), [0,z]) + 0.4740152182652628394264449 + + The arguments may be complex numbers:: + + >>> ellipe(3j, 0.5) + (0.0 + 7.551991234890371873502105j) + >>> ellipe(3+4j, 5-6j) + (24.15299022574220502424466 + 75.2503670480325997418156j) + >>> k = 35 + >>> z,m = 2+3j, 1.25 + >>> ellipe(z+pi*k,m) + (48.30138799412005235090766 + 17.47255216721987688224357j) + >>> ellipe(z,m) + 2*k*ellipe(m) + (48.30138799412005235090766 + 17.47255216721987688224357j) + + For `|\Re(z)| < \pi/2`, the function can be expressed as a + hypergeometric series of two variables + (see :func:`~mpmath.appellf1`):: + + >>> z,m = 0.5, 0.25 + >>> ellipe(z,m) + 0.4950017030164151928870375 + >>> sin(z)*appellf1(0.5,0.5,-0.5,1.5,sin(z)**2,m*sin(z)**2) + 0.4950017030164151928870376 + + """ + if len(args) == 1: + return ctx._ellipe(args[0]) + else: + phi, m = args + z = phi + if not (ctx.isnormal(z) and ctx.isnormal(m)): + if m == 0: + return z + m + if z == 0: + return z * m + if m == ctx.inf or m == ctx.ninf: + return ctx.inf + raise ValueError + x = z.real + ctx.prec += max(0, ctx.mag(x)) + pi = +ctx.pi + away = abs(x) > pi/2 + if away: + d = ctx.nint(x/pi) + z = z-pi*d + P = 2*d*ctx.ellipe(m) + else: + P = 0 + def terms(): + c, s = ctx.cos_sin(z) + x = c**2 + y = 1-m*s**2 + RF = ctx.elliprf(x, y, 1) + RD = ctx.elliprd(x, y, 1) + return s*RF, -m*s**3*RD/3 + return ctx.sum_accurately(terms) + P + +@defun_wrapped +def ellippi(ctx, *args): + r""" + Called with three arguments `n, \phi, m`, evaluates the Legendre + incomplete elliptic integral of the third kind + + .. math :: + + \Pi(n; \phi, m) = \int_0^{\phi} + \frac{dt}{(1-n \sin^2 t) \sqrt{1-m \sin^2 t}} = + \int_0^{\sin \phi} + \frac{dt}{(1-nt^2) \sqrt{1-t^2} \sqrt{1-mt^2}}. + + Called with two arguments `n, m`, evaluates the complete + elliptic integral of the third kind + `\Pi(n,m) = \Pi(n; \frac{\pi}{2},m)`. + + In the defining integral, it is assumed that the principal branch + of the square root is taken and that the path of integration avoids + crossing any branch cuts. Outside `-\pi/2 \le \Re(\phi) \le \pi/2`, + the function extends quasi-periodically as + + .. math :: + + \Pi(n,\phi+k\pi,m) = 2k\Pi(n,m) + \Pi(n,\phi,m), k \in \mathbb{Z}. + + **Plots** + + .. literalinclude :: /plots/ellippi.py + .. image :: /plots/ellippi.png + + **Examples for the complete integral** + + Some basic values and limits:: + + >>> from mpmath import (mp, ellippi, ellipk, inf, pi, sqrt, ellipe, + ... log, sec, tan, ellipf) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> ellippi(0,-5) + 0.9555039270640439337379334 + >>> ellipk(-5) + 0.9555039270640439337379334 + >>> ellippi(inf,2) + 0.0 + >>> ellippi(2,inf) + 0.0 + >>> abs(ellippi(1,5)) + inf + >>> abs(ellippi(0.25,1)) + inf + + Evaluation in terms of simpler functions:: + + >>> ellippi(0.25,0.25) + 1.956616279119236207279727 + >>> ellipe(0.25)/(1-0.25) + 1.956616279119236207279727 + >>> ellippi(3,0) + (0.0 - 1.11072073453959156175397j) + >>> pi/(2*sqrt(-2)) + (0.0 - 1.11072073453959156175397j) + >>> ellippi(-3,0) + 0.7853981633974483096156609 + >>> pi/(2*sqrt(4)) + 0.7853981633974483096156609 + + **Examples for the incomplete integral** + + Basic values and limits:: + + >>> ellippi(0.25,-0.5) + 1.622944760954741603710555 + >>> ellippi(0.25,pi/2,-0.5) + 1.622944760954741603710555 + >>> ellippi(1,0,1) + 0.0 + >>> ellippi(inf,0,1) + 0.0 + >>> ellippi(0,0.25,0.5) + 0.2513040086544925794134591 + >>> ellipf(0.25,0.5) + 0.2513040086544925794134591 + >>> ellippi(1,1,1) + 2.054332933256248668692452 + >>> (log(sec(1)+tan(1))+sec(1)*tan(1))/2 + 2.054332933256248668692452 + >>> ellippi(0.25, 53*pi/2, 0.75) + 135.240868757890840755058 + >>> 53*ellippi(0.25,0.75) + 135.240868757890840755058 + >>> ellippi(0.5,pi/4,0.5) + 0.9190227391656969903987269 + >>> 2*ellipe(pi/4,0.5)-1/sqrt(3) + 0.9190227391656969903987269 + + Complex arguments are supported:: + + >>> ellippi(0.5, 5+6j-2*pi, -7-8j) + (-0.3612856620076747660410167 + 0.5217735339984807829755815j) + + """ + if len(args) == 2: + n, m = args + complete = True + z = phi = ctx.pi/2 + else: + n, phi, m = args + complete = False + z = phi + if not (ctx.isnormal(n) and ctx.isnormal(z) and ctx.isnormal(m)): + if ctx.isnan(n) or ctx.isnan(z) or ctx.isnan(m): + raise ValueError + if complete: + if m == 0: return ctx.pi/(2*ctx.sqrt(1-n)) + if n == 0: return ctx.ellipk(m) + if ctx.isinf(n) or ctx.isinf(m): return ctx.zero + else: + if z == 0: return z + if ctx.isinf(n): return ctx.zero + if ctx.isinf(m): return ctx.zero + if ctx.isinf(n) or ctx.isinf(z) or ctx.isinf(m): + raise ValueError + if complete: + if m == 1: return -ctx.inf/ctx.sign(n-1) + away = False + else: + x = z.real + ctx.prec += max(0, ctx.mag(x)) + pi = +ctx.pi + away = abs(x) > pi/2 + if away: + d = ctx.nint(x/pi) + z = z-pi*d + P = 2*d*ctx.ellippi(n,m) + else: + P = 0 + def terms(): + if complete: + c, s = ctx.zero, ctx.one + else: + c, s = ctx.cos_sin(z) + x = c**2 + y = 1-m*s**2 + RF = ctx.elliprf(x, y, 1) + RJ = ctx.elliprj(x, y, 1, 1-n*s**2) + return s*RF, n*s**3*RJ/3 + return ctx.sum_accurately(terms) + P + + +# Weierstrass Elliptic Functions +# ============================================================================ + +def _roots_from_omega(ctx, omega1, omega2): + """ + Compute roots e1, e2, e3 of 4*z^3 - g2*z - g3 = 0 using theta functions. + This is ~10x faster than solving the cubic directly. + """ + tau = omega2 / omega1 + q = ctx.qfrom(tau=tau) + j24 = ctx.jtheta(2, 0, q)**4 + j44 = ctx.jtheta(4, 0, q)**4 + c = ctx.pi**2 / omega1**2 / 12 + e1 = c * (j24 + 2*j44) + e2 = c * (j24 - j44) + e3 = -c * (2*j24 + j44) + roots = sorted([(e.real, e.imag) for e in [e1, e2, e3]], reverse=True) + return [ctx.mpc(real=t[0], imag=t[1]) for t in roots] + +def _eisenstein_E4_E6(ctx, tau): + """ + Eisenstein E-series of weight 4 and 6. + Uses theta function formula to avoid numerical errors. + """ + q = ctx.qfrom(tau=tau) + j2 = ctx.jtheta(2, 0, q) + j3 = ctx.jtheta(3, 0, q) + j4 = ctx.jtheta(4, 0, q) + E4 = (j2**8 + j3**8 + j4**8) / 2 + E6 = (-3*j2**8 * (j3**4 + j4**4) + (j3**12 + j4**12)) / 2 + return E4, E6 + +def _eisenstein_G4_G6(ctx, tau): + """ + Eisenstein G-series of weight 4 and 6. + """ + E4, E6 = _eisenstein_E4_E6(ctx, tau) + G4 = 2 * ctx.zeta(4) * E4 + G6 = 2 * ctx.zeta(6) * E6 + return G4, G6 + +def _inverse_kleinj(ctx, J): + """ + Compute tau from Klein's J-invariant using the inverse j-function. + See: https://en.wikipedia.org/wiki/J-invariant + """ + J = ctx.convert(J) + _j = 1728 * J + sqrt_arg = 3*(1728*_j**2 - _j**3) + exponent = ctx.mpf(1) / ctx.mpf(3) + t = (-_j**3 + 2304*_j**2 - 884736*_j + + 12288*ctx.sqrt(sqrt_arg))**exponent + x = ctx.mpf(1)/768*t + (1 - _j/768) - (1536*_j - _j**2) / (768*t) + + lbd = (1 + ctx.sqrt(1 - 4*x)) / 2 + tau = ctx.j * ctx.agm(1, ctx.sqrt(1-lbd)) / ctx.agm(1, ctx.sqrt(lbd)) + return tau + +def _kleinj_from_g2g3(ctx, g2, g3): + """ + Klein's absolute invariant J from g2, g3. + (Not the j one with 1728 factor) + https://mathworld.wolfram.com/KleinsAbsoluteInvariant.html + """ + g2 = ctx.convert(g2) + g3 = ctx.convert(g3) + return 1 / (1 - 27*g3**2/g2**3) + +def _tau_from_g(ctx, g2, g3): + """ + Compute tau (half-period ratio) from g2, g3. + """ + g2 = ctx.convert(g2) + g3 = ctx.convert(g3) + J = _kleinj_from_g2g3(ctx, g2, g3) + tau = _inverse_kleinj(ctx, J) + return tau + +def _weierstrass_omega_tau(ctx, funcname, g2=None, g3=None, tau=None, + omega1=None, omega2=None): + """ + Resolve one Weierstrass parameterization to (omega1, tau). + """ + if (g2 is None) != (g3 is None): + raise ValueError("%s: must provide both g2 and g3" % funcname) + if (omega1 is None) != (omega2 is None): + raise ValueError("%s: must provide both omega1 and omega2" % funcname) + parameter_count = (int(g2 is not None) + int(tau is not None) + + int(omega1 is not None)) + if parameter_count != 1: + raise ValueError("%s: must provide exactly one of g2, g3; " + "omega1, omega2; or tau" % funcname) + if omega1 is not None: + omega1 = ctx.convert(omega1) + omega2 = ctx.convert(omega2) + tau = omega2 / omega1 + if ctx.im(tau) <= 0: + raise ValueError("%s: omega ratio must be in upper half-plane" % + funcname) + return omega1, tau + if tau is not None: + tau = ctx.convert(tau) + if ctx.im(tau) <= 0: + raise ValueError("%s: tau must be in upper half-plane" % funcname) + return ctx.one/2, tau + omega1, omega2 = ctx.weierhalfperiods(g2, g3) + return omega1, omega2 / omega1 + +# ============================================================================ +# Weierstrass parameter conversion functions +# ============================================================================ + +@defun +def weierinvariants(ctx, omega1, omega2): + r""" + Returns the Weierstrass invariants `(g_2, g_3)` corresponding to + the half-periods `(\omega_1, \omega_2)`:: + + >>> from mpmath import mp, chop, weierinvariants + >>> mp.pretty = True + >>> g2, g3 = weierinvariants(1, 0.5j) + >>> chop(g2) + 129.987495088848 + >>> chop(g3) + -284.355330876541 + + """ + with ctx.extraprec(10): + omega1 = ctx.convert(omega1) + omega2 = ctx.convert(omega2) + if ctx.im(omega2/omega1) <= 0: + raise ValueError("weierinvariants: omega ratio must be " + "in upper half-plane") + tau = omega2 / omega1 + q = ctx.qfrom(tau=tau) + j2 = ctx.jtheta(2, 0, q) + j3 = ctx.jtheta(3, 0, q) + factor = ctx.pi / (2 * omega1) + g2 = (ctx.mpf(4)/3) * factor**4 * (j2**8 - (j2*j3)**4 + j3**8) + g3 = ((ctx.mpf(8)/27) * factor**6 * + (j2**12 - (ctx.mpf(3)/2*j2**8*j3**4 + + ctx.mpf(3)/2*j2**4*j3**8) + + j3**12)) + return +g2, +g3 + +@defun +def weierhalfperiods(ctx, g2, g3): + r""" + Returns a pair of fundamental half-periods `(\omega_1, \omega_2)` + corresponding to the Weierstrass invariants `(g_2, g_3)`:: + + >>> from mpmath import mp, chop + >>> from mpmath import weierhalfperiods, weierinvariants + >>> mp.pretty = True + >>> omega1, omega2 = weierhalfperiods(60, 140) + >>> g2, g3 = weierinvariants(omega1, omega2) + >>> chop(g2), chop(g3) + (60.0, 140.0) + >>> chop(omega2/omega1) + (0.5 + 0.209032224450873j) + + """ + with ctx.extraprec(10): + g2 = ctx.convert(g2) + g3 = ctx.convert(g3) + + if g2 == 0: + omegaA = (g3 ** (ctx.mpf(-1)/ctx.mpf(6)) * + ctx.gamma(ctx.mpf(1)/ctx.mpf(3))**3 / (4*ctx.pi)) + tau = ctx.mpc(ctx.mpf(1)/ctx.mpf(2), ctx.sqrt(3)/2) + elif g3 == 0: + tau = _tau_from_g(ctx, g2, g3) + G4, G6 = _eisenstein_G4_G6(ctx, tau) + omegaA = (ctx.j * (ctx.mpf(15)/(4*g2) * G4) ** + (ctx.mpf(1)/ctx.mpf(4))) + else: + tau = _tau_from_g(ctx, g2, g3) + G4, G6 = _eisenstein_G4_G6(ctx, tau) + omegaA = ctx.sqrt(g2/g3 * G6/G4 * ctx.mpf(7)/ctx.mpf(12)) + + omegaB = tau * omegaA + omegaC = omegaA + omegaB + omegas = [omegaA, omegaB, omegaC] + index_combos = [(0,1,2), (0,2,1), (1,0,2), + (1,2,0), (2,0,1), (2,1,0)] + + e1, e2, e3 = _roots_from_omega(ctx, omegaA, omegaB) + wps = [] + for omegaN in omegas: + wps.append(ctx.weierp(omegaN, omega1=omegaA, omega2=omegaB)) + + maes = [] + for ic in index_combos: + mae = (abs(e1 - wps[ic[0]]) + abs(e2 - wps[ic[1]]) + + abs(e3 - wps[ic[2]])) / 3 + maes.append(mae) + + mae = min(maes) + min_index = maes.index(mae) + + scale = max([ctx.one] + [abs(x) for x in [e1, e2, e3] + wps]) + tolerance = ctx.sqrt(ctx.eps) * scale + if mae > tolerance: raise ValueError("weierhalfperiods: no convergence") + + omega1, omega2 = [omegas[k] for k in index_combos[min_index]][:2] + if ctx.im(omega2/omega1) <= 0: + omega2 = -omega2 + return +omega1, +omega2 + + +# ============================================================================ +# Main Weierstrass Elliptic Functions +# ============================================================================ + +@defun_wrapped +def weierp(ctx, z, g2=None, g3=None, tau=None, omega1=None, omega2=None): + r""" + Weierstrass elliptic function `\wp(z; g_2, g_3)`. + + Computes the Weierstrass P-function, a doubly-periodic elliptic function + satisfying the differential equation: + + .. math:: + + (\wp'(z))^2 = 4\wp(z)^3 - g_2 \wp(z) - g_3 + + The function may be parameterized in any one of the following ways: + + - by the elliptic invariants `g_2, g_3`; + - by the half-periods `\omega_1, \omega_2`; + - by `\tau`, corresponding to the normalized half-periods + `\omega_1 = 1/2`, `\omega_2 = \tau/2`. + + The periods of `\wp` are `2\omega_1` and `2\omega_2`. Thus the + `\tau` parameterization corresponds to periods `1` and `\tau`. + + For repeated evaluation with the same invariants, it is faster to compute + the half-periods once with :func:`~mpmath.weierhalfperiods` and pass them + using the `omega1` and `omega2` keywords. + + **Examples** + + Direct computation with invariants:: + + >>> from mpmath import mp, weierp, chop + >>> mp.pretty = True + >>> chop(weierp(0.5, g2=60, g3=140)) + 5.12943876105856 + + Using tau parameterization:: + + >>> chop(weierp(0.5, tau=0.5j)) + 13.7503716360407 + + **References** + + - [DLMF]_ Chapter 23: Weierstrass Elliptic and Modular Functions (23.2.4) + + """ + z = ctx.convert(z) + omega1, tau = _weierstrass_omega_tau(ctx, "weierp", g2, g3, tau, + omega1, omega2) + z_norm = z / (2 * omega1) + q = ctx.qfrom(tau=tau) + j1z = ctx.jtheta(1, ctx.pi*z_norm, q) + j2 = ctx.jtheta(2, 0, q) + j3 = ctx.jtheta(3, 0, q) + j4z = ctx.jtheta(4, ctx.pi*z_norm, q) + wp_theta = ((ctx.pi*j2*j3*j4z/j1z)**2 - + ctx.pi**2 * (j2**4 + j3**4) / 3) + return wp_theta / omega1**2 / 4 + +@defun_wrapped +def weierpprime(ctx, z, g2=None, g3=None, tau=None, + omega1=None, omega2=None): + r""" + Derivative of Weierstrass elliptic function `\wp'(z; g_2, g_3)`. + + Computes the derivative of the Weierstrass P-function. It satisfies + + .. math:: + + (\wp'(z))^2 = 4\wp(z)^3 - g_2 \wp(z) - g_3 + + The function accepts the same parameterizations as :func:`~mpmath.weierp`: + the invariants `g_2, g_3`, the half-periods `\omega_1, \omega_2`, or + `\tau`, corresponding to normalized periods `1` and `\tau`. + + **Examples** + + Compute derivative:: + + >>> from mpmath import mp, weierpprime, chop + >>> mp.pretty = True + >>> chop(weierpprime(0.5, g2=60, g3=140)) + -9.5957928748663 + + Verify differential equation:: + + >>> from mpmath import mp, weierp, weierpprime + >>> z = 0.5 + >>> g2, g3 = 60, 140 + >>> lhs = weierpprime(z, g2=g2, g3=g3)**2 + >>> rhs = 4*weierp(z, g2=g2, g3=g3)**3 + >>> rhs -= g2*weierp(z, g2=g2, g3=g3) + g3 + >>> mp.almosteq(lhs, rhs) + True + + **References** + + - [DLMF]_ Chapter 23: Weierstrass Elliptic and Modular Functions (23.3.10) + + """ + z = ctx.convert(z) + omega1, tau = _weierstrass_omega_tau(ctx, "weierpprime", + g2, g3, tau, omega1, omega2) + z_norm = z / (2 * omega1) + q = ctx.qfrom(tau=tau) + z1 = ctx.pi * z_norm + j10p = ctx.jtheta(1, 0, q, 1) + j20 = ctx.jtheta(2, 0, q) + j30 = ctx.jtheta(3, 0, q) + j40 = ctx.jtheta(4, 0, q) + k0 = j10p**3 / (j20 * j30 * j40) + j1z1 = ctx.jtheta(1, z1, q) + j2z1 = ctx.jtheta(2, z1, q) + j3z1 = ctx.jtheta(3, z1, q) + j4z1 = ctx.jtheta(4, z1, q) + kz = j2z1 * j3z1 * j4z1 / j1z1**3 + return -ctx.pi**3 / (4 * omega1**3) * k0 * kz + +@defun_wrapped +def weiersigma(ctx, z, g2=None, g3=None, tau=None, + omega1=None, omega2=None): + r""" + Weierstrass sigma function `\sigma(z; g_2, g_3)`. + + The Weierstrass sigma function is related to the P-function and zeta + function by + + .. math:: + + \zeta(z) = \frac{d}{dz} \log \sigma(z) + + and + + .. math:: + + \wp(z) = -\frac{d^2}{dz^2} \log \sigma(z). + + The function accepts the same parameterizations as :func:`~mpmath.weierp`: + the invariants `g_2, g_3`, the half-periods `\omega_1, \omega_2`, or + `\tau`, corresponding to normalized periods `1` and `\tau`. + + **Examples** + + Compute sigma function:: + + >>> from mpmath import mp, weiersigma, chop + >>> mp.pretty = True + >>> chop(weiersigma(0.5, g2=60, g3=140)) + 0.490839927387142 + + **References** + + - [DLMF]_ Chapter 23: Weierstrass Elliptic and Modular Functions (23.2.6) + + """ + z = ctx.convert(z) + omega1, tau = _weierstrass_omega_tau(ctx, "weiersigma", + g2, g3, tau, omega1, omega2) + z1 = ctx.pi * z / (2 * omega1) + q = ctx.qfrom(tau=tau) + j10p = ctx.jtheta(1, 0, q, 1) + j10ppp = ctx.jtheta(1, 0, q, 3) + j1z1 = ctx.jtheta(1, z1, q) + return (2 * omega1 / (ctx.pi * j10p) * + ctx.exp(-z1**2 * j10ppp / (6 * j10p)) * j1z1) + +@defun_wrapped +def weierzeta(ctx, z, g2=None, g3=None, tau=None, + omega1=None, omega2=None): + r""" + Weierstrass zeta function `\zeta(z; g_2, g_3)`. + + The Weierstrass zeta function is related to the sigma function and + P-function by + + .. math:: + + \zeta(z) = \frac{d}{dz} \log \sigma(z) + + and + + .. math:: + + \zeta'(z) = -\wp(z). + + Unlike `\wp`, the zeta function is quasi-periodic rather than doubly + periodic. + + The function accepts the same parameterizations as :func:`~mpmath.weierp`: + the invariants `g_2, g_3`, the half-periods `\omega_1, \omega_2`, or + `\tau`, corresponding to normalized periods `1` and `\tau`. + + **Examples** + + Compute zeta function:: + + >>> from mpmath import mp, weierzeta, chop + >>> mp.pretty = True + >>> chop(weierzeta(0.5, g2=60, g3=140)) + 1.83933548687454 + + **References** + + - [DLMF]_ Chapter 23: Weierstrass Elliptic and Modular Functions (23.2.5) + + """ + z = ctx.convert(z) + omega1, tau = _weierstrass_omega_tau(ctx, "weierzeta", + g2, g3, tau, omega1, omega2) + w1 = -omega1 / ctx.pi + q = ctx.qfrom(tau=tau) + p = 1 / 2 / w1 + eta1 = p / 6 / w1 * ctx.jtheta(1, 0, q, 3) / ctx.jtheta(1, 0, q, 1) + j1pz = ctx.jtheta(1, p*z, q, 1) + j1z = ctx.jtheta(1, p*z, q) + return -eta1 * z + p * j1pz / j1z + +@defun_wrapped +def weierpinv(ctx, p, g2=None, g3=None, tau=None, omega1=None, omega2=None, + weierp_prime=None): + r""" + Inverse Weierstrass elliptic function. + + Computes `z` such that + + .. math:: + + \wp(z; g_2, g_3) = p, + + using Carlson's symmetric integral. + + The function accepts the same parameterizations as :func:`~mpmath.weierp`: + the invariants `g_2, g_3`, the half-periods `\omega_1, \omega_2`, or + `\tau`, corresponding to normalized periods `1` and `\tau`. + + The inverse is multivalued up to periods and sign. If `weierp_prime` is + provided, it is used to choose between `z` and `-z` by matching the + corresponding value of `\wp'(z)`. + + **Parameters** + + - `p`: the target value + - `g2, g3`: elliptic invariants + - `tau` or `omega1, omega2`: alternative parameterizations + - `weierp_prime` (optional): derivative value used to choose the sign of + the inverse + + **Examples** + + Find preimage under Weierstrass P:: + + >>> from mpmath import mp, weierp, weierpinv + >>> mp.dps = 25 + >>> z0 = 0.5 + >>> g2, g3 = 60, 140 + >>> p_val = weierp(z0, g2=g2, g3=g3) + >>> z_recovered = weierpinv(p_val, g2=g2, g3=g3) + >>> mp.almosteq(z0, z_recovered) # May differ by periods + True + + **References** + + - [DLMF]_ Chapter 19: Elliptic Integrals (19.25.35) + + """ + p = ctx.convert(p) + omega1, tau = _weierstrass_omega_tau(ctx, "weierpinv", + g2, g3, tau, omega1, omega2) + omega2 = omega1 * tau + e1, e2, e3 = _roots_from_omega(ctx, omega1, omega2) + + # Compute via elliptic integral + z = ctx.elliprf(p - e1, p - e2, p - e3) + + # Optionally select sign based on derivative + if weierp_prime is not None: + weierp_prime = ctx.convert(weierp_prime) + wpprime_neg_z = ctx.weierpprime(-z, omega1=omega1, omega2=omega2) + wpprime_pos_z = ctx.weierpprime(z, omega1=omega1, omega2=omega2) + + if (abs(wpprime_neg_z - weierp_prime) < + abs(wpprime_pos_z - weierp_prime)): + return -z + + return z diff --git a/mpmath/functions/expintegrals.py b/mpmath/functions/expintegrals.py new file mode 100644 index 0000000..d9d236f --- /dev/null +++ b/mpmath/functions/expintegrals.py @@ -0,0 +1,437 @@ +from .functions import defun, defun_wrapped + +@defun_wrapped +def _erf_complex(ctx, z): + re_z = ctx.re(z) + if re_z > 2: + nz = ctx.fneg(z, exact=True) + v = ctx._erf_complex(nz) + return ctx.fneg(v, exact=True) + elif re_z < -2: + v = ctx._erfc_complex(ctx.fneg(z, exact=True)) - 1 + else: + z2 = ctx.square_exp_arg(z, -1) + v = (2/ctx.sqrt(ctx.pi))*z * ctx.hyp1f1((1,2),(3,2), z2) + if not re_z: + v = ctx._im(v)*ctx.j + return v + +@defun_wrapped +def _erfc_complex(ctx, z): + re_z = ctx.re(z) + if re_z > 2: + z2 = ctx.square_exp_arg(z) + nz2 = ctx.fneg(z2, exact=True) + v = ctx.exp(nz2)/ctx.sqrt(ctx.pi) * ctx.hyperu((1,2),(1,2), z2) + else: + v = 1 - ctx._erf_complex(z) + if not re_z: + v = 1+ctx._im(v)*ctx.j + return v + +@defun +def erf(ctx, z): + z = ctx.convert(z) + if ctx._is_real_type(z): + try: + return ctx._erf(z) + except NotImplementedError: + pass + if ctx._is_complex_type(z) and not z.imag: + try: + return ctx.mpc(ctx._erf(z.real)) + except NotImplementedError: + pass + return ctx._erf_complex(z) + +@defun +def erfc(ctx, z): + z = ctx.convert(z) + if ctx._is_real_type(z): + try: + return ctx._erfc(z) + except NotImplementedError: + pass + if ctx._is_complex_type(z) and not z.imag: + try: + return ctx.mpc(ctx._erfc(z.real)) + except NotImplementedError: + pass + return ctx._erfc_complex(z) + +@defun +def square_exp_arg(ctx, z, mult=1, reciprocal=False): + prec = ctx.prec*4+20 + if reciprocal: + z2 = ctx.fmul(z, z, prec=prec) + z2 = ctx.fdiv(ctx.one, z2, prec=prec) + else: + z2 = ctx.fmul(z, z, prec=prec) + if mult != 1: + z2 = ctx.fmul(z2, mult, exact=True) + return z2 + +@defun_wrapped +def erfi(ctx, z): + if not z: + return z + z2 = ctx.square_exp_arg(z) + v = (2/ctx.sqrt(ctx.pi)*z) * ctx.hyp1f1((1,2), (3,2), z2) + if not ctx._re(z): + v = ctx._im(v)*ctx.j + return v + +@defun_wrapped +def erfinv(ctx, x): + xre = ctx._re(x) + if (xre != x) or (xre < -1) or (xre > 1): + return ctx.bad_domain("erfinv(x) is defined only for -1 <= x <= 1") + x = xre + #if ctx.isnan(x): return x + if not x: return x + if x == 1: return ctx.inf + if x == -1: return ctx.ninf + if abs(x) < 0.9: + a = 0.53728*x**3 + 0.813198*x + else: + # An asymptotic formula + u = ctx.ln(2/ctx.pi/(abs(x)-1)**2) + a = ctx.sign(x) * ctx.sqrt(u - ctx.ln(u))/ctx.sqrt(2) + ctx.prec += 10 + return ctx.findroot(lambda t: ctx.erf(t)-x, a) + +@defun_wrapped +def npdf(ctx, x, mu=0, sigma=1): + sigma = ctx.convert(sigma) + return ctx.exp(-(x-mu)**2/(2*sigma**2)) / (sigma*ctx.sqrt(2*ctx.pi)) + +@defun_wrapped +def ncdf(ctx, x, mu=0, sigma=1): + a = (x-mu)/(sigma*ctx.sqrt(2)) + if a < 0: + return ctx.erfc(-a)/2 + else: + return (1+ctx.erf(a))/2 + +@defun_wrapped +def betainc(ctx, a, b, x1=0, x2=1, regularized=False): + if x1 == x2: + v = ctx.zero + elif not x1: + if x1 == 0 and x2 == 1: + v = ctx.beta(a, b) + else: + v = x2**a * ctx.hyp2f1(a, 1-b, a+1, x2) / a + else: + m, d = ctx.nint_distance(a) + if m <= 0: + if d < -ctx.prec: + h = +ctx.eps + ctx.prec *= 2 + a += h + elif d < -4: + ctx.prec -= d + s1 = x2**a * ctx.hyp2f1(a,1-b,a+1,x2) + s2 = x1**a * ctx.hyp2f1(a,1-b,a+1,x1) + v = (s1 - s2) / a + if regularized: + v /= ctx.beta(a,b) + return v + +@defun +def gammainc(ctx, z, a=0, b=None, regularized=False): + regularized = bool(regularized) + z = ctx.convert(z) + if a is None: + a = ctx.zero + lower_modified = False + else: + a = ctx.convert(a) + lower_modified = a != ctx.zero + if b is None: + b = ctx.inf + upper_modified = False + else: + b = ctx.convert(b) + upper_modified = b != ctx.inf + # Complete gamma function + if not (upper_modified or lower_modified): + if regularized: + if ctx.re(z) < 0: + return ctx.inf + elif ctx.re(z) > 0: + return ctx.one + else: + return ctx.nan + return ctx.gamma(z) + if a == b: + return ctx.zero + # Standardize + if ctx.re(a) > ctx.re(b): + return -ctx.gammainc(z, b, a, regularized) + # Generalized gamma + if upper_modified and lower_modified: + return +ctx._gamma3(z, a, b, regularized) + # Upper gamma + elif lower_modified: + return ctx.upper_gamma(z, a, regularized) + # Lower gamma + elif upper_modified: + return ctx.lower_gamma(z, b, regularized) + +@defun +def lower_gamma(ctx, z, b, regularized=False): + z = ctx.convert(z) + b = ctx.convert(b) + # Pole + if ctx.isnpint(z): + return ctx.inf + G = [z] * regularized + negb = ctx.fneg(b, exact=True) + def h(z): + T1 = [ctx.exp(negb), b, z], [1, z, -1], [], G, [1], [1+z], b + return (T1,) + return ctx.hypercomb(h, [z]) + +@defun +def upper_gamma(ctx, z, a, regularized=False): + z = ctx.convert(z) + a = ctx.convert(a) + # Fast integer case, when available + if ctx.isint(z): + try: + if regularized: + # Gamma pole + if ctx.isnpint(z): + return ctx.zero + orig = ctx.prec + try: + ctx.prec += 10 + return ctx._gamma_upper_int(z, a) / ctx.gamma(z) + finally: + ctx.prec = orig + else: + return ctx._gamma_upper_int(z, a) + except NotImplementedError: + pass + # hypercomb is unable to detect the exact zeros, so handle them here + if z == 2 and a == -1: + return (z+a)*0 + if z == 3 and (a == -1-1j or a == -1+1j): + return (z+a)*0 + nega = ctx.fneg(a, exact=True) + G = [z] * regularized + # Use 2F0 series when possible; fall back to lower gamma representation + try: + def h(z): + r = z-1 + return [([ctx.exp(nega), a], [1, r], [], G, [1, -r], [], 1/nega)] + return ctx.hypercomb(h, [z], force_series=True) + except ctx.NoConvergence: + def h(z): + T1 = [], [1, z-1], [z], G, [], [], 0 + T2 = [-ctx.exp(nega), a, z], [1, z, -1], [], G, [1], [1+z], a + return T1, T2 + return ctx.hypercomb(h, [z]) + +@defun +def _gamma3(ctx, z, a, b, regularized=False): + pole = ctx.isnpint(z) + if regularized and pole: + return ctx.zero + try: + ctx.prec += 15 + # We don't know in advance whether it's better to write as a difference + # of lower or upper gamma functions, so try both + T1 = ctx.gammainc(z, a, regularized=regularized) + T2 = ctx.gammainc(z, b, regularized=regularized) + R = T1 - T2 + if ctx.mag(R) - max(ctx.mag(T1), ctx.mag(T2)) > -10: + return R + if not pole: + T1 = ctx.gammainc(z, 0, b, regularized=regularized) + T2 = ctx.gammainc(z, 0, a, regularized=regularized) + R = T1 - T2 + # May be ok, but should probably at least print a warning + # about possible cancellation + if 1: #ctx.mag(R) - max(ctx.mag(T1), ctx.mag(T2)) > -10: + return R + finally: + ctx.prec -= 15 + raise NotImplementedError + +@defun_wrapped +def expint(ctx, n, z): + if ctx.isint(n) and ctx._is_real_type(z): + try: + return ctx._expint_int(n, z) + except NotImplementedError: + pass + if ctx.isnan(n) or ctx.isnan(z): + return z*n + if z == ctx.inf: + return 1/z + if z == 0: + # integral from 1 to infinity of t^n + if ctx.re(n) <= 1: + # TODO: reasonable sign of infinity + return ctx.inf + else: + return ctx.one/(n-1) + if n == 0: + return ctx.exp(-z)/z + if n == -1: + return ctx.exp(-z)*(z+1)/z**2 + return z**(n-1) * ctx.upper_gamma(1-n, z) + +@defun_wrapped +def li(ctx, z, offset=False): + if offset: + if z == 2: + return ctx.zero + return ctx.ei(ctx.ln(z)) - ctx.ei(ctx.ln2) + if not z: + return z + if z == 1: + return ctx.ninf + return ctx.ei(ctx.ln(z)) + +@defun +def ei(ctx, z): + try: + return ctx._ei(z) + except NotImplementedError: + return ctx._ei_generic(z) + +@defun_wrapped +def _ei_generic(ctx, z): + # Note: the following is currently untested because mp and fp + # both use special-case ei code + if z == ctx.inf: + return z + if z == ctx.ninf: + return ctx.zero + if ctx.mag(z) > 1: + try: + r = ctx.one/z + v = ctx.exp(z)*ctx.hyper([1,1],[],r, + maxterms=ctx.prec, force_series=True)/z + im = ctx._im(z) + if im > 0: + v += ctx.pi*ctx.j + if im < 0: + v -= ctx.pi*ctx.j + return v + except ctx.NoConvergence: + pass + v = z*ctx.hyp2f2(1,1,2,2,z) + ctx.euler + if ctx._im(z): + v += 0.5*(ctx.log(z) - ctx.log(ctx.one/z)) + else: + v += ctx.log(abs(z)) + return v + +@defun +def e1(ctx, z): + try: + return ctx._e1(z) + except NotImplementedError: + return ctx.expint(1, z) + +@defun +def ci(ctx, z): + try: + return ctx._ci(z) + except NotImplementedError: + return ctx._ci_generic(z) + +@defun_wrapped +def _ci_generic(ctx, z): + if ctx.isinf(z): + if z == ctx.inf: return ctx.zero + if z == ctx.ninf: return ctx.pi*1j + jz = ctx.fmul(ctx.j,z,exact=True) + njz = ctx.fneg(jz,exact=True) + v = 0.5*(ctx.ei(jz) + ctx.ei(njz)) + zreal = ctx._re(z) + zimag = ctx._im(z) + if zreal == 0: + if zimag > 0: v += ctx.pi*0.5j + if zimag < 0: v -= ctx.pi*0.5j + if zreal < 0: + if zimag >= 0: v += ctx.pi*1j + if zimag < 0: v -= ctx.pi*1j + if ctx._is_real_type(z) and zreal > 0: + v = ctx._re(v) + return v + +@defun +def si(ctx, z): + try: + return ctx._si(z) + except NotImplementedError: + return ctx._si_generic(z) + +@defun_wrapped +def _si_generic(ctx, z): + if ctx.isinf(z): + if z == ctx.inf: return 0.5*ctx.pi + if z == ctx.ninf: return -0.5*ctx.pi + # Suffers from cancellation near 0 + if ctx.mag(z) >= -1: + jz = ctx.fmul(ctx.j,z,exact=True) + njz = ctx.fneg(jz,exact=True) + v = (-0.5j)*(ctx.ei(jz) - ctx.ei(njz)) + zreal = ctx._re(z) + if zreal > 0: + v -= 0.5*ctx.pi + if zreal < 0: + v += 0.5*ctx.pi + if ctx._is_real_type(z): + v = ctx._re(v) + return v + else: + return z*ctx.hyp1f2((1,2),(3,2),(3,2),-0.25*z*z) + +@defun_wrapped +def chi(ctx, z): + nz = ctx.fneg(z, exact=True) + v = 0.5*(ctx.ei(z) + ctx.ei(nz)) + zreal = ctx._re(z) + zimag = ctx._im(z) + if zimag > 0: + v += ctx.pi*0.5j + elif zimag < 0: + v -= ctx.pi*0.5j + elif zreal < 0: + v += ctx.pi*1j + return v + +@defun_wrapped +def shi(ctx, z): + # Suffers from cancellation near 0 + if ctx.mag(z) >= -1: + nz = ctx.fneg(z, exact=True) + v = 0.5*(ctx.ei(z) - ctx.ei(nz)) + zimag = ctx._im(z) + if zimag > 0: v -= 0.5j*ctx.pi + if zimag < 0: v += 0.5j*ctx.pi + return v + else: + return z * ctx.hyp1f2((1,2),(3,2),(3,2),0.25*z*z) + +@defun_wrapped +def fresnels(ctx, z): + if z == ctx.inf: + return ctx.mpf(0.5) + if z == ctx.ninf: + return ctx.mpf(-0.5) + return ctx.pi*z**3/6*ctx.hyp1f2((3,4),(3,2),(7,4),-ctx.pi**2*z**4/16) + +@defun_wrapped +def fresnelc(ctx, z): + if z == ctx.inf: + return ctx.mpf(0.5) + if z == ctx.ninf: + return ctx.mpf(-0.5) + return z*ctx.hyp1f2((1,4),(1,2),(5,4),-ctx.pi**2*z**4/16) diff --git a/mpmath/functions/factorials.py b/mpmath/functions/factorials.py new file mode 100644 index 0000000..2ecd375 --- /dev/null +++ b/mpmath/functions/factorials.py @@ -0,0 +1,186 @@ +from .functions import defun, defun_wrapped + +@defun +def gammaprod(ctx, a, b, _infsign=False): + a = [ctx.convert(x) for x in a] + b = [ctx.convert(x) for x in b] + poles_num = [] + poles_den = [] + regular_num = [] + regular_den = [] + for x in a: [regular_num, poles_num][ctx.isnpint(x)].append(x) + for x in b: [regular_den, poles_den][ctx.isnpint(x)].append(x) + # One more pole in numerator or denominator gives 0 or inf + if len(poles_num) < len(poles_den): return ctx.zero + if len(poles_num) > len(poles_den): + # Get correct sign of infinity for x+h, h -> 0 from above + # XXX: hack, this should be done properly + if _infsign: + a = [x and x*(1+ctx.eps) or x+ctx.eps for x in poles_num] + b = [x and x*(1+ctx.eps) or x+ctx.eps for x in poles_den] + return ctx.sign(ctx.gammaprod(a+regular_num,b+regular_den)) * ctx.inf + else: + return ctx.inf + # All poles cancel + # lim G(i)/G(j) = (-1)**(i+j) * gamma(1-j) / gamma(1-i) + p = ctx.one + orig = ctx.prec + try: + ctx.prec = orig + 15 + while poles_num: + i = poles_num.pop() + j = poles_den.pop() + p *= (-1)**(i+j) * ctx.gamma(1-j) / ctx.gamma(1-i) + for x in regular_num: p *= ctx.gamma(x) + for x in regular_den: p /= ctx.gamma(x) + finally: + ctx.prec = orig + return +p + +@defun +def beta(ctx, x, y): + x = ctx.convert(x) + y = ctx.convert(y) + if ctx.isinf(y): + x, y = y, x + if ctx.isinf(x): + if x == ctx.inf and not ctx._im(y): + if y == ctx.ninf: + return ctx.nan + if y > 0: + return ctx.zero + if ctx.isint(y): + return ctx.nan + if y < 0: + return ctx.sign(ctx.gamma(y)) * ctx.inf + return ctx.nan + xy = ctx.fadd(x, y, prec=2*ctx.prec) + return ctx.gammaprod([x, y], [xy]) + +@defun +def binomial(ctx, n, k): + n1 = ctx.fadd(n, 1, prec=2*ctx.prec) + k1 = ctx.fadd(k, 1, prec=2*ctx.prec) + nk1 = ctx.fsub(n1, k, prec=2*ctx.prec) + return ctx.gammaprod([n1], [k1, nk1]) + +@defun +def rf(ctx, x, n): + xn = ctx.fadd(x, n, prec=2*ctx.prec) + return ctx.gammaprod([xn], [x]) + +@defun +def ff(ctx, x, n): + x1 = ctx.fadd(x, 1, prec=2*ctx.prec) + xn1 = ctx.fadd(ctx.fsub(x, n, prec=2*ctx.prec), 1, prec=2*ctx.prec) + return ctx.gammaprod([x1], [xn1]) + +@defun_wrapped +def fac2(ctx, x): + if ctx.isinf(x): + if x == ctx.inf: + return x + return ctx.nan + return 2**(x/2)*(ctx.pi/2)**((ctx.cospi(x)-1)/4)*ctx.gamma(x/2+1) + +@defun_wrapped +def barnesg(ctx, z): + if ctx.isinf(z): + if z == ctx.inf: + return z + return ctx.nan + if ctx.isnan(z): + return z + if ctx.isnpint(z): + return z*0 + # Account for size (would not be needed if computing log(G)) + if abs(z) > 5: + ctx.dps += 2*ctx.log(abs(z),2) + # Reflection formula + if ctx.re(z) < -ctx.dps: + w = 1-z + pi2 = 2*ctx.pi + u = ctx.expjpi(2*w) + v = ctx.j*ctx.pi/12 - ctx.j*ctx.pi*w**2/2 + w*ctx.ln(1-u) - \ + ctx.j*ctx.polylog(2, u)/pi2 + v = ctx.barnesg(2-z)*ctx.exp(v)/pi2**w + if ctx._is_real_type(z): + v = ctx._re(v) + return v + # Estimate terms for asymptotic expansion + # TODO: fixme, obviously + N = ctx.dps // 2 + 5 + G = 1 + while abs(z) < N or ctx.re(z) < 1: + G /= ctx.gamma(z) + z += 1 + z -= 1 + s = ctx.mpf(1)/12 + s -= ctx.log(ctx.glaisher) + s += z*ctx.log(2*ctx.pi)/2 + s += (z**2/2-ctx.mpf(1)/12)*ctx.log(z) + s -= 3*z**2/4 + z2k = z2 = z**2 + for k in range(1, N+1): + t = ctx.bernoulli(2*k+2) / (4*k*(k+1)*z2k) + if abs(t) < ctx.eps: + #print k, N # check how many terms were needed + break + z2k *= z2 + s += t + #if k == N: + # print "warning: series for barnesg failed to converge", ctx.dps + return G*ctx.exp(s) + +@defun +def superfac(ctx, z): + return ctx.barnesg(z+2) + +@defun_wrapped +def hyperfac(ctx, z): + # XXX: estimate needed extra bits accurately + if z == ctx.inf: + return z + if abs(z) > 5: + extra = 4*int(ctx.log(abs(z),2)) + else: + extra = 0 + ctx.prec += extra + if z and ctx.isnpint(z): + n = int(ctx.re(z)) + h = ctx.hyperfac(-n-1) + if ((n+1)//2) & 1: + h = -h + if ctx._is_complex_type(z): + return h + 0j + return h + zp1 = z+1 + # Wrong branch cut + #v = ctx.gamma(zp1)**z + #ctx.prec -= extra + #return v / ctx.barnesg(zp1) + v = ctx.exp(z*ctx.loggamma(zp1)) + ctx.prec -= extra + return v / ctx.barnesg(zp1) + +''' +@defun +def psi0(ctx, z): + """Shortcut for psi(0,z) (the digamma function)""" + return ctx.psi(0, z) + +@defun +def psi1(ctx, z): + """Shortcut for psi(1,z) (the trigamma function)""" + return ctx.psi(1, z) + +@defun +def psi2(ctx, z): + """Shortcut for psi(2,z) (the tetragamma function)""" + return ctx.psi(2, z) + +@defun +def psi3(ctx, z): + """Shortcut for psi(3,z) (the pentagamma function)""" + return ctx.psi(3, z) +''' diff --git a/mpmath/functions/functions.py b/mpmath/functions/functions.py new file mode 100644 index 0000000..07ce3c0 --- /dev/null +++ b/mpmath/functions/functions.py @@ -0,0 +1,691 @@ +class SpecialFunctions: + """ + This class implements special functions using high-level code. + + Elementary and some other functions (e.g. gamma function, basecase + hypergeometric series) are assumed to be predefined by the context as + "builtins" or "low-level" functions. + """ + defined_functions = {} + + def __init__(self): + cls = self.__class__ + for name in cls.defined_functions: + f, wrap = cls.defined_functions[name] + cls._wrap_specfun(name, f, wrap) + + self._misc_const_cache = {} + + self._aliases.update({ + 'phase' : 'arg', + 'conjugate' : 'conj', + 'nthroot' : 'root', + 'polygamma' : 'psi', + 'hurwitz' : 'zeta', + #'digamma' : 'psi0', + #'trigamma' : 'psi1', + #'tetragamma' : 'psi2', + #'pentagamma' : 'psi3', + 'fibonacci' : 'fib', + 'factorial' : 'fac', + }) + + self.zetazero_memoized = self.memoize(self.zetazero) + + # Default -- do nothing + @classmethod + def _wrap_specfun(cls, name, f, wrap): + setattr(cls, name, f) + + # Optional fast versions of common functions in common cases. + # If not overridden, default (generic hypergeometric series) + # implementations will be used + def _besselj(ctx, n, z): raise NotImplementedError + def _erf(ctx, z): raise NotImplementedError + def _erfc(ctx, z): raise NotImplementedError + def _gamma_upper_int(ctx, z, a): raise NotImplementedError + def _expint_int(ctx, n, z): raise NotImplementedError + def _zeta(ctx, s): raise NotImplementedError + def _zetasum_fast(ctx, s, a, n, derivatives, reflect): raise NotImplementedError + def _ei(ctx, z): raise NotImplementedError + def _e1(ctx, z): raise NotImplementedError + def _ci(ctx, z): raise NotImplementedError + def _si(ctx, z): raise NotImplementedError + def _altzeta(ctx, s): raise NotImplementedError + +def defun_wrapped(f): + SpecialFunctions.defined_functions[f.__name__] = f, True + return f + +def defun(f): + SpecialFunctions.defined_functions[f.__name__] = f, False + return f + +def defun_static(f): + setattr(SpecialFunctions, f.__name__, f) + return f + +@defun_wrapped +def cot(ctx, z): return ctx.one / ctx.tan(z) + +@defun_wrapped +def sec(ctx, z): return ctx.one / ctx.cos(z) + +@defun_wrapped +def csc(ctx, z): return ctx.one / ctx.sin(z) + +@defun_wrapped +def coth(ctx, z): return ctx.one / ctx.tanh(z) + +@defun_wrapped +def sech(ctx, z): return ctx.one / ctx.cosh(z) + +@defun_wrapped +def csch(ctx, z): return ctx.one / ctx.sinh(z) + +@defun_wrapped +def acot(ctx, z): + if not z: + return ctx.pi * 0.5 + else: + return ctx.atan(ctx.zero if ctx.isinf(z) else ctx.one / z) + +@defun_wrapped +def asec(ctx, z): return ctx.acos(ctx.zero if ctx.isinf(z) else ctx.one / z) + +@defun_wrapped +def acsc(ctx, z): return ctx.asin(ctx.zero if ctx.isinf(z) else ctx.one / z) + +@defun_wrapped +def acoth(ctx, z): + if not z: + return ctx.pi * 0.5j + else: + return ctx.atanh(ctx.zero if ctx.isinf(z) else ctx.one / z) + + +@defun_wrapped +def asech(ctx, z): return ctx.acosh(ctx.one / z) + +@defun_wrapped +def acsch(ctx, z): return ctx.asinh(ctx.one / z) + +@defun +def sign(ctx, x): + x = ctx.convert(x) + if not x or ctx.isnan(x): + return x + if ctx._is_real_type(x): + if x > 0: + return ctx.one + else: + return -ctx.one + return x / abs(x) + +@defun +def agm(ctx, a, b=1): + if b == 1: + return ctx.agm1(a) + a = ctx.convert(a) + b = ctx.convert(b) + return ctx._agm(a, b) + +@defun_wrapped +def sinc(ctx, x): + if ctx.isinf(x): + return 1/x + if not x: + return x+1 + return ctx.sin(x)/x + +@defun_wrapped +def sincpi(ctx, x): + if ctx.isinf(x): + return 1/x + if not x: + return x+1 + return ctx.sinpi(x)/(ctx.pi*x) + +# TODO: tests; improve implementation +@defun_wrapped +def expm1(ctx, x): + if not x: + return ctx.zero + # exp(x) - 1 ~ x + if ctx.mag(x) < -ctx.prec: + return x + 0.5*x**2 + # TODO: accurately eval the smaller of the real/imag parts + return ctx.sum_accurately(lambda: iter([ctx.exp(x),-1]),1) + +@defun_wrapped +def log1p(ctx, x): + if not x: + return ctx.zero + LOG1P_EXTRAPREC = 10 # ctx._wrap_specfun() + # Note that all cases could by handled by log(1+c) provided the + # add is done exactly. Our aim here is to be much faster than that, + # especially when |c| is small. + c = ctx.convert(x) + cmag = ctx.mag(c) + a, b = c.real, c.imag + wp = ctx.prec + if cmag >= -wp: + # |c| isn't very small. We call log(1+c) instead, but + # are careful about the precision used by the add. The + # real part of the result is log(|c+1|). That's + # determined by 1 + 2*a + a**2 + b**2, and the add has + # to preserve enough info so that no important bits of + # that sum are lost. It doesn't matter to this that 2*a, + # a**2, etc, are not computed explicitly here: we're + # deducing how many bits have to be present in the sum + # for log() to "reverse engineer" the value of 2*a + + # a**2 + b**2 to `prec` good bits, + if cmag < 4: + # |c| isn't very small, or large. + if ctx.mag(a) > ctx.mag(b): + # `a` already contributes the most to c's norm. + # After adding 1, it will utterly dominate it. + # We only need enough extra precision to avoid + # losing any of a's `prec` most significant bits + # when addiog, `b**2` is too small to matter. + wp *= 2 + else: + # b**2 is the larger of the square terms. The + # smallest b can be is about 2**-prec, so the + # smallest b**2 can be is about 2**(-2*prec). So + # for a bit to matter compared to b**2, it has + # to be at least about 2**(-3*prec). Bits of 2*a + # (if any) >= 2**(-3*prec) will be preserved if + # we use 3*prec bits for the add. + wp *= 3 + # Else (cmag >= 4), |c+1| >= |c| - 1 is so large that + # working precision is fine (although that takes some + # careful analysis for cmag=4, given that .mag() _may_ + # return a rexult too large by 2), So leave wp alone. + arg = ctx.fadd(1.0, c, prec=wp) + result = ctx.log(arg) + else: + # Else c is "very small", and we use a series expansion, + # c - c**2/2. The real part of that is a+(b*b-a*a)/2, + # and the imag part b-a*b. Given that cmag < -prec, it + # can be shown that "a*b" is numerically insignifcant in + # the imag part, and _usually_ the "a*a/2" in the real + # part. What remains is cheap to compute. In the real + # part, though, if `a` is negative, the remaining + # a+b**2/2 can suffer massive cancellation - even total. + real = a + b*b*0.5 # usually the real part of the result + if (a < 0.0 + and ctx.mag(real) <= ctx.mag(a) - LOG1P_EXTRAPREC): + # The guard bits were lost to cancellation. Rare. At + # the contrived + # -1.999999873062092e-40+1.999999936531045e-20j + # _all_ bits cancel out. Since a ~= -b*b/2 in this + # case, and |b| is at largest (worst case) about + # 2**-prec, |a| is about 2**(-2*prec), and the true + # result may be as small as a**2/2, which is about + # 2**(-4*prec), of which we want the leading prec + # bits. To get the leading prec bits starting at + # 2**(-4**prec) from addends starting at + # 2**-(2*prec), we need the subtraction to handle + # 3*prec bits (the first 2*prec of which may cancel + # to exactly 0). + a2 = a*a # only need at worst prec bits + b2 = ctx.fmul(b, b, prec=2*wp) + diff = ctx.fsub(b2, a2, prec=3*wp) + real = a + ctx.ldexp(diff, -1) + result = real if ctx._is_real_type(x) else ctx.mpc(real, b) + return result + +@defun_wrapped +def powm1(ctx, x, y): + mag = ctx.mag + one = ctx.one + w = x**y - one + M = mag(w) + # Only moderate cancellation + if M > -8: + return w + # Check for the only possible exact cases + if not w: + if (not y) or (x in (1, -1, 1j, -1j) and ctx.isint(y)): + return w + x1 = x - one + magy = mag(y) + lnx = ctx.ln(x) + # Small y: x^y - 1 ~ log(x)*y + O(log(x)^2 * y^2) + if magy + mag(lnx) < -ctx.prec: + return lnx*y + (lnx*y)**2/2 + # TODO: accurately eval the smaller of the real/imag part + return ctx.sum_accurately(lambda: iter([x**y, -1]), 1) + +@defun +def _rootof1(ctx, k, n): + k = int(k) + n = int(n) + k %= n + if not k: + return ctx.one + elif 2*k == n: + return -ctx.one + elif 4*k == n: + return ctx.j + elif 4*k == 3*n: + return -ctx.j + return ctx.expjpi(2*ctx.mpf(k)/n) + +@defun +def root(ctx, z, n, k=0): + n = int(n) + z = ctx.convert(z) + if k: + # Special case: there is an exact real root + if (n & 1 and 2*k == n-1) and (not ctx.im(z)) and (ctx.re(z) < 0): + return -ctx.root(-z, n) + # Multiply by root of unity + prec = ctx.prec + try: + ctx.prec += 10 + v = ctx.root(z, n, 0) * ctx._rootof1(k, n) + finally: + ctx.prec = prec + return +v + return ctx._nthroot(z, n) + +@defun +def unitroots(ctx, n, primitive=False): + gcd = ctx._gcd + prec = ctx.prec + try: + ctx.prec += 10 + if primitive: + v = [ctx._rootof1(k,n) for k in range(n) if gcd(k,n) == 1] + else: + # TODO: this can be done *much* faster + v = [ctx._rootof1(k,n) for k in range(n)] + finally: + ctx.prec = prec + return [+x for x in v] + +@defun +def arg(ctx, x): + x = ctx.convert(x) + re = ctx._re(x) + im = ctx._im(x) + return ctx.atan2(im, re) + +@defun +def fabs(ctx, x): + return abs(ctx.convert(x)) + +@defun +def re(ctx, x): + x = ctx.convert(x) + return x.real + +@defun +def im(ctx, x): + x = ctx.convert(x) + return x.imag + +@defun +def conj(ctx, x): + x = ctx.convert(x) + try: + return x.conjugate() + except AttributeError: + return x + +@defun +def polar(ctx, z): + return (ctx.fabs(z), ctx.arg(z)) + +@defun_wrapped +def rect(ctx, r, phi): + return r * ctx.mpc(*ctx.cos_sin(phi)) + +@defun +def log(ctx, x, b=None): + if b is None: + return ctx.ln(x) + wp = ctx.prec + 20 + return ctx.ln(x, prec=wp) / ctx.ln(b, prec=wp) + +@defun +def log10(ctx, x): + return ctx.log(x, 10) + +@defun +def log2(ctx, x): + return ctx.log(x, 2) + +@defun +def exp2(ctx, x): + return ctx.power(2, x) + +@defun +def fmod(ctx, x, y): + return ctx.convert(x) % ctx.convert(y) + +@defun +def degrees(ctx, x): + return x / ctx.degree + +@defun +def radians(ctx, x): + return x * ctx.degree + +def _lambertw_special(ctx, z, k): + # W(0,0) = 0; all other branches are singular + if not z: + if not k: + return z + return ctx.ninf + z + if z == ctx.inf: + if k == 0: + return z + else: + return z + 2*k*ctx.pi*ctx.j + if z == ctx.ninf: + return (-z) + (2*k+1)*ctx.pi*ctx.j + # Some kind of nan or complex inf/nan? + return ctx.ln(z) + +import math +import cmath + +def _lambertw_approx_hybrid(z, k): + imag_sign = 0 + x = float(z.real) + y = z.imag + if y: + imag_sign = (-1) ** (y < 0) + y = float(y) + # hack to work regardless of whether Python supports -0.0 + if not y: + y = 0.0 + z = complex(x,y) + if k == 0: + if -4.0 < y < 4.0 and -1.0 < x < 2.5: + if imag_sign: + # Taylor series in upper/lower half-plane + if y > 1.00: return (0.876+0.645j) + (0.118-0.174j)*(z-(0.75+2.5j)) + if y > 0.25: return (0.505+0.204j) + (0.375-0.132j)*(z-(0.75+0.5j)) + if y < -1.00: return (0.876-0.645j) + (0.118+0.174j)*(z-(0.75-2.5j)) + if y < -0.25: return (0.505-0.204j) + (0.375+0.132j)*(z-(0.75-0.5j)) + # Taylor series near -1 + if x < -0.5: + if imag_sign >= 0: + return (-0.318+1.34j) + (-0.697-0.593j)*(z+1) + else: + return (-0.318-1.34j) + (-0.697+0.593j)*(z+1) + # return real type + r = -0.367879441171442 + if (not imag_sign) and x > r: + z = x + # Singularity near -1/e + if x < -0.2: + return -1 + 2.33164398159712*(z-r)**0.5 - 1.81218788563936*(z-r) + # Taylor series near 0 + if x < 0.5: return z + # Simple linear approximation + return 0.2 + 0.3*z + if (not imag_sign) and x > 0.0: + L1 = math.log(x); L2 = math.log(L1) + else: + L1 = cmath.log(z); L2 = cmath.log(L1) + elif k == -1: + # return real type + r = -0.367879441171442 + if (not imag_sign) and r < x < 0.0: + z = x + if (imag_sign >= 0) and y < 0.1 and -0.6 < x < -0.2: + return -1 - 2.33164398159712*(z-r)**0.5 - 1.81218788563936*(z-r) + if (not imag_sign) and -0.2 <= x < 0.0: + L1 = math.log(-x) + return L1 - math.log(-L1) + else: + if imag_sign == -1 and (not y) and x < 0.0: + L1 = cmath.log(z) - 3.1415926535897932j + else: + L1 = cmath.log(z) - 6.2831853071795865j + L2 = cmath.log(L1) + return L1 - L2 + L2/L1 + L2*(L2-2)/(2*L1**2) + +def _lambertw_series(ctx, z, k, tol): + """ + Return rough approximation for W_k(z) from an asymptotic series, + sufficiently accurate for the Halley iteration to converge to + the correct value. + """ + magz = ctx.mag(z) + if (-10 < magz < 900) and (-1000 < k < 1000): + # Near the branch point at -1/e + if magz < 1 and abs(z+0.36787944117144) < 0.05: + if k == 0 or (k == -1 and ctx._im(z) >= 0) or \ + (k == 1 and ctx._im(z) < 0): + delta = ctx.sum_accurately(lambda: [z, ctx.exp(-1)]) + cancellation = -ctx.mag(delta) + ctx.prec += cancellation + # Use series given in Corless et al. + p = ctx.sqrt(2*(ctx.e*z+1)) + ctx.prec -= cancellation + u = {0:ctx.mpf(-1), 1:ctx.mpf(1)} + a = {0:ctx.mpf(2), 1:ctx.mpf(-1)} + if k != 0: + p = -p + s = ctx.zero + # The series converges, so we could use it directly, but unless + # *extremely* close, it is better to just use the first few + # terms to get a good approximation for the iteration + for l in range(max(2, cancellation)): + if l not in u: + a[l] = ctx.fsum(u[j]*u[l+1-j] for j in range(2, l)) + u[l] = (l-1)*(u[l-2]/2+a[l-2]/4)/(l+1)-a[l]/2-u[l-1]/(l+1) + term = u[l] * p**l + s += term + if ctx.mag(term) < -tol: + return s, True + l += 1 + ctx.prec += cancellation//2 + return s, False + if k == 0 or k == -1: + return _lambertw_approx_hybrid(z, k), False + if k == 0: + if magz < -1: + return z*(1-z), False + L1 = ctx.ln(z) + L2 = ctx.ln(L1) + elif k == -1 and (not ctx._im(z)) and (-0.36787944117144 < ctx._re(z) < 0): + L1 = ctx.ln(-z) + return L1 - ctx.ln(-L1), False + else: + # This holds both as z -> 0 and z -> inf. + # Relative error is O(1/log(z)). + L1 = ctx.ln(z) + 2j*ctx.pi*k + L2 = ctx.ln(L1) + return L1 - L2 + L2/L1 + L2*(L2-2)/(2*L1**2), False + +@defun +def lambertw(ctx, z, k=0): + z = ctx.convert(z) + k = int(k) + if not ctx.isnormal(z): + return _lambertw_special(ctx, z, k) + prec = ctx.prec + ctx.prec += 20 + ctx.mag(k or 1) + wp = ctx.prec + tol = wp - 5 + w, done = _lambertw_series(ctx, z, k, tol) + if not done: + # Use Halley iteration to solve w*exp(w) = z + two = ctx.mpf(2) + for i in range(100): + ew = ctx.exp(w) + wew = w*ew + wewz = wew-z + wn = w - wewz/(wew+ew-(w+two)*wewz/(two*w+two)) + if ctx.mag(wn-w) <= ctx.mag(wn) - tol: + w = wn + break + else: + w = wn + if i == 100: + ctx.warn("Lambert W iteration failed to converge for z = %s" % z) + ctx.prec = prec + return +w + +@defun_wrapped +def bell(ctx, n, x=1): + x = ctx.convert(x) + if not n: + if ctx.isnan(x): + return x + return ctx.one + if ctx.isinf(x) or ctx.isinf(n) or ctx.isnan(x) or ctx.isnan(n): + return x**n + if n == 1: return x + if n == 2: return x*(x+1) + if x == 0: return ctx.sincpi(n) + return _polyexp(ctx, n, x, True) / ctx.exp(x) + +def _polyexp(ctx, n, x, extra=False): + def _terms(): + if extra: + yield ctx.sincpi(n) + t = x + k = 1 + while 1: + yield k**n * t + k += 1 + t = t*x/k + return ctx.sum_accurately(_terms, check_step=4) + +@defun_wrapped +def polyexp(ctx, s, z): + if ctx.isinf(z) or ctx.isinf(s) or ctx.isnan(z) or ctx.isnan(s): + return z**s + if z == 0: return z*s + if s == 0: return ctx.expm1(z) + if s == 1: return ctx.exp(z)*z + if s == 2: return ctx.exp(z)*z*(z+1) + return _polyexp(ctx, s, z) + +@defun_wrapped +def cyclotomic(ctx, n, z): + n = int(n) + if n < 0: + raise ValueError("n cannot be negative") + p = ctx.one + if n == 0: + return p + if n == 1: + return z - p + if n == 2: + return z + p + # Use divisor product representation. Unfortunately, this sometimes + # includes singularities for roots of unity, which we have to cancel out. + # Matching zeros/poles pairwise, we have (1-z^a)/(1-z^b) ~ a/b + O(z-1). + a_prod = 1 + b_prod = 1 + num_zeros = 0 + num_poles = 0 + for d in range(1,n+1): + if not n % d: + w = ctx.moebius(n//d) + # Use powm1 because it is important that we get 0 only + # if it really is exactly 0 + b = -ctx.powm1(z, d) + if b: + p *= b**w + else: + if w == 1: + a_prod *= d + num_zeros += 1 + elif w == -1: + b_prod *= d + num_poles += 1 + #print n, num_zeros, num_poles + if num_zeros: + if num_zeros > num_poles: + p *= 0 + else: + p *= a_prod + p /= b_prod + return p + +@defun +def mangoldt(ctx, n): + r""" + Evaluates the von Mangoldt function `\Lambda(n) = \log p` + if `n = p^k` a power of a prime, and `\Lambda(n) = 0` otherwise. + + **Examples** + + >>> from mpmath import mp, mangoldt, fsum + >>> mp.dps = 25 + >>> mp.pretty = True + >>> [mangoldt(n) for n in range(-2,3)] + [0.0, 0.0, 0.0, 0.0, 0.6931471805599453094172321] + >>> mangoldt(6) + 0.0 + >>> mangoldt(7) + 1.945910149055313305105353 + >>> mangoldt(8) + 0.6931471805599453094172321 + >>> fsum(mangoldt(n) for n in range(101)) + 94.04531122935739224600493 + >>> fsum(mangoldt(n) for n in range(10001)) + 10013.39669326311478372032 + + """ + n = int(n) + if n < 2: + return ctx.zero + if n % 2 == 0: + # Must be a power of two + if n & (n-1) == 0: + return +ctx.ln2 + else: + return ctx.zero + # TODO: the following could be generalized into a perfect + # power testing function + # --- + # Look for a small factor + for p in (3,5,7,11,13,17,19,23,29,31): + if not n % p: + q, r = n // p, 0 + while q > 1: + q, r = divmod(q, p) + if r: + return ctx.zero + return ctx.ln(p) + if ctx.isprime(n): + return ctx.ln(n) + # Obviously, we could use arbitrary-precision arithmetic for this... + if n > 10**30: + raise NotImplementedError + k = 2 + while 1: + p = int(n**(1./k) + 0.5) + if p < 2: + return ctx.zero + if p ** k == n: + if ctx.isprime(p): + return ctx.ln(p) + k += 1 + +@defun +def stirling1(ctx, n, k, exact=False): + v = ctx._stirling1(int(n), int(k)) + if exact: + return int(v) + else: + return ctx.mpf(v) + +@defun +def stirling2(ctx, n, k, exact=False): + v = ctx._stirling2(int(n), int(k)) + if exact: + return int(v) + else: + return ctx.mpf(v) diff --git a/mpmath/functions/hypergeometric.py b/mpmath/functions/hypergeometric.py new file mode 100644 index 0000000..bfee0d5 --- /dev/null +++ b/mpmath/functions/hypergeometric.py @@ -0,0 +1,1495 @@ +from ..libmp.backend import MPQ +from .functions import defun, defun_wrapped +import math + +def _check_need_perturb(ctx, terms, prec, discard_known_zeros): + perturb = recompute = False + extraprec = 0 + discard = [] + for term_index, term in enumerate(terms): + w_s, c_s, alpha_s, beta_s, a_s, b_s, z = term + have_singular_nongamma_weight = False + # Avoid division by zero in leading factors (TODO: + # also check for near division by zero?) + for k, w in enumerate(w_s): + if not w: + if ctx.re(c_s[k]) <= 0 and c_s[k]: + perturb = recompute = True + have_singular_nongamma_weight = True + pole_count = [0, 0, 0] + # Check for gamma and series poles and near-poles + for data_index, data in enumerate([alpha_s, beta_s, b_s]): + for i, x in enumerate(data): + n, d = ctx.nint_distance(x) + # Poles + if n > 0: + continue + if d == ctx.ninf: + # OK if we have a polynomial + # ------------------------------ + ok = False + if data_index == 2: + for u in a_s: + if ctx.isnpint(u) and u >= int(n): + ok = True + break + if ok: + continue + pole_count[data_index] += 1 + # ------------------------------ + #perturb = recompute = True + #return perturb, recompute, extraprec + elif d < -4: + extraprec += -d + recompute = True + if discard_known_zeros and pole_count[1] > pole_count[0] + pole_count[2] \ + and not have_singular_nongamma_weight: + discard.append(term_index) + elif sum(pole_count): + perturb = recompute = True + return perturb, recompute, extraprec, discard + +_hypercomb_msg = """ +hypercomb() failed to converge to the requested %i bits of accuracy +using a working precision of %i bits. The function value may be zero or +infinite; try passing zeroprec=N or infprec=M to bound finite values between +2^(-N) and 2^M. Otherwise try a higher maxprec or maxterms. +""" + +@defun +def hypercomb(ctx, function, params=[], discard_known_zeros=True, **kwargs): + orig = ctx.prec + sumvalue = ctx.zero + dist = ctx.nint_distance + ninf = ctx.ninf + orig_params = params[:] + verbose = kwargs.get('verbose', False) + maxprec = kwargs.get('maxprec', ctx._default_hyper_maxprec(orig)) + kwargs['maxprec'] = maxprec # For calls to hypsum + zeroprec = kwargs.get('zeroprec') + infprec = kwargs.get('infprec') + perturbed_reference_value = None + hextra = 0 + try: + while 1: + ctx.prec += 10 + if ctx.prec > maxprec: + raise ValueError(_hypercomb_msg % (orig, ctx.prec)) + orig2 = ctx.prec + params = orig_params[:] + terms = function(*params) + if verbose: + print() + print("ENTERING hypercomb main loop") + print("prec =", ctx.prec) + print("hextra", hextra) + perturb, recompute, extraprec, discard = \ + _check_need_perturb(ctx, terms, orig, discard_known_zeros) + ctx.prec += extraprec + if perturb: + if "hmag" in kwargs: + hmag = kwargs["hmag"] + elif ctx._fixed_precision: + hmag = int(ctx.prec*0.3) + else: + hmag = orig + 10 + hextra + h = ctx.ldexp(ctx.one, -hmag) + ctx.prec = orig2 + 10 + hmag + 10 + for k in range(len(params)): + params[k] += h + # Heuristically ensure that the perturbations + # are "independent" so that two perturbations + # don't accidentally cancel each other out + # in a subtraction. + h += h/(k+1) + if recompute: + terms = function(*params) + if discard_known_zeros: + terms = [term for (i, term) in enumerate(terms) if i not in discard] + if not terms: + return ctx.zero + evaluated_terms = [] + for term_index, term_data in enumerate(terms): + w_s, c_s, alpha_s, beta_s, a_s, b_s, z = term_data + if verbose: + print() + print(" Evaluating term %i/%i : %iF%i" % \ + (term_index+1, len(terms), len(a_s), len(b_s))) + print(" powers", ctx.nstr(w_s), ctx.nstr(c_s)) + print(" gamma", ctx.nstr(alpha_s), ctx.nstr(beta_s)) + print(" hyper", ctx.nstr(a_s), ctx.nstr(b_s)) + print(" z", ctx.nstr(z)) + #v = ctx.hyper(a_s, b_s, z, **kwargs) + #for a in alpha_s: v *= ctx.gamma(a) + #for b in beta_s: v *= ctx.rgamma(b) + #for w, c in zip(w_s, c_s): v *= ctx.power(w, c) + v = ctx.fprod([ctx.hyper(a_s, b_s, z, **kwargs)] + \ + [ctx.gamma(a) for a in alpha_s] + \ + [ctx.rgamma(b) for b in beta_s] + \ + [ctx.power(w,c) for (w,c) in zip(w_s,c_s)]) + if verbose: + print(" Value:", v) + evaluated_terms.append(v) + + if len(terms) == 1 and (not perturb): + sumvalue = evaluated_terms[0] + break + + if ctx._fixed_precision: + sumvalue = ctx.fsum(evaluated_terms) + break + + sumvalue = ctx.fsum(evaluated_terms) + term_magnitudes = [ctx.mag(x) for x in evaluated_terms] + max_magnitude = max(term_magnitudes) + sum_magnitude = ctx.mag(sumvalue) + cancellation = max_magnitude - sum_magnitude + if verbose: + print() + print(" Cancellation:", cancellation, "bits") + print(" Increased precision:", ctx.prec - orig, "bits") + + precision_ok = cancellation < ctx.prec - orig + + if zeroprec is None: + zero_ok = False + else: + zero_ok = max_magnitude - ctx.prec < -zeroprec + if infprec is None: + inf_ok = False + else: + inf_ok = max_magnitude > infprec + + if precision_ok and (not perturb) or ctx.isnan(cancellation): + break + elif precision_ok: + if perturbed_reference_value is None: + hextra += 20 + perturbed_reference_value = sumvalue + continue + elif ctx.mag(sumvalue - perturbed_reference_value) <= \ + ctx.mag(sumvalue) - orig: + break + elif zero_ok: + sumvalue = ctx.zero + break + elif inf_ok: + sumvalue = ctx.inf + break + elif 'hmag' in kwargs: + break + else: + hextra *= 2 + perturbed_reference_value = sumvalue + # Increase precision + else: + increment = min(max(cancellation, orig//2), max(extraprec,orig)) + ctx.prec += increment + if verbose: + print(" Must start over with increased precision") + continue + finally: + ctx.prec = orig + return +sumvalue + +@defun +def hyper(ctx, a_s, b_s, z, **kwargs): + """ + Hypergeometric function, general case. + """ + z = ctx.convert(z) + if ctx.isnan(z): + return ctx.nan + p = len(a_s) + q = len(b_s) + a_s = [ctx._convert_param(a) for a in a_s] + b_s = [ctx._convert_param(b) for b in b_s] + # Reduce degree by eliminating common parameters + if kwargs.get('eliminate', True): + elim_nonpositive = kwargs.get('eliminate_all', False) + i = 0 + while i < q and a_s: + b = b_s[i] + if b in a_s and (elim_nonpositive or not ctx.isnpint(b[0])): + a_s.remove(b) + b_s.remove(b) + p -= 1 + q -= 1 + else: + i += 1 + # Handle special cases + if p == 0: + if q == 1: return ctx._hyp0f1(b_s, z, **kwargs) + elif q == 0: return ctx.exp(z) + elif p == 1: + if q == 1: return ctx._hyp1f1(a_s, b_s, z, **kwargs) + elif q == 2: return ctx._hyp1f2(a_s, b_s, z, **kwargs) + elif q == 0: return ctx._hyp1f0(a_s[0][0], z) + elif p == 2: + if q == 1: return ctx._hyp2f1(a_s, b_s, z, **kwargs) + elif q == 2: return ctx._hyp2f2(a_s, b_s, z, **kwargs) + elif q == 3: return ctx._hyp2f3(a_s, b_s, z, **kwargs) + elif q == 0: return ctx._hyp2f0(a_s, b_s, z, **kwargs) + elif p == q+1: + return ctx._hypq1fq(p, q, a_s, b_s, z, **kwargs) + elif p > q+1 and not kwargs.get('force_series'): + return ctx._hyp_borel(p, q, a_s, b_s, z, **kwargs) + coeffs, types = zip(*(a_s+b_s)) + return ctx.hypsum(p, q, types, coeffs, z, **kwargs) + +@defun +def hyp0f1(ctx,b,z,**kwargs): + return ctx.hyper([],[b],z,**kwargs) + +@defun +def hyp1f1(ctx,a,b,z,**kwargs): + return ctx.hyper([a],[b],z,**kwargs) + +@defun +def hyp1f2(ctx,a1,b1,b2,z,**kwargs): + return ctx.hyper([a1],[b1,b2],z,**kwargs) + +@defun +def hyp2f1(ctx,a,b,c,z,**kwargs): + return ctx.hyper([a,b],[c],z,**kwargs) + +@defun +def hyp2f2(ctx,a1,a2,b1,b2,z,**kwargs): + return ctx.hyper([a1,a2],[b1,b2],z,**kwargs) + +@defun +def hyp2f3(ctx,a1,a2,b1,b2,b3,z,**kwargs): + return ctx.hyper([a1,a2],[b1,b2,b3],z,**kwargs) + +@defun +def hyp2f0(ctx,a,b,z,**kwargs): + return ctx.hyper([a,b],[],z,**kwargs) + +@defun +def hyp3f2(ctx,a1,a2,a3,b1,b2,z,**kwargs): + return ctx.hyper([a1,a2,a3],[b1,b2],z,**kwargs) + +@defun_wrapped +def _hyp1f0(ctx, a, z): + return (1-z) ** (-a) + +@defun +def _hyp0f1(ctx, b_s, z, **kwargs): + (b, btype), = b_s + if z: + magz = ctx.mag(z) + else: + magz = 0 + if magz >= 8 and not kwargs.get('force_series'): + try: + # http://functions.wolfram.com/HypergeometricFunctions/ + # Hypergeometric0F1/06/02/03/0004/ + # TODO: handle the all-real case more efficiently! + # TODO: figure out how much precision is needed (exponential growth) + orig = ctx.prec + try: + ctx.prec += 12 + magz//2 + def h(): + w = ctx.sqrt(-z) + jw = ctx.j*w + u = 1/(4*jw) + c = MPQ(1,2) - b + E = ctx.exp(2*jw) + T1 = ([-jw,E], [c,-1], [], [], [b-MPQ(1,2), MPQ(3,2)-b], [], -u) + T2 = ([jw,E], [c,1], [], [], [b-MPQ(1,2), MPQ(3,2)-b], [], u) + return T1, T2 + v = ctx.hypercomb(h, [], force_series=True) + v = ctx.gamma(b)/(2*ctx.sqrt(ctx.pi))*v + finally: + ctx.prec = orig + if ctx._is_real_type(b) and ctx._is_real_type(z): + v = ctx._re(v) + return +v + except ctx.NoConvergence: + pass + return ctx.hypsum(0, 1, (btype,), [b], z, **kwargs) + +@defun +def _hyp1f1(ctx, a_s, b_s, z, **kwargs): + (a, atype), = a_s + (b, btype), = b_s + if not z: + return ctx.one+z + magz = ctx.mag(z) + if magz >= 7 and not ctx.isnpint(a): + if ctx.isinf(z) and ctx.sign(a) == ctx.sign(b) == ctx.sign(z) == 1: + return ctx.inf + if ctx.isinf(magz): + magz = 0 + try: + try: + ctx.prec += magz + sector = ctx._im(z) < 0 + def h(a,b): + if sector: + E = ctx.expjpi(ctx.fneg(a, exact=True)) + else: + E = ctx.expjpi(a) + rz = 1/z + T1 = ([E,z], [1,-a], [b], [b-a], [a, 1+a-b], [], -rz) + T2 = ([ctx.exp(z),z], [1,a-b], [b], [a], [b-a, 1-a], [], rz) + return T1, T2 + v = ctx.hypercomb(h, [a,b], force_series=True) + if ctx._is_real_type(a) and ctx._is_real_type(b) and ctx._is_real_type(z): + v = ctx._re(v) + return +v + except ctx.NoConvergence: + pass + finally: + ctx.prec -= magz + v = ctx.hypsum(1, 1, (atype, btype), [a, b], z, **kwargs) + return v + +def _hyp2f1_gosper(ctx,a,b,c,z,**kwargs): + # Use Gosper's recurrence + # See http://www.math.utexas.edu/pipermail/maxima/2006/000126.html + _a,_b,_c,_z = a, b, c, z + orig = ctx.prec + maxprec = kwargs.get('maxprec', 100*orig) + extra = 10 + while 1: + ctx.prec = orig + extra + #a = ctx.convert(_a) + #b = ctx.convert(_b) + #c = ctx.convert(_c) + z = ctx.convert(_z) + d = ctx.mpf(0) + e = ctx.mpf(1) + f = ctx.mpf(0) + k = 0 + # Common subexpression elimination, unfortunately making + # things a bit unreadable. The formula is quite messy to begin + # with, though... + abz = a*b*z + ch = c * MPQ(1,2) + c1h = (c+1) * MPQ(1,2) + nz = 1-z + g = z/nz + abg = a*b*g + cba = c-b-a + z2 = z-2 + tol = -ctx.prec - 10 + nstr = ctx.nstr + nprint = ctx.nprint + mag = ctx.mag + maxmag = ctx.ninf + while 1: + kch = k+ch + kakbz = (k+a)*(k+b)*z / (4*(k+1)*kch*(k+c1h)) + d1 = kakbz*(e-(k+cba)*d*g) + e1 = kakbz*(d*abg+(k+c)*e) + ft = d*(k*(cba*z+k*z2-c)-abz)/(2*kch*nz) + f1 = f + e - ft + maxmag = max(maxmag, mag(f1)) + if mag(f1-f) < tol: + break + d, e, f = d1, e1, f1 + k += 1 + cancellation = maxmag - mag(f1) + if cancellation < extra: + break + else: + extra += cancellation + if extra > maxprec: + raise ctx.NoConvergence + return f1 + +@defun +def _hyp2f1(ctx, a_s, b_s, z, **kwargs): + (a, atype), (b, btype) = a_s + (c, ctype), = b_s + if z == 1: + # TODO: the following logic can be simplified + convergent = ctx.re(c-a-b) > 0 + finite = ctx.isnpint(a) or ctx.isnpint(b) + zerodiv = ctx.isnpint(c) and not \ + ((ctx.isnpint(a) and c <= a) or (ctx.isnpint(b) and c <= b)) + #print "bz", a, b, c, z, convergent, finite, zerodiv + # Gauss's theorem gives the value if convergent + if (convergent or finite) and not zerodiv: + return ctx.gammaprod([c, c-a-b], [c-a, c-b], _infsign=True) + # Otherwise, there is a pole and we take the + # sign to be that when approaching from below + # XXX: this evaluation is not necessarily correct in all cases + return ctx.hyp2f1(a,b,c,1-ctx.eps*2) * ctx.inf + + # Equal to 1 (first term), unless there is a subsequent + # division by zero + if not z: + # Division by zero but power of z is higher than + # first order so cancels + if c or a == 0 or b == 0: + return 1+z + # Indeterminate + return ctx.nan + + # Hit zero denominator unless numerator goes to 0 first + if ctx.isnpint(c): + if (ctx.isnpint(a) and c <= a) or (ctx.isnpint(b) and c <= b): + pass + else: + # Pole in series + return ctx.inf + + absz = abs(z) + + # Fast case: standard series converges rapidly, + # possibly in finitely many terms + if ctx.isfinite(z) and (absz <= 0.8 or + (ctx.isnpint(a) and -1000 <= a) or + (ctx.isnpint(b) and -1000 <= b)): + try: + return ctx.hypsum(2, 1, (atype, btype, ctype), [a, b, c], z, **kwargs) + except ctx.NoConvergence: + if kwargs.get('force_series', False): + raise + + orig = ctx.prec + try: + ctx.prec += 10 + + # Use 1/z transformation + if absz >= 1.3: + def h(a,b): + t = MPQ(1)-c; ab = a-b; rz = 1/z + T1 = ([-z],[-a], [c,-ab],[b,c-a], [a,t+a],[MPQ(1)+ab], rz) + T2 = ([-z],[-b], [c,ab],[a,c-b], [b,t+b],[MPQ(1)-ab], rz) + return T1, T2 + v = ctx.hypercomb(h, [a,b], **kwargs) + + # Use 1-z transformation + elif abs(1-z) <= 0.75: + def h(a,b): + t = c-a-b; ca = c-a; cb = c-b; rz = 1-z + T1 = [], [], [c,t], [ca,cb], [a,b], [1-t], rz + T2 = [rz], [t], [c,a+b-c], [a,b], [ca,cb], [1+t], rz + return T1, T2 + v = ctx.hypercomb(h, [a,b], **kwargs) + + # Use z/(z-1) transformation + elif abs(z/(z-1)) <= 0.75: + v = ctx.hyp2f1(a, c-b, c, z/(z-1)) / (1-z)**a + + # Remaining part of unit circle + else: + v = _hyp2f1_gosper(ctx,a,b,c,z,**kwargs) + + finally: + ctx.prec = orig + return +v + +@defun +def _hypq1fq(ctx, p, q, a_s, b_s, z, **kwargs): + r""" + Evaluates 3F2, 4F3, 5F4, ... + """ + a_s, a_types = zip(*a_s) + b_s, b_types = zip(*b_s) + a_s = list(a_s) + b_s = list(b_s) + absz = abs(z) + ispoly = False + for a in a_s: + if ctx.isnpint(a): + ispoly = True + break + # Direct summation + if absz < 1 or ispoly: + try: + return ctx.hypsum(p, q, a_types+b_types, a_s+b_s, z, **kwargs) + except ctx.NoConvergence: + if absz > 1.1 or ispoly: + raise + # Use expansion at |z-1| -> 0. + # Reference: [Buhring]_ + # The current implementation has several problems: + # 1. We only implement it for 3F2. The expansion coefficients are + # given by extremely messy nested sums in the higher degree cases + # (see reference). Is efficient sequential generation of the coefficients + # possible in the > 3F2 case? + # 2. Although the series converges, it may do so slowly, so we need + # convergence acceleration. The acceleration implemented by + # nsum does not always help, so results returned are sometimes + # inaccurate! Can we do better? + # 3. We should check conditions for convergence, and possibly + # do a better job of cancelling out gamma poles if possible. + if z == 1: + # XXX: should also check for division by zero in the + # denominator of the series (cf. hyp2f1) + S = ctx.re(sum(b_s)-sum(a_s)) + if S <= 0: + #return ctx.hyper(a_s, b_s, 1-ctx.eps*2, **kwargs) * ctx.inf + return ctx.hyper(a_s, b_s, 0.9, **kwargs) * ctx.inf + if (p,q) == (3,2) and abs(z-1) < 0.05: # and kwargs.get('sum1') + #print "Using alternate summation (experimental)" + a1,a2,a3 = a_s + b1,b2 = b_s + u = b1+b2-a3 + initial = ctx.gammaprod([b2-a3,b1-a3,a1,a2],[b2-a3,b1-a3,1,u]) + def term(k, _cache={0:initial}): + u = b1+b2-a3+k + if k in _cache: + t = _cache[k] + else: + t = _cache[k-1] + t *= (b1+k-a3-1)*(b2+k-a3-1) + t /= k*(u-1) + _cache[k] = t + return t * ctx.hyp2f1(a1,a2,u,z) + try: + S = ctx.nsum(term, [0,ctx.inf], verbose=kwargs.get('verbose'), + strict=kwargs.get('strict', True)) + return S * ctx.gammaprod([b1,b2],[a1,a2,a3]) + except ctx.NoConvergence: + pass + # Try to use convergence acceleration on and close to the unit circle. + # Problem: the convergence acceleration degenerates as |z-1| -> 0, + # except for special cases. Everywhere else, the Shanks transformation + # is very efficient. + if absz < 1.1 and ctx._re(z) <= 1: + + def term(kk, _cache={0:ctx.one}): + k = int(kk) + if k != kk: + t = z ** ctx.mpf(kk) / ctx.fac(kk) + for a in a_s: t *= ctx.rf(a,kk) + for b in b_s: t /= ctx.rf(b,kk) + return t + if k in _cache: + return _cache[k] + t = term(k-1) + m = k-1 + for j in range(p): t *= (a_s[j]+m) + for j in range(q): t /= (b_s[j]+m) + t *= z + t /= k + _cache[k] = t + return t + + sum_method = kwargs.get('sum_method', 'r+s+e') + + try: + return ctx.nsum(term, [0,ctx.inf], verbose=kwargs.get('verbose'), + strict=kwargs.get('strict', True), + method=sum_method.replace('e','')) + except ctx.NoConvergence: + if 'e' not in sum_method: + raise + pass + + if kwargs.get('verbose'): + print("Attempting Euler-Maclaurin summation") + + + """ + Somewhat slower version (one diffs_exp for each factor). + However, this would be faster with fast direct derivatives + of the gamma function. + + def power_diffs(k0): + r = 0 + l = ctx.log(z) + while 1: + yield z**ctx.mpf(k0) * l**r + r += 1 + + def loggamma_diffs(x, reciprocal=False): + sign = (-1) ** reciprocal + yield sign * ctx.loggamma(x) + i = 0 + while 1: + yield sign * ctx.psi(i,x) + i += 1 + + def hyper_diffs(k0): + b2 = b_s + [1] + A = [ctx.diffs_exp(loggamma_diffs(a+k0)) for a in a_s] + B = [ctx.diffs_exp(loggamma_diffs(b+k0,True)) for b in b2] + Z = [power_diffs(k0)] + C = ctx.gammaprod([b for b in b2], [a for a in a_s]) + for d in ctx.diffs_prod(A + B + Z): + v = C * d + yield v + """ + + def log_diffs(k0): + b2 = b_s + [1] + yield sum(ctx.loggamma(a+k0) for a in a_s) - \ + sum(ctx.loggamma(b+k0) for b in b2) + k0*ctx.log(z) + i = 0 + while 1: + v = sum(ctx.psi(i,a+k0) for a in a_s) - \ + sum(ctx.psi(i,b+k0) for b in b2) + if i == 0: + v += ctx.log(z) + yield v + i += 1 + + def hyper_diffs(k0): + C = ctx.gammaprod([b for b in b_s], [a for a in a_s]) + for d in ctx.diffs_exp(log_diffs(k0)): + v = C * d + yield v + + tol = ctx.eps / 1024 + prec = ctx.prec + try: + trunc = 50 * ctx.dps + ctx.prec += 20 + for i in range(5): + head = ctx.fsum(term(k) for k in range(trunc)) + tail, err = ctx.sumem(term, [trunc, ctx.inf], tol=tol, + adiffs=hyper_diffs(trunc), + verbose=kwargs.get('verbose'), + error=True, + _fast_abort=True) + if err < tol: + v = head + tail + break + trunc *= 2 + # Need to increase precision because calculation of + # derivatives may be inaccurate + ctx.prec += ctx.prec//2 + if i == 4: + raise ctx.NoConvergence(\ + "Euler-Maclaurin summation did not converge") + finally: + ctx.prec = prec + return +v + + # Use 1/z transformation + # http://functions.wolfram.com/HypergeometricFunctions/ + # HypergeometricPFQ/06/01/05/02/0004/ + def h(*args): + a_s = list(args[:p]) + b_s = list(args[p:]) + Ts = [] + recz = ctx.one/z + negz = ctx.fneg(z, exact=True) + for k in range(q+1): + ak = a_s[k] + C = [negz] + Cp = [-ak] + Gn = b_s + [ak] + [a_s[j]-ak for j in range(q+1) if j != k] + Gd = a_s + [b_s[j]-ak for j in range(q)] + Fn = [ak] + [ak-b_s[j]+1 for j in range(q)] + Fd = [1-a_s[j]+ak for j in range(q+1) if j != k] + Ts.append((C, Cp, Gn, Gd, Fn, Fd, recz)) + return Ts + return ctx.hypercomb(h, a_s+b_s, **kwargs) + +@defun +def _hyp_borel(ctx, p, q, a_s, b_s, z, **kwargs): + if a_s: + a_s, a_types = zip(*a_s) + a_s = list(a_s) + else: + a_s, a_types = [], () + if b_s: + b_s, b_types = zip(*b_s) + b_s = list(b_s) + else: + b_s, b_types = [], () + kwargs['maxterms'] = kwargs.get('maxterms', ctx.prec) + try: + return ctx.hypsum(p, q, a_types+b_types, a_s+b_s, z, **kwargs) + except ctx.NoConvergence: + pass + prec = ctx.prec + try: + tol = kwargs.get('asymp_tol', ctx.eps/4) + ctx.prec += 10 + # hypsum is has a conservative tolerance. So we try again: + def term(k, cache={0:ctx.one}): + if k in cache: + return cache[k] + t = term(k-1) + for a in a_s: t *= (a+(k-1)) + for b in b_s: t /= (b+(k-1)) + t *= z + t /= k + cache[k] = t + return t + s = ctx.one + for k in range(1, ctx.prec): + t = term(k) + s += t + if abs(t) <= tol: + return s + finally: + ctx.prec = prec + if p <= q+3: + contour = kwargs.get('contour') + if not contour: + if ctx.arg(z) < 0.25: + u = z / max(1, abs(z)) + if ctx.arg(z) >= 0: + contour = [0, 2j, (2j+2)/u, 2/u, ctx.inf] + else: + contour = [0, -2j, (-2j+2)/u, 2/u, ctx.inf] + #contour = [0, 2j/z, 2/z, ctx.inf] + #contour = [0, 2j, 2/z, ctx.inf] + #contour = [0, 2j, ctx.inf] + else: + contour = [0, ctx.inf] + quad_kwargs = kwargs.get('quad_kwargs', {}) + def g(t): + return ctx.exp(-t)*ctx.hyper(a_s, b_s+[1], t*z) + I, err = ctx.quad(g, contour, error=True, **quad_kwargs) + if err <= abs(I)*ctx.eps*8: + return I + raise ctx.NoConvergence + + +@defun +def _hyp2f2(ctx, a_s, b_s, z, **kwargs): + (a1, a1type), (a2, a2type) = a_s + (b1, b1type), (b2, b2type) = b_s + + absz = abs(z) + magz = ctx.mag(z) + orig = ctx.prec + + # Asymptotic expansion is ~ exp(z) + asymp_extraprec = magz + + # Asymptotic series is in terms of 3F1 + can_use_asymptotic = (not kwargs.get('force_series')) and \ + (ctx.mag(absz) > 3) + + # TODO: much of the following could be shared with 2F3 instead of + # copypasted + if can_use_asymptotic: + #print "using asymp" + try: + try: + ctx.prec += asymp_extraprec + # http://functions.wolfram.com/HypergeometricFunctions/ + # Hypergeometric2F2/06/02/02/0002/ + def h(a1,a2,b1,b2): + X = a1+a2-b1-b2 + A2 = a1+a2 + B2 = b1+b2 + c = {} + c[0] = ctx.one + c[1] = (A2-1)*X+b1*b2-a1*a2 + s1 = 0 + k = 0 + tprev = 0 + while 1: + if k not in c: + uu1 = 1-B2+2*a1+a1**2+2*a2+a2**2-A2*B2+a1*a2+b1*b2+(2*B2-3*(A2+1))*k+2*k**2 + uu2 = (k-A2+b1-1)*(k-A2+b2-1)*(k-X-2) + c[k] = ctx.one/k * (uu1*c[k-1]-uu2*c[k-2]) + t1 = c[k] * z**(-k) + if abs(t1) < 0.1*ctx.eps: + #print "Convergence :)" + break + # Quit if the series doesn't converge quickly enough + if k > 5 and abs(tprev) / abs(t1) < 1.5: + #print "No convergence :(" + raise ctx.NoConvergence + s1 += t1 + tprev = t1 + k += 1 + S = ctx.exp(z)*s1 + T1 = [z,S], [X,1], [b1,b2],[a1,a2],[],[],0 + T2 = [-z],[-a1],[b1,b2,a2-a1],[a2,b1-a1,b2-a1],[a1,a1-b1+1,a1-b2+1],[a1-a2+1],-1/z + T3 = [-z],[-a2],[b1,b2,a1-a2],[a1,b1-a2,b2-a2],[a2,a2-b1+1,a2-b2+1],[-a1+a2+1],-1/z + return T1, T2, T3 + v = ctx.hypercomb(h, [a1,a2,b1,b2], force_series=True, maxterms=4*ctx.prec) + if sum(ctx._is_real_type(u) for u in [a1,a2,b1,b2,z]) == 5: + v = ctx.re(v) + return v + except ctx.NoConvergence: + pass + finally: + ctx.prec = orig + + return ctx.hypsum(2, 2, (a1type, a2type, b1type, b2type), [a1, a2, b1, b2], z, **kwargs) + + + +@defun +def _hyp1f2(ctx, a_s, b_s, z, **kwargs): + (a1, a1type), = a_s + (b1, b1type), (b2, b2type) = b_s + + absz = abs(z) + magz = ctx.mag(z) + orig = ctx.prec + + # Asymptotic expansion is ~ exp(sqrt(z)) + asymp_extraprec = z and magz//2 + + # Asymptotic series is in terms of 3F0 + can_use_asymptotic = (not kwargs.get('force_series')) and \ + (ctx.mag(absz) > 19) and \ + (ctx.sqrt(absz) > 1.5*orig) # and \ + # ctx._hyp_check_convergence([a1, a1-b1+1, a1-b2+1], [], + # 1/absz, orig+40+asymp_extraprec) + + # TODO: much of the following could be shared with 2F3 instead of + # copypasted + if can_use_asymptotic: + #print "using asymp" + try: + try: + ctx.prec += asymp_extraprec + # http://functions.wolfram.com/HypergeometricFunctions/ + # Hypergeometric1F2/06/02/03/ + def h(a1,b1,b2): + X = MPQ(1,2)*(a1-b1-b2+MPQ(1,2)) + c = {} + c[0] = ctx.one + c[1] = 2*(MPQ(1,4)*(3*a1+b1+b2-2)*(a1-b1-b2)+b1*b2-MPQ(3,16)) + c[2] = 2*(b1*b2+MPQ(1,4)*(a1-b1-b2)*(3*a1+b1+b2-2)-MPQ(3,16))**2+\ + MPQ(1,16)*(-16*(2*a1-3)*b1*b2 + \ + 4*(a1-b1-b2)*(-8*a1**2+11*a1+b1+b2-2)-3) + s1 = 0 + s2 = 0 + k = 0 + tprev = 0 + while 1: + if k not in c: + uu1 = (3*k**2+(-6*a1+2*b1+2*b2-4)*k + 3*a1**2 - \ + (b1-b2)**2 - 2*a1*(b1+b2-2) + MPQ(1,4)) + uu2 = (k-a1+b1-b2-MPQ(1,2))*(k-a1-b1+b2-MPQ(1,2))*\ + (k-a1+b1+b2-MPQ(5,2)) + c[k] = ctx.one/(2*k)*(uu1*c[k-1]-uu2*c[k-2]) + w = c[k] * (-z)**(-0.5*k) + t1 = (-ctx.j)**k * ctx.mpf(2)**(-k) * w + t2 = ctx.j**k * ctx.mpf(2)**(-k) * w + if abs(t1) < 0.1*ctx.eps: + #print "Convergence :)" + break + # Quit if the series doesn't converge quickly enough + if k > 5 and abs(tprev) / abs(t1) < 1.5: + #print "No convergence :(" + raise ctx.NoConvergence + s1 += t1 + s2 += t2 + tprev = t1 + k += 1 + S = ctx.expj(ctx.pi*X+2*ctx.sqrt(-z))*s1 + \ + ctx.expj(-(ctx.pi*X+2*ctx.sqrt(-z)))*s2 + T1 = [0.5*S, ctx.pi, -z], [1, -0.5, X], [b1, b2], [a1],\ + [], [], 0 + T2 = [-z], [-a1], [b1,b2],[b1-a1,b2-a1], \ + [a1,a1-b1+1,a1-b2+1], [], 1/z + return T1, T2 + v = ctx.hypercomb(h, [a1,b1,b2], force_series=True, maxterms=4*ctx.prec) + if sum(ctx._is_real_type(u) for u in [a1,b1,b2,z]) == 4: + v = ctx.re(v) + return v + except ctx.NoConvergence: + pass + finally: + ctx.prec = orig + + #print "not using asymp" + return ctx.hypsum(1, 2, (a1type, b1type, b2type), [a1, b1, b2], z, **kwargs) + + + +@defun +def _hyp2f3(ctx, a_s, b_s, z, **kwargs): + (a1, a1type), (a2, a2type) = a_s + (b1, b1type), (b2, b2type), (b3, b3type) = b_s + + absz = abs(z) + magz = ctx.mag(z) + + # Asymptotic expansion is ~ exp(sqrt(z)) + asymp_extraprec = z and magz//2 + orig = ctx.prec + + # Asymptotic series is in terms of 4F1 + # The square root below empirically provides a plausible criterion + # for the leading series to converge + can_use_asymptotic = (not kwargs.get('force_series')) and \ + (ctx.mag(absz) > 19) and (ctx.sqrt(absz) > 1.5*orig) + + if can_use_asymptotic: + #print "using asymp" + try: + try: + ctx.prec += asymp_extraprec + # http://functions.wolfram.com/HypergeometricFunctions/ + # Hypergeometric2F3/06/02/03/01/0002/ + def h(a1,a2,b1,b2,b3): + X = MPQ(1,2)*(a1+a2-b1-b2-b3+MPQ(1,2)) + A2 = a1+a2 + B3 = b1+b2+b3 + A = a1*a2 + B = b1*b2+b3*b2+b1*b3 + R = b1*b2*b3 + c = {} + c[0] = ctx.one + c[1] = 2*(B - A + MPQ(1,4)*(3*A2+B3-2)*(A2-B3) - MPQ(3,16)) + c[2] = MPQ(1,2)*c[1]**2 + MPQ(1,16)*(-16*(2*A2-3)*(B-A) + 32*R +\ + 4*(-8*A2**2 + 11*A2 + 8*A + B3 - 2)*(A2-B3)-3) + s1 = 0 + s2 = 0 + k = 0 + tprev = 0 + while 1: + if k not in c: + uu1 = (k-2*X-3)*(k-2*X-2*b1-1)*(k-2*X-2*b2-1)*\ + (k-2*X-2*b3-1) + uu2 = (4*(k-1)**3 - 6*(4*X+B3)*(k-1)**2 + \ + 2*(24*X**2+12*B3*X+4*B+B3-1)*(k-1) - 32*X**3 - \ + 24*B3*X**2 - 4*B - 8*R - 4*(4*B+B3-1)*X + 2*B3-1) + uu3 = (5*(k-1)**2+2*(-10*X+A2-3*B3+3)*(k-1)+2*c[1]) + c[k] = ctx.one/(2*k)*(uu1*c[k-3]-uu2*c[k-2]+uu3*c[k-1]) + w = c[k] * ctx.power(-z, -0.5*k) + t1 = (-ctx.j)**k * ctx.mpf(2)**(-k) * w + t2 = ctx.j**k * ctx.mpf(2)**(-k) * w + if abs(t1) < 0.1*ctx.eps: + break + # Quit if the series doesn't converge quickly enough + if k > 5 and abs(tprev) / abs(t1) < 1.5: + raise ctx.NoConvergence + s1 += t1 + s2 += t2 + tprev = t1 + k += 1 + S = ctx.expj(ctx.pi*X+2*ctx.sqrt(-z))*s1 + \ + ctx.expj(-(ctx.pi*X+2*ctx.sqrt(-z)))*s2 + T1 = [0.5*S, ctx.pi, -z], [1, -0.5, X], [b1, b2, b3], [a1, a2],\ + [], [], 0 + T2 = [-z], [-a1], [b1,b2,b3,a2-a1],[a2,b1-a1,b2-a1,b3-a1], \ + [a1,a1-b1+1,a1-b2+1,a1-b3+1], [a1-a2+1], 1/z + T3 = [-z], [-a2], [b1,b2,b3,a1-a2],[a1,b1-a2,b2-a2,b3-a2], \ + [a2,a2-b1+1,a2-b2+1,a2-b3+1],[-a1+a2+1], 1/z + return T1, T2, T3 + v = ctx.hypercomb(h, [a1,a2,b1,b2,b3], force_series=True, maxterms=4*ctx.prec) + if sum(ctx._is_real_type(u) for u in [a1,a2,b1,b2,b3,z]) == 6: + v = ctx.re(v) + return v + except ctx.NoConvergence: + pass + finally: + ctx.prec = orig + + return ctx.hypsum(2, 3, (a1type, a2type, b1type, b2type, b3type), [a1, a2, b1, b2, b3], z, **kwargs) + +@defun +def _hyp2f0(ctx, a_s, b_s, z, **kwargs): + (a, atype), (b, btype) = a_s + # We want to try aggressively to use the asymptotic expansion, + # and fall back only when absolutely necessary + try: + kwargsb = kwargs.copy() + kwargsb['maxterms'] = kwargsb.get('maxterms', ctx.prec) + return ctx.hypsum(2, 0, (atype,btype), [a,b], z, **kwargsb) + except ctx.NoConvergence: + if kwargs.get('force_series'): + raise + pass + def h(a, b): + w = ctx.sinpi(b) + rz = -1/z + T1 = ([ctx.pi,w,rz],[1,-1,a],[],[a-b+1,b],[a],[b],rz) + T2 = ([-ctx.pi,w,rz],[1,-1,1+a-b],[],[a,2-b],[a-b+1],[2-b],rz) + return T1, T2 + return ctx.hypercomb(h, [a, 1+a-b], **kwargs) + +@defun +def meijerg(ctx, a_s, b_s, z, r=1, series=None, **kwargs): + an, ap = a_s + bm, bq = b_s + n = len(an) + p = n + len(ap) + m = len(bm) + q = m + len(bq) + a = an+ap + b = bm+bq + a = [ctx.convert(_) for _ in a] + b = [ctx.convert(_) for _ in b] + z = ctx.convert(z) + if series is None: + if p < q: series = 1 + if p > q: series = 2 + if p == q: + if m+n == p and abs(z) > 1: + series = 2 + else: + series = 1 + if kwargs.get('verbose'): + print("Meijer G m,n,p,q,series =", m,n,p,q,series) + if series == 1: + def h(*args): + a = args[:p] + b = args[p:] + terms = [] + for k in range(m): + bases = [z] + expts = [b[k]/r] + gn = [b[j]-b[k] for j in range(m) if j != k] + gn += [1-a[j]+b[k] for j in range(n)] + gd = [a[j]-b[k] for j in range(n,p)] + gd += [1-b[j]+b[k] for j in range(m,q)] + hn = [1-a[j]+b[k] for j in range(p)] + hd = [1-b[j]+b[k] for j in range(q) if j != k] + hz = (-ctx.one)**(p-m-n) * z**(ctx.one/r) + terms.append((bases, expts, gn, gd, hn, hd, hz)) + return terms + else: + def h(*args): + a = args[:p] + b = args[p:] + terms = [] + for k in range(n): + bases = [z] + if r == 1: + expts = [a[k]-1] + else: + expts = [(a[k]-1)/ctx.convert(r)] + gn = [a[k]-a[j] for j in range(n) if j != k] + gn += [1-a[k]+b[j] for j in range(m)] + gd = [a[k]-b[j] for j in range(m,q)] + gd += [1-a[k]+a[j] for j in range(n,p)] + hn = [1-a[k]+b[j] for j in range(q)] + hd = [1+a[j]-a[k] for j in range(p) if j != k] + hz = (-ctx.one)**(q-m-n) / z**(ctx.one/r) + terms.append((bases, expts, gn, gd, hn, hd, hz)) + return terms + return ctx.hypercomb(h, a+b, **kwargs) + +@defun +def foxh(ctx, aA_s, bB_s, z, r=1, series=None, **kwargs): + aAn, aAp = aA_s + bBm, bBq = bB_s + n = len(aAn) + p = n + len(aAp) + m = len(bBm) + q = m + len(bBq) + aA = aAn+aAp + bB = bBm+bBq + a = [ctx.convert(a) for a, _ in aA] + b = [ctx.convert(b) for b, _ in bB] + A = [A for _, A in aA] + B = [B for _, B in bB] + z = ctx.convert(z) + r = ctx.convert(r) + + A = [ctx._convert_param(Ai) for Ai in A] + B = [ctx._convert_param(Bj) for Bj in B] + + if not all(Ai > 0 and (AiType == 'Z' or AiType == 'Q') for Ai, AiType in A + B): + raise NotImplementedError("All A and B must be positive rationals") + + # Find L.C.M. of denominators + D = math.lcm(*[Ai.denominator if AiType == 'Q' else 1 for Ai, AiType in A + B]) + + # Convert rationals to integers using common denominator + A = [Ai.numerator * (D // Ai.denominator) if AiType == 'Q' else Ai * D for Ai, AiType in A] + B = [Bi.numerator * (D // Bi.denominator) if BiType == 'Q' else Bi * D for Bi, BiType in B] + r = r / D + prefactor = ctx.convert(D) + + # Expand using Gauss Multiplication Formula + a_tilde = [] + for ai, Ai in zip(a, A): + for k in range(Ai): + a_tilde.append((ai + k) / Ai) + + b_tilde = [] + for bj, Bj in zip(b, B): + for k in range(Bj): + b_tilde.append((bj + k) / Bj) + + m_tilde = sum(B[:m]) + n_tilde = sum(A[:n]) + + a_star = ctx.convert(sum(A[:n]) - sum(A[n:]) + sum(B[:m]) - sum(B[m:])) + c_star = m + n - (ctx.convert(p) + q) / 2 + + beta = ctx.one + for Ai in A: + beta /= Ai**Ai + for Bj in B: + beta *= Bj**Bj + + # Compute M factor = prod(B_j^(b_j - 1/2)) / prod(A_i^(a_i - 1/2)) + M = ctx.one + for bj, Bj in zip(b, B): + M *= Bj**(bj - ctx.one/2) + for ai, Ai in zip(a, A): + M /= Ai**(ai - ctx.one/2) + + prefactor *= (2 * ctx.pi)**(c_star - a_star/2) * M + + return prefactor * meijerg( + ctx, + [a_tilde[:n_tilde], a_tilde[n_tilde:]], + [b_tilde[:m_tilde], b_tilde[m_tilde:]], + z / (beta ** r), + r, + series=series, + **kwargs + ) + +@defun_wrapped +def appellf1(ctx,a,b1,b2,c,x,y,**kwargs): + # Assume x smaller + # We will use x for the outer loop + if abs(x) > abs(y): + x, y = y, x + b1, b2 = b2, b1 + def ok(x): + return abs(x) < 0.99 + # Finite cases + if ctx.isnpint(a): + pass + elif ctx.isnpint(b1): + pass + elif ctx.isnpint(b2): + x, y, b1, b2 = y, x, b2, b1 + else: + #print x, y + # Note: ok if |y| > 1, because + # 2F1 implements analytic continuation + if not ok(x): + u1 = (x-y)/(x-1) + if not ok(u1): + raise ValueError("Analytic continuation not implemented") + #print "Using analytic continuation" + return (1-x)**(-b1)*(1-y)**(c-a-b2)*\ + ctx.appellf1(c-a,b1,c-b1-b2,c,u1,y,**kwargs) + return ctx.hyper2d({'m+n':[a],'m':[b1],'n':[b2]}, {'m+n':[c]}, x,y, **kwargs) + +@defun +def appellf2(ctx,a,b1,b2,c1,c2,x,y,**kwargs): + # TODO: continuation + return ctx.hyper2d({'m+n':[a],'m':[b1],'n':[b2]}, + {'m':[c1],'n':[c2]}, x,y, **kwargs) + +@defun +def appellf3(ctx,a1,a2,b1,b2,c,x,y,**kwargs): + outer_polynomial = ctx.isnpint(a1) or ctx.isnpint(b1) + inner_polynomial = ctx.isnpint(a2) or ctx.isnpint(b2) + if not outer_polynomial: + if inner_polynomial or abs(x) > abs(y): + x, y = y, x + a1,a2,b1,b2 = a2,a1,b2,b1 + return ctx.hyper2d({'m':[a1,b1],'n':[a2,b2]}, {'m+n':[c]},x,y,**kwargs) + +@defun +def appellf4(ctx,a,b,c1,c2,x,y,**kwargs): + # TODO: continuation + return ctx.hyper2d({'m+n':[a,b]}, {'m':[c1],'n':[c2]},x,y,**kwargs) + +@defun +def hyper2d(ctx, a, b, x, y, **kwargs): + r""" + Sums the generalized 2D hypergeometric series + + .. math :: + + \sum_{m=0}^{\infty} \sum_{n=0}^{\infty} + \frac{P((a),m,n)}{Q((b),m,n)} + \frac{x^m y^n} {m! n!} + + where `(a) = (a_1,\ldots,a_r)`, `(b) = (b_1,\ldots,b_s)` and where + `P` and `Q` are products of rising factorials such as `(a_j)_n` or + `(a_j)_{m+n}`. `P` and `Q` are specified in the form of dicts, with + the `m` and `n` dependence as keys and parameter lists as values. + The supported rising factorials are given in the following table + (note that only a few are supported in `Q`): + + +------------+-------------------+--------+ + | Key | Rising factorial | `Q` | + +============+===================+========+ + | ``'m'`` | `(a_j)_m` | Yes | + +------------+-------------------+--------+ + | ``'n'`` | `(a_j)_n` | Yes | + +------------+-------------------+--------+ + | ``'m+n'`` | `(a_j)_{m+n}` | Yes | + +------------+-------------------+--------+ + | ``'m-n'`` | `(a_j)_{m-n}` | No | + +------------+-------------------+--------+ + | ``'n-m'`` | `(a_j)_{n-m}` | No | + +------------+-------------------+--------+ + | ``'2m+n'`` | `(a_j)_{2m+n}` | No | + +------------+-------------------+--------+ + | ``'2m-n'`` | `(a_j)_{2m-n}` | No | + +------------+-------------------+--------+ + | ``'2n-m'`` | `(a_j)_{2n-m}` | No | + +------------+-------------------+--------+ + + For example, the Appell F1 and F4 functions + + .. math :: + + F_1 = \sum_{m=0}^{\infty} \sum_{n=0}^{\infty} + \frac{(a)_{m+n} (b)_m (c)_n}{(d)_{m+n}} + \frac{x^m y^n}{m! n!} + + F_4 = \sum_{m=0}^{\infty} \sum_{n=0}^{\infty} + \frac{(a)_{m+n} (b)_{m+n}}{(c)_m (d)_{n}} + \frac{x^m y^n}{m! n!} + + can be represented respectively as + + ``hyper2d({'m+n':[a], 'm':[b], 'n':[c]}, {'m+n':[d]}, x, y)`` + + ``hyper2d({'m+n':[a,b]}, {'m':[c], 'n':[d]}, x, y)`` + + More generally, :func:`~mpmath.hyper2d` can evaluate any of the 34 distinct + convergent second-order (generalized Gaussian) hypergeometric + series enumerated by Horn, as well as the Kampe de Feriet + function. + + The series is computed by rewriting it so that the inner + series (i.e. the series containing `n` and `y`) has the form of an + ordinary generalized hypergeometric series and thereby can be + evaluated efficiently using :func:`~mpmath.hyper`. If possible, + manually swapping `x` and `y` and the corresponding parameters + can sometimes give better results. + + **Examples** + + Two separable cases: a product of two geometric series, and a + product of two Gaussian hypergeometric functions:: + + >>> from mpmath import mp, mpf, hyper2d, hyp2f1, exp + >>> mp.dps = 25 + >>> mp.pretty = True + >>> x, y = mpf(0.25), mpf(0.5) + >>> hyper2d({'m':1,'n':1}, {}, x,y) + 2.666666666666666666666667 + >>> 1/(1-x)/(1-y) + 2.666666666666666666666667 + >>> hyper2d({'m':[1,2],'n':[3,4]}, {'m':[5],'n':[6]}, x,y) + 4.164358531238938319669856 + >>> hyp2f1(1,2,5,x)*hyp2f1(3,4,6,y) + 4.164358531238938319669856 + + Some more series that can be done in closed form:: + + >>> hyper2d({'m':1,'n':1},{'m+n':1},x,y) + 2.013417124712514809623881 + >>> (exp(x)*x-exp(y)*y)/(x-y) + 2.013417124712514809623881 + + Six of the 34 Horn functions, G1-G3 and H1-H3:: + + >>> from mpmath import mp, hyper2d, nsum, fac, inf, rf + >>> mp.dps = 10 + >>> mp.pretty = True + >>> x, y = 0.0625, 0.125 + >>> a1,a2,b1,b2,c1,c2,d = 1.1,-1.2,-1.3,-1.4,1.5,-1.6,1.7 + >>> hyper2d({'m+n':a1,'n-m':b1,'m-n':b2},{},x,y) # G1 + 1.139090746 + >>> nsum(lambda m,n: rf(a1,m+n)*rf(b1,n-m)*rf(b2,m-n)*\ + ... x**m*y**n/fac(m)/fac(n), [0,inf], [0,inf]) + 1.139090746 + >>> hyper2d({'m':a1,'n':a2,'n-m':b1,'m-n':b2},{},x,y) # G2 + 0.9503682696 + >>> nsum(lambda m,n: rf(a1,m)*rf(a2,n)*rf(b1,n-m)*rf(b2,m-n)*\ + ... x**m*y**n/fac(m)/fac(n), [0,inf], [0,inf]) + 0.9503682696 + >>> hyper2d({'2n-m':a1,'2m-n':a2},{},x,y) # G3 + 1.029372029 + >>> nsum(lambda m,n: rf(a1,2*n-m)*rf(a2,2*m-n)*\ + ... x**m*y**n/fac(m)/fac(n), [0,inf], [0,inf]) + 1.029372029 + >>> hyper2d({'m-n':a1,'m+n':b1,'n':c1},{'m':d},x,y) # H1 + -1.605331256 + >>> nsum(lambda m,n: rf(a1,m-n)*rf(b1,m+n)*rf(c1,n)/rf(d,m)*\ + ... x**m*y**n/fac(m)/fac(n), [0,inf], [0,inf]) + -1.605331256 + >>> hyper2d({'m-n':a1,'m':b1,'n':[c1,c2]},{'m':d},x,y) # H2 + -2.35405404 + >>> nsum(lambda m,n: rf(a1,m-n)*rf(b1,m)*rf(c1,n)*rf(c2,n)/rf(d,m)*\ + ... x**m*y**n/fac(m)/fac(n), [0,inf], [0,inf]) + -2.35405404 + >>> hyper2d({'2m+n':a1,'n':b1},{'m+n':c1},x,y) # H3 + 0.974479074 + >>> nsum(lambda m,n: rf(a1,2*m+n)*rf(b1,n)/rf(c1,m+n)*\ + ... x**m*y**n/fac(m)/fac(n), [0,inf], [0,inf]) + 0.974479074 + + **References** + + 1. [SrivastavaKarlsson]_ + 2. [Weisstein]_ http://mathworld.wolfram.com/HornFunction.html + 3. [Weisstein]_ http://mathworld.wolfram.com/AppellHypergeometricFunction.html + + """ + x = ctx.convert(x) + y = ctx.convert(y) + def parse(dct, key): + args = dct.pop(key, []) + try: + args = list(args) + except TypeError: + args = [args] + return [ctx.convert(arg) for arg in args] + a_s = dict(a) + b_s = dict(b) + a_m = parse(a, 'm') + a_n = parse(a, 'n') + a_m_add_n = parse(a, 'm+n') + a_m_sub_n = parse(a, 'm-n') + a_n_sub_m = parse(a, 'n-m') + a_2m_add_n = parse(a, '2m+n') + a_2m_sub_n = parse(a, '2m-n') + a_2n_sub_m = parse(a, '2n-m') + b_m = parse(b, 'm') + b_n = parse(b, 'n') + b_m_add_n = parse(b, 'm+n') + if a: raise ValueError("unsupported key: %r" % a.keys()[0]) + if b: raise ValueError("unsupported key: %r" % b.keys()[0]) + s = 0 + outer = ctx.one + m = ctx.mpf(0) + ok_count = 0 + prec = ctx.prec + maxterms = kwargs.get('maxterms', 20*prec) + try: + ctx.prec += 10 + tol = +ctx.eps + while 1: + inner_sign = 1 + outer_sign = 1 + inner_a = list(a_n) + inner_b = list(b_n) + outer_a = [a+m for a in a_m] + outer_b = [b+m for b in b_m] + # (a)_{m+n} = (a)_m (a+m)_n + for a in a_m_add_n: + a = a+m + inner_a.append(a) + outer_a.append(a) + # (b)_{m+n} = (b)_m (b+m)_n + for b in b_m_add_n: + b = b+m + inner_b.append(b) + outer_b.append(b) + # (a)_{n-m} = (a-m)_n / (a-m)_m + for a in a_n_sub_m: + inner_a.append(a-m) + outer_b.append(a-m-1) + # (a)_{m-n} = (-1)^(m+n) (1-a-m)_m / (1-a-m)_n + for a in a_m_sub_n: + inner_sign *= (-1) + outer_sign *= (-1)**(m) + inner_b.append(1-a-m) + outer_a.append(-a-m) + # (a)_{2m+n} = (a)_{2m} (a+2m)_n + for a in a_2m_add_n: + inner_a.append(a+2*m) + outer_a.append((a+2*m)*(1+a+2*m)) + # (a)_{2m-n} = (-1)^(2m+n) (1-a-2m)_{2m} / (1-a-2m)_n + for a in a_2m_sub_n: + inner_sign *= (-1) + inner_b.append(1-a-2*m) + outer_a.append((a+2*m)*(1+a+2*m)) + # (a)_{2n-m} = 4^n ((a-m)/2)_n ((a-m+1)/2)_n / (a-m)_m + for a in a_2n_sub_m: + inner_sign *= 4 + inner_a.append(0.5*(a-m)) + inner_a.append(0.5*(a-m+1)) + outer_b.append(a-m-1) + inner = ctx.hyper(inner_a, inner_b, inner_sign*y, + zeroprec=ctx.prec, **kwargs) + term = outer * inner * outer_sign + if abs(term) < tol: + ok_count += 1 + else: + ok_count = 0 + if ok_count >= 3 or not outer: + break + s += term + for a in outer_a: outer *= a + for b in outer_b: outer /= b + m += 1 + outer = outer * x / m + if m > maxterms: + raise ctx.NoConvergence("maxterms exceeded in hyper2d") + finally: + ctx.prec = prec + return +s + +""" +@defun +def kampe_de_feriet(ctx,a,b,c,d,e,f,x,y,**kwargs): + return ctx.hyper2d({'m+n':a,'m':b,'n':c}, + {'m+n':d,'m':e,'n':f}, x,y, **kwargs) +""" + +@defun +def bihyper(ctx, a_s, b_s, z, **kwargs): + r""" + Evaluates the bilateral hypergeometric series + + .. math :: + + \,_AH_B(a_1, \ldots, a_k; b_1, \ldots, b_B; z) = + \sum_{n=-\infty}^{\infty} + \frac{(a_1)_n \ldots (a_A)_n} + {(b_1)_n \ldots (b_B)_n} \, z^n + + where, for direct convergence, `A = B` and `|z| = 1`, although a + regularized sum exists more generally by considering the + bilateral series as a sum of two ordinary hypergeometric + functions. In order for the series to make sense, none of the + parameters may be integers. + + **Examples** + + The value of `\,_2H_2` at `z = 1` is given by Dougall's formula:: + + >>> from mpmath import mp, bihyper, mpf, hyper, gammaprod + >>> mp.dps = 25 + >>> mp.pretty = True + >>> a,b,c,d = 0.5, 1.5, 2.25, 3.25 + >>> bihyper([a,b],[c,d],1) + -14.49118026212345786148847 + >>> gammaprod([c,d,1-a,1-b,c+d-a-b-1],[c-a,d-a,c-b,d-b]) + -14.49118026212345786148847 + + The regularized function `\,_1H_0` can be expressed as the + sum of one `\,_2F_0` function and one `\,_1F_1` function:: + + >>> a = mpf(0.25) + >>> z = mpf(0.75) + >>> bihyper([a], [], z) + (0.2454393389657273841385582 + 0.2454393389657273841385582j) + >>> hyper([a,1],[],z) + (hyper([1],[1-a],-1/z)-1) + (0.2454393389657273841385582 + 0.2454393389657273841385582j) + >>> hyper([a,1],[],z) + hyper([1],[2-a],-1/z)/z/(a-1) + (0.2454393389657273841385582 + 0.2454393389657273841385582j) + + **References** + + 1. [Slater]_ (chapter 6: "Bilateral Series", pp. 180-189) + 2. [Wikipedia]_ http://en.wikipedia.org/wiki/Bilateral_hypergeometric_series + + """ + z = ctx.convert(z) + c_s = a_s + b_s + p = len(a_s) + q = len(b_s) + if (p, q) == (0,0) or (p, q) == (1,1): + return ctx.zero * z + neg = (p-q) % 2 + def h(*c_s): + a_s = list(c_s[:p]) + b_s = list(c_s[p:]) + aa_s = [2-b for b in b_s] + bb_s = [2-a for a in a_s] + rp = [(-1)**neg * z] + [1-b for b in b_s] + [1-a for a in a_s] + rc = [-1] + [1]*len(b_s) + [-1]*len(a_s) + T1 = [], [], [], [], a_s + [1], b_s, z + T2 = rp, rc, [], [], aa_s + [1], bb_s, (-1)**neg / z + return T1, T2 + return ctx.hypercomb(h, c_s, **kwargs) diff --git a/mpmath/functions/orthogonal.py b/mpmath/functions/orthogonal.py new file mode 100644 index 0000000..b7c5613 --- /dev/null +++ b/mpmath/functions/orthogonal.py @@ -0,0 +1,508 @@ +from ..libmp.backend import MPQ +from .functions import defun, defun_wrapped + +def _hermite_param(ctx, n, z, parabolic_cylinder): + """ + Combined calculation of the Hermite polynomial H_n(z) (and its + generalization to complex n) and the parabolic cylinder + function D. + """ + n, ntyp = ctx._convert_param(n) + z = ctx.convert(z) + q = -MPQ(1,2) + # For re(z) > 0, 2F0 -- http://functions.wolfram.com/ + # HypergeometricFunctions/HermiteHGeneral/06/02/0009/ + # Otherwise, there is a reflection formula + # 2F0 + http://functions.wolfram.com/HypergeometricFunctions/ + # HermiteHGeneral/16/01/01/0006/ + # + # TODO: + # An alternative would be to use + # http://functions.wolfram.com/HypergeometricFunctions/ + # HermiteHGeneral/06/02/0006/ + # + # Also, the 1F1 expansion + # http://functions.wolfram.com/HypergeometricFunctions/ + # HermiteHGeneral/26/01/02/0001/ + # should probably be used for tiny z + if not z: + T1 = [2, ctx.pi], [n, 0.5], [], [q*(n-1)], [], [], 0 + if parabolic_cylinder: + T1[1][0] += q*n + return T1, + can_use_2f0 = ctx.isnpint(-n) or ctx.re(z) > 0 or \ + (ctx.re(z) == 0 and ctx.im(z) > 0) + expprec = ctx.prec*4 + 20 + if parabolic_cylinder: + u = ctx.fmul(ctx.fmul(z,z,prec=expprec), -0.25, exact=True) + w = ctx.fmul(z, ctx.sqrt(0.5,prec=expprec), prec=expprec) + else: + w = z + w2 = ctx.fmul(w, w, prec=expprec) + rw2 = ctx.fdiv(1, w2, prec=expprec) + nrw2 = ctx.fneg(rw2, exact=True) + nw = ctx.fneg(w, exact=True) + if can_use_2f0: + T1 = [2, w], [n, n], [], [], [q*n, q*(n-1)], [], nrw2 + terms = [T1] + else: + T1 = [2, nw], [n, n], [], [], [q*n, q*(n-1)], [], nrw2 + T2 = [2, ctx.pi, nw], [n+2, 0.5, 1], [], [q*n], [q*(n-1)], [1-q], w2 + terms = [T1,T2] + # Multiply by prefactor for D_n + if parabolic_cylinder: + expu = ctx.exp(u) + for i in range(len(terms)): + terms[i][1][0] += q*n + terms[i][0].append(expu) + terms[i][1].append(1) + return tuple(terms) + +@defun +def hermite(ctx, n, z, **kwargs): + return ctx.hypercomb(lambda: _hermite_param(ctx, n, z, 0), [], **kwargs) + +@defun +def pcfd(ctx, n, z, **kwargs): + r""" + Gives the parabolic cylinder function in Whittaker's notation + `D_n(z) = U(-n-1/2, z)` (see :func:`~mpmath.pcfu`). + It solves the differential equation + + .. math :: + + y'' + \left(n + \frac{1}{2} - \frac{1}{4} z^2\right) y = 0. + + and can be represented in terms of Hermite polynomials + (see :func:`~mpmath.hermite`) as + + .. math :: + + D_n(z) = 2^{-n/2} e^{-z^2/4} H_n\left(\frac{z}{\sqrt{2}}\right). + + **Plots** + + .. literalinclude :: /plots/pcfd.py + .. image :: /plots/pcfd.png + + **Examples** + + >>> from mpmath import mp, pcfd, mpf, chop, diff, taylor + >>> mp.dps = 25 + >>> mp.pretty = True + >>> pcfd(0,0) + 1.0 + >>> pcfd(1,0) + 0.0 + >>> pcfd(2,0) + -1.0 + >>> pcfd(3,0) + 0.0 + >>> pcfd(4,0) + 3.0 + >>> pcfd(-3,0) + 0.6266570686577501256039413 + >>> pcfd('1/2', 2+3j) + (-5.363331161232920734849056 - 3.858877821790010714163487j) + >>> pcfd(2, -10) + 1.374906442631438038871515e-9 + + Verifying the differential equation:: + + >>> n = mpf(2.5) + >>> y = lambda z: pcfd(n,z) + >>> z = 1.75 + >>> chop(diff(y,z,2) + (n+0.5-0.25*z**2)*y(z)) + 0.0 + + Rational Taylor series expansion when `n` is an integer:: + + >>> taylor(lambda z: pcfd(5,z), 0, 7) + [0.0, 15.0, 0.0, -13.75, 0.0, 3.96875, 0.0, -0.6015625] + + """ + return ctx.hypercomb(lambda: _hermite_param(ctx, n, z, 1), [], **kwargs) + +@defun +def pcfu(ctx, a, z, **kwargs): + r""" + Gives the parabolic cylinder function `U(a,z)`, which may be + defined for `\Re(z) > 0` in terms of the confluent + U-function (see :func:`~mpmath.hyperu`) by + + .. math :: + + U(a,z) = 2^{-\frac{1}{4}-\frac{a}{2}} e^{-\frac{1}{4} z^2} + U\left(\frac{a}{2}+\frac{1}{4}, + \frac{1}{2}, \frac{1}{2}z^2\right) + + or, for arbitrary `z`, + + .. math :: + + e^{-\frac{1}{4}z^2} U(a,z) = + U(a,0) \,_1F_1\left(-\tfrac{a}{2}+\tfrac{1}{4}; + \tfrac{1}{2}; -\tfrac{1}{2}z^2\right) + + U'(a,0) z \,_1F_1\left(-\tfrac{a}{2}+\tfrac{3}{4}; + \tfrac{3}{2}; -\tfrac{1}{2}z^2\right). + + **Examples** + + Connection to other functions:: + + >>> from mpmath import mp, mpf, pcfu, sqrt, pi, exp, erfc + >>> mp.dps = 25 + >>> mp.pretty = True + >>> z = mpf(3) + >>> pcfu(0.5,z) + 0.03210358129311151450551963 + >>> sqrt(pi/2)*exp(z**2/4)*erfc(z/sqrt(2)) + 0.03210358129311151450551963 + >>> pcfu(0.5,-z) + 23.75012332835297233711255 + >>> sqrt(pi/2)*exp(z**2/4)*erfc(-z/sqrt(2)) + 23.75012332835297233711255 + >>> pcfu(0.5,-z) + 23.75012332835297233711255 + >>> sqrt(pi/2)*exp(z**2/4)*erfc(-z/sqrt(2)) + 23.75012332835297233711255 + + """ + n, _ = ctx._convert_param(a) + return ctx.pcfd(-n-MPQ(1,2), z) + +@defun +def pcfv(ctx, a, z, **kwargs): + r""" + Gives the parabolic cylinder function `V(a,z)`, which can be + represented in terms of :func:`~mpmath.pcfu` as + + .. math :: + + V(a,z) = \frac{\Gamma(a+\tfrac{1}{2}) (U(a,-z)-\sin(\pi a) U(a,z)}{\pi}. + + **Examples** + + Wronskian relation between `U` and `V`:: + + >>> from mpmath import mp, pcfu, diff, pcfv, sqrt, pi, chop + >>> mp.dps = 25 + >>> mp.pretty = True + >>> a, z = 2, 3 + >>> pcfu(a,z)*diff(pcfv,(a,z),(0,1))-diff(pcfu,(a,z),(0,1))*pcfv(a,z) + 0.7978845608028653558798921 + >>> sqrt(2/pi) + 0.7978845608028653558798921 + >>> a, z = 2.5, 3 + >>> pcfu(a,z)*diff(pcfv,(a,z),(0,1))-diff(pcfu,(a,z),(0,1))*pcfv(a,z) + 0.7978845608028653558798921 + >>> a, z = 0.25, -1 + >>> pcfu(a,z)*diff(pcfv,(a,z),(0,1))-diff(pcfu,(a,z),(0,1))*pcfv(a,z) + 0.7978845608028653558798921 + >>> a, z = 2+1j, 2+3j + >>> chop(pcfu(a,z)*diff(pcfv,(a,z),(0,1))-diff(pcfu,(a,z),(0,1))*pcfv(a,z)) + 0.7978845608028653558798921 + + """ + n, ntype = ctx._convert_param(a) + z = ctx.convert(z) + q = MPQ(1,2) + r = MPQ(1,4) + if ntype == 'Q' and ctx.isint(n*2): + # Faster for half-integers + def h(): + jz = ctx.fmul(z, -1j, exact=True) + T1terms = _hermite_param(ctx, -n-q, z, 1) + T2terms = _hermite_param(ctx, n-q, jz, 1) + for T in T1terms: + T[0].append(1j) + T[1].append(1) + T[3].append(q-n) + u = ctx.expjpi((q*n-r)) * ctx.sqrt(2/ctx.pi) + for T in T2terms: + T[0].append(u) + T[1].append(1) + return T1terms + T2terms + v = ctx.hypercomb(h, [], **kwargs) + if ctx._is_real_type(n) and ctx._is_real_type(z): + v = ctx._re(v) + return v + else: + def h(n): + w = ctx.square_exp_arg(z, -0.25) + u = ctx.square_exp_arg(z, 0.5) + e = ctx.exp(w) + l = [ctx.pi, q, ctx.exp(w)] + Y1 = l, [-q, n*q+r, 1], [r-q*n], [], [q*n+r], [q], u + Y2 = l + [z], [-q, n*q-r, 1, 1], [1-r-q*n], [], [q*n+1-r], [1+q], u + c, s = ctx.cospi_sinpi(r+q*n) + Y1[0].append(s) + Y2[0].append(c) + for Y in (Y1, Y2): + Y[1].append(1) + Y[3].append(q-n) + return Y1, Y2 + return ctx.hypercomb(h, [n], **kwargs) + + +@defun +def pcfw(ctx, a, z, **kwargs): + r""" + Gives the parabolic cylinder function `W(a,z)` defined in (DLMF 12.14). + + **Examples** + + Value at the origin:: + + >>> from mpmath import mp, mpf, pcfw, power, gamma, sqrt, diff + >>> mp.dps = 25 + >>> mp.pretty = True + >>> a = mpf(0.25) + >>> pcfw(a,0) + 0.9722833245718180765617104 + >>> power(2,-0.75)*sqrt(abs(gamma(0.25+0.5j*a)/gamma(0.75+0.5j*a))) + 0.9722833245718180765617104 + >>> diff(pcfw,(a,0),(0,1)) + -0.5142533944210078966003624 + >>> -power(2,-0.25)*sqrt(abs(gamma(0.75+0.5j*a)/gamma(0.25+0.5j*a))) + -0.5142533944210078966003624 + + """ + n, _ = ctx._convert_param(a) + z = ctx.convert(z) + def terms(): + phi2 = ctx.arg(ctx.gamma(0.5 + ctx.j*n)) + phi2 = (ctx.loggamma(0.5+ctx.j*n) - ctx.loggamma(0.5-ctx.j*n))/2j + rho = ctx.pi/8 + 0.5*phi2 + # XXX: cancellation computing k + k = ctx.sqrt(1 + ctx.exp(2*ctx.pi*n)) - ctx.exp(ctx.pi*n) + C = ctx.sqrt(k/2) * ctx.exp(0.25*ctx.pi*n) + yield C * ctx.expj(rho) * ctx.pcfu(ctx.j*n, z*ctx.expjpi(-0.25)) + yield C * ctx.expj(-rho) * ctx.pcfu(-ctx.j*n, z*ctx.expjpi(0.25)) + v = ctx.sum_accurately(terms) + if ctx._is_real_type(n) and ctx._is_real_type(z): + v = ctx._re(v) + return v + +""" +Even/odd PCFs. Useful? + +@defun +def pcfy1(ctx, a, z, **kwargs): + a, _ = ctx._convert_param(n) + z = ctx.convert(z) + def h(): + w = ctx.square_exp_arg(z) + w1 = ctx.fmul(w, -0.25, exact=True) + w2 = ctx.fmul(w, 0.5, exact=True) + e = ctx.exp(w1) + return [e], [1], [], [], [MPQ(1,2)*a+MPQ(1,4)], [MPQ(1,2)], w2 + return ctx.hypercomb(h, [], **kwargs) + +@defun +def pcfy2(ctx, a, z, **kwargs): + a, _ = ctx._convert_param(n) + z = ctx.convert(z) + def h(): + w = ctx.square_exp_arg(z) + w1 = ctx.fmul(w, -0.25, exact=True) + w2 = ctx.fmul(w, 0.5, exact=True) + e = ctx.exp(w1) + return [e, z], [1, 1], [], [], [MPQ(1,2)*a+MPQ(3,4)], \ + [MPQ(3,2)], w2 + return ctx.hypercomb(h, [], **kwargs) +""" + +@defun_wrapped +def gegenbauer(ctx, n, a, z, **kwargs): + # Special cases: a+0.5, a*2 poles + if ctx.isnpint(a): + return 0*(z+n) + if not z and ctx.isint(n) and int(n.real) % 2: + return ctx.zero + if ctx.isnpint(a+0.5): + # TODO: something else is required here + # E.g.: gegenbauer(-2, -0.5, 3) == -12 + if ctx.isnpint(n+1): + raise NotImplementedError("Gegenbauer function with two limits") + def h(a): + a2 = 2*a + T = [], [], [n+a2], [n+1, a2], [-n, n+a2], [a+0.5], 0.5*(1-z) + return [T] + return ctx.hypercomb(h, [a], **kwargs) + def h(n): + a2 = 2*a + T = [], [], [n+a2], [n+1, a2], [-n, n+a2], [a+0.5], 0.5*(1-z) + return [T] + return ctx.hypercomb(h, [n], **kwargs) + +@defun_wrapped +def jacobi(ctx, n, a, b, x, **kwargs): + if not ctx.isnpint(a): + def h(n): + return (([], [], [a+n+1], [n+1, a+1], [-n, a+b+n+1], [a+1], (1-x)*0.5),) + return ctx.hypercomb(h, [n], **kwargs) + if not ctx.isint(b): + def h(n, a): + return (([], [], [-b], [n+1, -b-n], [-n, a+b+n+1], [b+1], (x+1)*0.5),) + return ctx.hypercomb(h, [n, a], **kwargs) + # XXX: determine appropriate limit + return ctx.binomial(n+a,n) * ctx.hyp2f1(-n,1+n+a+b,a+1,(1-x)/2, **kwargs) + +@defun_wrapped +def laguerre(ctx, n, a, z, **kwargs): + # XXX: limits, poles + #if ctx.isnpint(n): + # return 0*(a+z) + def h(a): + return (([], [], [a+n+1], [a+1, n+1], [-n], [a+1], z),) + return ctx.hypercomb(h, [a], **kwargs) + +@defun_wrapped +def legendre(ctx, n, x, **kwargs): + if ctx.isint(n): + n = int(n) + # Accuracy near zeros + if (n + (n < 0)) & 1: + if not x: + return x + mag = ctx.mag(x) + if mag < -2*ctx.prec-10: + return x + if mag < -5: + ctx.prec += -mag + return ctx.hyp2f1(-n,n+1,1,(1-x)/2, **kwargs) + +@defun +def legenp(ctx, n, m, z, type=2, **kwargs): + # Legendre function, 1st kind + n = ctx.convert(n) + m = ctx.convert(m) + # Faster + if not m: + return ctx.legendre(n, z, **kwargs) + # TODO: correct evaluation at singularities + if type == 2: + def h(n,m): + g = m*0.5 + T = [1+z, 1-z], [g, -g], [], [1-m], [-n, n+1], [1-m], 0.5*(1-z) + return (T,) + return ctx.hypercomb(h, [n,m], **kwargs) + if type == 3: + def h(n,m): + g = m*0.5 + T = [z+1, z-1], [g, -g], [], [1-m], [-n, n+1], [1-m], 0.5*(1-z) + return (T,) + return ctx.hypercomb(h, [n,m], **kwargs) + raise ValueError("requires type=2 or type=3") + +@defun +def legenq(ctx, n, m, z, type=2, **kwargs): + # Legendre function, 2nd kind + n = ctx.convert(n) + m = ctx.convert(m) + z = ctx.convert(z) + if z in (1, -1): + #if ctx.isint(m): + # return ctx.nan + #return ctx.inf # unsigned + return ctx.nan + if type == 2: + def h(n, m): + cos, sin = ctx.cospi_sinpi(m) + s = 2 * sin / ctx.pi + c = cos + a = 1+z + b = 1-z + u = m/2 + w = (1-z)/2 + T1 = [s, c, a, b], [-1, 1, u, -u], [], [1-m], \ + [-n, n+1], [1-m], w + T2 = [-s, a, b], [-1, -u, u], [n+m+1], [n-m+1, m+1], \ + [-n, n+1], [m+1], w + return T1, T2 + return ctx.hypercomb(h, [n, m], **kwargs) + if type == 3: + # The following is faster when there only is a single series + # Note: not valid for -1 < z < 0 (?) + if abs(z) > 1: + def h(n, m): + T1 = [ctx.expjpi(m), 2, ctx.pi, z, z-1, z+1], \ + [1, -n-1, 0.5, -n-m-1, 0.5*m, 0.5*m], \ + [n+m+1], [n+1.5], \ + [0.5*(2+n+m), 0.5*(1+n+m)], [n+1.5], z**(-2) + return [T1] + return ctx.hypercomb(h, [n, m], **kwargs) + else: + # not valid for 1 < z < inf ? + def h(n, m): + s = 2 * ctx.sinpi(m) / ctx.pi + c = ctx.expjpi(m) + a = 1+z + b = z-1 + u = m/2 + w = (1-z)/2 + T1 = [s, c, a, b], [-1, 1, u, -u], [], [1-m], \ + [-n, n+1], [1-m], w + T2 = [-s, c, a, b], [-1, 1, -u, u], [n+m+1], [n-m+1, m+1], \ + [-n, n+1], [m+1], w + return T1, T2 + return ctx.hypercomb(h, [n, m], **kwargs) + raise ValueError("requires type=2 or type=3") + +@defun_wrapped +def chebyt(ctx, n, x, **kwargs): + if (not x) and ctx.isint(n) and int(ctx._re(n)) % 2 == 1: + return x * 0 + if kwargs.get('force_series') is None: + kwargs['force_series'] = True + return ctx.hyp2f1(-n,n,(1,2),(1-x)/2, **kwargs) + +@defun_wrapped +def chebyu(ctx, n, x, **kwargs): + if (not x) and ctx.isint(n) and int(ctx._re(n)) % 2 == 1: + return x * 0 + if kwargs.get('force_series') is None: + kwargs['force_series'] = True + return (n+1) * ctx.hyp2f1(-n, n+2, (3,2), (1-x)/2, **kwargs) + +@defun +def spherharm(ctx, l, m, theta, phi, **kwargs): + l = ctx.convert(l) + m = ctx.convert(m) + theta = ctx.convert(theta) + phi = ctx.convert(phi) + l_isint = ctx.isint(l) + l_natural = l_isint and l >= 0 + m_isint = ctx.isint(m) + if l_isint and l < 0 and m_isint: + return ctx.spherharm(-(l+1), m, theta, phi, **kwargs) + if theta == 0 and m_isint and m < 0: + return ctx.zero * 1j + if l_natural and m_isint: + if abs(m) > l: + return ctx.zero * 1j + # http://functions.wolfram.com/Polynomials/ + # SphericalHarmonicY/26/01/02/0004/ + def h(l,m): + absm = abs(m) + C = [-1, ctx.expj(m*phi), + (2*l+1)*ctx.fac(l+absm)/ctx.pi/ctx.fac(l-absm), + ctx.sin(theta)**2, + ctx.fac(absm), 2] + P = [0.5*m*(ctx.sign(m)+1), 1, 0.5, 0.5*absm, -1, -absm-1] + return ((C, P, [], [], [absm-l, l+absm+1], [absm+1], + ctx.sin(0.5*theta)**2),) + else: + # http://functions.wolfram.com/HypergeometricFunctions/ + # SphericalHarmonicYGeneral/26/01/02/0001/ + def h(l,m): + if ctx.isnpint(l-m+1) or ctx.isnpint(l+m+1) or ctx.isnpint(1-m): + return (([0], [-1], [], [], [], [], 0),) + cos, sin = ctx.cos_sin(0.5*theta) + C = [0.5*ctx.expj(m*phi), (2*l+1)/ctx.pi, + ctx.gamma(l-m+1), ctx.gamma(l+m+1), + cos**2, sin**2] + P = [1, 0.5, 0.5, -0.5, 0.5*m, -0.5*m] + return ((C, P, [], [1-m], [-l,l+1], [1-m], sin**2),) + return ctx.hypercomb(h, [l,m], **kwargs) diff --git a/mpmath/functions/qfunctions.py b/mpmath/functions/qfunctions.py new file mode 100644 index 0000000..f899be3 --- /dev/null +++ b/mpmath/functions/qfunctions.py @@ -0,0 +1,285 @@ +from .functions import defun, defun_wrapped + +@defun +def qp(ctx, a, q=None, n=None, **kwargs): + r""" + Evaluates the q-Pochhammer symbol (or q-rising factorial) + + .. math :: + + (a; q)_n = \prod_{k=0}^{n-1} (1-a q^k) + + where `n = \infty` is permitted if `|q| < 1`. Called with two arguments, + ``qp(a,q)`` computes `(a;q)_{\infty}`; with a single argument, ``qp(q)`` + computes `(q;q)_{\infty}`. The special case + + .. math :: + + \phi(q) = (q; q)_{\infty} = \prod_{k=1}^{\infty} (1-q^k) = + \sum_{k=-\infty}^{\infty} (-1)^k q^{(3k^2-k)/2} + + is also known as the Euler function, or (up to a factor `q^{-1/24}`) + the Dedekind eta function. + + **Examples** + + If `n` is a positive integer, the function amounts to a finite product:: + + >>> from mpmath import (mp, qp, fprod, limit, rf, taylor, findroot, + ... diffun, mpf, jtheta, pi, root) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> qp(2,3,5) + -725305.0 + >>> fprod(1-2*3**k for k in range(5)) + -725305.0 + >>> qp(2,3,0) + 1.0 + + Complex arguments are allowed:: + + >>> qp(2-1j, 0.75j) + (0.4628842231660149089976379 + 4.481821753552703090628793j) + + The regular Pochhammer symbol `(a)_n` is obtained in the + following limit as `q \to 1`:: + + >>> a, n = 4, 7 + >>> limit(lambda q: qp(q**a,q,n) / (1-q)**n, 1) + 604800.0 + >>> rf(a,n) + 604800.0 + + The Taylor series of the reciprocal Euler function gives + the partition function `P(n)`, i.e. the number of ways of writing + `n` as a sum of positive integers:: + + >>> taylor(lambda q: 1/qp(q), 0, 10) + [1.0, 1.0, 2.0, 3.0, 5.0, 7.0, 11.0, 15.0, 22.0, 30.0, 42.0] + + Special values include:: + + >>> qp(0) + 1.0 + >>> findroot(diffun(qp), -0.4) # location of maximum + -0.4112484791779547734440257 + >>> qp(_) + 1.228348867038575112586878 + + The q-Pochhammer symbol is related to the Jacobi theta functions. + For example, the following identity holds:: + + >>> q = mpf(0.5) # arbitrary + >>> qp(q) + 0.2887880950866024212788997 + >>> root(3,-2)*root(q,-24)*jtheta(2,pi/6,root(q,6)) + 0.2887880950866024212788997 + + """ + a = ctx.convert(a) + if n is None: + n = ctx.inf + else: + n = ctx.convert(n) + if n < 0: + raise ValueError("n cannot be negative") + if q is None: + q = a + else: + q = ctx.convert(q) + if n == 0: + return ctx.one + 0*(a+q) + infinite = (n == ctx.inf) + same = (a == q) + if infinite: + if abs(q) >= 1: + if same and (q == -1 or q == 1): + return ctx.zero * q + raise ValueError("q-function only defined for |q| < 1") + elif q == 0: + return ctx.one - a + maxterms = kwargs.get('maxterms', 50*ctx.prec) + if infinite and same: + # Euler's pentagonal theorem + def terms(): + t = 1 + yield t + k = 1 + x1 = q + x2 = q**2 + while 1: + yield (-1)**k * x1 + yield (-1)**k * x2 + x1 *= q**(3*k+1) + x2 *= q**(3*k+2) + k += 1 + if k > maxterms: + raise ctx.NoConvergence + return ctx.sum_accurately(terms) + # return ctx.nprod(lambda k: 1-a*q**k, [0,n-1]) + def factors(): + k = 0 + r = ctx.one + while 1: + yield 1 - a*r + r *= q + k += 1 + if k >= n: + return + if k > maxterms: + raise ctx.NoConvergence + return ctx.mul_accurately(factors) + +@defun_wrapped +def qgamma(ctx, z, q, **kwargs): + r""" + Evaluates the q-gamma function + + .. math :: + + \Gamma_q(z) = \frac{(q; q)_{\infty}}{(q^z; q)_{\infty}} (1-q)^{1-z}. + + + **Examples** + + Evaluation for real and complex arguments:: + + >>> from mpmath import mp, qgamma, mpf + >>> mp.dps = 25 + >>> mp.pretty = True + >>> qgamma(4,0.75) + 4.046875 + >>> qgamma(6,6) + 121226245.0 + >>> qgamma(3+4j, 0.5j) + (0.1663082382255199834630088 + 0.01952474576025952984418217j) + + The q-gamma function satisfies a functional equation similar + to that of the ordinary gamma function:: + + >>> q = mpf(0.25) + >>> z = mpf(2.5) + >>> qgamma(z+1,q) + 1.428277424823760954685912 + >>> (1-q**z)/(1-q)*qgamma(z,q) + 1.428277424823760954685912 + + """ + if abs(q) > 1: + return ctx.qgamma(z,1/q)*q**((z-2)*(z-1)*0.5) + return ctx.qp(q, q, None, **kwargs) / \ + ctx.qp(q**z, q, None, **kwargs) * (1-q)**(1-z) + +@defun_wrapped +def qfac(ctx, z, q, **kwargs): + r""" + Evaluates the q-factorial, + + .. math :: + + [n]_q! = (1+q)(1+q+q^2)\cdots(1+q+\cdots+q^{n-1}) + + or more generally + + .. math :: + + [z]_q! = \frac{(q;q)_z}{(1-q)^z}. + + **Examples** + + >>> from mpmath import mp, qfac + >>> mp.dps = 25 + >>> mp.pretty = True + >>> qfac(0,0) + 1.0 + >>> qfac(4,3) + 2080.0 + >>> qfac(5,6) + 121226245.0 + >>> qfac(1+1j, 2+1j) + (0.4370556551322672478613695 + 0.2609739839216039203708921j) + + """ + if ctx.isint(z) and ctx._re(z) > 0: + n = int(ctx._re(z)) + return ctx.qp(q, q, n, **kwargs) / (1-q)**n + return ctx.qgamma(z+1, q, **kwargs) + +@defun +def qhyper(ctx, a_s, b_s, q, z, **kwargs): + r""" + Evaluates the basic hypergeometric series or hypergeometric q-series + + .. math :: + + \,_r\phi_s \left[\begin{matrix} + a_1 & a_2 & \ldots & a_r \\ + b_1 & b_2 & \ldots & b_s + \end{matrix} ; q,z \right] = + \sum_{n=0}^\infty + \frac{(a_1;q)_n, \ldots, (a_r;q)_n} + {(b_1;q)_n, \ldots, (b_s;q)_n} + \left((-1)^n q^{n\choose 2}\right)^{1+s-r} + \frac{z^n}{(q;q)_n} + + where `(a;q)_n` denotes the q-Pochhammer symbol (see :func:`~mpmath.qp`). + + **Examples** + + Evaluation works for real and complex arguments:: + + >>> from mpmath import qhyper, mp, nsum, qp, inf, j + >>> mp.dps = 25 + >>> mp.pretty = True + >>> qhyper([0.5], [2.25], 0.25, 4) + -0.1975849091263356009534385 + >>> qhyper([0.5], [2.25], 0.25-0.25j, 4) + (2.806330244925716649839237 + 3.568997623337943121769938j) + >>> qhyper([1+j], [2,3+0.5j], 0.25, 3+4j) + (9.112885171773400017270226 - 1.272756997166375050700388j) + + Comparing with a summation of the defining series, using + :func:`~mpmath.nsum`:: + + >>> b, q, z = 3, 0.25, 0.5 + >>> qhyper([], [b], q, z) + 0.6221136748254495583228324 + >>> nsum(lambda n: z**n / qp(q,q,n)/qp(b,q,n) * q**(n*(n-1)), [0,inf]) + 0.6221136748254495583228324 + + """ + #a_s = [ctx._convert_param(a)[0] for a in a_s] + #b_s = [ctx._convert_param(b)[0] for b in b_s] + #q = ctx._convert_param(q)[0] + a_s = [ctx.convert(a) for a in a_s] + b_s = [ctx.convert(b) for b in b_s] + q = ctx.convert(q) + z = ctx.convert(z) + r = len(a_s) + s = len(b_s) + d = 1+s-r + maxterms = kwargs.get('maxterms', 50*ctx.prec) + def terms(): + t = ctx.one + yield t + qk = 1 + k = 0 + x = 1 + while 1: + for a in a_s: + p = 1 - a*qk + t *= p + for b in b_s: + p = 1 - b*qk + if not p: + raise ValueError + t /= p + t *= z + x *= (-1)**d * qk ** d + qk *= q + t /= (1 - qk) + k += 1 + yield t * x + if k > maxterms: + raise ctx.NoConvergence + return ctx.sum_accurately(terms) diff --git a/mpmath/functions/rszeta.py b/mpmath/functions/rszeta.py new file mode 100644 index 0000000..7752e86 --- /dev/null +++ b/mpmath/functions/rszeta.py @@ -0,0 +1,1403 @@ +""" +--------------------------------------------------------------------- +.. sectionauthor:: Juan Arias de Reyna + +This module implements zeta-related functions using the Riemann-Siegel +expansion: zeta_offline(s,k=0) + +* coef(J, eps): Need in the computation of Rzeta(s,k) + +* Rzeta_simul(s, der=0) computes Rzeta^(k)(s) and Rzeta^(k)(1-s) simultaneously + for 0 <= k <= der. Used by zeta_offline and z_offline + +* Rzeta_set(s, derivatives) computes Rzeta^(k)(s) for given derivatives, used by + z_half(t,k) and zeta_half + +* z_offline(w,k): Z(w) and its derivatives of order k <= 4 +* z_half(t,k): Z(t) (Riemann Siegel function) and its derivatives of order k <= 4 +* zeta_offline(s): zeta(s) and its derivatives of order k<= 4 +* zeta_half(1/2+it,k): zeta(s) and its derivatives of order k<= 4 + +* rs_zeta(s,k=0) Computes zeta^(k)(s) Unifies zeta_half and zeta_offline +* rs_z(w,k=0) Computes Z^(k)(w) Unifies z_offline and z_half +---------------------------------------------------------------------- + +This program uses Riemann-Siegel expansion even to compute +zeta(s) on points s = sigma + i t with sigma arbitrary not +necessarily equal to 1/2. + +It is founded on a new deduction of the formula, with rigorous +and sharp bounds for the terms and rest of this expansion. + +More information on the papers: + + J. Arias de Reyna, High Precision Computation of Riemann's + Zeta Function by the Riemann-Siegel Formula I, II + + We refer to them as I, II. + + In them we shall find detailed explanation of all the + procedure. + +The program uses Riemann-Siegel expansion. +This is useful when t is big, ( say t > 10000 ). +The precision is limited, roughly it can compute zeta(sigma+it) +with an error less than exp(-c t) for some constant c depending +on sigma. The program gives an error when the Riemann-Siegel +formula can not compute to the wanted precision. + +""" + +import math + +class RSCache: + def __init__(ctx): + ctx._rs_cache = [0, 10, {}, {}] + +from .functions import defun + +#-------------------------------------------------------------------------------# +# # +# coef(ctx, J, eps, _cache=[0, 10, {} ] ) # +# # +#-------------------------------------------------------------------------------# + +# This function computes the coefficients c[n] defined on (I, equation (47)) +# but see also (II, section 3.14). +# +# Since these coefficients are very difficult to compute we save the values +# in a cache. So if we compute several values of the functions Rzeta(s) for +# near values of s, we do not recompute these coefficients. +# +# c[n] are the Taylor coefficients of the function: +# +# F(z):= (exp(pi*j*(z*z/2+3/8))-j* sqrt(2) cos(pi*z/2))/(2*cos(pi *z)) +# +# + +def _coef(ctx, J, eps): + r""" + Computes the coefficients `c_n` for `0\le n\le 2J` with error less than eps + + **Definition** + + The coefficients c_n are defined by + + .. math :: + + \begin{equation} + F(z)=\frac{e^{\pi i + \bigl(\frac{z^2}{2}+\frac38\bigr)}-i\sqrt{2}\cos\frac{\pi}{2}z}{2\cos\pi + z}=\sum_{n=0}^\infty c_{2n} z^{2n} + \end{equation} + + they are computed applying the relation + + .. math :: + + \begin{multline} + c_{2n}=-\frac{i}{\sqrt{2}}\Bigl(\frac{\pi}{2}\Bigr)^{2n} + \sum_{k=0}^n\frac{(-1)^k}{(2k)!} + 2^{2n-2k}\frac{(-1)^{n-k}E_{2n-2k}}{(2n-2k)!}+\\ + +e^{3\pi i/8}\sum_{j=0}^n(-1)^j\frac{ + E_{2j}}{(2j)!}\frac{i^{n-j}\pi^{n+j}}{(n-j)!2^{n-j+1}}. + \end{multline} + """ + + newJ = J+2 # compute more coefficients that are needed + neweps6 = eps/2. # compute with a slight more precision that are needed + + # PREPARATION FOR THE COMPUTATION OF V(N) AND W(N) + # See II Section 3.16 + # + # Computing the exponent wpvw of the error II equation (81) + wpvw = max(ctx.mag(10*(newJ+3)), 4*newJ+5-ctx.mag(neweps6)) + + # Preparation of Euler numbers (we need until the 2*RS_NEWJ) + E = ctx._eulernum(2*newJ) + + # Now we have in the cache all the needed Euler numbers. + # + # Computing the powers of pi + # + # We need to compute the powers pi**n for 1<= n <= 2*J + # with relative error less than 2**(-wpvw) + # it is easy to show that this is obtained + # taking wppi as the least d with + # 2**d>40*J and 2**d> 4.24 *newJ + 2**wpvw + # In II Section 3.9 we need also that + # wppi > wptcoef[0], and that the powers + # here computed 0<= k <= 2*newJ are more + # than those needed there that are 2*L-2. + # so we need J >= L this will be checked + # before computing tcoef[] + wppi = max(ctx.mag(40*newJ), ctx.mag(newJ)+3 +wpvw) + ctx.prec = wppi + pipower = {} + pipower[0] = ctx.one + pipower[1] = ctx.pi + for n in range(2,2*newJ+1): + pipower[n] = pipower[n-1]*ctx.pi + + # COMPUTING THE COEFFICIENTS v(n) AND w(n) + # see II equation (61) and equations (81) and (82) + ctx.prec = wpvw+2 + v={} + w={} + for n in range(0,newJ+1): + va = (-1)**n * ctx._eulernum(2*n) + va = ctx.mpf(va)/ctx.fac(2*n) + v[n]=va*pipower[2*n] + for n in range(0,2*newJ+1): + wa = ctx.one/ctx.fac(n) + wa=wa/(2**n) + w[n]=wa*pipower[n] + + # COMPUTATION OF THE CONVOLUTIONS RS_P1 AND RS_P2 + # See II Section 3.16 + ctx.prec = 15 + wpp1a = 9 - ctx.mag(neweps6) + P1 = {} + for n in range(0,newJ+1): + ctx.prec = 15 + wpp1 = max(ctx.mag(10*(n+4)),4*n+wpp1a) + ctx.prec = wpp1 + sump = 0 + for k in range(0,n+1): + sump += ((-1)**k) * v[k]*w[2*n-2*k] + P1[n]=((-1)**(n+1))*ctx.j*sump + P2={} + for n in range(0,newJ+1): + ctx.prec = 15 + wpp2 = max(ctx.mag(10*(n+4)),4*n+wpp1a) + ctx.prec = wpp2 + sump = 0 + for k in range(0,n+1): + sump += (ctx.j**(n-k)) * v[k]*w[n-k] + P2[n]=sump + # COMPUTING THE COEFFICIENTS c[2n] + # See II Section 3.14 + ctx.prec = 15 + wpc0 = 5 - ctx.mag(neweps6) + wpc = max(6,4*newJ+wpc0) + ctx.prec = wpc + mu = ctx.sqrt(ctx.mpf('2'))/2 + nu = ctx.expjpi(3./8)/2 + c={} + for n in range(0,newJ): + ctx.prec = 15 + wpc = max(6,4*n+wpc0) + ctx.prec = wpc + c[2*n] = mu*P1[n]+nu*P2[n] + for n in range(1,2*newJ,2): + c[n] = 0 + return [newJ, neweps6, c, pipower] + +def coef(ctx, J, eps): + _cache = ctx._rs_cache + if J <= _cache[0] and eps >= _cache[1]: + return _cache[2], _cache[3] + orig = ctx._mp.prec + try: + data = _coef(ctx._mp, J, eps) + finally: + ctx._mp.prec = orig + if ctx is not ctx._mp: + data[2] = dict((k,ctx.convert(v)) for (k,v) in data[2].items()) + data[3] = dict((k,ctx.convert(v)) for (k,v) in data[3].items()) + ctx._rs_cache[:] = data + return ctx._rs_cache[2], ctx._rs_cache[3] + +#-------------------------------------------------------------------------------# +# # +# Rzeta_simul(s,k=0) # +# # +#-------------------------------------------------------------------------------# +# This function return a list with the values: +# Rzeta(sigma+it), conj(Rzeta(1-sigma+it)),Rzeta'(sigma+it), conj(Rzeta'(1-sigma+it)), +# .... , Rzeta^{(k)}(sigma+it), conj(Rzeta^{(k)}(1-sigma+it)) +# +# Useful to compute the function zeta(s) and Z(w) or its derivatives. +# + +def aux_M_Fp(ctx, xA, xeps4, a, xB1, xL): + # COMPUTING M NUMBER OF DERIVATIVES Fp[m] TO COMPUTE + # See II Section 3.11 equations (47) and (48) + aux1 = 126.0657606*xA/xeps4 # 126.06.. = 316/sqrt(2*pi) + aux1 = ctx.ln(aux1) + aux2 = (2*ctx.ln(ctx.pi)+ctx.ln(xB1)+ctx.ln(a))/3 -ctx.ln(2*ctx.pi)/2 + m = 3*xL-3 + aux3= (ctx.loggamma(m+1)-ctx.loggamma(m/3.0+2))/2 -ctx.loggamma((m+1)/2.) + while((aux1 < m*aux2+ aux3)and (m>1)): + m = m - 1 + aux3 = (ctx.loggamma(m+1)-ctx.loggamma(m/3.0+2))/2 -ctx.loggamma((m+1)/2.) + xM = m + return xM + +def aux_J_needed(ctx, xA, xeps4, a, xB1, xM): + # DETERMINATION OF J THE NUMBER OF TERMS NEEDED + # IN THE TAYLOR SERIES OF F. + # See II Section 3.11 equation (49)) + # Only determine one + h1 = xeps4/(632*xA) + h2 = xB1*a * 126.31337419529260248 # = pi^2*e^2*sqrt(3) + h2 = h1 * ctx.power((h2/xM**2),(xM-1)/3) / xM + h3 = min(h1,h2) + return h3 + +def Rzeta_simul(ctx, s, der=0): + # First we take the value of ctx.prec + wpinitial = ctx.prec + + # INITIALIZATION + # Take the real and imaginary part of s + t = ctx._im(s) + xsigma = ctx._re(s) + ysigma = 1 - xsigma + + # Now compute several parameter that appear on the program + ctx.prec = 15 + a = ctx.sqrt(t/(2*ctx.pi)) + xasigma = a ** xsigma + yasigma = a ** ysigma + + # We need a simple bound A1 < asigma (see II Section 3.1 and 3.3) + xA1=ctx.power(2, ctx.mag(xasigma)-1) + yA1=ctx.power(2, ctx.mag(yasigma)-1) + + # We compute various epsilon's (see II end of Section 3.1) + eps = ctx.power(2, -wpinitial) + eps1 = eps/6. + xeps2 = eps * xA1/3. + yeps2 = eps * yA1/3. + + # COMPUTING SOME COEFFICIENTS THAT DEPENDS + # ON sigma + # constant b and c (see I Theorem 2 formula (26) ) + # coefficients A and B1 (see I Section 6.1 equation (50)) + # + # here we not need high precision + ctx.prec = 15 + if xsigma > 0: + xb = 2. + xc = math.pow(9,xsigma)/4.44288 + # 4.44288 =(math.sqrt(2)*math.pi) + xA = math.pow(9,xsigma) + xB1 = 1 + else: + xb = 2.25158 # math.sqrt( (3-2* math.log(2))*math.pi ) + xc = math.pow(2,-xsigma)/4.44288 + xA = math.pow(2,-xsigma) + xB1 = 1.10789 # = 2*sqrt(1-log(2)) + + if(ysigma > 0): + yb = 2. + yc = math.pow(9,ysigma)/4.44288 + # 4.44288 =(math.sqrt(2)*math.pi) + yA = math.pow(9,ysigma) + yB1 = 1 + else: + yb = 2.25158 # math.sqrt( (3-2* math.log(2))*math.pi ) + yc = math.pow(2,-ysigma)/4.44288 + yA = math.pow(2,-ysigma) + yB1 = 1.10789 # = 2*sqrt(1-log(2)) + + # COMPUTING L THE NUMBER OF TERMS NEEDED IN THE RIEMANN-SIEGEL + # CORRECTION + # See II Section 3.2 + ctx.prec = 15 + xL = 1 + while 3*xc*ctx.gamma(xL*0.5) * ctx.power(xb*a,-xL) >= xeps2: + xL = xL+1 + xL = max(2,xL) + yL = 1 + while 3*yc*ctx.gamma(yL*0.5) * ctx.power(yb*a,-yL) >= yeps2: + yL = yL+1 + yL = max(2,yL) + + # The number L has to satify some conditions. + # If not RS can not compute Rzeta(s) with the prescribed precision + # (see II, Section 3.2 condition (20) ) and + # (II, Section 3.3 condition (22) ). Also we have added + # an additional technical condition in Section 3.17 Proposition 17 + if ((3*xL >= 2*a*a/25.) or (3*xL+2+xsigma<0) or (abs(xsigma) > a/2.) or \ + (3*yL >= 2*a*a/25.) or (3*yL+2+ysigma<0) or (abs(ysigma) > a/2.)): + ctx.prec = wpinitial + raise NotImplementedError("Riemann-Siegel can not compute with such precision") + + # We take the maximum of the two values + L = max(xL, yL) + + # INITIALIZATION (CONTINUATION) + # + # eps3 is the constant defined on (II, Section 3.5 equation (27) ) + # each term of the RS correction must be computed with error <= eps3 + xeps3 = xeps2/(4*xL) + yeps3 = yeps2/(4*yL) + + # eps4 is defined on (II Section 3.6 equation (30) ) + # each component of the formula (II Section 3.6 equation (29) ) + # must be computed with error <= eps4 + xeps4 = xeps3/(3*xL) + yeps4 = yeps3/(3*yL) + + # COMPUTING M NUMBER OF DERIVATIVES Fp[m] TO COMPUTE + xM = aux_M_Fp(ctx, xA, xeps4, a, xB1, xL) + yM = aux_M_Fp(ctx, yA, yeps4, a, yB1, yL) + M = max(xM, yM) + + # COMPUTING NUMBER OF TERMS J NEEDED + h3 = aux_J_needed(ctx, xA, xeps4, a, xB1, xM) + h4 = aux_J_needed(ctx, yA, yeps4, a, yB1, yM) + h3 = min(h3,h4) + J = 12 + jvalue = (2*ctx.pi)**J / ctx.gamma(J+1) + while jvalue > h3: + J = J+1 + jvalue = (2*ctx.pi)*jvalue/J + + # COMPUTING eps5[m] for 1 <= m <= 21 + # See II Section 10 equation (43) + # We choose the minimum of the two possibilities + eps5={} + xforeps5 = math.pi*math.pi*xB1*a + yforeps5 = math.pi*math.pi*yB1*a + for m in range(0,22): + xaux1 = math.pow(xforeps5, m/3)/(316.*xA) + yaux1 = math.pow(yforeps5, m/3)/(316.*yA) + aux1 = min(xaux1, yaux1) + aux2 = ctx.gamma(m+1)/ctx.gamma(m/3.0+0.5) + aux2 = math.sqrt(aux2) + eps5[m] = (aux1*aux2*min(xeps4,yeps4)) + + # COMPUTING wpfp + # See II Section 3.13 equation (59) + twenty = min(3*L-3, 21)+1 + aux = 6812*J + wpfp = ctx.mag(44*J) + for m in range(0,twenty): + wpfp = max(wpfp, ctx.mag(aux*ctx.gamma(m+1)/eps5[m])) + + # COMPUTING N AND p + # See II Section + ctx.prec = wpfp + ctx.mag(t)+20 + a = ctx.sqrt(t/(2*ctx.pi)) + N = ctx.floor(a) + p = 1-2*(a-N) + + # now we get a rounded version of p + # to the precision wpfp + # this possibly is not necessary + num=ctx.floor(p*(ctx.mpf('2')**wpfp)) + difference = p * (ctx.mpf('2')**wpfp)-num + if (difference < 0.5): + num = num + else: + num = num+1 + p = ctx.convert(num * (ctx.mpf('2')**(-wpfp))) + + # COMPUTING THE COEFFICIENTS c[n] = cc[n] + # We shall use the notation cc[n], since there is + # a constant that is called c + # See II Section 3.14 + # We compute the coefficients and also save then in a + # cache. The bulk of the computation is passed to + # the function coef() + # + # eps6 is defined in II Section 3.13 equation (58) + eps6 = ctx.power(ctx.convert(2*ctx.pi), J)/(ctx.gamma(J+1)*3*J) + + # Now we compute the coefficients + cc = {} + cont = {} + cont, pipowers = coef(ctx, J, eps6) + cc=cont.copy() # we need a copy since we have to change his values. + Fp={} # this is the adequate locus of this + for n in range(M, 3*L-2): + Fp[n] = 0 + Fp={} + ctx.prec = wpfp + for m in range(0,M+1): + sumP = 0 + for k in range(2*J-m-1,-1,-1): + sumP = (sumP * p)+ cc[k] + Fp[m] = sumP + # preparation of the new coefficients + for k in range(0,2*J-m-1): + cc[k] = (k+1)* cc[k+1] + + # COMPUTING THE NUMBERS xd[u,n,k], yd[u,n,k] + # See II Section 3.17 + # + # First we compute the working precisions xwpd[k] + # Se II equation (92) + xwpd={} + d1 = max(6,ctx.mag(40*L*L)) + xd2 = 13+ctx.mag((1+abs(xsigma))*xA)-ctx.mag(xeps4)-1 + xconst = ctx.ln(8/(ctx.pi*ctx.pi*a*a*xB1*xB1)) /2 + for n in range(0,L): + xd3 = ctx.mag(ctx.sqrt(ctx.gamma(n-0.5)))-ctx.floor(n*xconst)+xd2 + xwpd[n]=max(xd3,d1) + + # procedure of II Section 3.17 + ctx.prec = xwpd[1]+10 + xpsigma = 1-(2*xsigma) + xd = {} + xd[0,0,-2]=0; xd[0,0,-1]=0; xd[0,0,0]=1; xd[0,0,1]=0 + xd[0,-1,-2]=0; xd[0,-1,-1]=0; xd[0,-1,0]=1; xd[0,-1,1]=0 + for n in range(1,L): + ctx.prec = xwpd[n]+10 + for k in range(0,3*n//2+1): + m = 3*n-2*k + if(m!=0): + m1 = ctx.one/m + c1= m1/4 + c2=(xpsigma*m1)/2 + c3=-(m+1) + xd[0,n,k]=c3*xd[0,n-1,k-2]+c1*xd[0,n-1,k]+c2*xd[0,n-1,k-1] + else: + xd[0,n,k]=0 + for r in range(0,k): + add=xd[0,n,r]*(ctx.mpf('1.0')*ctx.fac(2*k-2*r)/ctx.fac(k-r)) + xd[0,n,k] -= ((-1)**(k-r))*add + xd[0,n,-2]=0; xd[0,n,-1]=0; xd[0,n,3*n//2+1]=0 + for mu in range(-2,der+1): + for n in range(-2,L): + for k in range(-3,max(1,3*n//2+2)): + if( (mu<0)or (n<0) or(k<0)or (k>3*n//2)): + xd[mu,n,k] = 0 + for mu in range(1,der+1): + for n in range(0,L): + ctx.prec = xwpd[n]+10 + for k in range(0,3*n//2+1): + aux=(2*mu-2)*xd[mu-2,n-2,k-3]+2*(xsigma+n-2)*xd[mu-1,n-2,k-3] + xd[mu,n,k] = aux - xd[mu-1,n-1,k-1] + + # Now we compute the working precisions ywpd[k] + # Se II equation (92) + ywpd={} + d1 = max(6,ctx.mag(40*L*L)) + yd2 = 13+ctx.mag((1+abs(ysigma))*yA)-ctx.mag(yeps4)-1 + yconst = ctx.ln(8/(ctx.pi*ctx.pi*a*a*yB1*yB1)) /2 + for n in range(0,L): + yd3 = ctx.mag(ctx.sqrt(ctx.gamma(n-0.5)))-ctx.floor(n*yconst)+yd2 + ywpd[n]=max(yd3,d1) + + # procedure of II Section 3.17 + ctx.prec = ywpd[1]+10 + ypsigma = 1-(2*ysigma) + yd = {} + yd[0,0,-2]=0; yd[0,0,-1]=0; yd[0,0,0]=1; yd[0,0,1]=0 + yd[0,-1,-2]=0; yd[0,-1,-1]=0; yd[0,-1,0]=1; yd[0,-1,1]=0 + for n in range(1,L): + ctx.prec = ywpd[n]+10 + for k in range(0,3*n//2+1): + m = 3*n-2*k + if(m!=0): + m1 = ctx.one/m + c1= m1/4 + c2=(ypsigma*m1)/2 + c3=-(m+1) + yd[0,n,k]=c3*yd[0,n-1,k-2]+c1*yd[0,n-1,k]+c2*yd[0,n-1,k-1] + else: + yd[0,n,k]=0 + for r in range(0,k): + add=yd[0,n,r]*(ctx.mpf('1.0')*ctx.fac(2*k-2*r)/ctx.fac(k-r)) + yd[0,n,k] -= ((-1)**(k-r))*add + yd[0,n,-2]=0; yd[0,n,-1]=0; yd[0,n,3*n//2+1]=0 + + for mu in range(-2,der+1): + for n in range(-2,L): + for k in range(-3,max(1,3*n//2+2)): + if( (mu<0)or (n<0) or(k<0)or (k>3*n//2)): + yd[mu,n,k] = 0 + for mu in range(1,der+1): + for n in range(0,L): + ctx.prec = ywpd[n]+10 + for k in range(0,3*n//2+1): + aux=(2*mu-2)*yd[mu-2,n-2,k-3]+2*(ysigma+n-2)*yd[mu-1,n-2,k-3] + yd[mu,n,k] = aux - yd[mu-1,n-1,k-1] + + # COMPUTING THE COEFFICIENTS xtcoef[k,l] + # See II Section 3.9 + # + # computing the needed wp + xwptcoef={} + xwpterm={} + ctx.prec = 15 + c1 = ctx.mag(40*(L+2)) + xc2 = ctx.mag(68*(L+2)*xA) + xc4 = ctx.mag(xB1*a*math.sqrt(ctx.pi))-1 + for k in range(0,L): + xc3 = xc2 - k*xc4+ctx.mag(ctx.fac(k+0.5))/2. + xwptcoef[k] = (max(c1,xc3-ctx.mag(xeps4)+1)+1 +20)*1.5 + xwpterm[k] = (max(c1,ctx.mag(L+2)+xc3-ctx.mag(xeps3)+1)+1 +20) + ywptcoef={} + ywpterm={} + ctx.prec = 15 + c1 = ctx.mag(40*(L+2)) + yc2 = ctx.mag(68*(L+2)*yA) + yc4 = ctx.mag(yB1*a*math.sqrt(ctx.pi))-1 + for k in range(0,L): + yc3 = yc2 - k*yc4+ctx.mag(ctx.fac(k+0.5))/2. + ywptcoef[k] = ((max(c1,yc3-ctx.mag(yeps4)+1))+10)*1.5 + ywpterm[k] = (max(c1,ctx.mag(L+2)+yc3-ctx.mag(yeps3)+1)+1)+10 + + # check of power of pi + # computing the fortcoef[mu,k,ell] + xfortcoef={} + for mu in range(0,der+1): + for k in range(0,L): + for ell in range(-2,3*k//2+1): + xfortcoef[mu,k,ell]=0 + for mu in range(0,der+1): + for k in range(0,L): + ctx.prec = xwptcoef[k] + for ell in range(0,3*k//2+1): + xfortcoef[mu,k,ell]=xd[mu,k,ell]*Fp[3*k-2*ell]/pipowers[2*k-ell] + xfortcoef[mu,k,ell]=xfortcoef[mu,k,ell]/((2*ctx.j)**ell) + + def trunc_a(t): + wp = ctx.prec + ctx.prec = wp + 2 + aa = ctx.sqrt(t/(2*ctx.pi)) + ctx.prec = wp + return aa + + # computing the tcoef[k,ell] + xtcoef={} + for mu in range(0,der+1): + for k in range(0,L): + for ell in range(-2,3*k//2+1): + xtcoef[mu,k,ell]=0 + ctx.prec = max(xwptcoef[0],ywptcoef[0])+3 + aa= trunc_a(t) + la = -ctx.ln(aa) + + for chi in range(0,der+1): + for k in range(0,L): + ctx.prec = xwptcoef[k] + for ell in range(0,3*k//2+1): + xtcoef[chi,k,ell] =0 + for mu in range(0, chi+1): + tcoefter=ctx.binomial(chi,mu)*ctx.power(la,mu)*xfortcoef[chi-mu,k,ell] + xtcoef[chi,k,ell] += tcoefter + + # COMPUTING THE COEFFICIENTS ytcoef[k,l] + # See II Section 3.9 + # + # computing the needed wp + # check of power of pi + # computing the fortcoef[mu,k,ell] + yfortcoef={} + for mu in range(0,der+1): + for k in range(0,L): + for ell in range(-2,3*k//2+1): + yfortcoef[mu,k,ell]=0 + for mu in range(0,der+1): + for k in range(0,L): + ctx.prec = ywptcoef[k] + for ell in range(0,3*k//2+1): + yfortcoef[mu,k,ell]=yd[mu,k,ell]*Fp[3*k-2*ell]/pipowers[2*k-ell] + yfortcoef[mu,k,ell]=yfortcoef[mu,k,ell]/((2*ctx.j)**ell) + # computing the tcoef[k,ell] + ytcoef={} + for chi in range(0,der+1): + for k in range(0,L): + for ell in range(-2,3*k//2+1): + ytcoef[chi,k,ell]=0 + for chi in range(0,der+1): + for k in range(0,L): + ctx.prec = ywptcoef[k] + for ell in range(0,3*k//2+1): + ytcoef[chi,k,ell] =0 + for mu in range(0, chi+1): + tcoefter=ctx.binomial(chi,mu)*ctx.power(la,mu)*yfortcoef[chi-mu,k,ell] + ytcoef[chi,k,ell] += tcoefter + + # COMPUTING tv[k,ell] + # See II Section 3.8 + # + # a has a good value + ctx.prec = max(xwptcoef[0], ywptcoef[0])+2 + av = {} + av[0] = 1 + av[1] = av[0]/a + + ctx.prec = max(xwptcoef[0],ywptcoef[0]) + for k in range(2,L): + av[k] = av[k-1] * av[1] + + # Computing the quotients + xtv = {} + for chi in range(0,der+1): + for k in range(0,L): + ctx.prec = xwptcoef[k] + for ell in range(0,3*k//2+1): + xtv[chi,k,ell] = xtcoef[chi,k,ell]* av[k] + # Computing the quotients + ytv = {} + for chi in range(0,der+1): + for k in range(0,L): + ctx.prec = ywptcoef[k] + for ell in range(0,3*k//2+1): + ytv[chi,k,ell] = ytcoef[chi,k,ell]* av[k] + + # COMPUTING THE TERMS xterm[k] + # See II Section 3.6 + xterm = {} + for chi in range(0,der+1): + for n in range(0,L): + ctx.prec = xwpterm[n] + te = 0 + for k in range(0, 3*n//2+1): + te += xtv[chi,n,k] + xterm[chi,n] = te + + # COMPUTING THE TERMS yterm[k] + # See II Section 3.6 + yterm = {} + for chi in range(0,der+1): + for n in range(0,L): + ctx.prec = ywpterm[n] + te = 0 + for k in range(0, 3*n//2+1): + te += ytv[chi,n,k] + yterm[chi,n] = te + + # COMPUTING rssum + # See II Section 3.5 + xrssum={} + ctx.prec=15 + xrsbound = math.sqrt(ctx.pi) * xc /(xb*a) + ctx.prec=15 + xwprssum = ctx.mag(4.4*((L+3)**2)*xrsbound / xeps2) + xwprssum = max(xwprssum, ctx.mag(10*(L+1))) + ctx.prec = xwprssum + for chi in range(0,der+1): + xrssum[chi] = 0 + for k in range(1,L+1): + xrssum[chi] += xterm[chi,L-k] + yrssum={} + ctx.prec=15 + yrsbound = math.sqrt(ctx.pi) * yc /(yb*a) + ctx.prec=15 + ywprssum = ctx.mag(4.4*((L+3)**2)*yrsbound / yeps2) + ywprssum = max(ywprssum, ctx.mag(10*(L+1))) + ctx.prec = ywprssum + for chi in range(0,der+1): + yrssum[chi] = 0 + for k in range(1,L+1): + yrssum[chi] += yterm[chi,L-k] + + # COMPUTING S3 + # See II Section 3.19 + ctx.prec = 15 + A2 = 2**(max(ctx.mag(abs(xrssum[0])), ctx.mag(abs(yrssum[0])))) + eps8 = eps/(3*A2) + T = t *ctx.ln(t/(2*ctx.pi)) + xwps3 = 5 + ctx.mag((1+(2/eps8)*ctx.power(a,-xsigma))*T) + ywps3 = 5 + ctx.mag((1+(2/eps8)*ctx.power(a,-ysigma))*T) + + ctx.prec = max(xwps3, ywps3) + + tpi = t/(2*ctx.pi) + arg = (t/2)*ctx.ln(tpi)-(t/2)-ctx.pi/8 + U = ctx.expj(-arg) + a = trunc_a(t) + xasigma = ctx.power(a, -xsigma) + yasigma = ctx.power(a, -ysigma) + xS3 = ((-1)**(N-1)) * xasigma * U + yS3 = ((-1)**(N-1)) * yasigma * U + + # COMPUTING S1 the zetasum + # See II Section 3.18 + ctx.prec = 15 + xwpsum = 4+ ctx.mag((N+ctx.power(N,1-xsigma))*ctx.ln(N) /eps1) + ywpsum = 4+ ctx.mag((N+ctx.power(N,1-ysigma))*ctx.ln(N) /eps1) + wpsum = max(xwpsum, ywpsum) + + ctx.prec = wpsum +10 + ''' + # This can be improved + xS1={} + yS1={} + for chi in range(0,der+1): + xS1[chi] = 0 + yS1[chi] = 0 + for n in range(1,int(N)+1): + ln = ctx.ln(n) + xexpn = ctx.exp(-ln*(xsigma+ctx.j*t)) + yexpn = ctx.conj(1/(n*xexpn)) + for chi in range(0,der+1): + pown = ctx.power(-ln, chi) + xterm = pown*xexpn + yterm = pown*yexpn + xS1[chi] += xterm + yS1[chi] += yterm + ''' + xS1, yS1 = ctx._zetasum(s, 1, int(N)-1, range(0,der+1), True) + + # END OF COMPUTATION of xrz, yrz + # See II Section 3.1 + ctx.prec = 15 + xabsS1 = abs(xS1[der]) + xabsS2 = abs(xrssum[der] * xS3) + xwpend = max(6, wpinitial+ctx.mag(6*(3*xabsS1+7*xabsS2) ) ) + + ctx.prec = xwpend + xrz={} + for chi in range(0,der+1): + xrz[chi] = xS1[chi]+xrssum[chi]*xS3 + + ctx.prec = 15 + yabsS1 = abs(yS1[der]) + yabsS2 = abs(yrssum[der] * yS3) + ywpend = max(6, wpinitial+ctx.mag(6*(3*yabsS1+7*yabsS2) ) ) + + ctx.prec = ywpend + yrz={} + for chi in range(0,der+1): + yrz[chi] = yS1[chi]+yrssum[chi]*yS3 + yrz[chi] = ctx.conj(yrz[chi]) + ctx.prec = wpinitial + return xrz, yrz + +def Rzeta_set(ctx, s, derivatives=[0]): + r""" + Computes several derivatives of the auxiliary function of Riemann `R(s)`. + + **Definition** + + The function is defined by + + .. math :: + + \begin{equation} + {\mathop{\mathcal R }\nolimits}(s)= + \int_{0\swarrow1}\frac{x^{-s} e^{\pi i x^2}}{e^{\pi i x}- + e^{-\pi i x}}\,dx + \end{equation} + + To this function we apply the Riemann-Siegel expansion. + """ + der = max(derivatives) + # First we take the value of ctx.prec + # During the computation we will change ctx.prec, and finally we will + # restaurate the initial value + wpinitial = ctx.prec + # Take the real and imaginary part of s + t = ctx._im(s) + sigma = ctx._re(s) + # Now compute several parameter that appear on the program + ctx.prec = 15 + a = ctx.sqrt(t/(2*ctx.pi)) # Careful + asigma = ctx.power(a, sigma) # Careful + # We need a simple bound A1 < asigma (see II Section 3.1 and 3.3) + A1 = ctx.power(2, ctx.mag(asigma)-1) + # We compute various epsilon's (see II end of Section 3.1) + eps = ctx.power(2, -wpinitial) + eps1 = eps/6. + eps2 = eps * A1/3. + # COMPUTING SOME COEFFICIENTS THAT DEPENDS + # ON sigma + # constant b and c (see I Theorem 2 formula (26) ) + # coefficients A and B1 (see I Section 6.1 equation (50)) + # here we not need high precision + ctx.prec = 15 + if sigma > 0: + b = 2. + c = math.pow(9,sigma)/4.44288 + # 4.44288 =(math.sqrt(2)*math.pi) + A = math.pow(9,sigma) + B1 = 1 + else: + b = 2.25158 # math.sqrt( (3-2* math.log(2))*math.pi ) + c = math.pow(2,-sigma)/4.44288 + A = math.pow(2,-sigma) + B1 = 1.10789 # = 2*sqrt(1-log(2)) + # COMPUTING L THE NUMBER OF TERMS NEEDED IN THE RIEMANN-SIEGEL + # CORRECTION + # See II Section 3.2 + ctx.prec = 15 + L = 1 + while 3*c*ctx.gamma(L*0.5) * ctx.power(b*a,-L) >= eps2: + L = L+1 + L = max(2,L) + # The number L has to satify some conditions. + # If not RS can not compute Rzeta(s) with the prescribed precision + # (see II, Section 3.2 condition (20) ) and + # (II, Section 3.3 condition (22) ). Also we have added + # an additional technical condition in Section 3.17 Proposition 17 + if ((3*L >= 2*a*a/25.) or (3*L+2+sigma<0) or (abs(sigma)> a/2.)): + #print 'Error Riemann-Siegel can not compute with such precision' + ctx.prec = wpinitial + raise NotImplementedError("Riemann-Siegel can not compute with such precision") + + # INITIALIZATION (CONTINUATION) + # + # eps3 is the constant defined on (II, Section 3.5 equation (27) ) + # each term of the RS correction must be computed with error <= eps3 + eps3 = eps2/(4*L) + + # eps4 is defined on (II Section 3.6 equation (30) ) + # each component of the formula (II Section 3.6 equation (29) ) + # must be computed with error <= eps4 + eps4 = eps3/(3*L) + + # COMPUTING M. NUMBER OF DERIVATIVES Fp[m] TO COMPUTE + M = aux_M_Fp(ctx, A, eps4, a, B1, L) + Fp = {} + for n in range(M, 3*L-2): + Fp[n] = 0 + + # But I have not seen an instance of M != 3*L-3 + # + # DETERMINATION OF J THE NUMBER OF TERMS NEEDED + # IN THE TAYLOR SERIES OF F. + # See II Section 3.11 equation (49)) + h1 = eps4/(632*A) + h2 = ctx.pi*ctx.pi*B1*a *ctx.sqrt(3)*math.e*math.e + h2 = h1 * ctx.power((h2/M**2),(M-1)/3) / M + h3 = min(h1,h2) + J=12 + jvalue = (2*ctx.pi)**J / ctx.gamma(J+1) + while jvalue > h3: + J = J+1 + jvalue = (2*ctx.pi)*jvalue/J + + # COMPUTING eps5[m] for 1 <= m <= 21 + # See II Section 10 equation (43) + eps5={} + foreps5 = math.pi*math.pi*B1*a + for m in range(0,22): + aux1 = math.pow(foreps5, m/3)/(316.*A) + aux2 = ctx.gamma(m+1)/ctx.gamma(m/3.0+0.5) + aux2 = math.sqrt(aux2) + eps5[m] = aux1*aux2*eps4 + + # COMPUTING wpfp + # See II Section 3.13 equation (59) + twenty = min(3*L-3, 21)+1 + aux = 6812*J + wpfp = ctx.mag(44*J) + for m in range(0, twenty): + wpfp = max(wpfp, ctx.mag(aux*ctx.gamma(m+1)/eps5[m])) + # COMPUTING N AND p + # See II Section + ctx.prec = wpfp + ctx.mag(t) + 20 + a = ctx.sqrt(t/(2*ctx.pi)) + N = ctx.floor(a) + p = 1-2*(a-N) + + # now we get a rounded version of p to the precision wpfp + # this possibly is not necessary + num = ctx.floor(p*(ctx.mpf(2)**wpfp)) + difference = p * (ctx.mpf(2)**wpfp)-num + if difference < 0.5: + num = num + else: + num = num+1 + p = ctx.convert(num * (ctx.mpf(2)**(-wpfp))) + + # COMPUTING THE COEFFICIENTS c[n] = cc[n] + # We shall use the notation cc[n], since there is + # a constant that is called c + # See II Section 3.14 + # We compute the coefficients and also save then in a + # cache. The bulk of the computation is passed to + # the function coef() + # + # eps6 is defined in II Section 3.13 equation (58) + eps6 = ctx.power(2*ctx.pi, J)/(ctx.gamma(J+1)*3*J) + + # Now we compute the coefficients + cc={} + cont={} + cont, pipowers = coef(ctx, J, eps6) + cc = cont.copy() # we need a copy since we have + Fp={} + for n in range(M, 3*L-2): + Fp[n] = 0 + ctx.prec = wpfp + for m in range(0,M+1): + sumP = 0 + for k in range(2*J-m-1,-1,-1): + sumP = (sumP * p) + cc[k] + Fp[m] = sumP + # preparation of the new coefficients + for k in range(0, 2*J-m-1): + cc[k] = (k+1) * cc[k+1] + + # COMPUTING THE NUMBERS d[n,k] + # See II Section 3.17 + + # First we compute the working precisions wpd[k] + # Se II equation (92) + wpd = {} + d1 = max(6, ctx.mag(40*L*L)) + d2 = 13+ctx.mag((1+abs(sigma))*A)-ctx.mag(eps4)-1 + const = ctx.ln(8/(ctx.pi*ctx.pi*a*a*B1*B1)) /2 + for n in range(0,L): + d3 = ctx.mag(ctx.sqrt(ctx.gamma(n-0.5)))-ctx.floor(n*const)+d2 + wpd[n] = max(d3,d1) + + # procedure of II Section 3.17 + ctx.prec = wpd[1]+10 + psigma = 1-(2*sigma) + d = {} + d[0,0,-2]=0; d[0,0,-1]=0; d[0,0,0]=1; d[0,0,1]=0 + d[0,-1,-2]=0; d[0,-1,-1]=0; d[0,-1,0]=1; d[0,-1,1]=0 + for n in range(1,L): + ctx.prec = wpd[n]+10 + for k in range(0,3*n//2+1): + m = 3*n-2*k + if (m!=0): + m1 = ctx.one/m + c1 = m1/4 + c2 = (psigma*m1)/2 + c3 = -(m+1) + d[0,n,k] = c3*d[0,n-1,k-2]+c1*d[0,n-1,k]+c2*d[0,n-1,k-1] + else: + d[0,n,k]=0 + for r in range(0,k): + add = d[0,n,r]*(ctx.one*ctx.fac(2*k-2*r)/ctx.fac(k-r)) + d[0,n,k] -= ((-1)**(k-r))*add + d[0,n,-2]=0; d[0,n,-1]=0; d[0,n,3*n//2+1]=0 + + for mu in range(-2,der+1): + for n in range(-2,L): + for k in range(-3,max(1,3*n//2+2)): + if ((mu<0)or (n<0) or(k<0)or (k>3*n//2)): + d[mu,n,k] = 0 + + for mu in range(1,der+1): + for n in range(0,L): + ctx.prec = wpd[n]+10 + for k in range(0,3*n//2+1): + aux=(2*mu-2)*d[mu-2,n-2,k-3]+2*(sigma+n-2)*d[mu-1,n-2,k-3] + d[mu,n,k] = aux - d[mu-1,n-1,k-1] + + # COMPUTING THE COEFFICIENTS t[k,l] + # See II Section 3.9 + # + # computing the needed wp + wptcoef = {} + wpterm = {} + ctx.prec = 15 + c1 = ctx.mag(40*(L+2)) + c2 = ctx.mag(68*(L+2)*A) + c4 = ctx.mag(B1*a*math.sqrt(ctx.pi))-1 + for k in range(0,L): + c3 = c2 - k*c4+ctx.mag(ctx.fac(k+0.5))/2. + wptcoef[k] = max(c1,c3-ctx.mag(eps4)+1)+1 +10 + wpterm[k] = max(c1,ctx.mag(L+2)+c3-ctx.mag(eps3)+1)+1 +10 + + # check of power of pi + + # computing the fortcoef[mu,k,ell] + fortcoef={} + for mu in derivatives: + for k in range(0,L): + for ell in range(-2,3*k//2+1): + fortcoef[mu,k,ell]=0 + + for mu in derivatives: + for k in range(0,L): + ctx.prec = wptcoef[k] + for ell in range(0,3*k//2+1): + fortcoef[mu,k,ell]=d[mu,k,ell]*Fp[3*k-2*ell]/pipowers[2*k-ell] + fortcoef[mu,k,ell]=fortcoef[mu,k,ell]/((2*ctx.j)**ell) + + def trunc_a(t): + wp = ctx.prec + ctx.prec = wp + 2 + aa = ctx.sqrt(t/(2*ctx.pi)) + ctx.prec = wp + return aa + + # computing the tcoef[chi,k,ell] + tcoef={} + for chi in derivatives: + for k in range(0,L): + for ell in range(-2,3*k//2+1): + tcoef[chi,k,ell]=0 + ctx.prec = wptcoef[0]+3 + aa = trunc_a(t) + la = -ctx.ln(aa) + + for chi in derivatives: + for k in range(0,L): + ctx.prec = wptcoef[k] + for ell in range(0,3*k//2+1): + tcoef[chi,k,ell] = 0 + for mu in range(0, chi+1): + tcoefter = ctx.binomial(chi,mu) * la**mu * \ + fortcoef[chi-mu,k,ell] + tcoef[chi,k,ell] += tcoefter + + # COMPUTING tv[k,ell] + # See II Section 3.8 + + # Computing the powers av[k] = a**(-k) + ctx.prec = wptcoef[0] + 2 + + # a has a good value of a. + # See II Section 3.6 + av = {} + av[0] = 1 + av[1] = av[0]/a + + ctx.prec = wptcoef[0] + for k in range(2,L): + av[k] = av[k-1] * av[1] + + # Computing the quotients + tv = {} + for chi in derivatives: + for k in range(0,L): + ctx.prec = wptcoef[k] + for ell in range(0,3*k//2+1): + tv[chi,k,ell] = tcoef[chi,k,ell]* av[k] + + # COMPUTING THE TERMS term[k] + # See II Section 3.6 + term = {} + for chi in derivatives: + for n in range(0,L): + ctx.prec = wpterm[n] + te = 0 + for k in range(0, 3*n//2+1): + te += tv[chi,n,k] + term[chi,n] = te + + # COMPUTING rssum + # See II Section 3.5 + rssum={} + ctx.prec=15 + rsbound = math.sqrt(ctx.pi) * c /(b*a) + ctx.prec=15 + wprssum = ctx.mag(4.4*((L+3)**2)*rsbound / eps2) + wprssum = max(wprssum, ctx.mag(10*(L+1))) + ctx.prec = wprssum + for chi in derivatives: + rssum[chi] = 0 + for k in range(1,L+1): + rssum[chi] += term[chi,L-k] + + # COMPUTING S3 + # See II Section 3.19 + ctx.prec = 15 + A2 = 2**(ctx.mag(rssum[0])) + eps8 = eps/(3* A2) + T = t * ctx.ln(t/(2*ctx.pi)) + wps3 = 5 + ctx.mag((1+(2/eps8)*ctx.power(a,-sigma))*T) + + ctx.prec = wps3 + tpi = t/(2*ctx.pi) + arg = (t/2)*ctx.ln(tpi)-(t/2)-ctx.pi/8 + U = ctx.expj(-arg) + a = trunc_a(t) + asigma = ctx.power(a, -sigma) + S3 = ((-1)**(N-1)) * asigma * U + + # COMPUTING S1 the zetasum + # See II Section 3.18 + ctx.prec = 15 + wpsum = 4 + ctx.mag((N+ctx.power(N,1-sigma))*ctx.ln(N)/eps1) + + ctx.prec = wpsum + 10 + ''' + # This can be improved + S1 = {} + for chi in derivatives: + S1[chi] = 0 + for n in range(1,int(N)+1): + ln = ctx.ln(n) + expn = ctx.exp(-ln*(sigma+ctx.j*t)) + for chi in derivatives: + term = ctx.power(-ln, chi)*expn + S1[chi] += term + ''' + S1 = ctx._zetasum(s, 1, int(N)-1, derivatives)[0] + + # END OF COMPUTATION + # See II Section 3.1 + ctx.prec = 15 + absS1 = abs(S1[der]) + absS2 = abs(rssum[der] * S3) + wpend = max(6, wpinitial + ctx.mag(6*(3*absS1+7*absS2))) + ctx.prec = wpend + rz = {} + for chi in derivatives: + rz[chi] = S1[chi]+rssum[chi]*S3 + ctx.prec = wpinitial + return rz + + +def z_half(ctx,t,der=0): + r""" + z_half(t,der=0) Computes Z^(der)(t) + """ + s=ctx.mpf('0.5')+ctx.j*t + wpinitial = ctx.prec + ctx.prec = 15 + tt = t/(2*ctx.pi) + wptheta = wpinitial +1 + ctx.mag(3*(tt**1.5)*ctx.ln(tt)) + wpz = wpinitial + 1 + ctx.mag(12*tt*ctx.ln(tt)) + ctx.prec = wptheta + theta = ctx.siegeltheta(t) + ctx.prec = wpz + rz = Rzeta_set(ctx,s, range(der+1)) + if der > 0: ps1 = ctx._re(ctx.psi(0,s/2)/2 - ctx.ln(ctx.pi)/2) + if der > 1: ps2 = ctx._re(ctx.j*ctx.psi(1,s/2)/4) + if der > 2: ps3 = ctx._re(-ctx.psi(2,s/2)/8) + if der > 3: ps4 = ctx._re(-ctx.j*ctx.psi(3,s/2)/16) + exptheta = ctx.expj(theta) + if der == 0: + z = 2*exptheta*rz[0] + if der == 1: + zf = 2j*exptheta + z = zf*(ps1*rz[0]+rz[1]) + if der == 2: + zf = 2 * exptheta + z = -zf*(2*rz[1]*ps1+rz[0]*ps1**2+rz[2]-ctx.j*rz[0]*ps2) + if der == 3: + zf = -2j*exptheta + z = 3*rz[1]*ps1**2+rz[0]*ps1**3+3*ps1*rz[2] + z = zf*(z-3j*rz[1]*ps2-3j*rz[0]*ps1*ps2+rz[3]-rz[0]*ps3) + if der == 4: + zf = 2*exptheta + z = 4*rz[1]*ps1**3+rz[0]*ps1**4+6*ps1**2*rz[2] + z = z-12j*rz[1]*ps1*ps2-6j*rz[0]*ps1**2*ps2-6j*rz[2]*ps2-3*rz[0]*ps2*ps2 + z = z + 4*ps1*rz[3]-4*rz[1]*ps3-4*rz[0]*ps1*ps3+rz[4]+ctx.j*rz[0]*ps4 + z = zf*z + ctx.prec = wpinitial + return ctx._re(z) + +def zeta_half(ctx, s, k=0): + """ + zeta_half(s,k=0) Computes zeta^(k)(s) when Re s = 0.5 + """ + wpinitial = ctx.prec + sigma = ctx._re(s) + t = ctx._im(s) + #--- compute wptheta, wpR, wpbasic --- + ctx.prec = 53 + # X see II Section 3.21 (109) and (110) + if sigma > 0: + X = ctx.sqrt(abs(s)) + else: + X = (2*ctx.pi)**(sigma-1) * abs(1-s)**(0.5-sigma) + # M1 see II Section 3.21 (111) and (112) + if sigma > 0: + M1 = 2*ctx.sqrt(t/(2*ctx.pi)) + else: + M1 = 4 * t * X + # T see II Section 3.21 (113) + abst = abs(0.5-s) + T = 2* abst*math.log(abst) + # computing wpbasic, wptheta, wpR see II Section 3.21 + wpbasic = max(6,3+ctx.mag(t)) + wpbasic2 = 2+ctx.mag(2.12*M1+21.2*M1*X+1.3*M1*X*T)+wpinitial+1 + wpbasic = max(wpbasic, wpbasic2) + wptheta = max(4, 3+ctx.mag(2.7*M1*X)+wpinitial+1) + wpR = 3+ctx.mag(1.1+2*X)+wpinitial+1 + ctx.prec = wptheta + theta = ctx.siegeltheta(t-ctx.j*(sigma-ctx.mpf('0.5'))) + if k > 0: ps1 = (ctx._re(ctx.psi(0,s/2)))/2 - ctx.ln(ctx.pi)/2 + if k > 1: ps2 = -(ctx._im(ctx.psi(1,s/2)))/4 + if k > 2: ps3 = -(ctx._re(ctx.psi(2,s/2)))/8 + if k > 3: ps4 = (ctx._im(ctx.psi(3,s/2)))/16 + ctx.prec = wpR + xrz = Rzeta_set(ctx,s,range(k+1)) + yrz={} + for chi in range(0,k+1): + yrz[chi] = ctx.conj(xrz[chi]) + ctx.prec = wpbasic + exptheta = ctx.expj(-2*theta) + if k==0: + zv = xrz[0]+exptheta*yrz[0] + if k==1: + zv1 = -yrz[1] - 2*yrz[0]*ps1 + zv = xrz[1] + exptheta*zv1 + if k==2: + zv1 = 4*yrz[1]*ps1+4*yrz[0]*(ps1**2)+yrz[2]+2j*yrz[0]*ps2 + zv = xrz[2]+exptheta*zv1 + if k==3: + zv1 = -12*yrz[1]*ps1**2-8*yrz[0]*ps1**3-6*yrz[2]*ps1-6j*yrz[1]*ps2 + zv1 = zv1 - 12j*yrz[0]*ps1*ps2-yrz[3]+2*yrz[0]*ps3 + zv = xrz[3]+exptheta*zv1 + if k == 4: + zv1 = 32*yrz[1]*ps1**3 +16*yrz[0]*ps1**4+24*yrz[2]*ps1**2 + zv1 = zv1 +48j*yrz[1]*ps1*ps2+48j*yrz[0]*(ps1**2)*ps2 + zv1 = zv1+12j*yrz[2]*ps2-12*yrz[0]*ps2**2+8*yrz[3]*ps1-8*yrz[1]*ps3 + zv1 = zv1-16*yrz[0]*ps1*ps3+yrz[4]-2j*yrz[0]*ps4 + zv = xrz[4]+exptheta*zv1 + ctx.prec = wpinitial + return zv + +def zeta_offline(ctx, s, k=0): + """ + Computes zeta^(k)(s) off the line + """ + wpinitial = ctx.prec + sigma = ctx._re(s) + t = ctx._im(s) + #--- compute wptheta, wpR, wpbasic --- + ctx.prec = 53 + # X see II Section 3.21 (109) and (110) + if sigma > 0: + X = ctx.power(abs(s), 0.5) + else: + X = ctx.power(2*ctx.pi, sigma-1)*ctx.power(abs(1-s),0.5-sigma) + # M1 see II Section 3.21 (111) and (112) + if (sigma > 0): + M1 = 2*ctx.sqrt(t/(2*ctx.pi)) + else: + M1 = 4 * t * X + # M2 see II Section 3.21 (111) and (112) + if (1-sigma > 0): + M2 = 2*ctx.sqrt(t/(2*ctx.pi)) + else: + M2 = 4*t*ctx.power(2*ctx.pi, -sigma)*ctx.power(abs(s),sigma-0.5) + # T see II Section 3.21 (113) + abst = abs(0.5-s) + T = 2* abst*math.log(abst) + # computing wpbasic, wptheta, wpR see II Section 3.21 + wpbasic = max(6,3+ctx.mag(t)) + wpbasic2 = 2+ctx.mag(2.12*M1+21.2*M2*X+1.3*M2*X*T)+wpinitial+1 + wpbasic = max(wpbasic, wpbasic2) + wptheta = max(4, 3+ctx.mag(2.7*M2*X)+wpinitial+1) + wpR = 3+ctx.mag(1.1+2*X)+wpinitial+1 + ctx.prec = wptheta + theta = ctx.siegeltheta(t-ctx.j*(sigma-ctx.mpf('0.5'))) + s1 = s + s2 = ctx.conj(1-s1) + ctx.prec = wpR + xrz, yrz = Rzeta_simul(ctx, s, k) + if k > 0: ps1 = (ctx.psi(0,s1/2)+ctx.psi(0,(1-s1)/2))/4 - ctx.ln(ctx.pi)/2 + if k > 1: ps2 = ctx.j*(ctx.psi(1,s1/2)-ctx.psi(1,(1-s1)/2))/8 + if k > 2: ps3 = -(ctx.psi(2,s1/2)+ctx.psi(2,(1-s1)/2))/16 + if k > 3: ps4 = -ctx.j*(ctx.psi(3,s1/2)-ctx.psi(3,(1-s1)/2))/32 + ctx.prec = wpbasic + exptheta = ctx.expj(-2*theta) + if k == 0: + zv = xrz[0]+exptheta*yrz[0] + if k == 1: + zv1 = -yrz[1]-2*yrz[0]*ps1 + zv = xrz[1]+exptheta*zv1 + if k == 2: + zv1 = 4*yrz[1]*ps1+4*yrz[0]*(ps1**2) +yrz[2]+2j*yrz[0]*ps2 + zv = xrz[2]+exptheta*zv1 + if k == 3: + zv1 = -12*yrz[1]*ps1**2 -8*yrz[0]*ps1**3-6*yrz[2]*ps1-6j*yrz[1]*ps2 + zv1 = zv1 - 12j*yrz[0]*ps1*ps2-yrz[3]+2*yrz[0]*ps3 + zv = xrz[3]+exptheta*zv1 + if k == 4: + zv1 = 32*yrz[1]*ps1**3 +16*yrz[0]*ps1**4+24*yrz[2]*ps1**2 + zv1 = zv1 +48j*yrz[1]*ps1*ps2+48j*yrz[0]*(ps1**2)*ps2 + zv1 = zv1+12j*yrz[2]*ps2-12*yrz[0]*ps2**2+8*yrz[3]*ps1-8*yrz[1]*ps3 + zv1 = zv1-16*yrz[0]*ps1*ps3+yrz[4]-2j*yrz[0]*ps4 + zv = xrz[4]+exptheta*zv1 + ctx.prec = wpinitial + return zv + +def z_offline(ctx, w, k=0): + r""" + Computes Z(w) and its derivatives off the line + """ + s = ctx.mpf('0.5')+ctx.j*w + s1 = s + s2 = ctx.conj(1-s1) + wpinitial = ctx.prec + ctx.prec = 35 + # X see II Section 3.21 (109) and (110) + # M1 see II Section 3.21 (111) and (112) + if (ctx._re(s1) >= 0): + M1 = 2*ctx.sqrt(ctx._im(s1)/(2 * ctx.pi)) + X = ctx.sqrt(abs(s1)) + else: + X = (2*ctx.pi)**(ctx._re(s1)-1) * abs(1-s1)**(0.5-ctx._re(s1)) + M1 = 4 * ctx._im(s1)*X + # M2 see II Section 3.21 (111) and (112) + if (ctx._re(s2) >= 0): + M2 = 2*ctx.sqrt(ctx._im(s2)/(2 * ctx.pi)) + else: + M2 = 4 * ctx._im(s2)*(2*ctx.pi)**(ctx._re(s2)-1)*abs(1-s2)**(0.5-ctx._re(s2)) + # T see II Section 3.21 Prop. 27 + T = 2*abs(ctx.siegeltheta(w)) + # defining some precisions + # see II Section 3.22 (115), (116), (117) + aux1 = ctx.sqrt(X) + aux2 = aux1*(M1+M2) + aux3 = 3 +wpinitial + wpbasic = max(6, 3+ctx.mag(T), ctx.mag(aux2*(26+2*T))+aux3) + wptheta = max(4,ctx.mag(2.04*aux2)+aux3) + wpR = ctx.mag(4*aux1)+aux3 + # now the computations + ctx.prec = wptheta + theta = ctx.siegeltheta(w) + ctx.prec = wpR + xrz, yrz = Rzeta_simul(ctx,s,k) + pta = 0.25 + 0.5j*w + ptb = 0.25 - 0.5j*w + if k > 0: ps1 = 0.25*(ctx.psi(0,pta)+ctx.psi(0,ptb)) - ctx.ln(ctx.pi)/2 + if k > 1: ps2 = (1j/8)*(ctx.psi(1,pta)-ctx.psi(1,ptb)) + if k > 2: ps3 = (-1./16)*(ctx.psi(2,pta)+ctx.psi(2,ptb)) + if k > 3: ps4 = (-1j/32)*(ctx.psi(3,pta)-ctx.psi(3,ptb)) + ctx.prec = wpbasic + exptheta = ctx.expj(theta) + if k == 0: + zv = exptheta*xrz[0]+yrz[0]/exptheta + j = ctx.j + if k == 1: + zv = j*exptheta*(xrz[1]+xrz[0]*ps1)-j*(yrz[1]+yrz[0]*ps1)/exptheta + if k == 2: + zv = exptheta*(-2*xrz[1]*ps1-xrz[0]*ps1**2-xrz[2]+j*xrz[0]*ps2) + zv =zv + (-2*yrz[1]*ps1-yrz[0]*ps1**2-yrz[2]-j*yrz[0]*ps2)/exptheta + if k == 3: + zv1 = -3*xrz[1]*ps1**2-xrz[0]*ps1**3-3*xrz[2]*ps1+j*3*xrz[1]*ps2 + zv1 = (zv1+ 3j*xrz[0]*ps1*ps2-xrz[3]+xrz[0]*ps3)*j*exptheta + zv2 = 3*yrz[1]*ps1**2+yrz[0]*ps1**3+3*yrz[2]*ps1+j*3*yrz[1]*ps2 + zv2 = j*(zv2 + 3j*yrz[0]*ps1*ps2+ yrz[3]-yrz[0]*ps3)/exptheta + zv = zv1+zv2 + if k == 4: + zv1 = 4*xrz[1]*ps1**3+xrz[0]*ps1**4 + 6*xrz[2]*ps1**2 + zv1 = zv1-12j*xrz[1]*ps1*ps2-6j*xrz[0]*ps1**2*ps2-6j*xrz[2]*ps2 + zv1 = zv1-3*xrz[0]*ps2*ps2+4*xrz[3]*ps1-4*xrz[1]*ps3-4*xrz[0]*ps1*ps3 + zv1 = zv1+xrz[4]+j*xrz[0]*ps4 + zv2 = 4*yrz[1]*ps1**3+yrz[0]*ps1**4 + 6*yrz[2]*ps1**2 + zv2 = zv2+12j*yrz[1]*ps1*ps2+6j*yrz[0]*ps1**2*ps2+6j*yrz[2]*ps2 + zv2 = zv2-3*yrz[0]*ps2*ps2+4*yrz[3]*ps1-4*yrz[1]*ps3-4*yrz[0]*ps1*ps3 + zv2 = zv2+yrz[4]-j*yrz[0]*ps4 + zv = exptheta*zv1+zv2/exptheta + ctx.prec = wpinitial + return zv + +@defun +def rs_zeta(ctx, s, derivative=0, **kwargs): + if derivative > 4: + raise NotImplementedError + s = ctx.convert(s) + re = ctx._re(s); im = ctx._im(s) + if im < 0: + z = ctx.conj(ctx.rs_zeta(ctx.conj(s), derivative)) + return z + critical_line = (re == 0.5) + if critical_line: + return zeta_half(ctx, s, derivative) + else: + return zeta_offline(ctx, s, derivative) + +@defun +def rs_z(ctx, w, derivative=0): + w = ctx.convert(w) + re = ctx._re(w); im = ctx._im(w) + if re < 0: + return rs_z(ctx, -w, derivative) + critical_line = (im == 0) + if critical_line : + return z_half(ctx, w, derivative) + else: + return z_offline(ctx, w, derivative) diff --git a/mpmath/functions/signals.py b/mpmath/functions/signals.py new file mode 100644 index 0000000..6fadafb --- /dev/null +++ b/mpmath/functions/signals.py @@ -0,0 +1,32 @@ +from .functions import defun_wrapped + +@defun_wrapped +def squarew(ctx, t, amplitude=1, period=1): + P = period + A = amplitude + return A*((-1)**ctx.floor(2*t/P)) + +@defun_wrapped +def trianglew(ctx, t, amplitude=1, period=1): + A = amplitude + P = period + + return 2*A*(0.5 - ctx.fabs(1 - 2*ctx.frac(t/P + 0.25))) + +@defun_wrapped +def sawtoothw(ctx, t, amplitude=1, period=1): + A = amplitude + P = period + return A*ctx.frac(t/P) + +@defun_wrapped +def unit_triangle(ctx, t, amplitude=1): + A = amplitude + if t <= -1 or t >= 1: + return ctx.zero + return A*(-ctx.fabs(t) + 1) + +@defun_wrapped +def sigmoid(ctx, t, amplitude=1): + A = amplitude + return A / (1 + ctx.exp(-t)) diff --git a/mpmath/functions/theta.py b/mpmath/functions/theta.py new file mode 100644 index 0000000..0d27ff8 --- /dev/null +++ b/mpmath/functions/theta.py @@ -0,0 +1,552 @@ +from mpmath.libmp.libintmath import jacobi_symbol + +from .functions import defun, defun_wrapped + + +@defun +def _djacobi_theta2(ctx, z, q, nd): + # the loops below break when the fixed precision quantities + # a and b go to zero; + # right shifting small negative numbers by wp one obtains -1, not zero, + # so the condition a**2 + b**2 > MIN is used to break the loops. + MIN = 2 + extra1 = 10 + extra2 = 20 + if not ctx._im(q) and not ctx._im(z): + wp = ctx.prec + extra1 + x = ctx.to_fixed(ctx._re(q), wp) + x2 = (x*x) >> wp + a = b = x2 + c1, s1 = ctx.cos_sin(ctx._re(z), prec=wp) + cn = c1 = ctx.to_fixed(c1, wp) + sn = s1 = ctx.to_fixed(s1, wp) + c2 = (c1*c1 - s1*s1) >> wp + s2 = (c1 * s1) >> (wp - 1) + cn, sn = (cn*c2 - sn*s2) >> wp, (sn*c2 + cn*s2) >> wp + if nd&1: + s = s1 + ((a * sn * 3**nd) >> wp) + else: + s = c1 + ((a * cn * 3**nd) >> wp) + n = 2 + while abs(a) > MIN: + b = (b*x2) >> wp + a = (a*b) >> wp + cn, sn = (cn*c2 - sn*s2) >> wp, (sn*c2 + cn*s2) >> wp + if nd&1: + s += (a * sn * (2*n+1)**nd) >> wp + else: + s += (a * cn * (2*n+1)**nd) >> wp + n += 1 + s = -(s << 1) + s = ctx.ldexp(s, -wp) + # case z real, q complex + elif not ctx._im(z): + wp = ctx.prec + extra2 + xre = ctx.to_fixed(ctx._re(q), wp) + xim = ctx.to_fixed(ctx._im(q), wp) + x2re = (xre*xre - xim*xim) >> wp + x2im = (xre*xim) >> (wp - 1) + are = bre = x2re + aim = bim = x2im + c1, s1 = ctx.cos_sin(ctx._re(z), prec=wp) + cn = c1 = ctx.to_fixed(c1, wp) + sn = s1 = ctx.to_fixed(s1, wp) + c2 = (c1*c1 - s1*s1) >> wp + s2 = (c1 * s1) >> (wp - 1) + cn, sn = (cn*c2 - sn*s2) >> wp, (sn*c2 + cn*s2) >> wp + if nd&1: + sre = s1 + ((are * sn * 3**nd) >> wp) + sim = ((aim * sn * 3**nd) >> wp) + else: + sre = c1 + ((are * cn * 3**nd) >> wp) + sim = ((aim * cn * 3**nd) >> wp) + n = 5 + while are**2 + aim**2 > MIN: + bre, bim = (bre * x2re - bim * x2im) >> wp, \ + (bre * x2im + bim * x2re) >> wp + are, aim = (are * bre - aim * bim) >> wp, \ + (are * bim + aim * bre) >> wp + cn, sn = (cn*c2 - sn*s2) >> wp, (sn*c2 + cn*s2) >> wp + + if nd&1: + sre += ((are * sn * n**nd) >> wp) + sim += ((aim * sn * n**nd) >> wp) + else: + sre += ((are * cn * n**nd) >> wp) + sim += ((aim * cn * n**nd) >> wp) + n += 2 + sre = -(sre << 1) + sim = -(sim << 1) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim) + # case z complex, q real + elif not ctx._im(q): + wp = ctx.prec + extra2 + x = ctx.to_fixed(ctx._re(q), wp) + x2 = (x*x) >> wp + a = b = x2 + c1, s1 = ctx.cos_sin(z, prec=wp) + cnre = c1re = ctx.to_fixed(ctx._re(c1), wp) + cnim = c1im = ctx.to_fixed(ctx._im(c1), wp) + snre = s1re = ctx.to_fixed(ctx._re(s1), wp) + snim = s1im = ctx.to_fixed(ctx._im(s1), wp) + c2re = (c1re*c1re - c1im*c1im - s1re*s1re + s1im*s1im) >> wp + c2im = (c1re*c1im - s1re*s1im) >> (wp - 1) + s2re = (c1re*s1re - c1im*s1im) >> (wp - 1) + s2im = (c1re*s1im + c1im*s1re) >> (wp - 1) + t1 = (cnre*c2re - cnim*c2im - snre*s2re + snim*s2im) >> wp + t2 = (cnre*c2im + cnim*c2re - snre*s2im - snim*s2re) >> wp + t3 = (snre*c2re - snim*c2im + cnre*s2re - cnim*s2im) >> wp + t4 = (snre*c2im + snim*c2re + cnre*s2im + cnim*s2re) >> wp + cnre = t1 + cnim = t2 + snre = t3 + snim = t4 + if nd&1: + sre = s1re + ((a * snre * 3**nd) >> wp) + sim = s1im + ((a * snim * 3**nd) >> wp) + else: + sre = c1re + ((a * cnre * 3**nd) >> wp) + sim = c1im + ((a * cnim * 3**nd) >> wp) + n = 5 + while abs(a) > MIN: + b = (b*x2) >> wp + a = (a*b) >> wp + t1 = (cnre*c2re - cnim*c2im - snre*s2re + snim*s2im) >> wp + t2 = (cnre*c2im + cnim*c2re - snre*s2im - snim*s2re) >> wp + t3 = (snre*c2re - snim*c2im + cnre*s2re - cnim*s2im) >> wp + t4 = (snre*c2im + snim*c2re + cnre*s2im + cnim*s2re) >> wp + cnre = t1 + cnim = t2 + snre = t3 + snim = t4 + if nd&1: + sre += ((a * snre * n**nd) >> wp) + sim += ((a * snim * n**nd) >> wp) + else: + sre += ((a * cnre * n**nd) >> wp) + sim += ((a * cnim * n**nd) >> wp) + n += 2 + sre = -(sre << 1) + sim = -(sim << 1) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim) + # case z and q complex + else: + wp = ctx.prec + extra2 + xre = ctx.to_fixed(ctx._re(q), wp) + xim = ctx.to_fixed(ctx._im(q), wp) + x2re = (xre*xre - xim*xim) >> wp + x2im = (xre*xim) >> (wp - 1) + are = bre = x2re + aim = bim = x2im + c1, s1 = ctx.cos_sin(z, prec=wp) + cnre = c1re = ctx.to_fixed(ctx._re(c1), wp) + cnim = c1im = ctx.to_fixed(ctx._im(c1), wp) + snre = s1re = ctx.to_fixed(ctx._re(s1), wp) + snim = s1im = ctx.to_fixed(ctx._im(s1), wp) + c2re = (c1re*c1re - c1im*c1im - s1re*s1re + s1im*s1im) >> wp + c2im = (c1re*c1im - s1re*s1im) >> (wp - 1) + s2re = (c1re*s1re - c1im*s1im) >> (wp - 1) + s2im = (c1re*s1im + c1im*s1re) >> (wp - 1) + t1 = (cnre*c2re - cnim*c2im - snre*s2re + snim*s2im) >> wp + t2 = (cnre*c2im + cnim*c2re - snre*s2im - snim*s2re) >> wp + t3 = (snre*c2re - snim*c2im + cnre*s2re - cnim*s2im) >> wp + t4 = (snre*c2im + snim*c2re + cnre*s2im + cnim*s2re) >> wp + cnre = t1 + cnim = t2 + snre = t3 + snim = t4 + if nd&1: + sre = s1re + (((are * snre - aim * snim) * 3**nd) >> wp) + sim = s1im + (((are * snim + aim * snre)* 3**nd) >> wp) + else: + sre = c1re + (((are * cnre - aim * cnim) * 3**nd) >> wp) + sim = c1im + (((are * cnim + aim * cnre)* 3**nd) >> wp) + n = 5 + while are**2 + aim**2 > MIN: + bre, bim = (bre * x2re - bim * x2im) >> wp, \ + (bre * x2im + bim * x2re) >> wp + are, aim = (are * bre - aim * bim) >> wp, \ + (are * bim + aim * bre) >> wp + t1 = (cnre*c2re - cnim*c2im - snre*s2re + snim*s2im) >> wp + t2 = (cnre*c2im + cnim*c2re - snre*s2im - snim*s2re) >> wp + t3 = (snre*c2re - snim*c2im + cnre*s2re - cnim*s2im) >> wp + t4 = (snre*c2im + snim*c2re + cnre*s2im + cnim*s2re) >> wp + cnre = t1 + cnim = t2 + snre = t3 + snim = t4 + if nd&1: + sre += (((are * snre - aim * snim) * n**nd) >> wp) + sim += (((aim * snre + are * snim) * n**nd) >> wp) + else: + sre += (((are * cnre - aim * cnim) * n**nd) >> wp) + sim += (((aim * cnre + are * cnim) * n**nd) >> wp) + n += 2 + sre = -(sre << 1) + sim = -(sim << 1) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim) + s *= ctx.nthroot(q, 4) + return (-1)**(1 - (nd&1) + nd//2) * s + +@defun +def _djacobi_theta3(ctx, z, q, nd): + MIN = 2 + extra1 = 10 + extra2 = 20 + if not ctx._im(q) and not ctx._im(z): + s = 0 + wp = ctx.prec + extra1 + x = ctx.to_fixed(ctx._re(q), wp) + a = (1 << wp) + b = x + x2 = (x*x) >> wp + c1, s1 = ctx.cos_sin(ctx._re(z)*2, prec=wp) + c1 = ctx.to_fixed(c1, wp) + s1 = ctx.to_fixed(s1, wp) + cn = c1 + sn = s1 + if nd&1: + s += (a * sn) >> wp + else: + s += (a * cn) >> wp + n = 2 + while True: + b = (b*x2) >> wp + a = (a*b) >> wp + if abs(a) <= MIN: + break + cn, sn = (cn*c1 - sn*s1) >> wp, (sn*c1 + cn*s1) >> wp + if nd&1: + s += (a * sn * n**nd) >> wp + else: + s += (a * cn * n**nd) >> wp + n += 1 + s = -(s << (nd+1)) + s = ctx.ldexp(s, -wp)*q + # case z real, q complex + elif not ctx._im(z): + wp = ctx.prec + extra2 + xre = ctx.to_fixed(ctx._re(q), wp) + xim = ctx.to_fixed(ctx._im(q), wp) + x2re = (xre*xre - xim*xim) >> wp + x2im = (xre*xim) >> (wp - 1) + are = (1 << wp) + aim = 0 + bre = xre + bim = xim + c1, s1 = ctx.cos_sin(ctx._re(z)*2, prec=wp) + c1 = ctx.to_fixed(c1, wp) + s1 = ctx.to_fixed(s1, wp) + cn = c1 + sn = s1 + if nd&1: + sre = (are * sn) >> wp + sim = (aim * sn) >> wp + else: + sre = (are * cn) >> wp + sim = (aim * cn) >> wp + n = 2 + while True: + bre, bim = (bre * x2re - bim * x2im) >> wp, \ + (bre * x2im + bim * x2re) >> wp + are, aim = (are * bre - aim * bim) >> wp, \ + (are * bim + aim * bre) >> wp + if are**2 + aim**2 <= MIN: + break + cn, sn = (cn*c1 - sn*s1) >> wp, (sn*c1 + cn*s1) >> wp + if nd&1: + sre += (are * sn * n**nd) >> wp + sim += (aim * sn * n**nd) >> wp + else: + sre += (are * cn * n**nd) >> wp + sim += (aim * cn * n**nd) >> wp + n += 1 + sre = -(sre << (nd+1)) + sim = -(sim << (nd+1)) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim)*q + # case z complex, q real + elif not ctx._im(q): + wp = ctx.prec + extra2 + x = ctx.to_fixed(ctx._re(q), wp) + a = (1 << wp) + b = x + x2 = (x*x) >> wp + c1, s1 = ctx.cos_sin(2*z, prec=wp) + cnre = c1re = ctx.to_fixed(ctx._re(c1), wp) + cnim = c1im = ctx.to_fixed(ctx._im(c1), wp) + snre = s1re = ctx.to_fixed(ctx._re(s1), wp) + snim = s1im = ctx.to_fixed(ctx._im(s1), wp) + if nd&1: + sre = (a * snre) >> wp + sim = (a * snim) >> wp + else: + sre = (a * cnre) >> wp + sim = (a * cnim) >> wp + n = 2 + while True: + b = (b*x2) >> wp + a = (a*b) >> wp + if abs(a) <= MIN: + break + t1 = (cnre*c1re - cnim*c1im - snre*s1re + snim*s1im) >> wp + t2 = (cnre*c1im + cnim*c1re - snre*s1im - snim*s1re) >> wp + t3 = (snre*c1re - snim*c1im + cnre*s1re - cnim*s1im) >> wp + t4 = (snre*c1im + snim*c1re + cnre*s1im + cnim*s1re) >> wp + cnre = t1 + cnim = t2 + snre = t3 + snim = t4 + if nd&1: + sre += (a * snre * n**nd) >> wp + sim += (a * snim * n**nd) >> wp + else: + sre += (a * cnre * n**nd) >> wp + sim += (a * cnim * n**nd) >> wp + n += 1 + sre = -(sre << (nd+1)) + sim = -(sim << (nd+1)) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim)*q + # case z and q complex + else: + wp = ctx.prec + extra2 + xre = ctx.to_fixed(ctx._re(q), wp) + xim = ctx.to_fixed(ctx._im(q), wp) + x2re = (xre*xre - xim*xim) >> wp + x2im = (xre*xim) >> (wp - 1) + are = (1 << wp) + aim = 0 + bre = xre + bim = xim + c1, s1 = ctx.cos_sin(2*z, prec=wp) + cnre = c1re = ctx.to_fixed(ctx._re(c1), wp) + cnim = c1im = ctx.to_fixed(ctx._im(c1), wp) + snre = s1re = ctx.to_fixed(ctx._re(s1), wp) + snim = s1im = ctx.to_fixed(ctx._im(s1), wp) + if nd&1: + sre = (are * snre - aim * snim) >> wp + sim = (aim * snre + are * snim) >> wp + else: + sre = (are * cnre - aim * cnim) >> wp + sim = (aim * cnre + are * cnim) >> wp + n = 2 + while True: + bre, bim = (bre * x2re - bim * x2im) >> wp, \ + (bre * x2im + bim * x2re) >> wp + are, aim = (are * bre - aim * bim) >> wp, \ + (are * bim + aim * bre) >> wp + if are**2 + aim**2 <= MIN: + break + t1 = (cnre*c1re - cnim*c1im - snre*s1re + snim*s1im) >> wp + t2 = (cnre*c1im + cnim*c1re - snre*s1im - snim*s1re) >> wp + t3 = (snre*c1re - snim*c1im + cnre*s1re - cnim*s1im) >> wp + t4 = (snre*c1im + snim*c1re + cnre*s1im + cnim*s1re) >> wp + cnre = t1 + cnim = t2 + snre = t3 + snim = t4 + if nd&1: + sre += ((are * snre - aim * snim) * n**nd) >> wp + sim += ((aim * snre + are * snim) * n**nd) >> wp + else: + sre += ((are * cnre - aim * cnim) * n**nd) >> wp + sim += ((aim * cnre + are * cnim) * n**nd) >> wp + n += 1 + sre = -(sre << (nd+1)) + sim = -(sim << (nd+1)) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim)*q + if nd&1: + return (-1)**(nd//2) * s + else: + return (-1)**(1 + nd//2) * s + (ctx.zero if nd else ctx.one) + +@defun +def _reduce_psl2z(ctx, z): + """ + Returns the cumulative transformation matrix, that reduces a complex + number z to the fundamental domain of PSL(2, Z), chosen to be + |Re(z)| ≤ 0.5 and |z| ≥ 1. + """ + z = ctx.convert(z) + assert z.imag > 0, f"Expected point from upper half-plane, got {ctx.mpc(z)}" + + a = d = 1 + b = c = 0 + + z_orig = z + with ctx.extraprec(30): + while True: + # Translate to center in |Re(z)| ≤ 1/2 + n = round(z.real) + if n: + z -= n + a -= n*c + b -= n*d + + # Maybe apply an inversion + if z.real**2 + z.imag**2 < 1: + z = -1/z + a, c = -c, a + b, d = -d, b + if abs(z.real) <= 0.5: + break + else: + break + + # Canonicalize matrix + if c < 0 or (c == 0 and d < 0): + a, b, c, d = -a, -b, -c, -d + + return a, b, c, d + +# +# General modular transformations for jtheta() +# +# References: +# * Hans Rademacher (1973), "Topics in Analytic Number Theory", +# Springer. Section 81. +# * [DLMF]_, §20.7(viii). +# + +_T_map = {(0, 0): 1, (0, 1): 2, (1, 0): 4, (1, 1): 3} + +def _jtheta_permutation(n, a, b, c, d): + if n == 2: + return _T_map[(c%2, d%2)] + if n == 3: + return _T_map[((a + c)%2, (b + d)%2)] + if n == 4: + return _T_map[(a%2, b%2)] + return 1 + +@defun +def _jtheta_eps(ctx, n, a, b, c, d): + if n != 1: + if n == 2: + phi = (c - 2)*d - 2 + 2*(1 - c)*((d + 1)%2) + elif n == 3: + phi = (a + c - 2)*(b + d) - 3 + 2*(1 - a - c)*((b + d + 1)%2) + else: + phi = (a - 2)*b - 4 + 2*(1 - a)*((b + 1)%2) + k = ctx._jtheta_eps(1, -d, b, c, -a) + else: + if c % 2 == 0: + phi = d*(b - c - 1) + 2 + k = jacobi_symbol(c, d) + else: + phi = c*(a + d + 1) - 3 + k = jacobi_symbol(d, c) + return ctx.expjpi(ctx.convert(phi)/4)/k + +@defun +def _jtheta_needs_modular(ctx, z, q): + if not z.imag: + return False + tau = ctx.taufrom(q=q) + assert abs(q) < 1 and tau.imag > 0 + return abs(tau.real) > 0.5 or tau.real**2 + tau.imag**2 < 1 + +@defun +def _jtheta_modular(ctx, g, n, z, q, nd): + a, b, c, d = g + tau = ctx.taufrom(q=q) + v = -1/(c*tau + d) + alpha = 1j*v*c/ctx.pi + + assert abs(q) < 1 and tau.imag > 0 + + new_n = _jtheta_permutation(n, -d, b, c, -a) + new_z = z*v + new_tau = (a*tau + b)/(c*tau + d) + new_q = ctx.qfrom(tau=new_tau) + + assert abs(new_tau.real) <= 0.5 and new_tau.real**2 + new_tau.imag**2 >= 1 + + def terms(): + Him1, Hi = ctx.zero, ctx.one + a2 = alpha*2 + a2z = a2*z + for i in range(nd + 1): + yield (ctx.binomial(nd, i) * Hi * v**(nd - i) + * ctx.jtheta(new_n, new_z, new_q, nd - i)) + Him1, Hi = Hi, a2z*Hi + a2*i*Him1 + + C = ctx._jtheta_eps(n, -d, b, c, -a)*ctx.sqrt(v/1j) + X = alpha*z**2 + return C*ctx.exp(X)*sum(terms()) + +@defun +def jtheta(ctx, n, z, q, derivative=0): + n = int(n) + z = ctx.convert(z) + q = ctx.convert(q) + nd = int(derivative) + + if n not in range(1, 5): + raise ValueError("First argument expected to be 1, 2, 3 or 4") + if abs(q) >= 1: + raise ValueError(f"abs(q) >= 1") + + # We use Fourier series (DLMF, §20.2(i)) to compute functions, when + # |q| is not near 1. Else, transform τ to the fundamental + # domain (|Re(τ)| ≤ 0.5 and |τ| ≥ 1), applying transformations + # of lattice parameter (DLMF, §20.7(viii)). + + if ctx._jtheta_needs_modular(z, q): + tau = ctx.taufrom(q=q) + g = ctx._reduce_psl2z(tau) + + # Estimate exponential factor + c, d = g[2:] + extra = 10*(nd + 1) + max(0, ctx.mag(c/(c*tau + d)*z**2)) + + return ctx.extraprec(extra, True)(ctx._jtheta_modular)(g, n, z, q, nd) + + # At that point, τ is in the fundamental domain and thus Im(τ) ≥ √3π/2. + # Using quasi-periodicity property (see DLMF, §20.2(ii)) brings + # z to the domain |Im(z)| ≤ π |Im(τ)|/2. + + if abs(z.imag) > abs(ctx.log(q).real)/2: + with ctx.extraprec(10): + tau = ctx.taufrom(q=q) + tau_pi = tau*ctx.pi + k = round(z.imag/tau_pi.imag) + assert k != 0 + beta = -ctx.j*2*k + C = q**(k**2)*ctx.exp(beta*z) + if n in (1, 4) and k & 1: + C = -C + new_z = z - k*tau_pi + + def terms(): + for i in range(nd + 1): + yield (ctx.binomial(nd, i) * beta**i + * ctx.jtheta(n, new_z, q, nd - i)) + + res = C*sum(terms()) + return +res + + extra = 10 + ctx.prec * nd // 10 + if z: + M = ctx.mag(z) + if M > 5 or ((n != 1 if nd else n == 1) and M < -5): + extra += 2*abs(M) + with ctx.extraprec(extra): + if n < 3: + z_inner = z - ctx.pi/2 if n == 1 else z + res = ctx._djacobi_theta2(z_inner, q, nd) + else: + q_inner = -q if n == 4 else q + res = ctx._djacobi_theta3(z, q_inner, nd) + return +res diff --git a/mpmath/functions/zeta.py b/mpmath/functions/zeta.py new file mode 100644 index 0000000..8e946f6 --- /dev/null +++ b/mpmath/functions/zeta.py @@ -0,0 +1,1204 @@ +from .functions import defun, defun_wrapped, defun_static + +@defun +def stieltjes(ctx, n, a=1): + n = ctx.convert(n) + a = ctx.convert(a) + if n < 0: + return ctx.bad_domain("Stieltjes constants defined for n >= 0") + if hasattr(ctx, "stieltjes_cache"): + stieltjes_cache = ctx.stieltjes_cache + else: + stieltjes_cache = ctx.stieltjes_cache = {} + if a == 1: + if n == 0: + return +ctx.euler + if n in stieltjes_cache: + prec, s = stieltjes_cache[n] + if prec >= ctx.prec: + return +s + mag = 1 + def f(x): + xa = x/a + v = (xa-ctx.j)*ctx.ln(a-ctx.j*x)**n/(1+xa**2)/(ctx.exp(2*ctx.pi*x)-1) + return ctx._re(v) / mag + orig = ctx.prec + try: + # Normalize integrand by approx. magnitude to + # speed up quadrature (which uses absolute error) + if n > 50: + ctx.prec = 20 + mag = ctx.quad(f, [0,ctx.inf], maxdegree=3) + ctx.prec = orig + 10 + int(n**0.5) + s = ctx.quad(f, [0,ctx.inf], maxdegree=20) + v = ctx.ln(a)**n/(2*a) - ctx.ln(a)**(n+1)/(n+1) + 2*s/a*mag + finally: + ctx.prec = orig + if a == 1 and ctx.isint(n): + stieltjes_cache[n] = (ctx.prec, v) + return +v + +@defun_wrapped +def siegeltheta(ctx, t, derivative=0): + d = int(derivative) + if (t == ctx.inf or t == ctx.ninf): + if d < 2: + if t == ctx.ninf and d == 0: + return ctx.ninf + return ctx.inf + else: + return ctx.zero + if d == 0: + if ctx._im(t): + # XXX: cancellation occurs + a = ctx.loggamma(0.25+0.5j*t) + b = ctx.loggamma(0.25-0.5j*t) + return -ctx.ln(ctx.pi)/2*t - 0.5j*(a-b) + else: + if ctx.isinf(t): + return t + return ctx._im(ctx.loggamma(0.25+0.5j*t)) - ctx.ln(ctx.pi)/2*t + if d > 0: + a = (-0.5j)**(d-1)*ctx.polygamma(d-1, 0.25-0.5j*t) + b = (0.5j)**(d-1)*ctx.polygamma(d-1, 0.25+0.5j*t) + if ctx._im(t): + if d == 1: + return -0.5*ctx.log(ctx.pi)+0.25*(a+b) + else: + return 0.25*(a+b) + else: + if d == 1: + return ctx._re(-0.5*ctx.log(ctx.pi)+0.25*(a+b)) + else: + return ctx._re(0.25*(a+b)) + +@defun_wrapped +def grampoint(ctx, n): + # asymptotic expansion, from + # http://mathworld.wolfram.com/GramPoint.html + g = 2*ctx.pi*ctx.exp(1+ctx.lambertw((8*n+1)/(8*ctx.e))) + return ctx.findroot(lambda t: ctx.siegeltheta(t)-ctx.pi*n, g) + + +@defun_wrapped +def siegelz(ctx, t, **kwargs): + d = int(kwargs.get("derivative", 0)) + t = ctx.convert(t) + t1 = ctx._re(t) + t2 = ctx._im(t) + prec = ctx.prec + try: + if abs(t1) > 500*prec and t2**2 < t1: + v = ctx.rs_z(t, d) + if ctx._is_real_type(t): + return ctx._re(v) + return v + except NotImplementedError: + pass + ctx.prec += 21 + e1 = ctx.expj(ctx.siegeltheta(t)) + z = ctx.zeta(0.5+ctx.j*t) + if d == 0: + v = e1*z + ctx.prec=prec + if ctx._is_real_type(t): + return ctx._re(v) + return +v + z1 = ctx.zeta(0.5+ctx.j*t, derivative=1) + theta1 = ctx.siegeltheta(t, derivative=1) + if d == 1: + v = ctx.j*e1*(z1+z*theta1) + ctx.prec=prec + if ctx._is_real_type(t): + return ctx._re(v) + return +v + z2 = ctx.zeta(0.5+ctx.j*t, derivative=2) + theta2 = ctx.siegeltheta(t, derivative=2) + comb1 = theta1**2-ctx.j*theta2 + if d == 2: + def terms(): + return [2*z1*theta1, z2, z*comb1] + v = ctx.sum_accurately(terms, 1) + v = -e1*v + ctx.prec = prec + if ctx._is_real_type(t): + return ctx._re(v) + return +v + ctx.prec += 10 + z3 = ctx.zeta(0.5+ctx.j*t, derivative=3) + theta3 = ctx.siegeltheta(t, derivative=3) + comb2 = theta1**3-3*ctx.j*theta1*theta2-theta3 + if d == 3: + def terms(): + return [3*theta1*z2, 3*z1*comb1, z3+z*comb2] + v = ctx.sum_accurately(terms, 1) + v = -ctx.j*e1*v + ctx.prec = prec + if ctx._is_real_type(t): + return ctx._re(v) + return +v + z4 = ctx.zeta(0.5+ctx.j*t, derivative=4) + theta4 = ctx.siegeltheta(t, derivative=4) + def terms(): + return [theta1**4, -6*ctx.j*theta1**2*theta2, -3*theta2**2, + -4*theta1*theta3, ctx.j*theta4] + comb3 = ctx.sum_accurately(terms, 1) + if d == 4: + def terms(): + return [6*theta1**2*z2, -6*ctx.j*z2*theta2, 4*theta1*z3, + 4*z1*comb2, z4, z*comb3] + v = ctx.sum_accurately(terms, 1) + v = e1*v + ctx.prec = prec + if ctx._is_real_type(t): + return ctx._re(v) + return +v + if d > 4: + h = lambda x: ctx.siegelz(x, derivative=4) + return ctx.diff(h, t, n=d-4) + + +_zeta_zeros = [ +14.134725142,21.022039639,25.010857580,30.424876126,32.935061588, +37.586178159,40.918719012,43.327073281,48.005150881,49.773832478, +52.970321478,56.446247697,59.347044003,60.831778525,65.112544048, +67.079810529,69.546401711,72.067157674,75.704690699,77.144840069, +79.337375020,82.910380854,84.735492981,87.425274613,88.809111208, +92.491899271,94.651344041,95.870634228,98.831194218,101.317851006, +103.725538040,105.446623052,107.168611184,111.029535543,111.874659177, +114.320220915,116.226680321,118.790782866,121.370125002,122.946829294, +124.256818554,127.516683880,129.578704200,131.087688531,133.497737203, +134.756509753,138.116042055,139.736208952,141.123707404,143.111845808, +146.000982487,147.422765343,150.053520421,150.925257612,153.024693811, +156.112909294,157.597591818,158.849988171,161.188964138,163.030709687, +165.537069188,167.184439978,169.094515416,169.911976479,173.411536520, +174.754191523,176.441434298,178.377407776,179.916484020,182.207078484, +184.874467848,185.598783678,187.228922584,189.416158656,192.026656361, +193.079726604,195.265396680,196.876481841,198.015309676,201.264751944, +202.493594514,204.189671803,205.394697202,207.906258888,209.576509717, +211.690862595,213.347919360,214.547044783,216.169538508,219.067596349, +220.714918839,221.430705555,224.007000255,224.983324670,227.421444280, +229.337413306,231.250188700,231.987235253,233.693404179,236.524229666, +] + +def _load_zeta_zeros(url): + import urllib + d = urllib.urlopen(url) + L = [float(x) for x in d.readlines()] + # Sanity check + assert round(L[0]) == 14 + _zeta_zeros[:] = L + +@defun +def oldzetazero(ctx, n, url='http://www.dtc.umn.edu/~odlyzko/zeta_tables/zeros1'): + n = int(n) + if n < 0: + return ctx.zetazero(-n).conjugate() + if n == 0: + raise ValueError("n must be nonzero") + if n > len(_zeta_zeros) and n <= 100000: + _load_zeta_zeros(url) + if n > len(_zeta_zeros): + raise NotImplementedError("n too large for zetazeros") + return ctx.mpc(0.5, ctx.findroot(ctx.siegelz, _zeta_zeros[n-1])) + +@defun_wrapped +def riemannr(ctx, x): + if x == 0: + return ctx.zero + # Check if a simple asymptotic estimate is accurate enough + if abs(x) > 1000: + a = ctx.li(x) + b = 0.5*ctx.li(ctx.sqrt(x)) + if abs(b) < abs(a)*ctx.eps: + return a + if abs(x) < 0.01: + # XXX + ctx.prec += int(-ctx.log(abs(x),2)) + # Sum Gram's series + s = t = ctx.one + u = ctx.ln(x) + k = 1 + while abs(t) > abs(s)*ctx.eps: + t = t * u / k + s += t / (k * ctx._zeta_int(k+1)) + k += 1 + return s + +@defun_static +def primepi(ctx, x): + x = int(x) + if x < 2: + return 0 + return len(ctx.list_primes(x)) + +# TODO: fix the interface wrt contexts +@defun_wrapped +def primepi2(ctx, x): + x = int(x) + if x < 2: + return ctx._iv.zero + if x < 2657: + return ctx._iv.mpf(ctx.primepi(x)) + mid = ctx.li(x) + # Schoenfeld's estimate for x >= 2657, assuming RH + err = ctx.sqrt(x,rounding='u')*ctx.ln(x,rounding='u')/8/ctx.pi(rounding='d') + a = ctx.floor((ctx._iv.mpf(mid)-err).a, rounding='d') + b = ctx.ceil((ctx._iv.mpf(mid)+err).b, rounding='u') + return ctx._iv.mpf([a,b]) + +@defun_wrapped +def primezeta(ctx, s): + if ctx.isnan(s): + return s + if ctx.re(s) <= 0: + raise ValueError("prime zeta function defined only for re(s) > 0") + if s == 1: + return ctx.inf + if s == 0.5: + return ctx.mpc(ctx.ninf, ctx.pi) + r = ctx.re(s) + if r > ctx.prec: + return 0.5**s + else: + wp = ctx.prec + int(r) + def terms(): + orig = ctx.prec + # zeta ~ 1+eps; need to set precision + # to get logarithm accurately + k = 0 + while 1: + k += 1 + u = ctx.moebius(k) + if not u: + continue + ctx.prec = wp + t = u*ctx.ln(ctx.zeta(k*s))/k + if not t: + return + #print ctx.prec, ctx.nstr(t) + ctx.prec = orig + yield t + return ctx.sum_accurately(terms) + +# TODO: for bernpoly and eulerpoly, ensure that all exact zeros are covered + +@defun_wrapped +def bernpoly(ctx, n, z): + n = int(n) + if n < 0: + raise ValueError("Bernoulli polynomials only defined for n >= 0") + if ctx.isinf(z): + return z ** n + if ctx.isnan(z): + return z + if n <= 3: + if n == 0: return z ** 0 + if n == 1: return z - 0.5 + if n == 2: return (6*z*(z-1)+1)/6 + if n == 3: return z*(z*(z-1.5)+0.5) + if z == 0 or z == 1: + return ctx.bernoulli(n) + if z == 0.5: + return (ctx.ldexp(1,1-n)-1)*ctx.bernoulli(n) + if abs(z) > 2: + def terms(): + t = ctx.one + yield t + r = ctx.one/z + t = t*n*r + yield -t/2 + k = 2 + while k <= n: + t = t*(n+1-k)/k*r + if not k & 1: + yield t*ctx.bernoulli(k) + k += 1 + return ctx.sum_accurately(terms) * z**n + else: + def terms(): + yield ctx.bernoulli(n) + t = ctx.one + k = 1 + while k < n - 1: + t = t*(n+1-k)/k * z + m = n-k + if not m & 1: + yield t*ctx.bernoulli(m) + k += 1 + t = t*2/(n-1)*z + yield -t/2 + t = t/n*z + yield t + return ctx.sum_accurately(terms) + +@defun_wrapped +def eulerpoly(ctx, n, z): + n = int(n) + if n < 0: + raise ValueError("Euler polynomials only defined for n >= 0") + if n <= 2: + if n == 0: return z ** 0 + if n == 1: return z - 0.5 + if n == 2: return z*(z-1) + if ctx.isinf(z): + return z**n + if ctx.isnan(z): + return z + m = n+1 + if z == 0: + return -2*(ctx.ldexp(1,m)-1)*ctx.bernoulli(m)/m * z**0 + if z == 1: + return 2*(ctx.ldexp(1,m)-1)*ctx.bernoulli(m)/m * z**0 + if z == 0.5: + if n % 2: + return ctx.zero + # Use exact code for Euler numbers + if n < 100 or n*ctx.mag(0.46839865*n) < ctx.prec*0.25: + return ctx.ldexp(ctx._eulernum(n), -n) + # http://functions.wolfram.com/Polynomials/EulerE2/06/01/02/01/0002/ + def terms(): + t = ctx.one + k = 0 + w = ctx.ldexp(1,n+2) + while 1: + v = n-k+1 + if not v & 1: + yield (2-w)*ctx.bernoulli(v)*t + k += 1 + t = t*z*(n-k+2)/k + w *= 0.5 + if k >= n: + break + yield -(2-w)*t/2 + return ctx.sum_accurately(terms) / m + +@defun +def eulernum(ctx, n, exact=False): + n = int(n) + if exact: + return int(ctx._eulernum(n)) + if n < 100: + return ctx.mpf(ctx._eulernum(n)) + if n % 2: + return ctx.zero + return ctx.ldexp(ctx.eulerpoly(n,0.5), n) + +# TODO: this should be implemented low-level +def polylog_series(ctx, s, z): + tol = +ctx.eps + l = ctx.zero + k = 1 + zk = z + while 1: + term = zk / k**s + l += term + if abs(term) < tol: + break + zk *= z + k += 1 + return l + +def polylog_continuation(ctx, n, z): + if n < 0: + return z*0 + if ctx._is_real_type(z) and ctx.isinf(z) and n > 0: + return ctx.ninf if z < 0 else ctx.mpc(ctx.ninf, ctx.nan) + twopij = 2j * ctx.pi + a = -twopij**n/ctx.fac(n) * ctx.bernpoly(n, ctx.ln(z)/twopij) + if ctx._is_real_type(z) and z < 0: + a = ctx._re(a) + if ctx._im(z) < 0 or (ctx._im(z) == 0 and ctx._re(z) >= 1): + a -= twopij*ctx.ln(z)**(n-1)/ctx.fac(n-1) + return a + +def polylog_unitcircle(ctx, n, z): + tol = +ctx.eps + if n > 1: + l = ctx.zero + logz = ctx.ln(z) + logmz = ctx.one + m = 0 + while 1: + if (n-m) != 1: + term = ctx.zeta(n-m) * logmz / ctx.fac(m) + if term and abs(term) < tol: + break + l += term + logmz *= logz + m += 1 + l += ctx.ln(z)**(n-1)/ctx.fac(n-1)*(ctx.harmonic(n-1)-ctx.ln(-ctx.ln(z))) + elif n < 1: # else + l = ctx.fac(-n)*(-ctx.ln(z))**(n-1) + logz = ctx.ln(z) + logkz = ctx.one + k = 0 + while 1: + b = ctx.bernoulli(k-n+1) + if b: + term = b*logkz/(ctx.fac(k)*(k-n+1)) + if abs(term) < tol: + break + l -= term + logkz *= logz + k += 1 + else: + raise ValueError + if ctx._is_real_type(z) and z < 0: + l = ctx._re(l) + return l + +def polylog_general(ctx, s, z): + v = ctx.zero + u = ctx.ln(z) + if not abs(u) < 5: # theoretically |u| < 2*pi + j = ctx.j + v = 1-s + y = ctx.ln(-z)/(2*ctx.pi*j) + return ctx.gamma(v)*(j**v*ctx.zeta(v,0.5+y) + j**-v*ctx.zeta(v,0.5-y))/(2*ctx.pi)**v + t = 1 + k = 0 + prec = ctx.prec + if ctx.isfinite(s): + ctx.prec += max(0, -ctx.nint_distance(s)[1]) + + while 1: + term = ctx.zeta(s-k) * t + if not abs(term) >= ctx.eps: + break + v += term + k += 1 + t *= u + t /= k + + r = ctx.gamma(1-s)*(-u)**(s-1) + v + ctx.prec = prec + return r + +@defun_wrapped +def polylog(ctx, s, z): + s = ctx.convert(s) + z = ctx.convert(z) + if z == 1: + return ctx.zeta(s) + if z == -1: + return -ctx.altzeta(s) + if s == 0: + return z/(1-z) + if s == 1: + return -ctx.ln(1-z) + if s == -1: + return z/(1-z)**2 + if abs(z) <= 0.75 or (not ctx.isint(s) and abs(z) < 0.9): + return polylog_series(ctx, s, z) + if abs(z) >= 1.4 and ctx.isint(s): + return (-1)**(s+1)*polylog_series(ctx, s, 1/z) + polylog_continuation(ctx, int(ctx.re(s)), z) + if ctx.isnan(z): + if ctx._is_real_type(z) and ctx.isnpint(s): + return ctx.nan + return ctx.mpc(ctx.nan, ctx.nan) + if ctx.isint(s): + return polylog_unitcircle(ctx, int(ctx.re(s)), z) + return polylog_general(ctx, s, z) + +@defun_wrapped +def clsin(ctx, s, z, pi=False): + if ctx.isint(s) and s < 0 and int(s) % 2 == 1: + return z*0 + if pi: + a = ctx.expjpi(z) + else: + a = ctx.expj(z) + if ctx._is_real_type(z) and ctx._is_real_type(s): + return ctx.im(ctx.polylog(s,a)) + b = 1/a + return (-0.5j)*(ctx.polylog(s,a) - ctx.polylog(s,b)) + +@defun_wrapped +def clcos(ctx, s, z, pi=False): + if ctx.isint(s) and s < 0 and int(s) % 2 == 0: + return z*0 + if pi: + a = ctx.expjpi(z) + else: + a = ctx.expj(z) + if ctx._is_real_type(z) and ctx._is_real_type(s): + return ctx.re(ctx.polylog(s,a)) + b = 1/a + return 0.5*(ctx.polylog(s,a) + ctx.polylog(s,b)) + +@defun +def altzeta(ctx, s, **kwargs): + try: + return ctx._altzeta(s, **kwargs) + except NotImplementedError: + return ctx._altzeta_generic(s) + +@defun_wrapped +def _altzeta_generic(ctx, s): + if s == 1: + return ctx.ln2 + 0*s + return -ctx.powm1(2, 1-s) * ctx.zeta(s) + +@defun +def zeta(ctx, s, a=1, derivative=0, method=None, **kwargs): + d = int(derivative) + if a == 1 and not (d or method): + try: + return ctx._zeta(s, **kwargs) + except NotImplementedError: + pass + s = ctx.convert(s) + prec = ctx.prec + verbose = kwargs.get('verbose') + if (not s) and (not derivative): + return ctx.mpf(0.5) - ctx._convert_param(a)[0] + if a == 1 and method != 'euler-maclaurin': + im = abs(ctx._im(s)) + re = abs(ctx._re(s)) + #if (im < prec or method == 'borwein') and not derivative: + # try: + # if verbose: + # print "zeta: Attempting to use the Borwein algorithm" + # return ctx._zeta(s, **kwargs) + # except NotImplementedError: + # if verbose: + # print "zeta: Could not use the Borwein algorithm" + # pass + if abs(im) > 500*prec and 10*re < prec and derivative <= 4 or \ + method == 'riemann-siegel': + try: + if verbose: + print("zeta: Attempting to use the Riemann-Siegel algorithm") + return ctx.rs_zeta(s, derivative, **kwargs) + except NotImplementedError: + if verbose: + print("zeta: Could not use the Riemann-Siegel algorithm") + finally: + ctx.prec = prec + if s == 1: + return ctx.inf + abss = abs(s) + if abss == ctx.inf: + if ctx.re(s) == ctx.inf: + if d == 0: + return ctx.one + return ctx.zero + return s*0 + elif ctx.isnan(abss): + return 1/s + if ctx.re(s) > 2*ctx.prec and a == 1 and not derivative: + return ctx.one + ctx.power(2, -s) + return +ctx._hurwitz(s, a, d, **kwargs) + +@defun +def _hurwitz(ctx, s, a=1, d=0, **kwargs): + prec = ctx.prec + verbose = kwargs.get('verbose') + try: + extraprec = 10 + ctx.prec += extraprec + # We strongly want to special-case rational a + a, atype = ctx._convert_param(a) + + if ctx.re(a) < 0 and ctx.isnpint(a): + raise ValueError("Hurwitz zeta complex infinity") + + if ctx.re(s) < 0: + if verbose: + print("zeta: Attempting reflection formula") + try: + return _hurwitz_reflection(ctx, s, a, d, atype) + except NotImplementedError: + pass + if verbose: + print("zeta: Reflection formula failed") + if verbose: + print("zeta: Using the Euler-Maclaurin algorithm") + while 1: + ctx.prec = prec + extraprec + T1, T2 = _hurwitz_em(ctx, s, a, d, prec+10, verbose) + cancellation = ctx.mag(T1) - ctx.mag(T1+T2) + if verbose: + print("Term 1:", T1) + print("Term 2:", T2) + print("Cancellation:", cancellation, "bits") + if cancellation < extraprec: + return T1 + T2 + else: + extraprec = max(2*extraprec, min(cancellation + 5, 100*prec)) + if extraprec > kwargs.get('maxprec', 100*prec): + raise ctx.NoConvergence("zeta: too much cancellation") + finally: + ctx.prec = prec + +def _hurwitz_reflection(ctx, s, a, d, atype): + # TODO: implement for derivatives + if d != 0: + raise NotImplementedError + res = ctx.re(s) + negs = -s + # Integer reflection formula + if ctx.isnpint(s): + n = int(res) + if n <= 0: + return ctx.bernpoly(1-n, a) / (n-1) + if not (atype == 'Q' or atype == 'Z'): + raise NotImplementedError + t = 1-s + # We now require a to be standardized + v = 0 + shift = 0 + b = ctx.mpf(a) + while ctx.re(b) > 1: + b -= 1 + v -= b**negs + shift -= 1 + while ctx.re(b) <= 0: + v += b**negs + b += 1 + shift += 1 + # Rational reflection formula + try: + p, q = a.numerator, a.denominator + except: + assert a == int(a) + p = int(a) + q = 1 + p += shift*q + assert 1 <= p <= q + g = ctx.fsum(ctx.cospi(t/2-2*k*b)*ctx._hurwitz(t,(k,q)) \ + for k in range(1, q+1)) + g *= 2*ctx.gamma(t)/(2*ctx.pi*q)**t + v += g + return v + +def _hurwitz_em(ctx, s, a, d, prec, verbose): + # May not be converted at this point + a = ctx.convert(a) + tol = -prec + # Estimate number of terms for Euler-Maclaurin summation; could be improved + M1 = 0 + M2 = prec // 3 + N = M2 + lsum = 0 + # This speeds up the recurrence for derivatives + if ctx.isint(s): + s = int(ctx._re(s)) + s1 = s-1 + while 1: + # Truncated L-series + l = ctx._zetasum(s, M1+a, M2-M1-1, [d])[0][0] + #if d: + # l = ctx.fsum((-ctx.ln(n+a))**d * (n+a)**negs for n in range(M1,M2)) + #else: + # l = ctx.fsum((n+a)**negs for n in range(M1,M2)) + lsum += l + M2a = M2+a + logM2a = ctx.ln(M2a) + logM2ad = logM2a**d + logs = [logM2ad] + logr = 1/logM2a + rM2a = 1/M2a + M2as = M2a**(-s) + if d: + tailsum = ctx.gammainc(d+1, s1*logM2a) / s1**(d+1) + else: + tailsum = 1/((s1)*(M2a)**s1) + tailsum += 0.5 * logM2ad * M2as + U = [1] + r = M2as + fact = 2 + for j in range(1, N+1): + # TODO: the following could perhaps be tidied a bit + j2 = 2*j + if j == 1: + upds = [1] + else: + upds = [j2-2, j2-1] + for m in upds: + D = min(m,d+1) + if m <= d: + logs.append(logs[-1] * logr) + Un = [0]*(D+1) + for i in range(D): Un[i] = (1-m-s)*U[i] + for i in range(1, D+1): Un[i] += (d-(i-1))*U[i-1] + U = Un + r *= rM2a + t = ctx.fdot(U, logs) * r * ctx.bernoulli(j2)/(-fact) + tailsum += t + if ctx.mag(t) < tol: + return lsum, (-1)**d * tailsum + fact *= (j2+1)*(j2+2) + if verbose: + print("Sum range:", M1, M2, "term magnitude", ctx.mag(t), "tolerance", tol) + M1, M2 = M2, M2*2 + if ctx.re(s) < 0: + N += N//2 + + + +@defun +def _zetasum(ctx, s, a, n, derivatives=[0], reflect=False): + """ + Returns [xd0,xd1,...,xdr], [yd0,yd1,...ydr] where + + xdk = D^k ( 1/a^s + 1/(a+1)^s + ... + 1/(a+n)^s ) + ydk = D^k conj( 1/a^(1-s) + 1/(a+1)^(1-s) + ... + 1/(a+n)^(1-s) ) + + D^k = kth derivative with respect to s, k ranges over the given list of + derivatives (which should consist of either a single element + or a range 0,1,...r). If reflect=False, the ydks are not computed. + """ + #print "zetasum", s, a, n + # don't use the fixed-point code if there are large exponentials + if abs(ctx.re(s)) < 0.5 * ctx.prec: + try: + return ctx._zetasum_fast(s, a, n, derivatives, reflect) + except NotImplementedError: + pass + negs = ctx.fneg(s, exact=True) + have_derivatives = derivatives != [0] + have_one_derivative = len(derivatives) == 1 + if not reflect: + if not have_derivatives: + return [ctx.fsum((a+k)**negs for k in range(n+1))], [] + if have_one_derivative: + d = derivatives[0] + x = ctx.fsum(ctx.ln(a+k)**d * (a+k)**negs for k in range(n+1)) + return [(-1)**d * x], [] + maxd = max(derivatives) + if not have_one_derivative: + derivatives = range(maxd+1) + xs = [ctx.zero for d in derivatives] + if reflect: + ys = [ctx.zero for d in derivatives] + else: + ys = [] + for k in range(n+1): + w = a + k + xterm = w ** negs + if reflect: + yterm = ctx.conj(ctx.one / (w * xterm)) + if have_derivatives: + logw = -ctx.ln(w) + if have_one_derivative: + logw = logw ** maxd + xs[0] += xterm * logw + if reflect: + ys[0] += yterm * logw + else: + t = ctx.one + for d in derivatives: + xs[d] += xterm * t + if reflect: + ys[d] += yterm * t + t *= logw + else: + xs[0] += xterm + if reflect: + ys[0] += yterm + return xs, ys + +@defun +def dirichlet(ctx, s, chi=[1], derivative=0): + s = ctx.convert(s) + q = len(chi) + d = int(derivative) + if d > 2: + raise NotImplementedError("arbitrary order derivatives") + prec = ctx.prec + try: + ctx.prec += 10 + if s == 1: + have_pole = True + for x in chi: + if x and x != 1: + have_pole = False + h = +ctx.eps + ctx.prec *= 2*(d+1) + s += h + if have_pole: + return +ctx.inf + z = ctx.zero + for p in range(1, q+1): + if chi[p%q]: + if d == 1: + z += chi[p%q] * (ctx.zeta(s, (p,q), 1) - \ + ctx.zeta(s, (p,q))*ctx.log(q)) + else: + z += chi[p%q] * ctx.zeta(s, (p,q)) + z /= q**s + finally: + ctx.prec = prec + return +z + + +def secondzeta_main_term(ctx, s, a, **kwargs): + tol = ctx.eps + f = lambda n: ctx.gammainc(0.5*s, a*gamm**2, regularized=True)*gamm**(-s) + totsum = term = ctx.zero + mg = ctx.inf + n = 0 + while mg > tol: + totsum += term + n += 1 + gamm = ctx.im(ctx.zetazero_memoized(n)) + term = f(n) + mg = abs(term) + err = 0 + if kwargs.get("error"): + sg = ctx.re(s) + err = 0.5*ctx.pi**(-1)*max(1,sg)*a**(sg-0.5)*ctx.log(gamm/(2*ctx.pi))*\ + ctx.gammainc(-0.5, a*gamm**2)/abs(ctx.gamma(s/2)) + err = abs(err) + return +totsum, err, n + +def secondzeta_prime_term(ctx, s, a, **kwargs): + tol = ctx.eps + f = lambda n: ctx.gammainc(0.5*(1-s),0.25*ctx.log(n)**2 * a**(-1))*\ + ((0.5*ctx.log(n))**(s-1))*ctx.mangoldt(n)/ctx.sqrt(n)/\ + (2*ctx.gamma(0.5*s)*ctx.sqrt(ctx.pi)) + totsum = term = ctx.zero + mg = ctx.inf + n = 1 + while mg > tol or n < 9: + totsum += term + n += 1 + term = f(n) + if term == 0: + mg = ctx.inf + else: + mg = abs(term) + if kwargs.get("error"): + err = mg + return +totsum, err, n + +def secondzeta_exp_term(ctx, s, a): + tol = ctx.eps + f = lambda n: (0.25*a)**n/((n+0.5*s)*ctx.fac(n)) + totsum = ctx.zero + term = f(0) + mg = ctx.inf + n = 0 + while mg > tol: + totsum += term + n += 1 + term = f(n) + mg = abs(term) + v = a**(0.5*s)*totsum/ctx.gamma(0.5*s) + return v + +def secondzeta_singular_term(ctx, s, a, **kwargs): + factor = a**(0.5*(s-1))/(4*ctx.sqrt(ctx.pi)*ctx.gamma(0.5*s)) + extraprec = ctx.mag(factor) + ctx.prec += extraprec + factor = a**(0.5*(s-1))/(4*ctx.sqrt(ctx.pi)*ctx.gamma(0.5*s)) + tol = ctx.eps + f = lambda n: ctx.bernpoly(n,0.75)*(4*ctx.sqrt(a))**n*\ + ctx.gamma(0.5*n)/((s+n-1)*ctx.fac(n)) + totsum = ctx.zero + mg1 = ctx.inf + n = 1 + term = f(n) + mg2 = abs(term) + while mg2 > tol and mg2 <= mg1: + totsum += term + n += 1 + term = f(n) + totsum += term + n +=1 + term = f(n) + mg1 = mg2 + mg2 = abs(term) + totsum += term + pole = -2*(s-1)**(-2)+(ctx.euler+ctx.log(16*ctx.pi**2*a))*(s-1)**(-1) + st = factor*(pole+totsum) + err = 0 + if kwargs.get("error"): + if not ((mg2 > tol) and (mg2 <= mg1)): + if mg2 <= tol: + err = ctx.mpf(10)**int(ctx.log(abs(factor*tol),10)) + if mg2 > mg1: + err = ctx.mpf(10)**int(ctx.log(abs(factor*mg1),10)) + err = max(err, ctx.eps*1.) + ctx.prec -= extraprec + return +st, err + +@defun +def secondzeta(ctx, s, a = 0.015, **kwargs): + r""" + Evaluates the secondary zeta function `Z(s)`, defined for + `\mathrm{Re}(s)>1` by + + .. math :: + + Z(s) = \sum_{n=1}^{\infty} \frac{1}{\tau_n^s} + + where `\frac12+i\tau_n` runs through the zeros of `\zeta(s)` with + imaginary part positive. + + `Z(s)` extends to a meromorphic function on `\mathbb{C}` with a + double pole at `s=1` and simple poles at the points `-2n` for + `n=0`, 1, 2, ... + + **Examples** + + >>> from mpmath import mp, secondzeta, pi, gamma, zeta, diff, chop, j + >>> mp.pretty = True + >>> secondzeta(2) + 0.023104993115419 + >>> xi = lambda s: 0.5*s*(s-1)*pi**(-0.5*s)*gamma(0.5*s)*zeta(s) + >>> Xi = lambda t: xi(0.5+t*j) + >>> chop(-0.5*diff(Xi,0,n=2)/Xi(0)) + 0.023104993115419 + + We may ask for an approximate error value:: + + >>> secondzeta(0.5+100j, error=True) + ((-0.216272011276718 - 0.844952708937228j), 2.22044604925031e-16) + + The function has poles at the negative odd integers, + and dyadic rational values at the negative even integers:: + + >>> mp.dps = 30 + >>> secondzeta(-8) + -0.67236328125 + >>> secondzeta(-7) + inf + + **Implementation notes** + + The function is computed as sum of four terms `Z(s)=A(s)-P(s)+E(s)-S(s)` + respectively main, prime, exponential and singular terms. + The main term `A(s)` is computed from the zeros of zeta. + The prime term depends on the von Mangoldt function. + The singular term is responsible for the poles of the function. + + The four terms depends on a small parameter `a`. We may change the + value of `a`. Theoretically this has no effect on the sum of the four + terms, but in practice may be important. + + A smaller value of the parameter `a` makes `A(s)` depend on + a smaller number of zeros of zeta, but `P(s)` uses more values of + von Mangoldt function. + + We may also add a verbose option to obtain data about the + values of the four terms. + + >>> mp.dps = 10 + >>> secondzeta(0.5 + 40j, error=True, verbose=True) + main term = (-30190318549.138656312556 - 13964804384.624622876523j) + computed using 19 zeros of zeta + prime term = (132717176.89212754625045 + 188980555.17563978290601j) + computed using 9 values of the von Mangoldt function + exponential term = (542447428666.07179812536 + 362434922978.80192435203j) + singular term = (512124392939.98154322355 + 348281138038.65531023921j) + ((0.059471043 + 0.3463514534j), 1.455191523e-11) + + >>> secondzeta(0.5 + 40j, a=0.04, error=True, verbose=True) + main term = (-151962888.19606243907725 - 217930683.90210294051982j) + computed using 9 zeros of zeta + prime term = (2476659342.3038722372461 + 28711581821.921627163136j) + computed using 37 values of the von Mangoldt function + exponential term = (178506047114.7838188264 + 819674143244.45677330576j) + singular term = (175877424884.22441310708 + 790744630738.28669174871j) + ((0.059471043 + 0.3463514534j), 1.455191523e-11) + + Notice the great cancellation between the four terms. Changing `a`, the + four terms are very different numbers but the cancellation gives + the good value of Z(s). + + **References** + + * [Voros2003]_ + * [Voros2009]_ + + """ + s = ctx.convert(s) + a = ctx.convert(a) + tol = ctx.eps + if ctx.isnpint(s-1): + if abs(s-1) < tol*1000: + return ctx.inf + m = round(ctx.re(s)) + if m & 1: + return ctx.inf + else: + return ((-1)**(-m//2)*\ + ctx.fraction(8-ctx.eulernum(-m,exact=True),2**(-m+3))) + prec = ctx.prec + try: + t3 = secondzeta_exp_term(ctx, s, a) + extraprec = max(ctx.mag(t3),0) + ctx.prec += extraprec + 3 + t1, r1, gt = secondzeta_main_term(ctx,s,a,error='True', verbose='True') + t2, r2, pt = secondzeta_prime_term(ctx,s,a,error='True', verbose='True') + t4, r4 = secondzeta_singular_term(ctx,s,a,error='True') + t3 = secondzeta_exp_term(ctx, s, a) + err = r1+r2+r4 + t = t1-t2+t3-t4 + if kwargs.get("verbose"): + print('main term =', t1) + print(' computed using', gt, 'zeros of zeta') + print('prime term =', t2) + print(' computed using', pt, 'values of the von Mangoldt function') + print('exponential term =', t3) + print('singular term =', t4) + finally: + ctx.prec = prec + if kwargs.get("error"): + w = max(ctx.mag(abs(t)),0) + err = max(err*2**w, ctx.eps*1.*2**w) + return +t, err + return +t + + +@defun_wrapped +def lerchphi(ctx, z, s, a): + r""" + Gives the Lerch transcendent, defined for `|z| < 1` and + `\Re{a} > 0` by + + .. math :: + + \Phi(z,s,a) = \sum_{k=0}^{\infty} \frac{z^k}{(a+k)^s} + + and generally by the recurrence `\Phi(z,s,a) = z \Phi(z,s,a+1) + a^{-s}` + along with the integral representation valid for `\Re{a} > 0` + + .. math :: + + \Phi(z,s,a) = \frac{1}{2 a^s} + + \int_0^{\infty} \frac{z^t}{(a+t)^s} dt - + 2 \int_0^{\infty} \frac{\sin(t \log z - s + \operatorname{arctan}(t/a)}{(a^2 + t^2)^{s/2} + (e^{2 \pi t}-1)} dt. + + The Lerch transcendent generalizes the Hurwitz zeta function :func:`zeta` + (`z = 1`) and the polylogarithm :func:`polylog` (`a = 1`). + + **Examples** + + Several evaluations in terms of simpler functions:: + + >>> from mpmath import (mp, lerchphi, catalan, diff, zeta, pi, log, + ... atanh, sqrt, j, polylog) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> lerchphi(-1,2,0.5) + 3.663862376708876060218414 + >>> 4*catalan + 3.663862376708876060218414 + >>> diff(lerchphi, (-1,-2,1), (0,1,0)) + 0.2131391994087528954617607 + >>> 7*zeta(3)/(4*pi**2) + 0.2131391994087528954617607 + >>> lerchphi(-4,1,1) + 0.4023594781085250936501898 + >>> log(5)/4 + 0.4023594781085250936501898 + >>> lerchphi(-3+2j,1,0.5) + (1.142423447120257137774002 + 0.2118232380980201350495795j) + >>> 2*atanh(sqrt(-3+2j))/sqrt(-3+2j) + (1.142423447120257137774002 + 0.2118232380980201350495795j) + + Evaluation works for complex arguments and `|z| \ge 1`:: + + >>> lerchphi(1+2j, 3-j, 4+2j) + (0.002025009957009908600539469 + 0.003327897536813558807438089j) + >>> lerchphi(-2,2,-2.5) + -12.28676272353094275265944 + >>> lerchphi(10,10,10) + (-4.462130727102185701817349e-11 - 1.575172198981096218823481e-12j) + >>> lerchphi(10,10,-10.5) + (112658784011940.5605789002 - 498113185.5756221777743631j) + + Some degenerate cases:: + + >>> lerchphi(0,1,2) + 0.5 + >>> lerchphi(0,1,-2) + -0.5 + + Reduction to simpler functions:: + + >>> lerchphi(1, 4.25+1j, 1) + (1.044674457556746668033975 - 0.04674508654012658932271226j) + >>> zeta(4.25+1j) + (1.044674457556746668033975 - 0.04674508654012658932271226j) + >>> lerchphi(1 - 0.5**10, 4.25+1j, 1) + (1.044629338021507546737197 - 0.04667768813963388181708101j) + >>> lerchphi(3, 4, 1) + (1.249503297023366545192592 - 0.2314252413375664776474462j) + >>> polylog(4, 3) / 3 + (1.249503297023366545192592 - 0.2314252413375664776474462j) + >>> lerchphi(3, 4, 1 - 0.5**10) + (1.253978063946663945672674 - 0.2316736622836535468765376j) + + **References** + + 1. [DLMF]_ section 25.14 + + """ + if z == 0: + return a ** (-s) + # Faster, but these cases are useful for testing right now + if z == 1: + return ctx.zeta(s, a) + if a == 1: + return ctx.polylog(s, z) / z + if ctx.re(a) < 1: + if ctx.isnpint(a): + raise ValueError("Lerch transcendent complex infinity") + m = int(ctx.ceil(1-ctx.re(a))) + v = ctx.zero + zpow = ctx.one + for n in range(m): + v += zpow / (a+n)**s + zpow *= z + return zpow * ctx.lerchphi(z,s, a+m) + v + if abs(z) < 0.5: + return ctx.nsum(lambda k: z**k/(a+k)**s, [0, ctx.inf]) + g = lambda t: t**(s - 1)*ctx.exp(-a*t)/(1 - z*ctx.exp(-t)) + h = lambda t: (-t)**(s - 1)*ctx.exp(-a*t)/(1 - z*ctx.exp(-t)) + L = ctx.log(z) + if ctx.isint(s) and s.real >= 1: + if abs(L.imag) < 0.25 and L.real >= 0: + if z.imag <= 0: + I = ctx.quad(g, [0, +1j, +1j + abs(L) + 1, abs(L) + 1, ctx.inf]) + else: + I = ctx.quad(g, [0, -1j, -1j + abs(L) + 1, abs(L) + 1, ctx.inf]) + else: + I = ctx.quad(g, [0, ctx.inf]) + return ctx.rgamma(s)*I + if L.real < -0.5: + residue = 0 + c = min(abs(L.real)/2, 1) + left = right = top = c + elif abs(L.imag) > 0.5: + residue = 0 + c = min(abs(L.imag)/2, 1) + left = right = top = c + else: + residue = (-L)**s/L/z**a + left = max(0, -L.real) + 1 + top = abs(L.imag) + 1 + right = abs(L) + 1 + isreal = not z.imag and z.real < 1 and not s.imag and not a.imag and a.real > 0 + w = ctx.mpc(-1)**(s - 1) + I = 0 + if isreal: + I += 2j*ctx.im(ctx.quad(g, [right, right + top*1j]) / w) + I += 2j*ctx.im(ctx.quad(g, [right + top*1j, -left + top*1j]) / w) + I += 2j*ctx.im(ctx.quad(h, [-left + top*1j, -left])) + I += ctx.quad(g, [right, ctx.inf]) * (w - 1/w) + else: + I += ctx.quad(g, [right, right + top*1j])/w + I += ctx.quad(g, [right + top*1j, -left + top*1j])/w + I += ctx.quad(h, [-left + top*1j, -left - top*1j]) + I += ctx.quad(g, [-left - top*1j, right - top*1j])*w + I += ctx.quad(g, [right - top*1j, right])*w + I += ctx.quad(g, [right, ctx.inf])*(w - 1/w) + I = I/(2*ctx.pi*1j) + residue + return -ctx.gamma(1 - s)*I diff --git a/mpmath/functions/zetazeros.py b/mpmath/functions/zetazeros.py new file mode 100644 index 0000000..c25fe40 --- /dev/null +++ b/mpmath/functions/zetazeros.py @@ -0,0 +1,1018 @@ +""" +The function zetazero(n) computes the n-th nontrivial zero of zeta(s). + +The general strategy is to locate a block of Gram intervals B where we +know exactly the number of zeros contained and which of those zeros +is that which we search. + +If n <= 400 000 000 we know exactly the Rosser exceptions, contained +in a list in this file. Hence for n<=400 000 000 we simply +look at these list of exceptions. If our zero is implicated in one of +these exceptions we have our block B. In other case we simply locate +the good Rosser block containing our zero. + +For n > 400 000 000 we apply the method of Turing, as complemented by +Lehman, Brent and Trudgian to find a suitable B. +""" + +from .functions import defun, defun_wrapped + +def find_rosser_block_zero(ctx, n): + """for n<400 000 000 determines a block were one find our zero""" + for k in range(len(_ROSSER_EXCEPTIONS)//2): + a=_ROSSER_EXCEPTIONS[2*k][0] + b=_ROSSER_EXCEPTIONS[2*k][1] + if ((a<= n-2) and (n-1 <= b)): + t0 = ctx.grampoint(a) + t1 = ctx.grampoint(b) + v0 = ctx._fp.siegelz(t0) + v1 = ctx._fp.siegelz(t1) + my_zero_number = n-a-1 + zero_number_block = b-a + pattern = _ROSSER_EXCEPTIONS[2*k+1] + return (my_zero_number, [a,b], [t0,t1], [v0,v1]) + k = n-2 + t,v,b = compute_triple_tvb(ctx, k) + T = [t] + V = [v] + while b < 0: + k -= 1 + t,v,b = compute_triple_tvb(ctx, k) + T.insert(0,t) + V.insert(0,v) + my_zero_number = n-k-1 + m = n-1 + t,v,b = compute_triple_tvb(ctx, m) + T.append(t) + V.append(v) + while b < 0: + m += 1 + t,v,b = compute_triple_tvb(ctx, m) + T.append(t) + V.append(v) + return (my_zero_number, [k,m], T, V) + +def wpzeros(t): + """Precision needed to compute higher zeros""" + wp = 53 + if t > 3*10**8: + wp = 63 + if t > 10**11: + wp = 70 + if t > 10**14: + wp = 83 + return wp + +def separate_zeros_in_block(ctx, zero_number_block, T, V, limitloop=None, + fp_tolerance=None): + """Separate the zeros contained in the block T, limitloop + determines how long one must search""" + if limitloop is None: + limitloop = ctx.inf + loopnumber = 0 + variations = count_variations(V) + while ((variations < zero_number_block) and (loopnumber 0): + alpha = ctx.sqrt(u/v) + b= (alpha*a+b2)/(alpha+1) + else: + b = (a+b2)/2 + if fp_tolerance < 10: + w = ctx._fp.siegelz(b) + if abs(w)ITERATION_LIMIT)and(loopnumber>2)and(variations+2==zero_number_block): + dtMax=0 + dtSec=0 + kMax = 0 + for k1 in range(1,len(T)): + dt = T[k1]-T[k1-1] + if dt > dtMax: + kMax=k1 + dtSec = dtMax + dtMax = dt + elif (dtdtSec): + dtSec = dt + if dtMax>3*dtSec: + f = lambda x: ctx.rs_z(x,derivative=1) + t0=T[kMax-1] + t1 = T[kMax] + t=ctx.findroot(f, (t0,t1), solver ='anderson') + v = ctx.siegelz(t) + if (t0 2*wpz: + index +=1 + precs = [precs[0] // 2 +3+2*index] + precs + ctx.prec = precs[0] + guard + r = ctx.findroot(ctx.siegelz, (t0,t1), solver ='anderson') + #print "first step at", ctx.dps, "digits" + z=ctx.mpc(0.5,r) + for prec in precs[1:]: + ctx.prec = prec + guard + #print "refining to", ctx.dps, "digits" + znew = z - ctx.zeta(z) / ctx.zeta(z, derivative=1) + #print "difference", ctx.nstr(abs(z-znew)) + z=ctx.mpc(0.5,ctx.im(znew)) + return ctx.im(z) + +def sure_number_block(ctx, n): + """The number of good Rosser blocks needed to apply + Turing method + References: + * [Brent79]_ + * [Trudgian]_ + """ + if n < 9*10**5: + return(2) + g = ctx.grampoint(n-100) + lg = ctx._fp.ln(g) + brent = 0.0061 * lg**2 +0.08*lg + trudgian = 0.0031 * lg**2 +0.11*lg + N = ctx.ceil(min(brent,trudgian)) + N = int(N) + return N + +def compute_triple_tvb(ctx, n): + t = ctx.grampoint(n) + v = ctx._fp.siegelz(t) + if ctx.mag(abs(v))400 000 000""" + sb = sure_number_block(ctx, n) + number_goodblocks = 0 + m2 = n-1 + t, v, b = compute_triple_tvb(ctx, m2) + Tf = [t] + Vf = [v] + while b < 0: + m2 += 1 + t,v,b = compute_triple_tvb(ctx, m2) + Tf.append(t) + Vf.append(v) + goodpoints = [m2] + T = [t] + V = [v] + while number_goodblocks < 2*sb: + m2 += 1 + t, v, b = compute_triple_tvb(ctx, m2) + T.append(t) + V.append(v) + while b < 0: + m2 += 1 + t,v,b = compute_triple_tvb(ctx, m2) + T.append(t) + V.append(v) + goodpoints.append(m2) + zn = len(T)-1 + A, B, separated =\ + separate_zeros_in_block(ctx, zn, T, V, limitloop=ITERATION_LIMIT, + fp_tolerance=fp_tolerance) + Tf.pop() + Tf.extend(A) + Vf.pop() + Vf.extend(B) + if separated: + number_goodblocks += 1 + else: + number_goodblocks = 0 + T = [t] + V = [v] + # Now the same procedure to the left + number_goodblocks = 0 + m2 = n-2 + t, v, b = compute_triple_tvb(ctx, m2) + Tf.insert(0,t) + Vf.insert(0,v) + while b < 0: + m2 -= 1 + t,v,b = compute_triple_tvb(ctx, m2) + Tf.insert(0,t) + Vf.insert(0,v) + goodpoints.insert(0,m2) + T = [t] + V = [v] + while number_goodblocks < 2*sb: + m2 -= 1 + t, v, b = compute_triple_tvb(ctx, m2) + T.insert(0,t) + V.insert(0,v) + while b < 0: + m2 -= 1 + t,v,b = compute_triple_tvb(ctx, m2) + T.insert(0,t) + V.insert(0,v) + goodpoints.insert(0,m2) + zn = len(T)-1 + A, B, separated =\ + separate_zeros_in_block(ctx, zn, T, V, limitloop=ITERATION_LIMIT, fp_tolerance=fp_tolerance) + A.pop() + Tf = A+Tf + B.pop() + Vf = B+Vf + if separated: + number_goodblocks += 1 + else: + number_goodblocks = 0 + T = [t] + V = [v] + r = goodpoints[2*sb] + lg = len(goodpoints) + s = goodpoints[lg-2*sb-1] + tr, vr, br = compute_triple_tvb(ctx, r) + ar = Tf.index(tr) + ts, vs, bs = compute_triple_tvb(ctx, s) + as1 = Tf.index(ts) + T = Tf[ar:as1+1] + V = Vf[ar:as1+1] + zn = s-r + A, B, separated =\ + separate_zeros_in_block(ctx, zn,T,V,limitloop=ITERATION_LIMIT, fp_tolerance=fp_tolerance) + if separated: + return (n-r-1,[r,s],A,B) + q = goodpoints[sb] + lg = len(goodpoints) + t = goodpoints[lg-sb-1] + tq, vq, bq = compute_triple_tvb(ctx, q) + aq = Tf.index(tq) + tt, vt, bt = compute_triple_tvb(ctx, t) + at = Tf.index(tt) + T = Tf[aq:at+1] + V = Vf[aq:at+1] + return (n-q-1,[q,t],T,V) + +def count_variations(V): + count = 0 + vold = V[0] + for n in range(1, len(V)): + vnew = V[n] + if vold*vnew < 0: + count +=1 + vold = vnew + return count + +def pattern_construct(ctx, block, T, V): + pattern = '(' + a = block[0] + b = block[1] + t0,v0,b0 = compute_triple_tvb(ctx, a) + k = 0 + k0 = 0 + for n in range(a+1,b+1): + t1,v1,b1 = compute_triple_tvb(ctx, n) + lgT =len(T) + while (k < lgT) and (T[k] <= t1): + k += 1 + L = V[k0:k] + L.append(v1) + L.insert(0,v0) + count = count_variations(L) + pattern = pattern + ("%s" % count) + if b1 > 0: + pattern = pattern + ')(' + k0 = k + t0,v0,b0 = t1,v1,b1 + pattern = pattern[:-1] + return pattern + +@defun +def zetazero(ctx, n, info=False, round=True): + r""" + Computes the `n`-th nontrivial zero of `\zeta(s)` on the critical line, + i.e. returns an approximation of the `n`-th largest complex number + `s = \frac{1}{2} + ti` for which `\zeta(s) = 0`. Equivalently, the + imaginary part `t` is a zero of the Z-function (:func:`~mpmath.siegelz`). + + **Examples** + + The first few zeros:: + + >>> from mpmath import mp, zetazero, chop, zeta, siegelz + >>> mp.dps = 25 + >>> mp.pretty = True + >>> zetazero(1) + (0.5 + 14.13472514173469379045725j) + >>> zetazero(2) + (0.5 + 21.02203963877155499262848j) + >>> zetazero(20) + (0.5 + 77.14484006887480537268266j) + + Verifying that the values are zeros:: + + >>> for n in range(1,5): + ... s = zetazero(n) + ... chop(zeta(s)), chop(siegelz(s.imag)) + ... + (0.0, 0.0) + (0.0, 0.0) + (0.0, 0.0) + (0.0, 0.0) + + Negative indices give the conjugate zeros (`n = 0` is undefined):: + + >>> zetazero(-1) + (0.5 - 14.13472514173469379045725j) + + :func:`~mpmath.zetazero` supports arbitrarily large `n` and arbitrary precision:: + + >>> mp.dps = 15 + >>> zetazero(1234567) + (0.5 + 727690.906948208j) + >>> mp.dps = 50 + >>> zetazero(1234567) + (0.5 + 727690.9069482075392389420041147142092708393819935j) + >>> chop(zeta(_)/_) + 0.0 + + with *info=True*, :func:`~mpmath.zetazero` gives additional information:: + + >>> mp.dps = 15 + >>> zetazero(542964976,info=True) + ((0.5 + 209039046.578535j), [542964969, 542964978], 6, '(013111110)') + + This means that the zero is between Gram points 542964969 and 542964978; + it is the 6-th zero between them. Finally (01311110) is the pattern + of zeros in this interval. The numbers indicate the number of zeros + in each Gram interval (Rosser blocks between parenthesis). In this case + there is only one Rosser block of length nine. + + **References** + + * [Brent79]_ + * [Trudgian]_ + * [Brent86]_ + * [Lune86]_ + * [Lune84]_ + + """ + n = int(n) + if n < 0: + return ctx.zetazero(-n).conjugate() + if n == 0: + raise ValueError("n must be nonzero") + wpinitial = ctx.prec + try: + wpz, fp_tolerance = comp_fp_tolerance(ctx, n) + ctx.prec = wpz + if n < 400000000: + my_zero_number, block, T, V =\ + find_rosser_block_zero(ctx, n) + else: + my_zero_number, block, T, V =\ + search_supergood_block(ctx, n, fp_tolerance) + zero_number_block = block[1]-block[0] + T, V, separated = separate_zeros_in_block(ctx, zero_number_block, T, V, + limitloop=ctx.inf, fp_tolerance=fp_tolerance) + if info: + pattern = pattern_construct(ctx,block,T,V) + prec = max(wpinitial, wpz) + t = separate_my_zero(ctx, my_zero_number, zero_number_block,T,V,prec) + v = ctx.mpc(0.5,t) + finally: + ctx.prec = wpinitial + if round: + v =+v + if info: + return (v,block,my_zero_number,pattern) + else: + return v + +def gram_index(ctx, t): + if t > 10**13: + wp = 3*ctx.log(t, 10) + else: + wp = 0 + prec = ctx.prec + try: + ctx.prec += wp + h = int(ctx.siegeltheta(t)/ctx.pi) + finally: + ctx.prec = prec + return(h) + +def count_to(ctx, t, T, V): + count = 0 + vold = V[0] + told = T[0] + tnew = T[1] + k = 1 + while tnew < t: + vnew = V[k] + if vold*vnew < 0: + count += 1 + vold = vnew + k += 1 + tnew = T[k] + a = ctx.siegelz(t) + if a*vold < 0: + count += 1 + return count + +def comp_fp_tolerance(ctx, n): + wpz = wpzeros(n*ctx.log(n)) + if n < 15*10**8: + fp_tolerance = 0.0005 + elif n <= 10**14: + fp_tolerance = 0.1 + else: + fp_tolerance = 100 + return wpz, fp_tolerance + +@defun +def nzeros(ctx, t): + r""" + Computes the number of zeros of the Riemann zeta function in + `(0,1) \times (0,t]`, usually denoted by `N(t)`. + + **Examples** + + The first zero has imaginary part between 14 and 15:: + + >>> from mpmath import mp, nzeros, zetazero + >>> mp.pretty = True + >>> nzeros(14) + 0 + >>> nzeros(15) + 1 + >>> zetazero(1) + (0.5 + 14.1347251417347j) + + Some closely spaced zeros:: + + >>> nzeros(10**7) + 21136125 + >>> zetazero(21136125) + (0.5 + 9999999.32718175j) + >>> zetazero(21136126) + (0.5 + 10000000.2400236j) + >>> nzeros(545439823.215) + 1500000001 + >>> zetazero(1500000001) + (0.5 + 545439823.201985j) + >>> zetazero(1500000002) + (0.5 + 545439823.325697j) + + This confirms the data given by J. van de Lune, + H. J. J. te Riele and D. T. Winter in 1986. + """ + if t < 14.1347251417347: + return 0 + x = gram_index(ctx, t) + k = int(ctx.floor(x)) + wpinitial = ctx.prec + wpz, fp_tolerance = comp_fp_tolerance(ctx, k) + ctx.prec = wpz + a = ctx.siegelz(t) + if k == -1 and a < 0: + return 0 + elif k == -1 and a > 0: + return 1 + if k+2 < 400000000: + Rblock = find_rosser_block_zero(ctx, k+2) + else: + Rblock = search_supergood_block(ctx, k+2, fp_tolerance) + n1, n2 = Rblock[1] + if n2-n1 == 1: + b = Rblock[3][0] + if a*b > 0: + ctx.prec = wpinitial + return k+1 + else: + ctx.prec = wpinitial + return k+2 + my_zero_number,block, T, V = Rblock + zero_number_block = n2-n1 + T, V, separated = separate_zeros_in_block(ctx,\ + zero_number_block, T, V,\ + limitloop=ctx.inf,\ + fp_tolerance=fp_tolerance) + n = count_to(ctx, t, T, V) + ctx.prec = wpinitial + return n+n1+1 + +@defun_wrapped +def backlunds(ctx, t): + r""" + Computes the function + `S(t) = \operatorname{arg} \zeta(\frac{1}{2} + it) / \pi`. + + See Titchmarsh Section 9.3 for details of the definition. + + **Examples** + + >>> from mpmath import (mp, backlunds, chop, grampoint, extraprec, + ... nzeros, siegeltheta, pi) + >>> mp.pretty = True + >>> backlunds(217.3) + 0.16302205431184 + + Generally, the value is a small number. At Gram points it is an integer, + frequently equal to 0:: + + >>> chop(backlunds(grampoint(200))) + 0.0 + >>> backlunds(extraprec(10)(grampoint)(211)) + 1.0 + >>> backlunds(extraprec(10)(grampoint)(232)) + -1.0 + + The number of zeros of the Riemann zeta function up to height `t` + satisfies `N(t) = \theta(t)/\pi + 1 + S(t)` (see :func:nzeros` and + :func:`siegeltheta`):: + + >>> t = 1234.55 + >>> nzeros(t) + 842 + >>> siegeltheta(t)/pi+1+backlunds(t) + 842.0 + + """ + return ctx.nzeros(t)-1-ctx.siegeltheta(t)/ctx.pi + + +""" +_ROSSER_EXCEPTIONS is a list of all exceptions to +Rosser's rule for n <= 400 000 000. + +Alternately the entry is of type [n,m], or a string. +The string is the zero pattern of the Block and the relevant +adjacent. For example (010)3 corresponds to a block +composed of three Gram intervals, the first ant third without +a zero and the intermediate with a zero. The next Gram interval +contain three zeros. So that in total we have 4 zeros in 4 Gram +blocks. n and m are the indices of the Gram points of this +interval of four Gram intervals. The Rosser exception is therefore +formed by the three Gram intervals that are signaled between +parenthesis. + +We have included also some Rosser's exceptions beyond n=400 000 000 +that are noted in the literature by some reason. + +The list is composed from the data published in the references: + +* [Brent86]_ +* [Lune86]_ +* [Lune84]_ + + +Thanks to the authors all this papers and those others that have +contributed to make this possible. +""" + + + + + + + +_ROSSER_EXCEPTIONS = \ +[[13999525, 13999528], '(00)3', +[30783329, 30783332], '(00)3', +[30930926, 30930929], '3(00)', +[37592215, 37592218], '(00)3', +[40870156, 40870159], '(00)3', +[43628107, 43628110], '(00)3', +[46082042, 46082045], '(00)3', +[46875667, 46875670], '(00)3', +[49624540, 49624543], '3(00)', +[50799238, 50799241], '(00)3', +[55221453, 55221456], '3(00)', +[56948779, 56948782], '3(00)', +[60515663, 60515666], '(00)3', +[61331766, 61331770], '(00)40', +[69784843, 69784846], '3(00)', +[75052114, 75052117], '(00)3', +[79545240, 79545243], '3(00)', +[79652247, 79652250], '3(00)', +[83088043, 83088046], '(00)3', +[83689522, 83689525], '3(00)', +[85348958, 85348961], '(00)3', +[86513820, 86513823], '(00)3', +[87947596, 87947599], '3(00)', +[88600095, 88600098], '(00)3', +[93681183, 93681186], '(00)3', +[100316551, 100316554], '3(00)', +[100788444, 100788447], '(00)3', +[106236172, 106236175], '(00)3', +[106941327, 106941330], '3(00)', +[107287955, 107287958], '(00)3', +[107532016, 107532019], '3(00)', +[110571044, 110571047], '(00)3', +[111885253, 111885256], '3(00)', +[113239783, 113239786], '(00)3', +[120159903, 120159906], '(00)3', +[121424391, 121424394], '3(00)', +[121692931, 121692934], '3(00)', +[121934170, 121934173], '3(00)', +[122612848, 122612851], '3(00)', +[126116567, 126116570], '(00)3', +[127936513, 127936516], '(00)3', +[128710277, 128710280], '3(00)', +[129398902, 129398905], '3(00)', +[130461096, 130461099], '3(00)', +[131331947, 131331950], '3(00)', +[137334071, 137334074], '3(00)', +[137832603, 137832606], '(00)3', +[138799471, 138799474], '3(00)', +[139027791, 139027794], '(00)3', +[141617806, 141617809], '(00)3', +[144454931, 144454934], '(00)3', +[145402379, 145402382], '3(00)', +[146130245, 146130248], '3(00)', +[147059770, 147059773], '(00)3', +[147896099, 147896102], '3(00)', +[151097113, 151097116], '(00)3', +[152539438, 152539441], '(00)3', +[152863168, 152863171], '3(00)', +[153522726, 153522729], '3(00)', +[155171524, 155171527], '3(00)', +[155366607, 155366610], '(00)3', +[157260686, 157260689], '3(00)', +[157269224, 157269227], '(00)3', +[157755123, 157755126], '(00)3', +[158298484, 158298487], '3(00)', +[160369050, 160369053], '3(00)', +[162962787, 162962790], '(00)3', +[163724709, 163724712], '(00)3', +[164198113, 164198116], '3(00)', +[164689301, 164689305], '(00)40', +[164880228, 164880231], '3(00)', +[166201932, 166201935], '(00)3', +[168573836, 168573839], '(00)3', +[169750763, 169750766], '(00)3', +[170375507, 170375510], '(00)3', +[170704879, 170704882], '3(00)', +[172000992, 172000995], '3(00)', +[173289941, 173289944], '(00)3', +[173737613, 173737616], '3(00)', +[174102513, 174102516], '(00)3', +[174284990, 174284993], '(00)3', +[174500513, 174500516], '(00)3', +[175710609, 175710612], '(00)3', +[176870843, 176870846], '3(00)', +[177332732, 177332735], '3(00)', +[177902861, 177902864], '3(00)', +[179979095, 179979098], '(00)3', +[181233726, 181233729], '3(00)', +[181625435, 181625438], '(00)3', +[182105255, 182105259], '22(00)', +[182223559, 182223562], '3(00)', +[191116404, 191116407], '3(00)', +[191165599, 191165602], '3(00)', +[191297535, 191297539], '(00)22', +[192485616, 192485619], '(00)3', +[193264634, 193264638], '22(00)', +[194696968, 194696971], '(00)3', +[195876805, 195876808], '(00)3', +[195916548, 195916551], '3(00)', +[196395160, 196395163], '3(00)', +[196676303, 196676306], '(00)3', +[197889882, 197889885], '3(00)', +[198014122, 198014125], '(00)3', +[199235289, 199235292], '(00)3', +[201007375, 201007378], '(00)3', +[201030605, 201030608], '3(00)', +[201184290, 201184293], '3(00)', +[201685414, 201685418], '(00)22', +[202762875, 202762878], '3(00)', +[202860957, 202860960], '3(00)', +[203832577, 203832580], '3(00)', +[205880544, 205880547], '(00)3', +[206357111, 206357114], '(00)3', +[207159767, 207159770], '3(00)', +[207167343, 207167346], '3(00)', +[207482539, 207482543], '3(010)', +[207669540, 207669543], '3(00)', +[208053426, 208053429], '(00)3', +[208110027, 208110030], '3(00)', +[209513826, 209513829], '3(00)', +[212623522, 212623525], '(00)3', +[213841715, 213841718], '(00)3', +[214012333, 214012336], '(00)3', +[214073567, 214073570], '(00)3', +[215170600, 215170603], '3(00)', +[215881039, 215881042], '3(00)', +[216274604, 216274607], '3(00)', +[216957120, 216957123], '3(00)', +[217323208, 217323211], '(00)3', +[218799264, 218799267], '(00)3', +[218803557, 218803560], '3(00)', +[219735146, 219735149], '(00)3', +[219830062, 219830065], '3(00)', +[219897904, 219897907], '(00)3', +[221205545, 221205548], '(00)3', +[223601929, 223601932], '(00)3', +[223907076, 223907079], '3(00)', +[223970397, 223970400], '(00)3', +[224874044, 224874048], '22(00)', +[225291157, 225291160], '(00)3', +[227481734, 227481737], '(00)3', +[228006442, 228006445], '3(00)', +[228357900, 228357903], '(00)3', +[228386399, 228386402], '(00)3', +[228907446, 228907449], '(00)3', +[228984552, 228984555], '3(00)', +[229140285, 229140288], '3(00)', +[231810024, 231810027], '(00)3', +[232838062, 232838065], '3(00)', +[234389088, 234389091], '3(00)', +[235588194, 235588197], '(00)3', +[236645695, 236645698], '(00)3', +[236962876, 236962879], '3(00)', +[237516723, 237516727], '04(00)', +[240004911, 240004914], '(00)3', +[240221306, 240221309], '3(00)', +[241389213, 241389217], '(010)3', +[241549003, 241549006], '(00)3', +[241729717, 241729720], '(00)3', +[241743684, 241743687], '3(00)', +[243780200, 243780203], '3(00)', +[243801317, 243801320], '(00)3', +[244122072, 244122075], '(00)3', +[244691224, 244691227], '3(00)', +[244841577, 244841580], '(00)3', +[245813461, 245813464], '(00)3', +[246299475, 246299478], '(00)3', +[246450176, 246450179], '3(00)', +[249069349, 249069352], '(00)3', +[250076378, 250076381], '(00)3', +[252442157, 252442160], '3(00)', +[252904231, 252904234], '3(00)', +[255145220, 255145223], '(00)3', +[255285971, 255285974], '3(00)', +[256713230, 256713233], '(00)3', +[257992082, 257992085], '(00)3', +[258447955, 258447959], '22(00)', +[259298045, 259298048], '3(00)', +[262141503, 262141506], '(00)3', +[263681743, 263681746], '3(00)', +[266527881, 266527885], '(010)3', +[266617122, 266617125], '(00)3', +[266628044, 266628047], '3(00)', +[267305763, 267305766], '(00)3', +[267388404, 267388407], '3(00)', +[267441672, 267441675], '3(00)', +[267464886, 267464889], '(00)3', +[267554907, 267554910], '3(00)', +[269787480, 269787483], '(00)3', +[270881434, 270881437], '(00)3', +[270997583, 270997586], '3(00)', +[272096378, 272096381], '3(00)', +[272583009, 272583012], '(00)3', +[274190881, 274190884], '3(00)', +[274268747, 274268750], '(00)3', +[275297429, 275297432], '3(00)', +[275545476, 275545479], '3(00)', +[275898479, 275898482], '3(00)', +[275953000, 275953003], '(00)3', +[277117197, 277117201], '(00)22', +[277447310, 277447313], '3(00)', +[279059657, 279059660], '3(00)', +[279259144, 279259147], '3(00)', +[279513636, 279513639], '3(00)', +[279849069, 279849072], '3(00)', +[280291419, 280291422], '(00)3', +[281449425, 281449428], '3(00)', +[281507953, 281507956], '3(00)', +[281825600, 281825603], '(00)3', +[282547093, 282547096], '3(00)', +[283120963, 283120966], '3(00)', +[283323493, 283323496], '(00)3', +[284764535, 284764538], '3(00)', +[286172639, 286172642], '3(00)', +[286688824, 286688827], '(00)3', +[287222172, 287222175], '3(00)', +[287235534, 287235537], '3(00)', +[287304861, 287304864], '3(00)', +[287433571, 287433574], '(00)3', +[287823551, 287823554], '(00)3', +[287872422, 287872425], '3(00)', +[288766615, 288766618], '3(00)', +[290122963, 290122966], '3(00)', +[290450849, 290450853], '(00)22', +[291426141, 291426144], '3(00)', +[292810353, 292810356], '3(00)', +[293109861, 293109864], '3(00)', +[293398054, 293398057], '3(00)', +[294134426, 294134429], '3(00)', +[294216438, 294216441], '(00)3', +[295367141, 295367144], '3(00)', +[297834111, 297834114], '3(00)', +[299099969, 299099972], '3(00)', +[300746958, 300746961], '3(00)', +[301097423, 301097426], '(00)3', +[301834209, 301834212], '(00)3', +[302554791, 302554794], '(00)3', +[303497445, 303497448], '3(00)', +[304165344, 304165347], '3(00)', +[304790218, 304790222], '3(010)', +[305302352, 305302355], '(00)3', +[306785996, 306785999], '3(00)', +[307051443, 307051446], '3(00)', +[307481539, 307481542], '3(00)', +[308605569, 308605572], '3(00)', +[309237610, 309237613], '3(00)', +[310509287, 310509290], '(00)3', +[310554057, 310554060], '3(00)', +[310646345, 310646348], '3(00)', +[311274896, 311274899], '(00)3', +[311894272, 311894275], '3(00)', +[312269470, 312269473], '(00)3', +[312306601, 312306605], '(00)40', +[312683193, 312683196], '3(00)', +[314499804, 314499807], '3(00)', +[314636802, 314636805], '(00)3', +[314689897, 314689900], '3(00)', +[314721319, 314721322], '3(00)', +[316132890, 316132893], '3(00)', +[316217470, 316217474], '(010)3', +[316465705, 316465708], '3(00)', +[316542790, 316542793], '(00)3', +[320822347, 320822350], '3(00)', +[321733242, 321733245], '3(00)', +[324413970, 324413973], '(00)3', +[325950140, 325950143], '(00)3', +[326675884, 326675887], '(00)3', +[326704208, 326704211], '3(00)', +[327596247, 327596250], '3(00)', +[328123172, 328123175], '3(00)', +[328182212, 328182215], '(00)3', +[328257498, 328257501], '3(00)', +[328315836, 328315839], '(00)3', +[328800974, 328800977], '(00)3', +[328998509, 328998512], '3(00)', +[329725370, 329725373], '(00)3', +[332080601, 332080604], '(00)3', +[332221246, 332221249], '(00)3', +[332299899, 332299902], '(00)3', +[332532822, 332532825], '(00)3', +[333334544, 333334548], '(00)22', +[333881266, 333881269], '3(00)', +[334703267, 334703270], '3(00)', +[334875138, 334875141], '3(00)', +[336531451, 336531454], '3(00)', +[336825907, 336825910], '(00)3', +[336993167, 336993170], '(00)3', +[337493998, 337494001], '3(00)', +[337861034, 337861037], '3(00)', +[337899191, 337899194], '(00)3', +[337958123, 337958126], '(00)3', +[342331982, 342331985], '3(00)', +[342676068, 342676071], '3(00)', +[347063781, 347063784], '3(00)', +[347697348, 347697351], '3(00)', +[347954319, 347954322], '3(00)', +[348162775, 348162778], '3(00)', +[349210702, 349210705], '(00)3', +[349212913, 349212916], '3(00)', +[349248650, 349248653], '(00)3', +[349913500, 349913503], '3(00)', +[350891529, 350891532], '3(00)', +[351089323, 351089326], '3(00)', +[351826158, 351826161], '3(00)', +[352228580, 352228583], '(00)3', +[352376244, 352376247], '3(00)', +[352853758, 352853761], '(00)3', +[355110439, 355110442], '(00)3', +[355808090, 355808094], '(00)40', +[355941556, 355941559], '3(00)', +[356360231, 356360234], '(00)3', +[356586657, 356586660], '3(00)', +[356892926, 356892929], '(00)3', +[356908232, 356908235], '3(00)', +[357912730, 357912733], '3(00)', +[358120344, 358120347], '3(00)', +[359044096, 359044099], '(00)3', +[360819357, 360819360], '3(00)', +[361399662, 361399666], '(010)3', +[362361315, 362361318], '(00)3', +[363610112, 363610115], '(00)3', +[363964804, 363964807], '3(00)', +[364527375, 364527378], '(00)3', +[365090327, 365090330], '(00)3', +[365414539, 365414542], '3(00)', +[366738474, 366738477], '3(00)', +[368714778, 368714783], '04(010)', +[368831545, 368831548], '(00)3', +[368902387, 368902390], '(00)3', +[370109769, 370109772], '3(00)', +[370963333, 370963336], '3(00)', +[372541136, 372541140], '3(010)', +[372681562, 372681565], '(00)3', +[373009410, 373009413], '(00)3', +[373458970, 373458973], '3(00)', +[375648658, 375648661], '3(00)', +[376834728, 376834731], '3(00)', +[377119945, 377119948], '(00)3', +[377335703, 377335706], '(00)3', +[378091745, 378091748], '3(00)', +[379139522, 379139525], '3(00)', +[380279160, 380279163], '(00)3', +[380619442, 380619445], '3(00)', +[381244231, 381244234], '3(00)', +[382327446, 382327450], '(010)3', +[382357073, 382357076], '3(00)', +[383545479, 383545482], '3(00)', +[384363766, 384363769], '(00)3', +[384401786, 384401790], '22(00)', +[385198212, 385198215], '3(00)', +[385824476, 385824479], '(00)3', +[385908194, 385908197], '3(00)', +[386946806, 386946809], '3(00)', +[387592175, 387592179], '22(00)', +[388329293, 388329296], '(00)3', +[388679566, 388679569], '3(00)', +[388832142, 388832145], '3(00)', +[390087103, 390087106], '(00)3', +[390190926, 390190930], '(00)22', +[390331207, 390331210], '3(00)', +[391674495, 391674498], '3(00)', +[391937831, 391937834], '3(00)', +[391951632, 391951636], '(00)22', +[392963986, 392963989], '(00)3', +[393007921, 393007924], '3(00)', +[393373210, 393373213], '3(00)', +[393759572, 393759575], '(00)3', +[394036662, 394036665], '(00)3', +[395813866, 395813869], '(00)3', +[395956690, 395956693], '3(00)', +[396031670, 396031673], '3(00)', +[397076433, 397076436], '3(00)', +[397470601, 397470604], '3(00)', +[398289458, 398289461], '3(00)', +# +[368714778, 368714783], '04(010)', +[437953499, 437953504], '04(010)', +[526196233, 526196238], '032(00)', +[744719566, 744719571], '(010)40', +[750375857, 750375862], '032(00)', +[958241932, 958241937], '04(010)', +[983377342, 983377347], '(00)410', +[1003780080, 1003780085], '04(010)', +[1070232754, 1070232759], '(00)230', +[1209834865, 1209834870], '032(00)', +[1257209100, 1257209105], '(00)410', +[1368002233, 1368002238], '(00)230' +] diff --git a/mpmath/identification.py b/mpmath/identification.py new file mode 100644 index 0000000..2030e4a --- /dev/null +++ b/mpmath/identification.py @@ -0,0 +1,844 @@ +""" +Implements the PSLQ algorithm for integer relation detection, +and derivative algorithms for constant recognition. +""" + +from .libmp import int_types +from .libmp.libintmath import sqrt_fixed + + +# round to nearest integer (can be done more elegantly...) +def round_fixed(x, prec): + return ((x + (1<<(prec-1))) >> prec) << prec + +class IdentificationMethods: + pass + + +def pslq(ctx, x, tol=None, maxcoeff=1000, maxsteps=100, verbose=False): + r""" + Given a vector of real numbers `x = [x_0, x_1, ..., x_n]`, ``pslq(x)`` + uses the PSLQ algorithm to find a list of integers + `[c_0, c_1, ..., c_n]` such that + + .. math :: + + |c_1 x_1 + c_2 x_2 + ... + c_n x_n| < \mathrm{tol} + + and such that `\max |c_k| < \mathrm{maxcoeff}`. If no such vector + exists, :func:`~mpmath.pslq` returns ``None``. The tolerance defaults to + 3/4 of the working precision. + + **Examples** + + Find rational approximations for `\pi`:: + + >>> from mpmath import mp, pslq, pi, mpf, sqrt, acot + >>> mp.pretty = True + >>> pslq([-1, pi], tol=0.01) + [22, 7] + >>> pslq([-1, pi], tol=0.001) + [355, 113] + >>> mpf(22)/7 + 3.14285714285714 + >>> mpf(355)/113 + 3.14159292035398 + >>> +pi + 3.14159265358979 + + Pi is not a rational number with denominator less than 1000:: + + >>> pslq([-1, pi]) + >>> + + To within the standard precision, it can however be approximated + by at least one rational number with denominator less than `10^{12}`:: + + >>> p, q = pslq([-1, pi], maxcoeff=10**12) + >>> print(p) + 238410049439 + >>> print(q) + 75888275702 + >>> mpf(p)/q + 3.14159265358979 + + The PSLQ algorithm can be applied to long vectors. For example, + we can investigate the rational (in)dependence of integer square + roots:: + + >>> mp.dps = 30 + >>> pslq([sqrt(n) for n in range(2, 5+1)]) + >>> + >>> pslq([sqrt(n) for n in range(2, 6+1)]) + >>> + >>> pslq([sqrt(n) for n in range(2, 8+1)]) + [2, 0, 0, 0, 0, 0, -1] + + **Machin formulas** + + A famous formula for `\pi` is Machin's, + + .. math :: + + \frac{\pi}{4} = 4 \operatorname{acot} 5 - \operatorname{acot} 239 + + There are actually infinitely many formulas of this type. Two + others are + + .. math :: + + \frac{\pi}{4} = \operatorname{acot} 1 + + \frac{\pi}{4} = 12 \operatorname{acot} 49 + 32 \operatorname{acot} 57 + - 5 \operatorname{acot} 239 + 12 \operatorname{acot} 110443 + + We can easily verify the formulas using the PSLQ algorithm:: + + >>> mp.dps = 30 + >>> pslq([pi/4, acot(1)]) + [1, -1] + >>> pslq([pi/4, acot(5), acot(239)]) + [1, -4, 1] + >>> pslq([pi/4, acot(49), acot(57), acot(239), acot(110443)]) + [1, -12, -32, 5, -12] + + We could try to generate a custom Machin-like formula by running + the PSLQ algorithm with a few inverse cotangent values, for example + acot(2), acot(3) ... acot(10). Unfortunately, there is a linear + dependence among these values, resulting in only that dependence + being detected, with a zero coefficient for `\pi`:: + + >>> pslq([pi] + [acot(n) for n in range(2,11)]) + [0, 1, -1, 0, 0, 0, -1, 0, 0, 0] + + We get better luck by removing linearly dependent terms:: + + >>> pslq([pi] + [acot(n) for n in range(2,11) if n not in (3, 5)]) + [1, -8, 0, 0, 4, 0, 0, 0] + + In other words, we found the following formula:: + + >>> 8*acot(2) - 4*acot(7) + 3.14159265358979323846264338328 + >>> +pi + 3.14159265358979323846264338328 + + **Algorithm** + + This is a fairly direct translation to Python of the pseudocode given by + David Bailey, "The PSLQ Integer Relation Algorithm": + http://www.cecm.sfu.ca/organics/papers/bailey/paper/html/node3.html + + The present implementation uses fixed-point instead of floating-point + arithmetic, since this is significantly (about 7x) faster. + """ + + n = len(x) + if n < 2: + raise ValueError("n cannot be less than 2") + + # At too low precision, the algorithm becomes meaningless + prec = ctx.prec + if prec < 53: + raise ValueError("prec cannot be less than 53") + + if verbose and prec // max(2,n) < 5: + print("Warning: precision for PSLQ may be too low") + + target = int(prec * 0.75) + + if tol is None: + tol = ctx.mpf(2)**(-target) + else: + tol = ctx.convert(tol) + + extra = 60 + prec += extra + + if verbose: + print("PSLQ using prec %i and tol %s" % (prec, ctx.nstr(tol))) + + tol = ctx.to_fixed(tol, prec) + assert tol + + # Convert to fixed-point numbers. The dummy None is added so we can + # use 1-based indexing. (This just allows us to be consistent with + # Bailey's indexing. The algorithm is 100 lines long, so debugging + # a single wrong index can be painful.) + x = [None] + [ctx.to_fixed(ctx.mpf(xk), prec) for xk in x] + + # Sanity check on magnitudes + minx = min(abs(xx) for xx in x[1:]) + if not minx: + raise ValueError("PSLQ requires a vector of nonzero numbers") + if minx < tol//100: + if verbose: + print("STOPPING: (one number is too small)") + return None + + g = sqrt_fixed((4<> prec) + s[k] = sqrt_fixed(t, prec) + t = s[1] + y = x[:] + for k in range(1, n+1): + y[k] = (x[k] << prec) // t + s[k] = (s[k] << prec) // t + # step 3 + for i in range(1, n+1): + for j in range(i+1, n): + H[i,j] = 0 + if i <= n-1: + if s[i]: + H[i,i] = (s[i+1] << prec) // s[i] + else: + H[i,i] = 0 + for j in range(1, i): + sjj1 = s[j]*s[j+1] + if sjj1: + H[i,j] = ((-y[i]*y[j])<> prec) + for k in range(1, j+1): + H[i,k] = H[i,k] - (t*H[j,k] >> prec) + for k in range(1, n+1): + A[i,k] = A[i,k] - (t*A[j,k] >> prec) + B[k,j] = B[k,j] + (t*B[k,i] >> prec) + # Main algorithm + for REP in range(maxsteps): + # Step 1 + m = -1 + szmax = -1 + for i in range(1, n): + h = H[i,i] + sz = (g**i * abs(h)) >> (prec*(i-1)) + if sz > szmax: + m = i + szmax = sz + # Step 2 + y[m], y[m+1] = y[m+1], y[m] + for i in range(1,n+1): H[m,i], H[m+1,i] = H[m+1,i], H[m,i] + for i in range(1,n+1): A[m,i], A[m+1,i] = A[m+1,i], A[m,i] + for i in range(1,n+1): B[i,m], B[i,m+1] = B[i,m+1], B[i,m] + # Step 3 + if m <= n - 2: + t0 = sqrt_fixed((H[m,m]**2 + H[m,m+1]**2)>>prec, prec) + # A zero element probably indicates that the precision has + # been exhausted. XXX: this could be spurious, due to + # using fixed-point arithmetic + if not t0: + break + t1 = (H[m,m] << prec) // t0 + t2 = (H[m,m+1] << prec) // t0 + for i in range(m, n+1): + t3 = H[i,m] + t4 = H[i,m+1] + H[i,m] = (t1*t3+t2*t4) >> prec + H[i,m+1] = (-t2*t3+t1*t4) >> prec + # Step 4 + for i in range(m+1, n+1): + for j in range(min(i-1, m+1), 0, -1): + try: + t = round_fixed((H[i,j] << prec)//H[j,j], prec) + # Precision probably exhausted + except ZeroDivisionError: + break + y[j] = y[j] + ((t*y[i]) >> prec) + for k in range(1, j+1): + H[i,k] = H[i,k] - (t*H[j,k] >> prec) + for k in range(1, n+1): + A[i,k] = A[i,k] - (t*A[j,k] >> prec) + B[k,j] = B[k,j] + (t*B[k,i] >> prec) + # Until a relation is found, the error typically decreases + # slowly (e.g. a factor 1-10) with each step TODO: we could + # compare err from two successive iterations. If there is a + # large drop (several orders of magnitude), that indicates a + # "high quality" relation was detected. Reporting this to + # the user somehow might be useful. + best_err = maxcoeff<> prec) for j in \ + range(1,n+1)] + if max(abs(v) for v in vec) < maxcoeff: + if verbose: + print("FOUND relation at iter %i/%i, error: %s" % \ + (REP, maxsteps, ctx.nstr(err / ctx.mpf(2)**prec, 1))) + return vec + best_err = min(err, best_err) + # Calculate a lower bound for the norm. We could do this + # more exactly (using the Euclidean norm) but there is probably + # no practical benefit. + recnorm = max(abs(h) for h in H.values()) + if recnorm: + norm = ((1 << (2*prec)) // recnorm) >> prec + norm //= 100 + else: + norm = ctx.inf + if verbose: + print("%i/%i: Error: %8s Norm: %s" % \ + (REP, maxsteps, ctx.nstr(best_err / ctx.mpf(2)**prec, 1), norm)) + if norm >= maxcoeff: + break + if verbose: + print("CANCELLING after step %i/%i." % (REP, maxsteps)) + print("Could not find an integer relation. Norm bound: %s" % norm) + return None + +def findpoly(ctx, x, n=1, asc=True, **kwargs): + r""" + ``findpoly(x, n)`` returns the coefficients of an integer + polynomial `P` of degree at most `n` such that `P(x) \approx 0`. + If no polynomial having `x` as a root can be found, + :func:`~mpmath.findpoly` returns ``None``. + + :func:`~mpmath.findpoly` works by successively calling :func:`~mpmath.pslq` with + the vectors `[1, x]`, `[1, x, x^2]`, `[1, x, x^2, x^3]`, ..., + `[1, x, x^2, .., x^n]` as input. Keyword arguments given to + :func:`~mpmath.findpoly` are forwarded verbatim to :func:`~mpmath.pslq`. In + particular, you can specify a tolerance for `P(x)` with ``tol`` + and a maximum permitted coefficient size with ``maxcoeff``. + + For large values of `n`, it is recommended to run :func:`~mpmath.findpoly` + at high precision; preferably 50 digits or more. + + If *asc=False*, descending order of coefficients is used (the term + of largest degree - first). + + **Examples** + + By default (degree `n = 1`), :func:`~mpmath.findpoly` simply finds a linear + polynomial with a rational root:: + + >>> from mpmath import (mp, findpoly, nprint, polyval, polyroots, + ... sqrt, pi, phi, euler, findroot) + >>> mp.pretty = True + >>> findpoly(0.7) + [7, -10] + + The generated coefficient list is valid input to ``polyval`` and + ``polyroots``:: + + >>> nprint(polyval(findpoly(phi, 2), phi), 1) + -2.0e-16 + >>> for r in polyroots(findpoly(phi, 2)): + ... print(r) + ... + -0.618033988749895 + 1.61803398874989 + + Numbers of the form `m + n \sqrt p` for integers `(m, n, p)` are + solutions to quadratic equations. As we find here, `1+\sqrt 2` + is a root of the polynomial `x^2 - 2x - 1`:: + + >>> findpoly(1+sqrt(2), 2) + [-1, -2, 1] + >>> findroot(lambda x: x**2 - 2*x - 1, 1) + 2.4142135623731 + + Despite only containing square roots, the following number results + in a polynomial of degree 4:: + + >>> findpoly(sqrt(2)+sqrt(3), 4) + [1, 0, -10, 0, 1] + + In fact, `x^4 - 10x^2 + 1` is the *minimal polynomial* of + `r = \sqrt 2 + \sqrt 3`, meaning that a rational polynomial of + lower degree having `r` as a root does not exist. Given sufficient + precision, :func:`~mpmath.findpoly` will usually find the correct + minimal polynomial of a given algebraic number. + + **Non-algebraic numbers** + + If :func:`~mpmath.findpoly` fails to find a polynomial with given + coefficient size and tolerance constraints, that means no such + polynomial exists. + + We can verify that `\pi` is not an algebraic number of degree 3 with + coefficients less than 1000:: + + >>> findpoly(pi, 3) + >>> + + It is always possible to find an algebraic approximation of a number + using one (or several) of the following methods: + + 1. Increasing the permitted degree + 2. Allowing larger coefficients + 3. Reducing the tolerance + + One example of each method is shown below:: + + >>> findpoly(pi, 4) + [-298, -183, 863, -545, 95] + >>> findpoly(pi, 3, maxcoeff=10000) + [-457, -2658, -1734, 836] + >>> findpoly(pi, 3, tol=1e-7) + [-2, -29, 22, -4] + + It is unknown whether Euler's constant is transcendental (or even + irrational). We can use :func:`~mpmath.findpoly` to check that if is + an algebraic number, its minimal polynomial must have degree + at least 7 and a coefficient of magnitude at least 1000000:: + + >>> mp.dps = 200 + >>> findpoly(euler, 6, maxcoeff=10**6, tol=1e-100, maxsteps=1000) + >>> + + Note that the high precision and strict tolerance is necessary + for such high-degree runs, since otherwise unwanted low-accuracy + approximations will be detected. It may also be necessary to set + maxsteps high to prevent a premature exit (before the coefficient + bound has been reached). Running with ``verbose=True`` to get an + idea what is happening can be useful. + """ + x = ctx.mpf(x) + if n < 1: + raise ValueError("n cannot be less than 1") + if x == 0: + return [1, 0] + xs = [ctx.mpf(1)] + for i in range(1,n+1): + xs.append(x**i) + a = ctx.pslq(xs, **kwargs) + if a is not None: + return a if asc else a[::-1] + +def fracgcd(p, q): + x, y = p, q + while y: + x, y = y, x % y + if x != 1: + p //= x + q //= x + if q == 1: + return p + return p, q + +def pslqstring(r, constants): + q = r[0] + r = r[1:] + s = [] + for i in range(len(r)): + p = r[i] + if p: + z = fracgcd(-p,q) + cs = constants[i][1] + if cs == '1': + cs = '' + else: + cs = '*' + cs + if isinstance(z, int_types): + if z > 0: term = str(z) + cs + else: term = ("(%s)" % z) + cs + else: + term = ("(%s/%s)" % z) + cs + s.append(term) + s = ' + '.join(s) + if '+' in s or '*' in s: + s = '(' + s + ')' + return s or '0' + +def prodstring(r, constants): + q = r[0] + r = r[1:] + num = [] + den = [] + for i in range(len(r)): + p = r[i] + if p: + z = fracgcd(-p,q) + cs = constants[i][1] + if isinstance(z, int_types): + if abs(z) == 1: t = cs + else: t = '%s**%s' % (cs, abs(z)) + ([num,den][z<0]).append(t) + else: + t = '%s**(%s/%s)' % (cs, abs(z[0]), z[1]) + ([num,den][z[0]<0]).append(t) + num = '*'.join(num) + den = '*'.join(den) + if num and den: return "(%s)/(%s)" % (num, den) + if num: return num + if den: return "1/(%s)" % den + +def quadraticstring(ctx,t,a,b,c): + if c < 0: + a,b,c = -a,-b,-c + u1 = (-b+ctx.sqrt(b**2-4*a*c))/(2*c) + u2 = (-b-ctx.sqrt(b**2-4*a*c))/(2*c) + if abs(u1-t) < abs(u2-t): + if b: s = '((%s+sqrt(%s))/%s)' % (-b,b**2-4*a*c,2*c) + else: s = '(sqrt(%s)/%s)' % (-4*a*c,2*c) + else: + if b: s = '((%s-sqrt(%s))/%s)' % (-b,b**2-4*a*c,2*c) + else: s = '(-sqrt(%s)/%s)' % (-4*a*c,2*c) + return s + +# Transformation y = f(x,c), with inverse function x = f(y,c) +# The third entry indicates whether the transformation is +# redundant when c = 1 +transforms = [ + (lambda ctx,x,c: x*c, '$y/$c', 0), + (lambda ctx,x,c: x/c, '$c*$y', 1), + (lambda ctx,x,c: c/x, '$c/$y', 0), + (lambda ctx,x,c: (x*c)**2, 'sqrt($y)/$c', 0), + (lambda ctx,x,c: (x/c)**2, '$c*sqrt($y)', 1), + (lambda ctx,x,c: (c/x)**2, '$c/sqrt($y)', 0), + (lambda ctx,x,c: c*x**2, 'sqrt($y)/sqrt($c)', 1), + (lambda ctx,x,c: x**2/c, 'sqrt($c)*sqrt($y)', 1), + (lambda ctx,x,c: c/x**2, 'sqrt($c)/sqrt($y)', 1), + (lambda ctx,x,c: ctx.sqrt(x*c), '$y**2/$c', 0), + (lambda ctx,x,c: ctx.sqrt(x/c), '$c*$y**2', 1), + (lambda ctx,x,c: ctx.sqrt(c/x), '$c/$y**2', 0), + (lambda ctx,x,c: c*ctx.sqrt(x), '$y**2/$c**2', 1), + (lambda ctx,x,c: ctx.sqrt(x)/c, '$c**2*$y**2', 1), + (lambda ctx,x,c: c/ctx.sqrt(x), '$c**2/$y**2', 1), + (lambda ctx,x,c: ctx.exp(x*c), 'log($y)/$c', 0), + (lambda ctx,x,c: ctx.exp(x/c), '$c*log($y)', 1), + (lambda ctx,x,c: ctx.exp(c/x), '$c/log($y)', 0), + (lambda ctx,x,c: c*ctx.exp(x), 'log($y/$c)', 1), + (lambda ctx,x,c: ctx.exp(x)/c, 'log($c*$y)', 1), + (lambda ctx,x,c: c/ctx.exp(x), 'log($c/$y)', 0), + (lambda ctx,x,c: ctx.ln(x*c), 'exp($y)/$c', 0), + (lambda ctx,x,c: ctx.ln(x/c), '$c*exp($y)', 1), + (lambda ctx,x,c: ctx.ln(c/x), '$c/exp($y)', 0), + (lambda ctx,x,c: c*ctx.ln(x), 'exp($y/$c)', 1), + (lambda ctx,x,c: ctx.ln(x)/c, 'exp($c*$y)', 1), + (lambda ctx,x,c: c/ctx.ln(x), 'exp($c/$y)', 0), +] + +def identify(ctx, x, constants=[], tol=None, maxcoeff=1000, full=False, + verbose=False): + r""" + Given a real number `x`, ``identify(x)`` attempts to find an exact + formula for `x`. This formula is returned as a string. If no match + is found, ``None`` is returned. With ``full=True``, a list of + matching formulas is returned. + + As a simple example, :func:`~mpmath.identify` will find an algebraic + formula for the golden ratio:: + + >>> from mpmath import (mp, identify, phi, pi, e, sqrt, log, mpf, + ... exp, catalan) + >>> mp.pretty = True + >>> identify(phi) + '((1+sqrt(5))/2)' + + :func:`~mpmath.identify` can identify simple algebraic numbers and simple + combinations of given base constants, as well as certain basic + transformations thereof. More specifically, :func:`~mpmath.identify` + looks for the following: + + 1. Fractions + 2. Quadratic algebraic numbers + 3. Rational linear combinations of the base constants + 4. Any of the above after first transforming `x` into `f(x)` where + `f(x)` is `1/x`, `\sqrt x`, `x^2`, `\log x` or `\exp x`, either + directly or with `x` or `f(x)` multiplied or divided by one of + the base constants + 5. Products of fractional powers of the base constants and + small integers + + Base constants can be given as a list of strings representing mpmath + expressions (:func:`~mpmath.identify` will ``eval`` the strings to numerical + values and use the original strings for the output), or as a dict of + formula:value pairs. + + In order not to produce spurious results, :func:`~mpmath.identify` should + be used with high precision; preferably 50 digits or more. + + **Examples** + + Simple identifications can be performed safely at standard + precision. Here the default recognition of rational, algebraic, + and exp/log of algebraic numbers is demonstrated:: + + >>> identify(0.22222222222222222) + '(2/9)' + >>> identify(1.9662210973805663) + 'sqrt(((24+sqrt(48))/8))' + >>> identify(4.1132503787829275) + 'exp((sqrt(8)/2))' + >>> identify(0.881373587019543) + 'log(((2+sqrt(8))/2))' + + By default, :func:`~mpmath.identify` does not recognize `\pi`. At standard + precision it finds a not too useful approximation. At slightly + increased precision, this approximation is no longer accurate + enough and :func:`~mpmath.identify` more correctly returns ``None``:: + + >>> identify(pi) + '(2**(176/117)*3**(20/117)*5**(35/39))/(7**(92/117))' + >>> mp.dps = 30 + >>> identify(pi) + >>> + + Numbers such as `\pi`, and simple combinations of user-defined + constants, can be identified if they are provided explicitly:: + + >>> identify(3*pi-2*e, ['pi', 'e']) + '(3*pi + (-2)*e)' + + Here is an example using a dict of constants. Note that the + constants need not be "atomic"; :func:`~mpmath.identify` can just + as well express the given number in terms of expressions + given by formulas:: + + >>> identify(pi+e, {'a':pi+2, 'b':2*e}) + '((-2) + 1*a + (1/2)*b)' + + Next, we attempt some identifications with a set of base constants. + It is necessary to increase the precision a bit. + + >>> mp.dps = 50 + >>> base = ['sqrt(2)','pi','log(2)'] + >>> identify(0.25, base) + '(1/4)' + >>> identify(3*pi + 2*sqrt(2) + 5*log(2)/7, base) + '(2*sqrt(2) + 3*pi + (5/7)*log(2))' + >>> identify(exp(pi+2), base) + 'exp((2 + 1*pi))' + >>> identify(1/(3+sqrt(2)), base) + '((3/7) + (-1/7)*sqrt(2))' + >>> identify(sqrt(2)/(3*pi+4), base) + 'sqrt(2)/(4 + 3*pi)' + >>> identify(5**(mpf(1)/3)*pi*log(2)**2, base) + '5**(1/3)*pi*log(2)**2' + + An example of an erroneous solution being found when too low + precision is used:: + + >>> mp.dps = 15 + >>> identify(1/(3*pi-4*e+sqrt(8)), ['pi', 'e', 'sqrt(2)']) + '((11/25) + (-158/75)*pi + (76/75)*e + (44/15)*sqrt(2))' + >>> mp.dps = 50 + >>> identify(1/(3*pi-4*e+sqrt(8)), ['pi', 'e', 'sqrt(2)']) + '1/(3*pi + (-4)*e + 2*sqrt(2))' + + **Finding approximate solutions** + + The tolerance ``tol`` defaults to 3/4 of the working precision. + Lowering the tolerance is useful for finding approximate matches. + We can for example try to generate approximations for pi:: + + >>> mp.dps = 15 + >>> identify(pi, tol=1e-2) + '(22/7)' + >>> identify(pi, tol=1e-3) + '(355/113)' + >>> identify(pi, tol=1e-10) + '(5**(339/269))/(2**(64/269)*3**(13/269)*7**(92/269))' + + With ``full=True``, and by supplying a few base constants, + ``identify`` can generate almost endless lists of approximations + for any number (the output below has been truncated to show only + the first few):: + + >>> for p in identify(pi, ['e', 'catalan'], tol=1e-5, full=True): + ... print(p) + e/log((6 + (-4/3)*e)) + (3**3*5*e*catalan**2)/(2*7**2) + sqrt(((-13) + 1*e + 22*catalan)) + log(((-6) + 24*e + 4*catalan)/e) + exp(catalan*((-1/5) + (8/15)*e)) + catalan*(6 + (-6)*e + 15*catalan) + sqrt((5 + 26*e + (-3)*catalan))/e + e*sqrt(((-27) + 2*e + 25*catalan)) + log(((-1) + (-11)*e + 59*catalan)) + ((3/20) + (21/20)*e + (3/20)*catalan) + ... + + The numerical values are roughly as close to `\pi` as permitted by the + specified tolerance: + + >>> e/log(6-4*e/3) + 3.14157719846001 + >>> 135*e*catalan**2/98 + 3.14166950419369 + >>> sqrt(e-13+22*catalan) + 3.14158000062992 + >>> log(24*e-6+4*catalan)-1 + 3.14158791577159 + + **Symbolic processing** + + The output formula can be evaluated as a Python expression. + Note however that if fractions (like '2/3') are present in + the formula, Python's :func:`eval` may erroneously perform + integer division. Note also that the output is not necessarily + in the algebraically simplest form:: + + >>> identify(sqrt(2)) + '(sqrt(8)/2)' + + As a solution to both problems, consider using SymPy's + :func:`~sympy.core.sympify.sympify` to convert the formula into a symbolic expression. + SymPy can be used to pretty-print or further simplify the formula + symbolically:: + + >>> from sympy import sympify # doctest: +SKIP + >>> sympify(identify(sqrt(2))) # doctest: +SKIP + 2**(1/2) + + Sometimes :func:`~mpmath.identify` can simplify an expression further than + a symbolic algorithm:: + + >>> from sympy import simplify # doctest: +SKIP + >>> x = sympify('-1/(-3/2+(1/2)*5**(1/2))*(3/2-1/2*5**(1/2))**(1/2)') # doctest: +SKIP + >>> x # doctest: +SKIP + (3/2 - 5**(1/2)/2)**(-1/2) + >>> x = simplify(x) # doctest: +SKIP + >>> x # doctest: +SKIP + 2/(6 - 2*5**(1/2))**(1/2) + >>> mp.dps = 30 # doctest: +SKIP + >>> x = sympify(identify(x.evalf(30))) # doctest: +SKIP + >>> x # doctest: +SKIP + 1/2 + 5**(1/2)/2 + + (In fact, this functionality is available directly in SymPy as the + function :func:`~sympy.simplify.simplify.nsimplify`, which is essentially a wrapper for + :func:`~mpmath.identify`.) + + **Miscellaneous issues and limitations** + + The input `x` must be a real number. All base constants must be + positive real numbers and must not be rationals or rational linear + combinations of each other. + + The worst-case computation time grows quickly with the number of + base constants. Already with 3 or 4 base constants, + :func:`~mpmath.identify` may require several seconds to finish. To search + for relations among a large number of constants, you should + consider using :func:`~mpmath.pslq` directly. + + The extended transformations are applied to x, not the constants + separately. As a result, ``identify`` will for example be able to + recognize ``exp(2*pi+3)`` with ``pi`` given as a base constant, but + not ``2*exp(pi)+3``. It will be able to recognize the latter if + ``exp(pi)`` is given explicitly as a base constant. + + """ + + solutions = [] + + def addsolution(s): + if verbose: print("Found: ", s) + solutions.append(s) + + x = ctx.mpf(x) + + # Further along, x will be assumed positive + if x == 0: + if full: return ['0'] + else: return '0' + if x < 0: + sol = ctx.identify(-x, constants, tol, maxcoeff, full, verbose) + if sol is None: + return sol + if full: + return ["-(%s)"%s for s in sol] + else: + return "-(%s)" % sol + + if tol: + tol = ctx.mpf(tol) + else: + tol = ctx.eps**0.7 + M = maxcoeff + + if constants: + if isinstance(constants, dict): + constants = [(ctx.mpf(v), name) for (name, v) in sorted(constants.items())] + else: + namespace = dict((name, getattr(ctx,name)) for name in dir(ctx)) + constants = [(eval(p, namespace), p) for p in constants] + else: + constants = [] + + # We always want to find at least rational terms + if 1 not in [value for (name, value) in constants]: + constants = [(ctx.mpf(1), '1')] + constants + + # PSLQ with simple algebraic and functional transformations + for ft, ftn, red in transforms: + for c, cn in constants: + if red and cn == '1': + continue + t = ft(ctx,x,c) + # Prevent exponential transforms from wreaking havoc + if abs(t) > M**2 or abs(t) < tol: + continue + # Linear combination of base constants + r = ctx.pslq([t] + [a[0] for a in constants], tol, M) + s = None + if r is not None and max(abs(uw) for uw in r) <= M and r[0]: + s = pslqstring(r, constants) + # Quadratic algebraic numbers + else: + q = ctx.pslq([ctx.one, t, t**2], tol, M) + if q is not None and len(q) == 3 and q[2]: + aa, bb, cc = q + if max(abs(aa),abs(bb),abs(cc)) <= M: + s = quadraticstring(ctx,t,aa,bb,cc) + if s: + if cn == '1' and ('/$c' in ftn): + s = ftn.replace('$y', s).replace('/$c', '') + else: + s = ftn.replace('$y', s).replace('$c', cn) + addsolution(s) + if not full: return solutions[0] + + if verbose: + print(".") + + # Check for a direct multiplicative formula + if x != 1: + # Allow fractional powers of fractions + ilogs = [2,3,5,7] + # Watch out for existing fractional powers of fractions + logs = [] + for a, s in constants: + if not sum(bool(ctx.findpoly(ctx.ln(a)/ctx.ln(i),1)) for i in ilogs): + logs.append((ctx.ln(a), s)) + logs = [(ctx.ln(i),str(i)) for i in ilogs] + logs + r = ctx.pslq([ctx.ln(x)] + [a[0] for a in logs], tol, M) + if r is not None and max(abs(uw) for uw in r) <= M and r[0]: + addsolution(prodstring(r, logs)) + if not full: return solutions[0] + + if full: + return sorted(solutions, key=len) + else: + return None + +IdentificationMethods.pslq = pslq +IdentificationMethods.findpoly = findpoly +IdentificationMethods.identify = identify diff --git a/mpmath/libfp.py b/mpmath/libfp.py new file mode 100644 index 0000000..ebd596b --- /dev/null +++ b/mpmath/libfp.py @@ -0,0 +1,523 @@ +""" +This module complements the math and cmath builtin modules by providing +fast machine precision versions of some additional functions (ei, e1, ...) +and wrapping math/cmath functions so that they can be called with either +real or complex arguments. +""" + +import operator +import math +import cmath + +# Irrational (?) constants +pi = math.pi +euler = 0.57721566490153286061 + +logpi = 1.1447298858494001741 + +def _mathfun_real(f_real, f_complex): + def f(x, **kwargs): + try: + x = float(x) + return f_real(x) + except (TypeError, ValueError): + x = complex(x) + return f_complex(x) + f.__name__ = f_real.__name__ + return f + +def _mathfun(f_real, f_complex): + def f(x, **kwargs): + if type(x) is complex: + return f_complex(x) + try: + return f_real(float(x)) + except (TypeError, ValueError): + return f_complex(complex(x)) + f.__name__ = f_real.__name__ + return f + +def _mathfun_n(f_real, f_complex): + def f(*args, **kwargs): + try: + return f_real(*(float(x) for x in args)) + except (TypeError, ValueError): + return f_complex(*(complex(x) for x in args)) + f.__name__ = f_real.__name__ + return f + + +def _tan_complex(z): + if math.isinf(z.real): + if z.imag == 0: + return cmath.nan + 0j + elif math.isfinite(z.imag): + return cmath.nan + cmath.nanj + return cmath.tan(z) + +def _tanh_complex(z): + if math.isinf(z.imag): + if z.real == 0: + return cmath.nanj + elif math.isfinite(z.real): + return cmath.nan + cmath.nanj + return cmath.tanh(z) + + +pow = _mathfun_n(operator.pow, lambda x, y: complex(x)**y) +log = _mathfun_n(math.log, cmath.log) +sqrt = _mathfun(math.sqrt, cmath.sqrt) +exp = _mathfun_real(math.exp, cmath.exp) + +cos = _mathfun_real(math.cos, cmath.cos) +sin = _mathfun_real(math.sin, cmath.sin) +tan = _mathfun_real(math.tan, _tan_complex) + +acos = _mathfun(math.acos, cmath.acos) +asin = _mathfun(math.asin, cmath.asin) +atan = _mathfun_real(math.atan, cmath.atan) + +cosh = _mathfun_real(math.cosh, cmath.cosh) +sinh = _mathfun_real(math.sinh, cmath.sinh) +tanh = _mathfun_real(math.tanh, _tanh_complex) + +acosh = _mathfun(math.acosh, cmath.acosh) +asinh = _mathfun(math.asinh, cmath.asinh) +atanh = _mathfun_real(math.atanh, cmath.atanh) + +floor = _mathfun_real(math.floor, + lambda z: complex(math.floor(z.real), math.floor(z.imag))) +ceil = _mathfun_real(math.ceil, + lambda z: complex(math.ceil(z.real), math.ceil(z.imag))) + + +cos_sin = _mathfun_real(lambda x: (math.cos(x), math.sin(x)), + lambda z: (cmath.cos(z), cmath.sin(z))) + +cbrt = _mathfun(lambda x: x**(1./3), lambda z: z**(1./3)) + +def nthroot(x, n): + r = 1./n + try: + return float(x) ** r + except (ValueError, TypeError): + return complex(x) ** r + +def _sinpi_real(x): + if x < 0: + return -_sinpi_real(-x) + n, r = divmod(x, 0.5) + r *= pi + n %= 4 + if n == 0: return math.sin(r) + if n == 1: return math.cos(r) + if n == 2: return -math.sin(r) + if n == 3: return -math.cos(r) + +def _cospi_real(x): + if x < 0: + x = -x + n, r = divmod(x, 0.5) + r *= pi + n %= 4 + if n == 0: return math.cos(r) + if n == 1: return -math.sin(r) + if n == 2: return -math.cos(r) + if n == 3: return math.sin(r) + +def _sinpi_complex(z): + if z.real < 0: + return -_sinpi_complex(-z) + n, r = divmod(z.real, 0.5) + z = pi*complex(r, z.imag) + n %= 4 + if n == 0: return cmath.sin(z) + if n == 1: return cmath.cos(z) + if n == 2: return -cmath.sin(z) + if n == 3: return -cmath.cos(z) + +def _cospi_complex(z): + if z.real < 0: + z = -z + n, r = divmod(z.real, 0.5) + z = pi*complex(r, z.imag) + n %= 4 + if n == 0: return cmath.cos(z) + if n == 1: return -cmath.sin(z) + if n == 2: return -cmath.cos(z) + if n == 3: return cmath.sin(z) + +cospi = _mathfun_real(_cospi_real, _cospi_complex) +sinpi = _mathfun_real(_sinpi_real, _sinpi_complex) + +def cotpi(x): + try: + return cospi(x) / sinpi(x) + except OverflowError: + if complex(x).imag > 10: + return -1j + if complex(x).imag < 10: + return 1j + raise + +INF = math.inf + +_exact_gamma = (INF, 1.0, 1.0, 2.0, 6.0, 24.0, 120.0, 720.0, 5040.0, 40320.0, + 362880.0, 3628800.0, 39916800.0, 479001600.0, 6227020800.0, 87178291200.0, + 1307674368000.0, 20922789888000.0, 355687428096000.0, 6402373705728000.0, + 121645100408832000.0, 2432902008176640000.0) + +_max_exact_gamma = len(_exact_gamma)-1 + +# Lanczos coefficients used by the GNU Scientific Library +_lanczos_g = 7 +_lanczos_p = (0.99999999999980993, 676.5203681218851, -1259.1392167224028, + 771.32342877765313, -176.61502916214059, 12.507343278686905, + -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7) + +def _gamma_complex(x): + if not x.imag: + if x.real == -INF: + return math.nan + return complex(math.gamma(x.real)) + if x.real < 0.5: + # TODO: sinpi + return pi / (_sinpi_complex(x)*_gamma_complex(1-x)) + else: + x -= 1.0 + r = _lanczos_p[0] + for i in range(1, _lanczos_g+2): + r += _lanczos_p[i]/(x+i) + t = x + _lanczos_g + 0.5 + return 2.506628274631000502417 * t**(x+0.5) * cmath.exp(-t) * r + +gamma = _mathfun_real(math.gamma, _gamma_complex) + +def rgamma(x): + try: + return 1./gamma(x) + except ZeroDivisionError: + return x*0.0 + +def factorial(x): + return gamma(x+1.0) + +# XXX: broken for negatives +def loggamma(x): + if type(x) not in (float, complex): + try: + x = float(x) + except (ValueError, TypeError): + x = complex(x) + # Reflection formula + # http://functions.wolfram.com/GammaBetaErf/LogGamma/16/01/01/0003/ + if x.real < 0.0: + if abs(x) < 0.5: + v = log(gamma(x)) + if x.imag == 0: + v = v.conjugate() + return v + z = 1-x + re = z.real + im = z.imag + refloor = floor(re) + if im == 0.0: + imsign = 0 + elif im < 0.0: + imsign = -1 + else: + imsign = 1 + return (-pi*1j)*abs(refloor)*(1-abs(imsign)) + logpi - \ + log(sinpi(z-refloor)) - loggamma(z) + 1j*pi*refloor*imsign + if x == 1.0 or x == 2.0: + return x*0 + p = 0. + while abs(x) < 11: + p -= log(x) + x += 1.0 + s = 0.918938533204672742 + (x-0.5)*log(x) - x + r = 1./x + r2 = r*r + s += 0.083333333333333333333*r; r *= r2 + s += -0.0027777777777777777778*r; r *= r2 + s += 0.00079365079365079365079*r; r *= r2 + s += -0.0005952380952380952381*r; r *= r2 + s += 0.00084175084175084175084*r; r *= r2 + s += -0.0019175269175269175269*r; r *= r2 + s += 0.0064102564102564102564*r; r *= r2 + s += -0.02955065359477124183*r + return s + p + +_psi_coeff = [ +0.083333333333333333333, +-0.0083333333333333333333, +0.003968253968253968254, +-0.0041666666666666666667, +0.0075757575757575757576, +-0.021092796092796092796, +0.083333333333333333333, +-0.44325980392156862745, +3.0539543302701197438, +-26.456212121212121212] + +def _digamma_real(x): + _intx = int(x) + if _intx == x: + if _intx <= 0: + raise ZeroDivisionError("polygamma pole") + if x < 0.5: + x = 1.0-x + s = pi*cotpi(x) + else: + s = 0.0 + while x < 10.0: + s -= 1.0/x + x += 1.0 + x2 = x**-2 + t = x2 + for c in _psi_coeff: + s -= c*t + if t < 1e-20: + break + t *= x2 + return s + math.log(x) - 0.5/x + +def _digamma_complex(x): + if not x.imag: + return complex(_digamma_real(x.real)) + if x.real < 0.5: + x = 1.0-x + s = pi*cotpi(x) + else: + s = 0.0 + while abs(x) < 10.0: + s -= 1.0/x + x += 1.0 + x2 = x**-2 + t = x2 + for c in _psi_coeff: + s -= c*t + if abs(t) < 1e-20: + break + t *= x2 + return s + cmath.log(x) - 0.5/x + +digamma = _mathfun_real(_digamma_real, _digamma_complex) + +def _polyval(coeffs, x): + p = coeffs[0] + for c in coeffs[1:]: + p = c + x*p + return p + +gauss42 = [ +(0.99839961899006235, 0.0041059986046490839), +(-0.99839961899006235, 0.0041059986046490839), +(0.9915772883408609, 0.009536220301748501), +(-0.9915772883408609,0.009536220301748501), +(0.97934250806374812, 0.014922443697357493), +(-0.97934250806374812, 0.014922443697357493), +(0.96175936533820439,0.020227869569052644), +(-0.96175936533820439, 0.020227869569052644), +(0.93892355735498811, 0.025422959526113047), +(-0.93892355735498811,0.025422959526113047), +(0.91095972490412735, 0.030479240699603467), +(-0.91095972490412735, 0.030479240699603467), +(0.87802056981217269,0.03536907109759211), +(-0.87802056981217269, 0.03536907109759211), +(0.8402859832618168, 0.040065735180692258), +(-0.8402859832618168,0.040065735180692258), +(0.7979620532554873, 0.044543577771965874), +(-0.7979620532554873, 0.044543577771965874), +(0.75127993568948048,0.048778140792803244), +(-0.75127993568948048, 0.048778140792803244), +(0.70049459055617114, 0.052746295699174064), +(-0.70049459055617114,0.052746295699174064), +(0.64588338886924779, 0.056426369358018376), +(-0.64588338886924779, 0.056426369358018376), +(0.58774459748510932, 0.059798262227586649), +(-0.58774459748510932, 0.059798262227586649), +(0.5263957499311922, 0.062843558045002565), +(-0.5263957499311922, 0.062843558045002565), +(0.46217191207042191, 0.065545624364908975), +(-0.46217191207042191, 0.065545624364908975), +(0.39542385204297503, 0.067889703376521934), +(-0.39542385204297503, 0.067889703376521934), +(0.32651612446541151, 0.069862992492594159), +(-0.32651612446541151, 0.069862992492594159), +(0.25582507934287907, 0.071454714265170971), +(-0.25582507934287907, 0.071454714265170971), +(0.18373680656485453, 0.072656175243804091), +(-0.18373680656485453, 0.072656175243804091), +(0.11064502720851986, 0.073460813453467527), +(-0.11064502720851986, 0.073460813453467527), +(0.036948943165351772, 0.073864234232172879), +(-0.036948943165351772, 0.073864234232172879)] + +EI_ASYMP_CONVERGENCE_RADIUS = 40.0 + +def ei_asymp(z, _e1=False): + r = 1./z + k = 1 + t = 1.0*k*r + s = 1.0 + t + while abs(t) >= 1e-16: + k += 1 + t *= k*r + s += t + v = s*exp(z)/z + if _e1: + if type(z) is complex: + zreal = z.real + zimag = z.imag + else: + zreal = z + zimag = 0.0 + if zimag == 0.0 and zreal > 0.0: + v += pi*1j + else: + if type(z) is complex: + if z.imag > 0: + v += pi*1j + if z.imag < 0: + v -= pi*1j + return v + +def ei_taylor(z, _e1=False): + s = t = z + k = 2 + while 1: + t = t*z/k + term = t/k + if abs(term) < 1e-17: + break + s += term + k += 1 + s += euler + if _e1: + s += log(-z) + else: + if type(z) is float or z.imag == 0.0: + s += math.log(abs(z)) + else: + s += cmath.log(z) + return s + +def ei(z, _e1=False): + try: + z = float(z) + typez = float + except (TypeError, ValueError): + z = complex(z) + typez = complex + if not z: + return -INF + absz = abs(z) + if absz > EI_ASYMP_CONVERGENCE_RADIUS: + return ei_asymp(z, _e1) + elif absz <= 2.0 or (typez is float and z > 0.0): + return ei_taylor(z, _e1) + # Integrate, starting from whichever is smaller of a Taylor + # series value or an asymptotic series value + if typez is complex and z.real > 0.0: + zref = z / absz + ref = ei_taylor(zref, _e1) + else: + zref = EI_ASYMP_CONVERGENCE_RADIUS * z / absz + ref = ei_asymp(zref, _e1) + C = (zref-z)*0.5 + D = (zref+z)*0.5 + s = 0.0 + if type(z) is complex: + _exp = cmath.exp + else: + _exp = math.exp + for x,w in gauss42: + t = C*x+D + s += w*_exp(t)/t + ref -= C*s + return ref + +def e1(z): + try: + z = float(z) + typez = float + except (TypeError, ValueError): + z = complex(z) + typez = complex + # hack to get consistent signs if the imaginary part if 0 + # and signed + if typez is complex and not z.imag: + z = complex(z.real, 0.0) + # end hack + return -ei(-z, _e1=True) + +_zeta_int = [ +-0.5, +0.0, +1.6449340668482264365,1.2020569031595942854,1.0823232337111381915, +1.0369277551433699263,1.0173430619844491397,1.0083492773819228268, +1.0040773561979443394,1.0020083928260822144,1.0009945751278180853, +1.0004941886041194646,1.0002460865533080483,1.0001227133475784891, +1.0000612481350587048,1.0000305882363070205,1.0000152822594086519, +1.0000076371976378998,1.0000038172932649998,1.0000019082127165539, +1.0000009539620338728,1.0000004769329867878,1.0000002384505027277, +1.0000001192199259653,1.0000000596081890513,1.0000000298035035147, +1.0000000149015548284] + +_zeta_P = [-3.50000000087575873, -0.701274355654678147, +-0.0672313458590012612, -0.00398731457954257841, +-0.000160948723019303141, -4.67633010038383371e-6, +-1.02078104417700585e-7, -1.68030037095896287e-9, +-1.85231868742346722e-11][::-1] + +_zeta_Q = [1.00000000000000000, -0.936552848762465319, +-0.0588835413263763741, -0.00441498861482948666, +-0.000143416758067432622, -5.10691659585090782e-6, +-9.58813053268913799e-8, -1.72963791443181972e-9, +-1.83527919681474132e-11][::-1] + +_zeta_1 = [3.03768838606128127e-10, -1.21924525236601262e-8, +2.01201845887608893e-7, -1.53917240683468381e-6, +-5.09890411005967954e-7, 0.000122464707271619326, +-0.000905721539353130232, -0.00239315326074843037, +0.084239750013159168, 0.418938517907442414, 0.500000001921884009] + +_zeta_0 = [-3.46092485016748794e-10, -6.42610089468292485e-9, +1.76409071536679773e-7, -1.47141263991560698e-6, -6.38880222546167613e-7, +0.000122641099800668209, -0.000905894913516772796, -0.00239303348507992713, +0.0842396947501199816, 0.418938533204660256, 0.500000000000000052] + +def zeta(s): + """ + Riemann zeta function, real argument + """ + if not isinstance(s, (float, int)): + try: + s = float(s) + except (ValueError, TypeError): + try: + s = complex(s) + if not s.imag: + return complex(zeta(s.real)) + except (ValueError, TypeError): + pass + raise NotImplementedError + if s == 1: + raise ValueError("zeta(1) pole") + if s >= 27: + return 1.0 + 2.0**(-s) + 3.0**(-s) + n = int(s) + if n == s: + if n >= 0: + return _zeta_int[n] + if not (n % 2): + return 0.0 + if s <= 0.0: + return 2.**s*pi**(s-1)*_sinpi_real(0.5*s)*math.gamma(1-s)*zeta(1-s) + if s <= 2.0: + if s <= 1.0: + return _polyval(_zeta_0,s)/(s-1) + return _polyval(_zeta_1,s)/(s-1) + z = _polyval(_zeta_P,s) / _polyval(_zeta_Q,s) + return 1.0 + 2.0**(-s) + 3.0**(-s) + 4.0**(-s)*z diff --git a/mpmath/libmp/__init__.py b/mpmath/libmp/__init__.py new file mode 100644 index 0000000..cdd5073 --- /dev/null +++ b/mpmath/libmp/__init__.py @@ -0,0 +1,18 @@ +from .backend import BACKEND, MPZ, MPZ_ONE, int_types +from .gammazeta import catalan_fixed, euler_fixed, mpf_bernoulli +from .libelefun import (mpf_atan, mpf_atan2, mpf_cos, mpf_cosh_sinh, mpf_e, + mpf_exp, mpf_log, mpf_pi, mpf_pow, mpf_sin, mpf_tan, + phi_fixed) +from .libhyper import NoConvergence +from .libintmath import giant_steps, ifac, ifib, isqrt, sqrtrem +from .libmpc import (mpc_abs, mpc_exp, mpc_pow, mpc_pow_int, mpc_pow_mpf, + mpc_sqrt) +from .libmpf import (ComplexResult, dps_to_prec, fhalf, finf, fnan, fninf, + fnone, fone, from_float, from_int, from_man_exp, + from_rational, from_str, fzero, mpf_abs, mpf_add, + mpf_ceil, mpf_cmp, mpf_div, mpf_eq, mpf_floor, mpf_ge, + mpf_gt, mpf_le, mpf_lt, mpf_mod, mpf_mul, mpf_neg, + mpf_pow_int, mpf_shift, mpf_sign, mpf_sqrt, mpf_sub, + normalize, prec_to_dps, repr_dps, round_ceiling, + round_down, round_floor, round_nearest, round_up, + to_float, to_int, to_man_exp, to_rational, to_str) diff --git a/mpmath/libmp/backend.py b/mpmath/libmp/backend.py new file mode 100644 index 0000000..e0b998f --- /dev/null +++ b/mpmath/libmp/backend.py @@ -0,0 +1,46 @@ +import fractions +import os + + +#----------------------------------------------------------------------------# +# Support GMPY for high-speed large integer arithmetic. # +# # +# To allow an external module to handle arithmetic, we need to make sure # +# that all high-precision variables are declared of the correct type. MPZ # +# is the constructor for the high-precision type. It defaults to Python's # +# long type but can be assinged another type, typically gmpy.mpz. # +# # +# MPZ must be used for the mantissa component of an mpf and must be used # +# for internal fixed-point operations. # +# # +# Side-effects: # +# * "is" cannot be used to test for special values. Must use "==". # +#----------------------------------------------------------------------------# + +gmpy = None +BACKEND = 'python' +MPZ = int +MPQ = fractions.Fraction + +if 'MPMATH_NOGMPY' not in os.environ: + try: + import gmpy2 as gmpy + BACKEND = 'gmpy' + MPQ = gmpy.mpq + except ImportError: + try: + import gmp as gmpy + BACKEND = 'gmp' + except ImportError: + pass + + if gmpy: + MPZ = gmpy.mpz + +MPZ_ZERO = MPZ(0) +MPZ_ONE = MPZ(1) +MPZ_TWO = MPZ(2) +MPZ_THREE = MPZ(3) +MPZ_FIVE = MPZ(5) + +int_types = (int,) if BACKEND == 'python' else (int, MPZ) diff --git a/mpmath/libmp/gammazeta.py b/mpmath/libmp/gammazeta.py new file mode 100644 index 0000000..278f054 --- /dev/null +++ b/mpmath/libmp/gammazeta.py @@ -0,0 +1,2153 @@ +""" +----------------------------------------------------------------------- +This module implements gamma- and zeta-related functions: + +* Bernoulli numbers +* Factorials +* The gamma function +* Polygamma functions +* Harmonic numbers +* The Riemann zeta function +* Constants related to these functions + +----------------------------------------------------------------------- +""" + +import math +import sys +import threading + +from .backend import MPZ, MPZ_ONE, MPZ_THREE, MPZ_ZERO +from .libelefun import (constant_memo, cos_sin_fixed, def_mpf_constant, + exp_fixed, ln2_fixed, ln_sqrt2pi_fixed, log_int_fixed, + mpf_cos_sin_pi, mpf_exp, mpf_ln, mpf_ln2, mpf_pi, + mpf_pow, mpf_sin_pi, mpf_sqrtpi, pi_fixed, + sqrtpi_fixed) +from .libintmath import ifac, ifac2, isqrt_fast, list_primes, lshift, moebius +from .libmpc import (mpc_abs, mpc_add, mpc_add_mpf, mpc_cos_pi, mpc_div, + mpc_div_mpf, mpc_exp, mpc_half, mpc_ln, mpc_mpf_div, + mpc_mul, mpc_mul_int, mpc_mul_mpf, mpc_neg, mpc_one, + mpc_pos, mpc_pow, mpc_pow_int, mpc_reciprocal, mpc_shift, + mpc_sin_pi, mpc_square, mpc_sub, mpc_sub_mpf, mpc_two, + mpc_zero) +from .libmpf import (ComplexResult, fhalf, finf, fnan, fninf, fone, from_int, + from_man_exp, from_rational, ftwo, fzero, mpf_abs, + mpf_add, mpf_div, mpf_floor, mpf_gt, mpf_le, mpf_lt, + mpf_mul, mpf_mul_int, mpf_neg, mpf_perturb, mpf_pos, + mpf_pow_int, mpf_rdiv_int, mpf_shift, mpf_sign, mpf_sub, + negative_rnd, round_down, round_nearest, to_fixed, + to_float, to_int) + + +local = threading.local() + + +# Catalan's constant is computed using Lupas's rapidly convergent series +# (listed on http://mathworld.wolfram.com/CatalansConstant.html) +# oo +# ___ n-1 8n 2 3 2 +# 1 \ (-1) 2 (40n - 24n + 3) [(2n)!] (n!) +# K = --- ) ----------------------------------------- +# 64 /___ 3 2 +# n (2n-1) [(4n)!] +# n = 1 + +@constant_memo +def catalan_fixed(prec): + prec = prec + 20 + a = one = MPZ_ONE << prec + s, t, n = 0, 1, 1 + while t: + a *= 32 * n**3 * (2*n-1) + a //= (3-16*n+16*n**2)**2 + t = a * (-1)**(n-1) * (40*n**2-24*n+3) // (n**3 * (2*n-1)) + s += t + n += 1 + return s >> (20 + 6) + +# Khinchin's constant is relatively difficult to compute. Here +# we use the rational zeta series + +# oo 2*n-1 +# ___ ___ +# \ ` zeta(2*n)-1 \ ` (-1)^(k+1) +# log(K)*log(2) = ) ------------ ) ---------- +# /___. n /___. k +# n = 1 k = 1 + +# which adds half a digit per term. The essential trick for achieving +# reasonable efficiency is to recycle both the values of the zeta +# function (essentially Bernoulli numbers) and the partial terms of +# the inner sum. + +# An alternative might be to use K = 2*exp[1/log(2) X] where + +# / 1 1 [ pi*x*(1-x^2) ] +# X = | ------ log [ ------------ ]. +# / 0 x(1+x) [ sin(pi*x) ] + +# and integrate numerically. In practice, this seems to be slightly +# slower than the zeta series at high precision. + +@constant_memo +def khinchin_fixed(prec): + wp = int(prec + prec**0.5 + 15) + s = MPZ_ZERO + fac = from_int(4) + t = ONE = MPZ_ONE << wp + pi = mpf_pi(wp) + pipow = twopi2 = mpf_shift(mpf_mul(pi, pi, wp), 2) + n = 1 + while 1: + zeta2n = mpf_abs(mpf_bernoulli(2*n, wp)) + zeta2n = mpf_mul(zeta2n, pipow, wp) + zeta2n = mpf_div(zeta2n, fac, wp) + zeta2n = to_fixed(zeta2n, wp) + term = (((zeta2n - ONE) * t) // n) >> wp + if term < 100: + break + #if not n % 10: + # print n, math.log(int(abs(term))) + s += term + t += ONE//(2*n+1) - ONE//(2*n) + n += 1 + fac = mpf_mul_int(fac, (2*n)*(2*n-1), wp) + pipow = mpf_mul(pipow, twopi2, wp) + s = (s << wp) // ln2_fixed(wp) + K = mpf_exp(from_man_exp(s, -wp), wp) + K = to_fixed(K, prec) + return K + + +# Glaisher's constant is defined as A = exp(1/2 - zeta'(-1)). +# One way to compute it would be to perform direct numerical +# differentiation, but computing arbitrary Riemann zeta function +# values at high precision is expensive. We instead use the formula + +# A = exp((6 (-zeta'(2))/pi^2 + log 2 pi + gamma)/12) + +# and compute zeta'(2) from the series representation + +# oo +# ___ +# \ log k +# -zeta'(2) = ) ----- +# /___ 2 +# k +# k = 2 + +# This series converges exceptionally slowly, but can be accelerated +# using Euler-Maclaurin formula. The important insight is that the +# E-M integral can be done in closed form and that the high order +# are given by + +# n / \ +# d | log x | a + b log x +# --- | ----- | = ----------- +# n | 2 | 2 + n +# dx \ x / x + +# where a and b are integers given by a simple recurrence. Note +# that just one logarithm is needed. However, lots of integer +# logarithms are required for the initial summation. + +# This algorithm could possibly be turned into a faster algorithm +# for general evaluation of zeta(s) or zeta'(s); this should be +# looked into. + +@constant_memo +def glaisher_fixed(prec): + wp = prec + 30 + # Number of direct terms to sum before applying the Euler-Maclaurin + # formula to the tail. TODO: choose more intelligently + N = int(0.33*prec + 5) + ONE = MPZ_ONE << wp + # Euler-Maclaurin, step 1: sum log(k)/k**2 for k from 2 to N-1 + s = MPZ_ZERO + for k in range(2, N): + #print k, N + s += log_int_fixed(k, wp) // k**2 + logN = log_int_fixed(N, wp) + #logN = to_fixed(mpf_ln(from_int(N), wp+20), wp) + # E-M step 2: integral of log(x)/x**2 from N to inf + s += (ONE + logN) // N + # E-M step 3: endpoint correction term f(N)/2 + s += logN // (N**2 * 2) + # E-M step 4: the series of derivatives + pN = N**3 + a = 1 + b = -2 + j = 3 + fac = from_int(2) + k = 1 + while 1: + # D(2*k-1) * B(2*k) / fac(2*k) [D(n) = nth derivative] + D = ((a << wp) + b*logN) // pN + D = from_man_exp(D, -wp) + B = mpf_bernoulli(2*k, wp) + term = mpf_mul(B, D, wp) + term = mpf_div(term, fac, wp) + term = to_fixed(term, wp) + if abs(term) < 100: + break + #if not k % 10: + # print k, math.log(int(abs(term)), 10) + s -= term + # Advance derivative twice + a, b, pN, j = b-a*j, -j*b, pN*N, j+1 + a, b, pN, j = b-a*j, -j*b, pN*N, j+1 + k += 1 + fac = mpf_mul_int(fac, (2*k)*(2*k-1), wp) + # A = exp((6*s/pi**2 + log(2*pi) + euler)/12) + pi = pi_fixed(wp) + s *= 6 + s = (s << wp) // (pi**2 >> wp) + s += euler_fixed(wp) + s += to_fixed(mpf_ln(from_man_exp(2*pi, -wp), wp), wp) + s //= 12 + A = mpf_exp(from_man_exp(s, -wp), wp) + return to_fixed(A, prec) + +# Apery's constant can be computed using the very rapidly convergent +# series +# oo +# ___ 2 10 +# \ n 205 n + 250 n + 77 (n!) +# zeta(3) = ) (-1) ------------------- ---------- +# /___ 64 5 +# n = 0 ((2n+1)!) + +@constant_memo +def apery_fixed(prec): + prec += 20 + d = MPZ_ONE << prec + term = MPZ(77) << prec + n = 1 + s = MPZ_ZERO + while term: + s += term + d *= (n**10) + d //= (((2*n+1)**5) * (2*n)**5) + term = (-1)**n * (205*(n**2) + 250*n + 77) * d + n += 1 + return s >> (20 + 6) + +""" +Euler's constant (gamma) is computed using the Brent-McMillan formula, +gamma ~= I(n)/J(n) - log(n), where + + I(n) = sum_{k=0,1,2,...} (n**k / k!)**2 * H(k) + J(n) = sum_{k=0,1,2,...} (n**k / k!)**2 + H(k) = 1 + 1/2 + 1/3 + ... + 1/k + +The error is bounded by O(exp(-4n)). Choosing n to be a power +of two, 2**p, the logarithm becomes particularly easy to calculate.[1] + +We use the formulation of Algorithm 3.9 in [2] to make the summation +more efficient. + +References: +[1] [Gourdon]_ + +[2] [BorweinBailey]_ +""" + +@constant_memo +def euler_fixed(prec): + extra = 30 + prec += extra + # choose p such that exp(-4*(2**p)) < 2**-n + p = int(math.log((prec/4) * math.log(2), 2)) + 1 + n = 2**p + A = U = -p*ln2_fixed(prec) + B = V = MPZ_ONE << prec + k = 1 + while 1: + B = B*n**2//k**2 + A = (A*n**2//k + B)//k + U += A + V += B + if max(abs(A), abs(B)) < 100: + break + k += 1 + return (U<<(prec-extra))//V + +# Use zeta accelerated formulas for the Mertens and twin +# prime constants; see +# http://mathworld.wolfram.com/MertensConstant.html +# http://mathworld.wolfram.com/TwinPrimesConstant.html + +@constant_memo +def mertens_fixed(prec): + wp = prec + 20 + m = 2 + s = mpf_euler(wp) + while 1: + t = mpf_zeta_int(m, wp) + if t == fone: + break + t = mpf_ln(t, wp) + t = mpf_mul_int(t, moebius(m), wp) + t = mpf_div(t, from_int(m), wp) + s = mpf_add(s, t) + m += 1 + return to_fixed(s, prec) + +@constant_memo +def twinprime_fixed(prec): + def I(n): + return sum(moebius(d)<<(n//d) for d in range(1,n+1) if not n%d)//n + wp = 2*prec + 30 + res = fone + primes = [from_rational(1,p,wp) for p in [2,3,5,7]] + ppowers = [mpf_mul(p,p,wp) for p in primes] + n = 2 + while 1: + a = mpf_zeta_int(n, wp) + for i in range(4): + a = mpf_mul(a, mpf_sub(fone, ppowers[i]), wp) + ppowers[i] = mpf_mul(ppowers[i], primes[i], wp) + a = mpf_pow_int(a, -I(n), wp) + if mpf_pos(a, prec+10, 'n') == fone: + break + #from libmpf import to_str + #print n, to_str(mpf_sub(fone, a), 6) + res = mpf_mul(res, a, wp) + n += 1 + res = mpf_mul(res, from_int(3*15*35), wp) + res = mpf_div(res, from_int(4*16*36), wp) + return to_fixed(res, prec) + + +mpf_euler = def_mpf_constant(euler_fixed) +mpf_apery = def_mpf_constant(apery_fixed) +mpf_khinchin = def_mpf_constant(khinchin_fixed) +mpf_glaisher = def_mpf_constant(glaisher_fixed) +mpf_catalan = def_mpf_constant(catalan_fixed) +mpf_mertens = def_mpf_constant(mertens_fixed) +mpf_twinprime = def_mpf_constant(twinprime_fixed) + + +#-----------------------------------------------------------------------# +# # +# Bernoulli numbers # +# # +#-----------------------------------------------------------------------# + +MAX_BERNOULLI_CACHE = 3000 + + +r""" +Small Bernoulli numbers and factorials are used in numerous summations, +so it is critical for speed that sequential computation is fast and that +values are cached up to a fairly high threshold. + +On the other hand, we also want to support fast computation of isolated +large numbers. Currently, no such acceleration is provided for integer +factorials (though it is for large floating-point factorials, which are +computed via gamma if the precision is low enough). + +For sequential computation of Bernoulli numbers, we use Ramanujan's formula + + / n + 3 \ + B = (A(n) - S(n)) / | | + n \ n / + +where A(n) = (n+3)/3 when n = 0 or 2 (mod 6), A(n) = -(n+3)/6 +when n = 4 (mod 6), and + + [n/6] + ___ + \ / n + 3 \ + S(n) = ) | | * B + /___ \ n - 6*k / n-6*k + k = 1 + +For isolated large Bernoulli numbers, we use the Riemann zeta function +to calculate a numerical value for B_n. The von Staudt-Clausen theorem +can then be used to optionally find the exact value of the +numerator and denominator. +""" + +bernoulli_cache = local.bernoulli_cache = {} +f3 = from_int(3) +f6 = from_int(6) + +def bernoulli_size(n): + """Accurately estimate the size of B_n (even n > 2 only)""" + lgn = math.log(n,2) + return int(2.326 + 0.5*lgn + n*(lgn - 4.094)) + +BERNOULLI_PREC_CUTOFF = bernoulli_size(MAX_BERNOULLI_CACHE) + +def mpf_bernoulli(n, prec, rnd=round_down, plus=False): + """Computation of Bernoulli numbers (numerically)""" + if n < 2: + if n < 0: + raise ValueError("Bernoulli numbers only defined for n >= 0") + if n == 0: + return fone + if n == 1: + return fhalf if plus else mpf_neg(fhalf) + # For odd n > 1, the Bernoulli numbers are zero + if n & 1: + return fzero + # If precision is extremely high, we can save time by computing + # the Bernoulli number at a lower precision that is sufficient to + # obtain the exact fraction, round to the exact fraction, and + # convert the fraction back to an mpf value at the original precision + if prec > BERNOULLI_PREC_CUTOFF and prec > bernoulli_size(n)*1.1 + 1000: + p, q = bernfrac(n) + return from_rational(p, q, prec, rnd) + if n > MAX_BERNOULLI_CACHE: + return mpf_bernoulli_huge(n, prec, rnd) + wp = prec + 30 + # Reuse nearby precisions + wp += 32 - (prec & 31) + cached = bernoulli_cache.get(wp) + if cached: + numbers, state = cached + if n in numbers: + return mpf_pos(numbers[n], prec, rnd) + m, bin, bin1 = state + if n - m > 10: + return mpf_bernoulli_huge(n, prec, rnd) + else: + if n > 10: + return mpf_bernoulli_huge(n, prec, rnd) + numbers = {0:fone} + m, bin, bin1 = state = [2, MPZ(10), MPZ_ONE] + bernoulli_cache[wp] = (numbers, state) + while m <= n: + #print m + case = m % 6 + # Accurately estimate size of B_m so we can use + # fixed point math without using too much precision + szbm = bernoulli_size(m) + s = MPZ(0) + sexp = max(0, szbm) - wp + if m < 6: + a = MPZ_ZERO + else: + a = bin1 + for j in range(1, m//6+1): + usign, uman, uexp, ubc = u = numbers[m-6*j] + if usign: + uman = -uman + s += lshift(a*uman, uexp-sexp) + # Update inner binomial coefficient + j6 = 6*j + a *= ((m-5-j6)*(m-4-j6)*(m-3-j6)*(m-2-j6)*(m-1-j6)*(m-j6)) + a //= ((4+j6)*(5+j6)*(6+j6)*(7+j6)*(8+j6)*(9+j6)) + if case == 0: b = mpf_rdiv_int(m+3, f3, wp) + if case == 2: b = mpf_rdiv_int(m+3, f3, wp) + if case == 4: b = mpf_rdiv_int(-m-3, f6, wp) + s = from_man_exp(s, sexp, wp) + b = mpf_div(mpf_sub(b, s, wp), from_int(bin), wp) + numbers[m] = b + m += 2 + # Update outer binomial coefficient + bin = bin * ((m+2)*(m+3)) // (m*(m-1)) + if m > 6: + bin1 = bin1 * ((2+m)*(3+m)) // ((m-7)*(m-6)) + state[:] = [m, bin, bin1] + return mpf_pos(numbers[n], prec, rnd) + +def mpf_bernoulli_huge(n, prec, rnd=round_down): + wp = prec + 10 + piprec = wp + int(math.log(n,2)) + v = mpf_gamma_int(n+1, wp) + v = mpf_mul(v, mpf_zeta_int(n, wp), wp) + v = mpf_mul(v, mpf_pow_int(mpf_pi(piprec), -n, wp)) + v = mpf_shift(v, 1-n) + if not n & 3: + v = mpf_neg(v) + return mpf_pos(v, prec, rnd) + +def bernfrac(n, plus=False): + r""" + Returns a tuple of integers `(p, q)` such that `p/q = B_n` exactly, + where `B_n` denotes the `n`-th Bernoulli number. The fraction is + always reduced to lowest terms. Note that for `n > 1` and `n` odd, + `B_n = 0`, and `(0, 1)` is returned. + + Optional ``plus`` flag (default: False) control the sign choice of + the `B_1` value (default: `(-1, 2)`). + + **Examples** + + The first few Bernoulli numbers are exactly:: + + >>> from mpmath import bernfrac, mp, mpf, bernoulli + >>> for n in range(15): + ... p, q = bernfrac(n) + ... print("%s %s/%s" % (n, p, q)) + ... + 0 1/1 + 1 -1/2 + 2 1/6 + 3 0/1 + 4 -1/30 + 5 0/1 + 6 1/42 + 7 0/1 + 8 -1/30 + 9 0/1 + 10 5/66 + 11 0/1 + 12 -691/2730 + 13 0/1 + 14 7/6 + + This function works for arbitrarily large `n`:: + + >>> import sys + >>> if hasattr(sys, 'set_int_max_str_digits'): + ... sys.set_int_max_str_digits(30000) + >>> del sys + >>> p, q = bernfrac(10**4) + >>> print(q) + 2338224387510 + >>> print(len(str(p))) + 27692 + >>> mp.dps = 15 + >>> print(mpf(p) / q) + -9.04942396360948e+27677 + >>> print(bernoulli(10**4)) + -9.04942396360948e+27677 + + .. note :: + + :func:`~mpmath.bernoulli` computes a floating-point approximation + directly, without computing the exact fraction first. + This is much faster for large `n`. + + **Algorithm** + + :func:`~mpmath.bernfrac` works by computing the value of `B_n` numerically + and then using the von Staudt-Clausen theorem [1] to reconstruct + the exact fraction. For large `n`, this is significantly faster than + computing `B_1, B_2, \ldots, B_2` recursively with exact arithmetic. + The implementation has been tested for `n = 10^m` up to `m = 6`. + + In practice, :func:`~mpmath.bernfrac` appears to be about three times + slower than the specialized program calcbn.exe [2] + + **References** + + 1. [Weisstein]_ http://mathworld.wolfram.com/vonStaudt-ClausenTheorem.html + + 2. [Bernoulli]_ + + 3. [Wikipedia]_ https://en.wikipedia.org/wiki/Bernoulli_number + + """ + n = int(n) + if n < 3: + return [(1, 1), (1 if plus else -1, 2), (1, 6)][n] + if n & 1: + return (0, 1) + q = 1 + for k in list_primes(n+1): + if not (n % (k-1)): + q *= k + prec = bernoulli_size(n) + int(math.log(q,2)) + 20 + b = mpf_bernoulli(n, prec) + p = mpf_mul(b, from_int(q)) + pint = to_int(p, round_nearest) + return (pint, q) + + +#-----------------------------------------------------------------------# +# # +# Polygamma functions # +# # +#-----------------------------------------------------------------------# + +r""" +For all polygamma (psi) functions, we use the Euler-Maclaurin summation +formula. It looks slightly different in the m = 0 and m > 0 cases. + +For m = 0, we have + oo + ___ B + (0) 1 \ 2 k -2 k + psi (z) ~ log z + --- - ) ------ z + 2 z /___ (2 k)! + k = 1 + +Experiment shows that the minimum term of the asymptotic series +reaches 2^(-p) when Re(z) > 0.11*p. So we simply use the recurrence +for psi (equivalent, in fact, to summing to the first few terms +directly before applying E-M) to obtain z large enough. + +Since, very crudely, log z ~= 1 for Re(z) > 1, we can use +fixed-point arithmetic (if z is extremely large, log(z) itself +is a sufficient approximation, so we can stop there already). + +For Re(z) << 0, we could use recurrence, but this is of course +inefficient for large negative z, so there we use the +reflection formula instead. + +For m > 0, we have + + N - 1 + ___ + ~~~(m) [ \ 1 ] 1 1 + psi (z) ~ [ ) -------- ] + ---------- + -------- + + [ /___ m+1 ] m+1 m + k = 1 (z+k) ] 2 (z+N) m (z+N) + + oo + ___ B + \ 2 k (m+1) (m+2) ... (m+2k-1) + + ) ------ ------------------------ + /___ (2 k)! m + 2 k + k = 1 (z+N) + +where ~~~ denotes the function rescaled by 1/((-1)^(m+1) m!). + +Here again N is chosen to make z+N large enough for the minimum +term in the last series to become smaller than eps. + +TODO: the current estimation of N for m > 0 is *very suboptimal*. + +TODO: implement the reflection formula for m > 0, Re(z) << 0. +It is generally a combination of multiple cotangents. Need to +figure out a reasonably simple way to generate these formulas +on the fly. + +TODO: maybe use exact algorithms to compute psi for integral +and certain rational arguments, as this can be much more +efficient. (On the other hand, the availability of these +special values provides a convenient way to test the general +algorithm.) +""" + +# Harmonic numbers are just shifted digamma functions +# We should calculate these exactly when x is an integer +# and when doing so is faster. + +def mpf_harmonic(x, prec, rnd): + if x in (fzero, fnan, finf): + return x + a = mpf_psi0(mpf_add(fone, x, prec+5), prec) + return mpf_add(a, mpf_euler(prec+5, rnd), prec, rnd) + +def mpc_harmonic(z, prec, rnd): + if z[1] == fzero: + return (mpf_harmonic(z[0], prec, rnd), fzero) + a = mpc_psi0(mpc_add_mpf(z, fone, prec+5), prec) + return mpc_add_mpf(a, mpf_euler(prec+5, rnd), prec, rnd) + +def mpf_psi0(x, prec, rnd=round_down): + """ + Computation of the digamma function (psi function of order 0) + of a real argument. + """ + sign, man, exp, bc = x + wp = prec + 10 + if not man: + if x == finf: return x + if x == fninf or x == fnan: return fnan + if x == fzero or (exp >= 0 and sign): + raise ValueError("polygamma pole") + # Near 0 -- fixed-point arithmetic becomes bad + if exp+bc < -5: + v = mpf_psi0(mpf_add(x, fone, prec, rnd), prec, rnd) + return mpf_sub(v, mpf_div(fone, x, wp, rnd), prec, rnd) + # Reflection formula + if sign and exp+bc > 3: + c, s = mpf_cos_sin_pi(x, wp) + q = mpf_mul(mpf_div(c, s, wp), mpf_pi(wp), wp) + p = mpf_psi0(mpf_sub(fone, x, wp), wp) + return mpf_sub(p, q, prec, rnd) + # The logarithmic term is accurate enough + if (not sign) and bc + exp > wp: + return mpf_ln(mpf_sub(x, fone, wp), prec, rnd) + # Initial recurrence to obtain a large enough x + m = to_int(x) + n = int(0.11*wp) + 2 + s = MPZ_ZERO + x = to_fixed(x, wp) + one = MPZ_ONE << wp + if m < n: + for k in range(m, n): + s -= (one << wp) // x + x += one + x -= one + # Logarithmic term + s += to_fixed(mpf_ln(from_man_exp(x, -wp, wp), wp), wp) + # Endpoint term in Euler-Maclaurin expansion + s += (one << wp) // (2*x) + # Euler-Maclaurin remainder sum + x2 = (x*x) >> wp + t = one + prev = 0 + k = 1 + while 1: + t = (t*x2) >> wp + bsign, bman, bexp, bbc = mpf_bernoulli(2*k, wp) + offset = (bexp + 2*wp) + if offset >= 0: term = (bman << offset) // (t*(2*k)) + else: term = (bman >> (-offset)) // (t*(2*k)) + if k & 1: s -= term + else: s += term + if k > 2 and term >= prev: + break + prev = term + k += 1 + return from_man_exp(s, -wp, wp, rnd) + +def mpc_psi0(z, prec, rnd=round_down): + """ + Computation of the digamma function (psi function of order 0) + of a complex argument. + """ + re, im = z + # Fall back to the real case + if im == fzero: + return (mpf_psi0(re, prec, rnd), fzero) + wp = prec + 20 + sign, man, exp, bc = re + # Reflection formula + if sign and exp+bc > 3: + c = mpc_cos_pi(z, wp) + s = mpc_sin_pi(z, wp) + q = mpc_mul_mpf(mpc_div(c, s, wp), mpf_pi(wp), wp) + p = mpc_psi0(mpc_sub(mpc_one, z, wp), wp) + return mpc_sub(p, q, prec, rnd) + # Just the logarithmic term + if (not sign) and bc + exp > wp: + return mpc_ln(mpc_sub(z, mpc_one, wp), prec, rnd) + # Initial recurrence to obtain a large enough z + w = to_int(re) + n = int(0.11*wp) + 2 + s = mpc_zero + if w < n: + for k in range(w, n): + s = mpc_sub(s, mpc_reciprocal(z, wp), wp) + z = mpc_add_mpf(z, fone, wp) + z = mpc_sub(z, mpc_one, wp) + # Logarithmic and endpoint term + s = mpc_add(s, mpc_ln(z, wp), wp) + s = mpc_add(s, mpc_div(mpc_half, z, wp), wp) + # Euler-Maclaurin remainder sum + z2 = mpc_square(z, wp) + t = mpc_one + prev = mpc_zero + szprev = fzero + k = 1 + eps = mpf_shift(fone, -wp+2) + while 1: + t = mpc_mul(t, z2, wp) + bern = mpf_bernoulli(2*k, wp) + term = mpc_mpf_div(bern, mpc_mul_int(t, 2*k, wp), wp) + s = mpc_sub(s, term, wp) + szterm = mpc_abs(term, 10) + if k > 2 and (mpf_le(szterm, eps) or mpf_le(szprev, szterm)): + break + prev = term + szprev = szterm + k += 1 + return s + +# Currently unoptimized +def mpf_psi(m, x, prec, rnd=round_down): + """ + Computation of the polygamma function of arbitrary integer order + m >= 0, for a real argument x. + """ + if m == 0: + return mpf_psi0(x, prec, rnd=round_down) + return mpc_psi(m, (x, fzero), prec, rnd)[0] + +def mpc_psi(m, z, prec, rnd=round_down): + """ + Computation of the polygamma function of arbitrary integer order + m >= 0, for a complex argument z. + """ + if m == 0: + return mpc_psi0(z, prec, rnd) + re, im = z + wp = prec + 20 + sign, man, exp, bc = re + if not im[1]: + if im in (finf, fninf, fnan): + return (fnan, fnan) + if not man: + if re == finf and im == fzero: + return (fzero, fzero) + if re == fnan: + return (fnan, fnan) + # Recurrence + w = to_int(re) + n = int(0.4*wp + 4*m) + s = mpc_zero + if w < n: + for k in range(w, n): + t = mpc_pow_int(z, -m-1, wp) + s = mpc_add(s, t, wp) + z = mpc_add_mpf(z, fone, wp) + zm = mpc_pow_int(z, -m, wp) + z2 = mpc_pow_int(z, -2, wp) + # 1/m*(z+N)^m + integral_term = mpc_div_mpf(zm, from_int(m), wp) + s = mpc_add(s, integral_term, wp) + # 1/2*(z+N)^(-(m+1)) + s = mpc_add(s, mpc_mul_mpf(mpc_div(zm, z, wp), fhalf, wp), wp) + a = m + 1 + b = 2 + k = 1 + # Important: we want to sum up to the *relative* error, + # not the absolute error, because psi^(m)(z) might be tiny + magn = mpc_abs(s, 10) + magn = magn[2]+magn[3] + eps = mpf_shift(fone, magn-wp+2) + while 1: + zm = mpc_mul(zm, z2, wp) + bern = mpf_bernoulli(2*k, wp) + scal = mpf_mul_int(bern, a, wp) + scal = mpf_div(scal, from_int(b), wp) + term = mpc_mul_mpf(zm, scal, wp) + s = mpc_add(s, term, wp) + szterm = mpc_abs(term, 10) + if k > 2 and mpf_le(szterm, eps): + break + #print k, to_str(szterm, 10), to_str(eps, 10) + a *= (m+2*k)*(m+2*k+1) + b *= (2*k+1)*(2*k+2) + k += 1 + # Scale and sign factor + v = mpc_mul_mpf(s, mpf_gamma(from_int(m+1), wp), prec, rnd) + if not (m & 1): + v = mpf_neg(v[0]), mpf_neg(v[1]) + return v + + +#-----------------------------------------------------------------------# +# # +# Riemann zeta function # +# # +#-----------------------------------------------------------------------# + +r""" +We use zeta(s) = eta(s) / (1 - 2**(1-s)) and Borwein's approximation + + n-1 + ___ k + -1 \ (-1) (d_k - d_n) + eta(s) ~= ---- ) ------------------ + d_n /___ s + k = 0 (k + 1) +where + k + ___ i + \ (n + i - 1)! 4 + d_k = n ) ---------------. + /___ (n - i)! (2i)! + i = 0 + +If s = a + b*I, the absolute error for eta(s) is bounded by + + 3 (1 + 2|b|) + ------------ * exp(|b| pi/2) + n + (3+sqrt(8)) + +Disregarding the linear term, we have approximately, + + log(err) ~= log(exp(1.58*|b|)) - log(5.8**n) + log(err) ~= 1.58*|b| - log(5.8)*n + log(err) ~= 1.58*|b| - 1.76*n + log2(err) ~= 2.28*|b| - 2.54*n + +So for p bits, we should choose n > (p + 2.28*|b|) / 2.54. + +References: +----------- + +* [BorweinZeta]_ + +* [Wikipedia]_ http://en.wikipedia.org/wiki/Dirichlet_eta_function +""" + +borwein_cache = local.borwein_cache = {} + +def borwein_coefficients(n): + if n in borwein_cache: + return borwein_cache[n] + ds = [MPZ_ZERO] * (n+1) + d = MPZ_ONE + s = ds[0] = MPZ_ONE + for i in range(1, n+1): + d = d * 4 * (n+i-1) * (n-i+1) + d //= ((2*i) * ((2*i)-1)) + s += d + ds[i] = s + borwein_cache[n] = ds + return ds + +ZETA_INT_CACHE_MAX_PREC = 1000 +zeta_int_cache = local.zeta_int_cache = {} + +def mpf_zeta_int(s, prec, rnd=round_down): + """ + Optimized computation of zeta(s) for an integer s. + """ + wp = prec + 20 + s = int(s) + if s in zeta_int_cache and zeta_int_cache[s][0] >= wp: + return mpf_pos(zeta_int_cache[s][1], prec, rnd) + if s < 2: + if s == 1: + raise ValueError("zeta(1) pole") + if not s: + return mpf_neg(fhalf) + return mpf_div(mpf_bernoulli(-s+1, wp), from_int(s-1), prec, rnd) + # 2^-s term vanishes? + if s >= wp: + return mpf_perturb(fone, 0, prec, rnd) + # 5^-s term vanishes? + elif s >= wp*0.431: + t = one = 1 << wp + t += 1 << (wp - s) + t += one // (MPZ_THREE ** s) + t += 1 << max(0, wp - s*2) + return from_man_exp(t, -wp, prec, rnd) + else: + # Fast enough to sum directly? + # Even better, we use the Euler product (idea stolen from pari) + m = (float(wp)/(s-1) + 1) + if m < 30: + needed_terms = int(2.0**m + 1) + if needed_terms < int(wp/2.54 + 5) / 10: + t = fone + for k in list_primes(needed_terms): + #print k, needed_terms + powprec = int(wp - s*math.log(k,2)) + if powprec < 2: + break + a = mpf_sub(fone, mpf_pow_int(from_int(k), -s, powprec), wp) + t = mpf_mul(t, a, wp) + return mpf_div(fone, t, wp) + # Use Borwein's algorithm + n = int(wp/2.54 + 5) + d = borwein_coefficients(n) + t = MPZ_ZERO + s = MPZ(s) + for k in range(n): + t += (((-1)**k * (d[k] - d[n])) << wp) // (k+1)**s + t = (t << wp) // (-d[n]) + t = (t << wp) // ((1 << wp) - (1 << (wp+1-s))) + if (s in zeta_int_cache and zeta_int_cache[s][0] < wp) or (s not in zeta_int_cache): + zeta_int_cache[s] = (wp, from_man_exp(t, -wp-wp)) + return from_man_exp(t, -wp-wp, prec, rnd) + +def mpf_zeta(s, prec, rnd=round_down, alt=0): + sign, man, exp, bc = s + if not man: + if s == fzero: + if alt: + return fhalf + else: + return mpf_neg(fhalf) + if s == finf: + return fone + return fnan + wp = prec + 20 + # First term vanishes? + if (not sign) and (exp + bc > (math.log(wp,2) + 2)): + return mpf_perturb(fone, alt, prec, rnd) + # Optimize for integer arguments + elif exp >= 0: + if alt: + if s == fone: + return mpf_ln2(prec, rnd) + z = mpf_zeta_int(to_int(s), wp, negative_rnd[rnd]) + q = mpf_sub(fone, mpf_pow(ftwo, mpf_sub(fone, s, wp), wp), wp) + return mpf_mul(z, q, prec, rnd) + else: + return mpf_zeta_int(to_int(s), prec, rnd) + # Negative: use the reflection formula + # Borwein only proves the accuracy bound for x >= 1/2. However, based on + # tests, the accuracy without reflection is quite good even some distance + # to the left of 1/2. XXX: verify this. + if sign: + # XXX: could use the separate refl. formula for Dirichlet eta + if alt: + q = mpf_sub(fone, mpf_pow(ftwo, mpf_sub(fone, s, wp), wp), wp) + return mpf_mul(mpf_zeta(s, wp), q, prec, rnd) + # XXX: -1 should be done exactly + y = mpf_sub(fone, s, 10*wp) + a = mpf_gamma(y, wp) + b = mpf_zeta(y, wp) + c = mpf_sin_pi(mpf_shift(s, -1), wp) + wp2 = wp + max(0,exp+bc) + pi = mpf_pi(wp+wp2) + d = mpf_div(mpf_pow(mpf_shift(pi, 1), s, wp2), pi, wp2) + return mpf_mul(a,mpf_mul(b,mpf_mul(c,d,wp),wp),prec,rnd) + + # Near pole + r = mpf_sub(fone, s, wp) + asign, aman, aexp, abc = mpf_abs(r) + pole_dist = -2*(aexp+abc) + if pole_dist > wp: + if alt: + return mpf_ln2(prec, rnd) + else: + q = mpf_neg(mpf_div(fone, r, wp)) + return mpf_add(q, mpf_euler(wp), prec, rnd) + else: + wp += max(0, pole_dist) + + t = MPZ_ZERO + #wp += 16 - (prec & 15) + # Use Borwein's algorithm + n = int(wp/2.54 + 5) + d = borwein_coefficients(n) + t = MPZ_ZERO + sf = to_fixed(s, wp) + ln2 = ln2_fixed(wp) + for k in range(n): + u = (-sf*log_int_fixed(k+1, wp, ln2)) >> wp + #esign, eman, eexp, ebc = mpf_exp(u, wp) + #offset = eexp + wp + #if offset >= 0: + # w = ((d[k] - d[n]) * eman) << offset + #else: + # w = ((d[k] - d[n]) * eman) >> (-offset) + eman = exp_fixed(u, wp, ln2) + w = (d[k] - d[n]) * eman + if k & 1: + t -= w + else: + t += w + t = t // (-d[n]) + t = from_man_exp(t, -wp, wp) + if alt: + return mpf_pos(t, prec, rnd) + else: + q = mpf_sub(fone, mpf_pow(ftwo, mpf_sub(fone, s, wp), wp), wp) + return mpf_div(t, q, prec, rnd) + +def mpc_zeta(s, prec, rnd=round_down, alt=0, force=False): + re, im = s + if im == fzero: + return mpf_zeta(re, prec, rnd, alt), fzero + + # slow for large s + if (not force) and mpf_gt(mpc_abs(s, 10), from_int(prec)): + raise NotImplementedError + + wp = prec + 20 + + # Near pole + r = mpc_sub(mpc_one, s, wp) + asign, aman, aexp, abc = mpc_abs(r, 10) + pole_dist = -2*(aexp+abc) + if pole_dist > wp: + if alt: + q = mpf_ln2(wp) + y = mpf_mul(q, mpf_euler(wp), wp) + g = mpf_shift(mpf_mul(q, q, wp), -1) + g = mpf_sub(y, g) + z = mpc_mul_mpf(r, mpf_neg(g), wp) + z = mpc_add_mpf(z, q, wp) + return mpc_pos(z, prec, rnd) + else: + q = mpc_neg(mpc_div(mpc_one, r, wp)) + q = mpc_add_mpf(q, mpf_euler(wp), wp) + return mpc_pos(q, prec, rnd) + else: + wp += max(0, pole_dist) + + # Reflection formula. To be rigorous, we should reflect to the left of + # re = 1/2 (see comments for mpf_zeta), but this leads to unnecessary + # slowdown for interesting values of s + if mpf_lt(re, fzero): + # XXX: could use the separate refl. formula for Dirichlet eta + if alt: + q = mpc_sub(mpc_one, mpc_pow(mpc_two, mpc_sub(mpc_one, s, wp), + wp), wp) + return mpc_mul(mpc_zeta(s, wp), q, prec, rnd) + # XXX: -1 should be done exactly + y = mpc_sub(mpc_one, s, 10*wp) + a = mpc_gamma(y, wp) + b = mpc_zeta(y, wp) + c = mpc_sin_pi(mpc_shift(s, -1), wp) + rsign, rman, rexp, rbc = re + isign, iman, iexp, ibc = im + mag = max(rexp+rbc, iexp+ibc) + wp2 = wp + max(0, mag) + pi = mpf_pi(wp+wp2) + pi2 = (mpf_shift(pi, 1), fzero) + d = mpc_div_mpf(mpc_pow(pi2, s, wp2), pi, wp2) + return mpc_mul(a,mpc_mul(b,mpc_mul(c,d,wp),wp),prec,rnd) + n = int(wp/2.54 + 5) + n += int(0.9*abs(to_int(im))) + d = borwein_coefficients(n) + ref = to_fixed(re, wp) + imf = to_fixed(im, wp) + tre = MPZ_ZERO + tim = MPZ_ZERO + one = MPZ_ONE << wp + one_2wp = MPZ_ONE << (2*wp) + critical_line = re == fhalf + ln2 = ln2_fixed(wp) + pi2 = pi_fixed(wp-1) + wp2 = wp+wp + for k in range(n): + log = log_int_fixed(k+1, wp, ln2) + # A square root is much cheaper than an exp + if critical_line: + w = one_2wp // isqrt_fast((k+1) << wp2) + else: + w = exp_fixed((-ref*log) >> wp, wp) + if k & 1: + w *= (d[n] - d[k]) + else: + w *= (d[k] - d[n]) + wre, wim = cos_sin_fixed((-imf*log)>>wp, wp, pi2) + tre += (w * wre) >> wp + tim += (w * wim) >> wp + tre //= (-d[n]) + tim //= (-d[n]) + tre = from_man_exp(tre, -wp, wp) + tim = from_man_exp(tim, -wp, wp) + if alt: + return mpc_pos((tre, tim), prec, rnd) + else: + q = mpc_sub(mpc_one, mpc_pow(mpc_two, r, wp), wp) + return mpc_div((tre, tim), q, prec, rnd) + +def mpf_altzeta(s, prec, rnd=round_down): + return mpf_zeta(s, prec, rnd, 1) + +def mpc_altzeta(s, prec, rnd=round_down): + return mpc_zeta(s, prec, rnd, 1) + +# Not optimized currently +mpf_zetasum = None + + +def pow_fixed(x, n, wp): + if n == 1: + return x + y = MPZ_ONE << wp + while n: + if n & 1: + y = (y*x) >> wp + n -= 1 + x = (x*x) >> wp + n //= 2 + return y + +# TODO: optimize / cleanup interface / unify with list_primes +sieve_cache = local.sieve_cache = [] +primes_cache = local.primes_cache = [] +mult_cache = local.mult_cache = [] + +def primesieve(n): + global sieve_cache, primes_cache, mult_cache + if n < len(sieve_cache): + sieve = sieve_cache#[:n+1] + primes = primes_cache[:primes_cache.index(max(sieve))+1] + mult = mult_cache#[:n+1] + return sieve, primes, mult + sieve = [0] * (n+1) + mult = [0] * (n+1) + primes = list_primes(n) + for p in primes: + #sieve[p::p] = p + for k in range(p,n+1,p): + sieve[k] = p + for i, p in enumerate(sieve): + if i >= 2: + m = 1 + n = i // p + while not n % p: + n //= p + m += 1 + mult[i] = m + sieve_cache = sieve + primes_cache = primes + mult_cache = mult + return sieve, primes, mult + +def zetasum_sieved(critical_line, sre, sim, a, n, wp): + if a < 1: + raise ValueError("a cannot be less than 1") + sieve, primes, mult = primesieve(a+n) + basic_powers = {} + one = MPZ_ONE << wp + one_2wp = MPZ_ONE << (2*wp) + wp2 = wp+wp + ln2 = ln2_fixed(wp) + pi2 = pi_fixed(wp-1) + for p in primes: + if p*2 > a+n: + break + log = log_int_fixed(p, wp, ln2) + cos, sin = cos_sin_fixed((-sim*log)>>wp, wp, pi2) + if critical_line: + u = one_2wp // isqrt_fast(p<>wp, wp) + pre = (u*cos) >> wp + pim = (u*sin) >> wp + basic_powers[p] = [(pre, pim)] + tre, tim = pre, pim + for m in range(1,int(math.log(a+n,p)+0.01)+1): + tre, tim = ((pre*tre-pim*tim)>>wp), ((pim*tre+pre*tim)>>wp) + basic_powers[p].append((tre,tim)) + xre = MPZ_ZERO + xim = MPZ_ZERO + if a == 1: + xre += one + aa = max(a,2) + for k in range(aa, a+n+1): + p = sieve[k] + if p in basic_powers: + m = mult[k] + tre, tim = basic_powers[p][m-1] + while 1: + k //= p**m + if k == 1: + break + p = sieve[k] + m = mult[k] + pre, pim = basic_powers[p][m-1] + tre, tim = ((pre*tre-pim*tim)>>wp), ((pim*tre+pre*tim)>>wp) + else: + log = log_int_fixed(k, wp, ln2) + cos, sin = cos_sin_fixed((-sim*log)>>wp, wp, pi2) + if critical_line: + u = one_2wp // isqrt_fast(k<>wp, wp) + tre = (u*cos) >> wp + tim = (u*sin) >> wp + xre += tre + xim += tim + return xre, xim + +# Set to something large to disable +ZETASUM_SIEVE_CUTOFF = 10 + +def mpc_zetasum(s, a, n, derivatives, reflect, prec): + """ + Fast version of mp._zetasum, assuming s = complex, a = integer. + """ + + wp = prec + 10 + derivatives = list(derivatives) + have_derivatives = derivatives != [0] + have_one_derivative = len(derivatives) == 1 + + # parse s + sre, sim = s + critical_line = (sre == fhalf) + sre = to_fixed(sre, wp) + sim = to_fixed(sim, wp) + + if a > 0 and n > ZETASUM_SIEVE_CUTOFF and not have_derivatives \ + and not reflect and (n < 4e7 or sys.maxsize > 2**32): + re, im = zetasum_sieved(critical_line, sre, sim, a, n, wp) + xs = [(from_man_exp(re, -wp, prec, 'n'), from_man_exp(im, -wp, prec, 'n'))] + return xs, [] + + maxd = max(derivatives) + if not have_one_derivative: + derivatives = range(maxd+1) + + # x_d = 0, y_d = 0 + xre = [MPZ_ZERO for d in derivatives] + xim = [MPZ_ZERO for d in derivatives] + if reflect: + yre = [MPZ_ZERO for d in derivatives] + yim = [MPZ_ZERO for d in derivatives] + else: + yre = yim = [] + + one = MPZ_ONE << wp + one_2wp = MPZ_ONE << (2*wp) + + ln2 = ln2_fixed(wp) + pi2 = pi_fixed(wp-1) + wp2 = wp+wp + + for w in range(a, a+n+1): + log = log_int_fixed(w, wp, ln2) + cos, sin = cos_sin_fixed((-sim*log)>>wp, wp, pi2) + if critical_line: + u = one_2wp // isqrt_fast(w<>wp, wp) + xterm_re = (u * cos) >> wp + xterm_im = (u * sin) >> wp + if reflect: + reciprocal = (one_2wp // (u*w)) + yterm_re = (reciprocal * cos) >> wp + yterm_im = (reciprocal * sin) >> wp + + if have_derivatives: + if have_one_derivative: + log = pow_fixed(log, maxd, wp) + xre[0] += (xterm_re * log) >> wp + xim[0] += (xterm_im * log) >> wp + if reflect: + yre[0] += (yterm_re * log) >> wp + yim[0] += (yterm_im * log) >> wp + else: + t = MPZ_ONE << wp + for d in derivatives: + xre[d] += (xterm_re * t) >> wp + xim[d] += (xterm_im * t) >> wp + if reflect: + yre[d] += (yterm_re * t) >> wp + yim[d] += (yterm_im * t) >> wp + t = (t * log) >> wp + else: + xre[0] += xterm_re + xim[0] += xterm_im + if reflect: + yre[0] += yterm_re + yim[0] += yterm_im + if have_derivatives: + if have_one_derivative: + if maxd % 2: + xre[0] = -xre[0] + xim[0] = -xim[0] + if reflect: + yre[0] = -yre[0] + yim[0] = -yim[0] + else: + xre = [(-1)**d * xre[d] for d in derivatives] + xim = [(-1)**d * xim[d] for d in derivatives] + if reflect: + yre = [(-1)**d * yre[d] for d in derivatives] + yim = [(-1)**d * yim[d] for d in derivatives] + xs = [(from_man_exp(xa, -wp, prec, 'n'), from_man_exp(xb, -wp, prec, 'n')) + for (xa, xb) in zip(xre, xim)] + ys = [(from_man_exp(ya, -wp, prec, 'n'), from_man_exp(yb, -wp, prec, 'n')) + for (ya, yb) in zip(yre, yim)] + return xs, ys + + +#-----------------------------------------------------------------------# +# # +# The gamma function (NEW IMPLEMENTATION) # +# # +#-----------------------------------------------------------------------# + +# Higher means faster, but more precomputation time +MAX_GAMMA_TAYLOR_PREC = 5000 +# Need to derive higher bounds for Taylor series to go higher +assert MAX_GAMMA_TAYLOR_PREC < 15000 + +# Use Stirling's series if abs(x) > beta*prec +# Important: must be large enough for convergence! +GAMMA_STIRLING_BETA = 0.2 + +SMALL_FACTORIAL_CACHE_SIZE = 150 + +gamma_taylor_cache = local.gamma_taylor_cache = {} +gamma_stirling_cache = local.gamma_stirling_cache = {} + +small_factorial_cache = [from_int(ifac(n)) for \ + n in range(SMALL_FACTORIAL_CACHE_SIZE+1)] + +def zeta_array(N, prec): + """ + zeta(n) = A * pi**n / n! + B + + where A is a rational number (A = Bernoulli number + for n even) and B is an infinite sum over powers of exp(2*pi). + (B = 0 for n even). + + TODO: this is currently only used for gamma, but could + be very useful elsewhere. + """ + extra = 30 + wp = prec+extra + zeta_values = [MPZ_ZERO] * (N+2) + pi = pi_fixed(wp) + # STEP 1: + one = MPZ_ONE << wp + zeta_values[0] = -one//2 + f_2pi = mpf_shift(mpf_pi(wp),1) + exp_2pi_k = exp_2pi = mpf_exp(f_2pi, wp) + # Compute exponential series + # Store values of 1/(exp(2*pi*k)-1), + # exp(2*pi*k)/(exp(2*pi*k)-1)**2, 1/(exp(2*pi*k)-1)**2 + # pi*k*exp(2*pi*k)/(exp(2*pi*k)-1)**2 + exps3 = [] + k = 1 + while 1: + tp = wp - 9*k + if tp < 1: + break + # 1/(exp(2*pi*k-1) + q1 = mpf_div(fone, mpf_sub(exp_2pi_k, fone, tp), tp) + # pi*k*exp(2*pi*k)/(exp(2*pi*k)-1)**2 + q2 = mpf_mul(exp_2pi_k, mpf_mul(q1,q1,tp), tp) + q1 = to_fixed(q1, wp) + q2 = to_fixed(q2, wp) + q2 = (k * q2 * pi) >> wp + exps3.append((q1, q2)) + # Multiply for next round + exp_2pi_k = mpf_mul(exp_2pi_k, exp_2pi, wp) + k += 1 + # Exponential sum + for n in range(3, N+1, 2): + s = MPZ_ZERO + k = 1 + for e1, e2 in exps3: + if n%4 == 3: + t = e1 // k**n + else: + U = (n-1)//4 + t = (e1 + e2//U) // k**n + if not t: + break + s += t + k += 1 + zeta_values[n] = -2*s + # Even zeta values + B = [mpf_abs(mpf_bernoulli(k,wp)) for k in range(N+2)] + pi_pow = fpi = mpf_pow_int(mpf_shift(mpf_pi(wp), 1), 2, wp) + pi_pow = mpf_div(pi_pow, from_int(4), wp) + for n in range(2,N+2,2): + z = mpf_mul(B[n], pi_pow, wp) + zeta_values[n] = to_fixed(z, wp) + pi_pow = mpf_mul(pi_pow, fpi, wp) + pi_pow = mpf_div(pi_pow, from_int((n+1)*(n+2)), wp) + # Zeta sum + reciprocal_pi = (one << wp) // pi + for n in range(3, N+1, 4): + U = (n-3)//4 + s = zeta_values[4*U+4]*(4*U+7)//4 + for k in range(1, U+1): + s -= (zeta_values[4*k] * zeta_values[4*U+4-4*k]) >> wp + zeta_values[n] += (2*s*reciprocal_pi) >> wp + for n in range(5, N+1, 4): + U = (n-1)//4 + s = zeta_values[4*U+2]*(2*U+1) + for k in range(1, 2*U+1): + s += ((-1)**k*2*k* zeta_values[2*k] * zeta_values[4*U+2-2*k])>>wp + zeta_values[n] += ((s*reciprocal_pi)>>wp)//(2*U) + return [x>>extra for x in zeta_values] + +def gamma_taylor_coefficients(inprec): + """ + Gives the Taylor coefficients of 1/gamma(1+x) as + a list of fixed-point numbers. Enough coefficients are returned + to ensure that the series converges to the given precision + when x is in [0.5, 1.5]. + """ + # Reuse nearby cache values (small case) + if inprec < 400: + prec = inprec + (10-(inprec%10)) + elif inprec < 1000: + prec = inprec + (30-(inprec%30)) + else: + prec = inprec + if prec in gamma_taylor_cache: + return gamma_taylor_cache[prec], prec + + # Experimentally determined bounds + if prec < 1000: + N = int(prec**0.76 + 2) + else: + # Valid to at least 15000 bits + N = int(prec**0.787 + 2) + + # Reuse higher precision values + for cprec in gamma_taylor_cache: + if cprec > prec: + coeffs = [x>>(cprec-prec) for x in gamma_taylor_cache[cprec][-N:]] + if inprec < 1000: + gamma_taylor_cache[prec] = coeffs + return coeffs, prec + + # Cache at a higher precision (large case) + if prec > 1000: + prec = int(prec * 1.2) + + wp = prec + 20 + A = [0] * N + A[0] = MPZ_ZERO + A[1] = MPZ_ONE << wp + A[2] = euler_fixed(wp) + # SLOW, reference implementation + #zeta_values = [0,0]+[to_fixed(mpf_zeta_int(k,wp),wp) for k in range(2,N)] + zeta_values = zeta_array(N, wp) + for k in range(3, N): + a = (-A[2]*A[k-1])>>wp + for j in range(2,k): + a += ((-1)**j * zeta_values[j] * A[k-j]) >> wp + a //= (1-k) + A[k] = a + A = [a>>20 for a in A] + A = A[::-1] + A = A[:-1] + gamma_taylor_cache[prec] = A + #return A, prec + return gamma_taylor_coefficients(inprec) + +def gamma_fixed_taylor(xmpf, x, wp, prec, rnd, type): + # Determine nearest multiple of N/2 + #n = int(x >> (wp-1)) + #steps = (n-1)>>1 + nearest_int = ((x >> (wp-1)) + MPZ_ONE) >> 1 + one = MPZ_ONE << wp + coeffs, cwp = gamma_taylor_coefficients(wp) + if nearest_int > 0: + r = one + for i in range(nearest_int-1): + x -= one + r = (r*x) >> wp + x -= one + p = MPZ_ZERO + for c in coeffs: + p = c + ((x*p)>>wp) + p >>= (cwp-wp) + if type == 0: + return from_man_exp((r<> wp + x += one + p = MPZ_ZERO + for c in coeffs: + p = c + ((x*p)>>wp) + p >>= (cwp-wp) + if wp - x.bit_length() > 10: + # pass very close to 0, so do floating-point multiply + g = mpf_add(xmpf, from_int(-nearest_int)) # exact + r = from_man_exp(p*r,-wp-wp) + r = mpf_mul(r, g, wp) + if type == 0: + return mpf_div(fone, r, prec, rnd) + if type == 2: + return mpf_pos(r, prec, rnd) + if type == 3: + return mpf_ln(mpf_abs(mpf_div(fone, r, wp)), prec, rnd) + else: + r = from_man_exp(x*p*r,-3*wp) + if type == 0: return mpf_div(fone, r, prec, rnd) + if type == 2: return mpf_pos(r, prec, rnd) + if type == 3: return mpf_neg(mpf_ln(mpf_abs(r), prec, rnd)) + +def stirling_coefficient(n): + if n in gamma_stirling_cache: + return gamma_stirling_cache[n] + p, q = bernfrac(n) + q *= MPZ(n*(n-1)) + gamma_stirling_cache[n] = p, q, p.bit_length(), q.bit_length() + return gamma_stirling_cache[n] + +def real_stirling_series(x, prec): + """ + Sums the rational part of Stirling's expansion, + + log(sqrt(2*pi)) - z + 1/(12*z) - 1/(360*z^3) + ... + + """ + t = (MPZ_ONE<<(prec+prec)) // x # t = 1/x + u = (t*t)>>prec # u = 1/x**2 + s = ln_sqrt2pi_fixed(prec) - x + # Add initial terms of Stirling's series + s += t//12; t = (t*u)>>prec + s -= t//360; t = (t*u)>>prec + s += t//1260; t = (t*u)>>prec + s -= t//1680; t = (t*u)>>prec + if not t: return s + s += t//1188; t = (t*u)>>prec + s -= 691*t//360360; t = (t*u)>>prec + s += t//156; t = (t*u)>>prec + if not t: return s + s -= 3617*t//122400; t = (t*u)>>prec + s += 43867*t//244188; t = (t*u)>>prec + s -= 174611*t//125400; t = (t*u)>>prec + if not t: return s + k = 22 + # From here on, the coefficients are growing, so we + # have to keep t at a roughly constant size + usize = u.bit_length() + tsize = t.bit_length() + texp = 0 + while 1: + p, q, pb, qb = stirling_coefficient(k) + term_mag = tsize + pb + texp + shift = -texp + m = pb - term_mag + if m > 0 and shift < m: + p >>= m + shift -= m + m = tsize - term_mag + if m > 0 and shift < m: + w = t >> m + shift -= m + else: + w = t + term = (t*p//q) >> shift + if not term: + break + s += term + t = (t*u) >> usize + texp -= (prec - usize) + k += 2 + return s + +def complex_stirling_series(x, y, prec): + # t = 1/z + _m = (x*x + y*y) >> prec + tre = (x << prec) // _m + tim = (-y << prec) // _m + # u = 1/z**2 + ure = (tre*tre - tim*tim) >> prec + uim = tim*tre >> (prec-1) + # s = log(sqrt(2*pi)) - z + sre = ln_sqrt2pi_fixed(prec) - x + sim = -y + + # Add initial terms of Stirling's series + sre += tre//12; sim += tim//12 + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + sre -= tre//360; sim -= tim//360 + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + sre += tre//1260; sim += tim//1260 + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + sre -= tre//1680; sim -= tim//1680 + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + if abs(tre) + abs(tim) < 5: return sre, sim + sre += tre//1188; sim += tim//1188 + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + sre -= 691*tre//360360; sim -= 691*tim//360360 + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + sre += tre//156; sim += tim//156 + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + if abs(tre) + abs(tim) < 5: return sre, sim + sre -= 3617*tre//122400; sim -= 3617*tim//122400 + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + sre += 43867*tre//244188; sim += 43867*tim//244188 + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + sre -= 174611*tre//125400; sim -= 174611*tim//125400 + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + if abs(tre) + abs(tim) < 5: return sre, sim + + k = 22 + # From here on, the coefficients are growing, so we + # have to keep t at a roughly constant size + usize = (max(abs(ure), abs(uim))).bit_length() + tsize = (max(abs(tre), abs(tim))).bit_length() + texp = 0 + while 1: + p, q, pb, qb = stirling_coefficient(k) + term_mag = tsize + pb + texp + shift = -texp + m = pb - term_mag + if m > 0 and shift < m: + p >>= m + shift -= m + m = tsize - term_mag + if m > 0 and shift < m: + wre = tre >> m + wim = tim >> m + shift -= m + else: + wre = tre + wim = tim + termre = (tre*p//q) >> shift + termim = (tim*p//q) >> shift + if abs(termre) + abs(termim) < 5: + break + sre += termre + sim += termim + tre, tim = ((tre*ure - tim*uim)>>usize), \ + ((tre*uim + tim*ure)>>usize) + texp -= (prec - usize) + k += 2 + return sre, sim + + +def mpf_gamma(x, prec, rnd=round_down, type=0): + """ + This function implements multipurpose evaluation of the gamma + function, G(x), as well as the following versions of the same: + + type = 0 -- G(x) [standard gamma function] + type = 1 -- G(x+1) = x*G(x) = x! [factorial] + type = 2 -- 1/G(x) [reciprocal gamma function] + type = 3 -- log(|G(x)|) [log-gamma function, real part] + """ + + # Specal values + sign, man, exp, bc = x + if not man: + if x == fzero: + if type == 1: return fone + if type == 2: return fzero + raise ValueError("gamma function pole") + if x == finf: + if type == 2: return fzero + return finf + return fnan + + # First of all, for log gamma, numbers can be well beyond the fixed-point + # range, so we must take care of huge numbers before e.g. trying + # to convert x to the nearest integer + if type == 3: + wp = prec+20 + if exp+bc > wp and not sign: + return mpf_sub(mpf_mul(x, mpf_ln(x, wp), wp), x, prec, rnd) + + # We strongly want to special-case small integers + is_integer = exp >= 0 + if is_integer: + # Poles + if sign: + if type == 2: + return fzero + raise ValueError("gamma function pole") + # n = x + n = man << exp + if n < SMALL_FACTORIAL_CACHE_SIZE: + if type == 0: + return mpf_pos(small_factorial_cache[n-1], prec, rnd) + if type == 1: + return mpf_pos(small_factorial_cache[n], prec, rnd) + if type == 2: + return mpf_div(fone, small_factorial_cache[n-1], prec, rnd) + if type == 3: + return mpf_ln(small_factorial_cache[n-1], prec, rnd) + else: + # floor(abs(x)) + n = int(man >> (-exp)) + + # Estimate size and precision + # Estimate log(gamma(|x|),2) as x*log(x,2) + mag = exp + bc + gamma_size = n*mag + + if type == 3: + wp = prec + 20 + else: + wp = prec + gamma_size.bit_length() + 20 + + # Very close to 0, pole + if mag < -wp: + if type == 0: + return mpf_sub(mpf_div(fone,x, wp),mpf_shift(fone,-wp),prec,rnd) + if type == 1: return mpf_sub(fone, x, prec, rnd) + if type == 2: return mpf_add(x, mpf_shift(fone,mag-wp), prec, rnd) + if type == 3: return mpf_neg(mpf_ln(mpf_abs(x), prec, rnd)) + + # From now on, we assume having a gamma function + if type == 1: + return mpf_gamma(mpf_add(x, fone), prec, rnd, 0) + + # Special case integers (those not small enough to be caught above, + # but still small enough for an exact factorial to be faster + # than an approximate algorithm), and half-integers + if exp >= -1: + if is_integer: + if gamma_size < 10*wp: + if type == 0: + return from_int(ifac(n-1), prec, rnd) + if type == 2: + return from_rational(MPZ_ONE, ifac(n-1), prec, rnd) + if type == 3: + return mpf_ln(from_int(ifac(n-1)), prec, rnd) + # half-integer + if n < 100 or gamma_size < 10*wp: + if sign: + w = sqrtpi_fixed(wp) + if n % 2: f = ifac2(2*n+1) + else: f = -ifac2(2*n+1) + if type == 0: + return mpf_shift(from_rational(w, f, prec, rnd), -wp+n+1) + if type == 2: + return mpf_shift(from_rational(f, w, prec, rnd), wp-n-1) + if type == 3: + return mpf_ln(mpf_shift(from_rational(w, abs(f), + prec, rnd), -wp+n+1), prec, rnd) + elif n == 0: + if type == 0: return mpf_sqrtpi(prec, rnd) + if type == 2: return mpf_div(fone, mpf_sqrtpi(wp), prec, rnd) + if type == 3: return mpf_ln(mpf_sqrtpi(wp), prec, rnd) + else: + w = sqrtpi_fixed(wp) + w = from_man_exp(w * ifac2(2*n-1), -wp-n) + if type == 0: return mpf_pos(w, prec, rnd) + if type == 2: return mpf_div(fone, w, prec, rnd) + if type == 3: return mpf_ln(mpf_abs(w), prec, rnd) + + # Convert to fixed point + offset = exp + wp + if offset >= 0: absxman = man << offset + else: absxman = man >> (-offset) + + # For log gamma, provide accurate evaluation for x = 1+eps and 2+eps + if type == 3 and not sign: + one = MPZ_ONE << wp + one_dist = abs(absxman-one) + two_dist = abs(absxman-2*one) + cancellation = (wp - min(one_dist, two_dist).bit_length()) + if cancellation > 10: + xsub1 = mpf_sub(fone, x) + xsub2 = mpf_sub(ftwo, x) + xsub1mag = xsub1[2]+xsub1[3] + xsub2mag = xsub2[2]+xsub2[3] + if xsub1mag < -wp: + return mpf_mul(mpf_euler(wp), mpf_sub(fone, x), prec, rnd) + if xsub2mag < -wp: + return mpf_mul(mpf_sub(fone, mpf_euler(wp)), + mpf_sub(x, ftwo), prec, rnd) + # Proceed but increase precision + wp += max(-xsub1mag, -xsub2mag) + offset = exp + wp + if offset >= 0: absxman = man << offset + else: absxman = man >> (-offset) + + # Use Taylor series if appropriate + n_for_stirling = int(GAMMA_STIRLING_BETA*wp) + if n < max(100, n_for_stirling) and wp < MAX_GAMMA_TAYLOR_PREC: + if sign: + absxman = -absxman + return gamma_fixed_taylor(x, absxman, wp, prec, rnd, type) + + # Use Stirling's series + # First ensure that |x| is large enough for rapid convergence + xorig = x + + # Argument reduction + r = 0 + if n < n_for_stirling: + r = one = MPZ_ONE << wp + d = n_for_stirling - n + for k in range(d): + r = (r * absxman) >> wp + absxman += one + x = xabs = from_man_exp(absxman, -wp) + if sign: + x = mpf_neg(x) + else: + xabs = mpf_abs(x) + + # Asymptotic series + y = real_stirling_series(absxman, wp) + u = to_fixed(mpf_ln(xabs, wp), wp) + u = ((absxman - (MPZ_ONE<<(wp-1))) * u) >> wp + y += u + w = from_man_exp(y, -wp) + + # Compute final value + if sign: + # Reflection formula + A = mpf_mul(mpf_sin_pi(xorig, wp), xorig, wp) + B = mpf_neg(mpf_pi(wp)) + if type == 0 or type == 2: + A = mpf_mul(A, mpf_exp(w, wp)) + if r: + B = mpf_mul(B, from_man_exp(r, -wp), wp) + if type == 0: + return mpf_div(B, A, prec, rnd) + if type == 2: + return mpf_div(A, B, prec, rnd) + if type == 3: + if r: + B = mpf_mul(B, from_man_exp(r, -wp), wp) + A = mpf_add(mpf_ln(mpf_abs(A), wp), w, wp) + return mpf_sub(mpf_ln(mpf_abs(B), wp), A, prec, rnd) + else: + if type == 0: + if r: + return mpf_div(mpf_exp(w, wp), + from_man_exp(r, -wp), prec, rnd) + return mpf_exp(w, prec, rnd) + if type == 2: + if r: + return mpf_div(from_man_exp(r, -wp), + mpf_exp(w, wp), prec, rnd) + return mpf_exp(mpf_neg(w), prec, rnd) + if type == 3: + if r: + return mpf_sub(w, mpf_ln(from_man_exp(r,-wp), wp), prec, rnd) + return mpf_pos(w, prec, rnd) + + +def mpc_gamma(z, prec, rnd=round_down, type=0): + a, b = z + asign, aman, aexp, abc = a + bsign, bman, bexp, bbc = b + + if b == fzero: + # Imaginary part on negative half-axis for log-gamma function + if type == 3 and asign: + re = mpf_gamma(a, prec, rnd, 3) + n = (-aman) >> (-aexp) + im = mpf_mul_int(mpf_pi(prec+10), n, prec, rnd) + return re, im + return mpf_gamma(a, prec, rnd, type), fzero + + # Some kind of complex inf/nan + if (not aman and aexp) or (not bman and bexp): + return (fnan, fnan) + + # Initial working precision + wp = prec + 20 + + amag = aexp+abc + bmag = bexp+bbc + if aman: + mag = max(amag, bmag) + else: + mag = bmag + + # Close to 0 + if mag < -8: + if mag < -wp: + # 1/gamma(z) = z + euler*z^2 + O(z^3) + v = mpc_add(z, mpc_mul_mpf(mpc_mul(z,z,wp),mpf_euler(wp),wp), wp) + if type == 0: return mpc_reciprocal(v, prec, rnd) + if type == 1: return mpc_div(z, v, prec, rnd) + if type == 2: return mpc_pos(v, prec, rnd) + if type == 3: return mpc_ln(mpc_reciprocal(v, prec), prec, rnd) + elif type != 1: + wp += (-mag) + + # Handle huge log-gamma values; must do this before converting to + # a fixed-point value. TODO: determine a precise cutoff of validity + # depending on amag and bmag + if type == 3 and mag > wp and ((not asign) or (bmag >= amag)): + return mpc_sub(mpc_mul(z, mpc_ln(z, wp), wp), z, prec, rnd) + + # From now on, we assume having a gamma function + if type == 1: + return mpc_gamma((mpf_add(a, fone), b), prec, rnd, 0) + + an = abs(to_int(a)) + bn = abs(to_int(b)) + absn = max(an, bn) + gamma_size = absn*mag + if type == 3: + pass + else: + wp += gamma_size.bit_length() + + # Reflect to the right half-plane. Note that Stirling's expansion + # is valid in the left half-plane too, as long as we're not too close + # to the real axis, but in order to use this argument reduction + # in the negative direction must be implemented. + #need_reflection = asign and ((bmag < 0) or (amag-bmag > 4)) + need_reflection = asign + zorig = z + if need_reflection: + z = mpc_neg(z) + asign, aman, aexp, abc = a = z[0] + bsign, bman, bexp, bbc = b = z[1] + + # Imaginary part very small compared to real one? + yfinal = 0 + balance_prec = 0 + if bmag < -10: + # Check z ~= 1 and z ~= 2 for loggamma + if type == 3: + zsub1 = mpc_sub_mpf(z, fone) + if zsub1[0] == fzero: + cancel1 = -bmag + else: + cancel1 = -max(zsub1[0][2]+zsub1[0][3], bmag) + if cancel1 > wp: + pi = mpf_pi(wp) + x = mpc_mul_mpf(zsub1, pi, wp) + x = mpc_mul(x, x, wp) + x = mpc_div_mpf(x, from_int(12), wp) + y = mpc_mul_mpf(zsub1, mpf_neg(mpf_euler(wp)), wp) + yfinal = mpc_add(x, y, wp) + if not need_reflection: + return mpc_pos(yfinal, prec, rnd) + elif cancel1 > 0: + wp += cancel1 + zsub2 = mpc_sub_mpf(z, ftwo) + if zsub2[0] == fzero: + cancel2 = -bmag + else: + cancel2 = -max(zsub2[0][2]+zsub2[0][3], bmag) + if cancel2 > wp: + pi = mpf_pi(wp) + t = mpf_sub(mpf_mul(pi, pi), from_int(6)) + x = mpc_mul_mpf(mpc_mul(zsub2, zsub2, wp), t, wp) + x = mpc_div_mpf(x, from_int(12), wp) + y = mpc_mul_mpf(zsub2, mpf_sub(fone, mpf_euler(wp)), wp) + yfinal = mpc_add(x, y, wp) + if not need_reflection: + return mpc_pos(yfinal, prec, rnd) + elif cancel2 > 0: + wp += cancel2 + if bmag < -wp: + # Compute directly from the real gamma function. + pp = 2*(wp+10) + aabs = mpf_abs(a) + eps = mpf_shift(fone, amag-wp) + x1 = mpf_gamma(aabs, pp, type=type) + x2 = mpf_gamma(mpf_add(aabs, eps), pp, type=type) + xprime = mpf_div(mpf_sub(x2, x1, pp), eps, pp) + y = mpf_mul(b, xprime, prec, rnd) + yfinal = (x1, y) + # Note: we still need to use the reflection formula for + # near-poles, and the correct branch of the log-gamma function + if not need_reflection: + return mpc_pos(yfinal, prec, rnd) + else: + balance_prec += (-bmag) + + wp += balance_prec + n_for_stirling = int(GAMMA_STIRLING_BETA*wp) + need_reduction = absn < n_for_stirling + + afix = to_fixed(a, wp) + bfix = to_fixed(b, wp) + + r = 0 + if not yfinal: + zprered = z + # Argument reduction + if absn < n_for_stirling: + absn = complex(an, bn) + d = int((1 + n_for_stirling**2 - bn**2)**0.5 - an) + rre = one = MPZ_ONE << wp + rim = MPZ_ZERO + for k in range(d): + rre, rim = ((afix*rre-bfix*rim)>>wp), ((afix*rim + bfix*rre)>>wp) + afix += one + r = from_man_exp(rre, -wp), from_man_exp(rim, -wp) + a = from_man_exp(afix, -wp) + z = a, b + + yre, yim = complex_stirling_series(afix, bfix, wp) + # (z-1/2)*log(z) + S + lre, lim = mpc_ln(z, wp) + lre = to_fixed(lre, wp) + lim = to_fixed(lim, wp) + yre = ((lre*afix - lim*bfix)>>wp) - (lre>>1) + yre + yim = ((lre*bfix + lim*afix)>>wp) - (lim>>1) + yim + y = from_man_exp(yre, -wp), from_man_exp(yim, -wp) + + if r and type == 3: + # If re(z) > 0 and abs(z) <= 4, the branches of loggamma(z) + # and log(gamma(z)) coincide. Otherwise, use the zeroth order + # Stirling expansion to compute the correct imaginary part. + y = mpc_sub(y, mpc_ln(r, wp), wp) + zfa = to_float(zprered[0]) + zfb = to_float(zprered[1]) + zfabs = math.hypot(zfa,zfb) + #if not (zfa > 0.0 and zfabs <= 4): + yfb = to_float(y[1]) + u = math.atan2(zfb, zfa) + if zfabs <= 0.5: + gi = 0.577216*zfb - u + else: + gi = -zfb - 0.5*u + zfa*u + zfb*math.log(zfabs) + n = int(math.floor((gi-yfb)/(2*math.pi)+0.5)) + y = (y[0], mpf_add(y[1], mpf_mul_int(mpf_pi(wp), 2*n, wp), wp)) + + if need_reflection: + if type == 0 or type == 2: + A = mpc_mul(mpc_sin_pi(zorig, wp), zorig, wp) + B = (mpf_neg(mpf_pi(wp)), fzero) + if yfinal: + if type == 2: + A = mpc_div(A, yfinal, wp) + else: + A = mpc_mul(A, yfinal, wp) + else: + A = mpc_mul(A, mpc_exp(y, wp), wp) + if r: + B = mpc_mul(B, r, wp) + if type == 0: return mpc_div(B, A, prec, rnd) + if type == 2: return mpc_div(A, B, prec, rnd) + + # Reflection formula for the log-gamma function with correct branch + # http://functions.wolfram.com/GammaBetaErf/LogGamma/16/01/01/0006/ + # LogGamma[z] == -LogGamma[-z] - Log[-z] + + # Sign[Im[z]] Floor[Re[z]] Pi I + Log[Pi] - + # Log[Sin[Pi (z - Floor[Re[z]])]] - + # Pi I (1 - Abs[Sign[Im[z]]]) Abs[Floor[Re[z]]] + if type == 3: + if yfinal: + s1 = mpc_neg(yfinal) + else: + s1 = mpc_neg(y) + # s -= log(-z) + s1 = mpc_sub(s1, mpc_ln(mpc_neg(zorig), wp), wp) + # floor(re(z)) + rezfloor = mpf_floor(zorig[0]) + imzsign = mpf_sign(zorig[1]) + pi = mpf_pi(wp) + t = mpf_mul(pi, rezfloor) + t = mpf_mul_int(t, imzsign, wp) + s1 = (s1[0], mpf_add(s1[1], t, wp)) + s1 = mpc_add_mpf(s1, mpf_ln(pi, wp), wp) + t = mpc_sin_pi(mpc_sub_mpf(zorig, rezfloor), wp) + t = mpc_ln(t, wp) + s1 = mpc_sub(s1, t, wp) + # Note: may actually be unused, because we fall back + # to the mpf_ function for real arguments + if not imzsign: + t = mpf_mul(pi, mpf_floor(rezfloor), wp) + s1 = (s1[0], mpf_sub(s1[1], t, wp)) + return mpc_pos(s1, prec, rnd) + else: + if type == 0: + if r: + return mpc_div(mpc_exp(y, wp), r, prec, rnd) + return mpc_exp(y, prec, rnd) + if type == 2: + if r: + return mpc_div(r, mpc_exp(y, wp), prec, rnd) + return mpc_exp(mpc_neg(y), prec, rnd) + if type == 3: + return mpc_pos(y, prec, rnd) + +def mpf_factorial(x, prec, rnd=round_down): + return mpf_gamma(x, prec, rnd, 1) + +def mpc_factorial(x, prec, rnd=round_down): + return mpc_gamma(x, prec, rnd, 1) + +def mpf_rgamma(x, prec, rnd=round_down): + return mpf_gamma(x, prec, rnd, 2) + +def mpc_rgamma(x, prec, rnd=round_down): + return mpc_gamma(x, prec, rnd, 2) + +def mpf_loggamma(x, prec, rnd=round_down): + sign, man, exp, bc = x + if sign: + raise ComplexResult + return mpf_gamma(x, prec, rnd, 3) + +def mpc_loggamma(z, prec, rnd=round_down): + a, b = z + asign, aman, aexp, abc = a + bsign, bman, bexp, bbc = b + if b == fzero and asign: + re = mpf_gamma(a, prec, rnd, 3) + n = (-aman) >> (-aexp) + im = mpf_mul_int(mpf_pi(prec+10), n, prec, rnd) + return re, im + return mpc_gamma(z, prec, rnd, 3) + +def mpf_gamma_int(n, prec, rnd=round_down): + if n < SMALL_FACTORIAL_CACHE_SIZE: + return mpf_pos(small_factorial_cache[n-1], prec, rnd) + return mpf_gamma(from_int(n), prec, rnd) diff --git a/mpmath/libmp/libelefun.py b/mpmath/libmp/libelefun.py new file mode 100644 index 0000000..80f170a --- /dev/null +++ b/mpmath/libmp/libelefun.py @@ -0,0 +1,1437 @@ +""" +This module implements computation of elementary transcendental +functions (powers, logarithms, trigonometric and hyperbolic +functions, inverse trigonometric and hyperbolic) for real +floating-point numbers. + +For complex and interval implementations of the same functions, +see libmpc and libmpi. + +""" + +import math +import threading +import warnings + +from .backend import BACKEND, MPZ, MPZ_FIVE, MPZ_ONE, MPZ_TWO, MPZ_ZERO +from .libintmath import (giant_steps, ifib, isqrt_fast, lshift, rshift, + sqrt_fixed) +from .libmpf import (ComplexResult, bctable, finf, fnan, fninf, fnone, fone, + from_int, from_man_exp, from_rational, fzero, mpf_abs, + mpf_add, mpf_cmp, mpf_div, mpf_mul, mpf_mul_int, mpf_neg, + mpf_perturb, mpf_pos, mpf_pow_int, mpf_rdiv_int, + mpf_shift, mpf_sign, mpf_sqrt, mpf_sub, negative_rnd, + normalize, reciprocal_rnd, round_ceiling, round_down, + round_up, to_fixed, to_int) + + +local = threading.local() + + +#------------------------------------------------------------------------------- +# Tuning parameters +#------------------------------------------------------------------------------- + +# Cutoff for computing exp from cosh+sinh. This reduces the +# number of terms by half, but also requires a square root which +# is expensive with the pure-Python square root code. +if BACKEND == 'python': + EXP_COSH_CUTOFF = 600 +else: + EXP_COSH_CUTOFF = 400 +# Cutoff for using more than 2 series +EXP_SERIES_U_CUTOFF = 1500 + +# Also basically determined by sqrt +if BACKEND == 'python': + COS_SIN_CACHE_PREC = 400 +else: + COS_SIN_CACHE_PREC = 200 +COS_SIN_CACHE_STEP = 8 +cos_sin_cache = local.cos_sin_cache = {} + +# Number of integer logarithms to cache (for zeta sums) +MAX_LOG_INT_CACHE = 2000 +log_int_cache = local.log_int_cache = {} + +LOG_TAYLOR_PREC = 2500 # Use Taylor series with caching up to this prec +LOG_TAYLOR_SHIFT = 9 # Cache log values in steps of size 2^-N +log_taylor_cache = local.log_taylor_cache = {} +# prec/size ratio of x for fastest convergence in AGM formula +LOG_AGM_MAG_PREC_RATIO = 20 + +ATAN_TAYLOR_PREC = 3000 # Same as for log +ATAN_TAYLOR_SHIFT = 7 # steps of size 2^-N +atan_taylor_cache = local.atan_taylor_cache = {} + + +# ~= next power of two + 20 +cache_prec_steps = [22,22] +for k in range(1, LOG_TAYLOR_PREC.bit_length()+1): + cache_prec_steps += [min(2**k,LOG_TAYLOR_PREC)+20] * 2**(k-1) + + +#----------------------------------------------------------------------------# +# # +# Elementary mathematical constants # +# # +#----------------------------------------------------------------------------# + +def constant_memo(f): + """ + Decorator for caching computed values of mathematical + constants. This decorator should be applied to a + function taking a single argument prec as input and + returning a fixed-point value with the given precision. + """ + f.memo_prec = -1 + f.memo_val = None + def g(prec, **kwargs): + memo_prec = f.memo_prec + if prec <= memo_prec: + return f.memo_val >> (memo_prec-prec) + newprec = int(prec*1.05+10) + f.memo_val = f(newprec, **kwargs) + f.memo_prec = newprec + return f.memo_val >> (newprec-prec) + g.__name__ = f.__name__ + g.__doc__ = f.__doc__ + return g + +def def_mpf_constant(fixed): + """ + Create a function that computes the mpf value for a mathematical + constant, given a function that computes the fixed-point value. + + Assumptions: the constant is positive and has magnitude ~= 1; + the fixed-point function rounds to floor. + """ + def f(prec, rnd=round_down): + wp = prec + 20 + v = fixed(wp) + if rnd in (round_up, round_ceiling): + v += 1 + return normalize(0, v, -wp, v.bit_length(), prec, rnd) + f.__doc__ = fixed.__doc__ + return f + +def bsp_acot(q, a, b, hyperbolic): + if b - a == 1: + a1 = MPZ(2*a + 3) + if hyperbolic or a&1: + return MPZ_ONE, a1 * q**2, a1 + else: + return -MPZ_ONE, a1 * q**2, a1 + m = (a+b)//2 + p1, q1, r1 = bsp_acot(q, a, m, hyperbolic) + p2, q2, r2 = bsp_acot(q, m, b, hyperbolic) + return q2*p1 + r1*p2, q1*q2, r1*r2 + +# the acoth(x) series converges like the geometric series for x^2 +# N = ceil(p*log(2)/(2*log(x))) +def acot_fixed(a, prec, hyperbolic): + """ + Compute acot(a) or acoth(a) for an integer a with binary splitting; see + http://numbers.computation.free.fr/Constants/Algorithms/splitting.html + """ + N = int(0.35 * prec/math.log(a) + 20) + p, q, r = bsp_acot(a, 0,N, hyperbolic) + return ((p+q)<> extraprec) + +# Logarithms of integers are needed for various computations involving +# logarithms, powers, radix conversion, etc + +@constant_memo +def ln2_fixed(prec): + """ + Computes ln(2). This is done with a hyperbolic Machin-type formula, + with binary splitting at high precision. + """ + return machin([(18, 26), (-2, 4801), (8, 8749)], prec, True) + +@constant_memo +def ln10_fixed(prec): + """ + Computes ln(10). This is done with a hyperbolic Machin-type formula. + """ + return machin([(46, 31), (34, 49), (20, 161)], prec, True) + + +r""" +For computation of pi, we use the Chudnovsky series: + + oo + ___ k + 1 \ (-1) (6 k)! (A + B k) + ----- = ) ----------------------- + 12 pi /___ 3 3k+3/2 + (3 k)! (k!) C + k = 0 + +where A, B, and C are certain integer constants. This series adds roughly +14 digits per term. Note that C^(3/2) can be extracted so that the +series contains only rational terms. This makes binary splitting very +efficient. + +The recurrence formulas for the binary splitting were taken from +ftp://ftp.gmplib.org/pub/src/gmp-chudnovsky.c + +Previously, Machin's formula was used at low precision and the AGM iteration +was used at high precision. However, the Chudnovsky series is essentially as +fast as the Machin formula at low precision and in practice about 3x faster +than the AGM at high precision (despite theoretically having a worse +asymptotic complexity), so there is no reason not to use it in all cases. + +""" + +# Constants in Chudnovsky's series +CHUD_A = MPZ(13591409) +CHUD_B = MPZ(545140134) +CHUD_C = MPZ(640320) +CHUD_D = MPZ(12) + +def bs_chudnovsky(a, b, level, verbose): + """ + Computes the sum from a to b of the series in the Chudnovsky + formula. Returns g, p, q where p/q is the sum as an exact + fraction and g is a temporary value used to save work + for recursive calls. + """ + if b-a == 1: + g = MPZ((6*b-5)*(2*b-1)*(6*b-1)) + p = b**3 * CHUD_C**3 // 24 + q = (-1)**b * g * (CHUD_A+CHUD_B*b) + else: + if verbose and level < 4: + print(" binary splitting", a, b) + mid = (a+b)//2 + g1, p1, q1 = bs_chudnovsky(a, mid, level+1, verbose) + g2, p2, q2 = bs_chudnovsky(mid, b, level+1, verbose) + p = p1*p2 + g = g1*g2 + q = q1*p2 + q2*g1 + return g, p, q + +@constant_memo +def pi_fixed(prec, verbose=False, verbose_base=None): + """ + Compute floor(pi * 2**prec) as a big integer. + + This is done using Chudnovsky's series (see comments in + libelefun.py for details). + """ + # The Chudnovsky series gives 14.18 digits per term + N = int(prec/3.3219280948/14.181647462 + 2) + if verbose: + print("binary splitting with N =", N) + g, p, q = bs_chudnovsky(0, N, 0, verbose) + sqrtC = isqrt_fast(CHUD_C<<(2*prec)) + v = p*CHUD_C*sqrtC//((q+CHUD_A*p)*CHUD_D) + return v + +def degree_fixed(prec): + return pi_fixed(prec)//180 + +def bspe(a, b): + """ + Sum series for exp(1)-1 between a, b, returning the result + as an exact fraction (p, q). + """ + if b-a == 1: + return MPZ_ONE, MPZ(b) + m = (a+b)//2 + p1, q1 = bspe(a, m) + p2, q2 = bspe(m, b) + return p1*q2+p2, q1*q2 + +@constant_memo +def e_fixed(prec): + """ + Computes exp(1). This is done using the ordinary Taylor series for + exp, with binary splitting. For a description of the algorithm, + see: + + http://numbers.computation.free.fr/Constants/ + Algorithms/splitting.html + """ + # Slight overestimate of N needed for 1/N! < 2**(-prec) + # This could be tightened for large N. + N = int(1.1*prec/math.log(prec) + 20) + p, q = bspe(0,N) + return ((p+q)<> 11 + +mpf_phi = def_mpf_constant(phi_fixed) +mpf_pi = def_mpf_constant(pi_fixed) +mpf_e = def_mpf_constant(e_fixed) +mpf_degree = def_mpf_constant(degree_fixed) +mpf_ln2 = def_mpf_constant(ln2_fixed) +mpf_ln10 = def_mpf_constant(ln10_fixed) + + +@constant_memo +def ln_sqrt2pi_fixed(prec): + wp = prec + 10 + # ln(sqrt(2*pi)) = ln(2*pi)/2 + return to_fixed(mpf_ln(mpf_shift(mpf_pi(wp), 1), wp), prec-1) + +@constant_memo +def sqrtpi_fixed(prec): + return sqrt_fixed(pi_fixed(prec), prec) + +mpf_sqrtpi = def_mpf_constant(sqrtpi_fixed) +mpf_ln_sqrt2pi = def_mpf_constant(ln_sqrt2pi_fixed) + + +#----------------------------------------------------------------------------# +# # +# Powers # +# # +#----------------------------------------------------------------------------# + +def mpf_pow(s, t, prec, rnd=round_down): + """ + Compute s**t. Raises ComplexResult if s is negative and t is + fractional. + """ + ssign, sman, sexp, sbc = s + tsign, tman, texp, tbc = t + if ssign and texp < 0: + raise ComplexResult("negative number raised to a fractional power") + if texp >= 0: + return mpf_pow_int(s, (-1)**tsign * (tman<> pbc)] + if pbc > workprec: + pm = pm >> (pbc-workprec) + pe += pbc - workprec + pbc = workprec + n -= 1 + if not n: + break + y = y*y + exp = exp+exp + bc = bc + bc - 2 + bc = bc + bctable[int(y >> bc)] + if bc > workprec: + y = y >> (bc-workprec) + exp += bc - workprec + bc = workprec + n = n // 2 + return pm, pe + +# froot(s, n, prec, rnd) computes the real n-th root of a +# positive mpf tuple s. +# To compute the root we start from a 50-bit estimate for r +# generated with ordinary floating-point arithmetic, and then refine +# the value to full accuracy using the iteration + +# 1 / y \ +# r = --- | (n-1) * r + ---------- | +# n+1 n \ n r_n**(n-1) / + +# which is simply Newton's method applied to the equation r**n = y. +# With giant_steps(start, prec+extra) = [p0,...,pm, prec+extra] +# and y = man * 2**-shift one has +# (man * 2**exp)**(1/n) = +# y**(1/n) * 2**(start-prec/n) * 2**(p0-start) * ... * 2**(prec+extra-pm) * +# 2**((exp+shift-(n-1)*prec)/n -extra)) +# The last factor is accounted for in the last line of froot. + +def nthroot_fixed(y, n, prec, exp1): + start = 50 + try: + y1 = rshift(y, prec - n*start) + r = MPZ(y1**(1.0/n)) + except OverflowError: + y1 = from_int(y1, start) + fn = from_int(n) + fn = mpf_rdiv_int(1, fn, start) + r = mpf_pow(y1, fn, start) + r = to_int(r) + extra = 10 + extra1 = n + prevp = start + for p in giant_steps(start, prec+extra): + pm, pe = int_pow_fixed(r, n-1, prevp) + r2 = rshift(pm, (n-1)*prevp - p - pe - extra1) + B = lshift(y, 2*p-prec+extra1)//r2 + r = (B + (n-1) * lshift(r, p-prevp))//n + prevp = p + return r + +def mpf_nthroot(s, n, prec, rnd=round_down): + """nth-root of a positive number + + Use the Newton method when faster, otherwise use x**(1/n) + """ + sign, man, exp, bc = s + if sign: + raise ComplexResult("nth root of a negative number") + if not man: + if s == fnan: + return fnan + if s == fzero: + if n > 0: + return fzero + if n == 0: + return fone + return finf + # Infinity + if not n: + return fnan + if n < 0: + return fzero + return finf + flag_inverse = False + if n < 2: + if n == 0: + return fone + if n == 1: + return mpf_pos(s, prec, rnd) + if n == -1: + return mpf_div(fone, s, prec, rnd) + # n < 0 + rnd = reciprocal_rnd[rnd] + flag_inverse = True + extra_inverse = 5 + prec += extra_inverse + n = -n + if n > 20 and (n >= 20000 or prec < int(233 + 28.3 * n**0.62)): + prec2 = prec + 10 + fn = from_int(n) + nth = mpf_rdiv_int(1, fn, prec2) + r = mpf_pow(s, nth, prec2, rnd) + s = normalize(r[0], r[1], r[2], r[3], prec, rnd) + if flag_inverse: + return mpf_div(fone, s, prec-extra_inverse, rnd) + else: + return s + # Convert to a fixed-point number with prec2 bits. + prec2 = prec + 2*n - (prec%n) + # a few tests indicate that + # for 10 < n < 10**4 a bit more precision is needed + if n > 10: + prec2 += prec2//10 + prec2 = prec2 - prec2%n + # Mantissa may have more bits than we need. Trim it down. + shift = bc - prec2 + # Adjust exponents to make prec2 and exp+shift multiples of n. + sign1 = 0 + es = exp+shift + if es < 0: + sign1 = 1 + es = -es + if sign1: + shift += es%n + else: + shift -= es%n + man = rshift(man, shift) + extra = 10 + exp1 = ((exp+shift-(n-1)*prec2)//n) - extra + rnd_shift = 0 + if flag_inverse: + if rnd == 'u' or rnd == 'c': + rnd_shift = 1 + else: + if rnd == 'd' or rnd == 'f': + rnd_shift = 1 + man = nthroot_fixed(man+rnd_shift, n, prec2, exp1) + s = from_man_exp(man, exp1, prec, rnd) + if flag_inverse: + return mpf_div(fone, s, prec-extra_inverse, rnd) + else: + return s + +def mpf_cbrt(s, prec, rnd=round_down): + """cubic root of a positive number""" + return mpf_nthroot(s, 3, prec, rnd) + +#----------------------------------------------------------------------------# +# # +# Logarithms # +# # +#----------------------------------------------------------------------------# + + +def log_int_fixed(n, prec, ln2=None): + """ + Fast computation of log(n), caching the value for small n, + intended for zeta sums. + """ + if n in log_int_cache: + value, vprec = log_int_cache[n] + if vprec >= prec: + return value >> (vprec - prec) + wp = prec + 10 + assert wp > LOG_TAYLOR_SHIFT + v = to_fixed(mpf_ln(from_int(n), wp+5), wp) + if n < MAX_LOG_INT_CACHE: + log_int_cache[n] = (v, wp) + return v >> (wp-prec) + +def agm_fixed(a, b, prec): + """ + Fixed-point computation of agm(a,b), assuming + a, b both close to unit magnitude. + """ + i = 0 + while 1: + anew = (a+b)>>1 + if i > 4 and abs(a-anew) < 8: + return a + b = isqrt_fast(a*b) + a = anew + i += 1 + return a + +def log_agm(x, prec): + """ + Fixed-point computation of -log(x) = log(1/x), suitable + for large precision. It is required that 0 < x < 1. The + algorithm used is the Sasaki-Kanada formula + + -log(x) = pi/agm(theta2(x)^2,theta3(x)^2). [1] + + For faster convergence in the theta functions, x should + be chosen closer to 0. + + Guard bits must be added by the caller. + + HYPOTHESIS: if x = 2^(-n), n bits need to be added to + account for the truncation to a fixed-point number, + and this is the only significant cancellation error. + + The number of bits lost to roundoff is small and can be + considered constant. + + [1] Richard P. Brent, "Fast Algorithms for High-Precision + Computation of Elementary Functions (extended abstract)", + http://wwwmaths.anu.edu.au/~brent/pd/RNC7-Brent.pdf + + """ + x2 = (x*x) >> prec + # Compute jtheta2(x)**2 + s = a = b = x2 + while a: + b = (b*x2) >> prec + a = (a*b) >> prec + s += a + s += (MPZ_ONE<>(prec-2) + s = (s*isqrt_fast(x<>prec + # Compute jtheta3(x)**2 + t = a = b = x + while a: + b = (b*x2) >> prec + a = (a*b) >> prec + t += a + t = (MPZ_ONE<>prec + # Final formula + p = agm_fixed(s, t, prec) + return (pi_fixed(prec) << prec) // p + +def log_taylor(x, prec, r=0): + """ + Fixed-point calculation of log(x). It is assumed that x is close + enough to 1 for the Taylor series to converge quickly. Convergence + can be improved by specifying r > 0 to compute + log(x^(1/2^r))*2^r, at the cost of performing r square roots. + + The caller must provide sufficient guard bits. + """ + for i in range(r): + x = isqrt_fast(x<> prec + v4 = (v2*v2) >> prec + s0 = v + s1 = v//3 + v = (v*v4) >> prec + k = 5 + while v: + s0 += v // k + k += 2 + s1 += v // k + v = (v*v4) >> prec + k += 2 + s1 = (s1*v2) >> prec + s = (s0+s1) << (1+r) + if sign: + return -s + return s + +def log_taylor_cached(x, prec): + """ + Fixed-point computation of log(x), assuming x in (0.5, 2) + and prec <= LOG_TAYLOR_PREC. + """ + n = x >> (prec-LOG_TAYLOR_SHIFT) + cached_prec = cache_prec_steps[prec] + dprec = cached_prec - prec + if (n, cached_prec) in log_taylor_cache: + a, log_a = log_taylor_cache[n, cached_prec] + else: + a = n << (cached_prec - LOG_TAYLOR_SHIFT) + log_a = log_taylor(a, cached_prec, 8) + log_taylor_cache[n, cached_prec] = (a, log_a) + a >>= dprec + log_a >>= dprec + u = ((x - a) << prec) // a + v = (u << prec) // ((MPZ_TWO << prec) + u) + v2 = (v*v) >> prec + v4 = (v2*v2) >> prec + s0 = v + s1 = v//3 + v = (v*v4) >> prec + k = 5 + while v: + s0 += v//k + k += 2 + s1 += v//k + v = (v*v4) >> prec + k += 2 + s1 = (s1*v2) >> prec + s = (s0+s1) << 1 + return log_a + s + +def mpf_ln(x, prec, rnd=round_down): + """ + Compute the natural logarithm of the mpf value x. If x is negative, + ComplexResult is raised. + """ + sign, man, exp, bc = x + #------------------------------------------------------------------ + # Handle special values + if not man: + if x == fzero: return fninf + if x == finf: return finf + if x == fnan: return fnan + if sign: + raise ComplexResult("logarithm of a negative number") + wp = prec + 20 + #------------------------------------------------------------------ + # Handle log(2^n) = log(n)*2. + # Here we catch the only possible exact value, log(1) = 0 + if man == 1: + if not exp: + return fzero + return from_man_exp(exp*ln2_fixed(wp), -wp, prec, rnd) + mag = exp+bc + abs_mag = abs(mag) + #------------------------------------------------------------------ + # Handle x = 1+eps, where log(x) ~ x. We need to check for + # cancellation when moving to fixed-point math and compensate + # by increasing the precision. Note that abs_mag in (0, 1) <=> + # 0.5 < x < 2 and x != 1 + if abs_mag <= 1: + # Calculate t = x-1 to measure distance from 1 in bits + tsign = 1-abs_mag + if tsign: + tman = (MPZ_ONE< wp: + t = normalize(tsign, tman, abs_mag-bc, tbc, tbc, 'n') + return mpf_perturb(t, tsign, prec, rnd) + else: + wp += cancellation + + # If close enough to 1, use Taylor series + # even in the AGM precision range, since the Taylor series + # converges rapidly. + # Taylor = AGM when O~(prec) = O~(prec^2/cancellation) where cancellation + # is greater than or equal to precision + wpb = wp.bit_length() + if wpb <= cancellation: # possibly include constant (big integer operations) + a = to_fixed(x, wp) + s = log_taylor(a, wp) + return from_man_exp(s, -wp, prec, rnd) + + #------------------------------------------------------------------ + # Another special case: + # n*log(2) is a good enough approximation + if abs_mag > 10000: + if abs_mag.bit_length() > wp: + return from_man_exp(exp*ln2_fixed(wp), -wp, prec, rnd) + #------------------------------------------------------------------ + # General case. + # Perform argument reduction using log(x) = log(x*2^n) - n*log(2): + # If we are in the Taylor precision range, choose magnitude 0 or 1. + # If we are in the AGM precision range, choose magnitude -m for + # some large m; benchmarking on one machine showed m = prec/20 to be + # optimal between 1000 and 100,000 digits. + if wp <= LOG_TAYLOR_PREC: + m = log_taylor_cached(lshift(man, wp-bc), wp) + if mag: + m += mag*ln2_fixed(wp) + else: + optimal_mag = -wp//LOG_AGM_MAG_PREC_RATIO + n = optimal_mag - mag + x = mpf_shift(x, n) + wp += (-optimal_mag) + m = -log_agm(to_fixed(x, wp), wp) + m -= n*ln2_fixed(wp) + return from_man_exp(m, -wp, prec, rnd) + +mpf_log = mpf_ln # deprecated alias + +def mpf_log1p(x, prec, rnd=round_down): + """ + Computes log(1+x) accurately. + """ + wp = prec + 20 + wp2 = wp*2 + _, man, exp, bc = x + if exp + bc < -wp and (man or exp): + # x - x**2/2 + x2 = mpf_sub(fone, mpf_shift(x, -1), wp2, rnd) + return mpf_mul(x, x2, wp, rnd) + return mpf_ln(mpf_add(fone, x, wp2), wp, rnd) + +def mpf_log_hypot(a, b, prec, rnd): + """ + Computes log(sqrt(a^2+b^2)) accurately. + """ + # If either a or b is inf/nan/0, assume it to be a + if not b[1]: + a, b = b, a + # a is inf/nan/0 + if not a[1]: + # both are inf/nan/0 + if not b[1]: + if a == b == fzero: + return fninf + if fnan in (a, b): + return fnan + # at least one term is (+/- inf)^2 + return finf + # only a is inf/nan/0 + if a == fzero: + # log(sqrt(0+b^2)) = log(|b|) + return mpf_ln(mpf_abs(b), prec, rnd) + if a == fnan: + return fnan + return finf + # Exact + a2 = mpf_mul(a,a) + b2 = mpf_mul(b,b) + extra = 20 + # Not exact + h2 = mpf_add(a2, b2, prec+extra) + cancelled = mpf_add(h2, fnone, 10) + mag_cancelled = cancelled[2]+cancelled[3] + # Just redo the sum exactly if necessary (could be smarter + # and avoid memory allocation when a or b is precisely 1 + # and the other is tiny...) + if cancelled == fzero or mag_cancelled < -extra//2: + h2 = mpf_add(a2, b2, prec+extra-min(a2[2],b2[2])) + return mpf_shift(mpf_ln(h2, prec, rnd), -1) + + +#---------------------------------------------------------------------- +# Inverse tangent +# + +def atan_newton(x, prec): + if prec >= 100: + r = math.atan(int((x>>(prec-53)))/2.0**53) + else: + r = math.atan(int(x)/2.0**prec) + prevp = 50 + r = MPZ(int(r * 2.0**53) >> (53-prevp)) + extra_p = 50 + for wp in giant_steps(prevp, prec): + wp += extra_p + r = r << (wp-prevp) + cos, sin = cos_sin_fixed(r, wp) + tan = (sin << wp) // cos + a = ((tan-rshift(x, prec-wp)) << wp) // ((MPZ_ONE<>wp)) + r = r - a + prevp = wp + return rshift(r, prevp-prec) + +def atan_taylor_get_cached(n, prec): + # Taylor series with caching wins up to huge precisions + # To avoid unnecessary precomputation at low precision, we + # do it in steps + # Round to next power of 2 + prec2 = (1<<(prec-1).bit_length()) + 20 + dprec = prec2 - prec + if (n, prec2) in atan_taylor_cache: + a, atan_a = atan_taylor_cache[n, prec2] + else: + a = n << (prec2 - ATAN_TAYLOR_SHIFT) + atan_a = atan_newton(a, prec2) + atan_taylor_cache[n, prec2] = (a, atan_a) + return (a >> dprec), (atan_a >> dprec) + +def atan_taylor(x, prec): + n = (x >> (prec-ATAN_TAYLOR_SHIFT)) + a, atan_a = atan_taylor_get_cached(n, prec) + d = x - a + s0 = v = (d << prec) // ((a**2 >> prec) + (a*d >> prec) + (MPZ_ONE << prec)) + v2 = (v**2 >> prec) + v4 = (v2 * v2) >> prec + s1 = v//3 + v = (v * v4) >> prec + k = 5 + while v: + s0 += v // k + k += 2 + s1 += v // k + v = (v * v4) >> prec + k += 2 + s1 = (s1 * v2) >> prec + s = s0 - s1 + return atan_a + s + +def atan_inf(sign, prec, rnd): + if not sign: + return mpf_shift(mpf_pi(prec, rnd), -1) + return mpf_neg(mpf_shift(mpf_pi(prec, negative_rnd[rnd]), -1)) + +def mpf_atan(x, prec, rnd=round_down): + sign, man, exp, bc = x + if not man: + if x == fzero: return fzero + if x == finf: return atan_inf(0, prec, rnd) + if x == fninf: return atan_inf(1, prec, rnd) + return fnan + mag = exp + bc + # Essentially infinity + if mag > prec+20: + return atan_inf(sign, prec, rnd) + # Essentially ~ x + if -mag > prec+20: + return mpf_perturb(x, 1-sign, prec, rnd) + wp = prec + 30 + abs(mag) + # For large x, use atan(x) = pi/2 - atan(1/x) + if mag >= 2: + x = mpf_rdiv_int(1, x, wp) + reciprocal = True + else: + reciprocal = False + t = to_fixed(x, wp) + if sign: + t = -t + if wp < ATAN_TAYLOR_PREC: + a = atan_taylor(t, wp) + else: + a = atan_newton(t, wp) + if reciprocal: + a = ((pi_fixed(wp)>>1)+1) - a + if sign: + a = -a + return from_man_exp(a, -wp, prec, rnd) + +# TODO: cleanup the special cases +def mpf_atan2(y, x, prec, rnd=round_down): + xsign, xman, xexp, xbc = x + ysign, yman, yexp, ybc = y + if not yman: + if y == fzero and x != fnan: + if mpf_sign(x) >= 0: + return fzero + return mpf_pi(prec, rnd) + if y in (finf, fninf): + if x == finf: + if y == finf: + return mpf_shift(mpf_pi(prec, rnd), -2) + rnd = negative_rnd[rnd] + return mpf_neg(mpf_shift(mpf_pi(prec, rnd), -2)) + if x == fninf: + if y == finf: + return mpf_shift(mpf_mul_int(mpf_pi(prec, rnd), + 3, prec, rnd), -2) + rnd = negative_rnd[rnd] + return mpf_neg(mpf_shift(mpf_mul_int(mpf_pi(prec, rnd), + 3, prec, rnd), -2)) + # pi/2 + if y == finf: + return mpf_shift(mpf_pi(prec, rnd), -1) + # -pi/2 + return mpf_neg(mpf_shift(mpf_pi(prec, negative_rnd[rnd]), -1)) + return fnan + if ysign: + return mpf_neg(mpf_atan2(mpf_neg(y), x, prec, negative_rnd[rnd])) + if not xman: + if x == fnan: + return fnan + if x == finf: + return fzero + if x == fninf: + return mpf_pi(prec, rnd) + if y == fzero: + return fzero + return mpf_shift(mpf_pi(prec, rnd), -1) + tquo = mpf_atan(mpf_div(y, x, prec+4), prec+4) + if xsign: + return mpf_add(mpf_pi(prec+4), tquo, prec, rnd) + else: + return mpf_pos(tquo, prec, rnd) + +def mpf_asin(x, prec, rnd=round_down): + sign, man, exp, bc = x + if bc+exp > 0 and x not in (fone, fnone): + raise ComplexResult("asin(x) is real only for -1 <= x <= 1") + # asin(x) = 2*atan(x/(1+sqrt(1-x**2))) + wp = prec + 15 + a = mpf_mul(x, x) + b = mpf_add(fone, mpf_sqrt(mpf_sub(fone, a, wp), wp), wp) + c = mpf_div(x, b, wp) + return mpf_shift(mpf_atan(c, prec, rnd), 1) + +def mpf_acos(x, prec, rnd=round_down): + # acos(x) = 2*atan(sqrt(1-x**2)/(1+x)) + sign, man, exp, bc = x + if bc + exp > 0: + if x not in (fone, fnone): + raise ComplexResult("acos(x) is real only for -1 <= x <= 1") + if x == fnone: + return mpf_pi(prec, rnd) + wp = prec + 15 + a = mpf_mul(x, x) + b = mpf_sqrt(mpf_sub(fone, a, wp), wp) + c = mpf_div(b, mpf_add(fone, x, wp), wp) + return mpf_shift(mpf_atan(c, prec, rnd), 1) + +def mpf_asinh(x, prec, rnd=round_down): + wp = prec + 20 + sign, man, exp, bc = x + mag = exp+bc + if mag < -8: + if mag < -wp: + return mpf_perturb(x, 1-sign, prec, rnd) + wp += (-mag) + # asinh(x) = log(x+sqrt(x**2+1)) + # use reflection symmetry to avoid cancellation + q = mpf_sqrt(mpf_add(mpf_mul(x, x), fone, wp), wp) + q = mpf_add(mpf_abs(x), q, wp) + if sign: + return mpf_neg(mpf_ln(q, prec, negative_rnd[rnd])) + else: + return mpf_ln(q, prec, rnd) + +def mpf_acosh(x, prec, rnd=round_down): + # acosh(x) = log(x+sqrt(x**2-1)) + wp = prec + 15 + if mpf_cmp(x, fone) == -1: + raise ComplexResult("acosh(x) is real only for x >= 1") + q = mpf_sqrt(mpf_add(mpf_mul(x,x), fnone, wp), wp) + return mpf_ln(mpf_add(x, q, wp), prec, rnd) + +def mpf_atanh(x, prec, rnd=round_down): + # atanh(x) = log((1+x)/(1-x))/2 + sign, man, exp, bc = x + if (not man) and exp: + if x in (fzero, fnan): + return x + raise ComplexResult("atanh(x) is real only for -1 <= x <= 1") + mag = bc + exp + if mag > 0: + if mag == 1 and man == 1: + return [finf, fninf][sign] + raise ComplexResult("atanh(x) is real only for -1 <= x <= 1") + wp = prec + 15 + if mag < -8: + if mag < -wp: + return mpf_perturb(x, sign, prec, rnd) + wp += (-mag) + a = mpf_add(x, fone, wp) + b = mpf_sub(fone, x, wp) + return mpf_shift(mpf_ln(mpf_div(a, b, wp), prec, rnd), -1) + +def mpf_fibonacci(x, prec, rnd=round_down): + sign, man, exp, bc = x + if not man: + if x == fninf: + return fnan + return x + # F(2^n) ~= 2^(2^n) + size = abs(exp+bc) + if exp >= 0 and not sign: + # Exact + if size < 10 or size <= prec.bit_length(): + return from_int(ifib(to_int(x)), prec, rnd) + # Use the modified Binet formula + wp = prec + size + 20 + a = mpf_phi(wp) + b = mpf_add(mpf_shift(a, 1), fnone, wp) + u = mpf_pow(a, x, wp) + v = mpf_cos_pi(x, wp) + v = mpf_div(v, u, wp) + u = mpf_sub(u, v, wp) + u = mpf_div(u, b, prec, rnd) + return u + + +#------------------------------------------------------------------------------- +# Exponential-type functions +#------------------------------------------------------------------------------- + +def exponential_series(x, prec, type=0): + """ + Taylor series for cosh/sinh or cos/sin. + + type = 0 -- returns exp(x) (slightly faster than cosh+sinh) + type = 1 -- returns (cosh(x), sinh(x)) + type = 2 -- returns (cos(x), sin(x)) + """ + if x < 0: + x = -x + sign = 1 + else: + sign = 0 + r = int(0.5*prec**0.5) + xmag = x.bit_length() - prec + r = max(0, xmag + r) + extra = 10 + 2*max(r,-xmag) + wp = prec + extra + x <<= (extra - r) + one = MPZ_ONE << wp + alt = (type == 2) + if prec < EXP_SERIES_U_CUTOFF: + x2 = a = (x*x) >> wp + x4 = (x2*x2) >> wp + s0 = s1 = MPZ_ZERO + k = 2 + while a: + a //= (k-1)*k; s0 += a; k += 2 + a //= (k-1)*k; s1 += a; k += 2 + a = (a*x4) >> wp + s1 = (x2*s1) >> wp + if alt: + c = s1 - s0 + one + else: + c = s1 + s0 + one + else: + u = int(0.3*prec**0.35) + x2 = a = (x*x) >> wp + xpowers = [one, x2] + for i in range(1, u): + xpowers.append((xpowers[-1]*x2)>>wp) + sums = [MPZ_ZERO] * u + k = 2 + while a: + for i in range(u): + a //= (k-1)*k + if alt and k & 2: sums[i] -= a + else: sums[i] += a + k += 2 + a = (a*xpowers[-1]) >> wp + for i in range(1, u): + sums[i] = (sums[i]*xpowers[i]) >> wp + c = sum(sums) + one + if type == 0: + s = isqrt_fast(c*c - (one<> wp + return v >> extra + else: + # Repeatedly apply the double-angle formula + # cosh(2*x) = 2*cosh(x)^2 - 1 + # cos(2*x) = 2*cos(x)^2 - 1 + pshift = wp-1 + for i in range(r): + c = ((c*c) >> pshift) - one + # With the abs, this is the same for sinh and sin + s = isqrt_fast(abs((one<>extra), (s>>extra) + +def exp_basecase(x, prec): + """ + Compute exp(x) as a fixed-point number. Works for any x, + but for speed should have |x| < 1. For an arbitrary number, + use exp(x) = exp(x-m*log(2)) * 2^m where m = floor(x/log(2)). + """ + if prec > EXP_COSH_CUTOFF: + return exponential_series(x, prec, 0) + r = int(prec**0.5) + prec += r + s0 = s1 = (MPZ_ONE << prec) + k = 2 + a = x2 = (x*x) >> prec + while a: + a //= k; s0 += a; k += 1 + a //= k; s1 += a; k += 1 + a = (a*x2) >> prec + s1 = (s1*x) >> prec + s = s0 + s1 + u = r + while r: + s = (s*s) >> prec + r -= 1 + return s >> u + +def exp_expneg_basecase(x, prec): + """ + Computation of exp(x), exp(-x) + """ + if prec > EXP_COSH_CUTOFF: + cosh, sinh = exponential_series(x, prec, 1) + return cosh+sinh, cosh-sinh + a = exp_basecase(x, prec) + b = (MPZ_ONE << (prec+prec)) // a + return a, b + +def cos_sin_basecase(x, prec): + """ + Compute cos(x), sin(x) as fixed-point numbers, assuming x + in [0, pi/2). For an arbitrary number, use x' = x - m*(pi/2) + where m = floor(x/(pi/2)) along with quarter-period symmetries. + """ + if prec > COS_SIN_CACHE_PREC: + return exponential_series(x, prec, 2) + precs = prec - COS_SIN_CACHE_STEP + t = x >> precs + n = int(t) + if n not in cos_sin_cache: + w = t<<(10+COS_SIN_CACHE_PREC-COS_SIN_CACHE_STEP) + cos_t, sin_t = exponential_series(w, 10+COS_SIN_CACHE_PREC, 2) + cos_sin_cache[n] = (cos_t>>10), (sin_t>>10) + cos_t, sin_t = cos_sin_cache[n] + offset = COS_SIN_CACHE_PREC - prec + cos_t >>= offset + sin_t >>= offset + x -= t << precs + cos = MPZ_ONE << prec + sin = x + k = 2 + a = -((x*x) >> prec) + while a: + a //= k; cos += a; k += 1; a = (a*x) >> prec + a //= k; sin += a; k += 1; a = -((a*x) >> prec) + return ((cos*cos_t-sin*sin_t) >> prec), ((sin*cos_t+cos*sin_t) >> prec) + +def mpf_exp(x, prec, rnd=round_down): + sign, man, exp, bc = x + if man: + mag = bc + exp + wp = prec + 14 + if sign: + man = -man + # TODO: the best cutoff depends on both x and the precision. + if prec > 600 and exp >= 0: + # Need about log2(exp(n)) ~= 1.45*mag extra precision + e = mpf_e(wp+int(1.45*mag)) + return mpf_pow_int(e, man<= 2 + if mag > 1: + # For large arguments: exp(2^mag*(1+eps)) = + # exp(2^mag)*exp(2^mag*eps) = exp(2^mag)*(1 + 2^mag*eps + ...) + # so about mag extra bits is required. + wpmod = wp + mag + offset = exp + wpmod + if offset >= 0: + t = man << offset + else: + t = man >> (-offset) + lg2 = ln2_fixed(wpmod) + n, t = divmod(t, lg2) + n = int(n) + t >>= mag + else: + offset = exp + wp + if offset >= 0: + t = man << offset + else: + t = man >> (-offset) + n = 0 + man = exp_basecase(t, wp) + return from_man_exp(man, n-wp, prec, rnd) + if not exp: + return fone + if x == fninf: + return fzero + return x + + +def mpf_cosh_sinh(x, prec, rnd=round_down, tanh=0): + """Simultaneously compute (cosh(x), sinh(x)) for real x""" + sign, man, exp, bc = x + if (not man) and exp: + if tanh: + if x == finf: return fone + if x == fninf: return fnone + return fnan + if x == finf: return (finf, finf) + if x == fninf: return (finf, fninf) + return fnan, fnan + mag = exp+bc + wp = prec+14 + if mag < -4: + # Extremely close to 0, sinh(x) ~= x and cosh(x) ~= 1 + if mag < -wp: + if tanh: + return mpf_perturb(x, 1-sign, prec, rnd) + cosh = mpf_perturb(fone, 0, prec, rnd) + sinh = mpf_perturb(x, sign, prec, rnd) + return cosh, sinh + # Fix for cancellation when computing sinh + wp += (-mag) + # Does exp(-2*x) vanish? + if mag > 10: + if 3*(1<<(mag-1)) > wp: + # XXX: rounding + if tanh: + return mpf_perturb([fone,fnone][sign], 1-sign, prec, rnd) + c = s = mpf_shift(mpf_exp(mpf_abs(x), prec, rnd), -1) + if sign: + s = mpf_neg(s) + return c, s + # |x| > 1 + if mag > 1: + wpmod = wp + mag + offset = exp + wpmod + if offset >= 0: + t = man << offset + else: + t = man >> (-offset) + lg2 = ln2_fixed(wpmod) + n, t = divmod(t, lg2) + n = int(n) + t >>= mag + else: + offset = exp + wp + if offset >= 0: + t = man << offset + else: + t = man >> (-offset) + n = 0 + a, b = exp_expneg_basecase(t, wp) + # TODO: optimize division precision + cosh = a + (b>>(2*n)) + sinh = a - (b>>(2*n)) + if sign: + sinh = -sinh + if tanh: + man = (sinh << wp) // cosh + return from_man_exp(man, -wp, prec, rnd) + else: + cosh = from_man_exp(cosh, n-wp-1, prec, rnd) + sinh = from_man_exp(sinh, n-wp-1, prec, rnd) + return cosh, sinh + + +def mod_pi2(man, exp, mag, wp): + # Reduce to standard interval + if mag > 0: + i = 0 + while 1: + cancellation_prec = 20 << i + wpmod = wp + mag + cancellation_prec + pi2 = pi_fixed(wpmod-1) + pi4 = pi2 >> 1 + offset = wpmod + exp + if offset >= 0: + t = man << offset + else: + t = man >> (-offset) + n, y = divmod(t, pi2) + if y > pi4: + small = pi2 - y + else: + small = y + if small >> (wp+mag-10): + n = int(n) + t = y >> mag + wp = wpmod - mag + break + i += 1 + else: + wp += (-mag) + offset = exp + wp + if offset >= 0: + t = man << offset + else: + t = man >> (-offset) + n = 0 + return t, n, wp + + +def mpf_cos_sin(x, prec, rnd=round_down, which=0, pi=False): + """ + which: + 0 -- return cos(x), sin(x) + 1 -- return cos(x) + 2 -- return sin(x) + 3 -- return tan(x) + + if pi=True, compute for pi*x + """ + sign, man, exp, bc = x + if not man: + if exp: + c, s = fnan, fnan + else: + c, s = fone, fzero + if which == 0: return c, s + if which == 1: return c + if which == 2: return s + if which == 3: return s + + mag = bc + exp + wp = prec + 10 + + # Extremely small? + if mag < 0: + if mag < -wp: + if pi: + x = mpf_mul(x, mpf_pi(wp)) + c = mpf_perturb(fone, 1, prec, rnd) + s = mpf_perturb(x, 1-sign, prec, rnd) + if which == 0: return c, s + if which == 1: return c + if which == 2: return s + if which == 3: return mpf_perturb(x, sign, prec, rnd) + if pi: + if exp >= -1: + if exp == -1: + c = fzero + s = (fone, fnone)[bool(man & 2) ^ sign] + elif exp == 0: + c, s = (fnone, fzero) + else: + c, s = (fone, fzero) + if which == 0: return c, s + if which == 1: return c + if which == 2: return s + if which == 3: return mpf_div(s, c, prec, rnd) + # Subtract nearest half-integer (= mod by pi/2) + n = ((man >> (-exp-2)) + 1) >> 1 + man = man - (n << (-exp-1)) + mag2 = man.bit_length() + exp + wp = prec + 10 - mag2 + offset = exp + wp + if offset >= 0: + t = man << offset + else: + t = man >> (-offset) + t = (t*pi_fixed(wp)) >> wp + else: + t, n, wp = mod_pi2(man, exp, mag, wp) + c, s = cos_sin_basecase(t, wp) + m = n & 3 + if m == 1: c, s = -s, c + elif m == 2: c, s = -c, -s + elif m == 3: c, s = s, -c + if sign: + s = -s + if which == 0: + c = from_man_exp(c, -wp, prec, rnd) + s = from_man_exp(s, -wp, prec, rnd) + return c, s + if which == 1: + return from_man_exp(c, -wp, prec, rnd) + if which == 2: + return from_man_exp(s, -wp, prec, rnd) + if which == 3: + return from_rational(s, c, prec, rnd) + +def mpf_cos(x, prec, rnd=round_down): return mpf_cos_sin(x, prec, rnd, 1) +def mpf_sin(x, prec, rnd=round_down): return mpf_cos_sin(x, prec, rnd, 2) +def mpf_tan(x, prec, rnd=round_down): return mpf_cos_sin(x, prec, rnd, 3) +def mpf_cos_sin_pi(x, prec, rnd=round_down): return mpf_cos_sin(x, prec, rnd, 0, 1) +def mpf_cos_pi(x, prec, rnd=round_down): return mpf_cos_sin(x, prec, rnd, 1, 1) +def mpf_sin_pi(x, prec, rnd=round_down): return mpf_cos_sin(x, prec, rnd, 2, 1) +def mpf_cosh(x, prec, rnd=round_down): return mpf_cosh_sinh(x, prec, rnd)[0] +def mpf_sinh(x, prec, rnd=round_down): return mpf_cosh_sinh(x, prec, rnd)[1] +def mpf_tanh(x, prec, rnd=round_down): return mpf_cosh_sinh(x, prec, rnd, tanh=1) + + +# Low-overhead fixed-point versions + +def cos_sin_fixed(x, prec, pi2=None): + if pi2 is None: + pi2 = pi_fixed(prec-1) + n, t = divmod(x, pi2) + n = int(n) + c, s = cos_sin_basecase(t, prec) + m = n & 3 + if m == 0: return c, s + if m == 1: return -s, c + if m == 2: return -c, -s + if m == 3: return s, -c + +def exp_fixed(x, prec, ln2=None): + if ln2 is None: + ln2 = ln2_fixed(prec) + n, t = divmod(x, ln2) + n = int(n) + v = exp_basecase(t, prec) + if n >= 0: + return v << n + else: + return v >> (-n) diff --git a/mpmath/libmp/libhyper.py b/mpmath/libmp/libhyper.py new file mode 100644 index 0000000..1f3cfe4 --- /dev/null +++ b/mpmath/libmp/libhyper.py @@ -0,0 +1,1118 @@ +""" +This module implements computation of hypergeometric and related +functions. In particular, it provides code for generic summation +of hypergeometric series. Optimized versions for various special +cases are also provided. +""" + +import math + +from .backend import MPZ, MPZ_ONE, MPZ_ZERO +from .gammazeta import euler_fixed, mpf_euler, mpf_gamma_int +from .libelefun import (agm_fixed, mpf_cos_sin, mpf_exp, mpf_ln, mpf_pi, + mpf_sin, mpf_sqrt, pi_fixed) +from .libintmath import ifac, sqrt_fixed +from .libmpc import (complex_int_pow, mpc_abs, mpc_add, mpc_add_mpf, mpc_div, + mpc_exp, mpc_is_infnan, mpc_ln, mpc_mpf_div, mpc_mul, + mpc_neg, mpc_one, mpc_pos, mpc_shift, mpc_sqrt, mpc_sub, + mpc_zero) +from .libmpf import (ComplexResult, finf, fnan, fninf, fnone, fone, from_int, + from_man_exp, from_rational, ftwo, fzero, mpf_abs, + mpf_add, mpf_div, mpf_le, mpf_lt, mpf_min_max, mpf_mul, + mpf_neg, mpf_perturb, mpf_pos, mpf_pow_int, mpf_shift, + mpf_sign, mpf_sqrt, mpf_sub, negative_rnd, round_down, + to_fixed, to_int) + + +class NoConvergence(Exception): + pass + + +#-----------------------------------------------------------------------# +# # +# Generic hypergeometric series # +# # +#-----------------------------------------------------------------------# + +""" +TODO: + +1. proper mpq parsing +2. imaginary z special-cased (also: rational, integer?) +3. more clever handling of series that don't converge because of stupid + upwards rounding +4. checking for cancellation + +""" + +def make_hyp_summator(key): + """ + Returns a function that sums a generalized hypergeometric series, + for given parameter types (integer, rational, real, complex). + + """ + p, q, param_types, ztype = key + + pstring = "".join(param_types) + fname = "hypsum_%i_%i_%s_%s_%s" % (p, q, pstring[:p], pstring[p:], ztype) + #print "generating hypsum", fname + + have_complex_param = 'C' in param_types + have_complex_arg = ztype == 'C' + have_complex = have_complex_param or have_complex_arg + + source = [] + add = source.append + + aint = [] + arat = [] + bint = [] + brat = [] + areal = [] + breal = [] + acomplex = [] + bcomplex = [] + + #add("wp = prec + 40") + add("MAX = kwargs.get('maxterms', wp*100)") + add("HIGH = MPZ_ONE<= 0:") + add(" ZRE = xm << offset") + add("else:") + add(" ZRE = xm >> (-offset)") + if have_complex_arg: + add("offset = ye + wp") + add("if offset >= 0:") + add(" ZIM = ym << offset") + add("else:") + add(" ZIM = ym >> (-offset)") + + for i, flag in enumerate(param_types): + W = ["A", "B"][i >= p] + if flag == 'Z': + ([aint,bint][i >= p]).append(i) + add("%sINT_%i = coeffs[%i]" % (W, i, i)) + elif flag == 'Q': + ([arat,brat][i >= p]).append(i) + add("%sP_%i, %sQ_%i = coeffs[%i].numerator, coeffs[%i].denominator" % (W, i, W, i, i, i)) + elif flag == 'R': + ([areal,breal][i >= p]).append(i) + add("xsign, xm, xe, xbc = coeffs[%i]._mpf_" % i) + add("if xsign: xm = -xm") + add("offset = xe + wp") + add("if offset >= 0:") + add(" %sREAL_%i = xm << offset" % (W, i)) + add("else:") + add(" %sREAL_%i = xm >> (-offset)" % (W, i)) + elif flag == 'C': + ([acomplex,bcomplex][i >= p]).append(i) + add("__re, __im = coeffs[%i]._mpc_" % i) + add("xsign, xm, xe, xbc = __re") + add("if xsign: xm = -xm") + add("ysign, ym, ye, ybc = __im") + add("if ysign: ym = -ym") + + add("offset = xe + wp") + add("if offset >= 0:") + add(" %sCRE_%i = xm << offset" % (W, i)) + add("else:") + add(" %sCRE_%i = xm >> (-offset)" % (W, i)) + add("offset = ye + wp") + add("if offset >= 0:") + add(" %sCIM_%i = ym << offset" % (W, i)) + add("else:") + add(" %sCIM_%i = ym >> (-offset)" % (W, i)) + else: + raise ValueError + + l_areal = len(areal) + l_breal = len(breal) + cancellable_real = min(l_areal, l_breal) + noncancellable_real_num = areal[cancellable_real:] + noncancellable_real_den = breal[cancellable_real:] + + # LOOP + add("for n in range(1,10**8):") + + add(" if n in magnitude_check:") + add(" p_mag = PRE.bit_length()") + if have_complex: + add(" p_mag = max(p_mag, PIM.bit_length())") + add(" magnitude_check[n] = wp-p_mag") + + # Real factors + multiplier = " * ".join(["AINT_#".replace("#", str(i)) for i in aint] + \ + ["AP_#".replace("#", str(i)) for i in arat] + \ + ["BQ_#".replace("#", str(i)) for i in brat]) + + divisor = " * ".join(["BINT_#".replace("#", str(i)) for i in bint] + \ + ["BP_#".replace("#", str(i)) for i in brat] + \ + ["AQ_#".replace("#", str(i)) for i in arat] + ["n"]) + + if multiplier: + add(" mul = " + multiplier) + add(" div = " + divisor) + + # Check for singular terms + add(" if not div:") + if multiplier: + add(" if not mul:") + add(" break") + add(" raise ZeroDivisionError") + + # Update product + if have_complex: + + # TODO: when there are several real parameters and just a few complex + # (maybe just the complex argument), we only need to do about + # half as many ops if we accumulate the real factor in a single real variable + for k in range(cancellable_real): add(" PRE = PRE * AREAL_%i // BREAL_%i" % (areal[k], breal[k])) + for i in noncancellable_real_num: add(" PRE = (PRE * AREAL_#) >> wp".replace("#", str(i))) + for i in noncancellable_real_den: add(" PRE = (PRE << wp) // BREAL_#".replace("#", str(i))) + for k in range(cancellable_real): add(" PIM = PIM * AREAL_%i // BREAL_%i" % (areal[k], breal[k])) + for i in noncancellable_real_num: add(" PIM = (PIM * AREAL_#) >> wp".replace("#", str(i))) + for i in noncancellable_real_den: add(" PIM = (PIM << wp) // BREAL_#".replace("#", str(i))) + + if multiplier: + if have_complex_arg: + add(" PRE, PIM = (mul*(PRE*ZRE-PIM*ZIM))//div, (mul*(PIM*ZRE+PRE*ZIM))//div") + add(" PRE >>= wp") + add(" PIM >>= wp") + else: + add(" PRE = ((mul * PRE * ZRE) >> wp) // div") + add(" PIM = ((mul * PIM * ZRE) >> wp) // div") + else: + if have_complex_arg: + add(" PRE, PIM = (PRE*ZRE-PIM*ZIM)//div, (PIM*ZRE+PRE*ZIM)//div") + add(" PRE >>= wp") + add(" PIM >>= wp") + else: + add(" PRE = ((PRE * ZRE) >> wp) // div") + add(" PIM = ((PIM * ZRE) >> wp) // div") + + for i in acomplex: + add(" PRE, PIM = PRE*ACRE_#-PIM*ACIM_#, PIM*ACRE_#+PRE*ACIM_#".replace("#", str(i))) + add(" PRE >>= wp") + add(" PIM >>= wp") + + for i in bcomplex: + add(" mag = BCRE_#*BCRE_#+BCIM_#*BCIM_#".replace("#", str(i))) + add(" re = PRE*BCRE_# + PIM*BCIM_#".replace("#", str(i))) + add(" im = PIM*BCRE_# - PRE*BCIM_#".replace("#", str(i))) + add(" PRE = (re << wp) // mag".replace("#", str(i))) + add(" PIM = (im << wp) // mag".replace("#", str(i))) + + else: + for k in range(cancellable_real): add(" PRE = PRE * AREAL_%i // BREAL_%i" % (areal[k], breal[k])) + for i in noncancellable_real_num: add(" PRE = (PRE * AREAL_#) >> wp".replace("#", str(i))) + for i in noncancellable_real_den: add(" PRE = (PRE << wp) // BREAL_#".replace("#", str(i))) + if multiplier: + add(" PRE = ((PRE * mul * ZRE) >> wp) // div") + else: + add(" PRE = ((PRE * ZRE) >> wp) // div") + + # Add product to sum + if have_complex: + add(" SRE += PRE") + add(" SIM += PIM") + add(" if (HIGH > PRE > LOW) and (HIGH > PIM > LOW):") + add(" break") + else: + add(" SRE += PRE") + add(" if HIGH > PRE > LOW:") + add(" break") + + #add(" from mpmath import nprint, log, ldexp") + #add(" nprint([n, log(abs(PRE),2), ldexp(PRE,-wp)])") + + add(" if n > MAX:") + add(" raise NoConvergence('Hypergeometric series converges too slowly. Try increasing maxterms.')") + + # +1 all parameters for next loop + for i in aint: add(" AINT_# += 1".replace("#", str(i))) + for i in bint: add(" BINT_# += 1".replace("#", str(i))) + for i in arat: add(" AP_# += AQ_#".replace("#", str(i))) + for i in brat: add(" BP_# += BQ_#".replace("#", str(i))) + for i in areal: add(" AREAL_# += one".replace("#", str(i))) + for i in breal: add(" BREAL_# += one".replace("#", str(i))) + for i in acomplex: add(" ACRE_# += one".replace("#", str(i))) + for i in bcomplex: add(" BCRE_# += one".replace("#", str(i))) + + if have_complex: + add("a = from_man_exp(SRE, -wp, prec, 'n')") + add("b = from_man_exp(SIM, -wp, prec, 'n')") + + add("if SRE:") + add(" if SIM:") + add(" magn = max(a[2]+a[3], b[2]+b[3])") + add(" else:") + add(" magn = a[2]+a[3]") + add("elif SIM:") + add(" magn = b[2]+b[3]") + add("else:") + add(" magn = -wp+1") + + add("return (a, b), True, magn") + else: + add("a = from_man_exp(SRE, -wp, prec, 'n')") + + add("if SRE:") + add(" magn = a[2]+a[3]") + add("else:") + add(" magn = -wp+1") + + add("return a, False, magn") + + source = "\n".join((" " + line) for line in source) + source = ("def %s(coeffs, z, prec, wp, epsshift, magnitude_check, **kwargs):\n" % fname) + source + + namespace = {} + + exec(source, globals(), namespace) + + #print source + return source, namespace[fname] + + +#-----------------------------------------------------------------------# +# # +# Error functions # +# # +#-----------------------------------------------------------------------# + +# TODO: mpf_erf should call mpf_erfc when appropriate (currently +# only the converse delegation is implemented) + +def mpf_erf(x, prec, rnd=round_down): + sign, man, exp, bc = x + if not man: + if x == fzero: return fzero + if x == finf: return fone + if x== fninf: return fnone + return fnan + size = exp + bc + lg = math.log + # The approximation erf(x) = 1 is accurate to > x^2 * log(e,2) bits + if size > 3 and 2*(size-1) + 0.528766 > lg(prec,2): + if sign: + return mpf_perturb(fnone, 0, prec, rnd) + else: + return mpf_perturb(fone, 1, prec, rnd) + # erf(x) ~ 2*x/sqrt(pi) close to 0 + if size < -prec: + # 2*x + x = mpf_shift(x,1) + c = mpf_sqrt(mpf_pi(prec+20), prec+20) + # TODO: interval rounding + return mpf_div(x, c, prec, rnd) + wp = prec + abs(size) + 25 + # Taylor series for erf, fixed-point summation + t = abs(to_fixed(x, wp)) + t2 = (t*t) >> wp + s, term, k = t, 12345, 1 + while term: + t = ((t * t2) >> wp) // k + term = t // (2*k+1) + if k & 1: + s -= term + else: + s += term + k += 1 + s = (s << (wp+1)) // sqrt_fixed(pi_fixed(wp), wp) + if sign: + s = -s + return from_man_exp(s, -wp, prec, rnd) + +# If possible, we use the asymptotic series for erfc. +# This is an alternating divergent asymptotic series, so +# the error is at most equal to the first omitted term. +# Here we check if the smallest term is small enough +# for a given x and precision +def erfc_check_series(x, prec): + n = to_int(x) + if n**2 * 1.44 > prec: + return True + return False + +def mpf_erfc(x, prec, rnd=round_down): + sign, man, exp, bc = x + if not man: + if x == fzero: return fone + if x == finf: return fzero + if x == fninf: return ftwo + return fnan + wp = prec + 20 + mag = bc+exp + # Preserve full accuracy when exponent grows huge + wp += max(0, 2*mag) + regular_erf = sign or mag < 2 + if regular_erf or not erfc_check_series(x, wp): + if regular_erf: + return mpf_sub(fone, mpf_erf(x, prec+10, negative_rnd[rnd]), prec, rnd) + # 1-erf(x) ~ exp(-x^2), increase prec to deal with cancellation + n = to_int(x)+1 + return mpf_sub(fone, mpf_erf(x, prec + int(n**2*1.44) + 10), prec, rnd) + s = term = MPZ_ONE << wp + term_prev = 0 + t = (2 * to_fixed(x, wp) ** 2) >> wp + k = 1 + while 1: + term = ((term * (2*k - 1)) << wp) // t + if k > 4 and term > term_prev or not term: + break + if k & 1: + s -= term + else: + s += term + term_prev = term + #print k, to_str(from_man_exp(term, -wp, 50), 10) + k += 1 + s = (s << wp) // sqrt_fixed(pi_fixed(wp), wp) + s = from_man_exp(s, -wp, wp) + z = mpf_exp(mpf_neg(mpf_mul(x,x,wp),wp),wp) + y = mpf_div(mpf_mul(z, s, wp), x, prec, rnd) + return y + + +#-----------------------------------------------------------------------# +# # +# Exponential integrals # +# # +#-----------------------------------------------------------------------# + +def ei_taylor(x, prec): + s = t = x + k = 2 + while t: + t = ((t*x) >> prec) // k + s += t // k + k += 1 + return s + +def complex_ei_taylor(zre, zim, prec): + _abs = abs + sre = tre = zre + sim = tim = zim + k = 2 + while _abs(tre) + _abs(tim) > 5: + tre, tim = ((tre*zre-tim*zim)//k)>>prec, ((tre*zim+tim*zre)//k)>>prec + sre += tre // k + sim += tim // k + k += 1 + return sre, sim + +def ei_asymptotic(x, prec): + one = MPZ_ONE << prec + x = t = ((one << prec) // x) + s = one + x + k = 2 + while t: + t = (k*t*x) >> prec + s += t + k += 1 + return s + +def complex_ei_asymptotic(zre, zim, prec): + _abs = abs + one = MPZ_ONE << prec + M = (zim*zim + zre*zre) >> prec + # 1 / z + xre = tre = (zre << prec) // M + xim = tim = ((-zim) << prec) // M + sre = one + xre + sim = xim + k = 2 + while _abs(tre) + _abs(tim) > 1000: + #print tre, tim + tre, tim = ((tre*xre-tim*xim)*k)>>prec, ((tre*xim+tim*xre)*k)>>prec + sre += tre + sim += tim + k += 1 + if k > prec: + raise NoConvergence + return sre, sim + +def mpf_ei(x, prec, rnd=round_down, e1=False): + if e1: + x = mpf_neg(x) + sign, man, exp, bc = x + if e1 and not sign: + if x == fzero: + return finf + raise ComplexResult("E1(x) for x < 0") + if man: + xabs = 0, man, exp, bc + xmag = exp+bc + wp = prec + 20 + can_use_asymp = xmag > wp + if not can_use_asymp: + if exp >= 0: + xabsint = man << exp + else: + xabsint = man >> (-exp) + can_use_asymp = xabsint > int(wp*0.693) + 10 + if can_use_asymp: + if xmag > wp: + v = fone + else: + v = from_man_exp(ei_asymptotic(to_fixed(x, wp), wp), -wp) + v = mpf_mul(v, mpf_exp(x, wp), wp) + v = mpf_div(v, x, prec, rnd) + else: + wp += 2*int(to_int(xabs)) + u = to_fixed(x, wp) + v = ei_taylor(u, wp) + euler_fixed(wp) + t1 = from_man_exp(v,-wp) + t2 = mpf_ln(xabs,wp) + v = mpf_add(t1, t2, prec, rnd) + else: + if x == fzero: v = fninf + elif x == finf: v = finf + elif x == fninf: v = fzero + else: v = fnan + if e1: + v = mpf_neg(v) + return v + +def mpc_ei(z, prec, rnd=round_down, e1=False): + if e1: + z = mpc_neg(z) + a, b = z + asign, aman, aexp, abc = a + bsign, bman, bexp, bbc = b + if b == fzero: + if e1: + x = mpf_neg(mpf_ei(a, prec, rnd)) + if not asign: + y = mpf_neg(mpf_pi(prec, rnd)) + else: + y = fzero + return x, y + else: + return mpf_ei(a, prec, rnd), fzero + if a != fzero: + if not aman or not bman: + return (fnan, fnan) + wp = prec + 40 + amag = aexp+abc + bmag = bexp+bbc + zmag = max(amag, bmag) + can_use_asymp = zmag > wp + if not can_use_asymp: + zabsint = abs(to_int(a)) + abs(to_int(b)) + can_use_asymp = zabsint > int(wp*0.693) + 20 + try: + if can_use_asymp: + if zmag > wp: + v = fone, fzero + else: + zre = to_fixed(a, wp) + zim = to_fixed(b, wp) + vre, vim = complex_ei_asymptotic(zre, zim, wp) + v = from_man_exp(vre, -wp), from_man_exp(vim, -wp) + v = mpc_mul(v, mpc_exp(z, wp), wp) + v = mpc_div(v, z, wp) + if e1: + v = mpc_neg(v, prec, rnd) + else: + x, y = v + if bsign: + v = mpf_pos(x, prec, rnd), mpf_sub(y, mpf_pi(wp), prec, rnd) + else: + v = mpf_pos(x, prec, rnd), mpf_add(y, mpf_pi(wp), prec, rnd) + return v + except NoConvergence: + pass + #wp += 2*max(0,zmag) + wp += 2*int(to_int(mpc_abs(z, 5))) + zre = to_fixed(a, wp) + zim = to_fixed(b, wp) + vre, vim = complex_ei_taylor(zre, zim, wp) + vre += euler_fixed(wp) + v = from_man_exp(vre,-wp), from_man_exp(vim,-wp) + if e1: + u = mpc_ln(mpc_neg(z),wp) + else: + u = mpc_ln(z,wp) + v = mpc_add(v, u, prec, rnd) + if e1: + v = mpc_neg(v) + return v + +def mpf_e1(x, prec, rnd=round_down): + return mpf_ei(x, prec, rnd, True) + +def mpc_e1(x, prec, rnd=round_down): + return mpc_ei(x, prec, rnd, True) + +def mpf_expint(n, x, prec, rnd=round_down, gamma=False): + """ + E_n(x), n an integer, x real + + With gamma=True, computes Gamma(n,x) (upper incomplete gamma function) + + Returns (real, None) if real, otherwise (real, imag) + The imaginary part is an optional branch cut term + + """ + sign, man, exp, bc = x + if not man: + if gamma: + if x == fzero: + # Actually gamma function pole + if n <= 0: + return finf, None + return mpf_gamma_int(n, prec, rnd), None + if x == finf: + return fzero, None + # TODO: could return finite imaginary value at -inf + return fnan, fnan + else: + if x == fzero: + if n > 1: + return from_rational(1, n-1, prec, rnd), None + else: + return finf, None + if x == finf: + return fzero, None + return fnan, fnan + n_orig = n + if gamma: + n = 1-n + wp = prec + 20 + xmag = exp + bc + # Beware of near-poles + if xmag < -10: + raise NotImplementedError + nmag = n.bit_length() + have_imag = n > 0 and sign + negx = mpf_neg(x) + # Skip series if direct convergence + if n == 0 or 2*nmag - xmag < -wp: + if gamma: + v = mpf_exp(negx, wp) + re = mpf_mul(v, mpf_pow_int(x, n_orig-1, wp), prec, rnd) + else: + v = mpf_exp(negx, wp) + re = mpf_div(v, x, prec, rnd) + else: + # Finite number of terms, or... + can_use_asymptotic_series = -3*wp < n <= 0 + # ...large enough? + if not can_use_asymptotic_series: + xi = abs(to_int(x)) + m = min(max(1, xi-n), 2*wp) + siz = -n*nmag + (m+n)*(m+n).bit_length() - m*xmag - (144*m//100) + tol = -wp-10 + can_use_asymptotic_series = siz < tol + if can_use_asymptotic_series: + r = ((-MPZ_ONE) << (wp+wp)) // to_fixed(x, wp) + m = n + t = r*m + s = MPZ_ONE << wp + while m and t: + s += t + m += 1 + t = (m*r*t) >> wp + v = mpf_exp(negx, wp) + if gamma: + # ~ exp(-x) * x^(n-1) * (1 + ...) + v = mpf_mul(v, mpf_pow_int(x, n_orig-1, wp), wp) + else: + # ~ exp(-x)/x * (1 + ...) + v = mpf_div(v, x, wp) + re = mpf_mul(v, from_man_exp(s, -wp), prec, rnd) + elif n == 1: + re = mpf_neg(mpf_ei(negx, prec, rnd)) + elif n > 0 and n < 3*wp: + T1 = mpf_neg(mpf_ei(negx, wp)) + if gamma: + if n_orig & 1: + T1 = mpf_neg(T1) + else: + T1 = mpf_mul(T1, mpf_pow_int(negx, n-1, wp), wp) + r = t = to_fixed(x, wp) + facs = [1] * (n-1) + for k in range(1,n-1): + facs[k] = facs[k-1] * k + facs = facs[::-1] + s = MPZ(facs[0]) << wp + for k in range(1, n-1): + if k & 1: + s -= facs[k] * t + else: + s += facs[k] * t + t = (t*r) >> wp + T2 = from_man_exp(s, -wp, wp) + T2 = mpf_mul(T2, mpf_exp(negx, wp)) + if gamma: + T2 = mpf_mul(T2, mpf_pow_int(x, n_orig, wp), wp) + R = mpf_add(T1, T2) + re = mpf_div(R, from_int(ifac(n-1)), prec, rnd) + else: + raise NotImplementedError + if have_imag: + M = from_int(-ifac(n-1)) + if gamma: + im = mpf_div(mpf_pi(wp), M, prec, rnd) + if n_orig & 1: + im = mpf_neg(im) + else: + im = mpf_div(mpf_mul(mpf_pi(wp), mpf_pow_int(negx, n_orig-1, wp), wp), M, prec, rnd) + return re, im + else: + return re, None + +def mpf_ci_si_taylor(x, wp, which=0): + """ + 0 - Ci(x) - (euler+log(x)) + 1 - Si(x) + """ + x = to_fixed(x, wp) + x2 = -(x*x) >> wp + if which == 0: + s, t, k = 0, (MPZ_ONE<>wp + s += t//k + k += 2 + return from_man_exp(s, -wp) + +def mpc_ci_si_taylor(re, im, wp, which=0): + # The following code is only designed for small arguments, + # and not too small arguments (for relative accuracy) + if re[1]: + mag = re[2]+re[3] + elif im[1]: + mag = im[2]+im[3] + if im[1]: + mag = max(mag, im[2]+im[3]) + if mag > 2 or mag < -wp: + raise NotImplementedError + wp += (2-mag) + zre = to_fixed(re, wp) + zim = to_fixed(im, wp) + z2re = (zim*zim-zre*zre)>>wp + z2im = (-2*zre*zim)>>wp + tre = zre + tim = zim + one = MPZ_ONE< 2: + f = k*(k-1) + tre, tim = ((tre*z2re-tim*z2im)//f)>>wp, ((tre*z2im+tim*z2re)//f)>>wp + sre += tre//k + sim += tim//k + k += 2 + return from_man_exp(sre, -wp), from_man_exp(sim, -wp) + +def mpf_ci_si(x, prec, rnd=round_down, which=2): + """ + Calculation of Ci(x), Si(x) for real x. + + which = 0 -- returns (Ci(x), -) + which = 1 -- returns (Si(x), -) + which = 2 -- returns (Ci(x), Si(x)) + + Note: if x < 0, Ci(x) needs an additional imaginary term, pi*i. + """ + wp = prec + 20 + sign, man, exp, bc = x + ci, si = None, None + if not man: + if x == fzero: + return (fninf, fzero) + if x == fnan: + return (x, x) + ci = fzero + if which != 0: + if x == finf: + si = mpf_shift(mpf_pi(prec, rnd), -1) + if x == fninf: + si = mpf_neg(mpf_shift(mpf_pi(prec, negative_rnd[rnd]), -1)) + return (ci, si) + # For small x: Ci(x) ~ euler + log(x), Si(x) ~ x + mag = exp+bc + if mag < -wp: + if which != 0: + si = mpf_perturb(x, 1-sign, prec, rnd) + if which != 1: + y = mpf_euler(wp) + xabs = mpf_abs(x) + ci = mpf_add(y, mpf_ln(xabs, wp), prec, rnd) + return ci, si + # For huge x: Ci(x) ~ sin(x)/x, Si(x) ~ pi/2 + elif mag > wp: + if which != 0: + if sign: + si = mpf_neg(mpf_pi(prec, negative_rnd[rnd])) + else: + si = mpf_pi(prec, rnd) + si = mpf_shift(si, -1) + if which != 1: + ci = mpf_div(mpf_sin(x, wp), x, prec, rnd) + return ci, si + else: + wp += abs(mag) + # Use an asymptotic series? The smallest value of n!/x^n + # occurs for n ~ x, where the magnitude is ~ exp(-x). + asymptotic = mag-1 > math.log(wp, 2) + # Case 1: convergent series near 0 + if not asymptotic: + if which != 0: + si = mpf_pos(mpf_ci_si_taylor(x, wp, 1), prec, rnd) + if which != 1: + ci = mpf_ci_si_taylor(x, wp, 0) + ci = mpf_add(ci, mpf_euler(wp), wp) + ci = mpf_add(ci, mpf_ln(mpf_abs(x), wp), prec, rnd) + return ci, si + x = mpf_abs(x) + # Case 2: asymptotic series for x >> 1 + xf = to_fixed(x, wp) + xr = (MPZ_ONE<<(2*wp)) // xf # 1/x + s1 = (MPZ_ONE << wp) + s2 = xr + t = xr + k = 2 + while t: + t = -t + t = (t*xr*k)>>wp + k += 1 + s1 += t + t = (t*xr*k)>>wp + k += 1 + s2 += t + s1 = from_man_exp(s1, -wp) + s2 = from_man_exp(s2, -wp) + s1 = mpf_div(s1, x, wp) + s2 = mpf_div(s2, x, wp) + cos, sin = mpf_cos_sin(x, wp) + # Ci(x) = sin(x)*s1-cos(x)*s2 + # Si(x) = pi/2-cos(x)*s1-sin(x)*s2 + if which != 0: + si = mpf_add(mpf_mul(cos, s1), mpf_mul(sin, s2), wp) + si = mpf_sub(mpf_shift(mpf_pi(wp), -1), si, wp) + if sign: + si = mpf_neg(si) + si = mpf_pos(si, prec, rnd) + if which != 1: + ci = mpf_sub(mpf_mul(sin, s1), mpf_mul(cos, s2), prec, rnd) + return ci, si + +def mpf_ci(x, prec, rnd=round_down): + if mpf_sign(x) < 0: + raise ComplexResult + return mpf_ci_si(x, prec, rnd, 0)[0] + +def mpf_si(x, prec, rnd=round_down): + return mpf_ci_si(x, prec, rnd, 1)[1] + +def mpc_ci(z, prec, rnd=round_down): + re, im = z + if im == fzero: + ci = mpf_ci_si(re, prec, rnd, 0)[0] + if mpf_sign(re) < 0: + return (ci, mpf_pi(prec, rnd)) + return (ci, fzero) + wp = prec + 20 + cre, cim = mpc_ci_si_taylor(re, im, wp, 0) + cre = mpf_add(cre, mpf_euler(wp), wp) + ci = mpc_add((cre, cim), mpc_ln(z, wp), prec, rnd) + return ci + +def mpc_si(z, prec, rnd=round_down): + re, im = z + if im == fzero: + return (mpf_ci_si(re, prec, rnd, 1)[1], fzero) + wp = prec + 20 + z = mpc_ci_si_taylor(re, im, wp, 1) + return mpc_pos(z, prec, rnd) + + +#-----------------------------------------------------------------------# +# # +# Bessel functions # +# # +#-----------------------------------------------------------------------# + +# A Bessel function of the first kind of integer order, J_n(x), is +# given by the power series + +# oo +# ___ k 2 k + n +# \ (-1) / x \ +# J_n(x) = ) ----------- | - | +# /___ k! (k + n)! \ 2 / +# k = 0 + +# Simplifying the quotient between two successive terms gives the +# ratio x^2 / (-4*k*(k+n)). Hence, we only need one full-precision +# multiplication and one division by a small integer per term. +# The complex version is very similar, the only difference being +# that the multiplication is actually 4 multiplies. + +# In the general case, we have +# J_v(x) = (x/2)**v / v! * 0F1(v+1, (-1/4)*z**2) + +# TODO: for extremely large x, we could use an asymptotic +# trigonometric approximation. + +# TODO: recompute at higher precision if the fixed-point mantissa +# is very small + +def mpf_besseljn(n, x, prec, rnd=round_down): + prec += 50 + negate = n < 0 and n & 1 + mag = x[2]+x[3] + n = abs(n) + wp = prec + 20 + n*n.bit_length() + if mag < 0: + wp -= n * mag + x = to_fixed(x, wp) + x2 = (x**2) >> wp + if not n: + s = t = MPZ_ONE << wp + else: + s = t = (x**n // ifac(n)) >> ((n-1)*wp + n) + k = 1 + while t: + t = ((t * x2) // (-4*k*(k+n))) >> wp + s += t + k += 1 + if negate: + s = -s + return from_man_exp(s, -wp, prec, rnd) + +def mpc_besseljn(n, z, prec, rnd=round_down): + negate = n < 0 and n & 1 + n = abs(n) + origprec = prec + zre, zim = z + mag = max(zre[2]+zre[3], zim[2]+zim[3]) + prec += 20 + n*n.bit_length() + abs(mag) + if mag < 0: + prec -= n * mag + zre = to_fixed(zre, prec) + zim = to_fixed(zim, prec) + z2re = (zre**2 - zim**2) >> prec + z2im = (zre*zim) >> (prec-1) + if not n: + sre = tre = MPZ_ONE << prec + sim = tim = MPZ_ZERO + else: + re, im = complex_int_pow(zre, zim, n) + sre = tre = (re // ifac(n)) >> ((n-1)*prec + n) + sim = tim = (im // ifac(n)) >> ((n-1)*prec + n) + k = 1 + while abs(tre) + abs(tim) > 3: + p = -4*k*(k+n) + tre, tim = tre*z2re - tim*z2im, tim*z2re + tre*z2im + tre = (tre // p) >> prec + tim = (tim // p) >> prec + sre += tre + sim += tim + k += 1 + if negate: + sre = -sre + sim = -sim + re = from_man_exp(sre, -prec, origprec, rnd) + im = from_man_exp(sim, -prec, origprec, rnd) + return (re, im) + +def mpf_agm(a, b, prec, rnd=round_down): + """ + Computes the arithmetic-geometric mean agm(a,b) for + nonnegative mpf values a, b. + """ + asign, aman, aexp, abc = a + bsign, bman, bexp, bbc = b + if asign or bsign: + raise ComplexResult("agm of a negative number") + # Handle inf, nan or zero in either operand + if not (aman and bman): + if a == fnan or b == fnan: + return fnan + if a == finf: + if b == fzero: + return fnan + return finf + if b == finf: + if a == fzero: + return fnan + return finf + # agm(0,x) = agm(x,0) = 0 + return fzero + wp = prec + 20 + amag = aexp+abc + bmag = bexp+bbc + mag_delta = amag - bmag + # Reduce to roughly the same magnitude using floating-point AGM + abs_mag_delta = abs(mag_delta) + if abs_mag_delta > 10: + while abs_mag_delta > 10: + a, b = mpf_shift(mpf_add(a,b,wp),-1), \ + mpf_sqrt(mpf_mul(a,b,wp),wp) + abs_mag_delta //= 2 + asign, aman, aexp, abc = a + bsign, bman, bexp, bbc = b + amag = aexp+abc + bmag = bexp+bbc + mag_delta = amag - bmag + #print to_float(a), to_float(b) + # Use agm(a,b) = agm(x*a,x*b)/x to obtain a, b ~= 1 + min_mag = min(amag,bmag) + max_mag = max(amag,bmag) + n = 0 + # If too small, we lose precision when going to fixed-point + if min_mag < -8: + n = -min_mag + # If too large, we waste time using fixed-point with large numbers + elif max_mag > 20: + n = -max_mag + if n: + a = mpf_shift(a, n) + b = mpf_shift(b, n) + #print to_float(a), to_float(b) + af = to_fixed(a, wp) + bf = to_fixed(b, wp) + g = agm_fixed(af, bf, wp) + return from_man_exp(g, -wp-n, prec, rnd) + +def mpf_agm1(a, prec, rnd=round_down): + """ + Computes the arithmetic-geometric mean agm(1,a) for a nonnegative + mpf value a. + """ + return mpf_agm(fone, a, prec, rnd) + +def mpc_agm(a, b, prec, rnd=round_down): + """ + Complex AGM. + + TODO: + * check that convergence works as intended + * optimize + * select a nonarbitrary branch + """ + if mpc_is_infnan(a) or mpc_is_infnan(b): + return fnan, fnan + if mpc_zero in (a, b): + return fzero, fzero + if mpc_neg(a) == b: + return fzero, fzero + wp = prec+20 + eps = mpf_shift(fone, -wp+10) + while 1: + a1 = mpc_shift(mpc_add(a, b, wp), -1) + b1 = mpc_sqrt(mpc_mul(a, b, wp), wp) + a, b = a1, b1 + size = mpf_min_max([mpc_abs(a,10), mpc_abs(b,10)])[1] + err = mpc_abs(mpc_sub(a, b, 10), 10) + if size == fzero or mpf_lt(err, mpf_mul(eps, size)): + return a + +def mpc_agm1(a, prec, rnd=round_down): + return mpc_agm(mpc_one, a, prec, rnd) + +def mpf_ellipk(x, prec, rnd=round_down): + if not x[1]: + if x == fzero: + return mpf_shift(mpf_pi(prec, rnd), -1) + if x == fninf: + return fzero + if x == fnan: + return x + if x == fone: + return finf + # TODO: for |x| << 1/2, one could use fall back to + # pi/2 * hyp2f1_rat((1,2),(1,2),(1,1), x) + wp = prec + 15 + # Use K(x) = pi/2/agm(1,a) where a = sqrt(1-x) + # The sqrt raises ComplexResult if x > 0 + a = mpf_sqrt(mpf_sub(fone, x, wp), wp) + v = mpf_agm1(a, wp) + r = mpf_div(mpf_pi(wp), v, prec, rnd) + return mpf_shift(r, -1) + +def mpc_ellipk(z, prec, rnd=round_down): + re, im = z + if im == fzero: + if re == finf: + return mpc_zero + if mpf_le(re, fone): + return mpf_ellipk(re, prec, rnd), fzero + wp = prec + 15 + a = mpc_sqrt(mpc_sub(mpc_one, z, wp), wp) + v = mpc_agm1(a, wp) + r = mpc_mpf_div(mpf_pi(wp), v, prec, rnd) + return mpc_shift(r, -1) + +def mpf_ellipe(x, prec, rnd=round_down): + # http://functions.wolfram.com/EllipticIntegrals/ + # EllipticK/20/01/0001/ + # E = (1-m)*(K'(m)*2*m + K(m)) + sign, man, exp, bc = x + if not man: + if x == fzero: + return mpf_shift(mpf_pi(prec, rnd), -1) + if x == fninf: + return finf + if x == fnan: + return x + if x == finf: + raise ComplexResult + if x == fone: + return fone + wp = prec+20 + mag = exp+bc + if mag < -wp: + return mpf_shift(mpf_pi(prec, rnd), -1) + # Compute a finite difference for K' + p = max(mag, 0) - wp + h = mpf_shift(fone, p) + K = mpf_ellipk(x, 2*wp) + Kh = mpf_ellipk(mpf_sub(x, h), 2*wp) + Kdiff = mpf_shift(mpf_sub(K, Kh), -p) + t = mpf_sub(fone, x) + b = mpf_mul(Kdiff, mpf_shift(x,1), wp) + return mpf_mul(t, mpf_add(K, b), prec, rnd) + +def mpc_ellipe(z, prec, rnd=round_down): + re, im = z + if im == fzero: + if re == finf: + return (fzero, finf) + if mpf_le(re, fone): + return mpf_ellipe(re, prec, rnd), fzero + wp = prec + 15 + mag = mpc_abs(z, 1) + p = max(mag[2]+mag[3], 0) - wp + h = mpf_shift(fone, p) + K = mpc_ellipk(z, 2*wp) + Kh = mpc_ellipk(mpc_add_mpf(z, h, 2*wp), 2*wp) + Kdiff = mpc_shift(mpc_sub(Kh, K, wp), -p) + t = mpc_sub(mpc_one, z, wp) + b = mpc_mul(Kdiff, mpc_shift(z,1), wp) + return mpc_mul(t, mpc_add(K, b, wp), prec, rnd) diff --git a/mpmath/libmp/libintmath.py b/mpmath/libmp/libintmath.py new file mode 100644 index 0000000..30e78f2 --- /dev/null +++ b/mpmath/libmp/libintmath.py @@ -0,0 +1,523 @@ +""" +Utility functions for integer math. + +TODO: rename, cleanup, perhaps move the gmpy wrapper code +here from settings.py + +""" + +import math +import sys +from functools import lru_cache + +from .backend import MPZ, MPZ_ONE, MPZ_ZERO, gmpy + + +def giant_steps(start, target, n=2): + """ + Return a list of integers ~= + + [start, n*start, ..., target/n^2, target/n, target] + + but conservatively rounded so that the quotient between two + successive elements is actually slightly less than n. + + With n = 2, this describes suitable precision steps for a + quadratically convergent algorithm such as Newton's method; + with n = 3 steps for cubic convergence (Halley's method), etc. + + >>> giant_steps(50,1000) + [66, 128, 253, 502, 1000] + >>> giant_steps(50,1000,4) + [65, 252, 1000] + + """ + L = [target] + while L[-1] > start*n: + L = L + [L[-1]//n + 2] + return L[::-1] + +def rshift(x, n): + """For an integer x, calculate x >> n with the fastest (floor) + rounding. Unlike the plain Python expression (x >> n), n is + allowed to be negative, in which case a left shift is performed.""" + if n >= 0: return x >> n + else: return x << (-n) + +def lshift(x, n): + """For an integer x, calculate x << n. Unlike the plain Python + expression (x << n), n is allowed to be negative, in which case a + right shift with default (floor) rounding is performed.""" + if n >= 0: return x << n + else: return x >> (-n) + +def trailing(n): + """Count the number of trailing zero bits in abs(n).""" + return MPZ((n & (-n)).bit_length() - 1 if n else 0) + +if gmpy and hasattr(MPZ, 'bit_scan1'): + def trailing(n): + return MPZ(n).bit_scan1() if n else MPZ(0) + +# Used to avoid slow function calls as far as possible +bctable = [n.bit_length() for n in range(1024)] + +# TODO: speed up for bases 2, 4, 8, 16, ... + +def bin_to_radix(x, xbits, base, bdigits): + """Changes radix of a fixed-point number; i.e., converts + x * 2**xbits to floor(x * base**bdigits).""" + return x * (MPZ(base)**bdigits) >> xbits + +stddigits = '0123456789abcdefghijklmnopqrstuvwxyz' + +def small_numeral(n, base=10, digits=stddigits): + """Return the string numeral of a positive integer in an arbitrary + base. Most efficient for small input.""" + if base == 10: + return str(n) + digs = [] + while n: + n, digit = divmod(n, base) + digs.append(digits[digit]) + return "".join(digs[::-1]) + +def numeral_python(n, base=10, size=0, digits=stddigits): + """Represent the integer n as a string of digits in the given base. + Recursive division is used to make this function about 3x faster + than Python's str() for converting integers to decimal strings. + + The 'size' parameters specifies the number of digits in n; this + number is only used to determine splitting points and need not be + exact.""" + if n <= 0: + if not n: + return "0" + return "-" + numeral(-n, base, size, digits) + # Fast enough to do directly + if size < 250: + return small_numeral(n, base, digits) + # Divide in half + half = (size // 2) + (size & 1) + A, B = divmod(n, base**half) + ad = numeral(A, base, half, digits) + bd = numeral(B, base, half, digits).rjust(half, "0") + return ad + bd + +def numeral_gmpy(n, base=10, size=0, digits=stddigits): + """Represent the integer n as a string of digits in the given base. + Recursive division is used to make this function about 3x faster + than Python's str() for converting integers to decimal strings. + + The 'size' parameters specifies the number of digits in n; this + number is only used to determine splitting points and need not be + exact.""" + if n < 0: + return "-" + numeral(-n, base, size, digits) + # gmpy.digits() may cause a segmentation fault when trying to convert + # extremely large values to a string. The size limit may need to be + # adjusted on some platforms, but 1500000 works on Windows and Linux. + if size < 1500000: + return MPZ(n).digits(base) + # Divide in half + half = (size // 2) + (size & 1) + A, B = divmod(n, MPZ(base)**half) + ad = numeral(A, base, half, digits) + bd = numeral(B, base, half, digits).rjust(half, "0") + return ad + bd + +numeral = numeral_python + +if gmpy: + numeral = numeral_gmpy + +_1_800 = 1<<800 +_1_600 = 1<<600 +_1_400 = 1<<400 +_1_200 = 1<<200 +_1_100 = 1<<100 +_1_50 = 1<<50 + +def isqrt_small_python(x): + """ + Correctly (floor) rounded integer square root, using + division. Fast up to ~200 digits. + """ + if not x: + return x + assert x < _1_800 + # Exact with IEEE double precision arithmetic + if x < _1_50: + return int(x**0.5) + # Initial estimate can be any integer >= the true root; round up + r = int(x**0.5 * 1.00000000000001) + 1 + # The following iteration now precisely computes floor(sqrt(x)) + # See e.g. Crandall & Pomerance, "Prime Numbers: A Computational + # Perspective" + while 1: + y = (r+x//r)>>1 + if y >= r: + return r + r = y + +def isqrt_fast_python(x): + """ + Fast approximate integer square root, computed using division-free + Newton iteration for large x. For random integers the result is almost + always correct (floor(sqrt(x))), but is 1 ulp too small with a roughly + 0.1% probability. If x is very close to an exact square, the answer is + 1 ulp wrong with high probability. + + With 0 guard bits, the largest error over a set of 10^5 random + inputs of size 1-10^5 bits was 3 ulp. The use of 10 guard bits + almost certainly guarantees a max 1 ulp error. + """ + # Use direct division-based iteration if sqrt(x) < 2^400 + # Assume floating-point square root accurate to within 1 ulp, then: + # 0 Newton iterations good to 52 bits + # 1 Newton iterations good to 104 bits + # 2 Newton iterations good to 208 bits + # 3 Newton iterations good to 416 bits + if x < _1_800: + y = int(x**0.5) + if x >= _1_100: + y = (y + x//y) >> 1 + if x >= _1_200: + y = (y + x//y) >> 1 + if x >= _1_400: + y = (y + x//y) >> 1 + return y + bc = x.bit_length() + guard_bits = 10 + x <<= 2*guard_bits + bc += 2*guard_bits + bc += (bc&1) + hbc = bc//2 + startprec = min(50, hbc) + # Newton iteration for 1/sqrt(x), with floating-point starting value + r = int(2.0**(2*startprec) * (x >> (bc-2*startprec)) ** -0.5) + pp = startprec + for p in giant_steps(startprec, hbc): + # r**2, scaled from real size 2**(-bc) to 2**p + r2 = (r*r) >> (2*pp - p) + # x*r**2, scaled from real size ~1.0 to 2**p + xr2 = ((x >> (bc-p)) * r2) >> p + # New value of r, scaled from real size 2**(-bc/2) to 2**p + r = (r * ((3<> (pp+1) + pp = p + # (1/sqrt(x))*x = sqrt(x) + return (r*(x>>hbc)) >> (p+guard_bits) + +def sqrtrem_python(x): + """Correctly rounded integer (floor) square root with remainder.""" + # to check cutoff: + # plot(lambda x: timing(isqrt, 2**int(x)), [0,2000]) + if x < _1_600: + y = isqrt_small_python(x) + return y, x - y*y + y = isqrt_fast_python(x) + 1 + rem = x - y*y + # Correct remainder + while rem < 0: + y -= 1 + rem += (1+2*y) + else: + if rem: + while rem > 2*(1+y): + y += 1 + rem -= (1+2*y) + return y, rem + +def isqrt_python(x): + """Integer square root with correct (floor) rounding.""" + return sqrtrem_python(x)[0] + +def sqrt_fixed(x, prec): + return isqrt_fast(x<= (3, 12): + isqrt_small = isqrt_fast = isqrt = math.isqrt + else: + isqrt_small = isqrt_small_python + isqrt_fast = isqrt_fast_python + isqrt = isqrt_python + sqrtrem = sqrtrem_python + _gcd2 = math.gcd + +gcd = math.gcd +if gmpy: + gcd = gmpy.gcd + + +@lru_cache(maxsize=250) +def ifib(n): + """Computes the nth Fibonacci number as an integer, for + integer n.""" + if n < 0: + return (-1)**(-n+1) * ifib(-n) + m = n + # Use Dijkstra's logarithmic algorithm + # The following implementation is basically equivalent to + # http://en.literateprograms.org/Fibonacci_numbers_(Scheme) + a, b, p, q = MPZ_ONE, MPZ_ZERO, MPZ_ZERO, MPZ_ONE + while n: + if n & 1: + aq = a*q + a, b = b*q+aq+a*p, b*p+aq + n -= 1 + else: + qq = q*q + p, q = p*p+qq, qq+2*p*q + n >>= 1 + return b +ifib_python = ifib + +MAX_FACTORIAL_CACHE = 1000 + +def ifac2(n, memo_pair=[{0:1}, {1:1}]): + """Return n!! (double factorial), integers n >= 0 only.""" + memo = memo_pair[n&1] + f = memo.get(n) + if f: + return f + k = max(memo) + p = memo[k] + MAX = MAX_FACTORIAL_CACHE + while k < n: + k += 2 + p *= k + if k <= MAX: + memo[k] = p + return p +ifac2_python = ifac2 +ifac = math.factorial + +if gmpy: + ifac = gmpy.fac + if hasattr(gmpy, 'double_fac'): + ifac2 = gmpy.double_fac + if hasattr(gmpy, 'fib'): + ifib = gmpy.fib + +ifac = lru_cache(maxsize=1024)(ifac) + +def list_primes(n): + n = n + 1 + sieve = list(range(n)) + sieve[:2] = [0, 0] + for i in range(2, int(n**0.5)+1): + if sieve[i]: + for j in range(i**2, n, i): + sieve[j] = 0 + return [p for p in sieve if p] + +small_odd_primes = (3,5,7,11,13,17,19,23,29,31,37,41,43,47) +small_odd_primes_set = set(small_odd_primes) + +def isprime(n): + """ + Determines whether n is a prime number. A probabilistic test is + performed if n is very large. No special trick is used for detecting + perfect powers. + + >>> sum(list_primes(100000)) + 454396537 + >>> sum(n*isprime(n) for n in range(100000)) + 454396537 + + """ + n = int(n) + if not n & 1: + return n == 2 + if n < 50: + return n in small_odd_primes_set + for p in small_odd_primes: + if not n % p: + return False + m = n-1 + s = trailing(m) + d = m >> s + def test(a): + x = pow(a,d,n) + if x == 1 or x == m: + return True + for r in range(1,s): + x = x**2 % n + if x == m: + return True + return False + # See http://primes.utm.edu/prove/prove2_3.html + if n < 1373653: + witnesses = [2,3] + elif n < 341550071728321: + witnesses = [2,3,5,7,11,13,17] + else: + witnesses = small_odd_primes + for a in witnesses: + if not test(a): + return False + return True +isprime_python = isprime + +if gmpy and hasattr(gmpy, 'is_prime'): + isprime = gmpy.is_prime + +def moebius(n): + """ + Evaluates the Moebius function which is `mu(n) = (-1)^k` if `n` + is a product of `k` distinct primes and `mu(n) = 0` otherwise. + + TODO: speed up using factorization + """ + n = abs(int(n)) + if n < 2: + return n + factors = [] + for p in range(2, n+1): + if not (n % p): + if not (n % p**2): + return 0 + if not sum(p % f for f in factors): + factors.append(p) + return (-1)**len(factors) + + +# Comment by Juan Arias de Reyna: +# +# I learn this method to compute EulerE[2n] from van de Lune. +# +# We apply the formula EulerE[2n] = (-1)^n 2**(-2n) sum_{j=0}^n a(2n,2j+1) +# +# where the numbers a(n,j) vanish for j > n+1 or j <= -1 and satisfies +# +# a(0,-1) = a(0,0) = 0; a(0,1)= 1; a(0,2) = a(0,3) = 0 +# +# a(n,j) = a(n-1,j) when n+j is even +# a(n,j) = (j-1) a(n-1,j-1) + (j+1) a(n-1,j+1) when n+j is odd +# +# +# But we can use only one array unidimensional a(j) since to compute +# a(n,j) we only need to know a(n-1,k) where k and j are of different parity +# and we have not to conserve the used values. +# +# We cached up the values of Euler numbers to sufficiently high order. +# +# Important Observation: If we pretend to use the numbers +# EulerE[1], EulerE[2], ... , EulerE[n] +# it is convenient to compute first EulerE[n], since the algorithm +# computes first all +# the previous ones, and keeps them in the CACHE + +@lru_cache(maxsize=500) +def eulernum(m): + r""" + Computes the Euler numbers `E(n)`, which can be defined as + coefficients of the Taylor expansion of `1/cosh x`: + + .. math :: + + \frac{1}{\cosh x} = \sum_{n=0}^\infty \frac{E_n}{n!} x^n + + Example:: + + >>> [int(eulernum(n)) for n in range(11)] + [1, 0, -1, 0, 5, 0, -61, 0, 1385, 0, -50521] + >>> [int(eulernum(n)) for n in range(11)] # test cache + [1, 0, -1, 0, 5, 0, -61, 0, 1385, 0, -50521] + + """ + # for odd m > 1, the Euler numbers are zero + if m & 1: + return MPZ_ZERO + n = m + a = [MPZ(_) for _ in [0,0,1,0,0,0]] + suma = MPZ(1) + for n in range(1, m+1): + for j in range(n+1, -1, -2): + a[j+1] = (j-1)*a[j] + (j+1)*a[j+2] + a.append(0) + suma = 0 + for k in range(n+1, -1, -2): + suma += a[k+1] + return ((-1)**(n//2))*suma // 2**n + +def stirling1(n, k): + """ + Stirling number of the first kind. + """ + if n < 0 or k < 0: + raise ValueError + if k >= n: + return MPZ(n == k) + if k < 1: + return MPZ_ZERO + L = [MPZ_ZERO] * (k+1) + L[1] = MPZ_ONE + for m in range(2, n+1): + for j in range(min(k, m), 0, -1): + L[j] = (m-1) * L[j] + L[j-1] + return (-1)**(n+k) * L[k] + +def stirling2(n, k): + """ + Stirling number of the second kind. + """ + if n < 0 or k < 0: + raise ValueError + if k >= n: + return MPZ(n == k) + if k <= 1: + return MPZ(k == 1) + s = MPZ_ZERO + t = MPZ_ONE + for j in range(k+1): + if (k + j) & 1: + s -= t * MPZ(j)**n + else: + s += t * MPZ(j)**n + t = t * (k - j) // (j + 1) + return s // ifac(k) + +def jacobi_symbol(m, n): + """Returns the Jacobi symbol (m / n).""" + m, n = MPZ(m), MPZ(n) + if not n % 2: + raise ValueError('n should be an odd integer') + if n < 0: + return jacobi_symbol(m, -n)*(MPZ(-1) if m < 0 else MPZ_ONE) + if m < 0 or m > n: + m = m % n + if not m: + return MPZ(n == 1) + if n == 1 or m == 1: + return MPZ_ONE + if math.gcd(m, n) != 1: + return MPZ_ZERO + + j = MPZ_ONE + s = trailing(m) + m = m >> s + if s % 2 and n % 8 in [3, 5]: + j *= -1 + + while m != 1: + if m % 4 == 3 and n % 4 == 3: + j *= -1 + m, n = n % m, m + s = trailing(m) + m = m >> s + if s % 2 and n % 8 in [3, 5]: + j *= -1 + return j + +if gmpy and hasattr(gmpy, 'jacobi'): + def jacobi_symbol(m, n): + if n < 0: + return gmpy.jacobi(m, -n)*(MPZ(-1) if m < 0 else MPZ_ONE) + return gmpy.jacobi(m, n) diff --git a/mpmath/libmp/libmpc.py b/mpmath/libmp/libmpc.py new file mode 100644 index 0000000..4acca28 --- /dev/null +++ b/mpmath/libmp/libmpc.py @@ -0,0 +1,852 @@ +""" +Low-level functions for complex arithmetic. +""" + +import sys + +from .backend import MPZ +from .libelefun import (mpf_acos, mpf_acosh, mpf_asin, mpf_atan, mpf_atan2, + mpf_cos, mpf_cos_pi, mpf_cos_sin, mpf_cos_sin_pi, + mpf_cosh, mpf_cosh_sinh, mpf_exp, mpf_fibonacci, + mpf_ln, mpf_log1p, mpf_log_hypot, mpf_nthroot, mpf_phi, + mpf_pi, mpf_pow_int, mpf_sin, mpf_sin_pi, mpf_sinh, + mpf_tan, mpf_tanh) +from .libintmath import giant_steps, lshift, rshift +from .libmpf import (ComplexResult, fhalf, finf, fnan, fninf, fnone, fone, + from_float, from_int, from_man_exp, ftwo, fzero, mpf_abs, + mpf_add, mpf_ceil, mpf_div, mpf_floor, mpf_frac, mpf_hash, + mpf_hypot, mpf_mul, mpf_mul_int, mpf_neg, mpf_nint, + mpf_pos, mpf_rdiv_int, mpf_shift, mpf_sqrt, mpf_sub, + normalize, reciprocal_rnd, round_down, round_floor, + to_fixed, to_float, to_int, to_str) + + +# An mpc value is a (real, imag) tuple +mpc_one = fone, fzero +mpc_zero = fzero, fzero +mpc_two = ftwo, fzero +mpc_half = (fhalf, fzero) + +_infs = (finf, fninf) +_infs_nan = (finf, fninf, fnan) + +def mpc_is_inf(z): + """Check if either real or imaginary part is infinite""" + re, im = z + if re in _infs: return True + if im in _infs: return True + return False + +def mpc_is_infnan(z): + """Check if either real or imaginary part is infinite or nan""" + re, im = z + if re in _infs_nan: return True + if im in _infs_nan: return True + return False + +def mpc_to_str(z, dps, **kwargs): + re, im = z + rs = to_str(re, dps, **kwargs) + if im[0]: + return rs + " - " + to_str(mpf_neg(im), dps, **kwargs) + "j" + else: + return rs + " + " + to_str(im, dps, **kwargs) + "j" + +def mpc_to_complex(z, strict=False, rnd=round_down): + re, im = z + return complex(to_float(re, strict, rnd), to_float(im, strict, rnd)) + +def mpc_hash(z): + re, im = z + h = mpf_hash(re) + sys.hash_info.imag * mpf_hash(im) + if h == -1: + h = -2 + return int(h) + +def mpc_conjugate(z, prec, rnd=round_down): + re, im = z + return re, mpf_neg(im, prec, rnd) + +def mpc_is_nonzero(z): + return z != mpc_zero + +def mpc_add(z, w, prec, rnd=round_down): + a, b = z + c, d = w + return mpf_add(a, c, prec, rnd), mpf_add(b, d, prec, rnd) + +def mpc_add_mpf(z, x, prec, rnd=round_down): + a, b = z + return mpf_add(a, x, prec, rnd), b + +def mpc_sub(z, w, prec=0, rnd=round_down): + a, b = z + c, d = w + return mpf_sub(a, c, prec, rnd), mpf_sub(b, d, prec, rnd) + +def mpc_sub_mpf(z, p, prec=0, rnd=round_down): + a, b = z + return mpf_sub(a, p, prec, rnd), b + +def mpc_mpf_sub(p, z, prec=0, rnd=round_down): + a, b = z + return mpf_sub(p, a, prec, rnd), mpf_neg(b, prec, rnd) + +def mpc_pos(z, prec, rnd=round_down): + a, b = z + return mpf_pos(a, prec, rnd), mpf_pos(b, prec, rnd) + +def mpc_neg(z, prec=0, rnd=round_down): + a, b = z + return mpf_neg(a, prec, rnd), mpf_neg(b, prec, rnd) + +def mpc_shift(z, n): + a, b = z + return mpf_shift(a, n), mpf_shift(b, n) + +def mpc_abs(z, prec, rnd=round_down): + """Absolute value of a complex number, |a+bi|. + Returns an mpf value.""" + a, b = z + return mpf_hypot(a, b, prec, rnd) + +def mpc_arg(z, prec, rnd=round_down): + """Argument of a complex number. Returns an mpf value.""" + a, b = z + return mpf_atan2(b, a, prec, rnd) + +def mpc_floor(z, prec, rnd=round_down): + a, b = z + return mpf_floor(a, prec, rnd), mpf_floor(b, prec, rnd) + +def mpc_ceil(z, prec, rnd=round_down): + a, b = z + return mpf_ceil(a, prec, rnd), mpf_ceil(b, prec, rnd) + +def mpc_nint(z, prec, rnd=round_down): + a, b = z + return mpf_nint(a, prec, rnd), mpf_nint(b, prec, rnd) + +def mpc_frac(z, prec, rnd=round_down): + a, b = z + return mpf_frac(a, prec, rnd), mpf_frac(b, prec, rnd) + + +def mpc_mul(z, w, prec, rnd=round_down): + """ + Complex multiplication. + + Returns the real and imaginary part of (a+bi)*(c+di), rounded to + the specified precision. The rounding mode applies to the real and + imaginary parts separately. + """ + a, b = z + c, d = w + p = mpf_mul(a, c) + q = mpf_mul(b, d) + r = mpf_mul(a, d) + s = mpf_mul(b, c) + re = mpf_sub(p, q, prec, rnd) + im = mpf_add(r, s, prec, rnd) + return re, im + +def mpc_square(z, prec, rnd=round_down): + # (a+b*I)**2 == a**2 - b**2 + 2*I*a*b + a, b = z + p = mpf_mul(a,a) + q = mpf_mul(b,b) + r = mpf_mul(a,b, prec, rnd) + re = mpf_sub(p, q, prec, rnd) + im = mpf_shift(r, 1) + return re, im + +def mpc_mul_mpf(z, p, prec, rnd=round_down): + a, b = z + re = mpf_mul(a, p, prec, rnd) + im = mpf_mul(b, p, prec, rnd) + return re, im + +def mpc_mul_int(z, n, prec, rnd=round_down): + a, b = z + re = mpf_mul_int(a, n, prec, rnd) + im = mpf_mul_int(b, n, prec, rnd) + return re, im + +def mpc_div(z, w, prec, rnd=round_down): + if mpc_is_inf(w) and not mpc_is_infnan(z): + return fzero, fzero + a, b = z + c, d = w + wp = prec + 10 + # mag = c*c + d*d + mag = mpf_add(mpf_mul(c, c), mpf_mul(d, d), wp) + # (a*c+b*d)/mag, (b*c-a*d)/mag + t = mpf_add(mpf_mul(a,c), mpf_mul(b,d), wp) + u = mpf_sub(mpf_mul(b,c), mpf_mul(a,d), wp) + return mpf_div(t,mag,prec,rnd), mpf_div(u,mag,prec,rnd) + +def mpc_div_mpf(z, p, prec, rnd=round_down): + """Calculate z/p where p is real""" + a, b = z + re = mpf_div(a, p, prec, rnd) + im = mpf_div(b, p, prec, rnd) + return re, im + +def mpc_reciprocal(z, prec, rnd=round_down): + """Calculate 1/z efficiently""" + if mpc_is_inf(z): + return fzero, fzero + a, b = z + m = mpf_add(mpf_mul(a,a),mpf_mul(b,b),prec+10) + re = mpf_div(a, m, prec, rnd) + im = mpf_neg(mpf_div(b, m, prec, rnd)) + return re, im + +def mpc_mpf_div(p, z, prec, rnd=round_down): + """Calculate p/z where p is real efficiently""" + if mpc_is_inf(z) and p not in (finf, fninf, fnan): + return fzero, fzero + a, b = z + m = mpf_add(mpf_mul(a,a),mpf_mul(b,b), prec+10) + re = mpf_div(mpf_mul(a,p), m, prec, rnd) + im = mpf_div(mpf_neg(mpf_mul(b,p)), m, prec, rnd) + return re, im + +def complex_int_pow(a, b, n): + """Complex integer power: computes (a+b*I)**n exactly for + nonnegative n (a and b must be Python ints).""" + wre = 1 + wim = 0 + while n: + if n & 1: + wre, wim = wre*a - wim*b, wim*a + wre*b + n -= 1 + a, b = a*a - b*b, 2*a*b + n //= 2 + return wre, wim + +def mpc_pow(z, w, prec, rnd=round_down): + if w[1] == fzero: + return mpc_pow_mpf(z, w[0], prec, rnd) + return mpc_exp(mpc_mul(mpc_ln(z, prec+10), w, prec+10), prec, rnd) + +def mpc_pow_mpf(z, p, prec, rnd=round_down): + psign, pman, pexp, pbc = p + if pexp >= 0: + return mpc_pow_int(z, (-1)**psign * (pman< 0: + if de > 0: + aman <<= de + aexp = bexp + else: + bman <<= (-de) + bexp = aexp + re, im = complex_int_pow(aman, bman, n) + re = from_man_exp(re, int(n*aexp), prec, rnd) + im = from_man_exp(im, int(n*bexp), prec, rnd) + return re, im + return mpc_exp(mpc_mul_int(mpc_ln(z, prec+10), n, prec+10), prec, rnd) + +def mpc_sqrt(z, prec, rnd=round_down): + """Complex square root (principal branch). + + We have sqrt(a+bi) = sqrt((r+a)/2) + b/sqrt(2*(r+a))*i where + r = abs(a+bi), when a+bi is not a negative real number.""" + a, b = z + if b == fzero: + if a == fzero: + return (a, b) + # When a+bi is a negative real number, we get a real sqrt times i + if a[0]: + im = mpf_sqrt(mpf_neg(a), prec, rnd) + return (fzero, im) + else: + re = mpf_sqrt(a, prec, rnd) + return (re, fzero) + if b in (finf, fninf): + return (finf, b) + wp = prec+20 + if not a[0]: # case a positive + t = mpf_add(mpc_abs((a, b), wp), a, wp) # t = abs(a+bi) + a + u = mpf_shift(t, -1) # u = t/2 + re = mpf_sqrt(u, prec, rnd) # re = sqrt(u) + v = mpf_shift(t, 1) # v = 2*t + w = mpf_sqrt(v, wp) # w = sqrt(v) + im = mpf_div(b, w, prec, rnd) # im = b / w + else: # case a negative + t = mpf_sub(mpc_abs((a, b), wp), a, wp) # t = abs(a+bi) - a + u = mpf_shift(t, -1) # u = t/2 + im = mpf_sqrt(u, prec, rnd) # im = sqrt(u) + v = mpf_shift(t, 1) # v = 2*t + w = mpf_sqrt(v, wp) # w = sqrt(v) + re = mpf_div(b, w, prec, rnd) # re = b/w + if b[0]: + re = mpf_neg(re) + im = mpf_neg(im) + return re, im + +def mpc_nthroot_fixed(a, b, n, prec): + # a, b signed integers at fixed precision prec + start = 50 + a1 = int(rshift(a, prec - n*start)) + b1 = int(rshift(b, prec - n*start)) + try: + r = (a1 + 1j * b1)**(1.0/n) + re = r.real + im = r.imag + re = MPZ(re) + im = MPZ(im) + except OverflowError: + a1 = from_int(a1, start) + b1 = from_int(b1, start) + fn = from_int(n) + nth = mpf_rdiv_int(1, fn, start) + re, im = mpc_pow((a1, b1), (nth, fzero), start) + re = to_int(re) + im = to_int(im) + extra = 10 + prevp = start + extra1 = n + for p in giant_steps(start, prec+extra): + # this is slow for large n, unlike int_pow_fixed + re2, im2 = complex_int_pow(re, im, n-1) + re2 = rshift(re2, (n-1)*prevp - p - extra1) + im2 = rshift(im2, (n-1)*prevp - p - extra1) + r4 = (re2*re2 + im2*im2) >> (p + extra1) + ap = rshift(a, prec - p) + bp = rshift(b, prec - p) + rec = (ap * re2 + bp * im2) >> p + imc = (-ap * im2 + bp * re2) >> p + reb = (rec << p) // r4 + imb = (imc << p) // r4 + re = (reb + (n-1)*lshift(re, p-prevp))//n + im = (imb + (n-1)*lshift(im, p-prevp))//n + prevp = p + return re, im + +def mpc_nthroot(z, n, prec, rnd=round_down): + """ + Complex n-th root. + + Use Newton method as in the real case when it is faster, + otherwise use z**(1/n) + """ + a, b = z + if a[0] == 0 and b == fzero: + re = mpf_nthroot(a, n, prec, rnd) + return (re, fzero) + if n < 2: + if n == 0: + return mpc_one + if n == 1: + return mpc_pos((a, b), prec, rnd) + if n == -1: + return mpc_div(mpc_one, (a, b), prec, rnd) + inverse = mpc_nthroot((a, b), -n, prec+5, reciprocal_rnd[rnd]) + return mpc_div(mpc_one, inverse, prec, rnd) + if n <= 20: + prec2 = int(1.2 * (prec + 10)) + asign, aman, aexp, abc = a + bsign, bman, bexp, bbc = b + pf = mpc_abs((a,b), prec) + if pf[-2] + pf[-1] > -10 and pf[-2] + pf[-1] < prec: + af = to_fixed(a, prec2) + bf = to_fixed(b, prec2) + re, im = mpc_nthroot_fixed(af, bf, n, prec2) + extra = 10 + re = from_man_exp(re, -prec2-extra, prec2, rnd) + im = from_man_exp(im, -prec2-extra, prec2, rnd) + return re, im + fn = from_int(n) + prec2 = prec+10 + 10 + nth = mpf_rdiv_int(1, fn, prec2) + re, im = mpc_pow((a, b), (nth, fzero), prec2, rnd) + re = normalize(re[0], re[1], re[2], re[3], prec, rnd) + im = normalize(im[0], im[1], im[2], im[3], prec, rnd) + return re, im + +def mpc_cbrt(z, prec, rnd=round_down): + """ + Complex cubic root. + """ + return mpc_nthroot(z, 3, prec, rnd) + +def mpc_exp(z, prec, rnd=round_down): + """ + Complex exponential function. + + We use the direct formula exp(a+bi) = exp(a) * (cos(b) + sin(b)*i) + for the computation. This formula is very nice because it is + pefectly stable; since we just do real multiplications, the only + numerical errors that can creep in are single-ulp rounding errors. + + The formula is efficient since mpmath's real exp is quite fast and + since we can compute cos and sin simultaneously. + + It is no problem if a and b are large; if the implementations of + exp/cos/sin are accurate and efficient for all real numbers, then + so is this function for all complex numbers. + """ + a, b = z + if a == fzero: + return mpf_cos_sin(b, prec, rnd) + if b == fzero: + return mpf_exp(a, prec, rnd), fzero + mag = mpf_exp(a, prec+4, rnd) + c, s = mpf_cos_sin(b, prec+4, rnd) + re = mpf_mul(mag, c, prec, rnd) + im = mpf_mul(mag, s, prec, rnd) + return re, im + +def mpc_ln(z, prec, rnd=round_down): + re = mpf_log_hypot(z[0], z[1], prec, rnd) + im = mpc_arg(z, prec, rnd) + return re, im + +mpc_log = mpc_ln # deprecated alias + +def mpc_cos(z, prec, rnd=round_down): + """Complex cosine. The formula used is cos(a+bi) = cos(a)*cosh(b) - + sin(a)*sinh(b)*i. + + The same comments apply as for the complex exp: only real + multiplications are pewrormed, so no cancellation errors are + possible. The formula is also efficient since we can compute both + pairs (cos, sin) and (cosh, sinh) in single stwps.""" + a, b = z + if b == fzero: + return mpf_cos(a, prec, rnd), fzero + if a == fzero: + return mpf_cosh(b, prec, rnd), fzero + wp = prec + 6 + c, s = mpf_cos_sin(a, wp) + ch, sh = mpf_cosh_sinh(b, wp) + re = mpf_mul(c, ch, prec, rnd) + im = mpf_mul(s, sh, prec, rnd) + return re, mpf_neg(im) + +def mpc_sin(z, prec, rnd=round_down): + """Complex sine. We have sin(a+bi) = sin(a)*cosh(b) + + cos(a)*sinh(b)*i. See the docstring for mpc_cos for additional + comments.""" + a, b = z + if b == fzero: + return mpf_sin(a, prec, rnd), fzero + if a == fzero: + return fzero, mpf_sinh(b, prec, rnd) + wp = prec + 6 + c, s = mpf_cos_sin(a, wp) + ch, sh = mpf_cosh_sinh(b, wp) + re = mpf_mul(s, ch, prec, rnd) + im = mpf_mul(c, sh, prec, rnd) + return re, im + +def mpc_tan(z, prec, rnd=round_down): + """Complex tangent. Computed as tan(a+bi) = sin(2a)/M + sinh(2b)/M*i + where M = cos(2a) + cosh(2b).""" + a, b = z + if b == finf: + return fzero, fone + if b == fninf: + return fzero, fnone + if a in (finf, fninf): + if b == fzero: + return fnan, fzero + return fnan, fnan + asign, aman, aexp, abc = a + bsign, bman, bexp, bbc = b + if b == fzero: return mpf_tan(a, prec, rnd), fzero + if a == fzero: return fzero, mpf_tanh(b, prec, rnd) + wp = prec + 15 + a = mpf_shift(a, 1) + b = mpf_shift(b, 1) + c, s = mpf_cos_sin(a, wp) + ch, sh = mpf_cosh_sinh(b, wp) + # TODO: handle cancellation when c ~= -1 and ch ~= 1 + mag = mpf_add(c, ch, wp) + re = mpf_div(s, mag, prec, rnd) + im = mpf_div(sh, mag, prec, rnd) + return re, im + +def mpc_cos_pi(z, prec, rnd=round_down): + a, b = z + if b == fzero: + return mpf_cos_pi(a, prec, rnd), fzero + b = mpf_mul(b, mpf_pi(prec+5), prec+5) + if a == fzero: + return mpf_cosh(b, prec, rnd), fzero + wp = prec + 6 + c, s = mpf_cos_sin_pi(a, wp) + ch, sh = mpf_cosh_sinh(b, wp) + re = mpf_mul(c, ch, prec, rnd) + im = mpf_mul(s, sh, prec, rnd) + return re, mpf_neg(im) + +def mpc_sin_pi(z, prec, rnd=round_down): + a, b = z + if b == fzero: + return mpf_sin_pi(a, prec, rnd), fzero + b = mpf_mul(b, mpf_pi(prec+5), prec+5) + if a == fzero: + return fzero, mpf_sinh(b, prec, rnd) + wp = prec + 6 + c, s = mpf_cos_sin_pi(a, wp) + ch, sh = mpf_cosh_sinh(b, wp) + re = mpf_mul(s, ch, prec, rnd) + im = mpf_mul(c, sh, prec, rnd) + return re, im + +def mpc_cos_sin(z, prec, rnd=round_down): + a, b = z + if a == fzero: + ch, sh = mpf_cosh_sinh(b, prec, rnd) + return (ch, fzero), (fzero, sh) + if b == fzero: + c, s = mpf_cos_sin(a, prec, rnd) + return (c, fzero), (s, fzero) + wp = prec + 6 + c, s = mpf_cos_sin(a, wp) + ch, sh = mpf_cosh_sinh(b, wp) + cre = mpf_mul(c, ch, prec, rnd) + cim = mpf_mul(s, sh, prec, rnd) + sre = mpf_mul(s, ch, prec, rnd) + sim = mpf_mul(c, sh, prec, rnd) + return (cre, mpf_neg(cim)), (sre, sim) + +def mpc_cos_sin_pi(z, prec, rnd=round_down): + a, b = z + if b == fzero: + c, s = mpf_cos_sin_pi(a, prec, rnd) + return (c, fzero), (s, fzero) + b = mpf_mul(b, mpf_pi(prec+5), prec+5) + if a == fzero: + ch, sh = mpf_cosh_sinh(b, prec, rnd) + return (ch, fzero), (fzero, sh) + wp = prec + 6 + c, s = mpf_cos_sin_pi(a, wp) + ch, sh = mpf_cosh_sinh(b, wp) + cre = mpf_mul(c, ch, prec, rnd) + cim = mpf_mul(s, sh, prec, rnd) + sre = mpf_mul(s, ch, prec, rnd) + sim = mpf_mul(c, sh, prec, rnd) + return (cre, mpf_neg(cim)), (sre, sim) + +def mpc_cosh(z, prec, rnd=round_down): + """Complex hyperbolic cosine. Computed as cosh(z) = cos(z*i).""" + a, b = z + return mpc_cos((b, mpf_neg(a)), prec, rnd) + +def mpc_sinh(z, prec, rnd=round_down): + """Complex hyperbolic sine. Computed as sinh(z) = -i*sin(z*i).""" + a, b = z + b, a = mpc_sin((b, a), prec, rnd) + return a, b + +def mpc_tanh(z, prec, rnd=round_down): + """Complex hyperbolic tangent. Computed as tanh(z) = -i*tan(z*i).""" + a, b = z + b, a = mpc_tan((b, a), prec, rnd) + return a, b + +# TODO: avoid loss of accuracy +def mpc_atan(z, prec, rnd=round_down): + a, b = z + # atan(z) = (I/2)*(log(1-I*z) - log(1+I*z)) + # x = 1-I*z = 1 + b - I*a + # y = 1+I*z = 1 - b + I*a + wp = prec + 15 + x = mpf_add(fone, b, wp), mpf_neg(a) + y = mpf_sub(fone, b, wp), a + l1 = mpc_ln(x, wp) + l2 = mpc_ln(y, wp) + a, b = mpc_sub(l1, l2, prec, rnd) + # (I/2) * (a+b*I) = (-b/2 + a/2*I) + v = mpf_neg(mpf_shift(b,-1)), mpf_shift(a,-1) + # Subtraction at infinity gives correct real part but + # wrong imaginary part (should be zero) + if v[1] == fnan and mpc_is_inf(z): + v = (v[0], fzero) + return v + +beta_crossover = from_float(0.6417) +alpha_crossover = from_float(1.5) + +def acos_asin(z, prec, rnd, n): + """ complex acos for n = 0, asin for n = 1 + The algorithm is described in + T.E. Hull, T.F. Fairgrieve and P.T.P. Tang + 'Implementing the Complex Arcsine and Arcosine Functions + using Exception Handling', + ACM Trans. on Math. Software Vol. 23 (1997), p299 + The complex acos and asin can be defined as + acos(z) = acos(beta) - I*sign(a)* log(alpha + sqrt(alpha**2 -1)) + asin(z) = asin(beta) + I*sign(a)* log(alpha + sqrt(alpha**2 -1)) + where z = a + I*b + alpha = (1/2)*(r + s); beta = (1/2)*(r - s) = a/alpha + r = sqrt((a+1)**2 + y**2); s = sqrt((a-1)**2 + y**2) + These expressions are rewritten in different ways in different + regions, delimited by two crossovers alpha_crossover and beta_crossover, + and by abs(a) <= 1, in order to improve the numerical accuracy. + """ + a, b = z + wp = prec + 10 + # special cases with real argument + if b == fzero: + am = mpf_sub(fone, mpf_abs(a), wp) + # case abs(a) <= 1 + if not am[0]: + if n == 0: + return mpf_acos(a, prec, rnd), fzero + else: + return mpf_asin(a, prec, rnd), fzero + # cases abs(a) > 1 + else: + # case a < -1 + if a[0]: + pi = mpf_pi(prec, rnd) + c = mpf_acosh(mpf_neg(a), prec, rnd) + if n == 0: + return pi, mpf_neg(c) + else: + return mpf_neg(mpf_shift(pi, -1)), c + # case a > 1 + else: + c = mpf_acosh(a, prec, rnd) + if n == 0: + return fzero, c + else: + pi = mpf_pi(prec, rnd) + return mpf_shift(pi, -1), mpf_neg(c) + asign = bsign = 0 + if a[0]: + a = mpf_neg(a) + asign = 1 + if b[0]: + b = mpf_neg(b) + bsign = 1 + am = mpf_sub(fone, a, wp) + ap = mpf_add(fone, a, wp) + r = mpf_hypot(ap, b, wp) + s = mpf_hypot(am, b, wp) + alpha = mpf_shift(mpf_add(r, s, wp), -1) + beta = mpf_div(a, alpha, wp) + b2 = mpf_mul(b,b, wp) + # case beta <= beta_crossover + if not mpf_sub(beta_crossover, beta, wp)[0]: + if n == 0: + re = mpf_acos(beta, wp) + else: + re = mpf_asin(beta, wp) + else: + # to compute the real part in this region use the identity + # asin(beta) = atan(beta/sqrt(1-beta**2)) + # beta/sqrt(1-beta**2) = (alpha + a) * (alpha - a) + # alpha + a is numerically accurate; alpha - a can have + # cancellations leading to numerical inaccuracies, so rewrite + # it in differente ways according to the region + Ax = mpf_add(alpha, a, wp) + # case a <= 1 + if not am[0]: + # c = b*b/(r + (a+1)); d = (s + (1-a)) + # alpha - a = (1/2)*(c + d) + # case n=0: re = atan(sqrt((1/2) * Ax * (c + d))/a) + # case n=1: re = atan(a/sqrt((1/2) * Ax * (c + d))) + c = mpf_div(b2, mpf_add(r, ap, wp), wp) + d = mpf_add(s, am, wp) + re = mpf_shift(mpf_mul(Ax, mpf_add(c, d, wp), wp), -1) + if n == 0: + re = mpf_atan(mpf_div(mpf_sqrt(re, wp), a, wp), wp) + else: + re = mpf_atan(mpf_div(a, mpf_sqrt(re, wp), wp), wp) + else: + # c = Ax/(r + (a+1)); d = Ax/(s - (1-a)) + # alpha - a = (1/2)*(c + d) + # case n = 0: re = atan(b*sqrt(c + d)/2/a) + # case n = 1: re = atan(a/(b*sqrt(c + d)/2) + c = mpf_div(Ax, mpf_add(r, ap, wp), wp) + d = mpf_div(Ax, mpf_sub(s, am, wp), wp) + re = mpf_shift(mpf_add(c, d, wp), -1) + re = mpf_mul(b, mpf_sqrt(re, wp), wp) + if n == 0: + re = mpf_atan(mpf_div(re, a, wp), wp) + else: + re = mpf_atan(mpf_div(a, re, wp), wp) + # to compute alpha + sqrt(alpha**2 - 1), if alpha <= alpha_crossover + # replace it with 1 + Am1 + sqrt(Am1*(alpha+1))) + # where Am1 = alpha -1 + # if alpha <= alpha_crossover: + if not mpf_sub(alpha_crossover, alpha, wp)[0]: + c1 = mpf_div(b2, mpf_add(r, ap, wp), wp) + # case a < 1 + if mpf_neg(am)[0]: + # Am1 = (1/2) * (b*b/(r + (a+1)) + b*b/(s + (1-a)) + c2 = mpf_add(s, am, wp) + c2 = mpf_div(b2, c2, wp) + Am1 = mpf_shift(mpf_add(c1, c2, wp), -1) + else: + # Am1 = (1/2) * (b*b/(r + (a+1)) + (s - (1-a))) + c2 = mpf_sub(s, am, wp) + Am1 = mpf_shift(mpf_add(c1, c2, wp), -1) + # im = log(1 + Am1 + sqrt(Am1*(alpha+1))) + im = mpf_mul(Am1, mpf_add(alpha, fone, wp), wp) + im = mpf_log1p(mpf_add(Am1, mpf_sqrt(im, wp), wp), wp) + else: + # im = log(alpha + sqrt(alpha*alpha - 1)) + im = mpf_sqrt(mpf_sub(mpf_mul(alpha, alpha, wp), fone, wp), wp) + im = mpf_ln(mpf_add(alpha, im, wp), wp) + if asign: + if n == 0: + re = mpf_sub(mpf_pi(wp), re, wp) + else: + re = mpf_neg(re) + if not bsign and n == 0: + im = mpf_neg(im) + if bsign and n == 1: + im = mpf_neg(im) + if re[3] >= 0: + re = normalize(re[0], re[1], re[2], re[3], prec, rnd) + if im[3] >= 0: + im = normalize(im[0], im[1], im[2], im[3], prec, rnd) + # Correct real part for infinities and nan in imaginary component + if re == fnan and mpc_is_inf(z): + a, b = z + if a in (finf, fninf): + if b in (finf, fninf): + re = mpf_shift(mpf_pi(prec, rnd), -2) + if a == fninf: + if n == 0: + re = mpf_mul_int(re, 3, prec, rnd) + else: + re = mpf_neg(re) + elif b == fnan: + im = finf if n == 0 else fninf + else: + if n == 0: + re = fzero if a == finf else mpf_pi(prec, rnd) + else: + re = mpf_shift(mpf_pi(prec, rnd), -1) + if a == fninf: + re = mpf_neg(re) + else: # a == fnan + if n == 0: + return fnan, mpf_neg(b) + else: + return fnan, b + return re, im + +def mpc_acos(z, prec, rnd=round_down): + return acos_asin(z, prec, rnd, 0) + +def mpc_asin(z, prec, rnd=round_down): + return acos_asin(z, prec, rnd, 1) + +def mpc_asinh(z, prec, rnd=round_down): + # asinh(z) = I * asin(-I z) + a, b = z + a, b = mpc_asin((b, mpf_neg(a)), prec, rnd) + return mpf_neg(b), a + +def mpc_acosh(z, prec, rnd=round_down): + # acosh(z) = -I * acos(z) for Im(acos(z)) <= 0 + # +I * acos(z) otherwise + a, b = mpc_acos(z, prec, rnd) + if b[0] or b == fzero: + return mpf_neg(b), a + else: + return b, mpf_neg(a) + +def mpc_atanh(z, prec, rnd=round_down): + # atanh(z) = (log(1+z)-log(1-z))/2 + wp = prec + 15 + a = mpc_add(z, mpc_one, wp) + b = mpc_sub(mpc_one, z, wp) + a = mpc_ln(a, wp) + b = mpc_ln(b, wp) + v = mpc_shift(mpc_sub(a, b, wp), -1) + # Subtraction at infinity gives correct imaginary part but + # wrong real part (should be zero) + if v[0] == fnan and mpc_is_inf(z): + v = (fzero, v[1]) + return v + +def mpc_fibonacci(z, prec, rnd=round_down): + re, im = z + if im == fzero: + return (mpf_fibonacci(re, prec, rnd), fzero) + size = max(abs(re[2]+re[3]), abs(im[2]+im[3])) + wp = prec + size + 20 + a = mpf_phi(wp) + b = mpf_add(mpf_shift(a, 1), fnone, wp) + u = mpc_pow((a, fzero), z, wp) + v = mpc_cos_pi(z, wp) + v = mpc_div(v, u, wp) + u = mpc_sub(u, v, wp) + u = mpc_div_mpf(u, b, prec, rnd) + return u + +def mpf_expj(x, prec, rnd=round_down): + raise ComplexResult + +def mpc_expj(z, prec, rnd=round_down): + re, im = z + if im == fzero: + return mpf_cos_sin(re, prec, rnd) + if re == fzero: + return mpf_exp(mpf_neg(im), prec, rnd), fzero + ey = mpf_exp(mpf_neg(im), prec+10) + c, s = mpf_cos_sin(re, prec+10) + re = mpf_mul(ey, c, prec, rnd) + im = mpf_mul(ey, s, prec, rnd) + return re, im + +def mpf_expjpi(x, prec, rnd=round_down): + raise ComplexResult + +def mpc_expjpi(z, prec, rnd=round_down): + re, im = z + if im == fzero: + return mpf_cos_sin_pi(re, prec, rnd) + sign, man, exp, bc = im + wp = prec+10 + if man: + wp += max(0, exp+bc) + im = mpf_neg(mpf_mul(mpf_pi(wp), im, wp)) + if re == fzero: + return mpf_exp(im, prec, rnd), fzero + ey = mpf_exp(im, prec+10) + c, s = mpf_cos_sin_pi(re, prec+10) + re = mpf_mul(ey, c, prec, rnd) + im = mpf_mul(ey, s, prec, rnd) + return re, im diff --git a/mpmath/libmp/libmpf.py b/mpmath/libmp/libmpf.py new file mode 100644 index 0000000..a5719b7 --- /dev/null +++ b/mpmath/libmp/libmpf.py @@ -0,0 +1,1805 @@ +""" +Low-level functions for arbitrary-precision floating-point arithmetic. +""" + +import math +import random +import re +import sys + +from .backend import BACKEND, MPZ, MPZ_FIVE, MPZ_ONE, MPZ_ZERO, gmpy, int_types +from .libintmath import (bctable, bin_to_radix, isqrt, numeral, sqrtrem, + stddigits, trailing) + + +class ComplexResult(ValueError): + pass + +# All supported rounding modes +round_nearest = sys.intern('n') +round_floor = sys.intern('f') +round_ceiling = sys.intern('c') +round_up = sys.intern('u') +round_down = sys.intern('d') + +def prec_to_dps(n): + """Return number of accurate decimals that can be represented + with a precision of n bits.""" + return max(1, round(int(n)/blog2_10) - 1) + +def dps_to_prec(n): + """Return the number of bits required to represent n decimals + accurately.""" + return max(1, round((int(n) + 1)*blog2_10)) + +def repr_dps(n): + """Return the number of decimal digits required to represent + a number with n-bit precision so that it can be uniquely + reconstructed from the representation.""" + return 1 + math.ceil(int(n)/blog2_10) + +#----------------------------------------------------------------------------# +# Some commonly needed float values # +#----------------------------------------------------------------------------# + +# Regular number format: +# (-1)**sign * mantissa * 2**exponent, plus mantissa.bit_length() +fzero = (0, MPZ_ZERO, 0, 0) +fone = (0, MPZ_ONE, 0, 1) +fnone = (1, MPZ_ONE, 0, 1) +ftwo = (0, MPZ_ONE, 1, 1) +ften = (0, MPZ_FIVE, 1, 3) +fhalf = (0, MPZ_ONE, -1, 1) + +# Arbitrary encoding for special numbers: zero mantissa, nonzero exponent +fnan = (0, MPZ_ZERO, -123, -1) +finf = (0, MPZ_ZERO, -456, -2) +fninf = (1, MPZ_ZERO, -789, -3) + +math_float_inf = math.inf +math_float_nan = math.nan +blog2_10 = 3.3219280948873626 + +float_mant_dig = sys.float_info.mant_dig +float_min_exp = sys.float_info.min_exp +float_max_exp = sys.float_info.max_exp +float_eps = sys.float_info.epsilon +float_max = sys.float_info.max +float_min = sys.float_info.min +float_min_subnormal_exp = float_min_exp - float_mant_dig + + +#----------------------------------------------------------------------------# +# Rounding # +#----------------------------------------------------------------------------# + +# This function can be used to round a mantissa generally. However, +# we will try to do most rounding inline for efficiency. +def round_int(x, n, rnd): + if rnd == round_nearest: + if x >= 0: + t = x >> (n-1) + if t & 1 and ((t & 2) or (x & h_mask[n<300][n])): + return (t>>1)+1 + else: + return t>>1 + else: + return -round_int(-x, n, rnd) + if rnd == round_floor: + return x >> n + if rnd == round_ceiling: + return -((-x) >> n) + if rnd == round_down: + if x >= 0: + return x >> n + return -((-x) >> n) + if rnd == round_up: + if x >= 0: + return -((-x) >> n) + return x >> n + +# These masks are used to pick out segments of numbers to determine +# which direction to round when rounding to nearest. +class h_mask_big: + def __getitem__(self, n): + return (MPZ_ONE<<(n-1))-1 + +h_mask_small = [0]+[((MPZ_ONE<<(_-1))-1) for _ in range(1, 300)] +h_mask = [h_mask_big(), h_mask_small] + +# The >> operator rounds to floor. shifts_down[rnd][sign] +# tells whether this is the right direction to use, or if the +# number should be negated before shifting +shifts_down = {round_floor:(1,0), round_ceiling:(0,1), + round_down:(1,1), round_up:(0,0)} + + +#----------------------------------------------------------------------------# +# Normalization of raw mpfs # +#----------------------------------------------------------------------------# + +# This function is called almost every time an mpf is created. +# It has been optimized accordingly. + +def normalize(sign, man, exp, bc, prec, rnd): + """ + Create a raw mpf tuple with value (-1)**sign * man * 2**exp and + normalized mantissa. The mantissa is rounded according to the specified + rounding mode if its size exceeds the precision. Trailing zero bits + are also stripped from the mantissa to ensure that the + representation is canonical. + + Conditions on the input: + * The input must represent a regular (finite) number + * The sign bit must be 0 or 1 + * The mantissa must be nonnegative + * The exponent must be an integer + * The bitcount must be exact + + If these conditions are not met, use from_man_exp, mpf_pos, or any + of the conversion functions to create normalized raw mpf tuples. + """ + assert type(man) == MPZ + assert type(bc) in _exp_types + assert type(exp) in _exp_types + assert bc == man.bit_length() + assert man >= 0 + + if not man: + return fzero + # Cut mantissa down to size if larger than target precision + n = bc - prec + if n > 0: + if rnd == round_nearest: + t = man >> (n-1) + if t & 1 and ((t & 2) or (man & h_mask[n<300][n])): + man = (t>>1)+1 + else: + man = t>>1 + elif shifts_down[rnd][sign]: + man >>= n + else: + man = -((-man)>>n) + exp += n + bc = prec + # Strip trailing bits + if not man & 1: + t = trailing(man) + man >>= t + exp += t + bc -= t + # Bit count can be wrong if the input mantissa was 1 less than + # a power of 2 and got rounded up, thereby adding an extra bit. + # With trailing bits removed, all powers of two have mantissa 1, + # so this is easy to check for. + if man == 1: + bc = 1 + return sign, man, int(exp), int(bc) + +_exp_types = (int,) + +if gmpy: + normalize = gmpy._mpmath_normalize + +#----------------------------------------------------------------------------# +# Conversion functions # +#----------------------------------------------------------------------------# + +def from_man_exp(man, exp, prec=0, rnd=round_down): + """Create raw mpf from (man, exp) pair. The mantissa may be signed. + If no precision is specified, the mantissa is stored exactly.""" + if isinstance(man, int_types): + man = MPZ(man) + else: + raise TypeError("man expected to be an integer") + sign = 0 + if man < 0: + sign = 1 + man = -man + if man < 1024: + bc = bctable[man] + else: + bc = man.bit_length() + if not prec: + if not man: + return fzero + if not man & 1: + t = trailing(man) + return sign, man >> t, int(exp + t), int(bc - t) + return sign, man, exp, bc + return normalize(sign, man, exp, bc, prec, rnd) + +int_cache = dict((n, from_man_exp(n, 0)) for n in range(-10, 257)) + +if gmpy: + from_man_exp = gmpy._mpmath_create + +def from_int(n, prec=0, rnd=round_down): + """Create a raw mpf from an integer. If no precision is specified, + the mantissa is stored exactly.""" + if not prec: + if n in int_cache: + return int_cache[n] + return from_man_exp(MPZ(n), 0, prec, rnd) + +def to_man_exp(s, signed=True): + """Return (man, exp) of a raw mpf. Raise an error if inf/nan.""" + sign, man, exp, bc = s + if (not man) and exp: + raise ValueError("mantissa and exponent are defined " + "for finite numbers only") + if signed and sign: + man = -man + return man, exp + +def to_int(s, rnd=round_down): + """Convert a raw mpf to the nearest int. Rounding is done down by + default (same as int(float) in Python), but can be changed. If the + input is inf/nan, an exception is raised.""" + sign, man, exp, bc = s + if (not man) and exp: + if s == fnan: + raise ValueError("cannot convert nan to int") + raise OverflowError("cannot convert infinity to int") + if exp >= 0: + if sign: + return (-man) << exp + return man << exp + # Make default rounding fast + if rnd == round_down: + if sign: + return -(man >> (-exp)) + else: + return man >> (-exp) + if sign: + return round_int(-man, -exp, rnd) + else: + return round_int(man, -exp, rnd) + +def mpf_round_int(s, rnd): + sign, man, exp, bc = s + if (not man) and exp: + return s + if exp >= 0: + return s + mag = exp+bc + if mag < 1: + if rnd == round_ceiling: + if sign: return fzero + else: return fone + elif rnd == round_floor: + if sign: return fnone + else: return fzero + elif rnd == round_nearest: + if mag < 0 or man == MPZ_ONE: return fzero + elif sign: return fnone + else: return fone + else: + raise NotImplementedError + return mpf_pos(s, min(bc, mag), rnd) + +def mpf_floor(s, prec=0, rnd=round_down): + v = mpf_round_int(s, round_floor) + if prec: + v = mpf_pos(v, prec, rnd) + return v + +def mpf_ceil(s, prec=0, rnd=round_down): + v = mpf_round_int(s, round_ceiling) + if prec: + v = mpf_pos(v, prec, rnd) + return v + +def mpf_nint(s, prec=0, rnd=round_down): + v = mpf_round_int(s, round_nearest) + if prec: + v = mpf_pos(v, prec, rnd) + return v + +def mpf_frac(s, prec=0, rnd=round_down): + return mpf_sub(s, mpf_floor(s), prec, rnd) + +def from_float(x, prec=53, rnd=round_down): + """Create a raw mpf from a Python float, rounding if necessary. + If prec >= 53, the result is guaranteed to represent exactly the + same number as the input. If prec is not specified, use prec=53.""" + # frexp only raises an exception for nan on some platforms + if x != x: return fnan + if x == math_float_inf: return finf + if x == -math_float_inf: return fninf + m, e = math.frexp(x) + return from_man_exp(MPZ(m*(1<<53)), e-53, prec, rnd) + +def from_npfloat(x, prec=113, rnd=round_down): + """Create a raw mpf from a numpy float, rounding if necessary. + If prec >= 113, the result is guaranteed to represent exactly the + same number as the input. If prec is not specified, use prec=113.""" + y = float(x) + if x == y: # ldexp overflows for float16 + return from_float(y, prec, rnd) + import numpy as np + if np.isfinite(x): + m, e = np.frexp(x) + return from_man_exp(MPZ(np.ldexp(m, 113)), int(e)-113, prec, rnd) + return fnan + +def from_Decimal(x, prec=0, rnd=round_down): + """Create a raw mpf from a Decimal, rounding if necessary. + If prec is not specified, use the equivalent bit precision + of the number of significant digits in x.""" + if x.is_nan(): return fnan + if x.is_infinite(): return fninf if x.is_signed() else finf + if not prec: + prec = int(len(x.as_tuple()[1])*blog2_10) + return from_str(str(x), prec, rnd) + +def to_float(s, strict=False, rnd=round_down): + """ + Convert a raw mpf to a Python float. The result is exact + if s.bit_length() <= sys.float_info.mant_dig and no + underflow/overflow occurs. Else result is correctly rounded. + + If the magnitude of rounded number is too large to represent as + a regular float, it will be converted to infinity. Setting + strict=True forces an OverflowError to be raised instead. + """ + sign, man, exp, bc = s + + if not man: + if s == fzero: return 0.0 + if s == finf: return math_float_inf + if s == fninf: return -math_float_inf + return math_float_nan + + exp2 = exp + bc + # The smallest normal number is 2^(-1022)=0.1p-1021, and the smallest + # subnormal is 2^(-1074)=0.1p-1073 + if exp2 <= float_min_subnormal_exp: + if sign: + if rnd == round_floor or (rnd == round_nearest + and mpf_cmp(s, (1, MPZ(1), float_min_subnormal_exp + - 1, 1)) < 0): + return -float_min * float_eps + return 0.0 + if rnd == round_ceiling or (rnd == round_nearest + and mpf_cmp(s, (0, MPZ(1), float_min_subnormal_exp + - 1, 1)) > 0): + return float_min * float_eps + return 0.0 + + # The largest normal number is 2^1024*(1-2^(-53))=0.111...111p1024 + if exp2 > float_max_exp: + if sign: + if rnd == round_down or rnd == round_ceiling: + return -float_max + if strict: + raise OverflowError("math range error") + return -math_float_inf + if rnd == round_down or rnd == round_floor: + return float_max + if strict: + raise OverflowError("math range error") + return math_float_inf + + nbits = float_mant_dig + if exp2 < float_min_exp: + # In the subnormal case, compute the exact number of significant bits. + nbits += exp2 - float_min_exp + assert 1 <= nbits < float_mant_dig + if bc > nbits: + sign, man, exp, bc = normalize(sign, man, exp, bc, nbits, rnd) + if sign: + man = -man + # Should be exact: + return math.ldexp(man, exp) + +def from_rational(p, q, prec, rnd=round_down): + """Create a raw mpf from a rational number p/q, round if + necessary.""" + return mpf_div(from_int(p), from_int(q), prec, rnd) + +def to_rational(s): + """Convert a raw mpf to a rational number. Return integers (p, q) + such that s = p/q exactly.""" + if s == fnan: + raise ValueError("cannot convert nan to a rational number") + if s in (finf, fninf): + raise OverflowError("cannot convert infinity to a rational number") + sign, man, exp, bc = s + if sign: + man = -man + if exp >= 0: + return man * (1<= 0: return (-man) << offset + else: return (-man) >> (-offset) + else: + if offset >= 0: return man << offset + else: return man >> (-offset) + + +############################################################################## +############################################################################## + +#----------------------------------------------------------------------------# +# Arithmetic operations, etc. # +#----------------------------------------------------------------------------# + +def mpf_rand(prec): + """Return a raw mpf chosen randomly from [0, 1), with prec bits + in the mantissa.""" + return from_man_exp(MPZ(random.getrandbits(prec)), -prec, prec, round_floor) + +def mpf_eq(s, t): + """Test equality of two raw mpfs. This is simply tuple comparison + unless either number is nan, in which case the result is False.""" + if not s[1] or not t[1]: + if s == fnan or t == fnan: + return False + return s == t + +def mpf_hash(s): + # Duplicate the new hash algorithm, introduced in Python 3.2. + ssign, sman, sexp, sbc = s + + # Handle special numbers + if not sman: + if s == fnan: return object.__hash__(s) + if s == finf: return sys.hash_info.inf + if s == fninf: return -sys.hash_info.inf + + hash_modulus = sys.hash_info.modulus + hash_bits = 31 if sys.hash_info.width == 32 else 61 + h = sman % hash_modulus + if sexp >= 0: + sexp = sexp % hash_bits + else: + sexp = hash_bits - 1 - ((-1 - sexp) % hash_bits) + h = (h << sexp) % hash_modulus + if ssign: h = -h + if h == -1: h = -2 + return int(h) + +def mpf_cmp(s, t): + """Compare the raw mpfs s and t. Return -1 if s < t, 0 if s == t, + and 1 if s > t. (Same convention as Python's cmp() function.)""" + + # In principle, a comparison amounts to determining the sign of s-t. + # A full subtraction is relatively slow, however, so we first try to + # look at the components. + ssign, sman, sexp, sbc = s + tsign, tman, texp, tbc = t + + # Handle zeros and special numbers + if not sman or not tman: + if s == fzero: return -mpf_sign(t) + if t == fzero: return mpf_sign(s) + if s == t: return 0 + # Follow same convention as Python's cmp for float nan + if t == fnan: return 1 + if s == finf: return 1 + if t == fninf: return 1 + return -1 + # Different sides of zero + if ssign != tsign: + if not ssign: return 1 + return -1 + # This reduces to direct integer comparison + if sexp == texp: + if sman == tman: + return 0 + if sman > tman: + if ssign: return -1 + else: return 1 + else: + if ssign: return 1 + else: return -1 + # Check position of the highest set bit in each number. If + # different, there is certainly an inequality. + a = sbc + sexp + b = tbc + texp + if ssign: + if a < b: return 1 + if a > b: return -1 + else: + if a < b: return -1 + if a > b: return 1 + + # Both numbers have the same highest bit. Subtract to find + # how the lower bits compare. + delta = mpf_sub(s, t, 5, round_floor) + if delta[0]: + return -1 + return 1 + +def mpf_lt(s, t): + if s == fnan or t == fnan: + return False + return mpf_cmp(s, t) < 0 + +def mpf_le(s, t): + if s == fnan or t == fnan: + return False + return mpf_cmp(s, t) <= 0 + +def mpf_gt(s, t): + if s == fnan or t == fnan: + return False + return mpf_cmp(s, t) > 0 + +def mpf_ge(s, t): + if s == fnan or t == fnan: + return False + return mpf_cmp(s, t) >= 0 + +def mpf_min_max(seq): + min = max = seq[0] + for x in seq[1:]: + if mpf_lt(x, min): min = x + if mpf_gt(x, max): max = x + return min, max + +def mpf_pos(s, prec=0, rnd=round_down): + """Calculate 0+s for a raw mpf (i.e., just round s to the specified + precision).""" + if prec: + sign, man, exp, bc = s + if (not man) and exp: + return s + return normalize(sign, man, exp, bc, prec, rnd) + return s + +def mpf_neg(s, prec=0, rnd=round_down): + """Negate a raw mpf (return -s), rounding the result to the + specified precision. The prec argument can be omitted to do the + operation exactly.""" + sign, man, exp, bc = s + if not man: + if exp: + if s == finf: return fninf + if s == fninf: return finf + return s + if not prec: + return (1-sign, man, exp, bc) + return normalize(1-sign, man, exp, bc, prec, rnd) + +def mpf_abs(s, prec=0, rnd=round_down): + """Return abs(s) of the raw mpf s, rounded to the specified + precision. The prec argument can be omitted to generate an + exact result.""" + sign, man, exp, bc = s + if (not man) and exp: + if s == fninf: + return finf + return s + if not prec: + if sign: + return (0, man, exp, bc) + return s + return normalize(0, man, exp, bc, prec, rnd) + +def mpf_sign(s): + """Return -1, 0, or 1 (as a Python int, not a raw mpf) depending on + whether s is negative, zero, or positive. (Nan is taken to give 0.)""" + sign, man, exp, bc = s + if not man: + if s == finf: return 1 + if s == fninf: return -1 + return 0 + return (-1) ** sign + +def mpf_add(s, t, prec=0, rnd=round_down, _sub=0): + """ + Add the two raw mpf values s and t. + + With prec=0, no rounding is performed. Note that this can + produce a very large mantissa (potentially too large to fit + in memory) if exponents are far apart. + """ + ssign, sman, sexp, sbc = s + tsign, tman, texp, tbc = t + tsign ^= _sub + # Standard case: two nonzero, regular numbers + if sman and tman: + offset = sexp - texp + if offset: + if offset > 0: + # Outside precision range; only need to perturb + if offset > 100 and prec: + delta = sbc + sexp - tbc - texp + if delta > prec + 4: + offset = prec + 4 + sman <<= offset + if tsign == ssign: sman += 1 + else: sman -= 1 + return normalize(ssign, sman, sexp-offset, + sman.bit_length(), prec, rnd) + # Add + if ssign == tsign: + man = tman + (sman << offset) + # Subtract + else: + if ssign: man = tman - (sman << offset) + else: man = (sman << offset) - tman + if man >= 0: + ssign = 0 + else: + man = -man + ssign = 1 + bc = man.bit_length() + return normalize(ssign, man, texp, bc, prec or bc, rnd) + elif offset < 0: + # Outside precision range; only need to perturb + if offset < -100 and prec: + delta = tbc + texp - sbc - sexp + if delta > prec + 4: + offset = prec + 4 + tman <<= offset + if ssign == tsign: tman += 1 + else: tman -= 1 + return normalize(tsign, tman, texp-offset, + tman.bit_length(), prec, rnd) + # Add + if ssign == tsign: + man = sman + (tman << -offset) + # Subtract + else: + if tsign: man = sman - (tman << -offset) + else: man = (tman << -offset) - sman + if man >= 0: + ssign = 0 + else: + man = -man + ssign = 1 + bc = man.bit_length() + return normalize(ssign, man, sexp, bc, prec or bc, rnd) + # Equal exponents; no shifting necessary + if ssign == tsign: + man = tman + sman + else: + if ssign: man = tman - sman + else: man = sman - tman + if man >= 0: + ssign = 0 + else: + man = -man + ssign = 1 + bc = man.bit_length() + return normalize(ssign, man, texp, bc, prec or bc, rnd) + # Handle zeros and special numbers + if _sub: + t = mpf_neg(t) + if not sman: + if sexp: + if s == t or tman or not texp: + return s + return fnan + if tman: + return normalize(tsign, tman, texp, tbc, prec or tbc, rnd) + return t + if texp: + return t + if sman: + return normalize(ssign, sman, sexp, sbc, prec or sbc, rnd) + return s + +def mpf_sub(s, t, prec=0, rnd=round_down): + """Return the difference of two raw mpfs, s-t. This function is + simply a wrapper of mpf_add that changes the sign of t.""" + return mpf_add(s, t, prec, rnd, 1) + +def mpf_sum(xs, prec=0, rnd=round_down, absolute=False): + """ + Sum a list of mpf values efficiently and accurately + (typically no temporary roundoff occurs). If prec=0, + the final result will not be rounded either. + + There may be roundoff error or cancellation if extremely + large exponent differences occur. + + With absolute=True, sums the absolute values. + """ + man = MPZ(0) + exp = 0 + max_extra_prec = prec*2 or 1000000 # XXX + special = None + for x in xs: + xsign, xman, xexp, xbc = x + if xman: + if xsign and not absolute: + xman = -xman + delta = xexp - exp + if xexp >= exp: + # x much larger than existing sum? + # first: quick test + if (delta > max_extra_prec) and \ + ((not man) or delta-man.bit_length() > max_extra_prec): + man = xman + exp = xexp + else: + man += (xman << delta) + else: + delta = -delta + # x much smaller than existing sum? + if delta-xbc > max_extra_prec: + if not man: + man, exp = xman, xexp + else: + man = (man << delta) + xman + exp = xexp + elif xexp: + if absolute: + x = mpf_abs(x) + special = mpf_add(special or fzero, x, 1) + # Will be inf or nan + if special: + return special + return from_man_exp(man, exp, prec, rnd) + +def mpf_mul(s, t, prec=0, rnd=round_down): + """Multiply two raw mpfs""" + ssign, sman, sexp, sbc = s + tsign, tman, texp, tbc = t + sign = ssign ^ tsign + man = sman*tman + if man: + bc = man.bit_length() + if prec: + return normalize(sign, man, sexp+texp, bc, prec, rnd) + else: + return (sign, man, sexp+texp, bc) + s_special = (not sman) and sexp + t_special = (not tman) and texp + if not s_special and not t_special: + return fzero + if fnan in (s, t): return fnan + if (not tman) and texp: s, t = t, s + if t == fzero: return fnan + return {1:finf, -1:fninf}[mpf_sign(s) * mpf_sign(t)] + +def gmpy_mpf_mul_int(s, n, prec, rnd=round_down): + """Multiply by a Python integer.""" + sign, man, exp, bc = s + if not man: + return mpf_mul(s, from_int(n), prec, rnd) + if not n: + return fzero + if n < 0: + sign ^= 1 + n = -n + man *= n + return normalize(sign, man, exp, man.bit_length(), prec, rnd) + +def python_mpf_mul_int(s, n, prec, rnd=round_down): + """Multiply by a Python integer.""" + sign, man, exp, bc = s + if not man: + return mpf_mul(s, from_int(n), prec, rnd) + if not n: + return fzero + if n < 0: + sign ^= 1 + n = -n + man *= n + # Generally n will be small + if n < 1024: + bc += bctable[n] - 1 + else: + bc += n.bit_length() - 1 + bc += man>>bc + return normalize(sign, man, exp, bc, prec, rnd) + +mpf_mul_int = python_mpf_mul_int + +if gmpy: + mpf_mul_int = gmpy_mpf_mul_int + +def mpf_shift(s, n): + """Quickly multiply the raw mpf s by 2**n without rounding.""" + sign, man, exp, bc = s + if not man: + return s + return sign, man, exp+n, bc + +def mpf_frexp(x): + """Convert x = y*2**n to (y, n) with abs(y) in [0.5, 1) if nonzero""" + sign, man, exp, bc = x + if not man: + return (x, 0) + return mpf_shift(x, -bc-exp), bc+exp + +def mpf_div(s, t, prec, rnd=round_down): + """Floating-point division""" + ssign, sman, sexp, sbc = s + tsign, tman, texp, tbc = t + if not sman or not tman: + if s == fzero: + if t == fzero: raise ZeroDivisionError + if t == fnan: return fnan + return fzero + if t == fzero: + raise ZeroDivisionError + s_special = (not sman) and sexp + t_special = (not tman) and texp + if s_special and t_special: + return fnan + if s == fnan or t == fnan: + return fnan + if not t_special: + if t == fzero: + return fnan + return {1:finf, -1:fninf}[mpf_sign(s) * mpf_sign(t)] + return fzero + sign = ssign ^ tsign + if tman == 1: + return normalize(sign, sman, sexp-texp, sbc, prec, rnd) + # Same strategy as for addition: if there is a remainder, perturb + # the result a few bits outside the precision range before rounding + if not prec: + extra = max(sbc, tbc) - sbc + tbc + 5 + else: + extra = prec - sbc + tbc + 5 + if extra < 5: + extra = 5 + quot, rem = divmod(sman< sexp+sbc: + return s + # Another important special case: this allows us to do e.g. x % 1.0 + # to find the fractional part of x, and it will work when x is huge. + if tman == 1 and sexp > texp+tbc: + return fzero + base = min(sexp, texp) + sman = (-1)**ssign * sman + tman = (-1)**tsign * tman + man = (sman << (sexp-base)) % (tman << (texp-base)) + if man >= 0: + sign = 0 + else: + man = -man + sign = 1 + return normalize(sign, man, base, man.bit_length(), prec, rnd) + +reciprocal_rnd = { + round_down : round_up, + round_up : round_down, + round_floor : round_ceiling, + round_ceiling : round_floor, + round_nearest : round_nearest +} + +negative_rnd = { + round_down : round_down, + round_up : round_up, + round_floor : round_ceiling, + round_ceiling : round_floor, + round_nearest : round_nearest +} + +def mpf_pow_int(s, n, prec, rnd=round_down): + """Compute s**n, where s is a raw mpf and n is a Python integer.""" + sign, man, exp, bc = s + + if (not man) and exp: + if s == finf: + if n > 0: return s + if n == 0: return fone + return fzero + if s == fninf: + if n > 0: return [finf, fninf][n & 1] + if n == 0: return fone + return fzero + if n == 0: + return fone + return fnan + + n = int(n) + if n == 0: return fone + if n == 1: return mpf_pos(s, prec, rnd) + if n == 2: + _, man, exp, bc = s + if not man: + return fzero + man = man*man + if man == 1: + return (0, MPZ_ONE, exp+exp, 1) + bc = bc + bc - 2 + bc += bctable[man>>bc] + return normalize(0, man, exp+exp, bc, prec, rnd) + if n == -1: return mpf_div(fone, s, prec, rnd) + if n < 0: + inverse = mpf_pow_int(s, -n, prec+5, reciprocal_rnd[rnd]) + return mpf_div(fone, inverse, prec, rnd) + + result_sign = sign & n + + # Use exact integer power when the exact mantissa is small + if man == 1: + return (result_sign, MPZ_ONE, exp*n, 1) + if bc*n < 1000: + man **= n + return normalize(result_sign, man, exp*n, man.bit_length(), prec, rnd) + + # Use directed rounding all the way through to maintain rigorous + # bounds for interval arithmetic + rounds_down = (rnd == round_nearest) or \ + shifts_down[rnd][result_sign] + + # Now we perform binary exponentiation. Need to estimate precision + # to avoid rounding errors from temporary operations. Roughly log_2(n) + # operations are performed. + workprec = prec + 4*n.bit_length() + 4 + _, pm, pe, pbc = fone + while 1: + if n & 1: + pm = pm*man + pe = pe+exp + pbc += bc - 2 + pbc = pbc + bctable[pm >> pbc] + if pbc > workprec: + if rounds_down: + pm = pm >> (pbc-workprec) + else: + pm = -((-pm) >> (pbc-workprec)) + pe += pbc - workprec + pbc = workprec + n -= 1 + if not n: + break + man = man*man + exp = exp+exp + bc = bc + bc - 2 + bc = bc + bctable[man >> bc] + if bc > workprec: + if rounds_down: + man = man >> (bc-workprec) + else: + man = -((-man) >> (bc-workprec)) + exp += bc - workprec + bc = workprec + n = n // 2 + + return normalize(result_sign, pm, pe, pbc, prec, rnd) + + +def mpf_perturb(x, eps_sign, prec, rnd): + """ + For nonzero x, calculate x + eps with directed rounding, where + eps < prec relatively and eps has the given sign (0 for + positive, 1 for negative). + + With rounding to nearest, this is taken to simply normalize + x to the given precision. + """ + if rnd == round_nearest: + return mpf_pos(x, prec, rnd) + sign, man, exp, bc = x + eps = (eps_sign, MPZ_ONE, exp+bc-prec-1, 1) + if sign: + away = (rnd in (round_down, round_ceiling)) ^ eps_sign + else: + away = (rnd in (round_up, round_ceiling)) ^ eps_sign + if away: + return mpf_add(x, eps, prec, rnd) + else: + return mpf_pos(x, prec, rnd) + + +#----------------------------------------------------------------------------# +# Radix conversion # +#----------------------------------------------------------------------------# + +def to_digits_exp(s, dps, base=10): + """Helper function for representing the floating-point number s as + a string with dps digits. Returns (sign, string, exponent) where + sign is '' or '-', string is the digit string in the given base, + and exponent is the exponent as an int. + + If inexact, the string representation is rounded toward zero.""" + + # Extract sign first so it doesn't mess up the string digit count + if s[0]: + sign = '-' + s = mpf_neg(s) + else: + sign = '' + _sign, man, exp, bc = s + + if not man: + return '', '0'*int(dps), 0 + + if base == 10: + blog2 = blog2_10 + elif pow(2, blog2 := int(math.log2(base))) == base: + pass + else: + raise NotImplementedError + + bitprec = int(dps * blog2) + 10 + + # Cut down to size + # TODO: account for precision when doing this + exp_from_1 = exp + bc + if base == 10 and abs(exp_from_1) > 3500: + from .libelefun import mpf_ln2, mpf_ln10 + + # Set b = int(exp * log(2)/log(10)) + # If exp is huge, we must use high-precision arithmetic to + # find the nearest power of ten + expprec = exp.bit_length() + 5 + tmp = from_int(exp) + tmp = mpf_mul(tmp, mpf_ln2(expprec)) + tmp = mpf_div(tmp, mpf_ln10(expprec), expprec) + b = to_int(tmp) + s = mpf_div(s, mpf_pow_int(ften, b, bitprec), bitprec) + _sign, man, exp, bc = s + exponent = b + else: + exponent = 0 + + # First, calculate mantissa digits by converting to a binary + # fixed-point number and then converting that number to + # a decimal fixed-point number. + fixprec = max(bitprec - exp - bc, 0) + fixdps = int(fixprec / blog2 + 0.5) + sf = to_fixed(s, fixprec) + sb = bin_to_radix(sf, fixprec, base, fixdps) + digits = numeral(sb, base=base, size=dps) + + exponent += len(digits) - fixdps - 1 + return sign, digits, exponent + +def round_digits(sign, digits, dps, base, rnd=round_down, fixed=False): + """ + Returns the rounded digits, and the number of places the decimal point was + shifted. + """ + + assert len(digits) > dps + assert rnd in (round_nearest, round_up, round_down, round_ceiling, + round_floor) + + if rnd == round_ceiling: + rnd = round_down if sign else round_up + elif rnd == round_floor: + rnd = round_up if sign else round_down + + exponent = 0 + + if rnd == round_down: + return digits[:dps], 0 + elif rnd == round_nearest: + rnd_digs = stddigits[(base//2 + base % 2):base] + else: + rnd_digs = stddigits[1:base] + + tie_down = False + tie_up = False + + if rnd == round_nearest: + # The first digit after dps is a 5 and we should determine whether we + # round it up or down. + if digits[dps] == rnd_digs[0]: + tie_down = True + + # If the digit we round to is even, we may round down if all the + # following digits are 0. + for i in range(dps+1, len(digits)): + if digits[i] != '0': + tie_down = False + break + # If the digit we round to is odd, we round up no matter what. + if digits[dps-1] in stddigits[1:base:2]: + tie_down = False + + elif rnd == round_up: + # If any digit following a 0 is different from zero, we round up. + if digits[dps] == '0': + for i in range(dps+1, len(digits)): + if digits[i] != '0': + tie_up = True + break + + # Add or subtract a unit to the digit following the one we round to. + if tie_down: + digits = digits[:dps] + stddigits[int(digits[dps], base) - 1] + elif tie_up: + digits = digits[:dps] + '1' + + # Rounding up kills some instances of "...99999" + if digits[dps] in rnd_digs: + digits = digits[:dps] + i = dps - 1 + dig = stddigits[base-1] + while i >= 0 and digits[i] == dig: + i -= 1 + if i >= 0: + digits = digits[:i] + stddigits[int(digits[i], base) + 1] + \ + '0' * (dps - i - 1) + else: + # When rounding up 0.9999... in fixed format, we lose one dps. + digits = '1' + '0' * (dps - (0 if fixed else 1)) + exponent += 1 + else: + digits = digits[:dps] + + return digits, exponent + + +def to_str(s, dps, strip_zeros=True, min_fixed=None, max_fixed=None, + show_zero_exponent=False, base=10, binary_exp=False, + rnd=round_nearest): + """ + Convert a raw mpf to a floating-point literal in the given base + with at most `dps` digits in the mantissa (not counting extra zeros + that may be inserted for visual purposes). + + The number will be printed in fixed-point format if the position + of the leading digit is strictly between min_fixed + (default = min(-dps/3,-5)) and max_fixed (default = dps). + + To force fixed-point format always, set min_fixed = -inf, + max_fixed = +inf. To force floating-point format, set + min_fixed >= max_fixed. + + If binary_exp is True and the base is either 2 or 16, the number will + be printed in a binary or hexadecimal notation, where the exponent + separator is the 'p' and the exponent is written in decimal rather than + hexadecimal or binary. The number is normalized, i.e. the first + digit is 1. This is format of the float.fromhex(). + + The literal is formatted so that it can be parsed back to a number + by from_str, float(), float.fromhex() or Decimal(). + """ + sep = '@' if base > 10 else 'e' + + if binary_exp: + sep = 'p' + if base not in (2, 16): + raise ValueError("binary_exp option could be used for base 2 and 16") + + if rnd not in (round_nearest, round_floor, round_ceiling, round_up, + round_down): + raise ValueError("rnd should be one of " + + ", ".join([round_nearest, round_floor, round_ceiling, + round_up, round_down]) + ".") + + if base == 2: + prefix = "0b" + elif base == 8: + prefix = "0o" + elif base == 16: + prefix = "0x" + else: + prefix = "" + + # Special numbers + if not s[1]: + if s == fzero: + if dps: t = '0.0' + else: t = '.0' + if show_zero_exponent: + t += sep + '+0' + return prefix + t + if s == finf: return 'inf' + if s == fninf: return '-inf' + if s == fnan: return 'nan' + raise ValueError + + if min_fixed is None: min_fixed = min(-(dps//3), -5) + if max_fixed is None: max_fixed = dps + + # to_digits_exp rounds to floor. + # This sometimes kills some instances of "...00001" + sign, digits, exponent = to_digits_exp(s, dps+10, base) + + rnd_digs = stddigits[(base//2 + base%2):base] + + # No digits: show only .0; round exponent to nearest + if not dps: + if digits[0] in rnd_digs: + exponent += 1 + digits = ".0" + + else: + if binary_exp and base == 16: + exponent *= 4 + # normalization + if int(digits[0], 16) > 1: + shift = math.floor(math.log2(int(digits[0], 16))) + exponent += shift + n = int(digits, 16) >> shift + digits = hex(n)[2:] + + digits, exp_add = round_digits(s[0], digits, dps, base, rnd) + exponent += exp_add + + # Prettify numbers close to unit magnitude + if not binary_exp and min_fixed < exponent < max_fixed: + if exponent < 0: + digits = ("0"*(-exponent)) + digits + split = 1 + else: + split = exponent + 1 + if split > dps: + digits += "0"*(split-dps) + exponent = 0 + else: + split = 1 + + digits = (digits[:split] + "." + digits[split:]) + + if strip_zeros: + # Clean up trailing zeros + digits = digits.rstrip('0') + if digits[-1] == ".": + digits += "0" + + sign += prefix + + if exponent == 0 and dps and not show_zero_exponent: return sign + digits + return sign + digits + sep + f"{exponent:+}" + +def str_to_man_exp(x, base=10): + """Helper function for from_str.""" + x = x.lower().rstrip('l').replace('_', '') + # Split into mantissa, exponent + if base <= 10: + sep = 'e' + else: + sep = '@' + if pow(2, e2 := int(math.log2(base))) == base and e2 in [1, 4] and x.find('p') >= 0: + sep = 'p' + parts = x.split(sep) + if len(parts) == 1: + exp = 0 + elif len(parts) == 2: + x = parts[0] + exp = int(parts[1]) + else: + raise ValueError("couldn't convert a str to mpf") + # Look for radix point in mantissa + parts = x.split('.') + if len(parts) == 2: + a, b = parts[0], parts[1].rstrip('0') + if sep != 'p': + exp -= len(b) + else: + exp -= len(b)*e2 + if a == '': + a = '0' + x = a + b + int_max_str_digits = 0 + if BACKEND == 'python' and hasattr(sys, 'get_int_max_str_digits'): + int_max_str_digits = sys.get_int_max_str_digits() + sys.set_int_max_str_digits(0) + x = MPZ(x, base) + if int_max_str_digits: + sys.set_int_max_str_digits(int_max_str_digits) + return x, exp + +special_str = {'inf':finf, '+inf':finf, '-inf':fninf, 'nan':fnan, + 'oo':finf, '+oo':finf, '-oo':fninf} + +def from_str(x, prec=0, rnd=round_down, base=0): + """Create a raw mpf from a string x in a given base, rounding in the + specified direction if the input number cannot be represented + exactly as a binary floating-point number with the given number of + bits. The string syntax accepted for float() or float.fromhex() + is accepted too. + + TODO: the rounding does not work properly for large exponents. + """ + x = x.lower().strip() + if x in special_str: + return special_str[x] + + if not base: + if x.startswith(('0b', '-0b', '0B', '-0B')): + base = 2 + elif x.startswith(('0x', '-0x', '0X', '-0X')): + base = 16 + elif x.startswith(('0o', '-0o')): + base = 8 + else: + base = 10 + + if '/' in x: + p, q = x.split('/') + p, q = p.rstrip('l'), q.rstrip('l') + return from_rational(int(p, base), int(q, base), prec, rnd) + + man, exp = str_to_man_exp(x, base) + + if base == 10: + # XXX: appropriate cutoffs & track direction + # note no factors of 5 + if abs(exp) > 400: + s = from_int(man, prec+10) + s = mpf_mul(s, mpf_pow_int(ften, exp, prec+10), prec, rnd) + else: + if exp >= 0: + s = from_int(man * 10**exp, prec, rnd) + else: + s = from_rational(man, 10**-exp, prec, rnd) + elif pow(2, e2 := int(math.log2(base))) == base: + if x.find('p') < 0: + s = from_man_exp(man, exp*e2, prec, rnd) + else: + s = from_man_exp(man, exp, prec, rnd) + else: + raise NotImplementedError + return s + + +#----------------------------------------------------------------------------# +# String formatting # +#----------------------------------------------------------------------------# + +_FLOAT_FORMAT_SPECIFICATION_MATCHER = re.compile(r""" + (?: + (?P.)? + (?P[<>=^]) + )? + (?P[-+ ]?) + (?Pz)? + (?P\#)? + (?P0(?=0*[1-9]))? + (?P[0-9]+)? + (?P[,_])? + (?:\. + (?=[,_0-9]) # lookahead for digit or separator + (?P[0-9]+)? + (?P[,_])? + )? + (?P[UDYZN])? + (?P[aAbeEfFgG%])? +""", re.DOTALL | re.VERBOSE).fullmatch + +_GMPY_ROUND_CHAR_DICT = { + 'U': round_ceiling, + 'D': round_floor, + 'Y': round_up, + 'Z': round_down, + 'N': round_nearest + } + +def calc_padding(nchars, width, align): + ''' + Computes the left and right padding required to fill the required width, + according to how the string will be aligned. + ''' + ntotal = max(nchars, width) + + if align in ('>', '='): + lpad = ntotal - nchars + rpad = 0 + elif align == '^': + lpad = (ntotal - nchars)//2 + rpad = ntotal - nchars - lpad + else: + lpad = 0 + rpad = ntotal - nchars + + return (lpad, rpad) + + +def read_format_spec(format_spec): + ''' + Reads the format spec into a dictionary. + This is more or less copied from the CPython implementation for regular + floats. + ''' + + format_dict = { + 'fill_char': ' ', + 'align': '>', + 'sign': '-', + 'no_neg_0': False, + 'alternate': False, + 'thousands_separators': '', + 'frac_separators': '', + 'width': -1, + 'precision': -1, + 'type': '' + } + + if match := _FLOAT_FORMAT_SPECIFICATION_MATCHER(format_spec): + format_dict['fill_char'] = match['fill_char'] or format_dict['fill_char'] + format_dict['align'] = match['align'] or format_dict['align'] + format_dict['sign'] = match['sign'] or format_dict['sign'] + format_dict['no_neg_0'] = bool(match['no_neg_0']) or format_dict['no_neg_0'] + format_dict['alternate'] = bool(match['alternate']) or \ + format_dict['alternate'] + format_dict['thousands_separators'] = match['thousands_separators'] \ + or format_dict['thousands_separators'] + format_dict['width'] = int(match['width'] or format_dict['width']) + format_dict['precision'] = int(match['precision'] or format_dict['precision']) + format_dict['frac_separators'] = match['frac_separators'] \ + or format_dict['frac_separators'] + rounding_char = match['rounding'] + format_dict['type'] = match['type'] or format_dict['type'] + + if rounding_char is not None: + format_dict['rounding'] = _GMPY_ROUND_CHAR_DICT[rounding_char] + + if match['zeropad']: + if not match['align']: + format_dict['align'] = '=' + if not match['fill_char']: + format_dict['fill_char'] = '0' + + if format_dict['precision'] < 0 and format_dict['type'].lower() not in ['', 'a', 'b']: + format_dict['precision'] = 6 + else: + raise ValueError("Invalid format specifier '{}'".format(format_spec)) + + return format_dict + + +def format_fixed(s, dps, rnd=round_down): + # First, get the exponent to know how many digits we will need + base = 10 + _, _, exponent = to_digits_exp(s, 1, base) + + # Now that we have an estimate, compute the correct digits + # (we do this because the previous computation could yield the wrong + # exponent by +- 1) + _, digits, exponent = to_digits_exp( + s, max(dps+exponent+4, int(s[3]/blog2_10)), base) + orig_dps = dps + dps += exponent + 1 + + # The number we want to print is lower in magnitude that the requested + # precision. We should only print 0s. + if dps < 0: + int_part = '0' + frac_part = orig_dps*'0' + + else: + digits, exp_add = round_digits(s[0], digits, dps, base, rnd, True) + exponent += exp_add + + # Here we prepend the corresponding 0s to the digits string, according + # to the value of exponent + if exponent < 0: + digits = ("0"*(-exponent)) + digits + split = 1 + else: + split = exponent + 1 + int_part = digits[:split] + + # Finally, assemble the digits including the decimal point + if orig_dps == 0: + return int_part, '' + + frac_part = digits[split:] + + return int_part, frac_part + + +def format_scientific(s, dps, rnd=round_down): + base = 10 + + # First, get the exponent to know how many digits we will need + dps += 1 + _, digits, exponent = to_digits_exp(s, max(dps + 10, + int(s[3]/blog2_10) + 10), + base) + digits, exp_add = round_digits(s[0], digits, dps, base, rnd) + exponent += exp_add + + return digits[0], digits[1:], f'e{exponent:+03d}' + + +def format_hexadecimal(s, dps, rnd=round_down): + prec = 4*dps + 1 if dps >= 0 else s[1].bit_length() + + if s[1]: + s = mpf_pos(s, prec, rnd) + + exponent = s[2] + s[3] - 1 + man = s[1] | (1 << s[3] + 2) # set leading digit (ignored) to 0x9 + man <<= 1 + 4*((s[3] + 3)//4) - s[3] + + frac_digits = hex(man)[3:] + digits = "1" + else: + exponent = 0 + frac_digits = "" + digits = "0" + + if dps >= 0: + frac_digits = frac_digits[:dps] + frac_digits += "0"*(dps - len(frac_digits)) + else: + # Clean up trailing zeros + frac_digits = frac_digits.rstrip('0') + + return digits, frac_digits, f'p{exponent:+01d}' + + +def format_binary(s, dps, rnd=round_down): + prec = dps + 1 if dps >= 0 else s[1].bit_length() + s = mpf_pos(s, prec, rnd) + + digits = bin(s[1])[2:] + digits = digits + '0'*(dps + 1 - len(digits)) + exponent = s[2] + if s[1]: + exponent += s[1].bit_length() - 1 + return digits[0], digits[1:], f'p{exponent:+01d}' + + +_MAP_SPEC_STR = {finf: 'inf', fninf: 'inf', fnan: 'nan'} + + +def fill_sep(digits, sep, prev, nmod, sep_range): + return prev + sep.join(digits[pos:pos + sep_range] + for pos in range(nmod, len(digits), sep_range)) + + +def format_digits(num, format_dict, prec, rnd, _pretty_repr_dps): + capitalize = False + if format_dict['type'] in list('AFGE'): + capitalize = True + + fmt_type = format_dict['type'].lower() + + percent = False + if fmt_type == '%': + percent = True + fmt_type = 'f' + num = mpf_mul(num, from_int(100), prec, rnd=round_nearest) + + dps = format_dict['precision'] + + int_part = '' + exponent = '' + sign = '' + + # Now the general case + strip_last_zero = False + strip_zeros = False + + rnd = format_dict.get('rounding', rnd) + + if not fmt_type or fmt_type == 'g': + if not format_dict['alternate']: + strip_zeros = True + if fmt_type == 'g': + strip_last_zero = True + + if dps < 0: + dps = repr_dps(prec) if _pretty_repr_dps else prec_to_dps(prec) + if dps == 0: + dps = 1 + + _, tdigits, exp = to_digits_exp(num, max(53/blog2_10, dps), 10) + if num[1]: + _, exp_add = round_digits(num, tdigits, dps, 10, rnd) + exp += exp_add + + fix0 = 0 if fmt_type else 1 + if -4 <= exp < dps - fix0: + dps = max(0, dps - exp - 1) + else: + fmt_type = 'e' + dps = max(0, dps - 1) + + if num in _MAP_SPEC_STR: # special cases + frac_part = _MAP_SPEC_STR[num] + if capitalize: + frac_part = frac_part.upper() + + elif fmt_type == 'e': + int_part, frac_part, exponent = format_scientific(num, dps, rnd=rnd) + if strip_zeros: + frac_part = frac_part.rstrip('0') + if frac_part or format_dict['alternate']: + frac_part = '.' + frac_part + if capitalize: + exponent = exponent.replace('e', 'E') + + elif fmt_type == 'a': + int_part, frac_part, exponent = format_hexadecimal(num, dps, rnd=rnd) + if capitalize: + int_part = '0X' + int_part + frac_part = frac_part.upper() + exponent = exponent.replace('p', 'P') + else: + int_part = '0x' + int_part + if frac_part or format_dict['alternate']: + frac_part = '.' + frac_part + + elif fmt_type == 'b': + int_part, frac_part, exponent = format_binary(num, dps, rnd=rnd) + if frac_part or format_dict['alternate']: + frac_part = '.' + frac_part + + else: # fixed-point formats + int_part, frac_part = format_fixed(num, dps, rnd=rnd) + + if strip_zeros: + frac_part = frac_part.rstrip('0') + if not frac_part and not fmt_type: + frac_part = '0' + if (frac_part or format_dict['alternate'] + or (dps and not strip_last_zero)): + frac_part = '.' + frac_part + + sep_range = 3 + sep = format_dict['frac_separators'] + if sep and frac_part: + frac_part = fill_sep(frac_part, sep, frac_part[0], 1, sep_range) + digits = frac_part + exponent + + sign = '-' if num[0] else '' + if sign != '-' and format_dict['sign'] != '-': + sign = format_dict['sign'] + if fmt_type == 'f' and format_dict['no_neg_0']: + if int_part == "0" and all(_ in ['0', '.', '_', ','] + for _ in digits): + if format_dict['sign'] == '-': + sign = '' + else: + sign = format_dict['sign'] + + if percent: + digits += '%' + + sep = format_dict['thousands_separators'] + width = format_dict['width'] + min_leading = width - len(digits) - len(sign) + if (int_part and fmt_type not in ['a', 'b'] + and format_dict['fill_char'] == '0' and format_dict['align'] == '=' + and min_leading > len(int_part)): + int_part = int_part.zfill(sep_range*min_leading//(sep_range + 1) + 1 + if sep else min_leading) + + # Add the thousands separator every 3 characters. + split = len(int_part) + if sep and split > sep_range: + # the first thousand separator may be located before 3 characters + nmod = split % sep_range + if nmod != 0: + prev = int_part[:nmod] + sep + else: + prev = '' + int_part = fill_sep(int_part, sep, prev, nmod, sep_range) + + return sign, int_part + digits + + +def format_mpf(num, format_spec, prec, rnd, _pretty_repr_dps): + format_dict = read_format_spec(format_spec) + sign, digits = format_digits(num, format_dict, prec, rnd, _pretty_repr_dps) + nchars = len(digits) + len(sign) + lpad, rpad = calc_padding( + nchars, format_dict['width'], format_dict['align']) + + if format_dict['align'] == '=': + return sign + lpad*format_dict['fill_char'] + digits + \ + rpad*format_dict['fill_char'] + + return lpad*format_dict['fill_char'] + sign + digits \ + + rpad*format_dict['fill_char'] + + +def format_mpc(num, format_spec, prec, rnd, _pretty_repr_dps): + format_dict = read_format_spec(format_spec) + + if format_dict['fill_char'] == '0': + raise ValueError("Zero padding is not allowed in complex format " + "specifier.") + if format_dict['align'] == '=': + raise ValueError("'=' alignment flag is not allowed in complex format " + "specifier.") + if format_dict['type'] == '%': + raise ValueError("'%' formatting type is not allowed in complex " + "format specifier.") + + fmt_type = format_dict['type'].lower() + if not fmt_type: + format_dict['type'] = 'g' + sign_re, digits_re = format_digits(num[0], format_dict, prec, rnd, _pretty_repr_dps) + fmt_sign = format_dict['sign'] + format_dict['sign'] = '+' + sign_im, digits_im = format_digits(num[1], format_dict, prec, rnd, _pretty_repr_dps) + digits_im += 'j' + + if not fmt_type: + if num[0] == fzero: + sign_re = '' + digits_re = '' + if sign_im == '+': + sign_im = fmt_sign if fmt_sign in [' ', '+'] else '' + else: + sign_re = '(' + sign_re + digits_im += ')' + + nchars = len(sign_re) + len(digits_re) + len(sign_im) + len(digits_im) + + lpad, rpad = calc_padding(nchars, format_dict['width'], + format_dict['align']) + + return (lpad*format_dict['fill_char'] + sign_re + digits_re + sign_im + + digits_im + rpad*format_dict['fill_char']) + + +#----------------------------------------------------------------------------# +# Square roots # +#----------------------------------------------------------------------------# + + +def mpf_sqrt(s, prec, rnd=round_down): + """ + Compute the square root of a nonnegative mpf value. The + result is correctly rounded. + """ + sign, man, exp, bc = s + if sign: + raise ComplexResult("square root of a negative number") + if not man: + return s + if exp & 1: + exp -= 1 + man <<= 1 + bc += 1 + elif man == 1: + return normalize(sign, man, exp//2, bc, prec, rnd) + shift = max(4, 2*prec-bc+4) + shift += shift & 1 + if rnd in 'fd': + man = isqrt(man<= 0: + a = mpf_pos(sa, prec, round_floor) + b = mpf_pos(sb, prec, round_ceiling) + # Upper point nonnegative? + elif sbs >= 0: + a = fzero + negsa = mpf_neg(sa) + if mpf_lt(negsa, sb): + b = mpf_pos(sb, prec, round_ceiling) + else: + b = mpf_pos(negsa, prec, round_ceiling) + # Both negative? + else: + a = mpf_neg(sb, prec, round_floor) + b = mpf_neg(sa, prec, round_ceiling) + return a, b + +# TODO: optimize +def mpi_mul_mpf(s, t, prec): + return mpi_mul(s, (t, t), prec) + +def mpi_div_mpf(s, t, prec): + return mpi_div(s, (t, t), prec) + +def mpi_mul(s, t, prec=0): + sa, sb = s + ta, tb = t + sas = mpf_sign(sa) + sbs = mpf_sign(sb) + tas = mpf_sign(ta) + tbs = mpf_sign(tb) + if sas == sbs == 0: + # Should maybe be undefined + if ta == fninf or tb == finf: + return fninf, finf + return fzero, fzero + if tas == tbs == 0: + # Should maybe be undefined + if sa == fninf or sb == finf: + return fninf, finf + return fzero, fzero + if sas >= 0: + # positive * positive + if tas >= 0: + a = mpf_mul(sa, ta, prec, round_floor) + b = mpf_mul(sb, tb, prec, round_ceiling) + if a == fnan: a = fzero + if b == fnan: b = finf + # positive * negative + elif tbs <= 0: + a = mpf_mul(sb, ta, prec, round_floor) + b = mpf_mul(sa, tb, prec, round_ceiling) + if a == fnan: a = fninf + if b == fnan: b = fzero + # positive * both signs + else: + a = mpf_mul(sb, ta, prec, round_floor) + b = mpf_mul(sb, tb, prec, round_ceiling) + if a == fnan: a = fninf + if b == fnan: b = finf + elif sbs <= 0: + # negative * positive + if tas >= 0: + a = mpf_mul(sa, tb, prec, round_floor) + b = mpf_mul(sb, ta, prec, round_ceiling) + if a == fnan: a = fninf + if b == fnan: b = fzero + # negative * negative + elif tbs <= 0: + a = mpf_mul(sb, tb, prec, round_floor) + b = mpf_mul(sa, ta, prec, round_ceiling) + if a == fnan: a = fzero + if b == fnan: b = finf + # negative * both signs + else: + a = mpf_mul(sa, tb, prec, round_floor) + b = mpf_mul(sa, ta, prec, round_ceiling) + if a == fnan: a = fninf + if b == fnan: b = finf + else: + # General case: perform all cross-multiplications and compare + # Since the multiplications can be done exactly, we need only + # do 4 (instead of 8: two for each rounding mode) + cases = [mpf_mul(sa, ta), mpf_mul(sa, tb), mpf_mul(sb, ta), mpf_mul(sb, tb)] + if fnan in cases: + a, b = (fninf, finf) + else: + a, b = mpf_min_max(cases) + a = mpf_pos(a, prec, round_floor) + b = mpf_pos(b, prec, round_ceiling) + return a, b + +def mpi_square(s, prec=0): + sa, sb = s + if mpf_ge(sa, fzero): + a = mpf_mul(sa, sa, prec, round_floor) + b = mpf_mul(sb, sb, prec, round_ceiling) + elif mpf_le(sb, fzero): + a = mpf_mul(sb, sb, prec, round_floor) + b = mpf_mul(sa, sa, prec, round_ceiling) + else: + sa = mpf_neg(sa) + sa, sb = mpf_min_max([sa, sb]) + a = fzero + b = mpf_mul(sb, sb, prec, round_ceiling) + return a, b + +def mpi_div(s, t, prec): + sa, sb = s + ta, tb = t + sas = mpf_sign(sa) + sbs = mpf_sign(sb) + tas = mpf_sign(ta) + tbs = mpf_sign(tb) + # 0 / X + if sas == sbs == 0: + # 0 / + if (tas < 0 and tbs > 0) or (tas == 0 or tbs == 0): + return fninf, finf + return fzero, fzero + # Denominator contains both negative and positive numbers; + # this should properly be a multi-interval, but the closest + # match is the entire (extended) real line + if tas < 0 and tbs > 0: + return fninf, finf + # Assume denominator to be nonnegative + if tas < 0: + return mpi_div(mpi_neg(s), mpi_neg(t), prec) + # Division by zero + # XXX: make sure all results make sense + if tas == 0: + # Numerator contains both signs? + if sas < 0 and sbs > 0: + return fninf, finf + if tas == tbs: + return fninf, finf + # Numerator positive? + if sas >= 0: + a = mpf_div(sa, tb, prec, round_floor) + b = finf + if sbs <= 0: + a = fninf + b = mpf_div(sb, tb, prec, round_ceiling) + # Division with positive denominator + # We still have to handle nans resulting from inf/0 or inf/inf + else: + # Nonnegative numerator + if sas >= 0: + a = mpf_div(sa, tb, prec, round_floor) + b = mpf_div(sb, ta, prec, round_ceiling) + if a == fnan: a = fzero + if b == fnan: b = finf + # Nonpositive numerator + elif sbs <= 0: + a = mpf_div(sa, ta, prec, round_floor) + b = mpf_div(sb, tb, prec, round_ceiling) + if a == fnan: a = fninf + if b == fnan: b = fzero + # Numerator contains both signs? + else: + a = mpf_div(sa, ta, prec, round_floor) + b = mpf_div(sb, ta, prec, round_ceiling) + if a == fnan: a = fninf + if b == fnan: b = finf + return a, b + +def mpi_pi(prec): + a = mpf_pi(prec, round_floor) + b = mpf_pi(prec, round_ceiling) + return a, b + +def mpi_exp(s, prec): + sa, sb = s + # exp is monotonic + a = mpf_exp(sa, prec, round_floor) + b = mpf_exp(sb, prec, round_ceiling) + return a, b + +def mpi_log(s, prec): + sa, sb = s + # log is monotonic + a = mpf_ln(sa, prec, round_floor) + b = mpf_ln(sb, prec, round_ceiling) + return a, b + +def mpi_sqrt(s, prec): + sa, sb = s + # sqrt is monotonic + a = mpf_sqrt(sa, prec, round_floor) + b = mpf_sqrt(sb, prec, round_ceiling) + return a, b + +def mpi_atan(s, prec): + sa, sb = s + a = mpf_atan(sa, prec, round_floor) + b = mpf_atan(sb, prec, round_ceiling) + return a, b + +def mpi_pow_int(s, n, prec): + sa, sb = s + if n < 0: + return mpi_div((fone, fone), mpi_pow_int(s, -n, prec+20), prec) + if n == 0: + return (fone, fone) + if n == 1: + return s + if n == 2: + return mpi_square(s, prec) + # Odd -- signs are preserved + if n & 1: + a = mpf_pow_int(sa, n, prec, round_floor) + b = mpf_pow_int(sb, n, prec, round_ceiling) + # Even -- important to ensure positivity + else: + sas = mpf_sign(sa) + sbs = mpf_sign(sb) + # Nonnegative? + if sas >= 0: + a = mpf_pow_int(sa, n, prec, round_floor) + b = mpf_pow_int(sb, n, prec, round_ceiling) + # Nonpositive? + elif sbs <= 0: + a = mpf_pow_int(sb, n, prec, round_floor) + b = mpf_pow_int(sa, n, prec, round_ceiling) + # Mixed signs? + else: + a = fzero + # max(-a,b)**n + sa = mpf_neg(sa) + if mpf_ge(sa, sb): + b = mpf_pow_int(sa, n, prec, round_ceiling) + else: + b = mpf_pow_int(sb, n, prec, round_ceiling) + return a, b + +def mpi_pow(s, t, prec): + ta, tb = t + if ta == tb and ta not in (finf, fninf): + if ta == from_int(to_int(ta)): + return mpi_pow_int(s, to_int(ta), prec) + if ta == fhalf: + return mpi_sqrt(s, prec) + u = mpi_log(s, prec + 20) + v = mpi_mul(u, t, prec + 20) + return mpi_exp(v, prec) + +def MIN(x, y): + if mpf_le(x, y): + return x + return y + +def MAX(x, y): + if mpf_ge(x, y): + return x + return y + +def cos_sin_quadrant(x, wp): + sign, man, exp, bc = x + if x == fzero: + return fone, fzero, 0 + # TODO: combine evaluation code to avoid duplicate modulo + c, s = mpf_cos_sin(x, wp) + t, n, wp_ = mod_pi2(man, exp, exp+bc, 15) + if sign: + n = -1-n + return c, s, n + +def mpi_cos_sin(x, prec): + a, b = x + if a == b == fzero: + return (fone, fone), (fzero, fzero) + # Guaranteed to contain both -1 and 1 + if (finf in x) or (fninf in x): + return (fnone, fone), (fnone, fone) + wp = prec + 20 + ca, sa, na = cos_sin_quadrant(a, wp) + cb, sb, nb = cos_sin_quadrant(b, wp) + ca, cb = mpf_min_max([ca, cb]) + sa, sb = mpf_min_max([sa, sb]) + # Both functions are monotonic within one quadrant + if na == nb: + pass + # Guaranteed to contain both -1 and 1 + elif nb - na >= 4: + return (fnone, fone), (fnone, fone) + else: + # cos has maximum between a and b + if na//4 != nb//4: + cb = fone + # cos has minimum + if (na-2)//4 != (nb-2)//4: + ca = fnone + # sin has maximum + if (na-1)//4 != (nb-1)//4: + sb = fone + # sin has minimum + if (na-3)//4 != (nb-3)//4: + sa = fnone + # Perturb to force interval rounding + more = from_man_exp((MPZ_ONE<= 1: + if sign: + return fnone + return fone + return v + ca = finalize(ca, round_floor) + cb = finalize(cb, round_ceiling) + sa = finalize(sa, round_floor) + sb = finalize(sb, round_ceiling) + return (ca,cb), (sa,sb) + +def mpi_cos(x, prec): + return mpi_cos_sin(x, prec)[0] + +def mpi_sin(x, prec): + return mpi_cos_sin(x, prec)[1] + +def mpi_tan(x, prec): + cos, sin = mpi_cos_sin(x, prec+20) + return mpi_div(sin, cos, prec) + +def mpi_cot(x, prec): + cos, sin = mpi_cos_sin(x, prec+20) + return mpi_div(cos, sin, prec) + +def mpi_from_str_a_b(x, y, percent, prec): + wp = prec + 20 + xa = from_str(x, wp, round_floor) + xb = from_str(x, wp, round_ceiling) + #ya = from_str(y, wp, round_floor) + y = from_str(y, wp, round_ceiling) + assert mpf_ge(y, fzero) + if percent: + y = mpf_mul(MAX(mpf_abs(xa), mpf_abs(xb)), y, wp, round_ceiling) + y = mpf_div(y, from_int(100), wp, round_ceiling) + a = mpf_sub(xa, y, prec, round_floor) + b = mpf_add(xb, y, prec, round_ceiling) + return a, b + +def mpi_from_str(s, prec): + """ + Parse an interval number given as a string. + + Allowed forms are + + "-1.23e-27" + Any single decimal floating-point literal. + "a +- b" or "a (b)" + a is the midpoint of the interval and b is the half-width + "a +- b%" or "a (b%)" + a is the midpoint of the interval and the half-width + is b percent of a (`a \times b / 100`). + "[a, b]" + The interval indicated directly. + "x[y,z]e" + x are shared digits, y and z are unequal digits, e is the exponent. + + """ + e = ValueError("Improperly formed interval number '%s'" % s) + s = s.replace(" ", "") + wp = prec + 20 + if "+-" in s: + x, y = s.split("+-") + return mpi_from_str_a_b(x, y, False, prec) + # case 2 + elif "(" in s: + # Don't confuse with a complex number (x,y) + if s[0] == "(" or ")" not in s: + raise e + s = s.replace(")", "") + percent = False + if "%" in s: + if s[-1] != "%": + raise e + percent = True + s = s.replace("%", "") + x, y = s.split("(") + return mpi_from_str_a_b(x, y, percent, prec) + elif "," in s: + if ('[' not in s) or (']' not in s): + raise e + if s[0] == '[': + # case 3 + s = s.replace("[", "") + s = s.replace("]", "") + a, b = s.split(",") + a = from_str(a, prec, round_floor) + b = from_str(b, prec, round_ceiling) + return a, b + else: + # case 4 + x, y = s.split('[') + y, z = y.split(',') + if 'e' in s: + z, e = z.split(']') + else: + z, e = z.rstrip(']'), '' + a = from_str(x+y+e, prec, round_floor) + b = from_str(x+z+e, prec, round_ceiling) + return a, b + else: + a = from_str(s, prec, round_floor) + b = from_str(s, prec, round_ceiling) + return a, b + +def mpi_to_str(x, dps, use_spaces=True, brackets='[]', mode='brackets', error_dps=4, **kwargs): + """ + Convert a mpi interval to a string. + + **Arguments** + + *dps* + decimal places to use for printing + *use_spaces* + use spaces for more readable output, defaults to true + *brackets* + pair of strings (or two-character string) giving left and right brackets + *mode* + mode of display: 'plusminus', 'percent', 'brackets' (default) or 'diff' + *error_dps* + limit the error to *error_dps* digits (mode 'plusminus and 'percent') + + Additional keyword arguments are forwarded to the mpf-to-string conversion + for the components of the output. + + **Examples** + + >>> from mpmath import mpi, mp, iv + >>> mp.dps = 30 + >>> x = mpi(1, 2)._mpi_ + >>> mpi_to_str(x, 2, mode='plusminus') + '1.5 +- 0.5' + >>> mpi_to_str(x, 2, mode='percent') + '1.5 (33.33%)' + >>> mpi_to_str(x, 2, mode='brackets') + '[1.0, 2.0]' + >>> mpi_to_str(x, 2, mode='brackets' , brackets=('<', '>')) + '<1.0, 2.0>' + >>> iv.dps = 30 + >>> x = mpi('5.2582327113062393041', '5.2582327113062749951')._mpi_ + >>> mpi_to_str(x, 15, mode='diff') + '5.2582327113062[4, 7]' + >>> mpi_to_str(mpi(0)._mpi_, 2, mode='percent') + '0.0 (0.0%)' + + """ + prec = dps_to_prec(dps) + wp = prec + 20 + a, b = x + mid = mpi_mid(x, prec) + delta = mpi_delta(x, prec) + a_str = to_str(a, dps, **kwargs) + b_str = to_str(b, dps, **kwargs) + mid_str = to_str(mid, dps, **kwargs) + sp = "" + if use_spaces: + sp = " " + br1, br2 = brackets + if mode == 'plusminus': + delta_str = to_str(mpf_shift(delta,-1), dps, **kwargs) + s = mid_str + sp + "+-" + sp + delta_str + elif mode == 'percent': + if mid == fzero: + p = fzero + else: + # p = 100 * delta(x) / (2*mid(x)) + p = mpf_mul(delta, from_int(100)) + p = mpf_div(p, mpf_mul(mid, from_int(2)), wp) + s = mid_str + sp + "(" + to_str(p, error_dps) + "%)" + elif mode == 'brackets': + s = br1 + a_str + "," + sp + b_str + br2 + elif mode == 'diff': + # use more digits if str(x.a) and str(x.b) are equal + if a_str == b_str: + a_str = to_str(a, dps+3, **kwargs) + b_str = to_str(b, dps+3, **kwargs) + # separate mantissa and exponent + a = a_str.split('e') + if len(a) == 1: + a.append('') + b = b_str.split('e') + if len(b) == 1: + b.append('') + if a[1] == b[1]: + if a[0] != b[0]: + for i in range(len(a[0]) + 1): + if a[0][i] != b[0][i]: + break + s = (a[0][:i] + br1 + a[0][i:] + ',' + sp + b[0][i:] + br2 + + 'e'*min(len(a[1]), 1) + a[1]) + else: # no difference + s = a[0] + br1 + br2 + 'e'*min(len(a[1]), 1) + a[1] + else: + s = br1 + 'e'.join(a) + ',' + sp + 'e'.join(b) + br2 + else: + raise ValueError("'%s' is unknown mode for printing mpi" % mode) + return s + +def mpci_add(x, y, prec): + a, b = x + c, d = y + return mpi_add(a, c, prec), mpi_add(b, d, prec) + +def mpci_sub(x, y, prec): + a, b = x + c, d = y + return mpi_sub(a, c, prec), mpi_sub(b, d, prec) + +def mpci_neg(x, prec=0): + a, b = x + return mpi_neg(a, prec), mpi_neg(b, prec) + +def mpci_pos(x, prec): + a, b = x + return mpi_pos(a, prec), mpi_pos(b, prec) + +def mpci_mul(x, y, prec): + # TODO: optimize for real/imag cases + a, b = x + c, d = y + r1 = mpi_mul(a,c) + r2 = mpi_mul(b,d) + re = mpi_sub(r1,r2,prec) + i1 = mpi_mul(a,d) + i2 = mpi_mul(b,c) + im = mpi_add(i1,i2,prec) + return re, im + +def mpci_div(x, y, prec): + # TODO: optimize for real/imag cases + a, b = x + c, d = y + wp = prec+20 + m1 = mpi_square(c) + m2 = mpi_square(d) + m = mpi_add(m1,m2,wp) + re = mpi_add(mpi_mul(a,c), mpi_mul(b,d), wp) + im = mpi_sub(mpi_mul(b,c), mpi_mul(a,d), wp) + re = mpi_div(re, m, prec) + im = mpi_div(im, m, prec) + return re, im + +def mpci_exp(x, prec): + a, b = x + wp = prec+20 + r = mpi_exp(a, wp) + c, s = mpi_cos_sin(b, wp) + a = mpi_mul(r, c, prec) + b = mpi_mul(r, s, prec) + return a, b + +def mpi_shift(x, n): + a, b = x + return mpf_shift(a,n), mpf_shift(b,n) + +def mpi_cosh_sinh(x, prec): + # TODO: accuracy for small x + wp = prec+20 + e1 = mpi_exp(x, wp) + e2 = mpi_div(mpi_one, e1, wp) + c = mpi_add(e1, e2, prec) + s = mpi_sub(e1, e2, prec) + c = mpi_shift(c, -1) + s = mpi_shift(s, -1) + return c, s + +def mpci_cos(x, prec): + a, b = x + wp = prec+10 + c, s = mpi_cos_sin(a, wp) + ch, sh = mpi_cosh_sinh(b, wp) + re = mpi_mul(c, ch, prec) + im = mpi_mul(s, sh, prec) + return re, mpi_neg(im) + +def mpci_sin(x, prec): + a, b = x + wp = prec+10 + c, s = mpi_cos_sin(a, wp) + ch, sh = mpi_cosh_sinh(b, wp) + re = mpi_mul(s, ch, prec) + im = mpi_mul(c, sh, prec) + return re, im + +def mpci_abs(x, prec): + a, b = x + if a == mpi_zero: + return mpi_abs(b) + if b == mpi_zero: + return mpi_abs(a) + # Important: nonnegative + a = mpi_square(a) + b = mpi_square(b) + t = mpi_add(a, b, prec+20) + return mpi_sqrt(t, prec) + +def mpi_atan2(y, x, prec): + ya, yb = y + xa, xb = x + # Constrained to the real line + if ya == yb == fzero: + if mpf_ge(xa, fzero): + return mpi_zero + return mpi_pi(prec) + # Right half-plane + if mpf_ge(xa, fzero): + if mpf_ge(ya, fzero): + a = mpf_atan2(ya, xb, prec, round_floor) + else: + a = mpf_atan2(ya, xa, prec, round_floor) + if mpf_ge(yb, fzero): + b = mpf_atan2(yb, xa, prec, round_ceiling) + else: + b = mpf_atan2(yb, xb, prec, round_ceiling) + # Upper half-plane + elif mpf_ge(ya, fzero): + b = mpf_atan2(ya, xa, prec, round_ceiling) + if mpf_le(xb, fzero): + a = mpf_atan2(yb, xb, prec, round_floor) + else: + a = mpf_atan2(ya, xb, prec, round_floor) + # Lower half-plane + elif mpf_le(yb, fzero): + a = mpf_atan2(yb, xa, prec, round_floor) + if mpf_le(xb, fzero): + b = mpf_atan2(ya, xb, prec, round_ceiling) + else: + b = mpf_atan2(yb, xb, prec, round_ceiling) + # Covering the origin + else: + b = mpf_pi(prec, round_ceiling) + a = mpf_neg(b) + return a, b + +def mpci_arg(z, prec): + x, y = z + return mpi_atan2(y, x, prec) + +def mpci_log(z, prec): + x, y = z + re = mpi_log(mpci_abs(z, prec+20), prec) + im = mpci_arg(z, prec) + return re, im + +def mpci_pow(x, y, prec): + # TODO: recognize/speed up real cases, integer y + yre, yim = y + if yim == mpi_zero: + ya, yb = yre + if ya == yb: + sign, man, exp, bc = yb + if man and exp >= 0: + return mpci_pow_int(x, (-1)**sign * int(man<>= 1 + return mpci_pos(result, prec) + +gamma_min_a = from_float(1.46163214496) +gamma_min_b = from_float(1.46163214497) +gamma_min = (gamma_min_a, gamma_min_b) +gamma_mono_imag_a = from_float(-1.1) +gamma_mono_imag_b = from_float(1.1) + +def mpi_overlap(x, y): + a, b = x + c, d = y + if mpf_lt(d, a): return False + if mpf_gt(c, b): return False + return True + +# type = 0 -- gamma +# type = 1 -- factorial +# type = 2 -- 1/gamma +# type = 3 -- log-gamma + +def mpi_gamma(z, prec, type=0): + a, b = z + wp = prec+20 + + if type == 1: + return mpi_gamma(mpi_add(z, mpi_one, wp), prec, 0) + + # increasing + if mpf_gt(a, gamma_min_b): + if type == 0: + c = mpf_gamma(a, prec, round_floor) + d = mpf_gamma(b, prec, round_ceiling) + elif type == 2: + c = mpf_rgamma(b, prec, round_floor) + d = mpf_rgamma(a, prec, round_ceiling) + elif type == 3: + c = mpf_loggamma(a, prec, round_floor) + d = mpf_loggamma(b, prec, round_ceiling) + # decreasing + elif mpf_gt(a, fzero) and mpf_lt(b, gamma_min_a): + if type == 0: + c = mpf_gamma(b, prec, round_floor) + d = mpf_gamma(a, prec, round_ceiling) + elif type == 2: + c = mpf_rgamma(a, prec, round_floor) + d = mpf_rgamma(b, prec, round_ceiling) + elif type == 3: + c = mpf_loggamma(b, prec, round_floor) + d = mpf_loggamma(a, prec, round_ceiling) + else: + # TODO: reflection formula + znew = mpi_add(z, mpi_one, wp) + if type == 0: return mpi_div(mpi_gamma(znew, prec+2, 0), z, prec) + if type == 2: return mpi_mul(mpi_gamma(znew, prec+2, 2), z, prec) + if type == 3: return mpi_sub(mpi_gamma(znew, prec+2, 3), mpi_log(z, prec+2), prec) + return c, d + +def mpci_gamma(z, prec, type=0): + (a1,a2), (b1,b2) = z + + # Real case + if b1 == b2 == fzero and (type != 3 or mpf_gt(a1,fzero)): + return mpi_gamma(z, prec, type), mpi_zero + + # Estimate precision + wp = prec+20 + if type != 3: + amag = a2[2]+a2[3] + bmag = b2[2]+b2[3] + if a2 != fzero: + mag = max(amag, bmag) + else: + mag = bmag + an = abs(to_int(a2)) + bn = abs(to_int(b2)) + absn = max(an, bn) + gamma_size = max(0,absn*mag) + wp += gamma_size.bit_length() + + # Assume type != 1 + if type == 1: + (a1,a2) = mpi_add((a1,a2), mpi_one, wp); z = (a1,a2), (b1,b2) + type = 0 + + # Avoid non-monotonic region near the negative real axis + if mpf_lt(a1, gamma_min_b): + if mpi_overlap((b1,b2), (gamma_mono_imag_a, gamma_mono_imag_b)): + # TODO: reflection formula + #if mpf_lt(a2, mpf_shift(fone,-1)): + # znew = mpci_sub((mpi_one,mpi_zero),z,wp) + # ... + # Recurrence: + # gamma(z) = gamma(z+1)/z + znew = mpi_add((a1,a2), mpi_one, wp), (b1,b2) + if type == 0: return mpci_div(mpci_gamma(znew, prec+2, 0), z, prec) + if type == 2: return mpci_mul(mpci_gamma(znew, prec+2, 2), z, prec) + if type == 3: return mpci_sub(mpci_gamma(znew, prec+2, 3), mpci_log(z,prec+2), prec) + + # Use monotonicity (except for a small region close to the + # origin and near poles) + # upper half-plane + if mpf_ge(b1, fzero): + minre = mpc_loggamma((a1,b2), wp, round_floor) + maxre = mpc_loggamma((a2,b1), wp, round_ceiling) + minim = mpc_loggamma((a1,b1), wp, round_floor) + maxim = mpc_loggamma((a2,b2), wp, round_ceiling) + # lower half-plane + elif mpf_le(b2, fzero): + minre = mpc_loggamma((a1,b1), wp, round_floor) + maxre = mpc_loggamma((a2,b2), wp, round_ceiling) + minim = mpc_loggamma((a2,b1), wp, round_floor) + maxim = mpc_loggamma((a1,b2), wp, round_ceiling) + # crosses real axis + else: + maxre = mpc_loggamma((a2,fzero), wp, round_ceiling) + # stretches more into the lower half-plane + if mpf_gt(mpf_neg(b1), b2): + minre = mpc_loggamma((a1,b1), wp, round_ceiling) + else: + minre = mpc_loggamma((a1,b2), wp, round_ceiling) + minim = mpc_loggamma((a2,b1), wp, round_floor) + maxim = mpc_loggamma((a2,b2), wp, round_floor) + + w = (minre[0], maxre[0]), (minim[1], maxim[1]) + if type == 3: + return mpi_pos(w[0], prec), mpi_pos(w[1], prec) + if type == 2: + w = mpci_neg(w) + return mpci_exp(w, prec) + +def mpi_loggamma(z, prec): return mpi_gamma(z, prec, type=3) +def mpci_loggamma(z, prec): return mpci_gamma(z, prec, type=3) + +def mpi_rgamma(z, prec): return mpi_gamma(z, prec, type=2) +def mpci_rgamma(z, prec): return mpci_gamma(z, prec, type=2) + +def mpi_factorial(z, prec): return mpi_gamma(z, prec, type=1) +def mpci_factorial(z, prec): return mpci_gamma(z, prec, type=1) diff --git a/mpmath/matrices/__init__.py b/mpmath/matrices/__init__.py new file mode 100644 index 0000000..293697b --- /dev/null +++ b/mpmath/matrices/__init__.py @@ -0,0 +1,2 @@ +from . import eigen # to set methods +from . import eigen_symmetric # to set methods diff --git a/mpmath/matrices/calculus.py b/mpmath/matrices/calculus.py new file mode 100644 index 0000000..fbd5c45 --- /dev/null +++ b/mpmath/matrices/calculus.py @@ -0,0 +1,537 @@ +# TODO: should use diagonalization-based algorithms + +class MatrixCalculusMethods: + + def _exp_pade(ctx, a): + """ + Exponential of a matrix using Pade approximants. + + See G. H. Golub, C. F. van Loan 'Matrix Computations', + third Ed., page 572 + + TODO: + - find a good estimate for q + - reduce the number of matrix multiplications to improve + performance + """ + def eps_pade(p): + return ctx.mpf(2)**(3-2*p) * \ + ctx.factorial(p)**2/(ctx.factorial(2*p)**2 * (2*p + 1)) + q = 4 + extraq = 8 + while 1: + if eps_pade(q) < ctx.eps: + break + q += 1 + q += extraq + j = int(max(1, ctx.mag(ctx.mnorm(a,'inf')))) + extra = q + prec = ctx.prec + ctx.dps += extra + 3 + try: + a = a/2**j + na = a.rows + den = ctx.eye(na) + num = ctx.eye(na) + x = ctx.eye(na) + c = ctx.mpf(1) + for k in range(1, q+1): + c *= ctx.mpf(q - k + 1)/((2*q - k + 1) * k) + x = a*x + cx = c*x + num += cx + den += (-1)**k * cx + f = ctx.lu_solve_mat(den, num) + for k in range(j): + f = f*f + finally: + ctx.prec = prec + return f*1 + + def expm(ctx, A, method='taylor'): + r""" + Computes the matrix exponential of a square matrix `A`, which is defined + by the power series + + .. math :: + + \exp(A) = I + A + \frac{A^2}{2!} + \frac{A^3}{3!} + \ldots + + With method='taylor', the matrix exponential is computed + using the Taylor series. With method='pade', Pade approximants + are used instead. + + **Examples** + + Basic examples:: + + >>> from mpmath import (mp, expm, zeros, eye, j, hilbert, chop, + ... mnorm, ones, matrix) + >>> mp.pretty = True + >>> expm(zeros(3)) + [1.0 0.0 0.0] + [0.0 1.0 0.0] + [0.0 0.0 1.0] + >>> expm(eye(3)) + [2.71828182845905 0.0 0.0] + [ 0.0 2.71828182845905 0.0] + [ 0.0 0.0 2.71828182845905] + >>> expm([[1,1,0],[1,0,1],[0,1,0]]) + [ 3.86814500615414 2.26812870852145 0.841130841230196] + [ 2.26812870852145 2.44114713886289 1.42699786729125] + [0.841130841230196 1.42699786729125 1.6000162976327] + >>> expm([[1,1,0],[1,0,1],[0,1,0]], method='pade') + [ 3.86814500615414 2.26812870852145 0.841130841230196] + [ 2.26812870852145 2.44114713886289 1.42699786729125] + [0.841130841230196 1.42699786729125 1.6000162976327] + >>> expm([[1+j, 0], [1+j,1]]) + [(1.46869393991589 + 2.28735528717884j) 0.0] + [ (1.03776739863568 + 3.536943175722j) 2.71828182845905] + + Matrices with large entries are allowed:: + + >>> expm(matrix([[1,2],[2,3]])**25) + [5.65024064048415e+2050488462815550 9.14228140091932e+2050488462815550] + [9.14228140091932e+2050488462815550 1.47925220414035e+2050488462815551] + + The identity `\exp(A+B) = \exp(A) \exp(B)` does not hold for + noncommuting matrices:: + + >>> A = hilbert(3) + >>> B = A + eye(3) + >>> chop(mnorm(A*B - B*A)) + 0.0 + >>> chop(mnorm(expm(A+B) - expm(A)*expm(B))) + 0.0 + >>> B = A + ones(3) + >>> mnorm(A*B - B*A) + 1.8 + >>> mnorm(expm(A+B) - expm(A)*expm(B)) + 42.0927851137247 + + """ + A = ctx.matrix(A) + + if method == 'pade': + prec = ctx.prec + try: + ctx.prec += 2*A.rows + res = ctx._exp_pade(A) + finally: + ctx.prec = prec + return res + + prec = ctx.prec + j = int(max(1, ctx.mag(ctx.mnorm(A,'inf')))) + j += int(0.5*prec**0.5) + try: + ctx.prec += 10 + 2*j + tol = +ctx.eps + A = A/2**j + T = A + Y = A**0 + A + k = 2 + while 1: + T *= A * (1/ctx.mpf(k)) + if ctx.mnorm(T, 'inf') < tol: + break + Y += T + k += 1 + for k in range(j): + Y = Y*Y + finally: + ctx.prec = prec + Y *= 1 + return Y + + def cosm(ctx, A): + r""" + Gives the cosine of a square matrix `A`, defined in analogy + with the matrix exponential. + + Examples:: + + >>> from mpmath import mp, eye, cosm, hilbert, j, matrix + >>> mp.pretty = True + >>> X = eye(3) + >>> cosm(X) + [0.54030230586814 0.0 0.0] + [ 0.0 0.54030230586814 0.0] + [ 0.0 0.0 0.54030230586814] + >>> X = hilbert(3) + >>> cosm(X) + [ 0.424403834569555 -0.316643413047167 -0.221474945949293] + [-0.316643413047167 0.820646708837824 -0.127183694770039] + [-0.221474945949293 -0.127183694770039 0.909236687217541] + >>> X = matrix([[1+j,-2],[0,-j]]) + >>> cosm(X) + [(0.833730025131149 - 0.988897705762865j) (1.07485840848393 - 0.17192140544213j)] + [ 0.0 (1.54308063481524 + 0.0j)] + """ + A = ctx.matrix(A) + B = 0.5 * (ctx.expm(A*ctx.j) + ctx.expm(A*(-ctx.j))) + if not sum(A.apply(ctx.im).apply(abs)): + B = B.apply(ctx.re) + return B + + def sinm(ctx, A): + r""" + Gives the sine of a square matrix `A`, defined in analogy + with the matrix exponential. + + Examples:: + + >>> from mpmath import mp, eye, sinm, hilbert, matrix, j + >>> mp.pretty = True + >>> X = eye(3) + >>> sinm(X) + [0.841470984807897 0.0 0.0] + [ 0.0 0.841470984807897 0.0] + [ 0.0 0.0 0.841470984807897] + >>> X = hilbert(3) + >>> sinm(X) + [0.711608512150994 0.339783913247439 0.220742837314741] + [0.339783913247439 0.244113865695532 0.187231271174372] + [0.220742837314741 0.187231271174372 0.155816730769635] + >>> X = matrix([[1+j,-2],[0,-j]]) + >>> sinm(X) + [(1.29845758141598 + 0.634963914784736j) (-1.96751511930922 + 0.314700021761367j)] + [ 0.0 (0.0 - 1.1752011936438j)] + """ + A = ctx.matrix(A) + B = (-0.5j) * (ctx.expm(A*ctx.j) - ctx.expm(A*(-ctx.j))) + if not sum(A.apply(ctx.im).apply(abs)): + B = B.apply(ctx.re) + return B + + def _sqrtm_rot(ctx, A, _may_rotate): + # If the iteration fails to converge, cheat by performing + # a rotation by a complex number + u = ctx.j**0.3 + return ctx.sqrtm(u*A, _may_rotate) / ctx.sqrt(u) + + def sqrtm(ctx, A, _may_rotate=2): + r""" + Computes a square root of the square matrix `A`, i.e. returns + a matrix `B = A^{1/2}` such that `B^2 = A`. The square root + of a matrix, if it exists, is not unique. + + **Examples** + + Square roots of some simple matrices:: + + >>> from mpmath import mp, sqrtm, j, matrix, cos, sin, chop, mnorm + >>> mp.pretty = True + >>> sqrtm([[1,0], [0,1]]) + [1.0 0.0] + [0.0 1.0] + >>> sqrtm([[0,0], [0,0]]) + [0.0 0.0] + [0.0 0.0] + >>> sqrtm([[2,0],[0,1]]) + [1.4142135623731 0.0] + [ 0.0 1.0] + >>> sqrtm([[1,1],[1,0]]) + [ (0.920442065259926 - 0.21728689675164j) (0.568864481005783 + 0.351577584254143j)] + [(0.568864481005783 + 0.351577584254143j) (0.351577584254143 - 0.568864481005783j)] + >>> sqrtm([[1,0],[0,1]]) + [1.0 0.0] + [0.0 1.0] + >>> sqrtm([[-1,0],[0,1]]) + [(0.0 - 1.0j) 0.0] + [ 0.0 (1.0 + 0.0j)] + >>> sqrtm([[j,0],[0,j]]) + [(0.707106781186547 + 0.707106781186547j) 0.0] + [ 0.0 (0.707106781186547 + 0.707106781186547j)] + + A square root of a rotation matrix, giving the corresponding + half-angle rotation matrix:: + + >>> t1 = 0.75 + >>> t2 = t1 * 0.5 + >>> A1 = matrix([[cos(t1), -sin(t1)], [sin(t1), cos(t1)]]) + >>> A2 = matrix([[cos(t2), -sin(t2)], [sin(t2), cos(t2)]]) + >>> sqrtm(A1) + [0.930507621912314 -0.366272529086048] + [0.366272529086048 0.930507621912314] + >>> A2 + [0.930507621912314 -0.366272529086048] + [0.366272529086048 0.930507621912314] + + The identity `(A^2)^{1/2} = A` does not necessarily hold:: + + >>> A = matrix([[4,1,4],[7,8,9],[10,2,11]]) + >>> sqrtm(A**2) + [ 4.0 1.0 4.0] + [ 7.0 8.0 9.0] + [10.0 2.0 11.0] + >>> sqrtm(A)**2 + [ 4.0 1.0 4.0] + [ 7.0 8.0 9.0] + [10.0 2.0 11.0] + >>> A = matrix([[-4,1,4],[7,-8,9],[10,2,11]]) + >>> sqrtm(A**2) + [ 7.43715112194995 -0.324127569985474 1.8481718827526] + [-0.251549715716942 9.32699765900402 2.48221180985147] + [ 4.11609388833616 0.775751877098258 13.017955697342] + >>> chop(sqrtm(A)**2) + [-4.0 1.0 4.0] + [ 7.0 -8.0 9.0] + [10.0 2.0 11.0] + + For some matrices, a square root does not exist:: + + >>> sqrtm([[0,1], [0,0]]) + Traceback (most recent call last): + ... + ZeroDivisionError: matrix is numerically singular + + Two examples from the documentation for Matlab's ``sqrtm``:: + + >>> mp.pretty = True + >>> sqrtm([[7,10],[15,22]]) + [1.56669890360128 1.74077655955698] + [2.61116483933547 4.17786374293675] + >>> + >>> X = matrix(\ + ... [[5,-4,1,0,0], + ... [-4,6,-4,1,0], + ... [1,-4,6,-4,1], + ... [0,1,-4,6,-4], + ... [0,0,1,-4,5]]) + >>> Y = matrix(\ + ... [[2,-1,-0,-0,-0], + ... [-1,2,-1,0,-0], + ... [0,-1,2,-1,0], + ... [-0,0,-1,2,-1], + ... [-0,-0,-0,-1,2]]) + >>> mnorm(sqrtm(X) - Y) + 4.53155328326114e-19 + + """ + A = ctx.matrix(A) + # Trivial + if A*0 == A: + return A + prec = ctx.prec + if _may_rotate: + d = ctx.det(A) + if abs(ctx.im(d)) < 16*ctx.eps and ctx.re(d) < 0: + return ctx._sqrtm_rot(A, _may_rotate-1) + try: + ctx.prec += 10 + tol = ctx.eps * 128 + Y = A + Z = I = A**0 + k = 0 + # Denman-Beavers iteration + while 1: + Yprev = Y + try: + Y, Z = 0.5*(Y+ctx.inverse(Z)), 0.5*(Z+ctx.inverse(Y)) + except ZeroDivisionError: + if _may_rotate: + Y = ctx._sqrtm_rot(A, _may_rotate-1) + break + else: + raise + mag1 = ctx.mnorm(Y-Yprev, 'inf') + mag2 = ctx.mnorm(Y, 'inf') + if mag1 <= mag2*tol: + break + if _may_rotate and k > 6 and not mag1 < mag2 * 0.001: + return ctx._sqrtm_rot(A, _may_rotate-1) + k += 1 + if k > ctx.prec: + raise ctx.NoConvergence + finally: + ctx.prec = prec + Y *= 1 + return Y + + def logm(ctx, A): + r""" + Computes a logarithm of the square matrix `A`, i.e. returns + a matrix `B = \log(A)` such that `\exp(B) = A`. The logarithm + of a matrix, if it exists, is not unique. + + **Examples** + + Logarithms of some simple matrices:: + + >>> from mpmath import (mp, eye, logm, expm, matrix, j, nprint, + ... chop, hilbert, cos, sin, pi, re) + >>> mp.pretty = True + >>> X = eye(3) + >>> logm(X) + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] + >>> logm(2*X) + [0.693147180559945 0.0 0.0] + [ 0.0 0.693147180559945 0.0] + [ 0.0 0.0 0.693147180559945] + >>> logm(expm(X)) + [1.0 0.0 0.0] + [0.0 1.0 0.0] + [0.0 0.0 1.0] + + A logarithm of a complex matrix:: + + >>> X = matrix([[2+j, 1, 3], [1-j, 1-2*j, 1], [-4, -5, j]]) + >>> B = logm(X) + >>> nprint(B) + [ (0.808757 + 0.107759j) (2.20752 + 0.202762j) (1.07376 - 0.773874j)] + [ (0.905709 - 0.107795j) (0.0287395 - 0.824993j) (0.111619 + 0.514272j)] + [(-0.930151 + 0.399512j) (-2.06266 - 0.674397j) (0.791552 + 0.519839j)] + >>> chop(expm(B)) + [(2.0 + 1.0j) 1.0 3.0] + [(1.0 - 1.0j) (1.0 - 2.0j) 1.0] + [ -4.0 -5.0 (0.0 + 1.0j)] + + A matrix `X` close to the identity matrix, for which + `\log(\exp(X)) = \exp(\log(X)) = X` holds:: + + >>> X = eye(3) + hilbert(3)/4 + >>> X + [ 1.25 0.125 0.0833333333333333] + [ 0.125 1.08333333333333 0.0625] + [0.0833333333333333 0.0625 1.05] + >>> logm(expm(X)) + [ 1.25 0.125 0.0833333333333333] + [ 0.125 1.08333333333333 0.0625] + [0.0833333333333333 0.0625 1.05] + >>> expm(logm(X)) + [ 1.25 0.125 0.0833333333333333] + [ 0.125 1.08333333333333 0.0625] + [0.0833333333333333 0.0625 1.05] + + A logarithm of a rotation matrix, giving back the angle of + the rotation:: + + >>> t = 3.7 + >>> A = matrix([[cos(t),sin(t)],[-sin(t),cos(t)]]) + >>> chop(logm(A)) + [ 0.0 -2.58318530717959] + [2.58318530717959 0.0] + >>> (2*pi-t) + 2.58318530717959 + + For some matrices, a logarithm does not exist:: + + >>> logm([[1,0], [0,0]]) + Traceback (most recent call last): + ... + ZeroDivisionError: matrix is numerically singular + + Logarithm of a matrix with large entries:: + + >>> logm(hilbert(3) * 10**20).apply(re) + [ 45.5597513593433 1.27721006042799 0.317662687717978] + [ 1.27721006042799 42.5222778973542 2.24003708791604] + [0.317662687717978 2.24003708791604 42.395212822267] + + """ + A = ctx.matrix(A) + if ctx.mnorm(A, 'inf') == 0: + raise ValueError("The logarithm is undefined for the zero matrix.") + prec = ctx.prec + try: + ctx.prec += 10 + tol = ctx.eps * 128 + I = A**0 + B = A + n = 0 + while 1: + B = ctx.sqrtm(B) + n += 1 + if ctx.mnorm(B-I, 'inf') < 0.125: + break + T = X = B-I + L = X*0 + k = 1 + while 1: + if k & 1: + L += T / k + else: + L -= T / k + T *= X + if ctx.mnorm(T, 'inf') < tol: + break + k += 1 + if k > ctx.prec: + raise ctx.NoConvergence + finally: + ctx.prec = prec + L *= 2**n + return L + + def powm(ctx, A, r): + r""" + Computes `A^r = \exp(A \log r)` for a matrix `A` and complex + number `r`. + + **Examples** + + Powers and inverse powers of a matrix:: + + >>> from mpmath import (mp, matrix, powm, chop, extraprec, fib, + ... phi, sqrt) + >>> mp.pretty = True + >>> A = matrix([[4,1,4],[7,8,9],[10,2,11]]) + >>> powm(A, 2) + [ 63.0 20.0 69.0] + [174.0 89.0 199.0] + [164.0 48.0 179.0] + >>> chop(powm(powm(A, 4), 1/4.)) + [ 4.0 1.0 4.0] + [ 7.0 8.0 9.0] + [10.0 2.0 11.0] + >>> powm(extraprec(20)(powm)(A, -4), -1/4.) + [ 4.0 1.0 4.0] + [ 7.0 8.0 9.0] + [10.0 2.0 11.0] + >>> chop(powm(powm(A, 1+0.5j), 1/(1+0.5j))) + [ 4.0 1.0 4.0] + [ 7.0 8.0 9.0] + [10.0 2.0 11.0] + >>> powm(extraprec(5)(powm)(A, -1.5), -1/(1.5)) + [ 4.0 1.0 4.0] + [ 7.0 8.0 9.0] + [10.0 2.0 11.0] + + A Fibonacci-generating matrix:: + + >>> powm([[1,1],[1,0]], 10) + [89.0 55.0] + [55.0 34.0] + >>> fib(10) + 55.0 + >>> powm([[1,1],[1,0]], 6.5) + [(16.5166626964253 - 0.0121089837381789j) (10.2078589271083 + 0.0195927472575932j)] + [(10.2078589271083 + 0.0195927472575932j) (6.30880376931698 - 0.0317017309957721j)] + >>> (phi**6.5 - (1-phi)**6.5)/sqrt(5) + (10.2078589271083 - 0.0195927472575932j) + >>> powm([[1,1],[1,0]], 6.2) + [ (14.3076953002666 - 0.008222855781077j) (8.81733464837593 + 0.0133048601383712j)] + [(8.81733464837593 + 0.0133048601383712j) (5.49036065189071 - 0.0215277159194482j)] + >>> (phi**6.2 - (1-phi)**6.2)/sqrt(5) + (8.81733464837593 - 0.0133048601383712j) + + """ + A = ctx.matrix(A) + r = ctx.convert(r) + prec = ctx.prec + try: + ctx.prec += 10 + if ctx.isint(r): + v = A ** int(r) + elif ctx.isint(r*2): + y = int(r*2) + v = ctx.sqrtm(A) ** y + else: + v = ctx.expm(r*ctx.logm(A)) + finally: + ctx.prec = prec + v *= 1 + return v diff --git a/mpmath/matrices/eigen.py b/mpmath/matrices/eigen.py new file mode 100644 index 0000000..4c61d4e --- /dev/null +++ b/mpmath/matrices/eigen.py @@ -0,0 +1,876 @@ +################################################################################################## +# module for the eigenvalue problem +# Copyright 2013 Timo Hartmann (thartmann15 at gmail.com) +# +# todo: +# - implement balancing +# - agressive early deflation +# +################################################################################################## + +""" +The eigenvalue problem +---------------------- + +This file contains routines for the eigenvalue problem. + +high level routines: + + hessenberg : reduction of a real or complex square matrix to upper Hessenberg form + schur : reduction of a real or complex square matrix to upper Schur form + eig : eigenvalues and eigenvectors of a real or complex square matrix + +low level routines: + + hessenberg_reduce_0 : reduction of a real or complex square matrix to upper Hessenberg form + hessenberg_reduce_1 : auxiliary routine to hessenberg_reduce_0 + qr_step : a single implicitly shifted QR step for an upper Hessenberg matrix + hessenberg_qr : Schur decomposition of an upper Hessenberg matrix + eig_tr_r : right eigenvectors of an upper triangular matrix + eig_tr_l : left eigenvectors of an upper triangular matrix +""" + + +class Eigen: + pass + +def defun(f): + setattr(Eigen, f.__name__, f) + return f + +def hessenberg_reduce_0(ctx, A, T): + """ + This routine computes the (upper) Hessenberg decomposition of a square matrix A. + Given A, an unitary matrix Q is calculated such that + + Q' A Q = H and Q' Q = Q Q' = 1 + + where H is an upper Hessenberg matrix, meaning that it only contains zeros + below the first subdiagonal. Here ' denotes the hermitian transpose (i.e. + transposition and conjugation). + + parameters: + A (input/output) On input, A contains the square matrix A of + dimension (n,n). On output, A contains a compressed representation + of Q and H. + T (output) An array of length n containing the first elements of + the Householder reflectors. + """ + + # internally we work with householder reflections from the right. + # let u be a row vector (i.e. u[i]=A[i,:i]). then + # Q is build up by reflectors of the type (1-v'v) where v is a suitable + # modification of u. these reflectors are applyed to A from the right. + # because we work with reflectors from the right we have to start with + # the bottom row of A and work then upwards (this corresponds to + # some kind of RQ decomposition). + # the first part of the vectors v (i.e. A[i,:(i-1)]) are stored as row vectors + # in the lower left part of A (excluding the diagonal and subdiagonal). + # the last entry of v is stored in T. + # the upper right part of A (including diagonal and subdiagonal) becomes H. + + + n = A.rows + if n <= 2: return + + for i in range(n-1, 1, -1): + + # scale the vector + + scale = 0 + for k in range(i): + scale += abs(ctx.re(A[i,k])) + abs(ctx.im(A[i,k])) + + scale_inv = 0 + if scale != 0: + scale_inv = 1 / scale + + if scale == 0 or ctx.isinf(scale_inv): + # sadly there are floating-point numbers not equal to zero whose reciprocal is infinity + T[i] = 0 + A[i,i-1] = 0 + continue + + # calculate parameters for housholder transformation + + H = 0 + for k in range(i): + A[i,k] *= scale_inv + rr = ctx.re(A[i,k]) + ii = ctx.im(A[i,k]) + H += rr * rr + ii * ii + + F = A[i,i-1] + f = abs(F) + G = ctx.sqrt(H) + A[i,i-1] = - G * scale + + if f == 0: + T[i] = G + else: + ff = F / f + T[i] = F + G * ff + A[i,i-1] *= ff + + H += G * f + H = 1 / ctx.sqrt(H) + + T[i] *= H + for k in range(i - 1): + A[i,k] *= H + + for j in range(i): + # apply housholder transformation (from right) + + G = ctx.conj(T[i]) * A[j,i-1] + for k in range(i-1): + G += ctx.conj(A[i,k]) * A[j,k] + + A[j,i-1] -= G * T[i] + for k in range(i-1): + A[j,k] -= G * A[i,k] + + for j in range(n): + # apply housholder transformation (from left) + + G = T[i] * A[i-1,j] + for k in range(i-1): + G += A[i,k] * A[k,j] + + A[i-1,j] -= G * ctx.conj(T[i]) + for k in range(i-1): + A[k,j] -= G * ctx.conj(A[i,k]) + + + +def hessenberg_reduce_1(ctx, A, T): + """ + This routine forms the unitary matrix Q described in hessenberg_reduce_0. + + parameters: + A (input/output) On input, A is the same matrix as delivered by + hessenberg_reduce_0. On output, A is set to Q. + + T (input) On input, T is the same array as delivered by hessenberg_reduce_0. + """ + + n = A.rows + + if n == 1: + A[0,0] = 1 + return + + A[0,0] = A[1,1] = 1 + A[0,1] = A[1,0] = 0 + + for i in range(2, n): + if T[i] != 0: + + for j in range(i): + G = T[i] * A[i-1,j] + for k in range(i-1): + G += A[i,k] * A[k,j] + + A[i-1,j] -= G * ctx.conj(T[i]) + for k in range(i-1): + A[k,j] -= G * ctx.conj(A[i,k]) + + A[i,i] = 1 + for j in range(i): + A[j,i] = A[i,j] = 0 + + + +@defun +def hessenberg(ctx, A, overwrite_a = False): + """ + This routine computes the Hessenberg decomposition of a square matrix A. + Given A, an unitary matrix Q is determined such that + + Q' A Q = H and Q' Q = Q Q' = 1 + + where H is an upper right Hessenberg matrix. Here ' denotes the hermitian + transpose (i.e. transposition and conjugation). + + input: + A : a real or complex square matrix + overwrite_a : if true, allows modification of A which may improve + performance. if false, A is not modified. + + output: + Q : an unitary matrix + H : an upper right Hessenberg matrix + + example: + >>> from mpmath import mp + >>> A = mp.matrix([[3, -1, 2], [2, 5, -5], [-2, -3, 7]]) + >>> Q, H = mp.hessenberg(A) + >>> mp.nprint(H, 3) + [ 3.15 2.23 4.44] + [-0.769 4.85 3.05] + [ 0.0 3.61 7.0] + >>> print(mp.chop(A - Q * H * Q.transpose_conj())) + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] + + return value: (Q, H) + """ + + n = A.rows + + if n == 1: + return (ctx.matrix([[1]]), A) + + if not overwrite_a: + A = A.copy() + + T = ctx.matrix(n, 1) + + hessenberg_reduce_0(ctx, A, T) + Q = A.copy() + hessenberg_reduce_1(ctx, Q, T) + + for x in range(n): + for y in range(x+2, n): + A[y,x] = 0 + + return Q, A + + +########################################################################### + + +def qr_step(ctx, n0, n1, A, Q, shift): + """ + This subroutine executes a single implicitly shifted QR step applied to an + upper Hessenberg matrix A. Given A and shift as input, first an QR + decomposition is calculated: + + Q R = A - shift * 1 . + + The output is then following matrix: + + R Q + shift * 1 + + parameters: + n0, n1 (input) Two integers which specify the submatrix A[n0:n1,n0:n1] + on which this subroutine operators. The subdiagonal elements + to the left and below this submatrix must be deflated (i.e. zero). + following restriction is imposed: n1>=n0+2 + A (input/output) On input, A is an upper Hessenberg matrix. + On output, A is replaced by "R Q + shift * 1" + Q (input/output) The parameter Q is multiplied by the unitary matrix + Q arising from the QR decomposition. Q can also be false, in which + case the unitary matrix Q is not computated. + shift (input) a complex number specifying the shift. idealy close to an + eigenvalue of the bottemmost part of the submatrix A[n0:n1,n0:n1]. + + references: + [Stoer]_ + [Kresser]_ + """ + + # implicitly shifted and bulge chasing is explained at p.398/399 in "Stoer, Bulirsch - Introduction to Numerical Analysis" + # for bulge chasing see also "Watkins - The Matrix Eigenvalue Problem" sec.4.5,p.173 + + # the Givens rotation we used is determined as follows: let c,s be two complex + # numbers. then we have following relation: + # + # v = sqrt(|c|^2 + |s|^2) + # + # 1/v [ c~ s~] [c] = [v] + # [-s c ] [s] [0] + # + # the matrix on the left is our Givens rotation. + + n = A.rows + + # first step + + # calculate givens rotation + c = A[n0 ,n0] - shift + s = A[n0+1,n0] + + v = ctx.hypot(ctx.hypot(ctx.re(c), ctx.im(c)), ctx.hypot(ctx.re(s), ctx.im(s))) + + if v == 0: + v = 1 + c = 1 + s = 0 + else: + c /= v + s /= v + + cc = ctx.conj(c) + cs = ctx.conj(s) + + for k in range(n0, n): + # apply givens rotation from the left + x = A[n0 ,k] + y = A[n0+1,k] + A[n0 ,k] = cc * x + cs * y + A[n0+1,k] = c * y - s * x + + for k in range(min(n1, n0+3)): + # apply givens rotation from the right + x = A[k,n0 ] + y = A[k,n0+1] + A[k,n0 ] = c * x + s * y + A[k,n0+1] = cc * y - cs * x + + if not isinstance(Q, bool): + for k in range(n): + # eigenvectors + x = Q[k,n0 ] + y = Q[k,n0+1] + Q[k,n0 ] = c * x + s * y + Q[k,n0+1] = cc * y - cs * x + + # chase the bulge + + for j in range(n0, n1 - 2): + # calculate givens rotation + + c = A[j+1,j] + s = A[j+2,j] + + v = ctx.hypot(ctx.hypot(ctx.re(c), ctx.im(c)), ctx.hypot(ctx.re(s), ctx.im(s))) + + if v == 0: + A[j+1,j] = 0 + v = 1 + c = 1 + s = 0 + else: + A[j+1,j] = v + c /= v + s /= v + + A[j+2,j] = 0 + + cc = ctx.conj(c) + cs = ctx.conj(s) + + for k in range(j+1, n): + # apply givens rotation from the left + x = A[j+1,k] + y = A[j+2,k] + A[j+1,k] = cc * x + cs * y + A[j+2,k] = c * y - s * x + + for k in range(min(n1, j+4)): + # apply givens rotation from the right + x = A[k,j+1] + y = A[k,j+2] + A[k,j+1] = c * x + s * y + A[k,j+2] = cc * y - cs * x + + if not isinstance(Q, bool): + for k in range(n): + # eigenvectors + x = Q[k,j+1] + y = Q[k,j+2] + Q[k,j+1] = c * x + s * y + Q[k,j+2] = cc * y - cs * x + + + +def hessenberg_qr(ctx, A, Q): + """ + This routine computes the Schur decomposition of an upper Hessenberg matrix A. + Given A, an unitary matrix Q is determined such that + + Q' A Q = R and Q' Q = Q Q' = 1 + + where R is an upper right triangular matrix. Here ' denotes the hermitian + transpose (i.e. transposition and conjugation). + + parameters: + A (input/output) On input, A contains an upper Hessenberg matrix. + On output, A is replace by the upper right triangluar matrix R. + + Q (input/output) The parameter Q is multiplied by the unitary + matrix Q arising from the Schur decomposition. Q can also be + false, in which case the unitary matrix Q is not computated. + """ + + n = A.rows + + norm = 0 + for x in range(n): + for y in range(min(x+2, n)): + norm += ctx.re(A[y,x]) ** 2 + ctx.im(A[y,x]) ** 2 + norm = ctx.sqrt(norm) / n + + if norm == 0: + return + + n0 = 0 + n1 = n + + eps = ctx.eps / (100 * n) + maxits = ctx.dps * 4 + + its = totalits = 0 + + while 1: + # kressner p.32 algo 3 + # the active submatrix is A[n0:n1,n0:n1] + + k = n0 + + while k + 1 < n1: + s = abs(ctx.re(A[k,k])) + abs(ctx.im(A[k,k])) + abs(ctx.re(A[k+1,k+1])) + abs(ctx.im(A[k+1,k+1])) + if s < eps * norm: + s = norm + if abs(A[k+1,k]) < eps * s: + break + k += 1 + + if k + 1 < n1: + # deflation found at position (k+1, k) + + A[k+1,k] = 0 + n0 = k + 1 + + its = 0 + + if n0 + 1 >= n1: + # block of size at most two has converged + n0 = 0 + n1 = k + 1 + if n1 < 2: + # QR algorithm has converged + return + else: + if (its % 30) == 10: + # exceptional shift + shift = A[n1-1,n1-2] + elif (its % 30) == 20: + # exceptional shift + shift = abs(A[n1-1,n1-2]) + elif (its % 30) == 29: + # exceptional shift + shift = norm + else: + # A = [ a b ] det(x-A)=x*x-x*tr(A)+det(A) + # [ c d ] + # + # eigenvalues bad: (tr(A)+sqrt((tr(A))**2-4*det(A)))/2 + # bad because of cancellation if |c| is small and |a-d| is small, too. + # + # eigenvalues good: (a+d+sqrt((a-d)**2+4*b*c))/2 + + t = A[n1-2,n1-2] + A[n1-1,n1-1] + s = (A[n1-1,n1-1] - A[n1-2,n1-2]) ** 2 + 4 * A[n1-1,n1-2] * A[n1-2,n1-1] + if ctx.re(s) > 0: + s = ctx.sqrt(s) + else: + s = ctx.sqrt(-s) * 1j + a = (t + s) / 2 + b = (t - s) / 2 + if abs(A[n1-1,n1-1] - a) > abs(A[n1-1,n1-1] - b): + shift = b + else: + shift = a + + its += 1 + totalits += 1 + + qr_step(ctx, n0, n1, A, Q, shift) + + if its > maxits: + raise RuntimeError("qr: failed to converge after %d steps" % its) + + +@defun +def schur(ctx, A, overwrite_a = False): + """ + This routine computes the Schur decomposition of a square matrix A. + Given A, an unitary matrix Q is determined such that + + Q' A Q = R and Q' Q = Q Q' = 1 + + where R is an upper right triangular matrix. Here ' denotes the + hermitian transpose (i.e. transposition and conjugation). + + input: + A : a real or complex square matrix + overwrite_a : if true, allows modification of A which may improve + performance. if false, A is not modified. + + output: + Q : an unitary matrix + R : an upper right triangular matrix + + return value: (Q, R) + + example: + >>> from mpmath import mp + >>> A = mp.matrix([[3, -1, 2], [2, 5, -5], [-2, -3, 7]]) + >>> Q, R = mp.schur(A) + >>> mp.nprint(R, 3) + [2.0 0.417 2.53] + [0.0 4.0 4.74] + [0.0 0.0 9.0] + >>> print(mp.chop(A - Q * R * Q.transpose_conj())) + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] + + warning: The Schur decomposition is not unique. + """ + + n = A.rows + + if n == 1: + return (ctx.matrix([[1]]), A) + + if not overwrite_a: + A = A.copy() + + T = ctx.matrix(n, 1) + + hessenberg_reduce_0(ctx, A, T) + Q = A.copy() + hessenberg_reduce_1(ctx, Q, T) + + for x in range(n): + for y in range(x + 2, n): + A[y,x] = 0 + + hessenberg_qr(ctx, A, Q) + + return Q, A + + +def eig_tr_r(ctx, A): + """ + This routine calculates the right eigenvectors of an upper right triangular matrix. + + input: + A an upper right triangular matrix + + output: + ER a matrix whose columns form the right eigenvectors of A + + return value: ER + """ + + # this subroutine is inspired by the lapack routines ctrevc.f,clatrs.f + + n = A.rows + + ER = ctx.eye(n) + + eps = ctx.eps + + unfl = ctx.ldexp(ctx.one, -ctx.prec * 30) + # since mpmath effectively has no limits on the exponent, we simply scale doubles up + # original double has prec*20 + + smlnum = unfl * (n / eps) + simin = 1 / ctx.sqrt(eps) + + rmax = 1 + + for i in range(1, n): + s = A[i,i] + + smin = max(eps * abs(s), smlnum) + + for j in range(i - 1, -1, -1): + + r = 0 + for k in range(j + 1, i + 1): + r += A[j,k] * ER[k,i] + + t = A[j,j] - s + if abs(t) < smin: + t = smin + + r = -r / t + ER[j,i] = r + + rmax = max(rmax, abs(r)) + if rmax > simin: + for k in range(j, i+1): + ER[k,i] /= rmax + rmax = 1 + + if rmax != 1: + for k in range(i + 1): + ER[k,i] /= rmax + + return ER + +def eig_tr_l(ctx, A): + """ + This routine calculates the left eigenvectors of an upper right triangular matrix. + + input: + A an upper right triangular matrix + + output: + EL a matrix whose rows form the left eigenvectors of A + + return value: EL + """ + + n = A.rows + + EL = ctx.eye(n) + + eps = ctx.eps + + unfl = ctx.ldexp(ctx.one, -ctx.prec * 30) + # since mpmath effectively has no limits on the exponent, we simply scale doubles up + # original double has prec*20 + + smlnum = unfl * (n / eps) + simin = 1 / ctx.sqrt(eps) + + rmax = 1 + + for i in range(n - 1): + s = A[i,i] + + smin = max(eps * abs(s), smlnum) + + for j in range(i + 1, n): + + r = 0 + for k in range(i, j): + r += EL[i,k] * A[k,j] + + t = A[j,j] - s + if abs(t) < smin: + t = smin + + r = -r / t + EL[i,j] = r + + rmax = max(rmax, abs(r)) + if rmax > simin: + for k in range(i, j + 1): + EL[i,k] /= rmax + rmax = 1 + + if rmax != 1: + for k in range(i, n): + EL[i,k] /= rmax + + return EL + +@defun +def eig(ctx, A, left = False, right = True, overwrite_a = False): + """ + This routine computes the eigenvalues and optionally the left and right + eigenvectors of a square matrix A. Given A, a vector E and matrices ER + and EL are calculated such that + + A ER[:,i] = E[i] ER[:,i] + EL[i,:] A = EL[i,:] E[i] + + E contains the eigenvalues of A. The columns of ER contain the right eigenvectors + of A whereas the rows of EL contain the left eigenvectors. + + + input: + A : a real or complex square matrix of shape (n, n) + left : if true, the left eigenvectors are calculated. + right : if true, the right eigenvectors are calculated. + overwrite_a : if true, allows modification of A which may improve + performance. if false, A is not modified. + + output: + E : a list of length n containing the eigenvalues of A. + ER : a matrix whose columns contain the right eigenvectors of A. + EL : a matrix whose rows contain the left eigenvectors of A. + + return values: + E if left and right are both false. + (E, ER) if right is true and left is false. + (E, EL) if left is true and right is false. + (E, EL, ER) if left and right are true. + + + examples: + >>> from mpmath import mp + >>> A = mp.matrix([[3, -1, 2], [2, 5, -5], [-2, -3, 7]]) + >>> E, ER = mp.eig(A) + >>> print(mp.chop(A * ER[:,0] - E[0] * ER[:,0])) + [0.0] + [0.0] + [0.0] + + >>> E, EL, ER = mp.eig(A,left = True, right = True) + >>> E, EL, ER = mp.eig_sort(E, EL, ER) + >>> mp.nprint(E) + [2.0, 4.0, 9.0] + >>> print(mp.chop(A * ER[:,0] - E[0] * ER[:,0])) + [0.0] + [0.0] + [0.0] + >>> print(mp.chop( EL[0,:] * A - EL[0,:] * E[0])) + [0.0 0.0 0.0] + + warning: + - If there are multiple eigenvalues, the eigenvectors do not necessarily + span the whole vectorspace, i.e. ER and EL may have not full rank. + Furthermore in that case the eigenvectors are numerical ill-conditioned. + - In the general case the eigenvalues have no natural order. + + see also: + - eigh (or eigsy, eighe) for the symmetric eigenvalue problem. + - eig_sort for sorting of eigenvalues and eigenvectors + """ + + n = A.rows + + if n == 1: + if not (left or right): + return [A[0]] + + if left and (not right): + return ([A[0]], ctx.matrix([[1]])) + + if right and (not left): + return ([A[0]], ctx.matrix([[1]])) + + return ([A[0]], ctx.matrix([[1]]), ctx.matrix([[1]])) + + if not overwrite_a: + A = A.copy() + + T = ctx.zeros(n, 1) + + hessenberg_reduce_0(ctx, A, T) + + if left or right: + Q = A.copy() + hessenberg_reduce_1(ctx, Q, T) + else: + Q = False + + for x in range(n): + for y in range(x + 2, n): + A[y,x] = 0 + + hessenberg_qr(ctx, A, Q) + + E = [0 for i in range(n)] + for i in range(n): + E[i] = A[i,i] + + if not (left or right): + return E + + if left: + EL = eig_tr_l(ctx, A) + EL = EL * Q.transpose_conj() + + if right: + ER = eig_tr_r(ctx, A) + ER = Q * ER + + if left and (not right): + return (E, EL) + + if right and (not left): + return (E, ER) + + return (E, EL, ER) + +@defun +def eig_sort(ctx, E, EL = False, ER = False, f = "real"): + """ + This routine sorts the eigenvalues and eigenvectors delivered by ``eig``. + + parameters: + E : the eigenvalues as delivered by eig + EL : the left eigenvectors as delivered by eig, or false + ER : the right eigenvectors as delivered by eig, or false + f : either a string ("real" sort by increasing real part, "imag" sort by + increasing imag part, "abs" sort by absolute value) or a function + mapping complexs to the reals, i.e. ``f = lambda x: -mp.re(x) `` + would sort the eigenvalues by decreasing real part. + + return values: + E if EL and ER are both false. + (E, ER) if ER is not false and left is false. + (E, EL) if EL is not false and right is false. + (E, EL, ER) if EL and ER are not false. + + example: + >>> from mpmath import mp + >>> A = mp.matrix([[3, -1, 2], [2, 5, -5], [-2, -3, 7]]) + >>> E, EL, ER = mp.eig(A,left = True, right = True) + >>> E, EL, ER = mp.eig_sort(E, EL, ER) + >>> mp.nprint(E) + [2.0, 4.0, 9.0] + >>> E, EL, ER = mp.eig_sort(E, EL, ER,f = lambda x: -mp.re(x)) + >>> mp.nprint(E) + [9.0, 4.0, 2.0] + >>> print(mp.chop(A * ER[:,0] - E[0] * ER[:,0])) + [0.0] + [0.0] + [0.0] + >>> print(mp.chop( EL[0,:] * A - EL[0,:] * E[0])) + [0.0 0.0 0.0] + """ + + if isinstance(f, str): + if f == "real": + f = ctx.re + elif f == "imag": + f = ctx.im + elif f == "abs": + f = abs + else: + raise RuntimeError("unknown function %s" % f) + + n = len(E) + + # Sort eigenvalues (bubble-sort) + + for i in range(n): + imax = i + s = f(E[i]) # s is the current maximal element + + for j in range(i + 1, n): + c = f(E[j]) + if c < s: + s = c + imax = j + + if imax != i: + # swap eigenvalues + + z = E[i] + E[i] = E[imax] + E[imax] = z + + if not isinstance(EL, bool): + for j in range(n): + z = EL[i,j] + EL[i,j] = EL[imax,j] + EL[imax,j] = z + + if not isinstance(ER, bool): + for j in range(n): + z = ER[j,i] + ER[j,i] = ER[j,imax] + ER[j,imax] = z + + if isinstance(EL, bool) and isinstance(ER, bool): + return E + + if isinstance(EL, bool) and not(isinstance(ER, bool)): + return (E, ER) + + if isinstance(ER, bool) and not(isinstance(EL, bool)): + return (E, EL) + + return (E, EL, ER) diff --git a/mpmath/matrices/eigen_symmetric.py b/mpmath/matrices/eigen_symmetric.py new file mode 100644 index 0000000..e4bdd93 --- /dev/null +++ b/mpmath/matrices/eigen_symmetric.py @@ -0,0 +1,1802 @@ +################################################################################################## +# module for the symmetric eigenvalue problem +# Copyright 2013 Timo Hartmann (thartmann15 at gmail.com) +# +# todo: +# - implement balancing +# +################################################################################################## + +""" +The symmetric eigenvalue problem. +--------------------------------- + +This file contains routines for the symmetric eigenvalue problem. + +high level routines: + + eigsy : real symmetric (ordinary) eigenvalue problem + eighe : complex hermitian (ordinary) eigenvalue problem + eigh : unified interface for eigsy and eighe + svd_r : singular value decomposition for real matrices + svd_c : singular value decomposition for complex matrices + svd : unified interface for svd_r and svd_c + + +low level routines: + + r_sy_tridiag : reduction of real symmetric matrix to real symmetric tridiagonal matrix + c_he_tridiag_0 : reduction of complex hermitian matrix to real symmetric tridiagonal matrix + c_he_tridiag_1 : auxiliary routine to c_he_tridiag_0 + c_he_tridiag_2 : auxiliary routine to c_he_tridiag_0 + tridiag_eigen : solves the real symmetric tridiagonal matrix eigenvalue problem + svd_r_raw : raw singular value decomposition for real matrices + svd_c_raw : raw singular value decomposition for complex matrices +""" + +from .eigen import defun + + +def r_sy_tridiag(ctx, A, D, E, calc_ev = True): + """ + This routine transforms a real symmetric matrix A to a real symmetric + tridiagonal matrix T using an orthogonal similarity transformation: + Q' * A * Q = T (here ' denotes the matrix transpose). + The orthogonal matrix Q is build up from Householder reflectors. + + parameters: + A (input/output) On input, A contains the real symmetric matrix of + dimension (n,n). On output, if calc_ev is true, A contains the + orthogonal matrix Q, otherwise A is destroyed. + + D (output) real array of length n, contains the diagonal elements + of the tridiagonal matrix + + E (output) real array of length n, contains the offdiagonal elements + of the tridiagonal matrix in E[0:(n-1)] where is the dimension of + the matrix A. E[n-1] is undefined. + + calc_ev (input) If calc_ev is true, this routine explicitly calculates the + orthogonal matrix Q which is then returned in A. If calc_ev is + false, Q is not explicitly calculated resulting in a shorter run time. + + This routine is a python translation of the fortran routine tred2.f in the + software library EISPACK (see netlib.org) which itself is based on the algol + procedure tred2 described in: + - Num. Math. 11, p.181-195 (1968) by Martin, Reinsch and Wilkonson + - Handbook for auto. comp., Vol II, Linear Algebra, p.212-226 (1971) + + For a good introduction to Householder reflections, see also + Stoer, Bulirsch - Introduction to Numerical Analysis. + """ + + # note : the vector v of the i-th houshoulder reflector is stored in a[(i+1):,i] + # whereas v/ is stored in a[i,(i+1):] + + n = A.rows + for i in range(n - 1, 0, -1): + # scale the vector + + scale = 0 + for k in range(i): + scale += abs(A[k,i]) + + scale_inv = 0 + if scale != 0: + scale_inv = 1/scale + + # sadly there are floating-point numbers not equal to zero whose reciprocal is infinity + + if i == 1 or scale == 0 or ctx.isinf(scale_inv): + E[i] = A[i-1,i] # nothing to do + D[i] = 0 + continue + + # calculate parameters for housholder transformation + + H = 0 + for k in range(i): + A[k,i] *= scale_inv + H += A[k,i] * A[k,i] + + F = A[i-1,i] + G = ctx.sqrt(H) + if F > 0: + G = -G + E[i] = scale * G + H -= F * G + A[i-1,i] = F - G + F = 0 + + # apply housholder transformation + + for j in range(i): + if calc_ev: + A[i,j] = A[j,i] / H + + G = 0 # calculate A*U + for k in range(j + 1): + G += A[k,j] * A[k,i] + for k in range(j + 1, i): + G += A[j,k] * A[k,i] + + E[j] = G / H # calculate P + F += E[j] * A[j,i] + + HH = F / (2 * H) + + for j in range(i): # calculate reduced A + F = A[j,i] + G = E[j] - HH * F # calculate Q + E[j] = G + + for k in range(j + 1): + A[k,j] -= F * E[k] + G * A[k,i] + + D[i] = H + + for i in range(1, n): # better for compatibility + E[i-1] = E[i] + E[n-1] = 0 + + if calc_ev: + D[0] = 0 + for i in range(n): + if D[i] != 0: + for j in range(i): # accumulate transformation matrices + G = 0 + for k in range(i): + G += A[i,k] * A[k,j] + for k in range(i): + A[k,j] -= G * A[k,i] + + D[i] = A[i,i] + A[i,i] = 1 + + for j in range(i): + A[j,i] = A[i,j] = 0 + else: + for i in range(n): + D[i] = A[i,i] + + + + + +def c_he_tridiag_0(ctx, A, D, E, T): + """ + This routine transforms a complex hermitian matrix A to a real symmetric + tridiagonal matrix T using an unitary similarity transformation: + Q' * A * Q = T (here ' denotes the hermitian matrix transpose, + i.e. transposition und conjugation). + The unitary matrix Q is build up from Householder reflectors and + an unitary diagonal matrix. + + parameters: + A (input/output) On input, A contains the complex hermitian matrix + of dimension (n,n). On output, A contains the unitary matrix Q + in compressed form. + + D (output) real array of length n, contains the diagonal elements + of the tridiagonal matrix. + + E (output) real array of length n, contains the offdiagonal elements + of the tridiagonal matrix in E[0:(n-1)] where is the dimension of + the matrix A. E[n-1] is undefined. + + T (output) complex array of length n, contains a unitary diagonal + matrix. + + This routine is a python translation (in slightly modified form) of the fortran + routine htridi.f in the software library EISPACK (see netlib.org) which itself + is a complex version of the algol procedure tred1 described in: + - Num. Math. 11, p.181-195 (1968) by Martin, Reinsch and Wilkonson + - Handbook for auto. comp., Vol II, Linear Algebra, p.212-226 (1971) + + For a good introduction to Householder reflections, see also + Stoer, Bulirsch - Introduction to Numerical Analysis. + """ + + n = A.rows + T[n-1] = 1 + for i in range(n - 1, 0, -1): + + # scale the vector + + scale = 0 + for k in range(i): + scale += abs(ctx.re(A[k,i])) + abs(ctx.im(A[k,i])) + + scale_inv = 0 + if scale != 0: + scale_inv = 1 / scale + + # sadly there are floating-point numbers not equal to zero whose reciprocal is infinity + + if scale == 0 or ctx.isinf(scale_inv): + E[i] = 0 + D[i] = 0 + T[i-1] = 1 + continue + + if i == 1: + F = A[i-1,i] + f = abs(F) + E[i] = f + D[i] = 0 + if f != 0: + T[i-1] = T[i] * F / f + else: + T[i-1] = T[i] + continue + + # calculate parameters for housholder transformation + + H = 0 + for k in range(i): + A[k,i] *= scale_inv + rr = ctx.re(A[k,i]) + ii = ctx.im(A[k,i]) + H += rr * rr + ii * ii + + F = A[i-1,i] + f = abs(F) + G = ctx.sqrt(H) + H += G * f + E[i] = scale * G + if f != 0: + F = F / f + TZ = - T[i] * F # T[i-1]=-T[i]*F, but we need T[i-1] as temporary storage + G *= F + else: + TZ = -T[i] # T[i-1]=-T[i] + A[i-1,i] += G + F = 0 + + # apply housholder transformation + + for j in range(i): + A[i,j] = A[j,i] / H + + G = 0 # calculate A*U + for k in range(j + 1): + G += ctx.conj(A[k,j]) * A[k,i] + for k in range(j + 1, i): + G += A[j,k] * A[k,i] + + T[j] = G / H # calculate P + F += ctx.conj(T[j]) * A[j,i] + + HH = F / (2 * H) + + for j in range(i): # calculate reduced A + F = A[j,i] + G = T[j] - HH * F # calculate Q + T[j] = G + + for k in range(j + 1): + A[k,j] -= ctx.conj(F) * T[k] + ctx.conj(G) * A[k,i] + # as we use the lower left part for storage + # we have to use the transpose of the normal formula + + T[i-1] = TZ + D[i] = H + + for i in range(1, n): # better for compatibility + E[i-1] = E[i] + E[n-1] = 0 + + D[0] = 0 + for i in range(n): + zw = D[i] + D[i] = ctx.re(A[i,i]) + A[i,i] = zw + + + + + + + +def c_he_tridiag_1(ctx, A, T): + """ + This routine forms the unitary matrix Q described in c_he_tridiag_0. + + parameters: + A (input/output) On input, A is the same matrix as delivered by + c_he_tridiag_0. On output, A is set to Q. + + T (input) On input, T is the same array as delivered by c_he_tridiag_0. + + """ + + n = A.rows + + for i in range(n): + if A[i,i] != 0: + for j in range(i): + G = 0 + for k in range(i): + G += ctx.conj(A[i,k]) * A[k,j] + for k in range(i): + A[k,j] -= G * A[k,i] + + A[i,i] = 1 + + for j in range(i): + A[j,i] = A[i,j] = 0 + + for i in range(n): + for k in range(n): + A[i,k] *= T[k] + + + + +def c_he_tridiag_2(ctx, A, T, B): + """ + This routine applied the unitary matrix Q described in c_he_tridiag_0 + onto the the matrix B, i.e. it forms Q*B. + + parameters: + A (input) On input, A is the same matrix as delivered by c_he_tridiag_0. + + T (input) On input, T is the same array as delivered by c_he_tridiag_0. + + B (input/output) On input, B is a complex matrix. On output B is replaced + by Q*B. + + This routine is a python translation of the fortran routine htribk.f in the + software library EISPACK (see netlib.org). See c_he_tridiag_0 for more + references. + """ + + n = A.rows + + for i in range(n): + for k in range(n): + B[k,i] *= T[k] + + for i in range(n): + if A[i,i] != 0: + for j in range(n): + G = 0 + for k in range(i): + G += ctx.conj(A[i,k]) * B[k,j] + for k in range(i): + B[k,j] -= G * A[k,i] + + + + + +def tridiag_eigen(ctx, d, e, z = False): + """ + This subroutine find the eigenvalues and the first components of the + eigenvectors of a real symmetric tridiagonal matrix using the implicit + QL method. + + parameters: + + d (input/output) real array of length n. on input, d contains the diagonal + elements of the input matrix. on output, d contains the eigenvalues in + ascending order. + + e (input) real array of length n. on input, e contains the offdiagonal + elements of the input matrix in e[0:(n-1)]. On output, e has been + destroyed. + + z (input/output) If z is equal to False, no eigenvectors will be computed. + Otherwise on input z should have the format z[0:m,0:n] (i.e. a real or + complex matrix of dimension (m,n) ). On output this matrix will be + multiplied by the matrix of the eigenvectors (i.e. the columns of this + matrix are the eigenvectors): z --> z*EV + That means if z[i,j]={1 if j==j; 0 otherwise} on input, then on output + z will contain the first m components of the eigenvectors. That means + if m is equal to n, the i-th eigenvector will be z[:,i]. + + This routine is a python translation (in slightly modified form) of the + fortran routine imtql2.f in the software library EISPACK (see netlib.org) + which itself is based on the algol procudure imtql2 desribed in: + - num. math. 12, p. 377-383(1968) by matrin and wilkinson + - modified in num. math. 15, p. 450(1970) by dubrulle + - handbook for auto. comp., vol. II-linear algebra, p. 241-248 (1971) + See also the routine gaussq.f in netlog.org or acm algorithm 726. + """ + + n = len(d) + e[n-1] = 0 + iterlim = 2 * ctx.dps + + for l in range(n): + j = 0 + while 1: + m = l + while 1: + # look for a small subdiagonal element + if m + 1 == n: + break + if abs(e[m]) <= ctx.eps * (abs(d[m]) + abs(d[m + 1])): + break + m = m + 1 + if m == l: + break + + if j >= iterlim: + raise RuntimeError("tridiag_eigen: no convergence to an eigenvalue after %d iterations" % iterlim) + + j += 1 + + # form shift + + p = d[l] + g = (d[l + 1] - p) / (2 * e[l]) + r = ctx.hypot(g, 1) + + if g < 0: + s = g - r + else: + s = g + r + + g = d[m] - p + e[l] / s + + s, c, p = 1, 1, 0 + + for i in range(m - 1, l - 1, -1): + f = s * e[i] + b = c * e[i] + if abs(f) > abs(g): # this here is a slight improvement also used in gaussq.f or acm algorithm 726. + c = g / f + r = ctx.hypot(c, 1) + e[i + 1] = f * r + s = 1 / r + c = c * s + else: + s = f / g + r = ctx.hypot(s, 1) + e[i + 1] = g * r + c = 1 / r + s = s * c + g = d[i + 1] - p + r = (d[i] - g) * s + 2 * c * b + p = s * r + d[i + 1] = g + p + g = c * r - b + + if not isinstance(z, bool): + # calculate eigenvectors + for w in range(z.rows): + f = z[w,i+1] + z[w,i+1] = s * z[w,i] + c * f + z[w,i ] = c * z[w,i] - s * f + + d[l] = d[l] - p + e[l] = g + e[m] = 0 + + for ii in range(1, n): + # sort eigenvalues and eigenvectors (bubble-sort) + i = ii - 1 + k = i + p = d[i] + for j in range(ii, n): + if d[j] >= p: + continue + k = j + p = d[k] + if k == i: + continue + d[k] = d[i] + d[i] = p + + if not isinstance(z, bool): + for w in range(z.rows): + p = z[w,i] + z[w,i] = z[w,k] + z[w,k] = p + +######################################################################################## + +@defun +def eigsy(ctx, A, eigvals_only = False, overwrite_a = False): + """ + This routine solves the (ordinary) eigenvalue problem for a real symmetric + square matrix A. Given A, an orthogonal matrix Q is calculated which + diagonalizes A: + + Q' A Q = diag(E) and Q Q' = Q' Q = 1 + + Here diag(E) is a diagonal matrix whose diagonal is E. + ' denotes the transpose. + + The columns of Q are the eigenvectors of A and E contains the eigenvalues: + + A Q[:,i] = E[i] Q[:,i] + + + input: + + A: real matrix of format (n,n) which is symmetric + (i.e. A=A' or A[i,j]=A[j,i]) + + eigvals_only: if true, calculates only the eigenvalues E. + if false, calculates both eigenvectors and eigenvalues. + + overwrite_a: if true, allows modification of A which may improve + performance. if false, A is not modified. + + output: + + E: vector of format (n). contains the eigenvalues of A in ascending order. + + Q: orthogonal matrix of format (n,n). contains the eigenvectors + of A as columns. + + return value: + + E if eigvals_only is true + (E, Q) if eigvals_only is false + + example: + >>> from mpmath import mp + >>> A = mp.matrix([[3, 2], [2, 0]]) + >>> E = mp.eigsy(A, eigvals_only = True) + >>> print(E) + [-1.0] + [ 4.0] + + >>> A = mp.matrix([[1, 2], [2, 3]]) + >>> E, Q = mp.eigsy(A) + >>> print(mp.chop(A * Q[:,0] - E[0] * Q[:,0])) + [0.0] + [0.0] + + see also: eighe, eigh, eig + """ + + if not overwrite_a: + A = A.copy() + + d = ctx.zeros(A.rows, 1) + e = ctx.zeros(A.rows, 1) + + if eigvals_only: + r_sy_tridiag(ctx, A, d, e, calc_ev = False) + tridiag_eigen(ctx, d, e, False) + return d + else: + r_sy_tridiag(ctx, A, d, e, calc_ev = True) + tridiag_eigen(ctx, d, e, A) + return (d, A) + + +@defun +def eighe(ctx, A, eigvals_only = False, overwrite_a = False): + """ + This routine solves the (ordinary) eigenvalue problem for a complex + hermitian square matrix A. Given A, an unitary matrix Q is calculated which + diagonalizes A: + + Q' A Q = diag(E) and Q Q' = Q' Q = 1 + + Here diag(E) a is diagonal matrix whose diagonal is E. + ' denotes the hermitian transpose (i.e. ordinary transposition and + complex conjugation). + + The columns of Q are the eigenvectors of A and E contains the eigenvalues: + + A Q[:,i] = E[i] Q[:,i] + + + input: + + A: complex matrix of format (n,n) which is hermitian + (i.e. A=A' or A[i,j]=conj(A[j,i])) + + eigvals_only: if true, calculates only the eigenvalues E. + if false, calculates both eigenvectors and eigenvalues. + + overwrite_a: if true, allows modification of A which may improve + performance. if false, A is not modified. + + output: + + E: vector of format (n). contains the eigenvalues of A in ascending order. + + Q: unitary matrix of format (n,n). contains the eigenvectors + of A as columns. + + return value: + + E if eigvals_only is true + (E, Q) if eigvals_only is false + + example: + >>> from mpmath import mp + >>> A = mp.matrix([[1, -3 - 1j], [-3 + 1j, -2]]) + >>> E = mp.eighe(A, eigvals_only = True) + >>> print(E) + [-4.0] + [ 3.0] + + >>> A = mp.matrix([[1, 2 + 5j], [2 - 5j, 3]]) + >>> E, Q = mp.eighe(A) + >>> print(mp.chop(A * Q[:,0] - E[0] * Q[:,0])) + [0.0] + [0.0] + + see also: eigsy, eigh, eig + """ + + if not overwrite_a: + A = A.copy() + + d = ctx.zeros(A.rows, 1) + e = ctx.zeros(A.rows, 1) + t = ctx.zeros(A.rows, 1) + + if eigvals_only: + c_he_tridiag_0(ctx, A, d, e, t) + tridiag_eigen(ctx, d, e, False) + return d + else: + c_he_tridiag_0(ctx, A, d, e, t) + B = ctx.eye(A.rows) + tridiag_eigen(ctx, d, e, B) + c_he_tridiag_2(ctx, A, t, B) + return (d, B) + +@defun +def eigh(ctx, A, eigvals_only = False, overwrite_a = False): + """ + "eigh" is a unified interface for "eigsy" and "eighe". Depending on + whether A is real or complex the appropriate function is called. + + This routine solves the (ordinary) eigenvalue problem for a real symmetric + or complex hermitian square matrix A. Given A, an orthogonal (A real) or + unitary (A complex) matrix Q is calculated which diagonalizes A: + + Q' A Q = diag(E) and Q Q' = Q' Q = 1 + + Here diag(E) a is diagonal matrix whose diagonal is E. + ' denotes the hermitian transpose (i.e. ordinary transposition and + complex conjugation). + + The columns of Q are the eigenvectors of A and E contains the eigenvalues: + + A Q[:,i] = E[i] Q[:,i] + + input: + + A: a real or complex square matrix of format (n,n) which is symmetric + (i.e. A[i,j]=A[j,i]) or hermitian (i.e. A[i,j]=conj(A[j,i])). + + eigvals_only: if true, calculates only the eigenvalues E. + if false, calculates both eigenvectors and eigenvalues. + + overwrite_a: if true, allows modification of A which may improve + performance. if false, A is not modified. + + output: + + E: vector of format (n). contains the eigenvalues of A in ascending order. + + Q: an orthogonal or unitary matrix of format (n,n). contains the + eigenvectors of A as columns. + + return value: + + E if eigvals_only is true + (E, Q) if eigvals_only is false + + example: + >>> from mpmath import mp + >>> A = mp.matrix([[3, 2], [2, 0]]) + >>> E = mp.eigh(A, eigvals_only = True) + >>> print(E) + [-1.0] + [ 4.0] + + >>> A = mp.matrix([[1, 2], [2, 3]]) + >>> E, Q = mp.eigh(A) + >>> print(mp.chop(A * Q[:,0] - E[0] * Q[:,0])) + [0.0] + [0.0] + + >>> A = mp.matrix([[1, 2 + 5j], [2 - 5j, 3]]) + >>> E, Q = mp.eigh(A) + >>> print(mp.chop(A * Q[:,0] - E[0] * Q[:,0])) + [0.0] + [0.0] + + see also: eigsy, eighe, eig + """ + + iscomplex = any(type(x) is ctx.mpc for x in A) + + if iscomplex: + return ctx.eighe(A, eigvals_only = eigvals_only, overwrite_a = overwrite_a) + else: + return ctx.eigsy(A, eigvals_only = eigvals_only, overwrite_a = overwrite_a) + + +@defun +def gauss_quadrature(ctx, n, qtype = "legendre", alpha = 0, beta = 0): + """ + This routine calulates gaussian quadrature rules for different + families of orthogonal polynomials. Let (a, b) be an interval, + W(x) a positive weight function and n a positive integer. + Then the purpose of this routine is to calculate pairs (x_k, w_k) + for k=0, 1, 2, ... (n-1) which give + + int(W(x) * F(x), x = a..b) = sum(w_k * F(x_k),k = 0..(n-1)) + + exact for all polynomials F(x) of degree (strictly) less than 2*n. For all + integrable functions F(x) the sum is a (more or less) good approximation to + the integral. The x_k are called nodes (which are the zeros of the + related orthogonal polynomials) and the w_k are called the weights. + + parameters + n (input) The degree of the quadrature rule, i.e. its number of + nodes. + + qtype (input) The family of orthogonal polynmomials for which to + compute the quadrature rule. See the list below. + + alpha (input) real number, used as parameter for some orthogonal + polynomials + + beta (input) real number, used as parameter for some orthogonal + polynomials. + + return value + + (X, W) a pair of two real arrays where x_k = X[k] and w_k = W[k]. + + + orthogonal polynomials: + + qtype polynomial + ----- ---------- + + "legendre" Legendre polynomials, W(x)=1 on the interval (-1, +1) + "legendre01" shifted Legendre polynomials, W(x)=1 on the interval (0, +1) + "hermite" Hermite polynomials, W(x)=exp(-x*x) on (-infinity,+infinity) + "laguerre" Laguerre polynomials, W(x)=exp(-x) on (0,+infinity) + "glaguerre" generalized Laguerre polynomials, W(x)=exp(-x)*x**alpha + on (0, +infinity) + "chebyshev1" Chebyshev polynomials of the first kind, W(x)=1/sqrt(1-x*x) + on (-1, +1) + "chebyshev2" Chebyshev polynomials of the second kind, W(x)=sqrt(1-x*x) + on (-1, +1) + "jacobi" Jacobi polynomials, W(x)=(1-x)**alpha * (1+x)**beta on (-1, +1) + with alpha>-1 and beta>-1 + + examples: + >>> from mpmath import mp + >>> f = lambda x: x**8 + 2 * x**6 - 3 * x**4 + 5 * x**2 - 7 + >>> X, W = mp.gauss_quadrature(5, "hermite") + >>> A = mp.fdot([(f(x), w) for x, w in zip(X, W)]) + >>> B = mp.sqrt(mp.pi) * 57 / 16 + >>> C = mp.quad(lambda x: mp.exp(- x * x) * f(x), [-mp.inf, +mp.inf]) + >>> mp.nprint((mp.chop(A-B, tol = 1e-10), mp.chop(A-C, tol = 1e-10))) + (0.0, 0.0) + + >>> f = lambda x: x**5 - 2 * x**4 + 3 * x**3 - 5 * x**2 + 7 * x - 11 + >>> X, W = mp.gauss_quadrature(3, "laguerre") + >>> A = mp.fdot([(f(x), w) for x, w in zip(X, W)]) + >>> B = 76 + >>> C = mp.quad(lambda x: mp.exp(-x) * f(x), [0, +mp.inf]) + >>> mp.nprint(mp.chop(A-B, tol = 1e-10), mp.chop(A-C, tol = 1e-10)) + .0 + + # orthogonality of the chebyshev polynomials: + >>> f = lambda x: mp.chebyt(3, x) * mp.chebyt(2, x) + >>> X, W = mp.gauss_quadrature(3, "chebyshev1") + >>> A = mp.fdot([(f(x), w) for x, w in zip(X, W)]) + >>> print(mp.chop(A, tol = 1e-10)) + 0.0 + + references: + - [GolubWelsch]_ + - [Golub]_ + - [Stroud]_ + + See also the routine gaussq.f in netlog.org or ACM Transactions on + Mathematical Software algorithm 726. + """ + + d = ctx.zeros(n, 1) + e = ctx.zeros(n, 1) + z = ctx.zeros(1, n) + + z[0,0] = 1 + + if qtype == "legendre": + # legendre on the range -1 +1 , abramowitz, table 25.4, p.916 + w = 2 + for i in range(n): + j = i + 1 + e[i] = ctx.sqrt(j * j / (4 * j * j - ctx.mpf(1))) + elif qtype == "legendre01": + # legendre shifted to 0 1 , abramowitz, table 25.8, p.921 + w = 1 + for i in range(n): + d[i] = 1 / ctx.mpf(2) + j = i + 1 + e[i] = ctx.sqrt(j * j / (16 * j * j - ctx.mpf(4))) + elif qtype == "hermite": + # hermite on the range -inf +inf , abramowitz, table 25.10,p.924 + w = ctx.sqrt(ctx.pi) + for i in range(n): + j = i + 1 + e[i] = ctx.sqrt(j / ctx.mpf(2)) + elif qtype == "laguerre": + # laguerre on the range 0 +inf , abramowitz, table 25.9, p. 923 + w = 1 + for i in range(n): + j = i + 1 + d[i] = 2 * j - 1 + e[i] = j + elif qtype=="chebyshev1": + # chebyshev polynimials of the first kind + w = ctx.pi + for i in range(n): + e[i] = 1 / ctx.mpf(2) + e[0] = ctx.sqrt(1 / ctx.mpf(2)) + elif qtype == "chebyshev2": + # chebyshev polynimials of the second kind + w = ctx.pi / 2 + for i in range(n): + e[i] = 1 / ctx.mpf(2) + elif qtype == "glaguerre": + # generalized laguerre on the range 0 +inf + w = ctx.gamma(1 + alpha) + for i in range(n): + j = i + 1 + d[i] = 2 * j - 1 + alpha + e[i] = ctx.sqrt(j * (j + alpha)) + elif qtype == "jacobi": + # jacobi polynomials + alpha = ctx.mpf(alpha) + beta = ctx.mpf(beta) + ab = alpha + beta + abi = ab + 2 + w = (2**(ab+1)) * ctx.gamma(alpha + 1) * ctx.gamma(beta + 1) / ctx.gamma(abi) + d[0] = (beta - alpha) / abi + e[0] = ctx.sqrt(4 * (1 + alpha) * (1 + beta) / ((abi + 1) * (abi * abi))) + a2b2 = beta * beta - alpha * alpha + for i in range(1, n): + j = i + 1 + abi = 2 * j + ab + d[i] = a2b2 / ((abi - 2) * abi) + e[i] = ctx.sqrt(4 * j * (j + alpha) * (j + beta) * (j + ab) / ((abi * abi - 1) * abi * abi)) + elif isinstance(qtype, str): + raise ValueError("unknown quadrature rule \"%s\"" % qtype) + elif not isinstance(qtype, str): + w = qtype(d, e) + else: + assert 0 + + tridiag_eigen(ctx, d, e, z) + + for i in range(len(z)): + z[i] *= z[i] + + z = z.transpose() + return (d, w * z) + +################################################################################################## +################################################################################################## +################################################################################################## + +def svd_r_raw(ctx, A, V = False, calc_u = False): + """ + This routine computes the singular value decomposition of a matrix A. + Given A, two orthogonal matrices U and V are calculated such that + + A = U S V + + where S is a suitable shaped matrix whose off-diagonal elements are zero. + The diagonal elements of S are the singular values of A, i.e. the + squareroots of the eigenvalues of A' A or A A'. Here ' denotes the transpose. + Householder bidiagonalization and a variant of the QR algorithm is used. + + overview of the matrices : + + A : m*n A gets replaced by U + U : m*n U replaces A. If n>m then only the first m*m block of U is + non-zero. column-orthogonal: U' U = B + here B is a n*n matrix whose first min(m,n) diagonal + elements are 1 and all other elements are zero. + S : n*n diagonal matrix, only the diagonal elements are stored in + the array S. only the first min(m,n) diagonal elements are non-zero. + V : n*n orthogonal: V V' = V' V = 1 + + parameters: + A (input/output) On input, A contains a real matrix of shape m*n. + On output, if calc_u is true A contains the column-orthogonal + matrix U; otherwise A is simply used as workspace and thus destroyed. + + V (input/output) if false, the matrix V is not calculated. otherwise + V must be a matrix of shape n*n. + + calc_u (input) If true, the matrix U is calculated and replaces A. + if false, U is not calculated and A is simply destroyed + + return value: + S an array of length n containing the singular values of A sorted by + decreasing magnitude. only the first min(m,n) elements are non-zero. + + This routine is a python translation of the fortran routine svd.f in the + software library EISPACK (see netlib.org) which itself is based on the + algol procedure svd described in: + - num. math. 14, 403-420(1970) by golub and reinsch. + - wilkinson/reinsch: handbook for auto. comp., vol ii-linear algebra, 134-151(1971). + + """ + + m, n = A.rows, A.cols + + S = ctx.zeros(n, 1) + + # work is a temporary array of size n + work = ctx.zeros(n, 1) + + g = scale = anorm = 0 + maxits = 3 * ctx.dps + + for i in range(n): # householder reduction to bidiagonal form + work[i] = scale*g + g = s = scale = 0 + if i < m: + for k in range(i, m): + scale += ctx.fabs(A[k,i]) + if scale != 0: + for k in range(i, m): + A[k,i] /= scale + s += A[k,i] * A[k,i] + f = A[i,i] + g = -ctx.sqrt(s) + if f < 0: + g = -g + h = f * g - s + A[i,i] = f - g + for j in range(i+1, n): + s = 0 + for k in range(i, m): + s += A[k,i] * A[k,j] + f = s / h + for k in range(i, m): + A[k,j] += f * A[k,i] + for k in range(i,m): + A[k,i] *= scale + + S[i] = scale * g + g = s = scale = 0 + + if i < m and i != n - 1: + for k in range(i+1, n): + scale += ctx.fabs(A[i,k]) + if scale: + for k in range(i+1, n): + A[i,k] /= scale + s += A[i,k] * A[i,k] + f = A[i,i+1] + g = -ctx.sqrt(s) + if f < 0: + g = -g + h = f * g - s + A[i,i+1] = f - g + + for k in range(i+1, n): + work[k] = A[i,k] / h + + for j in range(i+1, m): + s = 0 + for k in range(i+1, n): + s += A[j,k] * A[i,k] + for k in range(i+1, n): + A[j,k] += s * work[k] + + for k in range(i+1, n): + A[i,k] *= scale + + anorm = max(anorm, ctx.fabs(S[i]) + ctx.fabs(work[i])) + + if not isinstance(V, bool): + for i in range(n-2, -1, -1): # accumulation of right hand transformations + V[i+1,i+1] = 1 + + if work[i+1] != 0: + for j in range(i+1, n): + V[i,j] = (A[i,j] / A[i,i+1]) / work[i+1] + for j in range(i+1, n): + s = 0 + for k in range(i+1, n): + s += A[i,k] * V[j,k] + for k in range(i+1, n): + V[j,k] += s * V[i,k] + + for j in range(i+1, n): + V[j,i] = V[i,j] = 0 + + V[0,0] = 1 + + if m= maxits: + raise RuntimeError("svd: no convergence to an eigenvalue after %d iterations" % its) + + x = S[l] # shift from bottom 2 by 2 minor + nm = k-1 + y = S[nm] + g = work[nm] + h = work[k] + f = ((y - z) * (y + z) + (g - h) * (g + h))/(2 * h * y) + g = ctx.hypot(f, 1) + if f >= 0: f = ((x - z) * (x + z) + h * ((y / (f + g)) - h)) / x + else: f = ((x - z) * (x + z) + h * ((y / (f - g)) - h)) / x + + c = s = 1 # next qt transformation + + for j in range(l, nm + 1): + g = work[j+1] + y = S[j+1] + h = s * g + g = c * g + z = ctx.hypot(f, h) + work[j] = z + c = f / z + s = h / z + f = x * c + g * s + g = g * c - x * s + h = y * s + y *= c + if not isinstance(V, bool): + for jj in range(n): + x = V[j ,jj] + z = V[j+1,jj] + V[j ,jj]= x * c + z * s + V[j+1 ,jj]= z * c - x * s + z = ctx.hypot(f, h) + S[j] = z + if z != 0: # rotation can be arbitray if z=0 + z = 1 / z + c = f * z + s = h * z + f = c * g + s * y + x = c * y - s * g + + if calc_u: + for jj in range(m): + y = A[jj,j ] + z = A[jj,j+1] + A[jj,j ] = y * c + z * s + A[jj,j+1 ] = z * c - y * s + + work[l] = 0 + work[k] = f + S[k] = x + + ########################## + + # Sort singular values into decreasing order (bubble-sort) + + for i in range(n): + imax = i + s = ctx.fabs(S[i]) # s is the current maximal element + + for j in range(i + 1, n): + c = ctx.fabs(S[j]) + if c > s: + s = c + imax = j + + if imax != i: + # swap singular values + + z = S[i] + S[i] = S[imax] + S[imax] = z + + if calc_u: + for j in range(m): + z = A[j,i] + A[j,i] = A[j,imax] + A[j,imax] = z + + if not isinstance(V, bool): + for j in range(n): + z = V[i,j] + V[i,j] = V[imax,j] + V[imax,j] = z + + return S + +####################### + +def svd_c_raw(ctx, A, V = False, calc_u = False): + """ + This routine computes the singular value decomposition of a matrix A. + Given A, two unitary matrices U and V are calculated such that + + A = U S V + + where S is a suitable shaped matrix whose off-diagonal elements are zero. + The diagonal elements of S are the singular values of A, i.e. the + squareroots of the eigenvalues of A' A or A A'. Here ' denotes the hermitian + transpose (i.e. transposition and conjugation). Householder bidiagonalization + and a variant of the QR algorithm is used. + + overview of the matrices : + + A : m*n A gets replaced by U + U : m*n U replaces A. If n>m then only the first m*m block of U is + non-zero. column-unitary: U' U = B + here B is a n*n matrix whose first min(m,n) diagonal + elements are 1 and all other elements are zero. + S : n*n diagonal matrix, only the diagonal elements are stored in + the array S. only the first min(m,n) diagonal elements are non-zero. + V : n*n unitary: V V' = V' V = 1 + + parameters: + A (input/output) On input, A contains a complex matrix of shape m*n. + On output, if calc_u is true A contains the column-unitary + matrix U; otherwise A is simply used as workspace and thus destroyed. + + V (input/output) if false, the matrix V is not calculated. otherwise + V must be a matrix of shape n*n. + + calc_u (input) If true, the matrix U is calculated and replaces A. + if false, U is not calculated and A is simply destroyed + + return value: + S an array of length n containing the singular values of A sorted by + decreasing magnitude. only the first min(m,n) elements are non-zero. + + This routine is a python translation of the fortran routine svd.f in the + software library EISPACK (see netlib.org) which itself is based on the + algol procedure svd described in: + - num. math. 14, 403-420(1970) by golub and reinsch. + - wilkinson/reinsch: handbook for auto. comp., vol ii-linear algebra, 134-151(1971). + + """ + + m, n = A.rows, A.cols + + S = ctx.zeros(n, 1) + + # work is a temporary array of size n + work = ctx.zeros(n, 1) + lbeta = ctx.zeros(n, 1) + rbeta = ctx.zeros(n, 1) + dwork = ctx.zeros(n, 1) + + g = scale = anorm = 0 + maxits = 3 * ctx.dps + + for i in range(n): # householder reduction to bidiagonal form + dwork[i] = scale * g # dwork are the side-diagonal elements + g = s = scale = 0 + if i < m: + for k in range(i, m): + scale += ctx.fabs(ctx.re(A[k,i])) + ctx.fabs(ctx.im(A[k,i])) + if scale != 0: + for k in range(i, m): + A[k,i] /= scale + ar = ctx.re(A[k,i]) + ai = ctx.im(A[k,i]) + s += ar * ar + ai * ai + f = A[i,i] + g = -ctx.sqrt(s) + if ctx.re(f) < 0: + beta = -g - ctx.conj(f) + g = -g + else: + beta = -g + ctx.conj(f) + beta /= ctx.conj(beta) + beta += 1 + h = 2 * (ctx.re(f) * g - s) + A[i,i] = f - g + beta /= h + lbeta[i] = (beta / scale) / scale + for j in range(i+1, n): + s = 0 + for k in range(i, m): + s += ctx.conj(A[k,i]) * A[k,j] + f = beta * s + for k in range(i, m): + A[k,j] += f * A[k,i] + for k in range(i, m): + A[k,i] *= scale + + S[i] = scale * g # S are the diagonal elements + g = s = scale = 0 + + if i < m and i != n - 1: + for k in range(i+1, n): + scale += ctx.fabs(ctx.re(A[i,k])) + ctx.fabs(ctx.im(A[i,k])) + if scale: + for k in range(i+1, n): + A[i,k] /= scale + ar = ctx.re(A[i,k]) + ai = ctx.im(A[i,k]) + s += ar * ar + ai * ai + f = A[i,i+1] + g = -ctx.sqrt(s) + if ctx.re(f) < 0: + beta = -g - ctx.conj(f) + g = -g + else: + beta = -g + ctx.conj(f) + + beta /= ctx.conj(beta) + beta += 1 + + h = 2 * (ctx.re(f) * g - s) + A[i,i+1] = f - g + + beta /= h + rbeta[i] = (beta / scale) / scale + + for k in range(i+1, n): + work[k] = A[i, k] + + for j in range(i+1, m): + s = 0 + for k in range(i+1, n): + s += ctx.conj(A[i,k]) * A[j,k] + f = s * beta + for k in range(i+1,n): + A[j,k] += f * work[k] + + for k in range(i+1, n): + A[i,k] *= scale + + anorm = max(anorm,ctx.fabs(S[i]) + ctx.fabs(dwork[i])) + + if not isinstance(V, bool): + for i in range(n-2, -1, -1): # accumulation of right hand transformations + V[i+1,i+1] = 1 + + if dwork[i+1] != 0: + f = ctx.conj(rbeta[i]) + for j in range(i+1, n): + V[i,j] = A[i,j] * f + for j in range(i+1, n): + s = 0 + for k in range(i+1, n): + s += ctx.conj(A[i,k]) * V[j,k] + for k in range(i+1, n): + V[j,k] += s * V[i,k] + + for j in range(i+1,n): + V[j,i] = V[i,j] = 0 + + V[0,0] = 1 + + if m < n : minnm = m + else : minnm = n + + if calc_u: + for i in range(minnm-1, -1, -1): # accumulation of left hand transformations + g = S[i] + for j in range(i+1, n): + A[i,j] = 0 + if g != 0: + g = 1 / g + for j in range(i+1, n): + s = 0 + for k in range(i+1, m): + s += ctx.conj(A[k,i]) * A[k,j] + f = s * ctx.conj(lbeta[i]) + for k in range(i, m): + A[k,j] += f * A[k,i] + for j in range(i, m): + A[j,i] *= g + else: + for j in range(i, m): + A[j,i] = 0 + A[i,i] += 1 + + for k in range(n-1, -1, -1): + # diagonalization of the bidiagonal form: + # loop over singular values, and over allowed itations + + its = 0 + while 1: + its += 1 + flag = True + + for l in range(k, -1, -1): + nm = l - 1 + + if ctx.fabs(dwork[l]) + anorm == anorm: + flag = False + break + + if ctx.fabs(S[nm]) + anorm == anorm: + break + + if flag: + c = 0 + s = 1 + for i in range(l, k+1): + f = s * dwork[i] + dwork[i] *= c + if ctx.fabs(f) + anorm == anorm: + break + g = S[i] + h = ctx.hypot(f, g) + S[i] = h + h = 1 / h + c = g * h + s = -f * h + + if calc_u: + for j in range(m): + y = A[j,nm] + z = A[j,i] + A[j,nm]= y * c + z * s + A[j,i] = z * c - y * s + + z = S[k] + + if l == k: # convergence + if z < 0: # singular value is made nonnegative + S[k] = -z + if not isinstance(V, bool): + for j in range(n): + V[k,j] = -V[k,j] + break + + if its >= maxits: + raise RuntimeError("svd: no convergence to an eigenvalue after %d iterations" % its) + + x = S[l] # shift from bottom 2 by 2 minor + nm = k-1 + y = S[nm] + g = dwork[nm] + h = dwork[k] + f = ((y - z) * (y + z) + (g - h) * (g + h)) / (2 * h * y) + g = ctx.hypot(f, 1) + if f >=0: f = (( x - z) *( x + z) + h *((y / (f + g)) - h)) / x + else: f = (( x - z) *( x + z) + h *((y / (f - g)) - h)) / x + + c = s = 1 # next qt transformation + + for j in range(l, nm + 1): + g = dwork[j+1] + y = S[j+1] + h = s * g + g = c * g + z = ctx.hypot(f, h) + dwork[j] = z + c = f / z + s = h / z + f = x * c + g * s + g = g * c - x * s + h = y * s + y *= c + if not isinstance(V, bool): + for jj in range(n): + x = V[j ,jj] + z = V[j+1,jj] + V[j ,jj]= x * c + z * s + V[j+1,jj ]= z * c - x * s + z = ctx.hypot(f, h) + S[j] = z + if z != 0: # rotation can be arbitray if z=0 + z = 1 / z + c = f * z + s = h * z + f = c * g + s * y + x = c * y - s * g + if calc_u: + for jj in range(m): + y = A[jj,j ] + z = A[jj,j+1] + A[jj,j ]= y * c + z * s + A[jj,j+1 ]= z * c - y * s + + dwork[l] = 0 + dwork[k] = f + S[k] = x + + ########################## + + # Sort singular values into decreasing order (bubble-sort) + + for i in range(n): + imax = i + s = ctx.fabs(S[i]) # s is the current maximal element + + for j in range(i + 1, n): + c = ctx.fabs(S[j]) + if c > s: + s = c + imax = j + + if imax != i: + # swap singular values + + z = S[i] + S[i] = S[imax] + S[imax] = z + + if calc_u: + for j in range(m): + z = A[j,i] + A[j,i] = A[j,imax] + A[j,imax] = z + + if not isinstance(V, bool): + for j in range(n): + z = V[i,j] + V[i,j] = V[imax,j] + V[imax,j] = z + + return S + +################################################################################################## + +@defun +def svd_r(ctx, A, full_matrices = False, compute_uv = True, overwrite_a = False): + """ + This routine computes the singular value decomposition of a matrix A. + Given A, two orthogonal matrices U and V are calculated such that + + A = U S V and U' U = 1 and V V' = 1 + + where S is a suitable shaped matrix whose off-diagonal elements are zero. + Here ' denotes the transpose. The diagonal elements of S are the singular + values of A, i.e. the squareroots of the eigenvalues of A' A or A A'. + + input: + A : a real matrix of shape (m, n) + full_matrices : if true, U and V are of shape (m, m) and (n, n). + if false, U and V are of shape (m, min(m, n)) and (min(m, n), n). + compute_uv : if true, U and V are calculated. if false, only S is calculated. + overwrite_a : if true, allows modification of A which may improve + performance. if false, A is not modified. + + output: + U : an orthogonal matrix: U' U = 1. if full_matrices is true, U is of + shape (m, m). ortherwise it is of shape (m, min(m, n)). + + S : an array of length min(m, n) containing the singular values of A sorted by + decreasing magnitude. + + V : an orthogonal matrix: V V' = 1. if full_matrices is true, V is of + shape (n, n). ortherwise it is of shape (min(m, n), n). + + return value: + + S if compute_uv is false + (U, S, V) if compute_uv is true + + overview of the matrices: + + full_matrices true: + A : m*n + U : m*m U' U = 1 + S as matrix : m*n + V : n*n V V' = 1 + + full_matrices false: + A : m*n + U : m*min(n,m) U' U = 1 + S as matrix : min(m,n)*min(m,n) + V : min(m,n)*n V V' = 1 + + examples: + + >>> from mpmath import mp + >>> A = mp.matrix([[2, -2, -1], [3, 4, -2], [-2, -2, 0]]) + >>> S = mp.svd_r(A, compute_uv = False) + >>> print(S) + [6.0] + [3.0] + [1.0] + + >>> U, S, V = mp.svd_r(A) + >>> print(mp.chop(A - U * mp.diag(S) * V)) + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] + + + see also: svd, svd_c + """ + + m, n = A.rows, A.cols + + if not compute_uv: + if not overwrite_a: + A = A.copy() + S = svd_r_raw(ctx, A, V = False, calc_u = False) + S = S[:min(m,n)] + return S + + if full_matrices and n < m: + V = ctx.zeros(m, m) + A0 = ctx.zeros(m, m) + A0[:,:n] = A + S = svd_r_raw(ctx, A0, V, calc_u = True) + + S = S[:n] + V = V[:n,:n] + + return (A0, S, V) + else: + if not overwrite_a: + A = A.copy() + V = ctx.zeros(n, n) + S = svd_r_raw(ctx, A, V, calc_u = True) + + if n > m: + if full_matrices is False: + V = V[:m,:] + + S = S[:m] + A = A[:,:m] + + return (A, S, V) + +############################## + +@defun +def svd_c(ctx, A, full_matrices = False, compute_uv = True, overwrite_a = False): + """ + This routine computes the singular value decomposition of a matrix A. + Given A, two unitary matrices U and V are calculated such that + + A = U S V and U' U = 1 and V V' = 1 + + where S is a suitable shaped matrix whose off-diagonal elements are zero. + Here ' denotes the hermitian transpose (i.e. transposition and complex + conjugation). The diagonal elements of S are the singular values of A, + i.e. the squareroots of the eigenvalues of A' A or A A'. + + input: + A : a complex matrix of shape (m, n) + full_matrices : if true, U and V are of shape (m, m) and (n, n). + if false, U and V are of shape (m, min(m, n)) and (min(m, n), n). + compute_uv : if true, U and V are calculated. if false, only S is calculated. + overwrite_a : if true, allows modification of A which may improve + performance. if false, A is not modified. + + output: + U : an unitary matrix: U' U = 1. if full_matrices is true, U is of + shape (m, m). ortherwise it is of shape (m, min(m, n)). + + S : an array of length min(m, n) containing the singular values of A sorted by + decreasing magnitude. + + V : an unitary matrix: V V' = 1. if full_matrices is true, V is of + shape (n, n). ortherwise it is of shape (min(m, n), n). + + return value: + + S if compute_uv is false + (U, S, V) if compute_uv is true + + overview of the matrices: + + full_matrices true: + A : m*n + U : m*m U' U = 1 + S as matrix : m*n + V : n*n V V' = 1 + + full_matrices false: + A : m*n + U : m*min(n,m) U' U = 1 + S as matrix : min(m,n)*min(m,n) + V : min(m,n)*n V V' = 1 + + example: + >>> from mpmath import mp + >>> A = mp.matrix([[-2j, -1-3j, -2+2j], [2-2j, -1-3j, 1], [-3+1j,-2j,0]]) + >>> S = mp.svd_c(A, compute_uv = False) + >>> print(mp.chop(S - mp.matrix([mp.sqrt(34), mp.sqrt(15), mp.sqrt(6)]))) + [0.0] + [0.0] + [0.0] + + >>> U, S, V = mp.svd_c(A) + >>> print(mp.chop(A - U * mp.diag(S) * V)) + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] + + see also: svd, svd_r + """ + + m, n = A.rows, A.cols + + if not compute_uv: + if not overwrite_a: + A = A.copy() + S = svd_c_raw(ctx, A, V = False, calc_u = False) + S = S[:min(m,n)] + return S + + if full_matrices and n < m: + V = ctx.zeros(m, m) + A0 = ctx.zeros(m, m) + A0[:,:n] = A + S = svd_c_raw(ctx, A0, V, calc_u = True) + + S = S[:n] + V = V[:n,:n] + + return (A0, S, V) + else: + if not overwrite_a: + A = A.copy() + V = ctx.zeros(n, n) + S = svd_c_raw(ctx, A, V, calc_u = True) + + if n > m: + if full_matrices is False: + V = V[:m,:] + + S = S[:m] + A = A[:,:m] + + return (A, S, V) + +@defun +def svd(ctx, A, full_matrices = False, compute_uv = True, overwrite_a = False): + """ + "svd" is a unified interface for "svd_r" and "svd_c". Depending on + whether A is real or complex the appropriate function is called. + + This routine computes the singular value decomposition of a matrix A. + Given A, two orthogonal (A real) or unitary (A complex) matrices U and V + are calculated such that + + A = U S V and U' U = 1 and V V' = 1 + + where S is a suitable shaped matrix whose off-diagonal elements are zero. + Here ' denotes the hermitian transpose (i.e. transposition and complex + conjugation). The diagonal elements of S are the singular values of A, + i.e. the squareroots of the eigenvalues of A' A or A A'. + + input: + A : a real or complex matrix of shape (m, n) + full_matrices : if true, U and V are of shape (m, m) and (n, n). + if false, U and V are of shape (m, min(m, n)) and (min(m, n), n). + compute_uv : if true, U and V are calculated. if false, only S is calculated. + overwrite_a : if true, allows modification of A which may improve + performance. if false, A is not modified. + + output: + U : an orthogonal or unitary matrix: U' U = 1. if full_matrices is true, U is of + shape (m, m). ortherwise it is of shape (m, min(m, n)). + + S : an array of length min(m, n) containing the singular values of A sorted by + decreasing magnitude. + + V : an orthogonal or unitary matrix: V V' = 1. if full_matrices is true, V is of + shape (n, n). ortherwise it is of shape (min(m, n), n). + + return value: + + S if compute_uv is false + (U, S, V) if compute_uv is true + + overview of the matrices: + + full_matrices true: + A : m*n + U : m*m U' U = 1 + S as matrix : m*n + V : n*n V V' = 1 + + full_matrices false: + A : m*n + U : m*min(n,m) U' U = 1 + S as matrix : min(m,n)*min(m,n) + V : min(m,n)*n V V' = 1 + + examples: + + >>> from mpmath import mp + >>> A = mp.matrix([[2, -2, -1], [3, 4, -2], [-2, -2, 0]]) + >>> S = mp.svd(A, compute_uv = False) + >>> print(S) + [6.0] + [3.0] + [1.0] + + >>> U, S, V = mp.svd(A) + >>> print(mp.chop(A - U * mp.diag(S) * V)) + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] + + see also: svd_r, svd_c + """ + + iscomplex = any(type(x) is ctx.mpc for x in A) + + if iscomplex: + return ctx.svd_c(A, full_matrices = full_matrices, compute_uv = compute_uv, overwrite_a = overwrite_a) + else: + return ctx.svd_r(A, full_matrices = full_matrices, compute_uv = compute_uv, overwrite_a = overwrite_a) diff --git a/mpmath/matrices/linalg.py b/mpmath/matrices/linalg.py new file mode 100644 index 0000000..2dad1fd --- /dev/null +++ b/mpmath/matrices/linalg.py @@ -0,0 +1,964 @@ +""" +Linear algebra +-------------- + +Linear equations +................ + +Basic linear algebra is implemented; you can for example solve the linear +equation system:: + + x + 2*y = -10 + 3*x + 4*y = 10 + +using ``lu_solve``:: + + >>> from mpmath import matrix, lu_solve, residual, eps, fp, lu, iv + >>> A = matrix([[1, 2], [3, 4]]) + >>> b = matrix([-10, 10]) + >>> x = lu_solve(A, b) + >>> x + matrix( + [['30.0'], + ['-20.0']]) + +If you don't trust the result, use ``residual`` to calculate the residual ||A*x-b||:: + + >>> residual(A, x, b) + matrix( + [['3.46944695195361e-18'], + ['3.46944695195361e-18']]) + >>> str(eps) + '2.22044604925031e-16' + +As you can see, the solution is quite accurate. The error is caused by the +inaccuracy of the internal floating-point arithmetic. Though, it's even smaller +than the current machine epsilon, which basically means you can trust the +result. + +If you need more speed, use NumPy, or ``fp.lu_solve`` for a floating-point computation. + + >>> fp.lu_solve(A, b) + matrix(...) + +``lu_solve`` accepts overdetermined systems. It is usually not possible to solve +such systems, so the residual is minimized instead. Internally this is done +using Cholesky decomposition to compute a least squares approximation. This means +that that ``lu_solve`` will square the errors. If you can't afford this, use +``qr_solve`` instead. It is twice as slow but more accurate, and it calculates +the residual automatically. + + +Matrix factorization +.................... + +The function ``lu`` computes an explicit LU factorization of a matrix:: + + >>> P, L, U = lu(matrix([[0,2,3],[4,5,6],[7,8,9]])) + >>> print(P) + [0.0 0.0 1.0] + [1.0 0.0 0.0] + [0.0 1.0 0.0] + >>> print(L) + [ 1.0 0.0 0.0] + [ 0.0 1.0 0.0] + [0.571428571428571 0.214285714285714 1.0] + >>> print(U) + [7.0 8.0 9.0] + [0.0 2.0 3.0] + [0.0 0.0 0.214285714285714] + >>> print(P.T*L*U) + [0.0 2.0 3.0] + [4.0 5.0 6.0] + [7.0 8.0 9.0] + +Interval matrices +----------------- + +Matrices may contain interval elements. This allows one to perform +basic linear algebra operations such as matrix multiplication +and equation solving with rigorous error bounds:: + + >>> a = iv.matrix([['0.1','0.3','1.0'], + ... ['7.1','5.5','4.8'], + ... ['3.2','4.4','5.6']]) + >>> + >>> b = iv.matrix(['4','0.6','0.5']) + >>> c = iv.lu_solve(a, b) + >>> print(c) + [ [5.2582327113062393041, 5.2582327113062749951]] + [[-13.155049396267856583, -13.155049396267821167]] + [ [7.4206915477497212555, 7.4206915477497310922]] + >>> print(a*c) + [ [3.9999999999999866773, 4.0000000000000133227]] + [[0.59999999999972430942, 0.60000000000027142733]] + [[0.49999999999982236432, 0.50000000000018474111]] +""" + +# TODO: +# *implement high-level qr() +# *test unitvector +# *iterative solving + +from copy import copy + + +class LinearAlgebraMethods: + + def LU_decomp(ctx, A, overwrite=False, use_cache=True): + """ + LU-factorization of a n*n matrix using the Gauss algorithm. + Returns L and U in one matrix and the pivot indices. + + Use overwrite to specify whether A will be overwritten with L and U. + """ + if not A.rows == A.cols: + raise ValueError('need n*n matrix') + # get from cache if possible + if use_cache and isinstance(A, ctx.matrix) and A._LU: + return A._LU + if not overwrite: + orig = A + A = A.copy() + tol = ctx.absmin(ctx.mnorm(A,1) * ctx.eps) # each pivot element has to be bigger + n = A.rows + p = [None]*(n - 1) + for j in range(n - 1): + # pivoting, choose max(abs(reciprocal row sum)*abs(pivot element)) + biggest = 0 + for k in range(j, n): + s = ctx.fsum([ctx.absmin(A[k,l]) for l in range(j, n)]) + if ctx.absmin(s) <= tol: + raise ZeroDivisionError('matrix is numerically singular') + current = 1/s * ctx.absmin(A[k,j]) + if current > biggest: # TODO: what if equal? + biggest = current + p[j] = k + # without pivot LU fails + if p[j] is None: + raise ZeroDivisionError('matrix is numerically singular') + # swap rows according to p + ctx.swap_row(A, j, p[j]) + if ctx.absmin(A[j,j]) <= tol: + raise ZeroDivisionError('matrix is numerically singular') + # calculate elimination factors and add rows + for i in range(j + 1, n): + A[i,j] /= A[j,j] + for k in range(j + 1, n): + A[i,k] -= A[i,j]*A[j,k] + if p and ctx.absmin(A[n - 1,n - 1]) <= tol: + raise ZeroDivisionError('matrix is numerically singular') + # cache decomposition + if not overwrite and isinstance(orig, ctx.matrix): + orig._LU = (A, p) + return A, p + + def L_solve(ctx, L, b, p=None): + """ + Solve the lower part of a LU factorized matrix for y. + """ + if L.rows != L.cols: + raise RuntimeError("need n*n matrix") + n = L.rows + if len(b) != n: + raise ValueError("Value should be equal to n") + b = copy(b) + if p: # swap b according to p + for k in range(len(p)): + ctx.swap_row(b, k, p[k]) + # solve + for i in range(1, n): + for j in range(i): + b[i] -= L[i,j] * b[j] + return b + + def U_solve(ctx, U, y): + """ + Solve the upper part of a LU factorized matrix for x. + """ + if U.rows != U.cols: + raise RuntimeError("need n*n matrix") + n = U.rows + if len(y) != n: + raise ValueError("Value should be equal to n") + x = copy(y) + for i in range(n - 1, -1, -1): + for j in range(i + 1, n): + x[i] -= U[i,j] * x[j] + x[i] /= U[i,i] + return x + + def lu_solve(ctx, A, b, **kwargs): + """ + Ax = b => x + + Solve a determined or overdetermined linear equations system. + Fast LU decomposition is used, which is less accurate than QR decomposition + (especially for overdetermined systems), but it's twice as efficient. + Use qr_solve if you want more precision or have to solve a very ill- + conditioned system. + """ + prec = ctx.prec + try: + ctx.prec += 10 + # do not overwrite A nor b + A, b = ctx.matrix(A, **kwargs).copy(), ctx.matrix(b, **kwargs).copy() + if A.rows < A.cols: + raise ValueError('cannot solve underdetermined system') + if A.rows > A.cols: + # use least-squares method if overdetermined + # (this increases errors) + AH = A.H + A = AH * A + b = AH * b + x = ctx.cholesky_solve(A, b) + else: + # LU factorization + A, p = ctx.LU_decomp(A) + b = ctx.L_solve(A, b, p) + x = ctx.U_solve(A, b) + finally: + ctx.prec = prec + return x + + def improve_solution(ctx, A, x, b, maxsteps=1): + """ + Improve a solution to a linear equation system iteratively. + + This re-uses the LU decomposition and is thus cheap. + Usually 3 up to 4 iterations are giving the maximal improvement. + """ + if A.rows != A.cols: + raise RuntimeError("need n*n matrix") # TODO: really? + for _ in range(maxsteps): + r = ctx.residual(A, x, b) + if ctx.norm(r, 2) < 10*ctx.eps: + break + # this uses cached LU decomposition and is thus cheap + dx = ctx.lu_solve(A, -r) + x += dx + return x + + def lu(ctx, A): + """ + A -> P, L, U + + LU factorisation of a square matrix A. L is the lower, U the upper part. + P is the permutation matrix indicating the row swaps. + + P*A = L*U + + If you need efficiency, use the low-level method LU_decomp instead, it's + much more memory efficient. + """ + # get factorization + A, p = ctx.LU_decomp(A) + n = A.rows + L = ctx.matrix(n) + U = ctx.matrix(n) + for i in range(n): + for j in range(n): + if i > j: + L[i,j] = A[i,j] + elif i == j: + L[i,j] = 1 + U[i,j] = A[i,j] + else: + U[i,j] = A[i,j] + # calculate permutation matrix + P = ctx.eye(n) + for k in range(len(p)): + ctx.swap_row(P, k, p[k]) + return P, L, U + + def unitvector(ctx, n, i): + """ + Return the i-th n-dimensional unit vector. + """ + assert 0 < i <= n, 'this unit vector does not exist' + return [ctx.zero]*(i-1) + [ctx.one] + [ctx.zero]*(n-i) + + def inverse(ctx, A, **kwargs): + """ + Calculate the inverse of a matrix. + + If you want to solve an equation system Ax = b, it's recommended to use + solve(A, b) instead, it's about 3 times more efficient. + """ + prec = ctx.prec + try: + ctx.prec += 10 + # do not overwrite A + A = ctx.matrix(A, **kwargs).copy() + n = A.rows + # get LU factorisation + A, p = ctx.LU_decomp(A) + cols = [] + # calculate unit vectors and solve corresponding system to get columns + for i in range(1, n + 1): + e = ctx.unitvector(n, i) + y = ctx.L_solve(A, e, p) + cols.append(ctx.U_solve(A, y)) + # convert columns to matrix + inv = [] + for i in range(n): + row = [] + for j in range(n): + row.append(cols[j][i]) + inv.append(row) + result = ctx.matrix(inv, **kwargs) + finally: + ctx.prec = prec + return result + + def pinv(ctx, A, *, rtol=None): + """ + Returns Moore-Penrose pseudoinverse of the matrix `A`. + + This is a generalization of the matrix inverse that provides a unique + result even for singular and non-square matrices. In the overdetermined + case, it provides the least squares solution. In the underdetermined + case, it provides the minimum norm solution. + + The Moore-Penrose inverse of `A` is computed using its singular-value + decomposition. If `s` is the maximum singular value of `A`, then the + significance cut-off value is determined by `rtol * s`. Any singular + value below this value is assumed insignificant. + + **Arguments** + + A : The matrix to compute the pseudoinverse for. + rtol: Optional relative threshold term. + The default value is ctx.eps * max(A.rows, A.cols). + + **References** + + * [Wikipedia]_ https://en.wikipedia.org/wiki/Moore%E2%80%93Penrose_inverse + """ + U, S, V = ctx.svd(A) + + if not rtol: + rtol = max(A.rows, A.cols) * S[0] * ctx.eps + assert rtol > 0 + + Splus = ctx.zeros(V.cols, U.cols) + for ind, val in enumerate(S): + if val > rtol * max(S): + Splus[ind, ind] = 1/val + + v_conj_T = V.apply(lambda x: ctx.conj(x)).T + u_conj_T = U.apply(lambda x: ctx.conj(x)).T + return v_conj_T * Splus * u_conj_T + + def householder(ctx, A): + """ + (A|b) -> H, p, x, res + + (A|b) is the coefficient matrix with left hand side of an optionally + overdetermined linear equation system. + H and p contain all information about the transformation matrices. + x is the solution, res the residual. + """ + if not isinstance(A, ctx.matrix): + raise TypeError("A should be a type of ctx.matrix") + m = A.rows + n = A.cols + if m < n - 1: + raise RuntimeError("Columns should not be less than rows") + # calculate Householder matrix + p = [] + for j in range(n - 1): + s = ctx.fsum(abs(A[i,j])**2 for i in range(j, m)) + if not abs(s) > ctx.eps: + raise ValueError('matrix is numerically singular') + sign = ctx.sign(ctx.re(A[j,j])) + if sign == 0: + sign = ctx.one + p.append(-sign * ctx.sqrt(s)) + kappa = ctx.one / (s - p[j] * A[j,j]) + A[j,j] -= p[j] + for k in range(j+1, n): + y = ctx.fsum(ctx.conj(A[i,j]) * A[i,k] for i in range(j, m)) * kappa + for i in range(j, m): + A[i,k] -= A[i,j] * y + # solve Rx = c1 + x = [A[i,n - 1] for i in range(n - 1)] + for i in range(n - 2, -1, -1): + x[i] -= ctx.fsum(A[i,j] * x[j] for j in range(i + 1, n - 1)) + x[i] /= p[i] + # calculate residual + if not m == n - 1: + r = [A[m-1-i, n-1] for i in range(m - n + 1)] + else: + # determined system, residual should be 0 + r = [0]*m # maybe a bad idea, changing r[i] will change all elements + return A, p, x, r + + #def qr(ctx, A): + # """ + # A -> Q, R + # + # QR factorisation of a square matrix A using Householder decomposition. + # Q is orthogonal, this leads to very few numerical errors. + # + # A = Q*R + # """ + # H, p, x, res = householder(A) + # TODO: implement this + + def residual(ctx, A, x, b, **kwargs): + """ + Calculate the residual of a solution to a linear equation system. + + r = A*x - b for A*x = b + """ + oldprec = ctx.prec + try: + ctx.prec *= 2 + A, x, b = ctx.matrix(A, **kwargs), ctx.matrix(x, **kwargs), ctx.matrix(b, **kwargs) + return A*x - b + finally: + ctx.prec = oldprec + + def qr_solve(ctx, A, b, norm=None, **kwargs): + """ + Ax = b => x, ||Ax - b|| + + Solve a determined or overdetermined linear equations system and + calculate the norm of the residual (error). + QR decomposition using Householder factorization is applied, which gives very + accurate results even for ill-conditioned matrices. qr_solve is twice as + efficient. + """ + if norm is None: + norm = ctx.norm + prec = ctx.prec + try: + ctx.prec += 10 + # do not overwrite A nor b + A, b = ctx.matrix(A, **kwargs).copy(), ctx.matrix(b, **kwargs).copy() + if A.rows < A.cols: + raise ValueError('cannot solve underdetermined system') + H, p, x, r = ctx.householder(ctx.extend(A, b)) + res = ctx.norm(r) + # calculate residual "manually" for determined systems + if res == 0: + res = ctx.norm(ctx.residual(A, x, b)) + return ctx.matrix(x, **kwargs), res + finally: + ctx.prec = prec + + def cholesky(ctx, A, tol=None): + r""" + Cholesky decomposition of a symmetric positive-definite matrix `A`. + Returns a lower triangular matrix `L` such that `A = L \times L^T`. + More generally, for a complex Hermitian positive-definite matrix, + a Cholesky decomposition satisfying `A = L \times L^H` is returned. + + The Cholesky decomposition can be used to solve linear equation + systems twice as efficiently as LU decomposition, or to + test whether `A` is positive-definite. + + The optional parameter ``tol`` determines the tolerance for + verifying positive-definiteness. + + **Examples** + + Cholesky decomposition of a positive-definite symmetric matrix:: + + >>> from mpmath import (mp, eye, hilbert, nprint, cholesky, + ... chop, matrix) + >>> mp.dps = 25 + >>> mp.pretty = True + >>> A = eye(3) + hilbert(3) + >>> nprint(A) + [ 2.0 0.5 0.333333] + [ 0.5 1.33333 0.25] + [0.333333 0.25 1.2] + >>> L = cholesky(A) + >>> nprint(L) + [ 1.41421 0.0 0.0] + [0.353553 1.09924 0.0] + [0.235702 0.15162 1.05899] + >>> chop(A - L*L.T) + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] + + Cholesky decomposition of a Hermitian matrix:: + + >>> A = eye(3) + matrix([[0,0.25j,-0.5j],[-0.25j,0,0],[0.5j,0,0]]) + >>> L = cholesky(A) + >>> nprint(L) + [ 1.0 0.0 0.0] + [(0.0 - 0.25j) (0.968246 + 0.0j) 0.0] + [ (0.0 + 0.5j) (0.129099 + 0.0j) (0.856349 + 0.0j)] + >>> chop(A - L*L.H) + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] + + Attempted Cholesky decomposition of a matrix that is not positive + definite:: + + >>> A = -eye(3) + hilbert(3) + >>> L = cholesky(A) + Traceback (most recent call last): + ... + ValueError: matrix is not positive-definite + + **References** + + 1. [Wikipedia]_ https://en.wikipedia.org/wiki/Cholesky_decomposition + + """ + if not isinstance(A, ctx.matrix): + raise RuntimeError("A should be a type of ctx.matrix") + if not A.rows == A.cols: + raise ValueError('need n*n matrix') + if tol is None: + tol = +ctx.eps + n = A.rows + L = ctx.matrix(n) + for j in range(n): + c = ctx.re(A[j,j]) + if abs(c-A[j,j]) > tol: + raise ValueError('matrix is not Hermitian') + s = c - ctx.fsum((L[j,k] for k in range(j)), + absolute=True, squared=True) + if s < tol: + raise ValueError('matrix is not positive-definite') + L[j,j] = ctx.sqrt(s) + for i in range(j, n): + it1 = (L[i,k] for k in range(j)) + it2 = (L[j,k] for k in range(j)) + t = ctx.fdot(it1, it2, conjugate=True) + L[i,j] = (A[i,j] - t) / L[j,j] + return L + + def cholesky_solve(ctx, A, b, **kwargs): + """ + Ax = b => x + + Solve a symmetric positive-definite linear equation system. + This is twice as efficient as lu_solve. + + Typical use cases: + * A.T*A + * Hessian matrix + * differential equations + """ + prec = ctx.prec + try: + ctx.prec += 10 + # do not overwrite A nor b + A, b = ctx.matrix(A, **kwargs).copy(), ctx.matrix(b, **kwargs).copy() + if A.rows != A.cols: + raise ValueError('can only solve determined system') + # Cholesky factorization + L = ctx.cholesky(A) + # solve + n = L.rows + if len(b) != n: + raise ValueError("Value should be equal to n") + for i in range(n): + b[i] -= ctx.fsum(L[i,j] * b[j] for j in range(i)) + b[i] /= L[i,i] + x = ctx.U_solve(L.H, b) + return x + finally: + ctx.prec = prec + + def det(ctx, A): + """ + Calculate the determinant of a square matrix. + + The determinant is the normed, alternating n-linear from, + i.e. a multiplicative map for each matrix into the + field of numbers of its entries. + + **Examples** + + Determinant of identity is 1. + + >>> from mpmath import eye, matrix, det, mp + >>> mp.pretty = True + >>> A = eye(3) + >>> det(A) + 1.0 + + The determinant of a 0 by 0 matrix is 1 as the product of no factors + is by convention the multiplicative identity. + + >>> A = matrix(0, 0) + >>> det(A) + 1 + + But in general a matrix can have any number as its determinant. + + >>> A = matrix([[2, 6, 4],[3, 8, 6],[1, 1, 2]]) + >>> det(A) + 0 + + The determinant is vanishing if a matrix has no inverse. + + >>> A = matrix([[1, 3, 2],[0, 1, 0],[0, 0, 0]]) + >>> det(A) + 0 + + But, matrix has determinate different from zero full rank if and only is is equivalent to identity, + + >>> A = matrix([[1, 3, -2], [1, 9, -6], [1, 4, -3]]) + >>> det(A) + -2.0 + + i.e. has an inverse matrix. + + >>> B = matrix([[3, -1, 0], [3, 1, -4], [5, 1, -6]]) / 2 + >>> A*B == eye(3) + True + >>> det(B) + -0.5 + + Moreover, a matrix of integers has an inverse matrix of integers + if and only if the determinat is equal to either 1 or -1. + + >>> A = matrix([[1, 0, 1],[-2, 1, -2],[-4, 1, -5]]) + >>> B = matrix([[3, -1, 1],[2, 1, 0],[-2, 1, -1]]) + >>> A*B == eye(3) + True + >>> det(A), det(B) + (-1.0, -1.0) + + """ + prec = ctx.prec + try: + # do not overwrite A + A = ctx.matrix(A).copy() + # use LU factorization to calculate determinant + try: + R, p = ctx.LU_decomp(A) + except ZeroDivisionError: + return 0 + z = 1 + for i, e in enumerate(p): + if i != e: + z *= -1 + for i in range(A.rows): + z *= R[i,i] + return z + finally: + ctx.prec = prec + + def cond(ctx, A, norm=None): + """ + Calculate the condition number of a matrix using a specified matrix norm. + + The condition number estimates the sensitivity of a matrix to errors. + Example: small input errors for ill-conditioned coefficient matrices + alter the solution of the system dramatically. + + For ill-conditioned matrices it's recommended to use qr_solve() instead + of lu_solve(). This does not help with input errors however, it just avoids + to add additional errors. + + Definition: cond(A) = ||A|| * ||A**-1|| + """ + if norm is None: + norm = lambda x: ctx.mnorm(x,1) + return norm(A) * norm(ctx.inverse(A)) + + def lu_solve_mat(ctx, a, b): + """Solve a * x = b where a and b are matrices.""" + r = ctx.matrix(a.rows, b.cols) + for i in range(b.cols): + c = ctx.lu_solve(a, b.column(i)) + for j in range(len(c)): + r[j, i] = c[j] + return r + + def qr(ctx, A, mode = 'full', edps = 10): + """ + Compute a QR factorization $A = QR$ where + A is an m x n matrix of real or complex numbers where m >= n + + mode has following meanings: + (1) mode = 'raw' returns two matrixes (A, tau) in the + internal format used by LAPACK + (2) mode = 'skinny' returns the leading n columns of Q + and n rows of R + (3) Any other value returns the leading m columns of Q + and m rows of R + + edps is the increase in mp precision used for calculations + + **Examples** + + >>> from mpmath import mp, qr, matrix, chop, nprint, j + >>> mp.dps = 15 + >>> mp.pretty = True + >>> A = matrix([[1, 2], [3, 4], [1, 1]]) + >>> Q, R = qr(A) + >>> Q + [-0.301511344577764 0.861640436855329 0.408248290463863] + [-0.904534033733291 -0.123091490979333 -0.408248290463863] + [-0.301511344577764 -0.492365963917331 0.816496580927726] + >>> R + [-3.3166247903554 -4.52267016866645] + [ 0.0 0.738548945875996] + [ 0.0 0.0] + >>> Q * R + [1.0 2.0] + [3.0 4.0] + [1.0 1.0] + >>> chop(Q.T * Q) + [1.0 0.0 0.0] + [0.0 1.0 0.0] + [0.0 0.0 1.0] + >>> B = matrix([[1+0j, 2-3j], [3+j, 4+5j]]) + >>> Q, R = qr(B) + >>> nprint(Q) + [ (-0.301511 + 0.0j) (0.0695795 - 0.95092j)] + [(-0.904534 - 0.301511j) (-0.115966 + 0.278318j)] + >>> nprint(R) + [(-3.31662 + 0.0j) (-5.72872 - 2.41209j)] + [ 0.0 (3.91965 + 0.0j)] + >>> Q * R + [(1.0 + 0.0j) (2.0 - 3.0j)] + [(3.0 + 1.0j) (4.0 + 5.0j)] + >>> chop(Q.T * Q.conjugate()) + [1.0 0.0] + [0.0 1.0] + + """ + + # check values before continuing + assert isinstance(A, ctx.matrix) + m = A.rows + n = A.cols + assert n >= 0 + assert m >= n + assert edps >= 0 + + # check for complex data type + cmplx = any(type(x) is ctx.mpc for x in A) + + # temporarily increase the precision and initialize + with ctx.extradps(edps): + tau = ctx.matrix(n,1) + A = A.copy() + + # --------------- + # FACTOR MATRIX A + # --------------- + if cmplx: + one = ctx.mpc('1.0', '0.0') + zero = ctx.mpc('0.0', '0.0') + rzero = ctx.mpf('0.0') + + # main loop to factor A (complex) + for j in range(n): + alpha = A[j,j] + alphr = ctx.re(alpha) + alphi = ctx.im(alpha) + + if (m-j) >= 2: + xnorm = ctx.fsum( A[i,j]*ctx.conj(A[i,j]) for i in range(j+1, m) ) + xnorm = ctx.re( ctx.sqrt(xnorm) ) + else: + xnorm = rzero + + if (xnorm == rzero) and (alphi == rzero): + tau[j] = zero + continue + + if alphr < rzero: + beta = ctx.sqrt(alphr**2 + alphi**2 + xnorm**2) + else: + beta = -ctx.sqrt(alphr**2 + alphi**2 + xnorm**2) + + tau[j] = ctx.mpc( (beta - alphr) / beta, -alphi / beta ) + t = -ctx.conj(tau[j]) + za = one / (alpha - beta) + + for i in range(j+1, m): + A[i,j] *= za + + A[j,j] = one + for k in range(j+1, n): + y = ctx.fsum(A[i,j] * ctx.conj(A[i,k]) for i in range(j, m)) + temp = t * ctx.conj(y) + for i in range(j, m): + A[i,k] += A[i,j] * temp + + A[j,j] = ctx.mpc(beta, '0.0') + else: + one = ctx.mpf('1.0') + zero = ctx.mpf('0.0') + + # main loop to factor A (real) + for j in range(n): + alpha = A[j,j] + + if (m-j) > 2: + xnorm = ctx.fsum( (A[i,j])**2 for i in range(j+1, m) ) + xnorm = ctx.sqrt(xnorm) + elif (m-j) == 2: + xnorm = abs( A[m-1,j] ) + else: + xnorm = zero + + if xnorm == zero: + tau[j] = zero + continue + + if alpha < zero: + beta = ctx.sqrt(alpha**2 + xnorm**2) + else: + beta = -ctx.sqrt(alpha**2 + xnorm**2) + + tau[j] = (beta - alpha) / beta + t = -tau[j] + da = one / (alpha - beta) + + for i in range(j+1, m): + A[i,j] *= da + + A[j,j] = one + for k in range(j+1, n): + y = ctx.fsum( A[i,j] * A[i,k] for i in range(j, m) ) + temp = t * y + for i in range(j,m): + A[i,k] += A[i,j] * temp + + A[j,j] = beta + + # return factorization in same internal format as LAPACK + if (mode == 'raw') or (mode == 'RAW'): + return A, tau + + # ---------------------------------- + # FORM Q USING BACKWARD ACCUMULATION + # ---------------------------------- + + # form R before the values are overwritten + R = A.copy() + for j in range(n): + for i in range(j+1, m): + R[i,j] = zero + + # set the value of p (number of columns of Q to return) + p = m + if (mode == 'skinny') or (mode == 'SKINNY'): + p = n + + # add columns to A if needed and initialize + A.cols += (p-n) + for j in range(p): + A[j,j] = one + for i in range(j): + A[i,j] = zero + + # main loop to form Q + for j in range(n-1, -1, -1): + t = -tau[j] + A[j,j] += t + + for k in range(j+1, p): + if cmplx: + y = ctx.fsum(A[i,j] * ctx.conj(A[i,k]) for i in range(j+1, m)) + temp = t * ctx.conj(y) + else: + y = ctx.fsum(A[i,j] * A[i,k] for i in range(j+1, m)) + temp = t * y + A[j,k] = temp + for i in range(j+1, m): + A[i,k] += A[i,j] * temp + + for i in range(j+1, m): + A[i, j] *= t + + return A, R[0:p,0:n] + + # ------------------ + # END OF FUNCTION QR + # ------------------ + + def rank(ctx, A, iszerofunc=None): + """ + Calculate the rank of a matrix. + + This corresponds to the maximal + number of linear independent + columns (or rows equivalently). + + Rank is computed via singular value decomposition + by counting the number of non-zero singular values. + + The argument 'iszerofunc' allows for the provision + of a custom function to enable zero detection customization. + + **Examples** + + Rank of identity is same as its dimension. + + >>> from mpmath import eye, matrix, rank, zeros, qr + >>> A = eye(3) + >>> rank(A) + 3 + + But in general a matrix has rank less or equal of its dimension. + + >>> A = matrix([[2, 6, 4],[3, 8, 6],[1, 1, 2]]) + >>> rank(A) + 2 + + The rank is given by the number of non zero lines in an + equivalent triangular matrix. + + >>> R = matrix([[1, 3, 2],[0, 1, 0],[0, 0, 0]]) + >>> rank(A) + 2 + + The rank is zero if and only if the matrix is zero. + + >>> A = zeros(3) + >>> rank(A) + 0 + + The matrix has full rank if and only ist is equivalent to identity, + + >>> A = matrix([[1, 0, 1],[-2, 1, -2],[-4, 1, -5]]) + >>> rank(A) + 3 + + i.e. has an inverse matrix. + + >>> B = matrix([[3, -1, 1],[2, 1, 0],[-2, 1, -1]]) + >>> A*B == eye(3) + True + >>> rank(B) + 3 + + to handle numerical precision zero evaluation can be customized + by providing an `iszerofunc` + + >>> A = matrix([[2, 6, 4],[3, 8, 6],[1, 1, 2]]) + >>> _, R = qr(A) + >>> R + matrix( + [['-3.74165738677394', '-9.8886659507597', '-7.48331477354788'], + ['0.0', '1.79284291400159', '-2.548055495426e-26'], + ['0.0', '0.0', '4.35114889954169e-27']]) + + to take advantage of full precision provide a custom `iszerofunc` + + >>> iszerofunc = lambda x: not bool(x) + >>> rank(R, iszerofunc) + 3 + + """ + if iszerofunc is None: + iszerofunc = lambda v: ctx.absmin(v) < ctx.eps + + return sum(1 for v in ctx.svd_r(A, compute_uv=False) if not iszerofunc(v)) diff --git a/mpmath/matrices/matrices.py b/mpmath/matrices/matrices.py new file mode 100644 index 0000000..d36cb5c --- /dev/null +++ b/mpmath/matrices/matrices.py @@ -0,0 +1,1064 @@ +# TODO: interpret list as vectors (for multiplication) + +# pickling helper +def _make_matrix(x): + from mpmath import mp + return mp.matrix(x) + +class _matrix: + """ + Numerical matrix. + + Specify the dimensions or the data as a nested list. + Elements default to zero. + Use a flat list to create a column vector easily. + + The datatype of the context (mpf for mp, mpi for iv, and float for fp) is used to store the data. + + Creating matrices + ----------------- + + Matrices in mpmath are implemented using dictionaries. Only non-zero values + are stored, so it is cheap to represent sparse matrices. + + The most basic way to create one is to use the ``matrix`` class directly. + You can create an empty matrix specifying the dimensions: + + >>> from mpmath import (mp, matrix, randmatrix, nprint, ones, norm, + ... mnorm, inf) + >>> mp.dps = 15 + >>> matrix(2) + matrix( + [['0.0', '0.0'], + ['0.0', '0.0']]) + >>> matrix(2, 3) + matrix( + [['0.0', '0.0', '0.0'], + ['0.0', '0.0', '0.0']]) + + Calling ``matrix`` with one dimension will create a square matrix. + + To access the dimensions of a matrix, use the ``rows`` or ``cols`` keyword: + + >>> A = matrix(3, 2) + >>> A + matrix( + [['0.0', '0.0'], + ['0.0', '0.0'], + ['0.0', '0.0']]) + >>> A.rows + 3 + >>> A.cols + 2 + + You can also change the dimension of an existing matrix. This will set the + new elements to 0. If the new dimension is smaller than before, the + concerning elements are discarded: + + >>> A.rows = 2 + >>> A + matrix( + [['0.0', '0.0'], + ['0.0', '0.0']]) + + Internally ``mpmathify`` is used every time an element is set. This + is done using the syntax A[row,column], counting from 0: + + >>> A = matrix(2) + >>> A[1,1] = 1 + 1j + >>> A + matrix( + [['0.0', '0.0'], + ['0.0', mpc(real='1.0', imag='1.0')]]) + + A more comfortable way to create a matrix lets you use nested lists: + + >>> matrix([[1, 2], [3, 4]]) + matrix( + [['1.0', '2.0'], + ['3.0', '4.0']]) + + Convenient advanced functions are available for creating various standard + matrices, see ``zeros``, ``ones``, ``diag``, ``eye``, ``randmatrix`` and + ``hilbert``. + + Vectors + ....... + + Vectors may also be represented by the ``matrix`` class (with rows = 1 or cols = 1). + For vectors there are some things which make life easier. A column vector can + be created using a flat list, a row vectors using an almost flat nested list:: + + >>> matrix([1, 2, 3]) + matrix( + [['1.0'], + ['2.0'], + ['3.0']]) + >>> matrix([[1, 2, 3]]) + matrix( + [['1.0', '2.0', '3.0']]) + + Optionally vectors can be accessed like lists, using only a single index:: + + >>> x = matrix([1, 2, 3]) + >>> x[1] + mpf('2.0') + >>> x[1,0] + mpf('2.0') + + It is also possible to access matrices and vectors via negative index:: + >>> x = matrix([[1, 2], [3, 4]]) + >>> y = matrix([6, 7]) + >>> x[-1, -2] + mpf('3.0') + >>> y[-2] + mpf('6.0') + + Other + ..... + + Like you probably expected, matrices can be printed:: + + >>> print randmatrix(3) # doctest:+SKIP + [ 0.782963853573023 0.802057689719883 0.427895717335467] + [0.0541876859348597 0.708243266653103 0.615134039977379] + [ 0.856151514955773 0.544759264818486 0.686210904770947] + + Use ``nstr`` or ``nprint`` to specify the number of digits to print:: + + >>> nprint(randmatrix(5), 3) # doctest:+SKIP + [2.07e-1 1.66e-1 5.06e-1 1.89e-1 8.29e-1] + [6.62e-1 6.55e-1 4.47e-1 4.82e-1 2.06e-2] + [4.33e-1 7.75e-1 6.93e-2 2.86e-1 5.71e-1] + [1.01e-1 2.53e-1 6.13e-1 3.32e-1 2.59e-1] + [1.56e-1 7.27e-2 6.05e-1 6.67e-2 2.79e-1] + + As matrices are mutable, you will need to copy them sometimes:: + + >>> A = matrix(2) + >>> A + matrix( + [['0.0', '0.0'], + ['0.0', '0.0']]) + >>> B = A.copy() + >>> B[0,0] = 1 + >>> B + matrix( + [['1.0', '0.0'], + ['0.0', '0.0']]) + >>> A + matrix( + [['0.0', '0.0'], + ['0.0', '0.0']]) + + Finally, it is possible to convert a matrix to a nested list. This is very useful, + as most Python libraries involving matrices or arrays (namely NumPy or SymPy) + support this format:: + + >>> B.tolist() + [[mpf('1.0'), mpf('0.0')], [mpf('0.0'), mpf('0.0')]] + + + Matrix operations + ----------------- + + You can add and subtract matrices of compatible dimensions:: + + >>> A = matrix([[1, 2], [3, 4]]) + >>> B = matrix([[-2, 4], [5, 9]]) + >>> A + B + matrix( + [['-1.0', '6.0'], + ['8.0', '13.0']]) + >>> A - B + matrix( + [['3.0', '-2.0'], + ['-2.0', '-5.0']]) + >>> A + ones(3) + Traceback (most recent call last): + ... + ValueError: incompatible dimensions for addition + + It is possible to multiply or add matrices and scalars. In the latter case the + operation will be done element-wise:: + + >>> A * 2 + matrix( + [['2.0', '4.0'], + ['6.0', '8.0']]) + >>> A / 4 + matrix( + [['0.25', '0.5'], + ['0.75', '1.0']]) + >>> A - 1 + matrix( + [['0.0', '1.0'], + ['2.0', '3.0']]) + + Of course you can perform matrix multiplication, if the dimensions are + compatible, using ``@`` or ``*``. For clarity, ``@`` is + recommended (`PEP 465 `), because + the meaning of ``*`` is different in many other Python libraries such as NumPy. + + >>> A @ B + matrix( + [['8.0', '22.0'], + ['14.0', '48.0']]) + >>> A * B # same as A @ B + matrix( + [['8.0', '22.0'], + ['14.0', '48.0']]) + >>> matrix([[1, 2, 3]]) * matrix([[-6], [7], [-2]]) + matrix( + [['2.0']]) + + You can raise powers of square matrices:: + + >>> A**2 + matrix( + [['7.0', '10.0'], + ['15.0', '22.0']]) + + Negative powers will calculate the inverse:: + + >>> A**-1 + matrix( + [['-2.0', '1.0'], + ['1.5', '-0.5']]) + >>> A * A**-1 + matrix( + [['1.0', '1.0842021724855e-19'], + ['-2.16840434497101e-19', '1.0']]) + + + + Matrix transposition is straightforward:: + + >>> A = ones(2, 3) + >>> A + matrix( + [['1.0', '1.0', '1.0'], + ['1.0', '1.0', '1.0']]) + >>> A.T + matrix( + [['1.0', '1.0'], + ['1.0', '1.0'], + ['1.0', '1.0']]) + + Norms + ..... + + Sometimes you need to know how "large" a matrix or vector is. Due to their + multidimensional nature it's not possible to compare them, but there are + several functions to map a matrix or a vector to a positive real number, the + so called norms. + + For vectors the p-norm is intended, usually the 1-, the 2- and the oo-norm are + used. + + >>> x = matrix([-10, 2, 100]) + >>> norm(x, 1) + mpf('112.0') + >>> norm(x, 2) + mpf('100.5186549850325') + >>> norm(x, inf) + mpf('100.0') + + Please note that the 2-norm is the most used one, though it is more expensive + to calculate than the 1- or oo-norm. + + It is possible to generalize some vector norms to matrix norm:: + + >>> A = matrix([[1, -1000], [100, 50]]) + >>> mnorm(A, 1) + mpf('1050.0') + >>> mnorm(A, inf) + mpf('1001.0') + >>> mnorm(A, 'F') + mpf('1006.2310867787777') + + The last norm (the "Frobenius-norm") is an approximation for the 2-norm, which + is hard to calculate and not available. The Frobenius-norm lacks some + mathematical properties you might expect from a norm. + """ + + def __init__(self, *args, **kwargs): + self._data = {} + # LU decompostion cache, this is useful when solving the same system + # multiple times, when calculating the inverse and when calculating the + # determinant + self._LU = None + if isinstance(args[0], (list, tuple)): + if not args[0]: + self._rows = 0 + self._cols = 0 + elif isinstance(args[0][0], (list, tuple)): + # interpret nested list as matrix + A = args[0] + self._rows = len(A) + self._cols = len(A[0]) + for i, row in enumerate(A): + for j, a in enumerate(row): + # note: this will call __setitem__ which will call self.ctx.convert() to convert the datatype. + self[i, j] = a + else: + # interpret list as row vector + v = args[0] + self._rows = len(v) + self._cols = 1 + for i, e in enumerate(v): + self[i, 0] = e + elif isinstance(args[0], int): + # create empty matrix of given dimensions + if len(args) == 1: + if args[0] < 0: + raise ValueError("expected non-negative int") + self._rows = self._cols = args[0] + else: + if not isinstance(args[1], int): + raise TypeError("expected int") + if args[0] < 0 or args[1] < 0: + raise ValueError("expected non-negative int") + self._rows = args[0] + self._cols = args[1] + elif isinstance(args[0], _matrix): + A = args[0] + self._rows = A._rows + self._cols = A._cols + for i in range(A._rows): + for j in range(A._cols): + self[i, j] = A[i, j] + elif hasattr(args[0], 'tolist'): + A = self.ctx.matrix(args[0].tolist()) + self._data = A._data + self._rows = A._rows + self._cols = A._cols + else: + raise TypeError('could not interpret given arguments') + + def apply(self, f): + """ + Return a copy of self with the function `f` applied elementwise. + """ + new = self.ctx.matrix(self._rows, self._cols) + for i in range(self._rows): + for j in range(self._cols): + new[i,j] = f(self[i,j]) + return new + + def __nstr__(self, n=None, **kwargs): + # Build table of string representations of the elements + res = [] + # Track per-column max lengths for pretty alignment + maxlen = [0] * self.cols + for i in range(self.rows): + res.append([]) + for j in range(self.cols): + if n: + string = self.ctx.nstr(self[i,j], n, **kwargs) + else: + string = str(self[i,j]) + res[-1].append(string) + maxlen[j] = max(len(string), maxlen[j]) + # Patch strings together + rowsep = '\n' + colsep = ' ' + for i, row in enumerate(res): + for j, elem in enumerate(row): + # Pad each element up to maxlen so the columns line up + row[j] = elem.rjust(maxlen[j]) + res[i] = "[" + colsep.join(row) + "]" + return rowsep.join(res) if self.rows or self.cols else '' + + def __str__(self): + return self.__nstr__() + + def _toliststr(self): + """ + Create a list string from a matrix. + + If avoid_type: avoid multiple 'mpf's. + """ + # XXX: should be something like self.ctx._types + typ = self.ctx.mpf + s = '[' + for i in range(self._rows): + s += '[' + for j in range(self._cols): + if not isinstance(self[i,j], typ): + a = repr(self[i,j]) + else: + a = "'" + str(self[i,j]) + "'" + s += a + ', ' + if s[-1] != '[': + s = s[:-2] + s += '],\n ' + if s[-1] != '[': + s = s[:-3] + s += ']' + return s + + def tolist(self): + """ + Convert the matrix to a nested list. + """ + return [[self[i,j] for j in range(self._cols)] for i in range(self._rows)] + + def __repr__(self): + if self.ctx.pretty: + return self.__str__() + s = 'matrix(\n' + s += self._toliststr() + ')' + return s + + def _get_element(self, key): + ''' + Fast extraction of the i,j element from the matrix + This function is for private use only because is unsafe: + 1. Does not check on the value of key it expects key to be a integer tuple (i,j) + 2. Does not check bounds + ''' + if key in self._data: + return self._data[key] + else: + return self.ctx.zero + + def _set_element(self, key, value): + ''' + Fast assignment of the i,j element in the matrix + This function is unsafe: + 1. Does not check on the value of key it expects key to be a integer tuple (i,j) + 2. Does not check bounds + 3. Does not check the value type + 4. Does not reset the LU cache + ''' + if value: # only store non-zeros + self._data[key] = value + elif key in self._data: + del self._data[key] + + + def __getitem__(self, key): + ''' + Getitem function for mp matrix class with slice index enabled + it allows the following assingments + scalar to a slice of the matrix + B = A[:,2:6] + ''' + # Convert vector to matrix indexing + if isinstance(key, int) or isinstance(key,slice): + # only sufficent for vectors + if self._rows == 1: + key = (0, key) + elif self._cols == 1: + key = (key, 0) + else: + raise IndexError('insufficient indices for matrix') + + if isinstance(key[0],slice) or isinstance(key[1],slice): + + #Rows + if isinstance(key[0],slice): + #Check bounds + if (key[0].start is None or key[0].start >= 0) and \ + (key[0].stop is None or key[0].stop <= self._rows+1): + # Generate indices + rows = range(*key[0].indices(self._rows)) + else: + raise IndexError('Row index out of bounds') + else: + # Single row + if key[0] >= self._rows: + raise IndexError('Row index out of bounds') + rows = [key[0]] + + if(key[0] < 0 and key[0] >= - self._rows): + rows[0] += self._rows + + # Columns + if isinstance(key[1],slice): + # Check bounds + if (key[1].start is None or key[1].start >= 0) and \ + (key[1].stop is None or key[1].stop <= self._cols+1): + # Generate indices + columns = range(*key[1].indices(self._cols)) + else: + raise IndexError('Column index out of bounds') + + else: + # Single column + if key[1] >= self._cols: + raise IndexError('Column index out of bounds') + columns = [key[1]] + + if(key[1] < 0 and key[1] >= - self._cols): + columns[0] += self._cols + + # Create matrix slice + m = self.ctx.matrix(len(rows),len(columns)) + + # Assign elements to the output matrix + for i,x in enumerate(rows): + for j,y in enumerate(columns): + m._set_element((i,j),self._get_element((x,y))) + + return m + + else: + row, col = key + + if row < 0: + if -row <= self._rows: + row = self._rows + row + else: + raise IndexError('matrix index out of range') + + if col < 0: + if -col <= self._cols: + col = self._cols + col + else: + raise IndexError('matrix index out of range') + + key = (row, col) + + # single element extraction + if key[0] >= self._rows or key[1] >= self._cols: + raise IndexError('matrix index out of range') + if key in self._data: + return self._data[key] + else: + return self.ctx.zero + + def __setitem__(self, key, value): + # setitem function for mp matrix class with slice index enabled + # it allows the following assingments + # scalar to a slice of the matrix + # A[:,2:6] = 2.5 + # submatrix to matrix (the value matrix should be the same size as the slice size) + # A[3,:] = B where A is n x m and B is n x 1 + # Convert vector to matrix indexing + if isinstance(key, int) or isinstance(key,slice): + # only sufficent for vectors + if self._rows == 1: + key = (0, key) + elif self._cols == 1: + key = (key, 0) + else: + raise IndexError('insufficient indices for matrix') + # Slice indexing + if isinstance(key[0],slice) or isinstance(key[1],slice): + # Rows + if isinstance(key[0],slice): + # Check bounds + if (key[0].start is None or key[0].start >= 0) and \ + (key[0].stop is None or key[0].stop <= self._rows+1): + # generate row indices + rows = range(*key[0].indices(self._rows)) + else: + raise IndexError('Row index out of bounds') + else: + # Single row + rows = [key[0]] + + if(key[0] < 0 and key[0] >= - self._rows): + rows[0] += self._rows + # Columns + if isinstance(key[1],slice): + # Check bounds + if (key[1].start is None or key[1].start >= 0) and \ + (key[1].stop is None or key[1].stop <= self._cols+1): + # Generate column indices + columns = range(*key[1].indices(self._cols)) + else: + raise IndexError('Column index out of bounds') + else: + # Single column + columns = [key[1]] + + if(key[1] < 0 and key[1] >= - self._cols): + columns[0] += self._cols + # Assign slice with a scalar + if isinstance(value,self.ctx.matrix): + # Assign elements to matrix if input and output dimensions match + if len(rows) == value.rows and len(columns) == value.cols: + for i,x in enumerate(rows): + for j,y in enumerate(columns): + self._set_element((x,y), value._get_element((i,j))) + else: + raise ValueError('Dimensions do not match') + else: + # Assign slice with scalars + value = self.ctx.convert(value) + for i in rows: + for j in columns: + self._set_element((i,j), value) + else: + # Single element assingment + # Check bounds + row, col = key + + if row < 0: + if -row <= self._rows: + row = self._rows + row + else: + raise IndexError('matrix index out of range') + + if col < 0: + if -col <= self._cols: + col = self._cols + col + else: + raise IndexError('matrix index out of range') + + key = (row, col) + + if key[0] >= self._rows or key[1] >= self._cols: + raise IndexError('matrix index out of range') + # Convert and store value + value = self.ctx.convert(value) + if value: # only store non-zeros + self._data[key] = value + elif key in self._data: + del self._data[key] + + if self._LU: + self._LU = None + return + + def __iter__(self): + for i in range(self._rows): + for j in range(self._cols): + yield self[i,j] + + def __mul__(self, other): + if isinstance(other, self.ctx.matrix): + # dot multiplication + if self._cols != other._rows: + raise ValueError('dimensions not compatible for multiplication') + new = self.ctx.matrix(self._rows, other._cols) + for i in range(self._rows): + for j in range(other._cols): + new[i, j] = self.ctx.fdot((self._data[i,k], other._data[k,j]) + for k in range(other._rows) if (i,k) in self._data and (k,j) in other._data) + return new + else: + # try scalar multiplication + new = self.ctx.matrix(self._rows, self._cols) + for i in range(self._rows): + for j in range(self._cols): + new[i, j] = other * self[i, j] + return new + + def __matmul__(self, other): + return self.__mul__(other) + + def __rmul__(self, other): + # assume other is scalar and thus commutative + if isinstance(other, self.ctx.matrix): + raise TypeError("other should not be type of ctx.matrix") + return self.__mul__(other) + + def __pow__(self, other): + # avoid cyclic import problems + #from linalg import inverse + if not isinstance(other, int): + raise ValueError('only integer exponents are supported') + if not self._rows == self._cols: + raise ValueError('only powers of square matrices are defined') + n = other + if n == 0: + return self.ctx.eye(self._rows) + if n < 0: + n = -n + neg = True + else: + neg = False + i = n + y = 1 + z = self.copy() + while i != 0: + if i % 2 == 1: + y = y * z + z = z*z + i = i // 2 + if neg: + y = self.ctx.inverse(y) + return y + + def __truediv__(self, other): + # assume other is scalar and do element-wise divison + assert not isinstance(other, self.ctx.matrix) + new = self.ctx.matrix(self._rows, self._cols) + for i in range(self._rows): + for j in range(self._cols): + new[i,j] = self[i,j] / other + return new + + def __add__(self, other): + if isinstance(other, self.ctx.matrix): + if not (self._rows == other._rows and self._cols == other._cols): + raise ValueError('incompatible dimensions for addition') + new = self.ctx.matrix(self._rows, self._cols) + for i in range(self._rows): + for j in range(self._cols): + new[i,j] = self[i,j] + other[i,j] + return new + else: + # assume other is scalar and add element-wise + new = self.ctx.matrix(self._rows, self._cols) + for i in range(self._rows): + for j in range(self._cols): + new[i,j] += self[i,j] + other + return new + + def __radd__(self, other): + return self.__add__(other) + + def __sub__(self, other): + if isinstance(other, self.ctx.matrix) and not (self._rows == other._rows + and self._cols == other._cols): + raise ValueError('incompatible dimensions for subtraction') + return self.__add__(other * (-1)) + + def __pos__(self): + """ + +M returns a copy of M, rounded to current working precision. + """ + return (+1) * self + + def __neg__(self): + return (-1) * self + + def __rsub__(self, other): + return -self + other + + def __eq__(self, other): + try: + return (self._rows == other._rows and self._cols == other._cols + and self._data == other._data) + except AttributeError: + return NotImplemented + + def __len__(self): + if self.rows == 1: + return self.cols + elif self.cols == 1: + return self.rows + else: + return self.rows # do it like numpy + + @property + def rows(self): + """Number of rows.""" + return self._rows + + @rows.setter + def rows(self, value): + for key in self._data.copy(): + if key[0] >= value: + del self._data[key] + self._rows = value + + @property + def cols(self): + """Number of columns.""" + return self._cols + + @cols.setter + def cols(self, value): + for key in self._data.copy(): + if key[1] >= value: + del self._data[key] + self._cols = value + + def transpose(self): + new = self.ctx.matrix(self._cols, self._rows) + for i in range(self._rows): + for j in range(self._cols): + new[j,i] = self[i,j] + return new + + T = property(transpose) + + def conjugate(self): + return self.apply(self.ctx.conj) + + def transpose_conj(self): + return self.conjugate().transpose() + + H = property(transpose_conj) + + def copy(self): + new = self.ctx.matrix(self._rows, self._cols) + new._data = self._data.copy() + return new + __copy__ = copy + + def __reduce__(self): + return _make_matrix, (self.tolist(),) + + def __array__(self, dtype=None, copy=None): + if copy is not None and not copy: + raise ValueError("`copy=False` isn't supported. A copy is always created.") + from numpy import empty + r = empty((self.rows, self.cols), dtype=dtype) + for i in range(self.rows): + for j in range(self.cols): + r[i, j] = self[i, j] + return r + + def column(self, n): + m = self.ctx.matrix(self.rows, 1) + for i in range(self.rows): + m[i] = self[i,n] + return m + +class MatrixMethods: + + def __init__(ctx): + # XXX: subclass + ctx.matrix = type('matrix', (_matrix,), {}) + ctx.matrix.ctx = ctx + ctx.matrix.convert = ctx.convert + + def eye(ctx, n, **kwargs): + """ + Create square identity matrix n x n. + """ + A = ctx.matrix(n, **kwargs) + for i in range(n): + A[i,i] = 1 + return A + + def diag(ctx, diagonal, **kwargs): + """ + Create square diagonal matrix using given list. + + Example: + >>> from mpmath import diag, mp + >>> diag([1, 2, 3]) + matrix( + [['1.0', '0.0', '0.0'], + ['0.0', '2.0', '0.0'], + ['0.0', '0.0', '3.0']]) + """ + A = ctx.matrix(len(diagonal), **kwargs) + for i in range(len(diagonal)): + A[i,i] = diagonal[i] + return A + + def zeros(ctx, *args, **kwargs): + """ + Create matrix m x n filled with zeros. + One given dimension will create square matrix n x n. + + Example: + >>> from mpmath import zeros, mp + >>> zeros(2) + matrix( + [['0.0', '0.0'], + ['0.0', '0.0']]) + """ + if len(args) == 1: + m = n = args[0] + elif len(args) == 2: + m = args[0] + n = args[1] + else: + raise TypeError('zeros expected at most 2 arguments, got %i' % len(args)) + A = ctx.matrix(m, n, **kwargs) + for i in range(m): + for j in range(n): + A[i,j] = 0 + return A + + def ones(ctx, *args, **kwargs): + """ + Create matrix m x n filled with ones. + One given dimension will create square matrix n x n. + + Example: + >>> from mpmath import ones, mp + >>> ones(2) + matrix( + [['1.0', '1.0'], + ['1.0', '1.0']]) + """ + if len(args) == 1: + m = n = args[0] + elif len(args) == 2: + m = args[0] + n = args[1] + else: + raise TypeError('ones expected at most 2 arguments, got %i' % len(args)) + A = ctx.matrix(m, n, **kwargs) + for i in range(m): + for j in range(n): + A[i,j] = 1 + return A + + def hilbert(ctx, m, n=None): + """ + Create (pseudo) hilbert matrix m x n. + One given dimension will create hilbert matrix n x n. + + The matrix is very ill-conditioned and symmetric, positive definite if + square. + """ + if n is None: + n = m + A = ctx.matrix(m, n) + for i in range(m): + for j in range(n): + A[i,j] = ctx.one / (i + j + 1) + return A + + def randmatrix(ctx, m, n=None, min=0, max=1, **kwargs): + """ + Create a random m x n matrix. + + All values are >= min and >> from mpmath import randmatrix + >>> randmatrix(2) # doctest:+SKIP + matrix( + [['0.53491598236191806', '0.57195669543302752'], + ['0.85589992269513615', '0.82444367501382143']]) + """ + if not n: + n = m + A = ctx.matrix(m, n, **kwargs) + for i in range(m): + for j in range(n): + A[i,j] = ctx.rand() * (max - min) + min + return A + + def swap_row(ctx, A, i, j): + """ + Swap row i with row j. + """ + if i == j: + return + if isinstance(A, ctx.matrix): + for k in range(A.cols): + A[i,k], A[j,k] = A[j,k], A[i,k] + elif isinstance(A, list): + A[i], A[j] = A[j], A[i] + else: + raise TypeError('could not interpret type') + + def extend(ctx, A, b): + """ + Extend matrix A with column b and return result. + """ + if not isinstance(A, ctx.matrix): + raise TypeError("A should be a type of ctx.matrix") + if A.rows != len(b): + raise ValueError("Value should be equal to len(b)") + A = A.copy() + A.cols += 1 + for i in range(A.rows): + A[i, A.cols-1] = b[i] + return A + + def norm(ctx, x, p=2): + r""" + Gives the entrywise `p`-norm of an iterable *x*, i.e. the vector norm + `\left(\sum_k |x_k|^p\right)^{1/p}`, for any given `1 \le p \le \infty`. + + Special cases: + + If *x* is not iterable, this just returns ``absmax(x)``. + + ``p=1`` gives the sum of absolute values. + + ``p=2`` is the standard Euclidean vector norm. + + ``p=inf`` gives the magnitude of the largest element. + + For *x* a matrix, ``p=2`` is the Frobenius norm. + For operator matrix norms, use :func:`~mpmath.mnorm` instead. + + You can use the string 'inf' as well as float('inf') or mpf('inf') + to specify the infinity norm. + + **Examples** + + >>> from mpmath import matrix, norm, inf + >>> x = matrix([-10, 2, 100]) + >>> norm(x, 1) + mpf('112.0') + >>> norm(x, 2) + mpf('100.5186549850325') + >>> norm(x, inf) + mpf('100.0') + + """ + try: + iter(x) + except TypeError: + return ctx.absmax(x) + if type(p) is not int: + p = ctx.convert(p) + if p == ctx.inf: + return max(ctx.absmax(i) for i in x) + elif p == 1: + return ctx.fsum(x, absolute=1) + elif p == 2: + return ctx.sqrt(ctx.fsum(x, absolute=1, squared=1)) + elif p > 1: + return ctx.nthroot(ctx.fsum(abs(i)**p for i in x), p) + else: + raise ValueError('p has to be >= 1') + + def mnorm(ctx, A, p=1): + r""" + Gives the matrix (operator) `p`-norm of A. Currently ``p=1`` and ``p=inf`` + are supported: + + ``p=1`` gives the 1-norm (maximal column sum) + + ``p=inf`` gives the `\infty`-norm (maximal row sum). + You can use the string 'inf' as well as float('inf') or mpf('inf') + + ``p=2`` (not implemented) for a square matrix is the usual spectral + matrix norm, i.e. the largest singular value. + + ``p='f'`` (or 'F', 'fro', 'Frobenius, 'frobenius') gives the + Frobenius norm, which is the elementwise 2-norm. The Frobenius norm is an + approximation of the spectral norm and satisfies + + .. math :: + + \frac{1}{\sqrt{\mathrm{rank}(A)}} \|A\|_F \le \|A\|_2 \le \|A\|_F + + The Frobenius norm lacks some mathematical properties that might + be expected of a norm. + + For general elementwise `p`-norms, use :func:`~mpmath.norm` instead. + + **Examples** + + >>> from mpmath import matrix, mnorm, inf + >>> A = matrix([[1, -1000], [100, 50]]) + >>> mnorm(A, 1) + mpf('1050.0') + >>> mnorm(A, inf) + mpf('1001.0') + >>> mnorm(A, 'F') + mpf('1006.2310867787777') + + """ + A = ctx.matrix(A) + if type(p) is not int: + if type(p) is str and 'frobenius'.startswith(p.lower()): + return ctx.norm(A, 2) + p = ctx.convert(p) + m, n = A.rows, A.cols + if p == 1: + return max((ctx.fsum((A[i,j] for i in range(m)), absolute=1) for j in range(n)), default=0) + elif p == ctx.inf: + return max((ctx.fsum((A[i,j] for j in range(n)), absolute=1) for i in range(m)), default=0) + else: + raise NotImplementedError("matrix p-norm for arbitrary p") diff --git a/mpmath/tests/test_basic_ops.py b/mpmath/tests/test_basic_ops.py new file mode 100644 index 0000000..bf2f444 --- /dev/null +++ b/mpmath/tests/test_basic_ops.py @@ -0,0 +1,785 @@ +import collections +import decimal +import math +import operator +import random +import sys +from concurrent.futures import ThreadPoolExecutor + +import pytest +from hypothesis import example, given, settings +from hypothesis import strategies as st + +import mpmath +from mpmath import (ceil, fadd, fdiv, floor, fmul, fneg, fp, frac, fsub, inf, + isinf, isint, isnan, isnormal, iv, monitor, mp, mpc, mpf, + mpi, nan, ninf, nint, nint_distance, nstr, pi, rand, + workprec) +from mpmath.libmp import (MPZ, finf, fnan, fninf, fnone, fone, from_float, + from_int, from_str, mpf_add, mpf_mul, mpf_sub, + round_down, round_nearest, round_up, to_float, + to_int, to_man_exp) +from mpmath.libmp.backend import MPQ +from mpmath.libmp.libintmath import isprime, jacobi_symbol + + +def test_type_compare(): + assert mpf(2) == mpc(2,0) + assert mpf(0) == mpc(0) + assert mpf(2) != mpc(2, 0.00001) + assert mpf(2) == 2.0 + assert mpf(2) != 3.0 + assert mpf(2) == 2 + assert mpf(2) != '2.0' + assert mpc(2) != '2.0' + +def test_add(): + assert mpf(2.5) + mpf(3) == 5.5 + assert mpf(2.5) + 3 == 5.5 + assert mpf(2.5) + 3.0 == 5.5 + assert 3 + mpf(2.5) == 5.5 + assert 3.0 + mpf(2.5) == 5.5 + assert (3+0j) + mpf(2.5) == 5.5 + assert mpc(2.5) + mpf(3) == 5.5 + assert mpc(2.5) + 3 == 5.5 + assert mpc(2.5) + 3.0 == 5.5 + assert mpc(2.5) + (3+0j) == 5.5 + assert 3 + mpc(2.5) == 5.5 + assert 3.0 + mpc(2.5) == 5.5 + assert (3+0j) + mpc(2.5) == 5.5 + +def test_sub(): + assert mpf(2.5) - mpf(3) == -0.5 + assert mpf(2.5) - 3 == -0.5 + assert mpf(2.5) - 3.0 == -0.5 + assert 3 - mpf(2.5) == 0.5 + assert 3.0 - mpf(2.5) == 0.5 + assert (3+0j) - mpf(2.5) == 0.5 + assert mpc(2.5) - mpf(3) == -0.5 + assert mpc(2.5) - 3 == -0.5 + assert mpc(2.5) - 3.0 == -0.5 + assert mpc(2.5) - (3+0j) == -0.5 + assert 3 - mpc(2.5) == 0.5 + assert 3.0 - mpc(2.5) == 0.5 + assert (3+0j) - mpc(2.5) == 0.5 + +def test_mul(): + assert mpf(2.5) * mpf(3) == 7.5 + assert mpf(2.5) * 3 == 7.5 + assert mpf(2.5) * 3.0 == 7.5 + assert 3 * mpf(2.5) == 7.5 + assert 3.0 * mpf(2.5) == 7.5 + assert (3+0j) * mpf(2.5) == 7.5 + assert mpc(2.5) * mpf(3) == 7.5 + assert mpc(2.5) * 3 == 7.5 + assert mpc(2.5) * 3.0 == 7.5 + assert mpc(2.5) * (3+0j) == 7.5 + assert 3 * mpc(2.5) == 7.5 + assert 3.0 * mpc(2.5) == 7.5 + assert (3+0j) * mpc(2.5) == 7.5 + +def test_div(): + assert mpf(6) / mpf(3) == 2.0 + assert mpf(6) / 3 == 2.0 + assert mpf(6) / 3.0 == 2.0 + assert 6 / mpf(3) == 2.0 + assert 6.0 / mpf(3) == 2.0 + assert (6+0j) / mpf(3.0) == 2.0 + assert mpc(6) / mpf(3) == 2.0 + assert mpc(6) / 3 == 2.0 + assert mpc(6) / 3.0 == 2.0 + assert mpc(6) / (3+0j) == 2.0 + assert 6 / mpc(3) == 2.0 + assert 6.0 / mpc(3) == 2.0 + assert (6+0j) / mpc(3) == 2.0 + assert 1/mpc(inf, 1) == 0.0 + assert (1+1j)/mpc(2, inf) == 0.0 + assert mpc(inf, 1)**-1 == 0.0 + +def test_mod(): + assert mpf(3.1) % decimal.Decimal(5.3) == mpf('3.1000000000000001') + assert mpf(2.53) % inf == mpf(2.53) + assert mpf(2.53) % ninf == mpf(2.53) + +def test_floordiv(): + assert mpf(30.21) // mpf(2.53) == mpf(11) + +def test_divmod(): + assert divmod(mpf(30.21), mpf(2.53)) == (mpf(11), mpf(2.380000000000003)) + +def test_pow(): + assert mpf(6) ** mpf(3) == 216.0 + assert mpf(6) ** 3 == 216.0 + assert mpf(6) ** 3.0 == 216.0 + assert 6 ** mpf(3) == 216.0 + assert 6.0 ** mpf(3) == 216.0 + assert (6+0j) ** mpf(3.0) == 216.0 + assert mpc(6) ** mpf(3) == 216.0 + assert mpc(6) ** 3 == 216.0 + assert mpc(6) ** 3.0 == 216.0 + assert mpc(6) ** (3+0j) == 216.0 + assert 6 ** mpc(3) == 216.0 + assert 6.0 ** mpc(3) == 216.0 + assert (6+0j) ** mpc(3) == 216.0 + assert inf ** mpf(0) == mpf(1) + assert ninf ** mpf(0) == mpf(1) + assert nan ** mpf(0) == mpf(1) + assert mpc(1, -inf)**3 == mpc(-inf, inf) + assert mpc(1, -inf)**4 == mpc(inf, inf) + + +def test_mixed_misc(): + assert 1 + mpf(3) == mpf(3) + 1 == 4 + assert 1 - mpf(3) == -(mpf(3) - 1) == -2 + assert 3 * mpf(2) == mpf(2) * 3 == 6 + assert 6 / mpf(2) == mpf(6) / 2 == 3 + assert 1.0 + mpf(3) == mpf(3) + 1.0 == 4 + assert 1.0 - mpf(3) == -(mpf(3) - 1.0) == -2 + assert 3.0 * mpf(2) == mpf(2) * 3.0 == 6 + assert 6.0 / mpf(2) == mpf(6) / 2.0 == 3 + +def test_add_misc(): + assert mpf(4) + mpf(-70) == -66 + assert mpf(1) + mpf(1.1)/80 == 1 + 1.1/80 + assert mpf((1, 10000000000)) + mpf(3) == mpf((1, 10000000000)) + assert mpf(3) + mpf((1, 10000000000)) == mpf((1, 10000000000)) + assert mpf((1, -10000000000)) + mpf(3) == mpf(3) + assert mpf(3) + mpf((1, -10000000000)) == mpf(3) + assert mpf(1) + 1e-15 != 1 + assert mpf(1) + 1e-20 == 1 + assert mpf(1.07e-22) + 0 == mpf(1.07e-22) + assert mpf(0) + mpf(1.07e-22) == mpf(1.07e-22) + +def test_mpf_init(): + a1 = mpf(0.3, prec=20) + a2 = mpf(0.3, dps=5) + a3 = mpf(0.3) + assert a1 == a2 + assert a1 != a3 + assert str(a1) == '0.300000190734863' + assert str(a3) == '0.3' + pytest.raises(ValueError, lambda: mpf((1,))) + pytest.raises(ValueError, lambda: mpf(mpi(1, 2))) + pytest.raises(TypeError, lambda: mpf(object())) + pytest.raises(TypeError, lambda: mpf(1 + 1j)) + class SomethingReal: + def _mpmath_(self, prec, rounding): + return mp.make_mpf(from_str('1.3', prec, rounding)) + class SomethingComplex: + def _mpmath_(self, prec, rounding): + return mp.make_mpc((from_str('1.3', prec, rounding), \ + from_str('1.7', prec, rounding))) + class mympf: + @property + def _mpf_(self): + return mpf(3.5)._mpf_ + assert mpf(SomethingReal(), prec=20) == mpf('1.3', prec=20) + pytest.raises(TypeError, lambda: mpf(SomethingComplex())) + assert mpf(mympf()) == mpf(3.5) + assert mympf() - mpf(0.5) == mpf(3.0) + assert mpf(decimal.Decimal('1.5')) == mpf('1.5') + assert mpf(decimal.Decimal('+inf')) == +inf + assert mpf(decimal.Decimal('-inf')) == -inf + assert isnan(mpf(decimal.Decimal('nan'))) + assert mpf(decimal.Decimal(1).exp(), dps=5) == mpf('2.7182807922363281', dps=5) + assert mpf(decimal.Decimal(1).exp(), prec=0) == mpf('2.718281828459045235360287471', prec=93) + assert mpf('0x1.4ace478p+33') == mpf(11100000000.0) + assert mpf('0x1.4ace478p+33', base=0) == mpf(11100000000.0) + assert mpf('1.4ace478p+33', base=16) == mpf(11100000000.0) + assert mpf((1, 17813873926281399, -78, 54), prec=5, + rounding='u') == mpf('-5.9604644775390625e-8') + + assert mpf(float('+inf')) == +inf + assert mpf(float('-inf')) == -inf + assert isnan(mpf(float('nan'))) + +def test_mpc_init(): + class mympc: + @property + def _mpc_(self): + return (mpf(7)._mpf_, mpf(-1)._mpf_) + assert mpc(3+1j, 7-1j) == mpc(real='4.0', imag='8.0') + assert mpc(3+1j, mympc()) == mpc(real='4.0', imag='8.0') + assert mpc('(1+2j)') == mpc(real='1.0', imag='2.0') + +def test_mpf_props(): + a = mpf(0.5) + assert a.man_exp == (1, -1) + pytest.raises(ValueError, lambda: inf.man_exp) + pytest.raises(ValueError, lambda: nan.man_exp) + assert a.man == 1 + assert a.exp == -1 + assert a.bc == 1 + +def test_mpf_methods(): + assert mpf(0.5).as_integer_ratio() == (1, 2) + assert mpf('0.3').as_integer_ratio() == (5404319552844595, + 18014398509481984) + +def test_mpf_magic(): + assert complex(mpf(0.5)) == complex(0.5) + +def test_complex_misc(): + # many more tests needed + assert 1 + mpc(2) == 3 + assert not mpc(2).ae(2 + 1e-13) + assert mpc(2+1e-15j).ae(2) + +def test_complex_zeros(): + for a in [0,2]: + for b in [0,3]: + for c in [0,4]: + for d in [0,5]: + assert mpc(a,b)*mpc(c,d) == complex(a,b)*complex(c,d) + +def test_hash(): + for i in range(-256, 256): + assert hash(mpf(i)) == hash(i) + assert hash(mpf(0.5)) == hash(0.5) + assert hash(mpc(2,3)) == hash(2+3j) + # Check that this doesn't fail + assert hash(inf) + hash(nan) + # Check that overflow doesn't assign equal hashes to large numbers + assert hash(mpf('1e1000')) != hash('1e10000') + assert hash(mpc(100,'1e1000')) != hash(mpc(200,'1e1000')) + assert hash(MPQ(1,3)) + assert hash(MPQ(0,1)) == 0 + assert hash(MPQ(-1,1)) == hash(-1) + assert hash(MPQ(1,1)) == hash(1) + assert hash(MPQ(5,1)) == hash(5) + assert hash(MPQ(1,2)) == hash(0.5) + assert hash(mpf(1)*2**2000) == hash(2**2000) + assert hash(mpf(1)/2**2000) == hash(MPQ(1,2**2000)) + +# Advanced rounding test +def test_add_rounding(): + a = from_float(1e-50) + assert mpf_sub(mpf_add(fone, a, 53, round_up), fone, 53, round_up) == from_float(2.2204460492503131e-16) + assert mpf_sub(fone, a, 53, round_up) == fone + assert mpf_sub(fone, mpf_sub(fone, a, 53, round_down), 53, round_down) == from_float(1.1102230246251565e-16) + assert mpf_add(fone, a, 53, round_down) == fone + +def test_almost_equal(): + assert mpf(1.2).ae(mpf(1.20000001), 1e-7) + assert not mpf(1.2).ae(mpf(1.20000001), 1e-9) + assert not mpf(-0.7818314824680298).ae(mpf(-0.774695868667929)) + assert inf.ae(inf) + assert not inf.ae(-inf) + assert not mpf(1.2).ae(nan) + assert not mpf(1.2).ae(inf) + assert not nan.ae(nan) + assert not nan.ae(inf) + +def test_arithmetic_functions(): + ops = [(operator.add, fadd), (operator.sub, fsub), (operator.mul, fmul), + (operator.truediv, fdiv)] + a = mpf(0.27) + b = mpf(1.13) + c = mpc(0.51+2.16j) + d = mpc(1.08-0.99j) + for x in [a,b,c,d]: + for y in [a,b,c,d]: + for op, fop in ops: + if fop is not fdiv: + mp.prec = 200 + z0 = op(x,y) + mp.prec = 60 + z1 = op(x,y) + mp.prec = 53 + z2 = op(x,y) + assert fop(x, y, prec=60) == z1 + assert fop(x, y) == z2 + if fop is not fdiv: + assert fop(x, y, prec=inf) == z0 + assert fop(x, y, dps=inf) == z0 + assert fop(x, y, exact=True) == z0 + assert fneg(fneg(z1, exact=True), prec=inf) == z1 + assert fneg(z1) == -(+z1) + +def test_exact_integer_arithmetic(): + random.seed(0) + for prec in [6, 10, 25, 40, 100, 250, 725]: + for rounding in ['d', 'u', 'f', 'c', 'n']: + mp.dps = prec + mp.rounding = rounding + M = 10**(prec-2) + M2 = 10**(prec//2-2) + for i in range(10): + a = random.randint(-M, M) + b = random.randint(-M, M) + assert mpf(a, rounding=rounding) == a + assert int(mpf(a, rounding=rounding)) == a + assert int(mpf(str(a), rounding=rounding)) == a + assert mpf(a) + mpf(b) == a + b + assert mpf(a) - mpf(b) == a - b + assert -mpf(a) == -a + a = random.randint(-M2, M2) + b = random.randint(-M2, M2) + assert mpf(a) * mpf(b) == a*b + assert mpf_mul(from_int(a), from_int(b), mp.prec, rounding) == from_int(a*b) + +def test_odd_int_bug(): + assert to_int(from_int(3), round_nearest) == 3 + +def test_str_1000_digits(): + mp.dps = 1001 + # last digit may be wrong + assert str(mpf(2)**0.5)[-10:-1] == '9518488472'[:9] + assert str(pi)[-10:-1] == '2164201989'[:9] + +def test_str_10000_digits(): + mp.dps = 10001 + # last digit may be wrong + assert str(mpf(2)**0.5)[-10:-1] == '5873258351'[:9] + assert str(pi)[-10:-1] == '5256375678'[:9] + +def test_monitor(): + f = lambda x: x**2 + a = [] + b = [] + g = monitor(f, a.append, b.append) + assert g(3) == 9 + assert g(4) == 16 + assert a[0] == ((3,), {}) + assert b[0] == 9 + +def test_nint_distance(): + assert nint_distance(mpf(-3)) == (-3, -inf) + assert nint_distance(mpc(-3)) == (-3, -inf) + assert nint_distance(mpf(-3.1)) == (-3, -3) + assert nint_distance(mpf(-3.01)) == (-3, -6) + assert nint_distance(mpf(-3.001)) == (-3, -9) + assert nint_distance(mpf(-3.0001)) == (-3, -13) + assert nint_distance(mpf(-2.9)) == (-3, -3) + assert nint_distance(mpf(-2.99)) == (-3, -6) + assert nint_distance(mpf(-2.999)) == (-3, -9) + assert nint_distance(mpf(-2.9999)) == (-3, -13) + assert nint_distance(mpc(-3+0.1j)) == (-3, -3) + assert nint_distance(mpc(-3+0.01j)) == (-3, -6) + assert nint_distance(mpc(-3.1+0.1j)) == (-3, -3) + assert nint_distance(mpc(-3.01+0.01j)) == (-3, -6) + assert nint_distance(mpc(-3.001+0.001j)) == (-3, -9) + assert nint_distance(mpf(0)) == (0, -inf) + assert nint_distance(mpf(0.01)) == (0, -6) + assert nint_distance(mpf('1e-100')) == (0, -332) + pytest.raises(ValueError, lambda: nint_distance(mpc(1, inf))) + pytest.raises(ValueError, lambda: nint_distance(mpc(inf, 1))) + +def test_floor_ceil_nint_frac(): + for n in range(-10,10): + assert floor(n) == n + assert floor(n+0.5) == n + assert ceil(n) == n + assert ceil(n+0.5) == n+1 + assert nint(n) == n + # nint rounds to even + if n % 2 == 1: + assert nint(n+0.5) == n+1 + else: + assert nint(n+0.5) == n + assert floor(inf) == inf + assert floor(ninf) == ninf + assert isnan(floor(nan)) + assert ceil(inf) == inf + assert ceil(ninf) == ninf + assert isnan(ceil(nan)) + assert nint(inf) == inf + assert nint(ninf) == ninf + assert isnan(nint(nan)) + assert floor(0.1) == 0 + assert floor(0.9) == 0 + assert floor(-0.1) == -1 + assert floor(-0.9) == -1 + assert floor(10000000000.1) == 10000000000 + assert floor(10000000000.9) == 10000000000 + assert floor(-10000000000.1) == -10000000000-1 + assert floor(-10000000000.9) == -10000000000-1 + assert floor(1e-100) == 0 + assert floor(-1e-100) == -1 + assert floor(1e100) == 1e100 + assert floor(-1e100) == -1e100 + assert ceil(0.1) == 1 + assert ceil(0.9) == 1 + assert ceil(-0.1) == 0 + assert ceil(-0.9) == 0 + assert ceil(10000000000.1) == 10000000000+1 + assert ceil(10000000000.9) == 10000000000+1 + assert ceil(-10000000000.1) == -10000000000 + assert ceil(-10000000000.9) == -10000000000 + assert ceil(1e-100) == 1 + assert ceil(-1e-100) == 0 + assert ceil(1e100) == 1e100 + assert ceil(-1e100) == -1e100 + assert nint(0.1) == 0 + assert nint(0.9) == 1 + assert nint(-0.1) == 0 + assert nint(-0.9) == -1 + assert nint(10000000000.1) == 10000000000 + assert nint(10000000000.9) == 10000000000+1 + assert nint(-10000000000.1) == -10000000000 + assert nint(-10000000000.9) == -10000000000-1 + assert nint(1e-100) == 0 + assert nint(-1e-100) == 0 + assert nint(1e100) == 1e100 + assert nint(-1e100) == -1e100 + assert floor(3.2+4.6j) == 3+4j + assert ceil(3.2+4.6j) == 4+5j + assert nint(3.2+4.6j) == 3+5j + for n in range(-10,10): + assert frac(n) == 0 + assert frac(0.25) == 0.25 + assert frac(1.25) == 0.25 + assert frac(2.25) == 0.25 + assert frac(-0.25) == 0.75 + assert frac(-1.25) == 0.75 + assert frac(-2.25) == 0.75 + assert frac('1e100000000000000') == 0 + u = mpf('1e-100000000000000') + assert frac(u) == u + assert frac(-u) == 1 # rounding! + u = mpf('1e-400') + assert frac(-u, prec=0) == fsub(1, u, exact=True) + assert frac(3.25+4.75j) == 0.25+0.75j + +def test_isnan_etc(): + assert isnan(nan) is True + assert isnan(3) is False + assert isnan(mpf(3)) is False + assert isnan(inf) is False + assert isnan(mpc(2, nan)) is True + assert isnan(mpc(2, nan)) is True + assert isnan(mpc(nan, nan)) is True + assert isnan(mpc(2, 2)) is False + assert isnan(mpc(nan, inf)) is True + assert isnan(mpc(inf, inf)) is False + assert isnan(MPQ(3, 2)) is False + assert isnan(MPQ(0, 1)) is False + assert isinf(inf) is True + assert isinf(-inf) is True + assert isinf(3) is False + assert isinf(nan) is False + assert isinf(3 + 4j) is False + assert isinf(mpc(inf)) is True + assert isinf(mpc(3, inf)) is True + assert isinf(mpc(inf, 3)) is True + assert isinf(mpc(inf, inf)) is True + assert isinf(mpc(nan, inf)) is True + assert isinf(mpc(inf, nan)) is True + assert isinf(mpc(nan, nan)) is False + assert isinf(MPQ(3, 2)) is False + assert isinf(MPQ(0, 1)) is False + pytest.raises(TypeError, lambda: isinf(object())) + assert isnormal(3) is True + assert isnormal(3.5) is True + assert isnormal(mpf(3.5)) is True + assert isnormal(0) is False + assert isnormal(mpf(0)) is False + assert isnormal(0.0) is False + assert isnormal(inf) is False + assert isnormal(-inf) is False + assert isnormal(nan) is False + assert isnormal(float(inf)) is False + assert isnormal(mpc(0, 0)) is False + assert isnormal(mpc(3, 0)) is True + assert isnormal(mpc(0, 3)) is True + assert isnormal(mpc(3, 3)) is True + assert isnormal(mpc(0, nan)) is False + assert isnormal(mpc(0, inf)) is False + assert isnormal(mpc(3, nan)) is False + assert isnormal(mpc(3, inf)) is False + assert isnormal(mpc(3, -inf)) is False + assert isnormal(mpc(nan, 0)) is False + assert isnormal(mpc(inf, 0)) is False + assert isnormal(mpc(nan, 3)) is False + assert isnormal(mpc(inf, 3)) is False + assert isnormal(mpc(inf, nan)) is False + assert isnormal(mpc(nan, inf)) is False + assert isnormal(mpc(nan, nan)) is False + assert isnormal(mpc(inf, inf)) is False + assert isnormal(MPQ(3, 2)) is True + assert isnormal(MPQ(0, 1)) is False + pytest.raises(TypeError, lambda: isnormal(object())) + assert isnormal(math.nextafter(0, 1)) is True # issue 946 + assert fp.isnormal(math.nextafter(0, 1)) is False + assert fp.isnormal(0.0) is False + assert fp.isnormal(-0.0) is False + assert fp.isnormal(fp.nan) is False + assert fp.isnormal(fp.inf) is False + assert fp.isnormal(fp.ninf) is False + assert fp.isnormal(1.0) is True + assert fp.isnormal(sys.float_info.min) is True + assert fp.isnormal(1+0j) is True + assert fp.isnormal(0j) is False + assert fp.isnormal(-0j) is False + assert fp.isnormal(1+1j) is True + assert fp.isnormal(complex('inf+1j')) is False + assert isint(3) is True + assert isint(0) is True + assert isint(int(3)) is True + assert isint(int(0)) is True + assert isint(mpf(3)) is True + assert isint(mpf(0)) is True + assert isint(mpf(-3)) is True + assert isint(mpf(3.2)) is False + assert isint(3.2) is False + assert isint(nan) is False + assert isint(inf) is False + assert isint(-inf) is False + assert isint(mpc(0)) is True + assert isint(mpc(3)) is True + assert isint(mpc(3.2)) is False + assert isint(mpc(3, inf)) is False + assert isint(mpc(inf)) is False + assert isint(mpc(3, 2)) is False + assert isint(mpc(0, 2)) is False + assert isint(mpc(3, 2), gaussian=True) is True + assert isint(mpc(3, 0), gaussian=True) is True + assert isint(mpc(0, 3), gaussian=True) is True + assert isint(3 + 4j) is False + assert isint(3 + 4j, gaussian=True) is True + assert isint(3 + 0j) is True + assert isint(MPQ(3, 2)) is False + assert isint(MPQ(3, 9)) is False + assert isint(MPQ(9, 3)) is True + assert isint(MPQ(0, 4)) is True + assert isint(MPQ(1, 1)) is True + assert isint(MPQ(-1, 1)) is True + pytest.raises(TypeError, lambda: isint(object())) + assert mp.isnpint(0) is True + assert mp.isnpint(1) is False + assert mp.isnpint(-1) is True + assert mp.isnpint(-1.1) is False + assert mp.isnpint(-1.0) is True + assert mp.isnpint(MPQ(1, 2)) is False + assert mp.isnpint(MPQ(-1, 2)) is False + assert mp.isnpint(MPQ(-3, 1)) is True + assert mp.isnpint(MPQ(0, 1)) is True + assert mp.isnpint(MPQ(1, 1)) is False + assert mp.isnpint(0 + 0j) is True + assert mp.isnpint(-1 + 0j) is True + assert mp.isnpint(-1.1 + 0j) is False + assert mp.isnpint(-1 + 0.1j) is False + assert mp.isnpint(0 + 0.1j) is False + assert mp.isnpint(inf) is False + + +def test_isprime(): + assert isprime(MPZ(2)) + assert not isprime(MPZ(4)) + + +def test_issue_438(): + assert mpf(finf) == mpf('inf') + assert mpf(fninf) == mpf('-inf') + assert mpf(fnan)._mpf_ == mpf('nan')._mpf_ + + +def test_ctx_mag(): + assert mp.mag(MPQ(1, 2)) == 0 + assert mp.mag(MPQ(2)) == 2 + assert mp.mag(MPQ(0)) == mpf('-inf') + +def test_to_man_exp(): + assert to_man_exp(fnone, signed=False) == (1, 0) + +def test_rand_precision(): + """ + Test precision of rand() + """ + def get_remainder(x, bits): + """ + Return ``(x % 2**-bits) * (2**bits)``. + If this is nonzero, we know for sure that x was generated with a resolution greater than ``bits``. + """ + x = x * 2 ** bits + return x - int(x) + # Python float (to test the tests) + random.seed(42) + x = random.random() + assert x == 0.6394267984578837, "failed to initialize random() reproducibly" + assert get_remainder(x, 53) == 0 + assert get_remainder(x, 52) != 0 # Note: this is only true for specific random seeds! + # fp: + random.seed(42) + x = fp.rand() + assert get_remainder(x, 53) == 0 + assert get_remainder(x, 52) != 0 # Note: this is only true for specific random seeds! + # mp: + with workprec(123): + random.seed(43) + x = mp.rand() + assert get_remainder(x, 123) == 0 + assert get_remainder(x, 122) != 0 # Note: this is only true for specific random seeds! + # iv: + oldprec = iv.prec # REMOVE ME LATER - workaround for the bug that workprec doesn't work for iv + iv.prec=123 # REMOVE ME LATER - workaround for the bug that workprec doesn't work for iv + with workprec(123): + random.seed(43) + x = iv.rand() + assert get_remainder(x, 123) == 0 + assert get_remainder(x, 122) != 0 # Note: this is only true for specific random seeds! + iv.prec = oldprec # REMOVE ME LATER - workaround for the bug that workprec doesn't work for iv + +def test_issue_260(): + assert mpc(str(mpc(1j))) == mpc(1j) + + +@settings(max_examples=10000) +@given(st.floats(allow_nan=True, + allow_infinity=True, + allow_subnormal=True), + st.integers(min_value=0, max_value=15)) +@example(0.5, 0) +@example(-0.5, 0) +@example(1.5, 0) +@example(-1.5, 0) +@example(2.675, 2) +@example(math.inf, 3) +@example(-math.inf, 1) +@example(8.9884656743115795e+307, 0) +def test_round_bulk(x, n): + mp.prec = fp.prec + m = mpf(x) + mr = round(m, n) + xr = round(x, n) + if isnan(x): + assert isnan(mr) + assert isnan(xr) + else: + assert float(mr) == xr + # mp context doesn't support negative zero + if not xr and math.copysign(1., xr) == -1.: + return + assert nstr(mr, n=14, base=16, strip_zeros=False, + show_zero_exponent=True, binary_exp=True) == xr.hex() + try: + xr = round(x) + except ValueError: + pytest.raises(ValueError, lambda: round(m)) + except OverflowError: + pytest.raises(OverflowError, lambda: round(m)) + else: + mr = round(m) + assert type(mr) is int + assert mr == xr + + +def test_rounding_prop(): + assert mp.rounding == 'n' + assert mp.sin(1) == mpf('0x1.aed548f090ceep-1') + mp.rounding = 'u' + assert mp.rounding == 'u' + assert mp.sin(1) == mpf('0x1.aed548f090cefp-1') + with pytest.raises(ValueError): + mp.rounding = 'x' + + +def test_from_man_exp(): + with pytest.raises(TypeError): + mp.mpf(("!", 1)) + + +def test_issue_985(): + assert hash(mpc(-1)) == -2 + assert hash(mpmath.mpc(-1000004, 1)) == -2 + assert mpc(-1) in {1, -1} + + +def test_issue_975(): + def worker(): + mp = mpmath.MPContext() + mp.quad(lambda x: mp.exp(-x**2), [-mp.inf, mp.inf]) ** 2 + sz = 100 + tpe = ThreadPoolExecutor(max_workers=4) + futures = [None]*sz + for i in range(sz): + futures[i] = tpe.submit(worker) + assert len(collections.Counter(f.result() for f in futures)) + + +def test_to_float(): + # coverage tests + mp.dps = 1000 + + x = mpf('0b1.1111111111111111111111111111111111111' + '11111111111111011p-1023') + assert float(x).hex() == '0x0.fffffffffffffp-1022' + x = mpf('0b1.1111111111111111111111111111111111111' + '11111111111111111p-1023') + assert float(x).hex() == '0x1.0000000000000p-1022' + + assert math.isnan(float(mpf('nan'))) + assert float(-mpf('0x1.1p-1075')) == float.fromhex('-0x0.0000000000001p-1022') + assert float(mpf('0x1.1p-1075')) == float.fromhex('0x0.0000000000001p-1022') + + assert to_float(mpf('0x1p3000')._mpf_) == sys.float_info.max + assert to_float((-mpf('0x1p3000'))._mpf_) == -sys.float_info.max + pytest.raises(OverflowError, lambda: to_float(mpf('0x1p3000')._mpf_, + strict=True, + rnd=round_nearest)) + pytest.raises(OverflowError, lambda: to_float((-mpf('0x1p3000'))._mpf_, + strict=True, + rnd=round_nearest)) + +def test_issue_1078(): + mp.dps = 5000 # way too large + + # These are adjacent denormals (in 64-bit doubles) + lo = mpf("0x0.0000000000001p-1022") + hi = mpf("0x0.0000000000002p-1022") + + # Take a value that's a tiny bit below the + # midpoint (i.e. closer to `lo`): + mid = (lo + hi) / 2 + + # Offset of 2^-52 ULP: correctly rounds to lo + val_ok = mid - mpf(2) ** -(1074 + 52) + # Offset of 2^-53 ULP: was incorrectly rounded to hi (even) + val_bad = mid - mpf(2) ** -(1074 + 53) + + assert float(val_ok) == float(val_bad) == float(lo) + + +def test_jacobi_symbol(): + 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 + pytest.raises(ValueError, lambda: jacobi_symbol(3, 8)) + assert jacobi_symbol(10, 3) == 1 + assert jacobi_symbol(10, -3) == 1 + assert jacobi_symbol(-10, 3) == -1 + assert jacobi_symbol(-10, -3) == 1 + assert jacobi_symbol(11, 3) == -1 + assert jacobi_symbol(11, -3) == -1 + assert jacobi_symbol(-11, 3) == 1 + assert jacobi_symbol(-11, -3) == -1 + + +def test_issue_1116(): + mp.prec = 54 + x = mpf('0x1.d55368e2bef2p-4') + assert repr(x) != "mpf('0.11458149882303958')" + assert eval(repr(x)) == x + + +def test_eval_repr_roundtrip(): + for _ in range(10): + prec = random.randint(10, 1001) + with workprec(prec): + for _ in range(1000): + x = rand() + assert eval(repr(x)) == x, (prec, x) + n = random.randint(-100, 300) + if n > 0: + x *= 10**n + elif x < 0: + x /= 10**n + assert eval(repr(x)) == x, (prec, x) diff --git a/mpmath/tests/test_bitwise.py b/mpmath/tests/test_bitwise.py new file mode 100644 index 0000000..0dd655b --- /dev/null +++ b/mpmath/tests/test_bitwise.py @@ -0,0 +1,183 @@ +""" +Test bit-level integer and mpf operations +""" + +from mpmath import eps, fadd, ldexp, mp, mpc, mpf +from mpmath.libmp import (MPZ, fone, from_float, from_man_exp, fzero, mpf_add, + mpf_neg, mpf_sub, round_ceiling, round_down, + round_floor, round_nearest, round_up, to_float) +from mpmath.libmp.libintmath import trailing +from mpmath.libmp.libmpf import mpf_perturb + + +def test_trailing(): + assert trailing(0) == 0 + assert trailing(1) == 0 + assert trailing(2) == 1 + assert trailing(7) == 0 + assert trailing(8) == 3 + assert trailing(2**100) == 100 + assert trailing(2**100-1) == 0 + +def test_round_down(): + assert from_man_exp(MPZ(0), -4, 4, round_down)[:3] == (0, 0, 0) + assert from_man_exp(MPZ(0xf0), -4, 4, round_down)[:3] == (0, 15, 0) + assert from_man_exp(MPZ(0xf1), -4, 4, round_down)[:3] == (0, 15, 0) + assert from_man_exp(MPZ(0xff), -4, 4, round_down)[:3] == (0, 15, 0) + assert from_man_exp(MPZ(-0xf0), -4, 4, round_down)[:3] == (1, 15, 0) + assert from_man_exp(MPZ(-0xf1), -4, 4, round_down)[:3] == (1, 15, 0) + assert from_man_exp(MPZ(-0xff), -4, 4, round_down)[:3] == (1, 15, 0) + +def test_round_up(): + assert from_man_exp(MPZ(0), -4, 4, round_up)[:3] == (0, 0, 0) + assert from_man_exp(MPZ(0xf0), -4, 4, round_up)[:3] == (0, 15, 0) + assert from_man_exp(MPZ(0xf1), -4, 4, round_up)[:3] == (0, 1, 4) + assert from_man_exp(MPZ(0xff), -4, 4, round_up)[:3] == (0, 1, 4) + assert from_man_exp(MPZ(-0xf0), -4, 4, round_up)[:3] == (1, 15, 0) + assert from_man_exp(MPZ(-0xf1), -4, 4, round_up)[:3] == (1, 1, 4) + assert from_man_exp(MPZ(-0xff), -4, 4, round_up)[:3] == (1, 1, 4) + +def test_round_floor(): + assert from_man_exp(MPZ(0), -4, 4, round_floor)[:3] == (0, 0, 0) + assert from_man_exp(MPZ(0xf0), -4, 4, round_floor)[:3] == (0, 15, 0) + assert from_man_exp(MPZ(0xf1), -4, 4, round_floor)[:3] == (0, 15, 0) + assert from_man_exp(MPZ(0xff), -4, 4, round_floor)[:3] == (0, 15, 0) + assert from_man_exp(MPZ(-0xf0), -4, 4, round_floor)[:3] == (1, 15, 0) + assert from_man_exp(MPZ(-0xf1), -4, 4, round_floor)[:3] == (1, 1, 4) + assert from_man_exp(MPZ(-0xff), -4, 4, round_floor)[:3] == (1, 1, 4) + +def test_round_ceiling(): + assert from_man_exp(MPZ(0), -4, 4, round_ceiling)[:3] == (0, 0, 0) + assert from_man_exp(MPZ(0xf0), -4, 4, round_ceiling)[:3] == (0, 15, 0) + assert from_man_exp(MPZ(0xf1), -4, 4, round_ceiling)[:3] == (0, 1, 4) + assert from_man_exp(MPZ(0xff), -4, 4, round_ceiling)[:3] == (0, 1, 4) + assert from_man_exp(MPZ(-0xf0), -4, 4, round_ceiling)[:3] == (1, 15, 0) + assert from_man_exp(MPZ(-0xf1), -4, 4, round_ceiling)[:3] == (1, 15, 0) + assert from_man_exp(MPZ(-0xff), -4, 4, round_ceiling)[:3] == (1, 15, 0) + +def test_round_nearest(): + assert from_man_exp(MPZ(0), -4, 4, round_nearest)[:3] == (0, 0, 0) + assert from_man_exp(MPZ(0xf0), -4, 4, round_nearest)[:3] == (0, 15, 0) + assert from_man_exp(MPZ(0xf7), -4, 4, round_nearest)[:3] == (0, 15, 0) + assert from_man_exp(MPZ(0xf8), -4, 4, round_nearest)[:3] == (0, 1, 4) # 1111.1000 -> 10000.0 + assert from_man_exp(MPZ(0xf9), -4, 4, round_nearest)[:3] == (0, 1, 4) # 1111.1001 -> 10000.0 + assert from_man_exp(MPZ(0xe8), -4, 4, round_nearest)[:3] == (0, 7, 1) # 1110.1000 -> 1110.0 + assert from_man_exp(MPZ(0xe9), -4, 4, round_nearest)[:3] == (0, 15, 0) # 1110.1001 -> 1111.0 + assert from_man_exp(MPZ(-0xf0), -4, 4, round_nearest)[:3] == (1, 15, 0) + assert from_man_exp(MPZ(-0xf7), -4, 4, round_nearest)[:3] == (1, 15, 0) + assert from_man_exp(MPZ(-0xf8), -4, 4, round_nearest)[:3] == (1, 1, 4) + assert from_man_exp(MPZ(-0xf9), -4, 4, round_nearest)[:3] == (1, 1, 4) + assert from_man_exp(MPZ(-0xe8), -4, 4, round_nearest)[:3] == (1, 7, 1) + assert from_man_exp(MPZ(-0xe9), -4, 4, round_nearest)[:3] == (1, 15, 0) + +def test_rounding_bugs(): + # 1 less than power-of-two cases + assert from_man_exp(MPZ(72057594037927935), -56, 53, round_up)[:3] == (0, 1, 0) + assert from_man_exp(MPZ(73786976294838205979), -65, 53, round_nearest)[:3] == (0, 1, 1) + assert from_man_exp(MPZ(31), 0, 4, round_up)[:3] == (0, 1, 5) + assert from_man_exp(MPZ(-31), 0, 4, round_floor)[:3] == (1, 1, 5) + assert from_man_exp(MPZ(255), 0, 7, round_up)[:3] == (0, 1, 8) + assert from_man_exp(MPZ(-255), 0, 7, round_floor)[:3] == (1, 1, 8) + +def test_rounding_issue_200(): + a = from_man_exp(MPZ(9867),-100) + b = from_man_exp(MPZ(9867),-200) + c = from_man_exp(MPZ(-1),0) + z = (1, 1023, -10) + assert mpf_add(a, c, 10, 'd')[:3] == z + assert mpf_add(b, c, 10, 'd')[:3] == z + assert mpf_add(c, a, 10, 'd')[:3] == z + assert mpf_add(c, b, 10, 'd')[:3] == z + +def test_perturb(): + a = fone + b = from_float(0.99999999999999989) + c = from_float(1.0000000000000002) + assert mpf_perturb(a, 0, 53, round_nearest) == a + assert mpf_perturb(a, 1, 53, round_nearest) == a + assert mpf_perturb(a, 0, 53, round_up) == c + assert mpf_perturb(a, 0, 53, round_ceiling) == c + assert mpf_perturb(a, 0, 53, round_down) == a + assert mpf_perturb(a, 0, 53, round_floor) == a + assert mpf_perturb(a, 1, 53, round_up) == a + assert mpf_perturb(a, 1, 53, round_ceiling) == a + assert mpf_perturb(a, 1, 53, round_down) == b + assert mpf_perturb(a, 1, 53, round_floor) == b + a = mpf_neg(a) + b = mpf_neg(b) + c = mpf_neg(c) + assert mpf_perturb(a, 0, 53, round_nearest) == a + assert mpf_perturb(a, 1, 53, round_nearest) == a + assert mpf_perturb(a, 0, 53, round_up) == a + assert mpf_perturb(a, 0, 53, round_floor) == a + assert mpf_perturb(a, 0, 53, round_down) == b + assert mpf_perturb(a, 0, 53, round_ceiling) == b + assert mpf_perturb(a, 1, 53, round_up) == c + assert mpf_perturb(a, 1, 53, round_floor) == c + assert mpf_perturb(a, 1, 53, round_down) == a + assert mpf_perturb(a, 1, 53, round_ceiling) == a + +def test_add_exact(): + ff = from_float + assert mpf_add(ff(3.0), ff(2.5)) == ff(5.5) + assert mpf_add(ff(3.0), ff(-2.5)) == ff(0.5) + assert mpf_add(ff(-3.0), ff(2.5)) == ff(-0.5) + assert mpf_add(ff(-3.0), ff(-2.5)) == ff(-5.5) + assert mpf_sub(mpf_add(fone, ff(1e-100)), fone) == ff(1e-100) + assert mpf_sub(mpf_add(ff(1e-100), fone), fone) == ff(1e-100) + assert mpf_sub(mpf_add(fone, ff(-1e-100)), fone) == ff(-1e-100) + assert mpf_sub(mpf_add(ff(-1e-100), fone), fone) == ff(-1e-100) + assert mpf_add(fone, fzero) == fone + assert mpf_add(fzero, fone) == fone + assert mpf_add(fzero, fzero) == fzero + +def test_long_exponent_shifts(): + # Check for possible bugs due to exponent arithmetic overflow + # in a C implementation + x = mpf(1) + for p in [32, 64]: + a = ldexp(1,2**(p-1)) + b = ldexp(1,2**p) + c = ldexp(1,2**(p+1)) + d = ldexp(1,-2**(p-1)) + e = ldexp(1,-2**p) + f = ldexp(1,-2**(p+1)) + assert (x+a) == a + assert (x+b) == b + assert (x+c) == c + assert (x+d) == x + assert (x+e) == x + assert (x+f) == x + assert (a+x) == a + assert (b+x) == b + assert (c+x) == c + assert (d+x) == x + assert (e+x) == x + assert (f+x) == x + assert (x-a) == -a + assert (x-b) == -b + assert (x-c) == -c + assert (x-d) == x + assert (x-e) == x + assert (x-f) == x + assert (a-x) == a + assert (b-x) == b + assert (c-x) == c + assert (d-x) == -x + assert (e-x) == -x + assert (f-x) == -x + +def test_float_rounding(): + mp.prec = 64 + for x in [mpf(1), mpf(1)+eps, mpf(1)-eps, -mpf(1)+eps, -mpf(1)-eps]: + fa = float(x) + fb = float(fadd(x,0,prec=53,rounding='n')) + assert fa == fb + z = mpc(x,x) + ca = complex(z) + cb = complex(fadd(z,0,prec=53,rounding='n')) + assert ca == cb + for rnd in ['n', 'd', 'u', 'f', 'c']: + fa = to_float(x._mpf_, rnd=rnd) + fb = to_float(fadd(x,0,prec=53,rounding=rnd)._mpf_, rnd=rnd) + assert fa == fb diff --git a/mpmath/tests/test_calculus.py b/mpmath/tests/test_calculus.py new file mode 100644 index 0000000..b576051 --- /dev/null +++ b/mpmath/tests/test_calculus.py @@ -0,0 +1,278 @@ +import pytest + +from mpmath import (arange, chebyfit, cos, cosm, differint, e, euler, exp, + expm, fourier, fourierval, inf, invertlaplace, j, limit, + log, matrix, mp, mpf, norm, pade, pi, polyroots, polyval, + sin, sinm, sqrt, logm) + + +def test_approximation(): + f = lambda x: cos(2-2*x)/x + p, err = chebyfit(f, [2, 4], 8, error=True) + assert err < 1e-5 + for i in range(10): + x = 2 + i/5. + assert abs(polyval(p, x) - f(x)) < err + +def test_chebyfit(): + f = lambda x: cos(2-2*x)/x + p, err = chebyfit(f, [2, 4], 8, error=True, asc=False) + assert err < 1e-5 + p = p[::-1] + for i in range(10): + x = 2 + i/5. + assert abs(polyval(p, x) - f(x)) < err + +def test_chebyfit_nonpositive_N(): + with pytest.raises(ValueError): + chebyfit(sin, [-1, 1], 0) + +def test_limits(): + assert limit(lambda x: (x-sin(x))/x**3, 0).ae(mpf(1)/6) + assert limit(lambda n: (1+1/n)**n, inf).ae(e) + +def test_polyval(): + assert polyval([], 3) == 0 + assert polyval([0], 3) == 0 + assert polyval([5], 3) == 5 + # 4x^3 - 2x + 5 + p = [5, -2, 0, 4] + assert polyval(p, 4) == 253 + assert polyval(p, 4, derivative=True) == (253, 190) + assert polyval([1, 2, 3], 2, asc=False) == 11 + assert polyval(list(reversed(p)), 4, asc=False) == 253 + +def test_polyroots(): + p = polyroots([-4,1]) + assert p[0].ae(4) + p, q = polyroots([3,2,1]) + assert p.ae(-1 - sqrt(2)*j) + assert q.ae(-1 + sqrt(2)*j) + #this is not a real test, it only tests a specific case + assert polyroots([1]) == [] + pytest.raises(ValueError, lambda: polyroots([0])) + p, q = polyroots([1,2,3], asc=False) + assert p.ae(-1 - sqrt(2)*j) + assert q.ae(-1 + sqrt(2)*j) + +def test_polyroots_legendre(): + n = 64 + coeffs = [916312070471295267, 0, -1905929106580294155360, 0, + 659769125727878493447120, 0, -91048139350447232095702560, 0, + 6695289961520387531608984680, 0, -304114948474392713657972548576, + 0, 9330799555464321896324157740400, 0, + -205277590220215081719131470288800, 0, + 3378527005707706553294038781836500, 0, + -42927166660756742088912492757452000, 0, + 431305058712550634988073414073557200, 0, + -3491517141958743235617737161547844000, 0, + 23112325428835593809686977515028663000, 0, + -126584428502545713788439446082310831200, 0, + 579006552594977616773047095969088431600, 0, + -2228176940331017311443863996901733412640, 0, + 7255051932731034189479516844750603752850, 0, + -20071017111583894941305187420771723751200, 0, + 47310254620162038075933656063247634556400, 0, + -95158890516229191805647495979277603503200, 0, + 163356095386193445933028201431093219347160, 0, + -239057700565161140389797367947941296605600, 0, + 297432255354328395601259515935229287637200, 0, + -313237834141273382807123548182995095192800, 0, + 277415422258095841688223780704620656114900, 0, + -204721258548015217049921875719981284186016, 0, + 124284021969194758465450309166353645376880, 0, + -60969520211303089058522793175947071316960, 0, + 23556405536185284408974715545252277554280, 0, + -6897338342113537600691931230430793911840, 0, + 1437919688271127330313741595496589239248, 0, + -190100434726484311252477736051902332000, 0, + 11975573020964041433067793888190275875] + + with mp.workdps(3): + with pytest.raises(mp.NoConvergence): + polyroots(coeffs, maxsteps=5, cleanup=True, error=False, + extraprec=n*10) + + roots = polyroots(coeffs, maxsteps=50, cleanup=True, error=False, + extraprec=n*10) + roots = [str(r) for r in roots] + assert roots == \ + ['-0.999', '-0.996', '-0.991', '-0.983', '-0.973', '-0.961', + '-0.946', '-0.93', '-0.911', '-0.889', '-0.866', '-0.841', + '-0.813', '-0.784', '-0.753', '-0.72', '-0.685', '-0.649', + '-0.611', '-0.572', '-0.531', '-0.489', '-0.446', '-0.402', + '-0.357', '-0.311', '-0.265', '-0.217', '-0.17', '-0.121', + '-0.073', '-0.0243', '0.0243', '0.073', '0.121', '0.17', '0.217', + '0.265', '0.311', '0.357', '0.402', '0.446', '0.489', '0.531', + '0.572', '0.611', '0.649', '0.685', '0.72', '0.753', '0.784', + '0.813', '0.841', '0.866', '0.889', '0.911', '0.93', '0.946', + '0.961', '0.973', '0.983', '0.991', '0.996', '0.999'] + +def test_polyroots_legendre_init(): + extra_prec = 100 + coeffs = [916312070471295267, 0, -1905929106580294155360, 0, + 659769125727878493447120, 0, -91048139350447232095702560, 0, + 6695289961520387531608984680, 0, -304114948474392713657972548576, + 0, 9330799555464321896324157740400, 0, + -205277590220215081719131470288800, 0, + 3378527005707706553294038781836500, 0, + -42927166660756742088912492757452000, 0, + 431305058712550634988073414073557200, 0, + -3491517141958743235617737161547844000, 0, + 23112325428835593809686977515028663000, 0, + -126584428502545713788439446082310831200, 0, + 579006552594977616773047095969088431600, 0, + -2228176940331017311443863996901733412640, 0, + 7255051932731034189479516844750603752850, 0, + -20071017111583894941305187420771723751200, 0, + 47310254620162038075933656063247634556400, 0, + -95158890516229191805647495979277603503200, 0, + 163356095386193445933028201431093219347160, 0, + -239057700565161140389797367947941296605600, 0, + 297432255354328395601259515935229287637200, 0, + -313237834141273382807123548182995095192800, 0, + 277415422258095841688223780704620656114900, 0, + -204721258548015217049921875719981284186016, 0, + 124284021969194758465450309166353645376880, 0, + -60969520211303089058522793175947071316960, 0, + 23556405536185284408974715545252277554280, 0, + -6897338342113537600691931230430793911840, 0, + 1437919688271127330313741595496589239248, 0, + -190100434726484311252477736051902332000, 0, + 11975573020964041433067793888190275875] + + roots_init = matrix(['-0.999', '-0.996', '-0.991', '-0.983', '-0.973', + '-0.961', '-0.946', '-0.93', '-0.911', '-0.889', + '-0.866', '-0.841', '-0.813', '-0.784', '-0.753', + '-0.72', '-0.685', '-0.649', '-0.611', '-0.572', + '-0.531', '-0.489', '-0.446', '-0.402', '-0.357', + '-0.311', '-0.265', '-0.217', '-0.17', '-0.121', + '-0.073', '-0.0243', '0.0243', '0.073', '0.121', + '0.17', '0.217', '0.265', ' 0.311', '0.357', + '0.402', '0.446', '0.489', '0.531', '0.572', + '0.611', '0.649', '0.685', '0.72', '0.753', + '0.784', '0.813', '0.841', '0.866', '0.889', + '0.911', '0.93', '0.946', '0.961', '0.973', + '0.983', '0.991', '0.996', '0.999', '1.0']) + with mp.workdps(2*mp.dps): + roots_exact = polyroots(coeffs, maxsteps=50, cleanup=True, error=False, + extraprec=2*extra_prec) + with pytest.raises(mp.NoConvergence): + polyroots(coeffs, maxsteps=5, cleanup=True, error=False, + extraprec=extra_prec) + roots,err = polyroots(coeffs, maxsteps=5, cleanup=True, error=True, + extraprec=extra_prec,roots_init=roots_init) + assert max(matrix(roots_exact)-matrix(roots).apply(abs)) < err + roots1,err1 = polyroots(coeffs, maxsteps=25, cleanup=True, error=True, + extraprec=extra_prec,roots_init=roots_init[:60]) + assert max(matrix(roots_exact)-matrix(roots1).apply(abs)) < err1 + +def test_pade(): + one = mpf(1) + mp.dps = 20 + N = 10 + a = [one] + k = 1 + for i in range(1, N+1): + k *= i + a.append(one/k) + p, q = pade(a, N//2, N//2) + for x in arange(0, 1, 0.1): + r = polyval(p, x)/polyval(q, x) + assert r.ae(exp(x), 1.0e-10) + +def test_fourier(): + c, s = fourier(lambda x: x+1, [-1, 2], 2) + #plot([lambda x: x+1, lambda x: fourierval((c, s), [-1, 2], x)], [-1, 2]) + assert c[0].ae(1.5) + assert c[1].ae(-3*sqrt(3)/(2*pi)) + assert c[2].ae(3*sqrt(3)/(4*pi)) + assert s[0] == 0 + assert s[1].ae(3/(2*pi)) + assert s[2].ae(3/(4*pi)) + assert fourierval((c, s), [-1, 2], 1).ae(1.9134966715663442) + +def test_differint(): + assert differint(lambda t: t, 2, -0.5).ae(8*sqrt(2/pi)/3) + +def test_invlap(): + t = 0.01 + fp = lambda p: 1/(p+1)**2 + ft = lambda t: t*exp(-t) + ftt = ft(t) + assert invertlaplace(fp,t,method='talbot').ae(ftt) + assert invertlaplace(fp,t,method='stehfest').ae(ftt) + assert invertlaplace(fp,t,method='dehoog').ae(ftt) + assert invertlaplace(fp,t,method='cohen').ae(ftt) + t = 1.0 + ftt = ft(t) + assert invertlaplace(fp,t,method='talbot').ae(ftt) + assert invertlaplace(fp,t,method='stehfest').ae(ftt) + assert invertlaplace(fp,t,method='dehoog').ae(ftt) + assert invertlaplace(fp,t,method='cohen').ae(ftt) + + t = 0.01 + fp = lambda p: log(p)/p + ft = lambda t: -euler-log(t) + ftt = ft(t) + assert invertlaplace(fp,t,method='talbot').ae(ftt) + assert invertlaplace(fp,t,method='stehfest').ae(ftt) + assert invertlaplace(fp,t,method='dehoog').ae(ftt) + assert invertlaplace(fp,t,method='cohen').ae(ftt) + t = 1.0 + ftt = ft(t) + assert invertlaplace(fp,t,method='talbot').ae(ftt) + assert invertlaplace(fp,t,method='stehfest').ae(ftt) + assert invertlaplace(fp,t,method='dehoog').ae(ftt) + assert invertlaplace(fp,t,method='cohen').ae(ftt) + +def test_expm(): + # Simple tests with known exact results + A = matrix([[2, 0], [0, 1]]) + A = expm(A) + B = matrix([[e**2, 0], [0, e]]) + assert norm(A-B, inf) < 1e-15 + + A = matrix([[0, -pi], [pi, 0]]) + A = expm(A) + B = matrix([[-1, 0], [0, -1]]) + assert norm(A-B, inf) < 1e-15 + + # Test with input as list of lists + A = [[1, 0], [0, 2]] + A = expm(A) + B = matrix([[e, 0], [0, e**2]]) + assert norm(A-B, inf) < 1e-15 + + # Test non-square matrix input + A = [[1, 0], [0, 1], [0, 0]] + pytest.raises(ValueError, lambda: expm(A)) + +def test_cosm_sinm(): + # Simple test with known exact result + A = matrix([[-pi, 0], [0, pi]]) + C = cosm(A) + S = sinm(A) + C_exact = matrix([[cos(-pi), 0], [0, cos(pi)]]) + S_exact = matrix([[0, 0], [0, 0]]) + assert norm(C-C_exact, inf) < 1e-15 + assert norm(S-S_exact, inf) < 1e-15 + + # Test with input as list of lists + A = [[-pi, 0], [0, pi]] + C = cosm(A) + S = sinm(A) + C_exact = matrix([[cos(-pi), 0], [0, cos(pi)]]) + S_exact = matrix([[0, 0], [0, 0]]) + assert norm(C-C_exact, inf) < 1e-15 + assert norm(S-S_exact, inf) < 1e-15 + + # Test non-square matrix input + A = [[1, 0], [0, 1], [0, 0]] + pytest.raises(ValueError, lambda: cosm(A)) + pytest.raises(ValueError, lambda: sinm(A)) + +def test_logm(): + # Test for zero matrix + A = [[0, 0], [0, 0]] + pytest.raises(ValueError, lambda: logm(A)) diff --git a/mpmath/tests/test_cli.py b/mpmath/tests/test_cli.py new file mode 100644 index 0000000..15a5cc5 --- /dev/null +++ b/mpmath/tests/test_cli.py @@ -0,0 +1,138 @@ +"""Tests for the Command-Line Interface.""" + +import platform +import sys + +import pexpect +import pytest + +from mpmath.tests.test_demos import Console + + +if platform.python_implementation() == 'PyPy': + pytest.skip("Don't run CLI tests on PyPy.", + allow_module_level=True) + + +if sys.version_info >= (3, 15): + pytestmark = pytest.mark.filterwarnings("ignore:.*:DeprecationWarning") + + +def test_bare_console_no_bare_division(): + c = Console(f'{sys.executable} -m mpmath --no-ipython ' + '--no-wrap-floats --int-limits') # for coverage + + assert c.expect_exact('>>> ') == 0 + assert c.send('1 + 2\r\n') == 7 + assert c.expect_exact('3\r\n>>> ') == 0 + assert c.send('1/2\r\n') == 5 + assert c.expect_exact('Fraction(1, 2)\r\n>>> ') == 0 + assert c.send('-1/2\r\n') == 6 + assert c.expect_exact('Fraction(-1, 2)\r\n>>> ') == 0 + assert c.send('2**3/7\r\n') == 8 + assert c.expect_exact('Fraction(8, 7)\r\n>>> ') == 0 + assert c.send('(3 + 5)/7\r\n') == 11 + assert c.expect_exact('Fraction(8, 7)\r\n>>> ') == 0 + assert c.send('(0.5 + 1)/2\r\n') == 13 + assert c.expect_exact('0.75\r\n>>> ') == 0 + + +def test_bare_console_bare_division(): + c = Console(f'{sys.executable} -m mpmath --no-ipython --no-wrap-division ' + '--no-wrap-floats') + + assert c.expect_exact('>>> ') == 0 + assert c.send('1/2\r\n') == 5 + assert c.expect_exact('0.5\r\n>>> ') == 0 + + +def test_bare_console_without_ipython(): + try: + import IPython + del IPython + pytest.skip('IPython is available') + except ImportError: + pass + + c = Console(f'{sys.executable} -m mpmath') + + assert c.expect_exact('>>> ') == 0 + assert c.send('1 + 2\r\n') == 7 + assert c.expect_exact('3\r\n>>> ') == 0 + assert c.send('1/2\r\n') == 5 + assert c.expect_exact('\r\nFraction(1, 2)\r\n>>> ') == 0 + + +def test_ipython_console_bare_division_noauto(): + pytest.importorskip('IPython') + + c = Console(f'{sys.executable} -m mpmath --simple-prompt --no-wrap-floats ' + "--no-wrap-division --colors 'NoColor' ") + + assert c.expect_exact('\r\nIn [1]: ') == 0 + assert c.send('1/2\r\n') == 5 + assert c.expect_exact('\r\nOut[1]: 0.5\r\n\r\nIn [2]: ') == 0 + + +def test_ipython_console_wrap_floats(): + pytest.importorskip('IPython') + + c = Console(f'{sys.executable} -m mpmath --simple-prompt --prec 100 ' + "--colors 'NoColor' --no-pretty") + + assert c.expect_exact('\r\nIn [1]: ') == 0 + assert c.send('10.9\r\n') == 6 + assert c.expect_exact("\r\nOut[1]: mpf('10.899999999999999999999999999995')\r\n\r\nIn [2]: ") == 0 + assert c.send('def f():\r\n x = 1.1\n return x + 1\n\r\n\n') == 42 + assert c.expect_exact("\r\n\r\nIn [3]: ") == 0 + assert c.send('f()\r\n') == 5 + assert c.expect_exact("\r\nOut[3]: mpf('2.0999999999999999999999999999987')\r\n\r\nIn [4]: ") == 0 + + +def test_bare_console_wrap_floats(): + c = Console(f'{sys.executable} -m mpmath --simple-prompt --no-ipython --prec 100 ' + "--colors 'NoColor' --no-pretty") + + assert c.expect_exact('>>> ') == 0 + assert c.send("10.9\r\n") == 6 + assert c.expect_exact("mpf('10.899999999999999999999999999995')\r\n>>> ") == 0 + assert c.send("1e100\r\n") == 7 + assert c.expect_exact("mpf('9.9999999999999999999999999999997e+99')\r\n>>> ") == 0 + assert c.send("1E100\r\n") == 7 + assert c.expect_exact("mpf('9.9999999999999999999999999999997e+99')\r\n>>> ") == 0 + assert c.send("1+10.9j\r\n") == 9 + assert c.expect_exact("mpc(real='1.0', imag='10.899999999999999999999999999995')\r\n>>> ") == 0 + assert c.send("1+10.9J\r\n") == 9 + assert c.expect_exact("mpc(real='1.0', imag='10.899999999999999999999999999995')\r\n>>> ") == 0 + assert c.send('mpf(10.9)\r\n') == 11 + assert c.expect_exact("mpf('10.899999999999999999999999999995')\r\n>>> ") == 0 + assert c.send('0x1p-1\r\n') == 8 + assert c.expect_exact("mpf('0.5')\r\n>>> ") == 0 + assert c.send('0b1p+1\r\n') == 8 + assert c.expect_exact("mpf('2.0')\r\n>>> ") == 0 + + +@pytest.mark.skipif(sys.version_info < (3, 13), + reason="XXX: uses new REPL") +def test_bare_console_pretty(): + c = Console(f'{sys.executable} -m mpmath --simple-prompt --no-ipython --prec 100 ' + "--colors 'NoColor'", _dumb=False) + + assert c.expect('>>> ') == 0 + assert c.send("10.9\r\n") == 6 + assert c.expect("10.899999999999999999999999999995") == 0 + assert c.send("def f():\r\n x = ?\r\n\r\n") == 21 + assert c.expect('SyntaxError:') == 0 + assert c.send('def f():\r\n return 1.1\r\n\r\n') == 26 + assert c.expect('>>> ') == 0 + assert c.send("f()\r\n") == 5 + assert c.expect('1.1000000000000000000000000000003') == 0 + assert c.send("a = 2.1; a\r\n") == 12 + assert c.expect('2.0999999999999999999999999999987') == 0 + + +def test_mpmath_version(): + c = Console(f'{sys.executable} -m mpmath --version') + + assert c.expect(pexpect.EOF) == 0 + assert c.before.startswith('1.') diff --git a/mpmath/tests/test_compatibility.py b/mpmath/tests/test_compatibility.py new file mode 100644 index 0000000..a1cf8fd --- /dev/null +++ b/mpmath/tests/test_compatibility.py @@ -0,0 +1,77 @@ +import math +from random import randint, random, seed + +from mpmath import ceil, floor, mp, mpf + + +# Test compatibility with Python floats, which are +# IEEE doubles (53-bit) + +N = 5000 +seed(1) + +# Choosing exponents between roughly -140, 140 ensures that +# the Python floats don't overflow or underflow +xs = [(random()-1) * 10**randint(-140, 140) for x in range(N)] +ys = [(random()-1) * 10**randint(-140, 140) for x in range(N)] + +# include some equal values +ys[int(N*0.8):] = xs[int(N*0.8):] + +# Detect whether Python is compiled to use 80-bit floating-point +# instructions, in which case the double compatibility test breaks +uses_x87 = -4.1974624032366689e+117 / -8.4657370748010221e-47 \ + == 4.9581771393902231e+163 + +def test_double_compatibility(): + for x, y in zip(xs, ys): + mpx = mpf(x) + mpy = mpf(y) + assert mpf(x) == x + assert (mpx < mpy) == (x < y) + assert (mpx > mpy) == (x > y) + assert (mpx == mpy) == (x == y) + assert (mpx != mpy) == (x != y) + assert (mpx <= mpy) == (x <= y) + assert (mpx >= mpy) == (x >= y) + assert mpx == mpx + if uses_x87: + mp.prec = 64 + a = mpx + mpy + b = mpx * mpy + c = mpx / mpy + d = mpx % mpy + mp.prec = 53 + assert +a == x + y + assert +b == x * y + assert +c == x / y + assert +d == x % y + else: + assert mpx + mpy == x + y + assert mpx * mpy == x * y + assert mpx / mpy == x / y + assert mpx % mpy == x % y + assert abs(mpx) == abs(x) + assert mpf(repr(x)) == x + assert ceil(mpx) == math.ceil(x) + assert floor(mpx) == math.floor(x) + +def test_sqrt(): + # this fails quite often. it appers to be float + # that rounds the wrong way, not mpf + fail = 0 + for x in xs: + x = abs(x) + mp.prec = 100 + mp_high = mpf(x)**0.5 + mp.prec = 53 + mp_low = mpf(x)**0.5 + fp = x**0.5 + assert abs(mp_low-mp_high) <= abs(fp-mp_high) + fail += mp_low != fp + assert fail < N/10 + +def test_bugs(): + # particular bugs + assert mpf(4.4408920985006262E-16) < mpf(1.7763568394002505E-15) + assert mpf(-4.4408920985006262E-16) > mpf(-1.7763568394002505E-15) diff --git a/mpmath/tests/test_convert.py b/mpmath/tests/test_convert.py new file mode 100644 index 0000000..f4eca5a --- /dev/null +++ b/mpmath/tests/test_convert.py @@ -0,0 +1,291 @@ +import decimal +import random +from decimal import Decimal +from fractions import Fraction + +import pytest + +from mpmath import inf, isnan, iv, mp, mpc, mpf, mpi, mpmathify, sqrt +from mpmath.libmp import (fhalf, from_float, from_rational, from_str, + round_ceiling, round_floor, round_nearest, + to_rational, to_str) + + +def test_basic_string(): + """ + Test basic string conversion + """ + assert mpf('3') == mpf('3.0') == mpf('0003.') == mpf('0.03e2') == mpf(3.0) + assert mpf('30') == mpf('30.0') == mpf('00030.') == mpf(30.0) + for i in range(10): + for j in range(10): + assert mpf('%ie%i' % (i,j)) == i * 10**j + assert str(mpf('25000.0')) == '25000.0' + assert str(mpf('2500.0')) == '2500.0' + assert str(mpf('250.0')) == '250.0' + assert str(mpf('25.0')) == '25.0' + assert str(mpf('2.5')) == '2.5' + assert str(mpf('0.25')) == '0.25' + assert str(mpf('0.025')) == '0.025' + assert str(mpf('0.0025')) == '0.0025' + assert str(mpf('0.00025')) == '0.00025' + assert str(mpf('0.000025')) == '2.5e-5' + assert str(mpf(0)) == '0.0' + assert str(mpf('2.5e1000000000000000000000')) == '2.5e+1000000000000000000000' + assert str(mpf('2.6e-1000000000000000000000')) == '2.6e-1000000000000000000000' + assert str(mpf(1.23402834e-15)) == '1.23402834e-15' + assert str(mpf(-1.23402834e-15)) == '-1.23402834e-15' + assert str(mpf(-1.2344e-15)) == '-1.2344e-15' + assert repr(mpf(-1.2344e-15)) == "mpf('-1.2343999999999999e-15')" + assert str(mpf("2163048125L")) == '2163048125.0' + assert str(mpf("-2163048125l")) == '-2163048125.0' + assert str(mpf("-2163048125L/1088391168")) == '-1.98738118113799' + assert str(mpf("2163048125/1088391168l")) == '1.98738118113799' + assert str(mpf('inf')) == 'inf' + + # issue 613 + assert str(mpf('2_5_0_0.0')) == '2500.0' + # issue 377 + assert to_str(from_str('1_234.567891', 80), 24) == '1234.567891' + assert to_str(from_str('1_234.567_891', 80), 24) == '1234.567891' + assert to_str(from_str('1_234.567_8_9_1', 80), 24) == '1234.567891' + assert to_str(from_str('1.0_0', 80), 24) == '1.0' + assert to_str(from_str('.000', 80), 24) == '0.0' + +def test_from_str(): + assert mpf(from_str('ABC.ABC', base=16)) == mpf(float.fromhex('ABC.ABC')) + assert mpf(from_str('0xABC.ABC')) == mpf(float.fromhex('ABC.ABC')) + assert mpf(from_str('0x3.a7p10')) == mpf(float.fromhex('0x3.a7p10')) + assert mpf(from_str('0x1.4ace478p+33')) == mpf(float.fromhex('0x1.4ace478p+33')) + assert mpf(from_str('0x1.4ace478@+33')) == mpf('7.0354608312666732e+39') + assert mpf(from_str('0b1101.100101')) == mpf('13.578125') + assert mpf(from_str('0o1101.100101')) == mpf('577.12524795532227') + assert mpf(from_str('1.99999999', prec=0)) == mpf('1.9999999901046976') + +def test_eps_repr(): + mp.dps = 24 + assert repr(mp.eps) == '' + +def test_to_str(): + assert to_str(from_str('ABC.ABC', base=16), 6, base=16) == '0xabc.abc' + assert to_str(from_str('0x3.a7p10', base=16), 3, base=16) == '0xe9c.0' + assert to_str(from_str('0x1.4ace478p+33'), 7, base=16) == '0x2.959c8f@+8' + assert to_str(from_str('0o1101.100101'), 8, base=8) == '0o1101.1001' + assert to_str(from_str('0b1101.100101'), 10, base=2) == '0b1101.100101' + assert to_str(from_str('0x1.4ace478p+33'), 8, base=16, binary_exp=True) == '0x1.4ace478p+33' + assert to_str(from_str('0x1.4ace478p+33'), 7, base=16, binary_exp=True) == '0x1.4ace48p+33' + assert to_str(from_str('0x1.4ace478p+33'), 5, base=16, binary_exp=True) == '0x1.4acep+33' + assert to_str(from_str('1', base=16), 6, base=16, binary_exp=True) == '0x1.0' + x = mpf('1234.567891')._mpf_ + pytest.raises(ValueError, lambda: to_str(x, 6, binary_exp=True)) + pytest.raises(ValueError, lambda: to_str(x, 6, rnd='Y')) + pytest.raises(ValueError, lambda: to_str('1e400e2', 6)) + assert to_str(x, 5, rnd='n') == '1234.6' + assert to_str(x, 5, rnd='d') == '1234.5' + assert to_str(x, 5, rnd='u') == '1234.6' + +def test_pretty(): + mp.pretty = True + assert repr(mpf(2.5)) == '2.5' + assert repr(mpc(2.5,3.5)) == '(2.5 + 3.5j)' + iv.pretty = True + assert repr(mpi(2.5,3.5)) == '[2.5, 3.5]' + +def test_str_whitespace(): + assert mpf('1.26 ') == 1.26 + +def test_str_format(): + assert to_str(from_float(0.1),15,strip_zeros=False) == '0.100000000000000' + assert to_str(from_float(0.0),15,show_zero_exponent=True) == '0.0e+0' + assert to_str(from_float(0.0),0,show_zero_exponent=True) == '.0e+0' + assert to_str(from_float(0.0),0,show_zero_exponent=False) == '.0' + assert to_str(from_float(0.0),1,show_zero_exponent=True) == '0.0e+0' + assert to_str(from_float(0.0),1,show_zero_exponent=False) == '0.0' + assert to_str(from_float(1.23),3,show_zero_exponent=True) == '1.23e+0' + assert to_str(from_float(1.23456789000000e-2),15,strip_zeros=False,min_fixed=0,max_fixed=0) == '1.23456789000000e-2' + assert to_str(from_float(1.23456789000000e+2),15,strip_zeros=False,min_fixed=0,max_fixed=0) == '1.23456789000000e+2' + assert to_str(from_float(2.1287e14), 15, max_fixed=1000) == '212870000000000.0' + assert to_str(from_float(2.1287e15), 15, max_fixed=1000) == '2128700000000000.0' + assert to_str(from_float(2.1287e16), 15, max_fixed=1000) == '21287000000000000.0' + assert to_str(from_float(2.1287e30), 15, max_fixed=1000) == '2128700000000000000000000000000.0' + +def test_tight_string_conversion(): + # In an old version, '0.5' wasn't recognized as representing + # an exact binary number and was erroneously rounded up or down + assert from_str('0.5', 10, round_floor) == fhalf + assert from_str('0.5', 10, round_ceiling) == fhalf + +def test_eval_repr_invariant(): + """Test that eval(repr(x)) == x""" + random.seed(123) + for dps in [10, 15, 20, 50, 100]: + mp.dps = dps + for i in range(1000): + a = mpf(random.random())**0.5 * 10**random.randint(-100, 100) + assert eval(repr(a)) == a + +def test_str_bugs(): + # Decimal rounding used to give the wrong exponent in some cases + assert str(mpf('1e600')) == '1.0e+600' + assert str(mpf('1e10000')) == '1.0e+10000' + +def test_str_prec0(): + assert to_str(from_float(1.234), 0) == '.0e+0' + assert to_str(from_float(1e-15), 0) == '.0e-15' + assert to_str(from_float(1e+15), 0) == '.0e+15' + assert to_str(from_float(-1e-15), 0) == '-.0e-15' + assert to_str(from_float(-1e+15), 0) == '-.0e+15' + +def test_convert_rational(): + assert from_rational(30, 5, 53, round_nearest)[:3] == (0, 3, 1) + assert from_rational(-7, 4, 53, round_nearest)[:3] == (1, 7, -2) + assert to_rational(mpf('0.5')._mpf_) == (1, 2) + assert to_rational(mpf('1')._mpf_) == (1, 1) + pytest.raises(ValueError, lambda: to_rational(mpf('nan')._mpf_)) + pytest.raises(OverflowError, lambda: to_rational(mpf('inf')._mpf_)) + pytest.raises(OverflowError, lambda: to_rational(mpf('-inf')._mpf_)) + +def test_custom_class(): + class mympf: + @property + def _mpf_(self): + return mpf(3.5)._mpf_ + class mympc: + @property + def _mpc_(self): + return mpf(3.5)._mpf_, mpf(2.5)._mpf_ + assert mpf(2) + mympf() == 5.5 + assert mympf() + mpf(2) == 5.5 + assert mpf(mympf()) == 3.5 + assert mympc() + mpc(2) == mpc(5.5, 2.5) + assert mpc(2) + mympc() == mpc(5.5, 2.5) + assert mpc(mympc()) == (3.5+2.5j) + assert mpmathify(mympf()) == mpf(3.5) + assert mpmathify(mympc()) == mpc(3.5, 2.5) + +def test_conversion_methods(): + class SomethingRandom: + pass + class SomethingReal: + def _mpmath_(self, prec, rounding): + return mp.make_mpf(from_str('1.3', prec, rounding)) + class SomethingComplex: + def _mpmath_(self, prec, rounding): + return mp.make_mpc((from_str('1.3', prec, rounding), \ + from_str('1.7', prec, rounding))) + x = mpf(3) + z = mpc(3) + a = SomethingRandom() + y = SomethingReal() + w = SomethingComplex() + for d in [15, 45]: + mp.dps = d + assert (x+y).ae(mpf('4.3')) + assert (y+x).ae(mpf('4.3')) + assert (x+w).ae(mpc('4.3', '1.7')) + assert (w+x).ae(mpc('4.3', '1.7')) + assert (z+y).ae(mpc('4.3')) + assert (y+z).ae(mpc('4.3')) + assert (z+w).ae(mpc('4.3', '1.7')) + assert (w+z).ae(mpc('4.3', '1.7')) + x-y; y-x; x-w; w-x; z-y; y-z; z-w; w-z + x*y; y*x; x*w; w*x; z*y; y*z; z*w; w*z + x/y; y/x; x/w; w/x; z/y; y/z; z/w; w/z + x**y; y**x; x**w; w**x; z**y; y**z; z**w; w**z + x==y; y==x; x==w; w==x; z==y; y==z; z==w; w==z + mp.dps = 15 + assert x.__add__(a) is NotImplemented + assert x.__radd__(a) is NotImplemented + assert x.__lt__(a) is NotImplemented + assert x.__gt__(a) is NotImplemented + assert x.__le__(a) is NotImplemented + assert x.__ge__(a) is NotImplemented + assert x.__eq__(a) is NotImplemented + assert x.__ne__(a) is NotImplemented + assert x.__sub__(a) is NotImplemented + assert x.__rsub__(a) is NotImplemented + assert x.__mul__(a) is NotImplemented + assert x.__rmul__(a) is NotImplemented + assert x.__truediv__(a) is NotImplemented + assert x.__rtruediv__(a) is NotImplemented + assert x.__mod__(a) is NotImplemented + assert x.__rmod__(a) is NotImplemented + assert x.__pow__(a) is NotImplemented + assert x.__rpow__(a) is NotImplemented + assert z.__add__(a) is NotImplemented + assert z.__radd__(a) is NotImplemented + assert z.__eq__(a) is NotImplemented + assert z.__ne__(a) is NotImplemented + assert z.__sub__(a) is NotImplemented + assert z.__rsub__(a) is NotImplemented + assert z.__mul__(a) is NotImplemented + assert z.__rmul__(a) is NotImplemented + assert z.__truediv__(a) is NotImplemented + assert z.__rtruediv__(a) is NotImplemented + assert z.__pow__(a) is NotImplemented + assert z.__rpow__(a) is NotImplemented + +def test_mpmathify(): + assert mpmathify('1/2') == 0.5 + assert mpmathify('(1.0+1.0j)') == mpc(1, 1) + assert mpmathify('(1.2e-10 - 3.4e5j)') == mpc('1.2e-10', '-3.4e5') + assert mpmathify('1j') == mpc(1j) + assert mpmathify('oo') == mpf('inf') + assert mpmathify('+oo') == mpf('inf') + assert mpmathify('-oo') == mpf('-inf') + assert mpmathify('2+3*I') == mpc(2, 3) + assert mpmathify('2+3I') == mpc(2, 3) + assert mpmathify('2/3 + 4/5j') == mpc(2/3, 4/5) + +def test_issue548(): + try: + # This expression is invalid, but may trigger the ReDOS vulnerability + # in the regular expression for parsing complex numbers. + mpmathify('(' + '1' * 5000 + '!j') + except: + return + # The expression is invalid and should raise an exception. + assert False + +def test_compatibility(): + from packaging.version import Version, parse + np = pytest.importorskip("numpy") + # numpy types + for typecode in (np.typecodes['AllInteger'] + + np.typecodes['Float'] + + np.typecodes['Complex']): + nptype = np.dtype(typecode).type + if issubclass(nptype, np.complexfloating): + x = nptype(complex(0.5, -0.5)) + elif issubclass(nptype, np.floating): + x = nptype(0.5) + elif issubclass(nptype, np.integer): + x = nptype(2) + # Handle the weird types + try: diff = np.abs(type(np.sqrt(x))(sqrt(x)) - np.sqrt(x)) + except: continue + assert diff < np.float64(2.0**-53) + assert mpf(np.float64('inf')) == inf + assert isnan(mp.npconvert(np.float64('nan'))) + if hasattr(np, "float128"): + mp.prec = 64 + assert (mp.npconvert(np.float128('0.841470984807896506652502321630298954')) == + mpf('0.841470984807896506653')) + mp.prec = 53 + # issues 382 and 539 + assert mp.sqrt(np.int64(1)) == mpf('1.0') + assert mpf(np.int64(1)) == mpf('1.0') + #Fraction and Decimal + oldprec = mp.prec + mp.prec = 1000 + decimal.getcontext().prec = mp.dps + assert sqrt(Fraction(2, 3)).ae(sqrt(mpf('2/3'))) + assert sqrt(Decimal(2)/Decimal(3)).ae(sqrt(mpf('2/3'))) + mp.prec = oldprec + assert mpmathify(np.array(123)) == mpf(123) + assert mpmathify(np.array(1.25)) == mpf(1.25) + assert mpmathify(np.array(0.5+1j)) == mpc(0.5+1j) + pytest.raises(TypeError, lambda: mpmathify(np.array([1]))) + +def test_issue465(): + assert mpf(Fraction(1, 3)) == mpf('0.33333333333333331') diff --git a/mpmath/tests/test_demos.py b/mpmath/tests/test_demos.py new file mode 100644 index 0000000..9b7ed64 --- /dev/null +++ b/mpmath/tests/test_demos.py @@ -0,0 +1,140 @@ +"""Tests for demo scripts.""" + +import os +import subprocess +import sys +import time + +import pexpect +import pytest + + +class Console(pexpect.spawn): + """Spawned console for testing.""" + + def __init__(self, command, timeout=60, _dumb=True): + env = os.environ.copy() + if _dumb: + env['TERM'] = 'dumb' + else: + env['TERM'] = 'xterm' + env['NO_COLOR'] = '1' + super().__init__(command, timeout=timeout, encoding='utf-8', env=env) + + def __del__(self): + self.send('exit()\r\n') + time.sleep(10) # a delay to allow coverage finish work + if self.isalive(): + self.terminate(force=True) + + +# TODO: how to test plots? // mandelbrot.py and plotting.py + + +def test_manydigits(): + expected = r""" +This script prints answers to a selection of the "Many Digits" +competition problems: http://www.cs.ru.nl/~milad/manydigits/problems.php + +The output for each problem is the first 100 digits after the +decimal point in the result. + +C01: sin(tan(cos(1))) +56451092986195980582768640645029648577648661582588 +56955552147245934844803576138875921296745208522197 + +C02: sqrt(e/pi) +93019136710263285866812462363333155602971092070428 +87264450006489855422345460234483872155723942699765 + +C03: sin((e+1)^3) +90949524105726624718554721945217426889396524221380 +80108799599078079083693175099387713504636663839042 + +C04: exp(pi*sqrt(2011)) +08911292681099318912549002226654964403231616008375 +14260187657441716605755144354088871641544234358651 + +C05: exp(exp(exp(1/2))) +33130360854569351505757451265398380886369247851475 +92794392700131812592190818654155341658216570329325 + +C06: arctanh(1-arctanh(1-arctanh(1-arctanh(1/pi)))) +12376761044118329658639748452701440281087636723733 +55412845934779398491016984592299074199915669907895 + +C07: pi^1000 +96790874439619754260235142488458363174182234378720 +67532446047250097144332075967536835025898399733192 + +C08: sin(6^(6^6)) +95395374345732063524921114340552534258118576365118 +22065161716596988369691845451204872928519972839961 + +C09: sin(10*arctan(tanh(pi*(2011^(1/2))/3))) +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999868216408727535391618 + +C10: (7+2^(1/5)-5*(8^(1/5)))^(1/3) + 4^(1/5)-2^(1/5) +00000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000 + +C11: tan(2^(1/2))+arctanh(sin(1)) +56031033792570862486989423169964262718414115287379 +65510969436882273871745968195963502918253580384966 + +C12: arcsin(1/e^2) + arcsinh(e^2) +83344680806041761874543293615785770019293386147122 +63906848335142800750122119140978807925425237483497 + +C17: S= -4*Zeta(2) - 2*Zeta(3) + 4*Zeta(2)*Zeta(3) + 2*Zeta(5) +99922283776383000876193574924756988603699551613617 +09442048984358627610229735501242221963535035597647 + +C18: Catalan G = Sum{i=0}{\infty}(-1)^i/(2i+1)^2 +91596559417721901505460351493238411077414937428167 +21342664981196217630197762547694793565129261151062 + +C21: Equation exp(cos(x)) = x +30296400121601255253211430697335802538621997810467 +85962942111799929657676507417868401302803638230948 + +C22: J = integral(sin(sin(sin(x)))), x=0..1 +40783902635001567262733691845249456720742376991339 +01533400692321748591761662552762179981626145798049 + +""" + result = subprocess.run([f'{sys.executable}', + 'demo/manydigits.py'], + capture_output=True, text=True) + assert result.stdout == expected + + +@pytest.mark.filterwarnings("ignore:.*:DeprecationWarning") +def test_pidigits(): + c = Console(f'{sys.executable} demo/pidigits.py') + assert c.expect_exact('> ') == 0 + assert c.send('10\n') == 3 + assert c.expect_exact('> ') == 0 + assert c.send('100\n') == 4 + assert c.expect_exact('> ') == 0 + assert c.send('\n') == 1 + assert c.expect('5820974944 5923078164 0628620899 ' + '8628034825 3421170679 : 100') == 0 + + +def test_sofa(): + result = subprocess.run([f'{sys.executable}', + 'demo/sofa.py'], + capture_output=True, text=True) + assert result.stdout == '2.2195316688719674255462841007968\n' + + +@pytest.mark.filterwarnings("ignore:.*:DeprecationWarning") +def test_taylor(): + c = Console(f'{sys.executable} demo/taylor.py') + assert c.expect_exact('Enter the value of x (e.g. 3.5): ') == 0 + assert c.send('1\n') == 2 + assert c.expect_exact('Enter the number of terms n (e.g. 10): ') == 0 + assert c.send('10\n') == 3 + assert c.expect_exact('[2.7182818011463827368, 2.7182818011463862895]') == 0 diff --git a/mpmath/tests/test_diff.py b/mpmath/tests/test_diff.py new file mode 100644 index 0000000..4885b7a --- /dev/null +++ b/mpmath/tests/test_diff.py @@ -0,0 +1,59 @@ +from mpmath import (chop, cos, diff, diffs, diffun, e, exp, j, log, sin, sqrt, + taylor) + + +def test_diff(): + assert diff(log, 2.0, n=0).ae(log(2)) + assert diff(cos, 1.0).ae(-sin(1)) + assert diff(abs, 0.0) == 0 + assert diff(abs, 0.0, direction=1) == 1 + assert diff(abs, 0.0, direction=-1) == -1 + assert diff(exp, 1.0).ae(e) + assert diff(exp, 1.0, n=5).ae(e) + assert diff(exp, 2.0, n=5, direction=3*j).ae(e**2) + assert diff(lambda x: x**2, 3.0, method='quad').ae(6) + assert diff(lambda x: 3+x**5, 3.0, n=2, method='quad').ae(540) + assert diff(lambda x: 3+x**5, 3.0, n=2, method='step').ae(540) + assert diffun(sin)(2).ae(cos(2)) + assert diffun(sin, n=2)(2).ae(-sin(2)) + +def test_diffs(): + assert [chop(d) for d in diffs(sin, 0, 1)] == [0, 1] + assert [chop(d) for d in diffs(sin, 0, 1, method='quad')] == [0, 1] + assert [chop(d) for d in diffs(sin, 0, 2)] == [0, 1, 0] + assert [chop(d) for d in diffs(sin, 0, 2, method='quad')] == [0, 1, 0] + +def test_taylor(): + # Easy to test since the coefficients are exact in floating-point + assert taylor(sqrt, 1, 4) == [1, 0.5, -0.125, 0.0625, -0.0390625] + +def test_diff_partial(): + x,y,z = xyz = 2,3,7 + f = lambda x,y,z: 3*x**2 * (y+2)**3 * z**5 + assert diff(f, xyz, (0,0,0)).ae(25210500) + assert diff(f, xyz, (0,0,1)).ae(18007500) + assert diff(f, xyz, (0,0,2)).ae(10290000) + assert diff(f, xyz, (0,1,0)).ae(15126300) + assert diff(f, xyz, (0,1,1)).ae(10804500) + assert diff(f, xyz, (0,1,2)).ae(6174000) + assert diff(f, xyz, (0,2,0)).ae(6050520) + assert diff(f, xyz, (0,2,1)).ae(4321800) + assert diff(f, xyz, (0,2,2)).ae(2469600) + assert diff(f, xyz, (1,0,0)).ae(25210500) + assert diff(f, xyz, (1,0,1)).ae(18007500) + assert diff(f, xyz, (1,0,2)).ae(10290000) + assert diff(f, xyz, (1,1,0)).ae(15126300) + assert diff(f, xyz, (1,1,1)).ae(10804500) + assert diff(f, xyz, (1,1,2)).ae(6174000) + assert diff(f, xyz, (1,2,0)).ae(6050520) + assert diff(f, xyz, (1,2,1)).ae(4321800) + assert diff(f, xyz, (1,2,2)).ae(2469600) + assert diff(f, xyz, (2,0,0)).ae(12605250) + assert diff(f, xyz, (2,0,1)).ae(9003750) + assert diff(f, xyz, (2,0,2)).ae(5145000) + assert diff(f, xyz, (2,1,0)).ae(7563150) + assert diff(f, xyz, (2,1,1)).ae(5402250) + assert diff(f, xyz, (2,1,2)).ae(3087000) + assert diff(f, xyz, (2,2,0)).ae(3025260) + assert diff(f, xyz, (2,2,1)).ae(2160900) + assert diff(f, xyz, (2,2,2)).ae(1234800) diff --git a/mpmath/tests/test_division.py b/mpmath/tests/test_division.py new file mode 100644 index 0000000..2024937 --- /dev/null +++ b/mpmath/tests/test_division.py @@ -0,0 +1,139 @@ +from random import choice, randint, seed + +from mpmath import mpf +from mpmath.libmp import (from_int, from_str, mpf_div, mpf_mul, round_ceiling, + round_down, round_floor, round_nearest, round_up) +from mpmath.libmp.libintmath import trailing +from mpmath.libmp.libmpf import mpf_rdiv_int + + +def test_div_1_3(): + a = from_int(1) + b = from_int(3) + c = from_int(-1) + + # floor rounds down, ceiling rounds up + assert mpf_div(a, b, 7, round_floor) == from_str('0.01010101', base=2) + assert mpf_div(a, b, 7, round_ceiling) == from_str('0.01010110', base=2) + assert mpf_div(a, b, 7, round_down) == from_str('0.01010101', base=2) + assert mpf_div(a, b, 7, round_up) == from_str('0.01010110', base=2) + assert mpf_div(a, b, 7, round_nearest) == from_str('0.01010101', base=2) + + # floor rounds up, ceiling rounds down + assert mpf_div(c, b, 7, round_floor) == from_str('-0.01010110', base=2) + assert mpf_div(c, b, 7, round_ceiling) == from_str('-0.01010101', base=2) + assert mpf_div(c, b, 7, round_down) == from_str('-0.01010101', base=2) + assert mpf_div(c, b, 7, round_up) == from_str('-0.01010110', base=2) + assert mpf_div(c, b, 7, round_nearest) == from_str('-0.01010101', base=2) + +def test_mpf_divi_1_3(): + a = 1 + b = from_int(3) + c = -1 + assert mpf_rdiv_int(a, b, 7, round_floor) == from_str('0.01010101', base=2) + assert mpf_rdiv_int(a, b, 7, round_ceiling) == from_str('0.01010110', base=2) + assert mpf_rdiv_int(a, b, 7, round_down) == from_str('0.01010101', base=2) + assert mpf_rdiv_int(a, b, 7, round_up) == from_str('0.01010110', base=2) + assert mpf_rdiv_int(a, b, 7, round_nearest) == from_str('0.01010101', base=2) + assert mpf_rdiv_int(c, b, 7, round_floor) == from_str('-0.01010110', base=2) + assert mpf_rdiv_int(c, b, 7, round_ceiling) == from_str('-0.01010101', base=2) + assert mpf_rdiv_int(c, b, 7, round_down) == from_str('-0.01010101', base=2) + assert mpf_rdiv_int(c, b, 7, round_up) == from_str('-0.01010110', base=2) + assert mpf_rdiv_int(c, b, 7, round_nearest) == from_str('-0.01010101', base=2) + + +def test_div_300(): + + q = from_int(1000000) + a = from_int(300499999) # a/q is a little less than a half-integer + b = from_int(300500000) # b/q exactly a half-integer + c = from_int(300500001) # c/q is a little more than a half-integer + + # Check nearest integer rounding (prec=9 as 2**8 < 300 < 2**9) + + assert mpf_div(a, q, 9, round_down) == from_int(300) + assert mpf_div(b, q, 9, round_down) == from_int(300) + assert mpf_div(c, q, 9, round_down) == from_int(300) + assert mpf_div(a, q, 9, round_up) == from_int(301) + assert mpf_div(b, q, 9, round_up) == from_int(301) + assert mpf_div(c, q, 9, round_up) == from_int(301) + + # Nearest even integer is down + assert mpf_div(a, q, 9, round_nearest) == from_int(300) + assert mpf_div(b, q, 9, round_nearest) == from_int(300) + assert mpf_div(c, q, 9, round_nearest) == from_int(301) + + # Nearest even integer is up + a = from_int(301499999) + b = from_int(301500000) + c = from_int(301500001) + assert mpf_div(a, q, 9, round_nearest) == from_int(301) + assert mpf_div(b, q, 9, round_nearest) == from_int(302) + assert mpf_div(c, q, 9, round_nearest) == from_int(302) + + +def test_tight_integer_division(): + # Test that integer division at tightest possible precision is exact + N = 100 + seed(1) + for i in range(N): + a = choice([1, -1]) * randint(1, 1< 1: + print("original matrix (hessenberg):\n", A) + + n = A.rows + + Q, H = mp.hessenberg(A) + + if verbose > 1: + print("Q:\n",Q) + print("H:\n",H) + + B = Q * H * Q.transpose_conj() + + eps = mp.exp(0.8 * mp.log(mp.eps)) + + err0 = 0 + for x in range(n): + for y in range(n): + err0 += abs(A[y,x] - B[y,x]) + err0 /= n * n + + err1 = 0 + for x in range(n): + for y in range(x + 2, n): + err1 += abs(H[y,x]) + + if verbose > 0: + print("difference (H):", err0, err1) + + if verbose > 1: + print("B:\n", B) + + assert err0 < eps + assert err1 == 0 + + +def run_schur(A, verbose = 0): + if verbose > 1: + print("original matrix (schur):\n", A) + + n = A.rows + + Q, R = mp.schur(A) + + if verbose > 1: + print("Q:\n", Q) + print("R:\n", R) + + B = Q * R * Q.transpose_conj() + C = Q * Q.transpose_conj() + + eps = mp.exp(0.8 * mp.log(mp.eps)) + + err0 = 0 + for x in range(n): + for y in range(n): + err0 += abs(A[y,x] - B[y,x]) + err0 /= n * n + + err1 = 0 + for x in range(n): + for y in range(n): + if x == y: + C[y,x] -= 1 + err1 += abs(C[y,x]) + err1 /= n * n + + err2 = 0 + for x in range(n): + for y in range(x + 1, n): + err2 += abs(R[y,x]) + + if verbose > 0: + print("difference (S):", err0, err1, err2) + + if verbose > 1: + print("B:\n", B) + + assert err0 < eps + assert err1 < eps + assert err2 == 0 + +def run_eig(A, verbose = 0): + if verbose > 1: + print("original matrix (eig):\n", A) + + n = A.rows + + E, EL, ER = mp.eig(A, left = True, right = True) + + if verbose > 1: + print("E:\n", E) + print("EL:\n", EL) + print("ER:\n", ER) + + eps = mp.exp(0.8 * mp.log(mp.eps)) + + err0 = 0 + for i in range(n): + B = A * ER[:,i] - E[i] * ER[:,i] + err0 = max(err0, mp.mnorm(B)) + + B = EL[i,:] * A - EL[i,:] * E[i] + err0 = max(err0, mp.mnorm(B)) + + err0 /= n * n + + if verbose > 0: + print("difference (E):", err0) + + assert err0 < eps + +##################### + +def test_eig_dyn(): + v = 0 + for i in range(5): + n = 1 + int(mp.rand() * 5) + if mp.rand() > 0.5: + # real + A = 2 * mp.randmatrix(n, n) - 1 + if mp.rand() > 0.5: + A *= 10 + for x in range(n): + for y in range(n): + A[x,y] = int(A[x,y]) + else: + A = (2 * mp.randmatrix(n, n) - 1) + 1j * (2 * mp.randmatrix(n, n) - 1) + if mp.rand() > 0.5: + A *= 10 + for x in range(n): + for y in range(n): + A[x,y] = int(mp.re(A[x,y])) + 1j * int(mp.im(A[x,y])) + + run_hessenberg(A, verbose = v) + run_schur(A, verbose = v) + run_eig(A, verbose = v) + +def test_eig(): + v = 0 + AS = [] + + A = mp.matrix([[2, 1, 0], # jordan block of size 3 + [0, 2, 1], + [0, 0, 2]]) + AS.append(A) + AS.append(A.transpose()) + + A = mp.matrix([[2, 0, 0], # jordan block of size 2 + [0, 2, 1], + [0, 0, 2]]) + AS.append(A) + AS.append(A.transpose()) + + A = mp.matrix([[2, 0, 1], # jordan block of size 2 + [0, 2, 0], + [0, 0, 2]]) + AS.append(A) + AS.append(A.transpose()) + + A = mp.matrix([[0, 0, 1], # cyclic + [1, 0, 0], + [0, 1, 0]]) + AS.append(A) + AS.append(A.transpose()) + + for A in AS: + run_hessenberg(A, verbose = v) + run_schur(A, verbose = v) + run_eig(A, verbose = v) + + A = mp.matrix(1) + assert mp.eig(A, left=False, right=False) == [0] + + +def test_fp_eig(): + A = fp.matrix([[1, 2], + [3, 4]]) + E, ER = fp.eig(A) + assert all(_ == 0 for _ in fp.chop(A * ER[:,0] - E[0] * ER[:,0])) + assert all(_ == 0 for _ in fp.chop(A * ER[:,1] - E[1] * ER[:,1])) diff --git a/mpmath/tests/test_eigen_symmetric.py b/mpmath/tests/test_eigen_symmetric.py new file mode 100644 index 0000000..449c77f --- /dev/null +++ b/mpmath/tests/test_eigen_symmetric.py @@ -0,0 +1,352 @@ +from mpmath import mp + + +def run_eigsy(A, verbose = False): + if verbose: + print("original matrix:\n", str(A)) + + D, Q = mp.eigsy(A) + B = Q * mp.diag(D) * Q.transpose() + C = A - B + E = Q * Q.transpose() - mp.eye(A.rows) + + if verbose: + print("eigenvalues:\n", D) + print("eigenvectors:\n", Q) + + NC = mp.mnorm(C) + NE = mp.mnorm(E) + + if verbose: + print("difference:", NC, "\n", C, "\n") + print("difference:", NE, "\n", E, "\n") + + eps = mp.exp( 0.8 * mp.log(mp.eps)) + + assert NC < eps + assert NE < eps + + return NC + +def run_eighe(A, verbose = False): + if verbose: + print("original matrix:\n", str(A)) + + D, Q = mp.eighe(A) + B = Q * mp.diag(D) * Q.transpose_conj() + C = A - B + E = Q * Q.transpose_conj() - mp.eye(A.rows) + + if verbose: + print("eigenvalues:\n", D) + print("eigenvectors:\n", Q) + + NC = mp.mnorm(C) + NE = mp.mnorm(E) + + if verbose: + print("difference:", NC, "\n", C, "\n") + print("difference:", NE, "\n", E, "\n") + + eps = mp.exp( 0.8 * mp.log(mp.eps)) + + assert NC < eps + assert NE < eps + + return NC + +def run_svd_r(A, full_matrices = False, verbose = True): + + m, n = A.rows, A.cols + + eps = mp.exp(0.8 * mp.log(mp.eps)) + + if verbose: + print("original matrix:\n", str(A)) + print("full", full_matrices) + + U, S0, V = mp.svd_r(A, full_matrices = full_matrices) + + S = mp.zeros(U.cols, V.rows) + for j in range(min(m, n)): + S[j,j] = S0[j] + + if verbose: + print("U:\n", str(U)) + print("S:\n", str(S0)) + print("V:\n", str(V)) + + C = U * S * V - A + err = mp.mnorm(C) + if verbose: + print("C\n", str(C), "\n", err) + assert err < eps + + D = V * V.transpose() - mp.eye(V.rows) + err = mp.mnorm(D) + if verbose: + print("D:\n", str(D), "\n", err) + assert err < eps + + E = U.transpose() * U - mp.eye(U.cols) + err = mp.mnorm(E) + if verbose: + print("E:\n", str(E), "\n", err) + assert err < eps + +def run_svd_c(A, full_matrices = False, verbose = True): + + m, n = A.rows, A.cols + + eps = mp.exp(0.8 * mp.log(mp.eps)) + + if verbose: + print("original matrix:\n", str(A)) + print("full", full_matrices) + + U, S0, V = mp.svd_c(A, full_matrices = full_matrices) + + S = mp.zeros(U.cols, V.rows) + for j in range(min(m, n)): + S[j,j] = S0[j] + + if verbose: + print("U:\n", str(U)) + print("S:\n", str(S0)) + print("V:\n", str(V)) + + C = U * S * V - A + err = mp.mnorm(C) + if verbose: + print("C\n", str(C), "\n", err) + assert err < eps + + D = V * V.transpose_conj() - mp.eye(V.rows) + err = mp.mnorm(D) + if verbose: + print("D:\n", str(D), "\n", err) + assert err < eps + + E = U.transpose_conj() * U - mp.eye(U.cols) + err = mp.mnorm(E) + if verbose: + print("E:\n", str(E), "\n", err) + assert err < eps + +def run_gauss(qtype, a, b): + eps = 1e-5 + + d, e = mp.gauss_quadrature(len(a), qtype) + d -= mp.matrix(a) + e -= mp.matrix(b) + + assert mp.mnorm(d) < eps + assert mp.mnorm(e) < eps + +def irandmatrix(n, r=10): + """ + random matrix with integer entries + """ + A = mp.matrix(n, n) + for i in range(n): + for j in range(n): + A[i,j] = int((2 * mp.rand() - 1) * r) + return A + +####################### + +def test_eighe_fixed_matrix(): + A = mp.matrix([[2, 3], [3, 5]]) + run_eigsy(A) + run_eighe(A) + + A = mp.matrix([[7, -11], [-11, 13]]) + run_eigsy(A) + run_eighe(A) + + A = mp.matrix([[2, 11, 7], [11, 3, 13], [7, 13, 5]]) + run_eigsy(A) + run_eighe(A) + + A = mp.matrix([[2, 0, 7], [0, 3, 1], [7, 1, 5]]) + run_eigsy(A) + run_eighe(A) + + # + + A = mp.matrix([[2, 3+7j], [3-7j, 5]]) + run_eighe(A) + + A = mp.matrix([[2, -11j, 0], [+11j, 3, 29j], [0, -29j, 5]]) + run_eighe(A) + + A = mp.matrix([[2, 11 + 17j, 7 + 19j], [11 - 17j, 3, -13 + 23j], [7 - 19j, -13 - 23j, 5]]) + run_eighe(A) + +def test_eigsy_randmatrix(): + N = 5 + + for a in range(10): + A = 2 * mp.randmatrix(N, N) - 1 + + for i in range(0, N): + for j in range(i + 1, N): + A[j,i] = A[i,j] + + run_eigsy(A) + +def test_eighe_randmatrix(): + N = 5 + + for a in range(10): + A = (2 * mp.randmatrix(N, N) - 1) + 1j * (2 * mp.randmatrix(N, N) - 1) + + for i in range(0, N): + A[i,i] = mp.re(A[i,i]) + for j in range(i + 1, N): + A[j,i] = mp.conj(A[i,j]) + + run_eighe(A) + +def test_eigsy_irandmatrix(): + N = 4 + R = 4 + + for a in range(10): + A=irandmatrix(N, R) + + for i in range(0, N): + for j in range(i + 1, N): + A[j,i] = A[i,j] + + run_eigsy(A) + +def test_eighe_irandmatrix(): + N = 4 + R = 4 + + for a in range(10): + A=irandmatrix(N, R) + 1j * irandmatrix(N, R) + + for i in range(0, N): + A[i,i] = mp.re(A[i,i]) + for j in range(i + 1, N): + A[j,i] = mp.conj(A[i,j]) + + run_eighe(A) + +def test_svd_r_rand(): + for i in range(5): + full = mp.rand() > 0.5 + m = 1 + int(mp.rand() * 10) + n = 1 + int(mp.rand() * 10) + A = 2 * mp.randmatrix(m, n) - 1 + if mp.rand() > 0.5: + A *= 10 + for x in range(m): + for y in range(n): + A[x,y]=int(A[x,y]) + + run_svd_r(A, full_matrices = full, verbose = False) + +def test_svd_c_rand(): + for i in range(5): + full = mp.rand() > 0.5 + m = 1 + int(mp.rand() * 10) + n = 1 + int(mp.rand() * 10) + A = (2 * mp.randmatrix(m, n) - 1) + 1j * (2 * mp.randmatrix(m, n) - 1) + if mp.rand() > 0.5: + A *= 10 + for x in range(m): + for y in range(n): + A[x,y]=int(mp.re(A[x,y])) + 1j * int(mp.im(A[x,y])) + + run_svd_c(A, full_matrices=full, verbose=False) + +def test_svd_test_case(): + # a test case from Golub and Reinsch + # (see wilkinson/reinsch: handbook for auto. comp., vol ii-linear algebra, 134-151(1971).) + + eps = mp.exp(0.8 * mp.log(mp.eps)) + + a = [[22, 10, 2, 3, 7], + [14, 7, 10, 0, 8], + [-1, 13, -1, -11, 3], + [-3, -2, 13, -2, 4], + [ 9, 8, 1, -2, 4], + [ 9, 1, -7, 5, -1], + [ 2, -6, 6, 5, 1], + [ 4, 5, 0, -2, 2]] + + a = mp.matrix(a) + b = mp.matrix([mp.sqrt(1248), 20, mp.sqrt(384), 0, 0]) + + S = mp.svd_r(a, compute_uv = False) + S -= b + assert mp.mnorm(S) < eps + + S = mp.svd_c(a, compute_uv = False) + S -= b + assert mp.mnorm(S) < eps + + +def test_gauss_quadrature_static(): + a = [-0.57735027, 0.57735027] + b = [ 1, 1] + run_gauss("legendre", a , b) + + a = [ -0.906179846, -0.538469310, 0, 0.538469310, 0.906179846] + b = [ 0.23692689, 0.47862867, 0.56888889, 0.47862867, 0.23692689] + run_gauss("legendre", a , b) + + a = [ 0.06943184, 0.33000948, 0.66999052, 0.93056816] + b = [ 0.17392742, 0.32607258, 0.32607258, 0.17392742] + run_gauss("legendre01", a , b) + + a = [-0.70710678, 0.70710678] + b = [ 0.88622693, 0.88622693] + run_gauss("hermite", a , b) + + a = [ -2.02018287, -0.958572465, 0, 0.958572465, 2.02018287] + b = [ 0.01995324, 0.39361932, 0.94530872, 0.39361932, 0.01995324] + run_gauss("hermite", a , b) + + a = [ 0.41577456, 2.29428036, 6.28994508] + b = [ 0.71109301, 0.27851773, 0.01038926] + run_gauss("laguerre", a , b) + +def test_gauss_quadrature_dynamic(verbose = False): + n = 5 + + A = mp.randmatrix(2 * n, 1) + + def F(x): + r = 0 + for i in range(len(A) - 1, -1, -1): + r = r * x + A[i] + return r + + def run(qtype, FW, R, alpha = 0, beta = 0): + X, W = mp.gauss_quadrature(n, qtype, alpha = alpha, beta = beta) + + a = 0 + for i in range(len(X)): + a += W[i] * F(X[i]) + + b = mp.quad(lambda x: FW(x) * F(x), R) + + c = mp.fabs(a - b) + + if verbose: + print(qtype, c, a, b) + + assert c < 1e-5 + + run("legendre", lambda x: 1, [-1, 1]) + run("legendre01", lambda x: 1, [0, 1]) + run("hermite", lambda x: mp.exp(-x*x), [-mp.inf, mp.inf]) + run("laguerre", lambda x: mp.exp(-x), [0, mp.inf]) + run("glaguerre", lambda x: mp.sqrt(x)*mp.exp(-x), [0, mp.inf], alpha = 1 / mp.mpf(2)) + run("chebyshev1", lambda x: 1/mp.sqrt(1-x*x), [-1, 1]) + run("chebyshev2", lambda x: mp.sqrt(1-x*x), [-1, 1]) + run("jacobi", lambda x: (1-x)**(1/mp.mpf(3)) * (1+x)**(1/mp.mpf(5)), [-1, 1], alpha = 1 / mp.mpf(3), beta = 1 / mp.mpf(5) ) diff --git a/mpmath/tests/test_elliptic.py b/mpmath/tests/test_elliptic.py new file mode 100644 index 0000000..b2fec8c --- /dev/null +++ b/mpmath/tests/test_elliptic.py @@ -0,0 +1,1160 @@ +""" +Limited tests of the elliptic functions module. A full suite of +extensive testing can be found in elliptic_torture_tests.py + +Author of the first version: M.T. Taschuk + +**References** + +1. [AbramowitzStegun]_ +2. [WhittakerWatson]_ + +""" + +import random + +import pytest + +from mpmath import (cos, cosh, cot, coth, csc, csch, diff, ellipe, ellipfun, + ellipk, ellippi, elliprc, elliprd, elliprf, elliprg, + elliprj, eps, exp, gamma, inf, isnan, j, jtheta, kleinj, + ldexp, ln2, mp, mpc, mpf, nan, nsum, pi, polyroots, qfrom, + sec, sech, sin, sinh, sqrt, tan, tanh, weierhalfperiods, + weierinvariants, weierp, weierpinv, weierpprime, + weiersigma, weierzeta) + + +def mpc_ae(a, b, eps=eps): + res = True + res = res and a.real.ae(b.real, eps) + res = res and a.imag.ae(b.imag, eps) + return res + +zero = mpf(0) +one = mpf(1) + +jsn = ellipfun('sn') +jcn = ellipfun('cn') +jdn = ellipfun('dn') + +calculate_nome = lambda k: qfrom(k=k) + +def test_ellipfun(): + assert ellipfun('ss', 0, 0) == 1 + assert ellipfun('cc', 0, 0) == 1 + assert ellipfun('dd', 0, 0) == 1 + assert ellipfun('nn', 0, 0) == 1 + assert ellipfun('sn', 0.25, 0).ae(sin(0.25)) + assert ellipfun('cn', 0.25, 0).ae(cos(0.25)) + assert ellipfun('dn', 0.25, 0).ae(1) + assert ellipfun('ns', 0.25, 0).ae(csc(0.25)) + assert ellipfun('nc', 0.25, 0).ae(sec(0.25)) + assert ellipfun('nd', 0.25, 0).ae(1) + assert ellipfun('sc', 0.25, 0).ae(tan(0.25)) + assert ellipfun('sd', 0.25, 0).ae(sin(0.25)) + assert ellipfun('cd', 0.25, 0).ae(cos(0.25)) + assert ellipfun('cs', 0.25, 0).ae(cot(0.25)) + assert ellipfun('dc', 0.25, 0).ae(sec(0.25)) + assert ellipfun('ds', 0.25, 0).ae(csc(0.25)) + assert ellipfun('sn', 0.25, 1).ae(tanh(0.25)) + assert ellipfun('cn', 0.25, 1).ae(sech(0.25)) + assert ellipfun('dn', 0.25, 1).ae(sech(0.25)) + assert ellipfun('ns', 0.25, 1).ae(coth(0.25)) + assert ellipfun('nc', 0.25, 1).ae(cosh(0.25)) + assert ellipfun('nd', 0.25, 1).ae(cosh(0.25)) + assert ellipfun('sc', 0.25, 1).ae(sinh(0.25)) + assert ellipfun('sd', 0.25, 1).ae(sinh(0.25)) + assert ellipfun('cd', 0.25, 1).ae(1) + assert ellipfun('cs', 0.25, 1).ae(csch(0.25)) + assert ellipfun('dc', 0.25, 1).ae(1) + assert ellipfun('ds', 0.25, 1).ae(csch(0.25)) + assert ellipfun('sn', 0.25, 0.5).ae(0.24615967096986145833) + assert ellipfun('cn', 0.25, 0.5).ae(0.96922928989378439337) + assert ellipfun('dn', 0.25, 0.5).ae(0.98473484156599474563) + assert ellipfun('ns', 0.25, 0.5).ae(4.0624038700573130369) + assert ellipfun('nc', 0.25, 0.5).ae(1.0317476065024692949) + assert ellipfun('nd', 0.25, 0.5).ae(1.0155017958029488665) + assert ellipfun('sc', 0.25, 0.5).ae(0.25397465134058993408) + assert ellipfun('sd', 0.25, 0.5).ae(0.24997558792415733063) + assert ellipfun('cd', 0.25, 0.5).ae(0.98425408443195497052) + assert ellipfun('cs', 0.25, 0.5).ae(3.9374008182374110826) + assert ellipfun('dc', 0.25, 0.5).ae(1.0159978158253033913) + assert ellipfun('ds', 0.25, 0.5).ae(4.0003906313579720593) + + + + +def test_calculate_nome(): + mp.dps = 100 + + q = calculate_nome(zero) + assert q == zero + + mp.dps = 25 + # used Mathematica's EllipticNomeQ[m] + math1 = [(mpf(1)/10, mpf('0.006584651553858370274473060')), + (mpf(2)/10, mpf('0.01394285727531826872146409')), + (mpf(3)/10, mpf('0.02227743615715350822901627')), + (mpf(4)/10, mpf('0.03188334731336317755064299')), + (mpf(5)/10, mpf('0.04321391826377224977441774')), + (mpf(6)/10, mpf('0.05702025781460967637754953')), + (mpf(7)/10, mpf('0.07468994353717944761143751')), + (mpf(8)/10, mpf('0.09927369733882489703607378')), + (mpf(9)/10, mpf('0.1401731269542615524091055')), + (mpf(9)/10, mpf('0.1401731269542615524091055'))] + + for i in math1: + m = i[0] + q = calculate_nome(sqrt(m)) + assert q.ae(i[1]) + + assert qfrom(m=mp.ninf).ae(mpf('-1.0')) + +def test_jtheta(): + mp.dps = 25 + + z = q = zero + for n in range(1,5): + value = jtheta(n, z, q) + assert value == (n-1)//2 + + for q in [one, mpf(2)]: + for n in range(1,5): + pytest.raises(ValueError, lambda: jtheta(n, z, q)) + + z = one/10 + q = one/11 + + # Mathematical N[EllipticTheta[1, 1/10, 1/11], 25] + res = mpf('0.1069552990104042681962096') + result = jtheta(1, z, q) + assert result.ae(res) + + # Mathematica N[EllipticTheta[2, 1/10, 1/11], 25] + res = mpf('1.101385760258855791140606') + result = jtheta(2, z, q) + assert result.ae(res) + + # Mathematica N[EllipticTheta[3, 1/10, 1/11], 25] + res = mpf('1.178319743354331061795905') + result = jtheta(3, z, q) + assert result.ae(res) + + # Mathematica N[EllipticTheta[4, 1/10, 1/11], 25] + res = mpf('0.8219318954665153577314573') + result = jtheta(4, z, q) + assert result.ae(res) + + # test for sin zeros for jtheta(1, z, q) + # test for cos zeros for jtheta(2, z, q) + z1 = pi + z2 = pi/2 + for i in range(10): + qstring = str(random.random()) + q = mpf(qstring) + result = jtheta(1, z1, q) + assert result.ae(0), q + result = jtheta(2, z2, q) + assert result.ae(0), q + +def test_jtheta_issue_79(): + # near the circle of covergence |q| = 1 the convergence slows + # down; for |q| > Q_LIM the theta functions raise ValueError + mp.dps = 30 + mp.dps += 30 + q = mpf(6)/10 - one/10**6 - mpf(8)/10 * j + mp.dps -= 30 + # Mathematica run first + # N[EllipticTheta[3, 1, 6/10 - 10^-6 - 8/10*I], 2000] + # then it works: + # N[EllipticTheta[3, 1, 6/10 - 10^-6 - 8/10*I], 30] + res = mpf('32.0031009628901652627099524264') + \ + mpf('16.6153027998236087899308935624') * j + result = jtheta(3, 1, q) + mp.dps += 30 + q = mpf(6)/10 - one/10**7 - mpf(8)/10 * j + mp.dps -= 30 + # N[EllipticTheta[3, 1, 6/10 - 10^-7 - 8/10 I], 30] + # with $MaxExtraPrecision = 10000 + assert mpc_ae(jtheta(3, 1, q), + mpc('1.19143507322246897676014934229' + '+1.07603569085504321033898492583j'), + 100*eps) + + # check that for abs(q) >= 1 a ValueError exception is raised + pytest.raises(ValueError, lambda: jtheta(3, 1, 1)) + pytest.raises(ValueError, lambda: jtheta(3, 1, 2)) + + # bug reported in issue 79 + mp.dps = 100 + z = (1+j)/3 + q = mpf(368983957219251)/10**15 + mpf(636363636363636)/10**15 * j + # Mathematica N[EllipticTheta[1, z, q], 35] + res = mpf('2.4439389177990737589761828991467471') + \ + mpf('0.5446453005688226915290954851851490') *j + mp.dps = 30 + result = jtheta(1, z, q) + assert result.ae(res) + mp.dps = 80 + z = 3 + 4*j + q = 0.5 + 0.5*j + r1 = jtheta(1, z, q) + mp.dps = 15 + r2 = jtheta(1, z, q) + assert r1.ae(r2) + mp.dps = 80 + z = 3 + j + q1 = exp(j*3) + # longer test + # for n in range(1, 6) + for n in range(1, 2): + mp.dps = 80 + q = q1*(1 - mpf(1)/10**n) + r1 = jtheta(1, z, q) + mp.dps = 15 + r2 = jtheta(1, z, q) + assert r1.ae(r2) + mp.dps = 15 + # issue 79 about high derivatives + assert jtheta(3, 4.5, 0.25, 9).ae(1359.04892680683) + assert jtheta(3, 4.5, 0.25, 50).ae(-6.14832772630905e+33) + mp.dps = 50 + r = jtheta(3, 4.5, 0.25, 9) + assert r.ae('1359.048926806828939547859396600218966947753213803') + r = jtheta(3, 4.5, 0.25, 50) + assert r.ae('-6148327726309051673317975084654262.4119215720343656') + +def test_jtheta_invalid_n(): + pytest.raises(ValueError, jtheta, 5, 0.5, 0.3) + pytest.raises(ValueError, jtheta, 0, 0.5, 0.3) + pytest.raises(ValueError, jtheta, 5, 0.5, 0.3) + pytest.raises(ValueError, jtheta, 0, 0.5, 0.3) + +def test_issue_930(): + # for |q| close to 1 with complex z, jtheta's direct nome series + # suffered catastrophic cancellation and lost all precision. + # The PSL(2, Z) modular-reduction path fixes it. + # + mp.dps = 70 + q = mpf(99)/100 + z = 99+1j + mp.dps = 50 + eps1 = 100*eps + # Reference values computed with: + # N[N[Derivative[0, nd, 0][EllipticTheta][n, 99+I, 99/100], 300], 50]. + ref = { # jtheta(n, 99+I, q, derivative=nd) + (1, 0): mpc('1.779258740399125063008605585919688748246270941962e43+2.4531552106585761829132327656277889232764080000082e44j'), + (2, 0): mpc('-1.4988039420376218477379546959304839688561560327952e-57+1.126724649309213092636325219160375156534138267338e-58j'), + (3, 0): mpc('-1.4988039419916629783968527738038480059854883969519e-57+1.126724649277094787663809804454822463406493776515e-58j'), + (4, 0): mpc('-1.779258740399125063008605585919688748246270941962e43-2.4531552106585761829132327656277889232764080000082e44j'), + (1, 1): mpc('4.8676346890955099082032405931623721782101881475052e46-5.485160172971341958206637770436412266629883709382e45j'), + (2, 1): mpc('-4.3420315824972909797400114725699377626910985932552e-55+3.3258620549120325016554512103625432300841517973459e-55j'), + (3, 1): mpc('-4.3420315826509862620149131749487142244793486272826e-55+3.3258620548308694307995377047445340481881823181686e-55j'), + (4, 1): mpc('-4.8676346890955099082032405931623721782101881475052e46+5.485160172971341958206637770436412266629883709382e45j'), + (1, 2): mpc('-1.4809058110492439979937435179858838426773452717825e48-9.6918513863888705669477095057488664706217962565546e48j'), + (2, 2): mpc('-6.580173968678905306210609222130658781889044450531e-53+1.8770881118790176343351463096530497188645552821989e-52j'), + (3, 2): mpc('-6.5801739683487208669817955792020573096817453981375e-53+1.8770881119356228857428818901066687299013630001154e-53j'), + (4, 2): mpc('1.4809058110492439979937435179858838426773452717825e48+9.6918513863888705669477095057488664706217962565546e48j'), + } + for (n_, nd), r in ref.items(): + assert mpc_ae(jtheta(n_, z, q, derivative=nd), r, eps1), (n_, nd) + + # larger Im(z): N[EllipticTheta[n, 99+2I, 99/100], 300], 50] + ref_z2 = { + 2: mpc('6.4249758037350518725606864570348600840795103250997e72' + '-9.714841891170799887220046736000777198237616535325e71j'), + 3: mpc('6.4249758039322926903898883193670191561073076453307e72' + '-9.714841891447835957536206094692144086296486157276e71j'), + } + for n_, r in ref_z2.items(): + assert mpc_ae(jtheta(n_, 99 + 2j, q), r, eps1) + + # small Im(z): + mp.dps = 70 + z1 = 99 + j/100 + z2 = 99 + j/1000 + mp.dps = 50 + # N[EllipticTheta[n, 99+I/100, 99/100], 300], 50] + assert mpc_ae(jtheta(2, z1, q), + mpc('-9.2766223348824196728753370062221747173794042129425e-101' + '+8.839222869333177347710982216539973334987056177494e-102j')) + # N[EllipticTheta[n, 99+2I, 99/1000], 300], 50] + assert mpc_ae(jtheta(2, z2, q), + mpc('8.8023727531603529839604538627814233285505151260344e-101' + '+2.7678970233217267414786149238410133410022311980703e-101j')) + + mp.dps = 15 + r1 = mp.extradps(45)(jtheta)(3, 0.25+0.25j, 0.5) + assert mpc_ae(jtheta(3, 0.25+0.25j, 0.5), +r1) + + z = 1+0.5j + # N[EllipticTheta[1, 1 + I/2, 99*Exp[Pi*I/4]/100], 17] + with mp.extraprec(10): + q = 99*mp.nthroot(1, 8, 1)/100 + assert mpc_ae(jtheta(1, z, q), + mpc('2.0519200161807602e10-1.2299274570292357e10j')) + # N[EllipticTheta[1, 1 + I/2, 99*Exp[3*Pi*I/4]/100], 17] + with mp.extraprec(10): + q = 99*mp.nthroot(1, 8, 3)/100 + assert mpc_ae(jtheta(1, z, q), + mpc('1.4250540444836117e10-1.9215405987536610e10j')) + +def test_issue_930_random(): + # random data in the modular-reduction regime (|q| close to 1 and + # complex z): check jtheta against itself at two precisions, like + # the |q| -> 1 checks in test_jtheta_issue_79 above + for i in range(10): + q = mpf(str(random.random()*mpf('0.0999') + mpf('0.9'))) + if i % 2: + q = -q # exercise the tau -> tau - k translation + z = mpc(str(10*random.random()), str(4*random.random() - 2)) + for n_ in range(1, 5): + for nd in (0, 1, 2, 5, 8, 10): + r1 = mp.extradps(45)(jtheta)(n_, z, q, nd) + r2 = jtheta(n_, z, q, nd) + assert mpc_ae(r1, r2), (n_, z, q, nd) + +def test_jtheta_modular_translation(): + mp.dps = 25 + q = -0.5 + z = 1+2j + assert mpc_ae(jtheta(3, z, q), jtheta(4, z, -q)) + assert mpc_ae(jtheta(4, z, q), jtheta(3, z, -q)) + for nd in (1, 2): + assert mpc_ae(jtheta(3, z, q, derivative=nd), + jtheta(4, z, -q, derivative=nd)) + assert mpc_ae(jtheta(4, z, q, derivative=nd), + jtheta(3, z, -q, derivative=nd)) + for n_ in (1, 2): + assert jtheta(n_, z, q).ae(exp(j*pi/4)*jtheta(n_, z, -q)) + assert mpc_ae(jtheta(3, z, q), jtheta(3, -z, q)) + assert mpc_ae(jtheta(4, z, q), jtheta(4, -z, q)) + +def test_jtheta_identities(): + """ + Tests the some of the jacobi identidies found in Abramowitz, + Sec. 16.28, Pg. 576. The identities are tested to 1 part in 10^98. + """ + mp.dps = 110 + eps1 = ldexp(eps, 30) + + for i in range(10): + qstring = str(random.random()) + q = mpf(qstring) + + zstring = str(10*random.random()) + z = mpf(zstring) + # Abramowitz 16.28.1 + # v_1(z, q)**2 * v_4(0, q)**2 = v_3(z, q)**2 * v_2(0, q)**2 + # - v_2(z, q)**2 * v_3(0, q)**2 + term1 = (jtheta(1, z, q)**2) * (jtheta(4, zero, q)**2) + term2 = (jtheta(3, z, q)**2) * (jtheta(2, zero, q)**2) + term3 = (jtheta(2, z, q)**2) * (jtheta(3, zero, q)**2) + equality = term1 - term2 + term3 + assert equality.ae(0, eps1), (z, q) + + zstring = str(100*random.random()) + z = mpf(zstring) + # Abramowitz 16.28.2 + # v_2(z, q)**2 * v_4(0, q)**2 = v_4(z, q)**2 * v_2(0, q)**2 + # - v_1(z, q)**2 * v_3(0, q)**2 + term1 = (jtheta(2, z, q)**2) * (jtheta(4, zero, q)**2) + term2 = (jtheta(4, z, q)**2) * (jtheta(2, zero, q)**2) + term3 = (jtheta(1, z, q)**2) * (jtheta(3, zero, q)**2) + equality = term1 - term2 + term3 + assert equality.ae(0, eps1), (z, q) + + # Abramowitz 16.28.3 + # v_3(z, q)**2 * v_4(0, q)**2 = v_4(z, q)**2 * v_3(0, q)**2 + # - v_1(z, q)**2 * v_2(0, q)**2 + term1 = (jtheta(3, z, q)**2) * (jtheta(4, zero, q)**2) + term2 = (jtheta(4, z, q)**2) * (jtheta(3, zero, q)**2) + term3 = (jtheta(1, z, q)**2) * (jtheta(2, zero, q)**2) + equality = term1 - term2 + term3 + assert equality.ae(0, eps1), (z, q) + + # Abramowitz 16.28.4 + # v_4(z, q)**2 * v_4(0, q)**2 = v_3(z, q)**2 * v_3(0, q)**2 + # - v_2(z, q)**2 * v_2(0, q)**2 + term1 = (jtheta(4, z, q)**2) * (jtheta(4, zero, q)**2) + term2 = (jtheta(3, z, q)**2) * (jtheta(3, zero, q)**2) + term3 = (jtheta(2, z, q)**2) * (jtheta(2, zero, q)**2) + equality = term1 - term2 + term3 + assert equality.ae(0, eps1), (z, q) + + # Abramowitz 16.28.5 + # v_2(0, q)**4 + v_4(0, q)**4 == v_3(0, q)**4 + term1 = (jtheta(2, zero, q))**4 + term2 = (jtheta(4, zero, q))**4 + term3 = (jtheta(3, zero, q))**4 + equality = term1 + term2 - term3 + assert equality.ae(0, eps1), (z, q) + +def test_jtheta_complex(): + mp.dps = 30 + z = mpf(1)/4 + j/8 + q = mpf(1)/3 + j/7 + # Mathematica N[EllipticTheta[1, 1/4 + I/8, 1/3 + I/7], 35] + res = mpf('0.31618034835986160705729105731678285') + \ + mpf('0.07542013825835103435142515194358975') * j + r = jtheta(1, z, q) + assert mpc_ae(r, res) + + # Mathematica N[EllipticTheta[2, 1/4 + I/8, 1/3 + I/7], 35] + res = mpf('1.6530986428239765928634711417951828') + \ + mpf('0.2015344864707197230526742145361455') * j + r = jtheta(2, z, q) + assert mpc_ae(r, res) + + # Mathematica N[EllipticTheta[3, 1/4 + I/8, 1/3 + I/7], 35] + res = mpf('1.6520564411784228184326012700348340') + \ + mpf('0.1998129119671271328684690067401823') * j + r = jtheta(3, z, q) + assert mpc_ae(r, res) + + # Mathematica N[EllipticTheta[4, 1/4 + I/8, 1/3 + I/7], 35] + res = mpf('0.37619082382228348252047624089973824') - \ + mpf('0.15623022130983652972686227200681074') * j + r = jtheta(4, z, q) + assert mpc_ae(r, res) + + # check some theta function identities + mp.dos = 100 + z = mpf(1)/4 + j/8 + q = mpf(1)/3 + j/7 + mp.dps += 10 + a = [0,0, jtheta(2, 0, q), jtheta(3, 0, q), jtheta(4, 0, q)] + t = [0, jtheta(1, z, q), jtheta(2, z, q), jtheta(3, z, q), jtheta(4, z, q)] + r = [(t[2]*a[4])**2 - (t[4]*a[2])**2 + (t[1] *a[3])**2, + (t[3]*a[4])**2 - (t[4]*a[3])**2 + (t[1] *a[2])**2, + (t[1]*a[4])**2 - (t[3]*a[2])**2 + (t[2] *a[3])**2, + (t[4]*a[4])**2 - (t[3]*a[3])**2 + (t[2] *a[2])**2, + a[2]**4 + a[4]**4 - a[3]**4] + mp.dps -= 10 + for x in r: + assert mpc_ae(x, mpc(0)) + +def test_djtheta(): + mp.dps = 30 + + z = one/7 + j/3 + q = one/8 + j/5 + # Mathematica N[EllipticThetaPrime[1, 1/7 + I/3, 1/8 + I/5], 35] + res = mpf('1.5555195883277196036090928995803201') - \ + mpf('0.02439761276895463494054149673076275') * j + result = jtheta(1, z, q, 1) + assert mpc_ae(result, res) + + # Mathematica N[EllipticThetaPrime[2, 1/7 + I/3, 1/8 + I/5], 35] + res = mpf('0.19825296689470982332701283509685662') - \ + mpf('0.46038135182282106983251742935250009') * j + result = jtheta(2, z, q, 1) + assert mpc_ae(result, res) + + # Mathematica N[EllipticThetaPrime[3, 1/7 + I/3, 1/8 + I/5], 35] + res = mpf('0.36492498415476212680896699407390026') - \ + mpf('0.57743812698666990209897034525640369') * j + result = jtheta(3, z, q, 1) + assert mpc_ae(result, res) + + # Mathematica N[EllipticThetaPrime[4, 1/7 + I/3, 1/8 + I/5], 35] + res = mpf('-0.38936892528126996010818803742007352') + \ + mpf('0.66549886179739128256269617407313625') * j + result = jtheta(4, z, q, 1) + assert mpc_ae(result, res) + + for i in range(10): + q = (one*random.random() + j*random.random())/2 + # identity in Wittaker, Watson &21.41 + a = jtheta(1, 0, q, 1) + b = jtheta(2, 0, q)*jtheta(3, 0, q)*jtheta(4, 0, q) + assert a.ae(b), q + + # test higher derivatives + mp.dps = 20 + for q,z in [(one/3, one/5), (one/3 + j/8, one/5), + (one/3, one/5 + j/8), (one/3 + j/7, one/5 + j/8)]: + for n in [1, 2, 3, 4]: + r = jtheta(n, z, q, 2) + r1 = diff(lambda zz: jtheta(n, zz, q), z, n=2) + assert r.ae(r1) + r = jtheta(n, z, q, 3) + r1 = diff(lambda zz: jtheta(n, zz, q), z, n=3) + assert r.ae(r1) + + # identity in Wittaker, Watson &21.41 + q = one/3 + z = zero + a = [0]*5 + a[1] = jtheta(1, z, q, 3)/jtheta(1, z, q, 1) + for n in [2,3,4]: + a[n] = jtheta(n, z, q, 2)/jtheta(n, z, q) + equality = a[2] + a[3] + a[4] - a[1] + assert equality.ae(0) + +def test_jsn(): + """ + Test some special cases of the sn(z, q) function. + """ + mp.dps = 100 + + # trival case + result = jsn(zero, zero) + assert result == zero + + # Abramowitz Table 16.5 + # + # sn(0, m) = 0 + + for i in range(10): + qstring = str(random.random()) + q = mpf(qstring) + + equality = jsn(zero, q) + assert equality.ae(0), q + + # Abramowitz Table 16.6.1 + # + # sn(z, 0) = sin(z), m == 0 + # + # sn(z, 1) = tanh(z), m == 1 + # + # It would be nice to test these, but I find that they run + # in to numerical trouble. I'm currently treating as a boundary + # case for sn function. + + mp.dps = 25 + arg = one/10 + # N[JacobiSN[1/10, 2^-100], 25] + res = mpf('0.09983341664682815230681420') + m = ldexp(one, -100) + result = jsn(arg, m) + assert result.ae(res) + + # N[JacobiSN[1/10, 1/10], 25] + res = mpf('0.09981686718599080096451168') + result = jsn(arg, arg) + assert result.ae(res) + +def test_jcn(): + """ + Test some special cases of the cn(z, q) function. + """ + mp.dps = 100 + + # Abramowitz Table 16.5 + # cn(0, q) = 1 + qstring = str(random.random()) + q = mpf(qstring) + cn = jcn(zero, q) + assert cn.ae(one), q + + # Abramowitz Table 16.6.2 + # + # cn(u, 0) = cos(u), m == 0 + # + # cn(u, 1) = sech(z), m == 1 + # + # It would be nice to test these, but I find that they run + # in to numerical trouble. I'm currently treating as a boundary + # case for cn function. + + mp.dps = 25 + arg = one/10 + m = ldexp(one, -100) + # N[JacobiCN[1/10, 2^-100], 25] + res = mpf('0.9950041652780257660955620') + result = jcn(arg, m) + assert result.ae(res) + + # N[JacobiCN[1/10, 1/10], 25] + res = mpf('0.9950058256237368748520459') + result = jcn(arg, arg) + assert result.ae(res) + +def test_jdn(): + """ + Test some special cases of the dn(z, q) function. + """ + mp.dps = 100 + + # Abramowitz Table 16.5 + # dn(0, q) = 1 + mstring = str(random.random()) + m = mpf(mstring) + + dn = jdn(zero, m) + assert dn.ae(one), m + + mp.dps = 25 + # N[JacobiDN[1/10, 1/10], 25] + res = mpf('0.9995017055025556219713297') + arg = one/10 + result = jdn(arg, arg) + assert result.ae(res) + +def test_sn_cn_dn_identities(): + """ + Tests the some of the jacobi elliptic function identities found + on Mathworld. Haven't found in Abramowitz. + """ + mp.dps = 100 + N = 5 + for i in range(N): + qstring = str(random.random()) + q = mpf(qstring) + zstring = str(100*random.random()) + z = mpf(zstring) + + # MathWorld + # sn(z, q)**2 + cn(z, q)**2 == 1 + term1 = jsn(z, q)**2 + term2 = jcn(z, q)**2 + equality = one - term1 - term2 + assert equality.ae(0), (z, q) + + # MathWorld + # k**2 * sn(z, m)**2 + dn(z, m)**2 == 1 + for i in range(N): + mstring = str(random.random()) + m = mpf(qstring) + k = m.sqrt() + zstring = str(10*random.random()) + z = mpf(zstring) + term1 = k**2 * jsn(z, m)**2 + term2 = jdn(z, m)**2 + equality = one - term1 - term2 + assert equality.ae(0), (z, m) + + + for i in range(N): + mstring = str(random.random()) + m = mpf(mstring) + k = mp.extraprec(10)(sqrt)(m) + zstring = str(random.random()) + z = mpf(zstring) + + # MathWorld + # k**2 * cn(z, m)**2 + (1 - k**2) = dn(z, m)**2 + term1 = k**2 * jcn(z, m)**2 + term2 = 1 - k**2 + term3 = jdn(z, m)**2 + equality = term3 - term1 - term2 + assert equality.ae(0), (z, m) + + K = ellipk(k**2) + # Abramowitz Table 16.5 + # sn(K, m) = 1; K is K(k), first complete elliptic integral + r = jsn(K, m) + assert r.ae(one), (K, m) + + # Abramowitz Table 16.5 + # cn(K, q) = 0; K is K(k), first complete elliptic integral + equality = jcn(K, m) + assert equality.ae(0), (K, m) + + # Abramowitz Table 16.6.3 + # dn(z, 0) = 1, m == 0 + z = m + value = jdn(z, zero) + assert value.ae(one), z + +def test_sn_cn_dn_complex(): + mp.dps = 30 + # N[JacobiSN[1/4 + I/8, 1/3 + I/7], 35] in Mathematica + res = mpf('0.2495674401066275492326652143537') + \ + mpf('0.12017344422863833381301051702823') * j + u = mpf(1)/4 + j/8 + m = mpf(1)/3 + j/7 + r = jsn(u, m) + assert mpc_ae(r, res) + + # N[JacobiCN[1/4 + I/8, 1/3 + I/7], 35] + res = mpf('0.9762691700944007312693721148331') - \ + mpf('0.0307203994181623243583169154824')*j + r = jcn(u, m) + assert mpc_ae(r, res) + + # N[JacobiDN[1/4 + I/8, 1/3 + I/7], 35] + res = mpf('0.99639490163039577560547478589753039') - \ + mpf('0.01346296520008176393432491077244994')*j + r = jdn(u, m) + assert mpc_ae(r, res) + +def test_elliptic_integrals(): + # Test cases from Carlson's paper + assert elliprd(0,2,1).ae(1.7972103521033883112) + assert elliprd(2,3,4).ae(0.16510527294261053349) + assert elliprd(j,-j,2).ae(0.65933854154219768919) + assert elliprd(0,j,-j).ae(1.2708196271909686299 + 2.7811120159520578777j) + assert elliprd(0,j-1,j).ae(-1.8577235439239060056 - 0.96193450888838559989j) + assert elliprd(-2-j,-j,-1+j).ae(1.8249027393703805305 - 1.2218475784827035855j) + # extra test cases + assert elliprg(0,0,0) == 0 + assert elliprg(0,0,16).ae(2) + assert elliprg(0,16,0).ae(2) + assert elliprg(16,0,0).ae(2) + assert elliprg(1,4,0).ae(1.2110560275684595248036) + assert elliprg(1,0,4).ae(1.2110560275684595248036) + assert elliprg(0,4,1).ae(1.2110560275684595248036) + # should be symmetric -- fixes a bug present in the paper + x,y,z = 1,1j,-1+1j + assert elliprg(x,y,z).ae(0.64139146875812627545 + 0.58085463774808290907j) + assert elliprg(x,z,y).ae(0.64139146875812627545 + 0.58085463774808290907j) + assert elliprg(y,x,z).ae(0.64139146875812627545 + 0.58085463774808290907j) + assert elliprg(y,z,x).ae(0.64139146875812627545 + 0.58085463774808290907j) + assert elliprg(z,x,y).ae(0.64139146875812627545 + 0.58085463774808290907j) + assert elliprg(z,y,x).ae(0.64139146875812627545 + 0.58085463774808290907j) + + for n in [5, 15, 30, 60, 100]: + mp.dps = n + assert elliprf(1,2,0).ae('1.3110287771460599052324197949455597068413774757158115814084108519003952935352071251151477664807145467230678763') + assert elliprf(0.5,1,0).ae('1.854074677301371918433850347195260046217598823521766905585928045056021776838119978357271861650371897277771871') + assert elliprf(j,-j,0).ae('1.854074677301371918433850347195260046217598823521766905585928045056021776838119978357271861650371897277771871') + assert elliprf(j-1,j,0).ae(mpc('0.79612586584233913293056938229563057846592264089185680214929401744498956943287031832657642790719940442165621412', + '-1.2138566698364959864300942567386038975419875860741507618279563735753073152507112254567291141460317931258599889')) + assert elliprf(2,3,4).ae('0.58408284167715170669284916892566789240351359699303216166309375305508295130412919665541330837704050454472379308') + assert elliprf(j,-j,2).ae('1.0441445654064360931078658361850779139591660747973017593275012615517220315993723776182276555339288363064476126') + assert elliprf(j-1,j,1-j).ae(mpc('0.93912050218619371196624617169781141161485651998254431830645241993282941057500174238125105410055253623847335313', + '-0.53296252018635269264859303449447908970360344322834582313172115220559316331271520508208025270300138589669326136')) + assert elliprc(0,0.25).ae(+pi) + assert elliprc(2.25,2).ae(+ln2) + assert elliprc(0,j).ae(mpc('1.1107207345395915617539702475151734246536554223439225557713489017391086982748684776438317336911913093408525532', + '-1.1107207345395915617539702475151734246536554223439225557713489017391086982748684776438317336911913093408525532')) + assert elliprc(-j,j).ae(mpc('1.2260849569072198222319655083097718755633725139745941606203839524036426936825652935738621522906572884239069297', + '-0.34471136988767679699935618332997956653521218571295874986708834375026550946053920574015526038040124556716711353')) + assert elliprc(0.25,-2).ae(ln2/3) + assert elliprc(j,-1).ae(mpc('0.77778596920447389875196055840799837589537035343923012237628610795937014001905822029050288316217145443865649819', + '0.1983248499342877364755170948292130095921681309577950696116251029742793455964385947473103628983664877025779304')) + assert elliprj(0,1,2,3).ae('0.77688623778582332014190282640545501102298064276022952731669118325952563819813258230708177398475643634103990878') + assert elliprj(2,3,4,5).ae('0.14297579667156753833233879421985774801466647854232626336218889885463800128817976132826443904216546421431528308') + assert elliprj(2,3,4,-1+j).ae(mpc('0.13613945827770535203521374457913768360237593025944342652613569368333226052158214183059386307242563164036672709', + '-0.38207561624427164249600936454845112611060375760094156571007648297226090050927156176977091273224510621553615189')) + assert elliprj(j,-j,0,2).ae('1.6490011662710884518243257224860232300246792717163891216346170272567376981346412066066050103935109581019055806') + assert elliprj(-1+j,-1-j,1,2).ae('0.94148358841220238083044612133767270187474673547917988681610772381758628963408843935027667916713866133196845063') + assert elliprj(j,-j,0,1-j).ae(mpc('1.8260115229009316249372594065790946657011067182850435297162034335356430755397401849070610280860044610878657501', + '1.2290661908643471500163617732957042849283739403009556715926326841959667290840290081010472716420690899886276961')) + assert elliprj(-1+j,-1-j,1,-3+j).ae(mpc('-0.61127970812028172123588152373622636829986597243716610650831553882054127570542477508023027578037045504958619422', + '-1.0684038390006807880182112972232562745485871763154040245065581157751693730095703406209466903752930797510491155')) + assert elliprj(-1+j,-2-j,-j,-1+j).ae(mpc('1.8249027393703805304622013339009022294368078659619988943515764258335975852685224202567854526307030593012768954', + '-1.2218475784827035854568450371590419833166777535029296025352291308244564398645467465067845461070602841312456831')) + + assert elliprg(0,16,16).ae(+pi) + assert elliprg(2,3,4).ae('1.7255030280692277601061148835701141842692457170470456590515892070736643637303053506944907685301315299153040991') + assert elliprg(0,j,-j).ae('0.42360654239698954330324956174109581824072295516347109253028968632986700241706737986160014699730561497106114281') + assert elliprg(j-1,j,0).ae(mpc('0.44660591677018372656731970402124510811555212083508861036067729944477855594654762496407405328607219895053798354', + '0.70768352357515390073102719507612395221369717586839400605901402910893345301718731499237159587077682267374159282')) + assert elliprg(-j,j-1,j).ae(mpc('0.36023392184473309033675652092928695596803358846377334894215349632203382573844427952830064383286995172598964266', + '0.40348623401722113740956336997761033878615232917480045914551915169013722542827052849476969199578321834819903921')) + assert elliprg(0, mpf('0.0796'), 4).ae('1.0284758090288040009838871385180217366569777284430590125081211090574701293154645750017813190805144572673802094') + mp.dps = 15 + + # more test cases for the branch of ellippi / elliprj + assert elliprj(-1-0.5j, -10-6j, -10-3j, -5+10j).ae(0.128470516743927699 + 0.102175950778504625j, abs_eps=1e-8) + assert elliprj(1.987, 4.463 - 1.614j, 0, -3.965).ae(-0.341575118513811305 - 0.394703757004268486j, abs_eps=1e-8) + assert elliprj(0.3068, -4.037+0.632j, 1.654, -0.9609).ae(-1.14735199581485639 - 0.134450158867472264j, abs_eps=1e-8) + assert elliprj(0.3068, -4.037-0.632j, 1.654, -0.9609).ae(1.758765901861727 - 0.161002343366626892j, abs_eps=1e-5) + assert elliprj(0.3068, -4.037+0.0632j, 1.654, -0.9609).ae(-1.17157627949475577 - 0.069182614173988811j, abs_eps=1e-8) + assert elliprj(0.3068, -4.037+0.00632j, 1.654, -0.9609).ae(-1.17337595670549633 - 0.0623069224526925j, abs_eps=1e-8) + + # these require accurate integration + assert elliprj(0.3068, -4.037-0.0632j, 1.654, -0.9609).ae(1.77940452391261626 + 0.0388711305592447234j) + assert elliprj(0.3068, -4.037-0.00632j, 1.654, -0.9609).ae(1.77806722756403055 + 0.0592749824572262329j) + # issue 571 + assert ellippi(2.1 + 0.94j, 2.3 + 0.98j, 2.5 + 0.01j).ae(-0.40652414240811963438 + 2.1547659461404749309j) + + assert ellippi(2.0-1.0j, 2.0+1.0j).ae(1.8578723151271115 - 1.18642180609983531j) + assert ellippi(2.0-0.5j, 0.5+1.0j).ae(0.936761970766645807 - 1.61876787838890786j) + assert ellippi(2.0, 1.0+1.0j).ae(0.999881420735506708 - 2.4139272867045391j) + assert ellippi(2.0+1.0j, 2.0-1.0j).ae(1.8578723151271115 + 1.18642180609983531j) + assert ellippi(2.0+1.0j, 2.0).ae(2.78474654927885845 + 2.02204728966993314j) + +def test_issue_238(): + assert isnan(qfrom(m=nan)) + +def test_issue_604(): + assert ellipe(pi, 1).ae('2.0') + +def test_issue_486(): + assert isnan(elliprj(1, 2, 3, nan)) + +def test_issue_1104(): + z, q = mpc(2479 + 1020j), mpf('1e-2141') + + # N[Im[EllipticTheta[4, 2479 + 1020 I, 10^-2141]], 15] + ref_im = mpf('4.90523636450946e-1256') + ans = jtheta(4, z, q) + assert mpc_ae(ans, 1 + ref_im*1j) + assert mpc_ae(ans, mp.extraprec(10000)(jtheta)(4, z, q)) + + # N[Derivative[0, 3, 0][EllipticTheta][4, 2479 + 1020 I, 10^-2141], 15] + ref = mpc('-3.92418909160757e-1255-6.16571954074132e-1255j') + ans = jtheta(4, z, q, 3) + assert mpc_ae(ans, ref) + assert mpc_ae(ans, mp.extraprec(10000)(jtheta)(4, z, q, 3)) + +# Weierstrass Elliptic Functions +# ============================================================================ + +def test_weierstrass_tau_uses_normalized_periods(): + mp.dps = 30 + + z = mpf('0.3') + tau = j/2 + omega1 = 0.5 + omega2 = tau/2 + + for f in [weierp, weierpprime, weiersigma, weierzeta]: + assert mpc_ae(f(z, tau=tau), + f(z, omega1=omega1, omega2=omega2), eps=eps*1000) + +def test_weierstrass_g2g3_differential_equation(): + # https://dlmf.nist.gov/23.3#E10 + mp.dps = 30 + + z = mpf('0.3') + for g2, g3 in [(60, 140), (0, 140), (60, 0)]: + p = weierp(z, g2=g2, g3=g3) + pp = weierpprime(z, g2=g2, g3=g3) + assert mpc_ae(pp**2, 4*p**3 - g2*p - g3, eps=eps*1000) + +def test_weierstrass_parameter_conversions(): + mp.dps = 30 + + omega1 = 1 + omega2 = j/2 + g2, g3 = weierinvariants(omega1, omega2) + + omega1, omega2 = weierhalfperiods(g2, g3) + + g2_roundtrip, g3_roundtrip = weierinvariants(omega1, omega2) + assert mpc_ae(g2, g2_roundtrip, eps=eps*10000) + assert mpc_ae(g3, g3_roundtrip, eps=eps*10000) + assert (omega2/omega1).imag > 0 + +def test_weierstrass_special_half_periods(): + mp.dps = 30 + + # Scaled version of http://dlmf.nist.gov/23.5.E5 + lemniscatic = gamma(1/4)**2/(4*sqrt(pi)) + omega1, omega2 = weierhalfperiods(1, 0) + lattice_points = [ + m*omega1 + n*omega2 + for m in [-1, 0, 1] + for n in [-1, 0, 1] + if m or n + ] + assert min(abs(point - lemniscatic) for point in lattice_points) < eps*1000 + assert min(abs(point - j*lemniscatic) for point in lattice_points) < eps*1000 + + # Scaled version of http://dlmf.nist.gov/23.5.E9 + equianharmonic = gamma(mpf(1)/3)**3/(4*pi) + tau = 0.5 + sqrt(3)*j/2 + omega1, omega2 = weierhalfperiods(0, 1) + assert mpc_ae(omega1, equianharmonic, eps=eps*1000) + assert mpc_ae(omega2, equianharmonic*tau, eps=eps*1000) + +def test_weierstrass_half_periods_high_precision(): + mp.dps = 80 + + g2 = 60 + g3 = 140 + omega1, omega2 = weierhalfperiods(g2, g3) + g2_roundtrip, g3_roundtrip = weierinvariants(omega1, omega2) + + assert mpc_ae(g2_roundtrip, g2, eps=eps*10000) + assert mpc_ae(g3_roundtrip, g3, eps=eps*10000) + +def test_weierstrass_parameter_conversions_with_kleinj(): + mp.dps = 30 + + tau = 0.625 + 0.75j + g2, g3 = weierinvariants(0.5, tau/2) + recovered_omega1, recovered_omega2 = weierhalfperiods(g2, g3) + recovered_tau = recovered_omega2/recovered_omega1 + j_from_invariants = g2**3/(g2**3 - 27*g3**2) + + assert mpc_ae(kleinj(tau), j_from_invariants, eps=eps*1000) + assert mpc_ae(kleinj(recovered_tau), kleinj(tau), eps=eps*1000) + +def test_weierstrass_half_period_values_are_cubic_roots(): + mp.dps = 30 + + omega1 = 1 + omega2 = j/2 + g2, g3 = weierinvariants(omega1, omega2) + + roots = polyroots([-g3, -g2, 0, 4], maxsteps=50) + half_period_values = [ + weierp(omega1, omega1=omega1, omega2=omega2), + weierp(omega2, omega1=omega1, omega2=omega2), + weierp(omega1 + omega2, omega1=omega1, omega2=omega2), + ] + + for value in half_period_values: + assert mpc_ae(4*value**3 - g2*value - g3, 0, + eps=eps*1000) + assert min(abs(value - root) for root in roots) < eps*1000 + for root in roots: + assert min(abs(value - root) for value in half_period_values) < eps*1000 + +def test_weierstrass_conversions_with_weierp(): + mp.dps = 30 + + z = mpf('0.3') + g2, g3 = 60, 140 + omega1, omega2 = weierhalfperiods(g2, g3) + assert mpc_ae(weierp(z, g2=g2, g3=g3), + weierp(z, omega1=omega1, omega2=omega2), eps=eps*1000) + +def test_weierstrass_periodicity(): + mp.dps = 30 + + # http://dlmf.nist.gov/23.2.E9 + z = mpf('0.3') + omega1 = 1 + omega2 = j/2 + p = weierp(z, omega1=omega1, omega2=omega2) + pp = weierpprime(z, omega1=omega1, omega2=omega2) + + assert mpc_ae(weierp(z + 2*omega1, omega1=omega1, omega2=omega2), + p, eps=eps*1000) + assert mpc_ae(weierp(z + 2*omega2, omega1=omega1, omega2=omega2), + p, eps=eps*1000) + assert mpc_ae(weierpprime(z + 2*omega1, + omega1=omega1, omega2=omega2), pp, + eps=eps*1000) + assert mpc_ae(weierpprime(z + 2*omega2, + omega1=omega1, omega2=omega2), pp, + eps=eps*1000) + +def test_weierstrass_scaling_laws(): + mp.dps = 30 + + # http://dlmf.nist.gov/23.10.iv + z = mpf('0.3') + scale = mpf('1.7') + omega1 = 1 + omega2 = j/2 + scaled_omega1 = scale*omega1 + scaled_omega2 = scale*omega2 + + assert mpc_ae(weierp(scale*z, omega1=scaled_omega1, + omega2=scaled_omega2), + weierp(z, omega1=omega1, omega2=omega2)/scale**2, + eps=eps*1000) + assert mpc_ae(weierpprime(scale*z, omega1=scaled_omega1, + omega2=scaled_omega2), + weierpprime(z, omega1=omega1, omega2=omega2)/scale**3, + eps=eps*1000) + assert mpc_ae(weiersigma(scale*z, omega1=scaled_omega1, + omega2=scaled_omega2), + scale*weiersigma(z, omega1=omega1, omega2=omega2), + eps=eps*1000) + assert mpc_ae(weierzeta(scale*z, omega1=scaled_omega1, + omega2=scaled_omega2), + weierzeta(z, omega1=omega1, omega2=omega2)/scale, + eps=eps*1000) + +def test_weierstrass_tau_omega_parameterizations(): + mp.dps = 30 + + z = mpf('0.3') + tau = j/2 + omega1 = 0.5 + omega2 = tau/2 + for f in [weierp, weierpprime, weiersigma, weierzeta]: + assert mpc_ae(f(z, tau=tau), f(z, omega1=omega1, omega2=omega2)) + +def test_weierstrass_addition_theorem(): + mp.dps = 30 + + # http://dlmf.nist.gov/23.10.E1 + z = mpf('0.3') + w = mpf('0.4') + j/10 + omega1 = 1 + omega2 = j/2 + + pz = weierp(z, omega1=omega1, omega2=omega2) + pw = weierp(w, omega1=omega1, omega2=omega2) + ppz = weierpprime(z, omega1=omega1, omega2=omega2) + ppw = weierpprime(w, omega1=omega1, omega2=omega2) + rhs = ((ppz - ppw)/(pz - pw))**2/4 - pz - pw + + assert mpc_ae(weierp(z + w, omega1=omega1, omega2=omega2), + rhs, eps=eps*1000) + +def test_weierstrass_zeta_legendre_relation(): + mp.dps = 30 + + # http://dlmf.nist.gov/23.2.E11 + # http://dlmf.nist.gov/23.2.E14 + z = mpf('0.3') + j/10 + omega1 = 1 + omega2 = j/2 + + eta1_increment = weierzeta(z + 2*omega1, + omega1=omega1, omega2=omega2) + eta1_increment -= weierzeta(z, omega1=omega1, omega2=omega2) + eta2_increment = weierzeta(z + 2*omega2, + omega1=omega1, omega2=omega2) + eta2_increment -= weierzeta(z, omega1=omega1, omega2=omega2) + assert mpc_ae(eta1_increment*omega2 - eta2_increment*omega1, + pi*j, eps=eps*1000) + + eta1 = weierzeta(omega1, omega1=omega1, omega2=omega2) + eta2 = weierzeta(omega2, omega1=omega1, omega2=omega2) + assert mpc_ae(eta1*omega2 - eta2*omega1, pi*j/2, eps=eps*1000) + +def test_weierstrass_sigma_zeta_identities(): + mp.dps = 30 + + # http://dlmf.nist.gov/23.2.E8 + z = mpf('0.3') + tau = j/2 + assert mpc_ae(diff(lambda t: weiersigma(t, tau=tau), z) / + weiersigma(z, tau=tau), weierzeta(z, tau=tau), + eps=eps*1000) + assert mpc_ae(diff(lambda t: weierzeta(t, tau=tau), z), + -weierp(z, tau=tau), eps=eps*1000) + +def test_weierstrass_weierpinv(): + mp.dps = 30 + + z = mpf('0.3') + g2, g3 = 60, 140 + p = weierp(z, g2=g2, g3=g3) + pp = weierpprime(z, g2=g2, g3=g3) + z2 = weierpinv(p, g2=g2, g3=g3) + assert mpc_ae(z2, z, eps=eps*1000) + assert mpc_ae(weierp(z2, g2=g2, g3=g3), p, eps=eps*1000) + + z2 = weierpinv(p, g2=g2, g3=g3, weierp_prime=pp) + assert mpc_ae(z2, z, eps=eps*1000) + assert mpc_ae(weierpprime(z2, g2=g2, g3=g3), pp, eps=eps*1000) + + z2 = weierpinv(p, g2=g2, g3=g3, weierp_prime=-pp) + assert mpc_ae(z2, -z, eps=eps*1000) + assert mpc_ae(weierpprime(z2, g2=g2, g3=g3), -pp, eps=eps*1000) + +def test_weierstrass_p_agrees_with_jacobi_sn(): + mp.dps = 30 + + # If e1 + e2 + e3 = 0, then + # + # wp(z; g2, g3) = e3 + (e1 - e3)/sn(sqrt(e1 - e3)*z, m)**2 + # + # where + # + # m = (e2 - e3)/(e1 - e3) + # + # and 4*(x - e1)*(x - e2)*(x - e3) = 4*x**3 - g2*x - g3. + # Shifted version of http://dlmf.nist.gov/23.6.E26 + e1 = 2 + e2 = -0.5 + e3 = -mpf(3)/2 + + g2 = -4*(e1*e2 + e1*e3 + e2*e3) + g3 = 4*e1*e2*e3 + + scale = sqrt(e1 - e3) + m = (e2 - e3)/(e1 - e3) + + z_values = [ + mpf('0.2'), + mpf('0.3'), + mpf('0.2') + j/10, + mpf('0.4') - j/20, + ] + + for z in z_values: + sn = ellipfun('sn', scale*z, m) + expected = e3 + (e1 - e3)/sn**2 + assert mpc_ae(weierp(z, g2=g2, g3=g3), expected, eps=eps*1000) + +def test_weierstrass_degenerate_sinh_case(): + mp.dps = 30 + + z = mpf('2.3456') + g2 = mpf(1)/12 + g3 = -mpf(1)/216 + + expected = mpf(1)/12 + 1/(4*sinh(z/2)**2) + actual = weierp(z, g2=g2, g3=g3) + + assert mpc_ae(actual, expected, eps=eps*1000) + +def test_weierstrass_values_from_wolfram_engine(): + """ + Test values computed with Wolfram Engine at 50 decimal digits. + """ + mp.dps = 30 + + z = mpf(1)/5 + j/10 + g2 = 23 + g3 = -6 + + # Wolfram Engine N[WeierstrassP[1/5 + I/10, {23, -6}], 50] + res = (mpf('12.034598774562061614120425445264439480909180451987') - + mpf('15.954494688453814173909097100149572873560659025384')*j) + result = weierp(z, g2=g2, g3=g3) + assert mpc_ae(result, res, eps=eps*1000) + + # Wolfram Engine N[WeierstrassZeta[1/5 + I/10, {23, -6}], 50] + res = (mpf('3.9992187928039781633477175010941910299674720024081') - + mpf('2.0041989210825396679692243492987154755001509689191')*j) + result = weierzeta(z, g2=g2, g3=g3) + assert mpc_ae(result, res, eps=eps*1000) + + # Wolfram Engine N[WeierstrassPPrime[1/5 + I/10, {23, -6}], 50] + res = (-mpf('31.54270502882344156819611453200111232882467293381') + + mpf('176.22165647344596777337677909680659880143827576854')*j) + result = weierpprime(z, g2=g2, g3=g3) + assert mpc_ae(result, res, eps=eps*1000) + + # Wolfram Engine N[WeierstrassSigma[1/5 + I/10, {23, -6}], 50] + res = (mpf('0.20003622045198197835660834749697373254229661740043') + + mpf('0.09996069154774003218065176170970294010303412640179')*j) + result = weiersigma(z, g2=g2, g3=g3) + assert mpc_ae(result, res, eps=eps*1000) + + z = mpf(23)/7 + j/19 + g2 = 4 + g3 = j/7 + + # Wolfram Engine N[WeierstrassP[23/7 + I/19, {4, I/7}], 50] + res = (mpf('2.2404307465194869190166863785647513208647609853834') - + mpf('0.5325343281547884762112232120255440012711106470188')*j) + result = weierp(z, g2=g2, g3=g3) + assert mpc_ae(result, res, eps=eps*1000) + + # Wolfram Engine N[WeierstrassZeta[23/7 + I/19, {4, I/7}], 50] + res = (mpf('2.6598023545241487676259152806188261775361664938905') - + mpf('0.1724953898440469052904087998933282167782688769615')*j) + result = weierzeta(z, g2=g2, g3=g3) + assert mpc_ae(result, res, eps=eps*1000) + + # Wolfram Engine N[WeierstrassPPrime[23/7 + I/19, {4, I/7}], 50] + res = (-mpf('5.8878748476527295086161128667469618082615948921289') + + mpf('2.5039163494781823494565528962139346083786451647413')*j) + result = weierpprime(z, g2=g2, g3=g3) + assert mpc_ae(result, res, eps=eps*1000) + + # Wolfram Engine N[WeierstrassSigma[23/7 + I/19, {4, I/7}], 50] + res = (-mpf('6.9051546244372935099218335621773818059877316069834') - + mpf('1.7875281795111006668737717361366903871401611117693')*j) + result = weiersigma(z, g2=g2, g3=g3) + assert mpc_ae(result, res, eps=eps*1000) + + +def test_weierstrass_invalid_parameterization(): + z = mpf('0.3') + pytest.raises(ValueError, lambda: weierp(z)) + pytest.raises(ValueError, lambda: weierp(z, g2=1)) + pytest.raises(ValueError, lambda: weierp(z, tau=-j)) + pytest.raises(ValueError, lambda: weierp(z, omega1=1)) + pytest.raises(ValueError, lambda: weierp(z, omega1=1, omega2=-j)) + pytest.raises(ValueError, lambda: weierp(z, g2=60, g3=140, tau=j/2)) + pytest.raises(ValueError, lambda: weierinvariants(1, -j)) + pytest.raises(TypeError, lambda: weierinvariants(1)) + pytest.raises(TypeError, lambda: weierhalfperiods(1)) diff --git a/mpmath/tests/test_extra_gamma.py b/mpmath/tests/test_extra_gamma.py new file mode 100644 index 0000000..2a100ae --- /dev/null +++ b/mpmath/tests/test_extra_gamma.py @@ -0,0 +1,163 @@ +import pytest + +from mpmath import (agm, eps, exp, factorial, fadd, fsub, gamma, j, log, + loggamma, mp, mpc, mpf, nstr, pi, rgamma, sqrt) +from mpmath.libmp import ifac + + +def check(name, func, z, y): + x = func(z) + xre = x.real + xim = x.imag + yre = y.real + yim = y.imag + tol = eps*8 + err = 0 + return abs(xre-yre) <= abs(yre)*tol and abs(xim-yim) <= abs(yim)*tol + +testcases = [] + +# Basic values +for n in list(range(1,200)) + list(range(201,2000,17)): + testcases.append(["%s" % n, None]) +for n in range(-200,200): + testcases.append(["%s+0.5" % n, None]) + testcases.append(["%s+0.37" % n, None]) + +testcases += [ +["(0.1+1j)", None], +["(-0.1+1j)", None], +["(0.1-1j)", None], +["(-0.1-1j)", None], +["10j", None], +["-10j", None], +["100j", None], +["10000j", None], +["-10000000j", None], +["(10**100)*j", None], +["125+(10**100)*j", None], +["-125+(10**100)*j", None], +["(10**10)*(1+j)", None], +["(10**10)*(-1+j)", None], +["(10**100)*(1+j)", None], +["(10**100)*(-1+j)", None], +["(1.5-1j)", None], +["(6+4j)", None], +["(4+1j)", None], +["(3.5+2j)", None], +["(1.5-1j)", None], +["(-6-4j)", None], +["(-2-3j)", None], +["(-2.5-2j)", None], +["(4+1j)", None], +["(3+3j)", None], +["(2-2j)", None], +["1", "0"], +["2", "0"], +["3", "log(2)"], +["4", "log(6)"], +["5", "log(24)"], +["0.5", "log(pi)/2"], +["1.5", "log(sqrt(pi)/2)"], +["2.5", "log(3*sqrt(pi)/4)"], +["mpf('0.37')", None], +["0.25", "log(sqrt(2*sqrt(2*pi**3)/agm(1,sqrt(2))))"], +["-0.4", None], +["mpf('-1.9')", None], +["mpf('12.8')", None], +["mpf('33.7')", None], +["mpf('95.2')", None], +["mpf('160.3')", None], +["mpf('2057.8')", None], +["25", "log(ifac(24))"], +["80", "log(ifac(79))"], +["500", "log(ifac(500-1))"], +["8000", "log(ifac(8000-1))"], +["8000.5", None], +["mpf('8000.1')", None], +["mpf('1.37e10')", None], +["mpf('1.37e10')*(1+j)", None], +["mpf('1.37e10')*(-1+j)", None], +["mpf('1.37e10')*(-1-j)", None], +["mpf('1.37e10')*(-1+j)", None], +["mpf('1.37e100')", None], +["mpf('1.37e100')*(1+j)", None], +["mpf('1.37e100')*(-1+j)", None], +["mpf('1.37e100')*(-1-j)", None], +["mpf('1.37e100')*(-1+j)", None], +["3+4j", +"mpc('" +"-1.7566267846037841105306041816232757851567066070613445016197619371316057169" +"4723618263960834804618463052988607348289672535780644470689771115236512106002" +"5970873471563240537307638968509556191696167970488390423963867031934333890838" +"8009531786948197210025029725361069435208930363494971027388382086721660805397" +"9163230643216054580167976201709951509519218635460317367338612500626714783631" +"7498317478048447525674016344322545858832610325861086336204591943822302971823" +"5161814175530618223688296232894588415495615809337292518431903058265147109853" +"1710568942184987827643886816200452860853873815413367529829631430146227470517" +"6579967222200868632179482214312673161276976117132204633283806161971389519137" +"1243359764435612951384238091232760634271570950240717650166551484551654327989" +"9360285030081716934130446150245110557038117075172576825490035434069388648124" +"6678152254554001586736120762641422590778766100376515737713938521275749049949" +"1284143906816424244705094759339932733567910991920631339597278805393743140853" +"391550313363278558195609260225928','" +"4.74266443803465792819488940755002274088830335171164611359052405215840070271" +"5906813009373171139767051863542508136875688550817670379002790304870822775498" +"2809996675877564504192565392367259119610438951593128982646945990372179860613" +"4294436498090428077839141927485901735557543641049637962003652638924845391650" +"9546290137755550107224907606529385248390667634297183361902055842228798984200" +"9591180450211798341715874477629099687609819466457990642030707080894518168924" +"6805549314043258530272479246115112769957368212585759640878745385160943755234" +"9398036774908108204370323896757543121853650025529763655312360354244898913463" +"7115955702828838923393113618205074162812089732064414530813087483533203244056" +"0546577484241423134079056537777170351934430586103623577814746004431994179990" +"5318522939077992613855205801498201930221975721246498720895122345420698451980" +"0051215797310305885845964334761831751370672996984756815410977750799748813563" +"8784405288158432214886648743541773208808731479748217023665577802702269468013" +"673719173759245720489020315779001')"], +] + +for z in [4, 14, 34, 64]: + testcases.append(["(2+j)*%s/3" % z, None]) + testcases.append(["(-2+j)*%s/3" % z, None]) + testcases.append(["(1+2*j)*%s/3" % z, None]) + testcases.append(["(2-j)*%s/3" % z, None]) + testcases.append(["(20+j)*%s/3" % z, None]) + testcases.append(["(-20+j)*%s/3" % z, None]) + testcases.append(["(1+20*j)*%s/3" % z, None]) + testcases.append(["(20-j)*%s/3" % z, None]) + testcases.append(["(200+j)*%s/3" % z, None]) + testcases.append(["(-200+j)*%s/3" % z, None]) + testcases.append(["(1+200*j)*%s/3" % z, None]) + testcases.append(["(200-j)*%s/3" % z, None]) + +# Poles +for n in [0,1,2,3,4,25,-1,-2,-3,-4,-20,-21,-50,-51,-200,-201,-20000,-20001]: + for t in ['1e-5', '1e-20', '1e-100', '1e-10000']: + testcases.append(["fadd(%s,'%s',exact=True)" % (n, t), None]) + testcases.append(["fsub(%s,'%s',exact=True)" % (n, t), None]) + testcases.append(["fadd(%s,'%sj',exact=True)" % (n, t), None]) + testcases.append(["fsub(%s,'%sj',exact=True)" % (n, t), None]) + + +@pytest.mark.parametrize("z,result", testcases) +def test_extra_gamma(z, result): + mp.dps = 1010 + z = eval(z) + mp.dps = 1050 + if result is None: + gamma_val = gamma(z) + loggamma_val = loggamma(z) + factorial_val = factorial(z) + rgamma_val = rgamma(z) + else: + loggamma_val = eval(result) + gamma_val = exp(loggamma_val) + factorial_val = z * gamma_val + rgamma_val = 1/gamma_val + for dps in [5, 10, 15, 25, 40, 60, 90, 120, 250, 600, 1000]: + mp.dps = dps + assert check("gamma", gamma, z, gamma_val) + assert check("rgamma", rgamma, z, rgamma_val) + assert check("loggamma", loggamma, z, loggamma_val) + assert check("factorial", factorial, z, factorial_val) diff --git a/mpmath/tests/test_extra_zeta.py b/mpmath/tests/test_extra_zeta.py new file mode 100644 index 0000000..6b0cc9c --- /dev/null +++ b/mpmath/tests/test_extra_zeta.py @@ -0,0 +1,28 @@ +import pytest + +from mpmath import fp, zetazero + + +@pytest.mark.parametrize("n,v", + [(399999999, 156762524.6750591511), + (241389216, 97490234.2276711795), + (526196239, 202950727.691229534), + (542964976, 209039046.578535272), + (1048449112, 388858885.231056486), + (1048449113, 388858885.384337406), + (1048449114, 388858886.002285122), + (1048449115, 388858886.00239369), + (1048449116, 388858886.690745053), + (3570918901, 1239587702.54745031), + (3570918902, 1239587702.54752387), + # Huge zeros (this may take hours): +# (8637740722917, 2124447368584.39296466152), +# (8637740722918, 2124447368584.39298170604), + ]) +def test_zetazero(n, v): + assert zetazero(n).ae(complex(0.5,v)) + +def test_zeta_param(capsys): + fp.zeta(0.5+100j, method="riemann-siegel", verbose=True) + captured = capsys.readouterr() + assert "Attempting to use the Riemann-Siegel algorithm" in captured.out diff --git a/mpmath/tests/test_format.py b/mpmath/tests/test_format.py new file mode 100644 index 0000000..27b9c23 --- /dev/null +++ b/mpmath/tests/test_format.py @@ -0,0 +1,914 @@ +import cmath +import ctypes +import math +import sys + +import hypothesis.strategies as st +import pytest +from hypothesis import example, given, settings + +from mpmath import fp, inf, mp, nan, ninf, workdps +from mpmath.libmp.libmpf import read_format_spec + + +@st.composite +def fmt_str(draw, types='fFeE', for_complex=False): + res = '' + + # fill_char and align + fill_char = draw(st.sampled_from(['']*3 + list('z;clxvjqwer'))) + if fill_char: + skip_0_padding = True + if for_complex: + align = draw(st.sampled_from(list('<^>'))) + else: + align = draw(st.sampled_from(list('<^>='))) + res += fill_char + align + else: + align = draw(st.sampled_from([''] + list('<^>='))) + if align == '=' and for_complex: + align = '' + if align: + skip_0_padding = True + res += align + else: + skip_0_padding = False + + # sign character + res += draw(st.sampled_from([''] + list('-+ '))) + + # no_neg_0 (not used yet.) + if sys.version_info >= (3, 11): + res += draw(st.sampled_from([''] + ['z'])) + + # alternate mode + res += draw(st.sampled_from(['', '#'])) + + # pad with 0s + pad0 = draw(st.sampled_from(['', '0'])) + if pad0 and for_complex: + pad0 = '' + skip_thousand_separators = False + if pad0 and not skip_0_padding: + res += pad0 + skip_thousand_separators = True + + # Width + res += draw(st.sampled_from(['']*7 + list(map(str, range(1, 40))) + + ([] if for_complex else ['0' + str(_) + for _ in range(40)]))) + + # grouping character (thousand_separators) + gchar = draw(st.sampled_from([''] + list(',_'))) + if gchar and not skip_thousand_separators: + res += gchar + + # Precision + prec = draw(st.sampled_from(['']*7 + list(map(str, range(40))) + + ['0' + str(_) for _ in range(40)])) + if prec: + res += '.' + prec + if sys.version_info >= (3, 14): + gchar = draw(st.sampled_from([''] + list(',_'))) + res += gchar + + # Type + res += draw(st.sampled_from(types)) + + return res + + +def test_mpf_fmt_cpython(): + ''' + These tests assure that mpf.__format__ yields the same result as regular + float.__format__, when dps is default. + ''' + + # 'f' code formatting. + + # zeros + assert f'{mp.mpf(0):.0f}' == '0' + assert f'{mp.mpf(0):.1f}' == '0.0' + assert f'{mp.mpf(0):.2f}' == '0.00' + assert f'{mp.mpf(0):.3f}' == '0.000' + assert f'{mp.mpf(0):.50f}' == '0.00000000000000000000000000000000000000000000000000' + + # nan, infs + assert f'{inf:f}' == 'inf' + assert f'{inf:+f}' == '+inf' + assert f'{inf:F}' == 'INF' + assert f'{inf:+F}' == '+INF' + + assert f'{ninf:f}' == '-inf' + assert f'{ninf:+f}' == '-inf' + assert f'{ninf:F}' == '-INF' + assert f'{ninf:+F}' == '-INF' + + assert f'{nan:f}' == 'nan' + assert f'{nan:+f}' == '+nan' + assert f'{nan:F}' == 'NAN' + assert f'{nan:+F}' == '+NAN' + + # precision 0; result should never include a . + assert f'{mp.mpf(1.5):.0f}' == '2' + assert f'{mp.mpf(2.5):.0f}' == '2' + assert f'{mp.mpf(3.5):.0f}' == '4' + assert f'{mp.mpf(0.0):.0f}' == '0' + assert f'{mp.mpf(0.1):.0f}' == '0' + assert f'{mp.mpf(0.001):.0f}' == '0' + assert f'{mp.mpf(10.0):.0f}' == '10' + assert f'{mp.mpf(10.1):.0f}' == '10' + assert f'{mp.mpf(10.01):.0f}' == '10' + assert f'{mp.mpf(123.456):.0f}' == '123' + assert f'{mp.mpf(1234.56):.0f}' == '1235' + assert f'{mp.mpf(1e49):.0f}' == '9999999999999999464902769475481793196872414789632' + assert f'{mp.mpf(9.9999999999999987e+49):.0f}' == '99999999999999986860582406952576489172979654066176' + assert f'{mp.mpf(1e50):.0f}' == '100000000000000007629769841091887003294964970946560' + + # precision 1 + assert f'{mp.mpf(0.0001):.1f}' == '0.0' + assert f'{mp.mpf(0.001):.1f}' == '0.0' + assert f'{mp.mpf(0.01):.1f}' == '0.0' + assert f'{mp.mpf(0.04):.1f}' == '0.0' + assert f'{mp.mpf(0.06):.1f}' == '0.1' + assert f'{mp.mpf(0.25):.1f}' == '0.2' + assert f'{mp.mpf(0.75):.1f}' == '0.8' + assert f'{mp.mpf(1.4):.1f}' == '1.4' + assert f'{mp.mpf(1.5):.1f}' == '1.5' + assert f'{mp.mpf(10.0):.1f}' == '10.0' + assert f'{mp.mpf(1000.03):.1f}' == '1000.0' + assert f'{mp.mpf(1234.5678):.1f}' == '1234.6' + assert f'{mp.mpf(1234.7499):.1f}' == '1234.7' + assert f'{mp.mpf(1234.75):.1f}' == '1234.8' + + # precision 2 + assert f'{mp.mpf(0.0001):.2f}' == '0.00' + assert f'{mp.mpf(0.001):.2f}' == '0.00' + assert f'{mp.mpf(0.004999):.2f}' == '0.00' + assert f'{mp.mpf(0.005001):.2f}' == '0.01' + assert f'{mp.mpf(0.01):.2f}' == '0.01' + assert f'{mp.mpf(0.125):.2f}' == '0.12' + assert f'{mp.mpf(0.375):.2f}' == '0.38' + assert f'{mp.mpf(1234500):.2f}' == '1234500.00' + assert f'{mp.mpf(1234560):.2f}' == '1234560.00' + assert f'{mp.mpf(1234567):.2f}' == '1234567.00' + assert f'{mp.mpf(1234567.8):.2f}' == '1234567.80' + assert f'{mp.mpf(1234567.89):.2f}' == '1234567.89' + assert f'{mp.mpf(1234567.891):.2f}' == '1234567.89' + assert f'{mp.mpf(1234567.8912):.2f}' == '1234567.89' + + # alternate form always includes a decimal point. This only + # makes a difference when the precision is 0. + assert f'{mp.mpf(0):#.0f}' == '0.' + assert f'{mp.mpf(0):#.1f}' == '0.0' + assert f'{mp.mpf(1.5):#.0f}' == '2.' + assert f'{mp.mpf(2.5):#.0f}' == '2.' + assert f'{mp.mpf(10.1):#.0f}' == '10.' + assert f'{mp.mpf(1234.56):#.0f}' == '1235.' + assert f'{mp.mpf(1.4):#.1f}' == '1.4' + assert f'{mp.mpf(0.375):#.2f}' == '0.38' + + # if precision is omitted it defaults to 6 + assert f'{mp.mpf(0):f}' == '0.000000' + assert f'{mp.mpf(1230000):f}' == '1230000.000000' + assert f'{mp.mpf(1234567):f}' == '1234567.000000' + assert f'{mp.mpf(123.4567):f}' == '123.456700' + assert f'{mp.mpf(1.23456789):f}' == '1.234568' + assert f'{mp.mpf(0.00012):f}' == '0.000120' + assert f'{mp.mpf(0.000123):f}' == '0.000123' + assert f'{mp.mpf(0.00012345):f}' == '0.000123' + assert f'{mp.mpf(0.000001):f}' == '0.000001' + assert f'{mp.mpf(0.0000005001):f}' == '0.000001' + assert f'{mp.mpf(0.0000004999):f}' == '0.000000' + + # grouping in fractional part + assert f'{mp.mpf(0.0000004999):.9_f}' == '0.000_000_500' + + # 'e' code formatting with explicit precision (>= 0). Output should + # always have exactly the number of places after the point that were + # requested. + + # zeros + assert f'{mp.mpf(0):.0e}' == '0e+00' + assert f'{mp.mpf(0):.1e}' == '0.0e+00' + assert f'{mp.mpf(0):.2e}' == '0.00e+00' + assert f'{mp.mpf(0):.10e}' == '0.0000000000e+00' + assert f'{mp.mpf(0):.50e}' == '0.00000000000000000000000000000000000000000000000000e+00' + + # nan, infs + assert f'{inf:e}' == 'inf' + assert f'{inf:+e}' == '+inf' + assert f'{inf:E}' == 'INF' + assert f'{inf:+E}' == '+INF' + + assert f'{ninf:e}' == '-inf' + assert f'{ninf:+e}' == '-inf' + assert f'{ninf:E}' == '-INF' + assert f'{ninf:+E}' == '-INF' + + assert f'{nan:e}' == 'nan' + assert f'{nan:+e}' == '+nan' + assert f'{nan:E}' == 'NAN' + assert f'{nan:+E}' == '+NAN' + + # precision 0. no decimal point in the output + assert f'{mp.mpf(0.01):.0e}' == '1e-02' + assert f'{mp.mpf(0.1):.0e}' == '1e-01' + assert f'{mp.mpf(1):.0e}' == '1e+00' + assert f'{mp.mpf(10):.0e}' == '1e+01' + assert f'{mp.mpf(100):.0e}' == '1e+02' + assert f'{mp.mpf(0.012):.0e}' == '1e-02' + assert f'{mp.mpf(0.12):.0e}' == '1e-01' + assert f'{mp.mpf(1.2):.0e}' == '1e+00' + assert f'{mp.mpf(12):.0e}' == '1e+01' + assert f'{mp.mpf(120):.0e}' == '1e+02' + assert f'{mp.mpf(123.456):.0e}' == '1e+02' + assert f'{mp.mpf(0.000123456):.0e}' == '1e-04' + assert f'{mp.mpf(123456000):.0e}' == '1e+08' + assert f'{mp.mpf(0.5):.0e}' == '5e-01' + assert f'{mp.mpf(1.4):.0e}' == '1e+00' + assert f'{mp.mpf(1.5):.0e}' == '2e+00' + assert f'{mp.mpf(1.6):.0e}' == '2e+00' + assert f'{mp.mpf(2.4999999):.0e}' == '2e+00' + assert f'{mp.mpf(2.5):.0e}' == '2e+00' + assert f'{mp.mpf(2.5000001):.0e}' == '3e+00' + assert f'{mp.mpf(3.499999999999):.0e}' == '3e+00' + assert f'{mp.mpf(3.5):.0e}' == '4e+00' + assert f'{mp.mpf(4.5):.0e}' == '4e+00' + assert f'{mp.mpf(5.5):.0e}' == '6e+00' + assert f'{mp.mpf(6.5):.0e}' == '6e+00' + assert f'{mp.mpf(7.5):.0e}' == '8e+00' + assert f'{mp.mpf(8.5):.0e}' == '8e+00' + assert f'{mp.mpf(9.4999):.0e}' == '9e+00' + assert f'{mp.mpf(9.5):.0e}' == '1e+01' + assert f'{mp.mpf(10.5):.0e}' == '1e+01' + assert f'{mp.mpf(14.999):.0e}' == '1e+01' + assert f'{mp.mpf(15):.0e}' == '2e+01' + + # precision 1 + assert f'{mp.mpf(0.0001):.1e}' == '1.0e-04' + assert f'{mp.mpf(0.001):.1e}' == '1.0e-03' + assert f'{mp.mpf(0.01):.1e}' == '1.0e-02' + assert f'{mp.mpf(0.1):.1e}' == '1.0e-01' + assert f'{mp.mpf(1):.1e}' == '1.0e+00' + assert f'{mp.mpf(10):.1e}' == '1.0e+01' + assert f'{mp.mpf(100):.1e}' == '1.0e+02' + assert f'{mp.mpf(120):.1e}' == '1.2e+02' + assert f'{mp.mpf(123):.1e}' == '1.2e+02' + assert f'{mp.mpf(123.4):.1e}' == '1.2e+02' + + # precision 2 + assert f'{mp.mpf(0.00013):.2e}' == '1.30e-04' + assert f'{mp.mpf(0.000135):.2e}' == '1.35e-04' + assert f'{mp.mpf(0.0001357):.2e}' == '1.36e-04' + assert f'{mp.mpf(0.0001):.2e}' == '1.00e-04' + assert f'{mp.mpf(0.001):.2e}' == '1.00e-03' + assert f'{mp.mpf(0.01):.2e}' == '1.00e-02' + assert f'{mp.mpf(0.1):.2e}' == '1.00e-01' + assert f'{mp.mpf(1):.2e}' == '1.00e+00' + assert f'{mp.mpf(10):.2e}' == '1.00e+01' + assert f'{mp.mpf(100):.2e}' == '1.00e+02' + assert f'{mp.mpf(1000):.2e}' == '1.00e+03' + assert f'{mp.mpf(1500):.2e}' == '1.50e+03' + assert f'{mp.mpf(1590):.2e}' == '1.59e+03' + assert f'{mp.mpf(1598):.2e}' == '1.60e+03' + assert f'{mp.mpf(1598.7):.2e}' == '1.60e+03' + assert f'{mp.mpf(1598.76):.2e}' == '1.60e+03' + assert f'{mp.mpf(9999):.2e}' == '1.00e+04' + + # omitted precision defaults to 6 + assert f'{mp.mpf(0):e}' == '0.000000e+00' + assert f'{mp.mpf(165):e}' == '1.650000e+02' + assert f'{mp.mpf(1234567):e}' == '1.234567e+06' + assert f'{mp.mpf(12345678):e}' == '1.234568e+07' + assert f'{mp.mpf(1.1):e}' == '1.100000e+00' + + # alternate form always contains a decimal point. This only makes + # a difference when precision is 0. + + assert f'{mp.mpf(0.01):#.0e}' == '1.e-02' + assert f'{mp.mpf(0.1):#.0e}' == '1.e-01' + assert f'{mp.mpf(1):#.0e}' == '1.e+00' + assert f'{mp.mpf(10):#.0e}' == '1.e+01' + assert f'{mp.mpf(100):#.0e}' == '1.e+02' + assert f'{mp.mpf(0.012):#.0e}' == '1.e-02' + assert f'{mp.mpf(0.12):#.0e}' == '1.e-01' + assert f'{mp.mpf(1.2):#.0e}' == '1.e+00' + assert f'{mp.mpf(12):#.0e}' == '1.e+01' + assert f'{mp.mpf(120):#.0e}' == '1.e+02' + assert f'{mp.mpf(123.456):#.0e}' == '1.e+02' + assert f'{mp.mpf(0.000123456):#.0e}' == '1.e-04' + assert f'{mp.mpf(123456000):#.0e}' == '1.e+08' + assert f'{mp.mpf(0.5):#.0e}' == '5.e-01' + assert f'{mp.mpf(1.4):#.0e}' == '1.e+00' + assert f'{mp.mpf(1.5):#.0e}' == '2.e+00' + assert f'{mp.mpf(1.6):#.0e}' == '2.e+00' + assert f'{mp.mpf(2.4999999):#.0e}' == '2.e+00' + assert f'{mp.mpf(2.5):#.0e}' == '2.e+00' + assert f'{mp.mpf(2.5000001):#.0e}' == '3.e+00' + assert f'{mp.mpf(3.499999999999):#.0e}' == '3.e+00' + assert f'{mp.mpf(3.5):#.0e}' == '4.e+00' + assert f'{mp.mpf(4.5):#.0e}' == '4.e+00' + assert f'{mp.mpf(5.5):#.0e}' == '6.e+00' + assert f'{mp.mpf(6.5):#.0e}' == '6.e+00' + assert f'{mp.mpf(7.5):#.0e}' == '8.e+00' + assert f'{mp.mpf(8.5):#.0e}' == '8.e+00' + assert f'{mp.mpf(9.4999):#.0e}' == '9.e+00' + assert f'{mp.mpf(9.5):#.0e}' == '1.e+01' + assert f'{mp.mpf(10.5):#.0e}' == '1.e+01' + assert f'{mp.mpf(14.999):#.0e}' == '1.e+01' + assert f'{mp.mpf(15):#.0e}' == '2.e+01' + assert f'{mp.mpf(123.4):#.1e}' == '1.2e+02' + assert f'{mp.mpf(0.0001357):#.2e}' == '1.36e-04' + + # 'g' code formatting. + + # zeros + assert f'{mp.mpf(0):.0g}' == '0' + assert f'{mp.mpf(0):.1g}' == '0' + assert f'{mp.mpf(0):.2g}' == '0' + assert f'{mp.mpf(0):.3g}' == '0' + assert f'{mp.mpf(0):.4g}' == '0' + assert f'{mp.mpf(0):.10g}' == '0' + assert f'{mp.mpf(0):.50g}' == '0' + assert f'{mp.mpf(0):.100g}' == '0' + + # nan, infs + assert f'{inf:g}' == 'inf' + assert f'{inf:+g}' == '+inf' + assert f'{inf:G}' == 'INF' + assert f'{inf:+G}' == '+INF' + + assert f'{ninf:g}' == '-inf' + assert f'{ninf:+g}' == '-inf' + assert f'{ninf:G}' == '-INF' + assert f'{ninf:+G}' == '-INF' + + assert f'{nan:g}' == 'nan' + assert f'{nan:+g}' == '+nan' + assert f'{nan:G}' == 'NAN' + assert f'{nan:+G}' == '+NAN' + + # precision 0 doesn't make a lot of sense for the 'g' code (what does + # it mean to have no significant digits?); in practice, it's interpreted + # as identical to precision 1 + assert f'{mp.mpf(1000):.0g}' == '1e+03' + assert f'{mp.mpf(100):.0g}' == '1e+02' + assert f'{mp.mpf(10):.0g}' == '1e+01' + assert f'{mp.mpf(1):.0g}' == '1' + assert f'{mp.mpf(0.1):.0g}' == '0.1' + assert f'{mp.mpf(0.01):.0g}' == '0.01' + assert f'{mp.mpf(1e-3):.0g}' == '0.001' + assert f'{mp.mpf(1e-4):.0g}' == '0.0001' + assert f'{mp.mpf(1e-5):.0g}' == '1e-05' + assert f'{mp.mpf(1e-6):.0g}' == '1e-06' + assert f'{mp.mpf(12):.0g}' == '1e+01' + assert f'{mp.mpf(120):.0g}' == '1e+02' + assert f'{mp.mpf(1.2):.0g}' == '1' + assert f'{mp.mpf(0.12):.0g}' == '0.1' + assert f'{mp.mpf(0.012):.0g}' == '0.01' + assert f'{mp.mpf(0.0012):.0g}' == '0.001' + assert f'{mp.mpf(0.00012):.0g}' == '0.0001' + assert f'{mp.mpf(0.000012):.0g}' == '1e-05' + assert f'{mp.mpf(0.0000012):.0g}' == '1e-06' + + # precision 1 identical to precision 0 + assert f'{mp.mpf(1000):.1g}' == '1e+03' + assert f'{mp.mpf(100):.1g}' == '1e+02' + assert f'{mp.mpf(10):.1g}' == '1e+01' + assert f'{mp.mpf(1):.1g}' == '1' + assert f'{mp.mpf(0.1):.1g}' == '0.1' + assert f'{mp.mpf(0.01):.1g}' == '0.01' + assert f'{mp.mpf(1e-3):.1g}' == '0.001' + assert f'{mp.mpf(1e-4):.1g}' == '0.0001' + assert f'{mp.mpf(1e-5):.1g}' == '1e-05' + assert f'{mp.mpf(1e-6):.1g}' == '1e-06' + assert f'{mp.mpf(12):.1g}' == '1e+01' + assert f'{mp.mpf(120):.1g}' == '1e+02' + assert f'{mp.mpf(1.2):.1g}' == '1' + assert f'{mp.mpf(0.12):.1g}' == '0.1' + assert f'{mp.mpf(0.012):.1g}' == '0.01' + assert f'{mp.mpf(0.0012):.1g}' == '0.001' + assert f'{mp.mpf(0.00012):.1g}' == '0.0001' + assert f'{mp.mpf(0.000012):.1g}' == '1e-05' + assert f'{mp.mpf(0.0000012):.1g}' == '1e-06' + + # precision 2 + assert f'{mp.mpf(1000):.2g}' == '1e+03' + assert f'{mp.mpf(100):.2g}' == '1e+02' + assert f'{mp.mpf(10):.2g}' == '10' + assert f'{mp.mpf(1):.2g}' == '1' + assert f'{mp.mpf(0.1):.2g}' == '0.1' + assert f'{mp.mpf(0.01):.2g}' == '0.01' + assert f'{mp.mpf(0.001):.2g}' == '0.001' + assert f'{mp.mpf(1e-4):.2g}' == '0.0001' + assert f'{mp.mpf(1e-5):.2g}' == '1e-05' + assert f'{mp.mpf(1e-6):.2g}' == '1e-06' + assert f'{mp.mpf(1234):.2g}' == '1.2e+03' + assert f'{mp.mpf(123):.2g}' == '1.2e+02' + assert f'{mp.mpf(12.3):.2g}' == '12' + assert f'{mp.mpf(1.23):.2g}' == '1.2' + assert f'{mp.mpf(0.123):.2g}' == '0.12' + assert f'{mp.mpf(0.0123):.2g}' == '0.012' + assert f'{mp.mpf(0.00123):.2g}' == '0.0012' + assert f'{mp.mpf(0.000123):.2g}' == '0.00012' + assert f'{mp.mpf(0.0000123):.2g}' == '1.2e-05' + + # bad cases from http://bugs.python.org/issue9980 + assert f'{mp.mpf(38210.0):.12g}' == '38210' + assert f'{mp.mpf(37210.0):.12g}' == '37210' + assert f'{mp.mpf(36210.0):.12g}' == '36210' + + # alternate g formatting: always include decimal point and + # exactly significant digits. + assert f'{mp.mpf(0):#.0g}' == '0.' + assert f'{mp.mpf(0):#.1g}' == '0.' + assert f'{mp.mpf(0):#.2g}' == '0.0' + assert f'{mp.mpf(0):#.3g}' == '0.00' + assert f'{mp.mpf(0):#.4g}' == '0.000' + + assert f'{mp.mpf(0.2):#.0g}' == '0.2' + assert f'{mp.mpf(0.2):#.1g}' == '0.2' + assert f'{mp.mpf(0.2):#.2g}' == '0.20' + assert f'{mp.mpf(0.2):#.3g}' == '0.200' + assert f'{mp.mpf(0.2):#.4g}' == '0.2000' + assert f'{mp.mpf(0.2):#.10g}' == '0.2000000000' + + assert f'{mp.mpf(2):#.0g}' == '2.' + assert f'{mp.mpf(2):#.1g}' == '2.' + assert f'{mp.mpf(2):#.2g}' == '2.0' + assert f'{mp.mpf(2):#.3g}' == '2.00' + assert f'{mp.mpf(2):#.4g}' == '2.000' + + assert f'{mp.mpf(20):#.0g}' == '2.e+01' + assert f'{mp.mpf(20):#.1g}' == '2.e+01' + assert f'{mp.mpf(20):#.2g}' == '20.' + assert f'{mp.mpf(20):#.3g}' == '20.0' + assert f'{mp.mpf(20):#.4g}' == '20.00' + + assert f'{mp.mpf(234.56):#.0g}' == '2.e+02' + assert f'{mp.mpf(234.56):#.1g}' == '2.e+02' + assert f'{mp.mpf(234.56):#.2g}' == '2.3e+02' + assert f'{mp.mpf(234.56):#.3g}' == '235.' + assert f'{mp.mpf(234.56):#.4g}' == '234.6' + assert f'{mp.mpf(234.56):#.5g}' == '234.56' + assert f'{mp.mpf(234.56):#.6g}' == '234.560' + + # '%' code formatting. + + # nan, infs + assert f'{inf:%}' == 'inf%' + assert f'{inf:+%}' == '+inf%' + + assert f'{ninf:%}' == '-inf%' + assert f'{ninf:+%}' == '-inf%' + + assert f'{nan:%}' == 'nan%' + assert f'{nan:+%}' == '+nan%' + + # No formatting code. + + assert f'{mp.mpf(0.0):.0}' == '0e+00' + assert f'{mp.pi}' == '3.14159265358979' + mp.pretty_dps = 'repr' + assert f'{mp.pi}' == '3.1415926535897931' + + +@given(fmt_str(types=list('fFeEgG%') + ['']), + st.floats(allow_nan=True, + allow_infinity=True, + allow_subnormal=True)) +@example(fmt='.0g', x=9.995074823339339e-05) # issue 880 +@example(fmt='.016f', x=0.1) # issue 915 +@example(fmt='0030f', x=0.3) +@example(fmt='0=13,f', x=1.1) # issue 917 +@example(fmt='013,f', x=1.1) +@example(fmt='013,.0%', x=1.1) +@example(fmt='010.6,f', x=0.1234567891) +@example(fmt='010.7,f', x=0.1234567891) +@example(fmt='010._f', x=0.1234567891) +def test_mpf_floats_bulk(fmt, x): + ''' + These are additional random tests that check that mp.mpf and fp.mpf yield + the same results for default precision. + ''' + + mp.pretty_dps = "repr" + if not x and math.copysign(1, x) == -1: + return # skip negative zero + spec = read_format_spec(fmt) + if spec['frac_separators'] and sys.version_info < (3, 14): + mp.pretty_dps = "str" + return # see also python/cpython#130860 + if not spec['type'] and spec['precision'] < 0 and math.isfinite(x): + # The mpmath could choose a different decimal + # representative (wrt CPython) for same binary + # floating-point number. + assert float(format(x)) == float(format(mp.mpf(x))) + else: + if spec['type'] == '%' and math.isinf(100*x): + return # mpf can't overflow + assert format(x, fmt) == format(mp.mpf(x), fmt) + + +@given(fmt_str(types=list('gGfFeE') + [''], for_complex=True), + st.complex_numbers(allow_nan=True, + allow_infinity=True, + allow_subnormal=True)) +def test_mpc_complexes(fmt, z): + mp.pretty_dps = "repr" + if ((not z.real and math.copysign(1, z.real) == -1) + or (not z.imag and math.copysign(1, z.imag) == -1)): + return # skip negative zero + spec = read_format_spec(fmt) + if spec['frac_separators'] and sys.version_info < (3, 14): + return # see also python/cpython#130860 + if spec['precision'] < 0 and any(math.isfinite(_) for _ in [z.real, z.imag]): + # The mpmath could choose a different decimal + # representative (wrt CPython) for same binary + # floating-point number. + if cmath.isnan(complex(format(z))): + assert cmath.isnan(complex(format(mp.mpc(z)))) + else: + assert complex(format(z)) == complex(format(mp.mpc(z))) + else: + assert format(z, fmt) == format(mp.mpc(z), fmt) + + +def test_mpc_fmt(): + pytest.raises(ValueError, lambda: f'{mp.mpc(1j):=10f}') + pytest.raises(ValueError, lambda: f'{mp.mpc(1j):010f}') + pytest.raises(ValueError, lambda: f'{mp.mpc(1j):%}') + + assert f'{1+1j:.0g}' == f'{mp.mpc(1+1j):.0g}' + assert f'{1+1.1j:.2g}' == f'{mp.mpc(1+1.1j):.2g}' + + +def test_mpf_fmt(): + ''' + These tests are either specific tests to mpf, or tests that cover + code that is not covered in the CPython tests. + ''' + + with workdps(1000): + # Numbers with more than 15 significant digits + # fixed format + assert f"{mp.mpf('1.234567890123456789'):.20f}" == '1.23456789012345678900' + assert f"{mp.mpf('1.234567890123456789'):.25f}" == '1.2345678901234567890000000' + assert f"{mp.mpf('1.234567890123456789'):.30f}" == '1.234567890123456789000000000000' + assert f"{mp.mpf('1e-50'):.50f}" == '0.00000000000000000000000000000000000000000000000001' + + # scientific notation + assert f"{mp.mpf('1.234567890123456789'):.20e}" == '1.23456789012345678900e+00' + assert f"{mp.mpf('1.234567890123456789'):.25e}" == '1.2345678901234567890000000e+00' + assert f"{mp.mpf('1.234567890123456789'):.30e}" == '1.234567890123456789000000000000e+00' + assert f"{mp.mpf('1e-50'):.50e}" == '1.00000000000000000000000000000000000000000000000000e-50' + + # width and fill char + assert f"{mp.mpf('0.01'):z<10.5f}" == '0.01000zzz' + assert f"{mp.mpf('0.01'):z^10.5f}" == 'z0.01000zz' + assert f"{mp.mpf('0.01'):z>10.5f}" == 'zzz0.01000' + assert f"{mp.mpf('0.01'):z=10.5f}" == 'zzz0.01000' + + assert f"{mp.mpf('0.01'):z<+10.5f}" == '+0.01000zz' + assert f"{mp.mpf('0.01'):z^+10.5f}" == 'z+0.01000z' + assert f"{mp.mpf('0.01'):z>+10.5f}" == 'zz+0.01000' + assert f"{mp.mpf('0.01'):z=+10.5f}" == '+zz0.01000' + + assert f"{mp.mpf('-0.01'):z<10.5f}" == '-0.01000zz' + assert f"{mp.mpf('-0.01'):z^10.5f}" == 'z-0.01000z' + assert f"{mp.mpf('-0.01'):z>10.5f}" == 'zz-0.01000' + assert f"{mp.mpf('-0.01'):z=10.5f}" == '-zz0.01000' + + assert f"{mp.mpf('0.01'):z<15.5e}" == '1.00000e-02zzzz' + assert f"{mp.mpf('0.01'):z^15.5e}" == 'zz1.00000e-02zz' + assert f"{mp.mpf('0.01'):z>15.5e}" == 'zzzz1.00000e-02' + assert f"{mp.mpf('0.01'):z=15.5e}" == 'zzzz1.00000e-02' + + assert f"{mp.mpf('0.01'):z<+15.5e}" == '+1.00000e-02zzz' + assert f"{mp.mpf('0.01'):z^+15.5e}" == 'z+1.00000e-02zz' + assert f"{mp.mpf('0.01'):z>+15.5e}" == 'zzz+1.00000e-02' + assert f"{mp.mpf('0.01'):z=+15.5e}" == '+zzz1.00000e-02' + + assert f"{mp.mpf('-0.01'):z<15.5e}" == '-1.00000e-02zzz' + assert f"{mp.mpf('-0.01'):z^15.5e}" == 'z-1.00000e-02zz' + assert f"{mp.mpf('-0.01'):z>15.5e}" == 'zzz-1.00000e-02' + assert f"{mp.mpf('-0.01'):z=15.5e}" == '-zzz1.00000e-02' + + # capitalized scientific notation + assert f"{mp.mpf('-0.01'):z<15.5E}" == '-1.00000E-02zzz' + + # generalized format + assert f"{mp.mpf('1.234567890123456789'):.20g}" == '1.234567890123456789' + assert f"{mp.mpf('1.234567890123456789'):.25g}" == '1.234567890123456789' + assert f"{mp.mpf('1.234567890123456789'):.30g}" == '1.234567890123456789' + assert f"{mp.mpf('1e-50'):.50g}" == '1e-50' + assert f"{mp.mpf('1e-50'):.50G}" == '1E-50' + + assert f"{mp.mpf('1e-51'):}" == '1e-51' + + # thousands separator + assert f"{mp.mpf('1e9'):,.0f}" == '1,000,000,000' + assert f"{mp.mpf('123456789.0123456'):,.4f}" == '123,456,789.0123' + assert f"{mp.mpf('1234567890.123456'):_.4f}" == '1_234_567_890.1235' + assert f"{mp.mpf('1234.5678'):_.4f}" == '1_234.5678' + + assert f"{mp.mpf('1e9'):,.0e}" == '1e+09' + assert f"{mp.mpf('123456789.0123456'):,.4e}" == '1.2346e+08' + assert f"{mp.mpf('1234567890.123456'):_.4e}" == '1.2346e+09' + assert f"{mp.mpf('1234.5678'):_.4e}" == '1.2346e+03' + + # Tests for no_neg_0 + assert f"{mp.mpf('-1e-4'):,.2f}" == '-0.00' + assert f"{mp.mpf('-1e-4'):z,.2f}" == '0.00' + + # Tests for = alignment + assert f"{mp.mpf('0.24'):=+20.2f}" == '+ 0.24' + assert f"{mp.mpf('0.24'):=+020.2e}" == '+000000000002.40e-01' + assert f"{mp.mpf('0.24'):=+020.2g}" == '+0000000000000000.24' + + # Tests for different kinds of rounding + num = mp.mpf('-1.23456789999901234567') + assert f"{num:=.2Uf}" == "-1.23" + assert f"{num:=.2Df}" == "-1.24" + assert f"{num:=.2Zf}" == "-1.23" + assert f"{num:=.2Nf}" == "-1.23" + assert f"{num:=.2Yf}" == "-1.24" + + assert f"{num:=.3Uf}" == "-1.234" + assert f"{num:=.3Df}" == "-1.235" + assert f"{num:=.3Zf}" == "-1.234" + assert f"{num:=.3Nf}" == "-1.235" + assert f"{num:=.3Yf}" == "-1.235" + + assert f"{num:=.10Uf}" == "-1.2345678999" + assert f"{num:=.10Df}" == "-1.2345679000" + assert f"{num:=.10Zf}" == "-1.2345678999" + assert f"{num:=.10Nf}" == "-1.2345679000" + assert f"{num:=.10Yf}" == "-1.2345679000" + + num = mp.mpf('1.23456789999901234567') + assert f"{num:=.2Uf}" == "1.24" + assert f"{num:=.2Df}" == "1.23" + assert f"{num:=.2Zf}" == "1.23" + assert f"{num:=.2Nf}" == "1.23" + assert f"{num:=.2Yf}" == "1.24" + + assert f"{num:=.3Uf}" == "1.235" + assert f"{num:=.3Df}" == "1.234" + assert f"{num:=.3Zf}" == "1.234" + assert f"{num:=.3Nf}" == "1.235" + assert f"{num:=.3Yf}" == "1.235" + + assert f"{num:=.10Uf}" == "1.2345679000" + assert f"{num:=.10Df}" == "1.2345678999" + assert f"{num:=.10Zf}" == "1.2345678999" + assert f"{num:=.10Nf}" == "1.2345679000" + assert f"{num:=.10Yf}" == "1.2345679000" + + num = mp.mpf('-123.456789999901234567') + assert f"{num:=.2Ue}" == "-1.23e+02" + assert f"{num:=.2De}" == "-1.24e+02" + assert f"{num:=.2Ze}" == "-1.23e+02" + assert f"{num:=.2Ne}" == "-1.23e+02" + assert f"{num:=.2Ye}" == "-1.24e+02" + + assert f"{num:=.3Ue}" == "-1.234e+02" + assert f"{num:=.3De}" == "-1.235e+02" + assert f"{num:=.3Ze}" == "-1.234e+02" + assert f"{num:=.3Ne}" == "-1.235e+02" + assert f"{num:=.3Ye}" == "-1.235e+02" + + assert f"{num:=.10Ue}" == "-1.2345678999e+02" + assert f"{num:=.10De}" == "-1.2345679000e+02" + assert f"{num:=.10Ze}" == "-1.2345678999e+02" + assert f"{num:=.10Ne}" == "-1.2345679000e+02" + assert f"{num:=.10Ye}" == "-1.2345679000e+02" + + num = mp.mpf('123456.789999901234567') + assert f"{num:=.2Ue}" == "1.24e+05" + assert f"{num:=.2De}" == "1.23e+05" + assert f"{num:=.2Ze}" == "1.23e+05" + assert f"{num:=.2Ne}" == "1.23e+05" + assert f"{num:=.2Ye}" == "1.24e+05" + + assert f"{num:=.3Ue}" == "1.235e+05" + assert f"{num:=.3De}" == "1.234e+05" + assert f"{num:=.3Ze}" == "1.234e+05" + assert f"{num:=.3Ne}" == "1.235e+05" + assert f"{num:=.3Ye}" == "1.235e+05" + + assert f"{num:=.10Ue}" == "1.2345679000e+05" + assert f"{num:=.10De}" == "1.2345678999e+05" + assert f"{num:=.10Ze}" == "1.2345678999e+05" + assert f"{num:=.10Ne}" == "1.2345679000e+05" + assert f"{num:=.10Ye}" == "1.2345679000e+05" + + assert f"{mp.mpf('123.456'):.2Ug}" == "1.3e+02" + assert f"{mp.mpf('123.456'):.2Dg}" == "1.2e+02" + assert f"{mp.mpf('123.456'):.2Zg}" == "1.2e+02" + assert f"{mp.mpf('123.456'):.2Ng}" == "1.2e+02" + assert f"{mp.mpf('123.456'):.2Yg}" == "1.3e+02" + + assert f"{mp.mpf('-123.456'):.2Ug}" == "-1.2e+02" + assert f"{mp.mpf('-123.456'):.2Dg}" == "-1.3e+02" + assert f"{mp.mpf('-123.456'):.2Zg}" == "-1.2e+02" + assert f"{mp.mpf('-123.456'):.2Ng}" == "-1.2e+02" + assert f"{mp.mpf('-123.456'):.2Yg}" == "-1.3e+02" + + assert f"{mp.mpf('123.456'):.5Ug}" == "123.46" + assert f"{mp.mpf('123.456'):.5Dg}" == "123.45" + assert f"{mp.mpf('123.456'):.5Zg}" == "123.45" + assert f"{mp.mpf('123.456'):.5Ng}" == "123.46" + assert f"{mp.mpf('123.456'):.5Yg}" == "123.46" + + assert f"{mp.mpf('-123.456'):.5Ug}" == "-123.45" + assert f"{mp.mpf('-123.456'):.5Dg}" == "-123.46" + assert f"{mp.mpf('-123.456'):.5Zg}" == "-123.45" + assert f"{mp.mpf('-123.456'):.5Ng}" == "-123.46" + assert f"{mp.mpf('-123.456'):.5Yg}" == "-123.46" + + # Special cases were tying is relevant (cases involve exact floats) + assert f"{mp.mpf('0.25'):.1Nf}" == "0.2" + assert f"{mp.mpf('0.75'):.1Nf}" == "0.8" + + num = mp.mpf('0.1') + assert f"{-num:=.2Df}" == "-0.11" + assert f"{-num:=.3Df}" == "-0.101" + assert f"{-num:=.4Df}" == "-0.1001" + assert f"{-num:=.5Df}" == "-0.10001" + assert f"{-num:=.6Df}" == "-0.100001" + assert f"{-num:=.7Df}" == "-0.1000001" + + assert f"{-num:=.2De}" == "-1.01e-01" + assert f"{-num:=.3De}" == "-1.001e-01" + assert f"{-num:=.4De}" == "-1.0001e-01" + assert f"{-num:=.5De}" == "-1.00001e-01" + assert f"{-num:=.6De}" == "-1.000001e-01" + assert f"{-num:=.7De}" == "-1.0000001e-01" + + assert f"{num:=.2Uf}" == "0.11" + assert f"{num:=.3Uf}" == "0.101" + assert f"{num:=.4Uf}" == "0.1001" + assert f"{num:=.5Uf}" == "0.10001" + assert f"{num:=.6Uf}" == "0.100001" + assert f"{num:=.7Uf}" == "0.1000001" + + assert f"{num:=.2Ue}" == "1.01e-01" + assert f"{num:=.3Ue}" == "1.001e-01" + assert f"{num:=.4Ue}" == "1.0001e-01" + assert f"{num:=.5Ue}" == "1.00001e-01" + assert f"{num:=.6Ue}" == "1.000001e-01" + assert f"{num:=.7Ue}" == "1.0000001e-01" + + num = mp.mpf('0.25') + assert f"{-num:=.2Df}" == "-0.25" + assert f"{-num:=.3Df}" == "-0.250" + assert f"{-num:=.4Df}" == "-0.2500" + assert f"{-num:=.5Df}" == "-0.25000" + assert f"{-num:=.6Df}" == "-0.250000" + assert f"{-num:=.7Df}" == "-0.2500000" + + assert f"{-num:=.2De}" == "-2.50e-01" + assert f"{-num:=.3De}" == "-2.500e-01" + assert f"{-num:=.4De}" == "-2.5000e-01" + assert f"{-num:=.5De}" == "-2.50000e-01" + assert f"{-num:=.6De}" == "-2.500000e-01" + assert f"{-num:=.7De}" == "-2.5000000e-01" + + assert f"{num:=.2Uf}" == "0.25" + assert f"{num:=.3Uf}" == "0.250" + assert f"{num:=.4Uf}" == "0.2500" + assert f"{num:=.5Uf}" == "0.25000" + assert f"{num:=.6Uf}" == "0.250000" + assert f"{num:=.7Uf}" == "0.2500000" + + assert f"{num:=.2Ue}" == "2.50e-01" + assert f"{num:=.3Ue}" == "2.500e-01" + assert f"{num:=.4Ue}" == "2.5000e-01" + assert f"{num:=.5Ue}" == "2.50000e-01" + assert f"{num:=.6Ue}" == "2.500000e-01" + assert f"{num:=.7Ue}" == "2.5000000e-01" + + # Changing the work precision changes the number printed (This is + # expected behavior) + with mp.workdps(20): + assert f"{mp.mpf('0.1'):=.4Uf}" == "0.1000" + assert f"{mp.mpf('-0.1'):=.4Df}" == "-0.1000" + + +def test_default_rounding(): + x = mp.mpf(mp.pi) + + assert f"{x:.3f}" == '3.142' + mp.rounding = 'd' + assert f"{x:.3f}" == '3.141' + mp.rounding = 'u' + assert f"{x:.3f}" == '3.142' + + +def test_issue_858(): + for n in range(2, 15): + str_num = '0.' + (n)*'9' + fmt_str = '.' + str(n-1) + 'f' + + assert format(mp.mpf(str_num), fmt_str) == format(fp.mpf(str_num), fmt_str) + + assert format(mp.mpf(0.96875), '#.0g') == '1.' + + +def test_errors(): + with pytest.raises(ValueError): + # wrong format type + f"{mp.mpf('-4'):22.15k}" + + with pytest.raises(ValueError, match="Invalid format specifier '= 0: + fmt = '.' + str(p) + 'b' + else: + fmt = 'b' + g_bin = format(gmpy2.mpfr(x), fmt) + m_bin = format(mp.mpf(x), fmt) + assert m_bin == g_bin + + +def test_binary_fmt(): + x = mp.mpf(3) + assert f'{x:b}' == '1.1p+1' + assert f'{x:.2b}' == '1.10p+1' + assert f'{x:+.2b}' == '+1.10p+1' + assert f'{x:#.2b}' == '1.10p+1' + assert f'{x:.0b}' == '1p+2' + assert f'{x:#.0b}' == '1.p+2' + + x = mp.mpf(0) + assert f'{x:.2b}' == '0.00p+0' + assert f'{x:b}' == '0p+0' + + +def test_hexadecimal_fmt(): + with workdps(1000): + x = mp.mpf('1.234567890123456789') + assert f'{x:.20a}' == '0x1.3c0ca428c59fb71a4194p+0' + assert f'{x:.20A}' == '0X1.3C0CA428C59FB71A4194P+0' + assert f'{x:.0a}' == '0x1p+0' + assert f'{x:#.0a}' == '0x1.p+0' + assert f"{mp.mpf('1.234567890123456789'):+.0a}" == '+0x1p+0' diff --git a/mpmath/tests/test_fp.py b/mpmath/tests/test_fp.py new file mode 100644 index 0000000..b9247b3 --- /dev/null +++ b/mpmath/tests/test_fp.py @@ -0,0 +1,1813 @@ +""" +Easy-to-use test-generating code: + +cases = ''' +exp 2.25 +log 2.25 +''' + +from mpmath import ae, fp, mp +mp.dps = 20 +for test in cases.splitlines(): + if not test: + continue + words = test.split() + fname = words[0] + args = words[1:] + argstr = ", ".join(args) + testline = "%s(%s)" % (fname, argstr) + ans = str(eval(testline)) + print " assert ae(fp.%s, %s)" % (testline, ans) + +""" + +import pytest + +from mpmath import fp + + +def ae(x, y, tol=1e-12): + if x == y: + return True + return abs(x-y) <= tol*abs(y) + +def test_conj(): + assert fp.conj(4) == 4 + assert fp.conj(3+4j) == 3-4j + assert fp.fdot([1,2],[3,2+1j], conjugate=True) == 7-2j + +def test_fp_number_parts(): + assert ae(fp.arg(3), 0.0) + assert ae(fp.arg(-3), 3.1415926535897932385) + assert ae(fp.arg(3j), 1.5707963267948966192) + assert ae(fp.arg(-3j), -1.5707963267948966192) + assert ae(fp.arg(2+3j), 0.98279372324732906799) + assert ae(fp.arg(-1-1j), -2.3561944901923449288) + assert ae(fp.re(2.5), 2.5) + assert ae(fp.re(2.5+3j), 2.5) + assert ae(fp.im(2.5), 0.0) + assert ae(fp.im(2.5+3j), 3.0) + assert ae(fp.floor(2.5), 2.0) + assert ae(fp.floor(2), 2.0) + assert ae(fp.floor(2.0+0j), (2.0 + 0.0j)) + assert ae(fp.floor(-1.5-0.5j), (-2.0 - 1.0j)) + assert ae(fp.ceil(2.5), 3.0) + assert ae(fp.ceil(2), 2.0) + assert ae(fp.ceil(2.0+0j), (2.0 + 0.0j)) + assert ae(fp.ceil(-1.5-0.5j), (-1.0 + 0.0j)) + +def test_fp_cospi_sinpi(): + assert ae(fp.sinpi(0), 0.0) + assert ae(fp.sinpi(0.25), 0.7071067811865475244) + assert ae(fp.sinpi(0.5), 1.0) + assert ae(fp.sinpi(0.75), 0.7071067811865475244) + assert ae(fp.sinpi(1), 0.0) + assert ae(fp.sinpi(1.25), -0.7071067811865475244) + assert ae(fp.sinpi(1.5), -1.0) + assert ae(fp.sinpi(1.75), -0.7071067811865475244) + assert ae(fp.sinpi(2), 0.0) + assert ae(fp.sinpi(2.25), 0.7071067811865475244) + assert ae(fp.sinpi(0+3j), (0.0 + 6195.8238636085899556j)) + assert ae(fp.sinpi(0.25+3j), (4381.1091260582448033 + 4381.1090689950686908j)) + assert ae(fp.sinpi(0.5+3j), (6195.8239443081075259 + 0.0j)) + assert ae(fp.sinpi(0.75+3j), (4381.1091260582448033 - 4381.1090689950686908j)) + assert ae(fp.sinpi(1+3j), (0.0 - 6195.8238636085899556j)) + assert ae(fp.sinpi(1.25+3j), (-4381.1091260582448033 - 4381.1090689950686908j)) + assert ae(fp.sinpi(1.5+3j), (-6195.8239443081075259 + 0.0j)) + assert ae(fp.sinpi(1.75+3j), (-4381.1091260582448033 + 4381.1090689950686908j)) + assert ae(fp.sinpi(2+3j), (0.0 + 6195.8238636085899556j)) + assert ae(fp.sinpi(2.25+3j), (4381.1091260582448033 + 4381.1090689950686908j)) + assert ae(fp.sinpi(-0.75), -0.7071067811865475244) + assert ae(fp.sinpi(-1e-10), -3.1415926535897933529e-10) + assert ae(fp.sinpi(1e-10), 3.1415926535897933529e-10) + assert ae(fp.sinpi(1e-10+1e-10j), (3.141592653589793353e-10 + 3.1415926535897933528e-10j)) + assert ae(fp.sinpi(1e-10-1e-10j), (3.141592653589793353e-10 - 3.1415926535897933528e-10j)) + assert ae(fp.sinpi(-1e-10+1e-10j), (-3.141592653589793353e-10 + 3.1415926535897933528e-10j)) + assert ae(fp.sinpi(-1e-10-1e-10j), (-3.141592653589793353e-10 - 3.1415926535897933528e-10j)) + assert ae(fp.cospi(0), 1.0) + assert ae(fp.cospi(0.25), 0.7071067811865475244) + assert ae(fp.cospi(0.5), 0.0) + assert ae(fp.cospi(0.75), -0.7071067811865475244) + assert ae(fp.cospi(1), -1.0) + assert ae(fp.cospi(1.25), -0.7071067811865475244) + assert ae(fp.cospi(1.5), 0.0) + assert ae(fp.cospi(1.75), 0.7071067811865475244) + assert ae(fp.cospi(2), 1.0) + assert ae(fp.cospi(2.25), 0.7071067811865475244) + assert ae(fp.cospi(0+3j), (6195.8239443081075259 + 0.0j)) + assert ae(fp.cospi(0.25+3j), (4381.1091260582448033 - 4381.1090689950686908j)) + assert ae(fp.cospi(0.5+3j), (0.0 - 6195.8238636085899556j)) + assert ae(fp.cospi(0.75+3j), (-4381.1091260582448033 - 4381.1090689950686908j)) + assert ae(fp.cospi(1+3j), (-6195.8239443081075259 + 0.0j)) + assert ae(fp.cospi(1.25+3j), (-4381.1091260582448033 + 4381.1090689950686908j)) + assert ae(fp.cospi(1.5+3j), (0.0 + 6195.8238636085899556j)) + assert ae(fp.cospi(1.75+3j), (4381.1091260582448033 + 4381.1090689950686908j)) + assert ae(fp.cospi(2+3j), (6195.8239443081075259 + 0.0j)) + assert ae(fp.cospi(2.25+3j), (4381.1091260582448033 - 4381.1090689950686908j)) + assert ae(fp.cospi(-0.75), -0.7071067811865475244) + assert ae(fp.sinpi(-0.7), -0.80901699437494750611) + assert ae(fp.cospi(-0.7), -0.5877852522924730163) + assert ae(fp.cospi(-3+2j), (-267.74676148374822225 + 0.0j)) + assert ae(fp.sinpi(-3+2j), (0.0 - 267.74489404101651426j)) + assert ae(fp.sinpi(-0.7+2j), (-216.6116802292079471 - 157.37650009392034693j)) + assert ae(fp.cospi(-0.7+2j), (-157.37759774921754565 + 216.61016943630197336j)) + +def test_fp_asinh_acosh_atanh(): + assert ae(fp.asinh(0), 0.0) + assert ae(fp.acosh(1), 0.0) + assert ae(fp.atanh(0), 0.0) + +def test_fp_tanh(): + assert fp.tanh(fp.mpc(+fp.inf, +fp.inf)) == fp.mpc(+1, 0) + assert fp.tanh(fp.mpc(+fp.inf, -fp.inf)) == fp.mpc(+1, 0) + assert fp.tanh(fp.mpc(-fp.inf, +fp.inf)) == fp.mpc(-1, 0) + assert fp.tanh(fp.mpc(-fp.inf, -fp.inf)) == fp.mpc(-1, 0) + assert fp.tanh(fp.mpc(+fp.inf, 2)) == fp.mpc(+1, 0) + assert fp.tanh(fp.mpc(-fp.inf, 2)) == fp.mpc(-1, 0) + r = fp.tanh(fp.mpc(0, -fp.inf)) + assert r.real == 0 and fp.isnan(r.imag) + r = fp.tanh(fp.mpc(2, -fp.inf)) + assert fp.isnan(r.real) and fp.isnan(r.imag) + assert fp.tanh(fp.mpc(+fp.inf, fp.nan)) == fp.mpc(+1, 0) + assert fp.tanh(fp.mpc(-fp.inf, fp.nan)) == fp.mpc(-1, 0) + +def test_fp_tan(): + assert fp.tan(fp.mpc(+fp.inf, +fp.inf)) == fp.mpc(0, +1) + assert fp.tan(fp.mpc(+fp.inf, -fp.inf)) == fp.mpc(0, -1) + assert fp.tan(fp.mpc(-fp.inf, +fp.inf)) == fp.mpc(0, +1) + assert fp.tan(fp.mpc(-fp.inf, -fp.inf)) == fp.mpc(0, -1) + assert fp.tan(fp.mpc(2, +fp.inf)) == fp.mpc(0, +1) + assert fp.tan(fp.mpc(2, -fp.inf)) == fp.mpc(0, -1) + r = fp.tan(fp.mpc(-fp.inf, 0)) + assert fp.isnan(r.real) and r.imag == 0 + r = fp.tan(fp.mpc(-fp.inf, 2)) + assert fp.isnan(r.real) and fp.isnan(r.imag) + assert fp.tan(fp.mpc(fp.nan, +fp.inf)) == fp.mpc(0, +1) + assert fp.tan(fp.mpc(fp.nan, -fp.inf)) == fp.mpc(0, -1) + +def test_fp_asin(): + pi4 = fp.pi/4 + assert fp.asin(fp.mpc(+fp.inf, +fp.inf)) == fp.mpc(+pi4, +fp.inf) + assert fp.asin(fp.mpc(+fp.inf, -fp.inf)) == fp.mpc(+pi4, -fp.inf) + assert fp.asin(fp.mpc(-fp.inf, +fp.inf)) == fp.mpc(-pi4, +fp.inf) + assert fp.asin(fp.mpc(-fp.inf, -fp.inf)) == fp.mpc(-pi4, -fp.inf) + r = fp.asin(fp.mpc(+fp.inf, fp.nan)) + assert fp.isnan(r.real) and r.imag == -fp.inf + r = fp.asin(fp.mpc(-fp.inf, fp.nan)) + assert fp.isnan(r.real) and r.imag == -fp.inf + r = fp.asin(fp.mpc(fp.nan, +fp.inf)) + assert fp.isnan(r.real) and r.imag == +fp.inf + r = fp.asin(fp.mpc(fp.nan, -fp.inf)) + assert fp.isnan(r.real) and r.imag == -fp.inf + pi2 = fp.pi/2 + assert fp.asin(fp.mpc(+fp.inf, +1)) == fp.mpc(pi2, +fp.inf) + assert fp.asin(fp.mpc(+fp.inf, -1)) == fp.mpc(pi2, -fp.inf) + assert fp.asin(fp.mpc(+fp.inf, +0.0)) == fp.mpc(pi2, +fp.inf) + assert fp.asin(fp.mpc(+fp.inf, -0.0)) == fp.mpc(pi2, -fp.inf) + assert fp.asin(fp.mpc(-fp.inf, +1)) == fp.mpc(-pi2, +fp.inf) + assert fp.asin(fp.mpc(-fp.inf, -1)) == fp.mpc(-pi2, -fp.inf) + assert fp.asin(fp.mpc(-fp.inf, +0.0)) == fp.mpc(-pi2, +fp.inf) + assert fp.asin(fp.mpc(-fp.inf, -0.0)) == fp.mpc(-pi2, -fp.inf) + assert fp.asin(fp.mpc(+1, +fp.inf)) == fp.mpc(+0.0, +fp.inf) + assert fp.asin(fp.mpc(-1, +fp.inf)) == fp.mpc(-0.0, +fp.inf) + assert fp.asin(fp.mpc(+0.0, +fp.inf)) == fp.mpc(+0.0, +fp.inf) + assert fp.asin(fp.mpc(-0.0, +fp.inf)) == fp.mpc(-0.0, +fp.inf) + assert fp.asin(fp.mpc(+1, -fp.inf)) == fp.mpc(+0.0, -fp.inf) + assert fp.asin(fp.mpc(-1, -fp.inf)) == fp.mpc(-0.0, -fp.inf) + assert fp.asin(fp.mpc(+0.0, -fp.inf)) == fp.mpc(+0.0, -fp.inf) + assert fp.asin(fp.mpc(-0.0, -fp.inf)) == fp.mpc(-0.0, -fp.inf) + assert ae(fp.asin(fp.mpc(-2, +0.0)), fp.mpc(-pi2, -fp.log(2 - fp.sqrt(3)))) + assert ae(fp.asin(fp.mpc(-2, -0.0)), fp.mpc(-pi2, +fp.log(2 - fp.sqrt(3)))) + assert ae(fp.asin(fp.mpc(+2, +0.0)), fp.mpc(+pi2, +fp.log(2 + fp.sqrt(3)))) + assert ae(fp.asin(fp.mpc(+2, -0.0)), fp.mpc(+pi2, -fp.log(2 + fp.sqrt(3)))) + assert ae(fp.asin(fp.mpc(0.5, +0.0)), fp.pi/6) + assert ae(fp.asin(fp.mpc(0.5, -0.0)), fp.pi/6) + +def test_fp_acos(): + pi4 = fp.pi/4 + assert fp.acos(fp.mpc(+fp.inf, +fp.inf)) == fp.mpc(+pi4, -fp.inf) + assert fp.acos(fp.mpc(+fp.inf, -fp.inf)) == fp.mpc(+pi4, +fp.inf) + assert fp.acos(fp.mpc(-fp.inf, +fp.inf)) == fp.mpc(pi4*3, -fp.inf) + assert fp.acos(fp.mpc(-fp.inf, -fp.inf)) == fp.mpc(pi4*3, +fp.inf) + r = fp.acos(fp.mpc(+fp.inf, fp.nan)) + assert fp.isnan(r.real) and r.imag == fp.inf + r = fp.acos(fp.mpc(-fp.inf, fp.nan)) + assert fp.isnan(r.real) and r.imag == fp.inf + r = fp.acos(fp.mpc(fp.nan, +fp.inf)) + assert fp.isnan(r.real) and r.imag == -fp.inf + r = fp.acos(fp.mpc(fp.nan, -fp.inf)) + assert fp.isnan(r.real) and r.imag == +fp.inf + pi2 = fp.pi/2 + assert fp.acos(fp.mpc(+fp.inf, +1)) == fp.mpc(0.0, -fp.inf) + assert fp.acos(fp.mpc(+fp.inf, -1)) == fp.mpc(0.0, +fp.inf) + assert fp.acos(fp.mpc(+fp.inf, +0.0)) == fp.mpc(0.0, -fp.inf) + assert fp.acos(fp.mpc(+fp.inf, -0.0)) == fp.mpc(0.0, +fp.inf) + assert fp.acos(fp.mpc(-fp.inf, +1)) == fp.mpc(fp.pi, -fp.inf) + assert fp.acos(fp.mpc(-fp.inf, -1)) == fp.mpc(fp.pi, +fp.inf) + assert fp.acos(fp.mpc(-fp.inf, +0.0)) == fp.mpc(fp.pi, -fp.inf) + assert fp.acos(fp.mpc(-fp.inf, -0.0)) == fp.mpc(fp.pi, +fp.inf) + assert fp.acos(fp.mpc(+1, +fp.inf)) == fp.mpc(pi2, -fp.inf) + assert fp.acos(fp.mpc(-1, +fp.inf)) == fp.mpc(pi2, -fp.inf) + assert fp.acos(fp.mpc(+0.0, +fp.inf)) == fp.mpc(pi2, -fp.inf) + assert fp.acos(fp.mpc(-0.0, +fp.inf)) == fp.mpc(pi2, -fp.inf) + assert fp.acos(fp.mpc(+1, -fp.inf)) == fp.mpc(pi2, +fp.inf) + assert fp.acos(fp.mpc(-1, -fp.inf)) == fp.mpc(pi2, +fp.inf) + assert fp.acos(fp.mpc(+0.0, -fp.inf)) == fp.mpc(pi2, +fp.inf) + assert fp.acos(fp.mpc(-0.0, -fp.inf)) == fp.mpc(pi2, +fp.inf) + assert ae(fp.acos(fp.mpc(-2, +0.0)), fp.mpc(fp.pi, +fp.log(2 - fp.sqrt(3)))) + assert ae(fp.acos(fp.mpc(-2, -0.0)), fp.mpc(fp.pi, -fp.log(2 - fp.sqrt(3)))) + assert ae(fp.acos(fp.mpc(+2, +0.0)), fp.mpc(0, -fp.log(2 + fp.sqrt(3)))) + assert ae(fp.acos(fp.mpc(+2, -0.0)), fp.mpc(0, +fp.log(2 + fp.sqrt(3)))) + assert ae(fp.acos(fp.mpc(0.5, +0.0)), fp.pi/3) + assert ae(fp.acos(fp.mpc(0.5, -0.0)), fp.pi/3) + +def test_fp_expj(): + assert ae(fp.expj(0), (1.0 + 0.0j)) + assert ae(fp.expj(1), (0.5403023058681397174 + 0.84147098480789650665j)) + assert ae(fp.expj(2), (-0.416146836547142387 + 0.9092974268256816954j)) + assert ae(fp.expj(0.75), (0.73168886887382088631 + 0.68163876002333416673j)) + assert ae(fp.expj(2+3j), (-0.020718731002242879378 + 0.045271253156092975488j)) + assert ae(fp.expjpi(0), (1.0 + 0.0j)) + assert ae(fp.expjpi(1), (-1.0 + 0.0j)) + assert ae(fp.expjpi(2), (1.0 + 0.0j)) + assert ae(fp.expjpi(0.75), (-0.7071067811865475244 + 0.7071067811865475244j)) + assert ae(fp.expjpi(2+3j), (0.000080699517570304599239 + 0.0j)) + +@pytest.mark.parametrize('plus', [True, False]) +def test_fp_bernoulli(plus): + assert ae(fp.bernoulli(0, plus), 1.0) + assert ae(fp.bernoulli(1, plus), 0.5 if plus else -0.5) + assert ae(fp.bernoulli(2, plus), 0.16666666666666666667) + assert ae(fp.bernoulli(10, plus), 0.075757575757575757576) + assert ae(fp.bernoulli(11, plus), 0.0) + +def test_fp_gamma(): + assert ae(fp.gamma(1), 1.0) + assert ae(fp.gamma(1.5), 0.88622692545275801365) + assert ae(fp.gamma(10), 362880.0) + assert ae(fp.gamma(-0.5), -3.5449077018110320546) + assert ae(fp.gamma(-7.1), 0.0016478244570263333622) + assert ae(fp.gamma(12.3), 83385367.899970000963) + assert ae(fp.gamma(2+0j), (1.0 + 0.0j)) + assert fp.isnan(fp.gamma(-fp.inf)) + assert ae(fp.gamma(-2.5+0j), (-0.94530872048294188123 + 0.0j)) + assert ae(fp.gamma(3+4j), (0.0052255384713692141947 - 0.17254707929430018772j)) + assert ae(fp.gamma(-3-4j), (0.00001460997305874775607 - 0.000020760733311509070396j)) + assert ae(fp.fac(0), 1.0) + assert ae(fp.fac(1), 1.0) + assert ae(fp.fac(20), 2432902008176640000.0) + assert ae(fp.fac(-3.5), -0.94530872048294188123) + assert ae(fp.fac(2+3j), (-0.44011340763700171113 - 0.06363724312631702183j)) + assert ae(fp.loggamma(1.0), 0.0) + assert ae(fp.loggamma(2.0), 0.0) + assert ae(fp.loggamma(3.0), 0.69314718055994530942) + assert ae(fp.loggamma(7.25), 7.0521854507385394449) + assert ae(fp.loggamma(1000.0), 5905.2204232091812118) + assert ae(fp.loggamma(1e50), 1.1412925464970229298e+52) + assert ae(fp.loggamma(1e25+1e25j), (5.6125802751733671621e+26 + 5.7696599078528568383e+26j)) + assert ae(fp.loggamma(3+4j), (-1.7566267846037841105 + 4.7426644380346579282j)) + assert ae(fp.loggamma(-0.5), (1.2655121234846453965 - 3.1415926535897932385j)) + assert ae(fp.loggamma(-1.25), (1.3664317612369762346 - 6.2831853071795864769j)) + assert ae(fp.loggamma(-2.75), (0.0044878975359557733115 - 9.4247779607693797154j)) + assert ae(fp.loggamma(-3.5), (-1.3090066849930420464 - 12.566370614359172954j)) + assert ae(fp.loggamma(-4.5), (-2.8130840817693161197 - 15.707963267948966192j)) + assert ae(fp.loggamma(-2+3j), (-6.776523813485657093 - 4.568791367260286402j)) + assert ae(fp.loggamma(-1000.3), (-5912.8440347785205041 - 3144.7342462433830317j)) + assert ae(fp.loggamma(-100-100j), (-632.35117666833135562 - 158.37641469650352462j)) + assert ae(fp.loggamma(1e-10), 23.025850929882735237) + assert ae(fp.loggamma(-1e-10), (23.02585092999817837 - 3.1415926535897932385j)) + assert ae(fp.loggamma(1e-10j), (23.025850929940456804 - 1.5707963268526181857j)) + assert ae(fp.loggamma(1e-10j-1e-10), (22.679277339718205716 - 2.3561944902500664954j)) + +def test_fp_psi(): + assert ae(fp.psi(0, 3.7), 1.1671535393615114409) + assert ae(fp.psi(0, 0.5), -1.9635100260214234794) + assert ae(fp.psi(0, 1), -0.57721566490153286061) + assert ae(fp.psi(0, -2.5), 1.1031566406452431872) + assert ae(fp.psi(0, 12.9), 2.5179671503279156347) + assert ae(fp.psi(0, 100), 4.6001618527380874002) + assert ae(fp.psi(0, 2500.3), 7.8239660143238547877) + assert ae(fp.psi(0, 1e40), 92.103403719761827391) + assert ae(fp.psi(0, 1e200), 460.51701859880913677) + assert ae(fp.psi(0, 3.7+0j), (1.1671535393615114409 + 0.0j)) + assert ae(fp.psi(1, 3), 0.39493406684822643647) + assert ae(fp.psi(3, 2+3j), (-0.05383196209159972116 + 0.0076890935247364805218j)) + assert ae(fp.psi(4, -0.5+1j), (1.2719531355492328195 - 18.211833410936276774j)) + assert ae(fp.harmonic(0), 0.0) + assert ae(fp.harmonic(1), 1.0) + assert ae(fp.harmonic(2), 1.5) + assert ae(fp.harmonic(100), 5.1873775176396202608) + assert ae(fp.harmonic(-2.5), 1.2803723055467760478) + assert ae(fp.harmonic(2+3j), (1.9390425294578375875 + 0.87336044981834544043j)) + assert ae(fp.harmonic(-5-4j), (2.3725754822349437733 - 2.4160904444801621j)) + +def test_fp_zeta(): + assert ae(fp.zeta(1e100), 1.0) + assert ae(fp.zeta(3), 1.2020569031595942854) + assert ae(fp.zeta(2+0j), (1.6449340668482264365 + 0.0j)) + assert ae(fp.zeta(0.93), -13.713619351638164784) + assert ae(fp.zeta(1.74), 1.9796863545771774095) + assert ae(fp.zeta(0.0), -0.5) + assert ae(fp.zeta(-1.0), -0.083333333333333333333) + assert ae(fp.zeta(-2.0), 0.0) + assert ae(fp.zeta(-3.0), 0.0083333333333333333333) + assert ae(fp.zeta(-500.0), 0.0) + assert ae(fp.zeta(-7.4), 0.0036537321227995882447) + assert ae(fp.zeta(2.1), 1.5602165335033620158) + assert ae(fp.zeta(26.9), 1.0000000079854809935) + assert ae(fp.zeta(26), 1.0000000149015548284) + assert ae(fp.zeta(27), 1.0000000074507117898) + assert ae(fp.zeta(28), 1.0000000037253340248) + assert ae(fp.zeta(27.1), 1.000000006951755045) + assert ae(fp.zeta(32.7), 1.0000000001433243232) + assert ae(fp.zeta(100), 1.0) + assert ae(fp.altzeta(3.5), 0.92755357777394803511) + assert ae(fp.altzeta(1), 0.69314718055994530942) + assert ae(fp.altzeta(2), 0.82246703342411321824) + assert ae(fp.altzeta(0), 0.5) + assert ae(fp.zeta(-2+3j, 1), (0.13297115587929864827 + 0.12305330040458776494j)) + assert ae(fp.zeta(-2+3j, 5), (18.384866151867576927 - 11.377015110597711009j)) + assert ae(fp.zeta(1.0000000001), 9999999173.1735741337) + assert ae(fp.zeta(0.9999999999), -9999999172.0191428039) + assert ae(fp.zeta(1+0.000000001j), (0.57721566490153286061 - 999999999.99999993765j)) + assert ae(fp.primezeta(2.5+4j), (-0.16922458243438033385 - 0.010847965298387727811j)) + assert ae(fp.primezeta(4), 0.076993139764246844943) + assert ae(fp.riemannr(3.7), 2.3034079839110855717) + assert ae(fp.riemannr(8), 3.9011860449341499474) + assert ae(fp.riemannr(3+4j), (2.2369653314259991796 + 1.6339943856990281694j)) + +def test_fp_hyp2f1(): + assert ae(fp.hyp2f1(1, (3,2), 3.25, 5.0), (-0.46600275923108143059 - 0.74393667908854842325j)) + assert ae(fp.hyp2f1(1+1j, (3,2), 3.25, 5.0), (-5.9208875603806515987 - 2.3813557707889590686j)) + assert ae(fp.hyp2f1(1+1j, (3,2), 3.25, 2+3j), (0.17174552030925080445 + 0.19589781970539389999j)) + +def test_fp_erf(): + assert fp.erf(2) == fp.erf(2.0) == fp.erf(2.0+0.0j) + assert fp.erf(fp.inf) == 1.0 + assert fp.erf(fp.ninf) == -1.0 + assert ae(fp.erf(0), 0.0) + assert ae(fp.erf(-0), -0.0) + assert ae(fp.erf(0.3), 0.32862675945912741619) + assert ae(fp.erf(-0.3), -0.32862675945912741619) + assert ae(fp.erf(0.9), 0.79690821242283213966) + assert ae(fp.erf(-0.9), -0.79690821242283213966) + assert ae(fp.erf(1.0), 0.84270079294971486934) + assert ae(fp.erf(-1.0), -0.84270079294971486934) + assert ae(fp.erf(1.1), 0.88020506957408172966) + assert ae(fp.erf(-1.1), -0.88020506957408172966) + assert ae(fp.erf(8.5), 1.0) + assert ae(fp.erf(-8.5), -1.0) + assert ae(fp.erf(9.1), 1.0) + assert ae(fp.erf(-9.1), -1.0) + assert ae(fp.erf(20.0), 1.0) + assert ae(fp.erf(-20.0), -1.0) + assert ae(fp.erf(10000.0), 1.0) + assert ae(fp.erf(-10000.0), -1.0) + assert ae(fp.erf(1e+50), 1.0) + assert ae(fp.erf(-1e+50), -1.0) + assert ae(fp.erf(1j), 1.650425758797542876j) + assert ae(fp.erf(-1j), -1.650425758797542876j) + assert ae(fp.erf((2+3j)), (-20.829461427614568389 + 8.6873182714701631444j)) + assert ae(fp.erf(-(2+3j)), -(-20.829461427614568389 + 8.6873182714701631444j)) + assert ae(fp.erf((8+9j)), (-1072004.2525062051158 + 364149.91954310255423j)) + assert ae(fp.erf(-(8+9j)), -(-1072004.2525062051158 + 364149.91954310255423j)) + assert fp.erfc(fp.inf) == 0.0 + assert fp.erfc(fp.ninf) == 2.0 + assert fp.erfc(0) == 1 + assert fp.erfc(-0.0) == 1 + assert fp.erfc(0+0j) == 1 + assert ae(fp.erfc(0.3), 0.67137324054087258381) + assert ae(fp.erfc(-0.3), 1.3286267594591274162) + assert ae(fp.erfc(0.9), 0.20309178757716786034) + assert ae(fp.erfc(-0.9), 1.7969082124228321397) + assert ae(fp.erfc(1.0), 0.15729920705028513066) + assert ae(fp.erfc(-1.0), 1.8427007929497148693) + assert ae(fp.erfc(1.1), 0.11979493042591827034) + assert ae(fp.erfc(-1.1), 1.8802050695740817297) + assert ae(fp.erfc(8.5), 2.7623240713337714461e-33) + assert ae(fp.erfc(-8.5), 2.0) + assert ae(fp.erfc(9.1), 6.6969004279886077452e-38) + assert ae(fp.erfc(-9.1), 2.0) + assert ae(fp.erfc(20.0), 5.3958656116079009289e-176) + assert ae(fp.erfc(-20.0), 2.0) + assert ae(fp.erfc(10000.0), 0.0) + assert ae(fp.erfc(-10000.0), 2.0) + assert ae(fp.erfc(1e+50), 0.0) + assert ae(fp.erfc(-1e+50), 2.0) + assert ae(fp.erfc(1j), (1.0 - 1.650425758797542876j)) + assert ae(fp.erfc(-1j), (1.0 + 1.650425758797542876j)) + assert ae(fp.erfc((2+3j)), (21.829461427614568389 - 8.6873182714701631444j), 1e-13) + assert ae(fp.erfc(-(2+3j)), (-19.829461427614568389 + 8.6873182714701631444j), 1e-13) + assert ae(fp.erfc((8+9j)), (1072005.2525062051158 - 364149.91954310255423j)) + assert ae(fp.erfc(-(8+9j)), (-1072003.2525062051158 + 364149.91954310255423j)) + assert ae(fp.erfc(20+0j), (5.3958656116079009289e-176 + 0.0j)) + +def test_fp_lambertw(): + assert ae(fp.lambertw(0.0), 0.0) + assert ae(fp.lambertw(1.0), 0.567143290409783873) + assert ae(fp.lambertw(7.5), 1.5662309537823875394) + assert ae(fp.lambertw(-0.25), -0.35740295618138890307) + assert ae(fp.lambertw(-10.0), (1.3699809685212708156 + 2.140194527074713196j)) + assert ae(fp.lambertw(0+0j), (0.0 + 0.0j)) + assert ae(fp.lambertw(4+0j), (1.2021678731970429392 + 0.0j)) + assert ae(fp.lambertw(1000.5), 5.2500227450408980127) + assert ae(fp.lambertw(1e100), 224.84310644511850156) + assert ae(fp.lambertw(-1000.0), (5.1501630246362515223 + 2.6641981432905204596j)) + assert ae(fp.lambertw(1e-10), 9.9999999990000003645e-11) + assert ae(fp.lambertw(1e-10j), (1.0000000000000000728e-20 + 1.0000000000000000364e-10j)) + assert ae(fp.lambertw(3+4j), (1.2815618061237758782 + 0.53309522202097107131j)) + assert ae(fp.lambertw(-3-4j), (1.0750730665692549276 - 1.3251023817343588823j)) + assert ae(fp.lambertw(10000+1000j), (7.2361526563371602186 + 0.087567810943839352034j)) + assert ae(fp.lambertw(0.0, -1), -fp.inf) + assert ae(fp.lambertw(1.0, -1), (-1.5339133197935745079 - 4.3751851530618983855j)) + assert ae(fp.lambertw(7.5, -1), (0.44125668415098614999 - 4.8039842008452390179j)) + assert ae(fp.lambertw(-0.25, -1), -2.1532923641103496492) + assert ae(fp.lambertw(-10.0, -1), (1.3699809685212708156 - 2.140194527074713196j)) + assert ae(fp.lambertw(0+0j, -1), -fp.inf) + assert ae(fp.lambertw(4+0j, -1), (-0.15730793189620765317 - 4.6787800704666656212j)) + assert ae(fp.lambertw(1000.5, -1), (4.9153765415404024736 - 5.4465682700815159569j)) + assert ae(fp.lambertw(1e100, -1), (224.84272130101601052 - 6.2553713838167244141j)) + assert ae(fp.lambertw(-1000.0, -1), (5.1501630246362515223 - 2.6641981432905204596j)) + assert ae(fp.lambertw(1e-10, -1), (-26.303186778379041521 - 3.2650939117038283975j)) + assert ae(fp.lambertw(1e-10j, -1), (-26.297238779529035028 - 1.6328071613455765135j)) + assert ae(fp.lambertw(3+4j, -1), (0.25856740686699741676 - 3.8521166861614355895j)) + assert ae(fp.lambertw(-3-4j, -1), (-0.32028750204310768396 - 6.8801677192091972343j)) + assert ae(fp.lambertw(10000+1000j, -1), (7.0255308742285435567 - 5.5177506835734067601j)) + assert ae(fp.lambertw(0.0, 2), -fp.inf) + assert ae(fp.lambertw(1.0, 2), (-2.4015851048680028842 + 10.776299516115070898j)) + assert ae(fp.lambertw(7.5, 2), (-0.38003357962843791529 + 10.960916473368746184j)) + assert ae(fp.lambertw(-0.25, 2), (-4.0558735269061511898 + 13.852334658567271386j)) + assert ae(fp.lambertw(-10.0, 2), (-0.34479123764318858696 + 14.112740596763592363j)) + assert ae(fp.lambertw(0+0j, 2), -fp.inf) + assert ae(fp.lambertw(4+0j, 2), (-1.0070343323804262788 + 10.903476551861683082j)) + assert ae(fp.lambertw(1000.5, 2), (4.4076185165459395295 + 11.365524591091402177j)) + assert ae(fp.lambertw(1e100, 2), (224.84156762724875878 + 12.510785262632255672j)) + assert ae(fp.lambertw(-1000.0, 2), (4.1984245610246530756 + 14.420478573754313845j)) + assert ae(fp.lambertw(1e-10, 2), (-26.362258095445866488 + 9.7800247407031482519j)) + assert ae(fp.lambertw(1e-10j, 2), (-26.384250801683084252 + 11.403535950607739763j)) + assert ae(fp.lambertw(3+4j, 2), (-0.86554679943333993562 + 11.849956798331992027j)) + assert ae(fp.lambertw(-3-4j, 2), (-0.55792273874679112639 + 8.7173627024159324811j)) + assert ae(fp.lambertw(10000+1000j, 2), (6.6223802254585662734 + 11.61348646825020766j)) + +def test_fp_stress_ei_e1(): + # Can be tightened on recent Pythons with more accurate math/cmath + ATOL = 1e-13 + PTOL = 1e-12 + v = fp.e1(1.1641532182693481445e-10) + assert ae(v, 22.296641293693077672, tol=ATOL) + assert type(v) is float + v = fp.e1(0.25) + assert ae(v, 1.0442826344437381945, tol=ATOL) + assert type(v) is float + v = fp.e1(1.0) + assert ae(v, 0.21938393439552027368, tol=ATOL) + assert type(v) is float + v = fp.e1(2.0) + assert ae(v, 0.048900510708061119567, tol=ATOL) + assert type(v) is float + v = fp.e1(5.0) + assert ae(v, 0.0011482955912753257973, tol=ATOL) + assert type(v) is float + v = fp.e1(20.0) + assert ae(v, 9.8355252906498816904e-11, tol=ATOL) + assert type(v) is float + v = fp.e1(30.0) + assert ae(v, 3.0215520106888125448e-15, tol=ATOL) + assert type(v) is float + v = fp.e1(40.0) + assert ae(v, 1.0367732614516569722e-19, tol=ATOL) + assert type(v) is float + v = fp.e1(50.0) + assert ae(v, 3.7832640295504590187e-24, tol=ATOL) + assert type(v) is float + v = fp.e1(80.0) + assert ae(v, 2.2285432586884729112e-37, tol=ATOL) + assert type(v) is float + v = fp.e1((1.1641532182693481445e-10 + 0.0j)) + assert ae(v, (22.296641293693077672 + 0.0j), tol=ATOL) + assert ae(v.real, 22.296641293693077672, tol=PTOL) + assert v.imag == 0 + v = fp.e1((0.25 + 0.0j)) + assert ae(v, (1.0442826344437381945 + 0.0j), tol=ATOL) + assert ae(v.real, 1.0442826344437381945, tol=PTOL) + assert v.imag == 0 + v = fp.e1((1.0 + 0.0j)) + assert ae(v, (0.21938393439552027368 + 0.0j), tol=ATOL) + assert ae(v.real, 0.21938393439552027368, tol=PTOL) + assert v.imag == 0 + v = fp.e1((2.0 + 0.0j)) + assert ae(v, (0.048900510708061119567 + 0.0j), tol=ATOL) + assert ae(v.real, 0.048900510708061119567, tol=PTOL) + assert v.imag == 0 + v = fp.e1((5.0 + 0.0j)) + assert ae(v, (0.0011482955912753257973 + 0.0j), tol=ATOL) + assert ae(v.real, 0.0011482955912753257973, tol=PTOL) + assert v.imag == 0 + v = fp.e1((20.0 + 0.0j)) + assert ae(v, (9.8355252906498816904e-11 + 0.0j), tol=ATOL) + assert ae(v.real, 9.8355252906498816904e-11, tol=PTOL) + assert v.imag == 0 + v = fp.e1((30.0 + 0.0j)) + assert ae(v, (3.0215520106888125448e-15 + 0.0j), tol=ATOL) + assert ae(v.real, 3.0215520106888125448e-15, tol=PTOL) + assert v.imag == 0 + v = fp.e1((40.0 + 0.0j)) + assert ae(v, (1.0367732614516569722e-19 + 0.0j), tol=ATOL) + assert ae(v.real, 1.0367732614516569722e-19, tol=PTOL) + assert v.imag == 0 + v = fp.e1((50.0 + 0.0j)) + assert ae(v, (3.7832640295504590187e-24 + 0.0j), tol=ATOL) + assert ae(v.real, 3.7832640295504590187e-24, tol=PTOL) + assert v.imag == 0 + v = fp.e1((80.0 + 0.0j)) + assert ae(v, (2.2285432586884729112e-37 + 0.0j), tol=ATOL) + assert ae(v.real, 2.2285432586884729112e-37, tol=PTOL) + assert v.imag == 0 + v = fp.e1((4.6566128730773925781e-10 + 1.1641532182693481445e-10j)) + assert ae(v, (20.880034622014215597 - 0.24497866301044883237j), tol=ATOL) + assert ae(v.real, 20.880034622014215597, tol=PTOL) + assert ae(v.imag, -0.24497866301044883237, tol=PTOL) + v = fp.e1((1.0 + 0.25j)) + assert ae(v, (0.19731063945004229095 - 0.087366045774299963672j), tol=ATOL) + assert ae(v.real, 0.19731063945004229095, tol=PTOL) + assert ae(v.imag, -0.087366045774299963672, tol=PTOL) + v = fp.e1((4.0 + 1.0j)) + assert ae(v, (0.0013106173980145506944 - 0.0034542480199350626699j), tol=ATOL) + assert ae(v.real, 0.0013106173980145506944, tol=PTOL) + assert ae(v.imag, -0.0034542480199350626699, tol=PTOL) + v = fp.e1((8.0 + 2.0j)) + assert ae(v, (-0.000022278049065270225945 - 0.000029191940456521555288j), tol=ATOL) + assert ae(v.real, -0.000022278049065270225945, tol=PTOL) + assert ae(v.imag, -0.000029191940456521555288, tol=PTOL) + v = fp.e1((20.0 + 5.0j)) + assert ae(v, (4.7711374515765346894e-11 + 8.2902652405126947359e-11j), tol=ATOL) + assert ae(v.real, 4.7711374515765346894e-11, tol=PTOL) + assert ae(v.imag, 8.2902652405126947359e-11, tol=PTOL) + v = fp.e1((80.0 + 20.0j)) + assert ae(v, (3.8353473865788235787e-38 - 2.129247592349605139e-37j), tol=ATOL) + assert ae(v.real, 3.8353473865788235787e-38, tol=PTOL) + assert ae(v.imag, -2.129247592349605139e-37, tol=PTOL) + v = fp.e1((120.0 + 30.0j)) + assert ae(v, (2.3836002337480334716e-55 + 5.6704043587126198306e-55j), tol=ATOL) + assert ae(v.real, 2.3836002337480334716e-55, tol=PTOL) + assert ae(v.imag, 5.6704043587126198306e-55, tol=PTOL) + v = fp.e1((160.0 + 40.0j)) + assert ae(v, (-1.6238022898654510661e-72 - 1.104172355572287367e-72j), tol=ATOL) + assert ae(v.real, -1.6238022898654510661e-72, tol=PTOL) + assert ae(v.imag, -1.104172355572287367e-72, tol=PTOL) + v = fp.e1((200.0 + 50.0j)) + assert ae(v, (6.6800061461666228487e-90 + 1.4473816083541016115e-91j), tol=ATOL) + assert ae(v.real, 6.6800061461666228487e-90, tol=PTOL) + assert ae(v.imag, 1.4473816083541016115e-91, tol=PTOL) + v = fp.e1((320.0 + 80.0j)) + assert ae(v, (4.2737871527778786157e-143 + 3.1789935525785660314e-142j), tol=ATOL) + assert ae(v.real, 4.2737871527778786157e-143, tol=PTOL) + assert ae(v.imag, 3.1789935525785660314e-142, tol=PTOL) + v = fp.e1((1.1641532182693481445e-10 + 1.1641532182693481445e-10j)) + assert ae(v, (21.950067703413105017 - 0.7853981632810329878j), tol=ATOL) + assert ae(v.real, 21.950067703413105017, tol=PTOL) + assert ae(v.imag, -0.7853981632810329878, tol=PTOL) + v = fp.e1((0.25 + 0.25j)) + assert ae(v, (0.71092525792923287894 - 0.56491812441304194711j), tol=ATOL) + assert ae(v.real, 0.71092525792923287894, tol=PTOL) + assert ae(v.imag, -0.56491812441304194711, tol=PTOL) + v = fp.e1((1.0 + 1.0j)) + assert ae(v, (0.00028162445198141832551 - 0.17932453503935894015j), tol=ATOL) + assert ae(v.real, 0.00028162445198141832551, tol=PTOL) + assert ae(v.imag, -0.17932453503935894015, tol=PTOL) + v = fp.e1((2.0 + 2.0j)) + assert ae(v, (-0.033767089606562004246 - 0.018599414169750541925j), tol=ATOL) + assert ae(v.real, -0.033767089606562004246, tol=PTOL) + assert ae(v.imag, -0.018599414169750541925, tol=PTOL) + v = fp.e1((5.0 + 5.0j)) + assert ae(v, (0.0007266506660356393891 + 0.00047102780163522245054j), tol=ATOL) + assert ae(v.real, 0.0007266506660356393891, tol=PTOL) + assert ae(v.imag, 0.00047102780163522245054, tol=PTOL) + v = fp.e1((20.0 + 20.0j)) + assert ae(v, (-2.3824537449367396579e-11 - 6.6969873156525615158e-11j), tol=ATOL) + assert ae(v.real, -2.3824537449367396579e-11, tol=PTOL) + assert ae(v.imag, -6.6969873156525615158e-11, tol=PTOL) + v = fp.e1((30.0 + 30.0j)) + assert ae(v, (1.7316045841744061617e-15 + 1.3065678019487308689e-15j), tol=ATOL) + assert ae(v.real, 1.7316045841744061617e-15, tol=PTOL) + assert ae(v.imag, 1.3065678019487308689e-15, tol=PTOL) + v = fp.e1((40.0 + 40.0j)) + assert ae(v, (-7.4001043002899232182e-20 - 4.991847855336816304e-21j), tol=ATOL) + assert ae(v.real, -7.4001043002899232182e-20, tol=PTOL) + assert ae(v.imag, -4.991847855336816304e-21, tol=PTOL) + v = fp.e1((50.0 + 50.0j)) + assert ae(v, (2.3566128324644641219e-24 - 1.3188326726201614778e-24j), tol=ATOL) + assert ae(v.real, 2.3566128324644641219e-24, tol=PTOL) + assert ae(v.imag, -1.3188326726201614778e-24, tol=PTOL) + v = fp.e1((80.0 + 80.0j)) + assert ae(v, (9.8279750572186526673e-38 + 1.243952841288868831e-37j), tol=ATOL) + assert ae(v.real, 9.8279750572186526673e-38, tol=PTOL) + assert ae(v.imag, 1.243952841288868831e-37, tol=PTOL) + v = fp.e1((1.1641532182693481445e-10 + 4.6566128730773925781e-10j)) + assert ae(v, (20.880034621664969632 - 1.3258176632023711778j), tol=ATOL) + assert ae(v.real, 20.880034621664969632, tol=PTOL) + assert ae(v.imag, -1.3258176632023711778, tol=PTOL) + v = fp.e1((0.25 + 1.0j)) + assert ae(v, (-0.16868306393667788761 - 0.4858011885947426971j), tol=ATOL) + assert ae(v.real, -0.16868306393667788761, tol=PTOL) + assert ae(v.imag, -0.4858011885947426971, tol=PTOL) + v = fp.e1((1.0 + 4.0j)) + assert ae(v, (0.03373591813926547318 + 0.073523452241083821877j), tol=ATOL) + assert ae(v.real, 0.03373591813926547318, tol=PTOL) + assert ae(v.imag, 0.073523452241083821877, tol=PTOL) + v = fp.e1((2.0 + 8.0j)) + assert ae(v, (-0.015392833434733785143 - 0.0031747121557605415914j), tol=ATOL) + assert ae(v.real, -0.015392833434733785143, tol=PTOL) + assert ae(v.imag, -0.0031747121557605415914, tol=PTOL) + v = fp.e1((5.0 + 20.0j)) + assert ae(v, (-0.00024419662286542966525 - 0.00021008322966152755674j), tol=ATOL) + assert ae(v.real, -0.00024419662286542966525, tol=PTOL) + assert ae(v.imag, -0.00021008322966152755674, tol=PTOL) + v = fp.e1((20.0 + 80.0j)) + assert ae(v, (2.3255552781051330088e-11 + 8.9463918891349438007e-12j), tol=ATOL) + assert ae(v.real, 2.3255552781051330088e-11, tol=PTOL) + assert ae(v.imag, 8.9463918891349438007e-12, tol=PTOL) + v = fp.e1((30.0 + 120.0j)) + assert ae(v, (-2.7068919097124652332e-16 - 7.0477762411705130239e-16j), tol=ATOL) + assert ae(v.real, -2.7068919097124652332e-16, tol=PTOL) + assert ae(v.imag, -7.0477762411705130239e-16, tol=PTOL) + v = fp.e1((40.0 + 160.0j)) + assert ae(v, (-1.1695597827678024687e-20 + 2.2907401455645736661e-20j), tol=ATOL) + assert ae(v.real, -1.1695597827678024687e-20, tol=PTOL) + assert ae(v.imag, 2.2907401455645736661e-20, tol=PTOL) + v = fp.e1((50.0 + 200.0j)) + assert ae(v, (9.0323746914410162531e-25 - 2.3950601790033530935e-25j), tol=ATOL) + assert ae(v.real, 9.0323746914410162531e-25, tol=PTOL) + assert ae(v.imag, -2.3950601790033530935e-25, tol=PTOL) + v = fp.e1((80.0 + 320.0j)) + assert ae(v, (3.4819106748728063576e-38 - 4.215653005615772724e-38j), tol=ATOL) + assert ae(v.real, 3.4819106748728063576e-38, tol=PTOL) + assert ae(v.imag, -4.215653005615772724e-38, tol=PTOL) + v = fp.e1((0.0 + 1.1641532182693481445e-10j)) + assert ae(v, (22.29664129357666235 - 1.5707963266784812974j), tol=ATOL) + assert ae(v.real, 22.29664129357666235, tol=PTOL) + assert ae(v.imag, -1.5707963266784812974, tol=PTOL) + v = fp.e1((0.0 + 0.25j)) + assert ae(v, (0.82466306258094565309 - 1.3216627564751394551j), tol=ATOL) + assert ae(v.real, 0.82466306258094565309, tol=PTOL) + assert ae(v.imag, -1.3216627564751394551, tol=PTOL) + v = fp.e1((0.0 + 1.0j)) + assert ae(v, (-0.33740392290096813466 - 0.62471325642771360429j), tol=ATOL) + assert ae(v.real, -0.33740392290096813466, tol=PTOL) + assert ae(v.imag, -0.62471325642771360429, tol=PTOL) + v = fp.e1((0.0 + 2.0j)) + assert ae(v, (-0.4229808287748649957 + 0.034616650007798229345j), tol=ATOL) + assert ae(v.real, -0.4229808287748649957, tol=PTOL) + assert ae(v.imag, 0.034616650007798229345, tol=PTOL) + v = fp.e1((0.0 + 5.0j)) + assert ae(v, (0.19002974965664387862 - 0.020865081850222481957j), tol=ATOL) + assert ae(v.real, 0.19002974965664387862, tol=PTOL) + assert ae(v.imag, -0.020865081850222481957, tol=PTOL) + v = fp.e1((0.0 + 20.0j)) + assert ae(v, (-0.04441982084535331654 - 0.022554625751456779068j), tol=ATOL) + assert ae(v.real, -0.04441982084535331654, tol=PTOL) + assert ae(v.imag, -0.022554625751456779068, tol=PTOL) + v = fp.e1((0.0 + 30.0j)) + assert ae(v, (0.033032417282071143779 - 0.0040397867645455082476j), tol=ATOL) + assert ae(v.real, 0.033032417282071143779, tol=PTOL) + assert ae(v.imag, -0.0040397867645455082476, tol=PTOL) + v = fp.e1((0.0 + 40.0j)) + assert ae(v, (-0.019020007896208766962 + 0.016188792559887887544j), tol=ATOL) + assert ae(v.real, -0.019020007896208766962, tol=PTOL) + assert ae(v.imag, 0.016188792559887887544, tol=PTOL) + v = fp.e1((0.0 + 50.0j)) + assert ae(v, (0.0056283863241163054402 - 0.019179254308960724503j), tol=ATOL) + assert ae(v.real, 0.0056283863241163054402, tol=PTOL) + assert ae(v.imag, -0.019179254308960724503, tol=PTOL) + v = fp.e1((0.0 + 80.0j)) + assert ae(v, (0.012402501155070958192 + 0.0015345601175906961199j), tol=ATOL) + assert ae(v.real, 0.012402501155070958192, tol=PTOL) + assert ae(v.imag, 0.0015345601175906961199, tol=PTOL) + v = fp.e1((-1.1641532182693481445e-10 + 4.6566128730773925781e-10j)) + assert ae(v, (20.880034621432138988 - 1.8157749894560994861j), tol=ATOL) + assert ae(v.real, 20.880034621432138988, tol=PTOL) + assert ae(v.imag, -1.8157749894560994861, tol=PTOL) + v = fp.e1((-0.25 + 1.0j)) + assert ae(v, (-0.59066621214766308594 - 0.74474454765205036972j), tol=ATOL) + assert ae(v.real, -0.59066621214766308594, tol=PTOL) + assert ae(v.imag, -0.74474454765205036972, tol=PTOL) + v = fp.e1((-1.0 + 4.0j)) + assert ae(v, (0.49739047283060471093 + 0.41543605404038863174j), tol=ATOL) + assert ae(v.real, 0.49739047283060471093, tol=PTOL) + assert ae(v.imag, 0.41543605404038863174, tol=PTOL) + v = fp.e1((-2.0 + 8.0j)) + assert ae(v, (-0.8705211147733730969 + 0.24099328498605539667j), tol=ATOL) + assert ae(v.real, -0.8705211147733730969, tol=PTOL) + assert ae(v.imag, 0.24099328498605539667, tol=PTOL) + v = fp.e1((-5.0 + 20.0j)) + assert ae(v, (-7.0789514293925893007 - 1.6102177171960790536j), tol=ATOL) + assert ae(v.real, -7.0789514293925893007, tol=PTOL) + assert ae(v.imag, -1.6102177171960790536, tol=PTOL) + v = fp.e1((-20.0 + 80.0j)) + assert ae(v, (5855431.4907298084434 - 720920.93315409165707j), tol=ATOL) + assert ae(v.real, 5855431.4907298084434, tol=PTOL) + assert ae(v.imag, -720920.93315409165707, tol=PTOL) + v = fp.e1((-30.0 + 120.0j)) + assert ae(v, (-65402491644.703470747 - 56697658399.657460294j), tol=ATOL) + assert ae(v.real, -65402491644.703470747, tol=PTOL) + assert ae(v.imag, -56697658399.657460294, tol=PTOL) + v = fp.e1((-40.0 + 160.0j)) + assert ae(v, (25504929379604.776769 + 1429035198630573.2463j), tol=ATOL) + assert ae(v.real, 25504929379604.776769, tol=PTOL) + assert ae(v.imag, 1429035198630573.2463, tol=PTOL) + v = fp.e1((-50.0 + 200.0j)) + assert ae(v, (18437746526988116954.0 - 17146362239046152345.0j), tol=ATOL) + assert ae(v.real, 18437746526988116954.0, tol=PTOL) + assert ae(v.imag, -17146362239046152345.0, tol=PTOL) + v = fp.e1((-80.0 + 320.0j)) + assert ae(v, (3.3464697299634526706e+31 - 1.6473152633843023919e+32j), tol=ATOL) + assert ae(v.real, 3.3464697299634526706e+31, tol=PTOL) + assert ae(v.imag, -1.6473152633843023919e+32, tol=PTOL) + v = fp.e1((-4.6566128730773925781e-10 + 1.1641532182693481445e-10j)) + assert ae(v, (20.880034621082893023 - 2.8966139903465137624j), tol=ATOL) + assert ae(v.real, 20.880034621082893023, tol=PTOL) + assert ae(v.imag, -2.8966139903465137624, tol=PTOL) + v = fp.e1((-1.0 + 0.25j)) + assert ae(v, (-1.8942716983721074932 - 2.4689102827070540799j), tol=ATOL) + assert ae(v.real, -1.8942716983721074932, tol=PTOL) + assert ae(v.imag, -2.4689102827070540799, tol=PTOL) + v = fp.e1((-4.0 + 1.0j)) + assert ae(v, (-14.806699492675420438 + 9.1384225230837893776j), tol=ATOL) + assert ae(v.real, -14.806699492675420438, tol=PTOL) + assert ae(v.imag, 9.1384225230837893776, tol=PTOL) + v = fp.e1((-8.0 + 2.0j)) + assert ae(v, (54.633252667426386294 + 413.20318163814670688j), tol=ATOL) + assert ae(v.real, 54.633252667426386294, tol=PTOL) + assert ae(v.imag, 413.20318163814670688, tol=PTOL) + v = fp.e1((-20.0 + 5.0j)) + assert ae(v, (-711836.97165402624643 - 24745250.939695900956j), tol=ATOL) + assert ae(v.real, -711836.97165402624643, tol=PTOL) + assert ae(v.imag, -24745250.939695900956, tol=PTOL) + v = fp.e1((-80.0 + 20.0j)) + assert ae(v, (-4.2139911108612653091e+32 + 5.3367124741918251637e+32j), tol=ATOL) + assert ae(v.real, -4.2139911108612653091e+32, tol=PTOL) + assert ae(v.imag, 5.3367124741918251637e+32, tol=PTOL) + v = fp.e1((-120.0 + 30.0j)) + assert ae(v, (9.7760616203707508892e+48 - 1.058257682317195792e+50j), tol=ATOL) + assert ae(v.real, 9.7760616203707508892e+48, tol=PTOL) + assert ae(v.imag, -1.058257682317195792e+50, tol=PTOL) + v = fp.e1((-160.0 + 40.0j)) + assert ae(v, (8.7065541466623638861e+66 + 1.6577106725141739889e+67j), tol=ATOL) + assert ae(v.real, 8.7065541466623638861e+66, tol=PTOL) + assert ae(v.imag, 1.6577106725141739889e+67, tol=PTOL) + v = fp.e1((-200.0 + 50.0j)) + assert ae(v, (-3.070744996327018106e+84 - 1.7243244846769415903e+84j), tol=ATOL) + assert ae(v.real, -3.070744996327018106e+84, tol=PTOL) + assert ae(v.imag, -1.7243244846769415903e+84, tol=PTOL) + v = fp.e1((-320.0 + 80.0j)) + assert ae(v, (9.9960598637998647276e+135 - 2.6855081527595608863e+136j), tol=ATOL) + assert ae(v.real, 9.9960598637998647276e+135, tol=PTOL) + assert ae(v.imag, -2.6855081527595608863e+136, tol=PTOL) + v = fp.e1(-1.1641532182693481445e-10) + assert ae(v, (22.296641293460247028 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, 22.296641293460247028, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1(-0.25) + assert ae(v, (0.54254326466191372953 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, 0.54254326466191372953, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1(-1.0) + assert ae(v, (-1.8951178163559367555 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -1.8951178163559367555, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1(-2.0) + assert ae(v, (-4.9542343560018901634 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -4.9542343560018901634, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1(-5.0) + assert ae(v, (-40.185275355803177455 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -40.185275355803177455, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1(-20.0) + assert ae(v, (-25615652.66405658882 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -25615652.66405658882, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1(-30.0) + assert ae(v, (-368973209407.27419706 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -368973209407.27419706, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1(-40.0) + assert ae(v, (-6039718263611241.5784 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -6039718263611241.5784, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1(-50.0) + assert ae(v, (-1.0585636897131690963e+20 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -1.0585636897131690963e+20, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1(-80.0) + assert ae(v, (-7.0146000049047999696e+32 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -7.0146000049047999696e+32, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-1.1641532182693481445e-10 + 0.0j)) + assert ae(v, (22.296641293460247028 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, 22.296641293460247028, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-0.25 + 0.0j)) + assert ae(v, (0.54254326466191372953 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, 0.54254326466191372953, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-1.0 + 0.0j)) + assert ae(v, (-1.8951178163559367555 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -1.8951178163559367555, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-2.0 + 0.0j)) + assert ae(v, (-4.9542343560018901634 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -4.9542343560018901634, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-5.0 + 0.0j)) + assert ae(v, (-40.185275355803177455 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -40.185275355803177455, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-20.0 + 0.0j)) + assert ae(v, (-25615652.66405658882 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -25615652.66405658882, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-30.0 + 0.0j)) + assert ae(v, (-368973209407.27419706 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -368973209407.27419706, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-40.0 + 0.0j)) + assert ae(v, (-6039718263611241.5784 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -6039718263611241.5784, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-50.0 + 0.0j)) + assert ae(v, (-1.0585636897131690963e+20 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -1.0585636897131690963e+20, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-80.0 + 0.0j)) + assert ae(v, (-7.0146000049047999696e+32 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -7.0146000049047999696e+32, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-4.6566128730773925781e-10 - 1.1641532182693481445e-10j)) + assert ae(v, (20.880034621082893023 + 2.8966139903465137624j), tol=ATOL) + assert ae(v.real, 20.880034621082893023, tol=PTOL) + assert ae(v.imag, 2.8966139903465137624, tol=PTOL) + v = fp.e1((-1.0 - 0.25j)) + assert ae(v, (-1.8942716983721074932 + 2.4689102827070540799j), tol=ATOL) + assert ae(v.real, -1.8942716983721074932, tol=PTOL) + assert ae(v.imag, 2.4689102827070540799, tol=PTOL) + v = fp.e1((-4.0 - 1.0j)) + assert ae(v, (-14.806699492675420438 - 9.1384225230837893776j), tol=ATOL) + assert ae(v.real, -14.806699492675420438, tol=PTOL) + assert ae(v.imag, -9.1384225230837893776, tol=PTOL) + v = fp.e1((-8.0 - 2.0j)) + assert ae(v, (54.633252667426386294 - 413.20318163814670688j), tol=ATOL) + assert ae(v.real, 54.633252667426386294, tol=PTOL) + assert ae(v.imag, -413.20318163814670688, tol=PTOL) + v = fp.e1((-20.0 - 5.0j)) + assert ae(v, (-711836.97165402624643 + 24745250.939695900956j), tol=ATOL) + assert ae(v.real, -711836.97165402624643, tol=PTOL) + assert ae(v.imag, 24745250.939695900956, tol=PTOL) + v = fp.e1((-80.0 - 20.0j)) + assert ae(v, (-4.2139911108612653091e+32 - 5.3367124741918251637e+32j), tol=ATOL) + assert ae(v.real, -4.2139911108612653091e+32, tol=PTOL) + assert ae(v.imag, -5.3367124741918251637e+32, tol=PTOL) + v = fp.e1((-120.0 - 30.0j)) + assert ae(v, (9.7760616203707508892e+48 + 1.058257682317195792e+50j), tol=ATOL) + assert ae(v.real, 9.7760616203707508892e+48, tol=PTOL) + assert ae(v.imag, 1.058257682317195792e+50, tol=PTOL) + v = fp.e1((-160.0 - 40.0j)) + assert ae(v, (8.7065541466623638861e+66 - 1.6577106725141739889e+67j), tol=ATOL) + assert ae(v.real, 8.7065541466623638861e+66, tol=PTOL) + assert ae(v.imag, -1.6577106725141739889e+67, tol=PTOL) + v = fp.e1((-200.0 - 50.0j)) + assert ae(v, (-3.070744996327018106e+84 + 1.7243244846769415903e+84j), tol=ATOL) + assert ae(v.real, -3.070744996327018106e+84, tol=PTOL) + assert ae(v.imag, 1.7243244846769415903e+84, tol=PTOL) + v = fp.e1((-320.0 - 80.0j)) + assert ae(v, (9.9960598637998647276e+135 + 2.6855081527595608863e+136j), tol=ATOL) + assert ae(v.real, 9.9960598637998647276e+135, tol=PTOL) + assert ae(v.imag, 2.6855081527595608863e+136, tol=PTOL) + v = fp.e1((-1.1641532182693481445e-10 - 1.1641532182693481445e-10j)) + assert ae(v, (21.950067703180274374 + 2.356194490075929607j), tol=ATOL) + assert ae(v.real, 21.950067703180274374, tol=PTOL) + assert ae(v.imag, 2.356194490075929607, tol=PTOL) + v = fp.e1((-0.25 - 0.25j)) + assert ae(v, (0.21441047326710323254 + 2.0732153554307936389j), tol=ATOL) + assert ae(v.real, 0.21441047326710323254, tol=PTOL) + assert ae(v.imag, 2.0732153554307936389, tol=PTOL) + v = fp.e1((-1.0 - 1.0j)) + assert ae(v, (-1.7646259855638540684 + 0.7538228020792708192j), tol=ATOL) + assert ae(v.real, -1.7646259855638540684, tol=PTOL) + assert ae(v.imag, 0.7538228020792708192, tol=PTOL) + v = fp.e1((-2.0 - 2.0j)) + assert ae(v, (-1.8920781621855474089 - 2.1753697842428647236j), tol=ATOL) + assert ae(v.real, -1.8920781621855474089, tol=PTOL) + assert ae(v.imag, -2.1753697842428647236, tol=PTOL) + v = fp.e1((-5.0 - 5.0j)) + assert ae(v, (13.470936071475245856 + 18.464085049321024206j), tol=ATOL) + assert ae(v.real, 13.470936071475245856, tol=PTOL) + assert ae(v.imag, 18.464085049321024206, tol=PTOL) + v = fp.e1((-20.0 - 20.0j)) + assert ae(v, (-16589317.398788971896 - 5831702.3296441771206j), tol=ATOL) + assert ae(v.real, -16589317.398788971896, tol=PTOL) + assert ae(v.imag, -5831702.3296441771206, tol=PTOL) + v = fp.e1((-30.0 - 30.0j)) + assert ae(v, (154596484273.69322527 + 204179357837.41389696j), tol=ATOL) + assert ae(v.real, 154596484273.69322527, tol=PTOL) + assert ae(v.imag, 204179357837.41389696, tol=PTOL) + v = fp.e1((-40.0 - 40.0j)) + assert ae(v, (-287512180321448.45408 - 4203502407932314.974j), tol=ATOL) + assert ae(v.real, -287512180321448.45408, tol=PTOL) + assert ae(v.imag, -4203502407932314.974, tol=PTOL) + v = fp.e1((-50.0 - 50.0j)) + assert ae(v, (-36128528616649268826.0 + 64648801861338741963.0j), tol=ATOL) + assert ae(v.real, -36128528616649268826.0, tol=PTOL) + assert ae(v.imag, 64648801861338741963.0, tol=PTOL) + v = fp.e1((-80.0 - 80.0j)) + assert ae(v, (3.8674816337930010217e+32 + 3.0540709639658071041e+32j), tol=ATOL) + assert ae(v.real, 3.8674816337930010217e+32, tol=PTOL) + assert ae(v.imag, 3.0540709639658071041e+32, tol=PTOL) + v = fp.e1((-1.1641532182693481445e-10 - 4.6566128730773925781e-10j)) + assert ae(v, (20.880034621432138988 + 1.8157749894560994861j), tol=ATOL) + assert ae(v.real, 20.880034621432138988, tol=PTOL) + assert ae(v.imag, 1.8157749894560994861, tol=PTOL) + v = fp.e1((-0.25 - 1.0j)) + assert ae(v, (-0.59066621214766308594 + 0.74474454765205036972j), tol=ATOL) + assert ae(v.real, -0.59066621214766308594, tol=PTOL) + assert ae(v.imag, 0.74474454765205036972, tol=PTOL) + v = fp.e1((-1.0 - 4.0j)) + assert ae(v, (0.49739047283060471093 - 0.41543605404038863174j), tol=ATOL) + assert ae(v.real, 0.49739047283060471093, tol=PTOL) + assert ae(v.imag, -0.41543605404038863174, tol=PTOL) + v = fp.e1((-2.0 - 8.0j)) + assert ae(v, (-0.8705211147733730969 - 0.24099328498605539667j), tol=ATOL) + assert ae(v.real, -0.8705211147733730969, tol=PTOL) + assert ae(v.imag, -0.24099328498605539667, tol=PTOL) + v = fp.e1((-5.0 - 20.0j)) + assert ae(v, (-7.0789514293925893007 + 1.6102177171960790536j), tol=ATOL) + assert ae(v.real, -7.0789514293925893007, tol=PTOL) + assert ae(v.imag, 1.6102177171960790536, tol=PTOL) + v = fp.e1((-20.0 - 80.0j)) + assert ae(v, (5855431.4907298084434 + 720920.93315409165707j), tol=ATOL) + assert ae(v.real, 5855431.4907298084434, tol=PTOL) + assert ae(v.imag, 720920.93315409165707, tol=PTOL) + v = fp.e1((-30.0 - 120.0j)) + assert ae(v, (-65402491644.703470747 + 56697658399.657460294j), tol=ATOL) + assert ae(v.real, -65402491644.703470747, tol=PTOL) + assert ae(v.imag, 56697658399.657460294, tol=PTOL) + v = fp.e1((-40.0 - 160.0j)) + assert ae(v, (25504929379604.776769 - 1429035198630573.2463j), tol=ATOL) + assert ae(v.real, 25504929379604.776769, tol=PTOL) + assert ae(v.imag, -1429035198630573.2463, tol=PTOL) + v = fp.e1((-50.0 - 200.0j)) + assert ae(v, (18437746526988116954.0 + 17146362239046152345.0j), tol=ATOL) + assert ae(v.real, 18437746526988116954.0, tol=PTOL) + assert ae(v.imag, 17146362239046152345.0, tol=PTOL) + v = fp.e1((-80.0 - 320.0j)) + assert ae(v, (3.3464697299634526706e+31 + 1.6473152633843023919e+32j), tol=ATOL) + assert ae(v.real, 3.3464697299634526706e+31, tol=PTOL) + assert ae(v.imag, 1.6473152633843023919e+32, tol=PTOL) + v = fp.e1((0.0 - 1.1641532182693481445e-10j)) + assert ae(v, (22.29664129357666235 + 1.5707963266784812974j), tol=ATOL) + assert ae(v.real, 22.29664129357666235, tol=PTOL) + assert ae(v.imag, 1.5707963266784812974, tol=PTOL) + v = fp.e1((0.0 - 0.25j)) + assert ae(v, (0.82466306258094565309 + 1.3216627564751394551j), tol=ATOL) + assert ae(v.real, 0.82466306258094565309, tol=PTOL) + assert ae(v.imag, 1.3216627564751394551, tol=PTOL) + v = fp.e1((0.0 - 1.0j)) + assert ae(v, (-0.33740392290096813466 + 0.62471325642771360429j), tol=ATOL) + assert ae(v.real, -0.33740392290096813466, tol=PTOL) + assert ae(v.imag, 0.62471325642771360429, tol=PTOL) + v = fp.e1((0.0 - 2.0j)) + assert ae(v, (-0.4229808287748649957 - 0.034616650007798229345j), tol=ATOL) + assert ae(v.real, -0.4229808287748649957, tol=PTOL) + assert ae(v.imag, -0.034616650007798229345, tol=PTOL) + v = fp.e1((0.0 - 5.0j)) + assert ae(v, (0.19002974965664387862 + 0.020865081850222481957j), tol=ATOL) + assert ae(v.real, 0.19002974965664387862, tol=PTOL) + assert ae(v.imag, 0.020865081850222481957, tol=PTOL) + v = fp.e1((0.0 - 20.0j)) + assert ae(v, (-0.04441982084535331654 + 0.022554625751456779068j), tol=ATOL) + assert ae(v.real, -0.04441982084535331654, tol=PTOL) + assert ae(v.imag, 0.022554625751456779068, tol=PTOL) + v = fp.e1((0.0 - 30.0j)) + assert ae(v, (0.033032417282071143779 + 0.0040397867645455082476j), tol=ATOL) + assert ae(v.real, 0.033032417282071143779, tol=PTOL) + assert ae(v.imag, 0.0040397867645455082476, tol=PTOL) + v = fp.e1((0.0 - 40.0j)) + assert ae(v, (-0.019020007896208766962 - 0.016188792559887887544j), tol=ATOL) + assert ae(v.real, -0.019020007896208766962, tol=PTOL) + assert ae(v.imag, -0.016188792559887887544, tol=PTOL) + v = fp.e1((0.0 - 50.0j)) + assert ae(v, (0.0056283863241163054402 + 0.019179254308960724503j), tol=ATOL) + assert ae(v.real, 0.0056283863241163054402, tol=PTOL) + assert ae(v.imag, 0.019179254308960724503, tol=PTOL) + v = fp.e1((0.0 - 80.0j)) + assert ae(v, (0.012402501155070958192 - 0.0015345601175906961199j), tol=ATOL) + assert ae(v.real, 0.012402501155070958192, tol=PTOL) + assert ae(v.imag, -0.0015345601175906961199, tol=PTOL) + v = fp.e1((1.1641532182693481445e-10 - 4.6566128730773925781e-10j)) + assert ae(v, (20.880034621664969632 + 1.3258176632023711778j), tol=ATOL) + assert ae(v.real, 20.880034621664969632, tol=PTOL) + assert ae(v.imag, 1.3258176632023711778, tol=PTOL) + v = fp.e1((0.25 - 1.0j)) + assert ae(v, (-0.16868306393667788761 + 0.4858011885947426971j), tol=ATOL) + assert ae(v.real, -0.16868306393667788761, tol=PTOL) + assert ae(v.imag, 0.4858011885947426971, tol=PTOL) + v = fp.e1((1.0 - 4.0j)) + assert ae(v, (0.03373591813926547318 - 0.073523452241083821877j), tol=ATOL) + assert ae(v.real, 0.03373591813926547318, tol=PTOL) + assert ae(v.imag, -0.073523452241083821877, tol=PTOL) + v = fp.e1((2.0 - 8.0j)) + assert ae(v, (-0.015392833434733785143 + 0.0031747121557605415914j), tol=ATOL) + assert ae(v.real, -0.015392833434733785143, tol=PTOL) + assert ae(v.imag, 0.0031747121557605415914, tol=PTOL) + v = fp.e1((5.0 - 20.0j)) + assert ae(v, (-0.00024419662286542966525 + 0.00021008322966152755674j), tol=ATOL) + assert ae(v.real, -0.00024419662286542966525, tol=PTOL) + assert ae(v.imag, 0.00021008322966152755674, tol=PTOL) + v = fp.e1((20.0 - 80.0j)) + assert ae(v, (2.3255552781051330088e-11 - 8.9463918891349438007e-12j), tol=ATOL) + assert ae(v.real, 2.3255552781051330088e-11, tol=PTOL) + assert ae(v.imag, -8.9463918891349438007e-12, tol=PTOL) + v = fp.e1((30.0 - 120.0j)) + assert ae(v, (-2.7068919097124652332e-16 + 7.0477762411705130239e-16j), tol=ATOL) + assert ae(v.real, -2.7068919097124652332e-16, tol=PTOL) + assert ae(v.imag, 7.0477762411705130239e-16, tol=PTOL) + v = fp.e1((40.0 - 160.0j)) + assert ae(v, (-1.1695597827678024687e-20 - 2.2907401455645736661e-20j), tol=ATOL) + assert ae(v.real, -1.1695597827678024687e-20, tol=PTOL) + assert ae(v.imag, -2.2907401455645736661e-20, tol=PTOL) + v = fp.e1((50.0 - 200.0j)) + assert ae(v, (9.0323746914410162531e-25 + 2.3950601790033530935e-25j), tol=ATOL) + assert ae(v.real, 9.0323746914410162531e-25, tol=PTOL) + assert ae(v.imag, 2.3950601790033530935e-25, tol=PTOL) + v = fp.e1((80.0 - 320.0j)) + assert ae(v, (3.4819106748728063576e-38 + 4.215653005615772724e-38j), tol=ATOL) + assert ae(v.real, 3.4819106748728063576e-38, tol=PTOL) + assert ae(v.imag, 4.215653005615772724e-38, tol=PTOL) + v = fp.e1((1.1641532182693481445e-10 - 1.1641532182693481445e-10j)) + assert ae(v, (21.950067703413105017 + 0.7853981632810329878j), tol=ATOL) + assert ae(v.real, 21.950067703413105017, tol=PTOL) + assert ae(v.imag, 0.7853981632810329878, tol=PTOL) + v = fp.e1((0.25 - 0.25j)) + assert ae(v, (0.71092525792923287894 + 0.56491812441304194711j), tol=ATOL) + assert ae(v.real, 0.71092525792923287894, tol=PTOL) + assert ae(v.imag, 0.56491812441304194711, tol=PTOL) + v = fp.e1((1.0 - 1.0j)) + assert ae(v, (0.00028162445198141832551 + 0.17932453503935894015j), tol=ATOL) + assert ae(v.real, 0.00028162445198141832551, tol=PTOL) + assert ae(v.imag, 0.17932453503935894015, tol=PTOL) + v = fp.e1((2.0 - 2.0j)) + assert ae(v, (-0.033767089606562004246 + 0.018599414169750541925j), tol=ATOL) + assert ae(v.real, -0.033767089606562004246, tol=PTOL) + assert ae(v.imag, 0.018599414169750541925, tol=PTOL) + v = fp.e1((5.0 - 5.0j)) + assert ae(v, (0.0007266506660356393891 - 0.00047102780163522245054j), tol=ATOL) + assert ae(v.real, 0.0007266506660356393891, tol=PTOL) + assert ae(v.imag, -0.00047102780163522245054, tol=PTOL) + v = fp.e1((20.0 - 20.0j)) + assert ae(v, (-2.3824537449367396579e-11 + 6.6969873156525615158e-11j), tol=ATOL) + assert ae(v.real, -2.3824537449367396579e-11, tol=PTOL) + assert ae(v.imag, 6.6969873156525615158e-11, tol=PTOL) + v = fp.e1((30.0 - 30.0j)) + assert ae(v, (1.7316045841744061617e-15 - 1.3065678019487308689e-15j), tol=ATOL) + assert ae(v.real, 1.7316045841744061617e-15, tol=PTOL) + assert ae(v.imag, -1.3065678019487308689e-15, tol=PTOL) + v = fp.e1((40.0 - 40.0j)) + assert ae(v, (-7.4001043002899232182e-20 + 4.991847855336816304e-21j), tol=ATOL) + assert ae(v.real, -7.4001043002899232182e-20, tol=PTOL) + assert ae(v.imag, 4.991847855336816304e-21, tol=PTOL) + v = fp.e1((50.0 - 50.0j)) + assert ae(v, (2.3566128324644641219e-24 + 1.3188326726201614778e-24j), tol=ATOL) + assert ae(v.real, 2.3566128324644641219e-24, tol=PTOL) + assert ae(v.imag, 1.3188326726201614778e-24, tol=PTOL) + v = fp.e1((80.0 - 80.0j)) + assert ae(v, (9.8279750572186526673e-38 - 1.243952841288868831e-37j), tol=ATOL) + assert ae(v.real, 9.8279750572186526673e-38, tol=PTOL) + assert ae(v.imag, -1.243952841288868831e-37, tol=PTOL) + v = fp.e1((4.6566128730773925781e-10 - 1.1641532182693481445e-10j)) + assert ae(v, (20.880034622014215597 + 0.24497866301044883237j), tol=ATOL) + assert ae(v.real, 20.880034622014215597, tol=PTOL) + assert ae(v.imag, 0.24497866301044883237, tol=PTOL) + v = fp.e1((1.0 - 0.25j)) + assert ae(v, (0.19731063945004229095 + 0.087366045774299963672j), tol=ATOL) + assert ae(v.real, 0.19731063945004229095, tol=PTOL) + assert ae(v.imag, 0.087366045774299963672, tol=PTOL) + v = fp.e1((4.0 - 1.0j)) + assert ae(v, (0.0013106173980145506944 + 0.0034542480199350626699j), tol=ATOL) + assert ae(v.real, 0.0013106173980145506944, tol=PTOL) + assert ae(v.imag, 0.0034542480199350626699, tol=PTOL) + v = fp.e1((8.0 - 2.0j)) + assert ae(v, (-0.000022278049065270225945 + 0.000029191940456521555288j), tol=ATOL) + assert ae(v.real, -0.000022278049065270225945, tol=PTOL) + assert ae(v.imag, 0.000029191940456521555288, tol=PTOL) + v = fp.e1((20.0 - 5.0j)) + assert ae(v, (4.7711374515765346894e-11 - 8.2902652405126947359e-11j), tol=ATOL) + assert ae(v.real, 4.7711374515765346894e-11, tol=PTOL) + assert ae(v.imag, -8.2902652405126947359e-11, tol=PTOL) + v = fp.e1((80.0 - 20.0j)) + assert ae(v, (3.8353473865788235787e-38 + 2.129247592349605139e-37j), tol=ATOL) + assert ae(v.real, 3.8353473865788235787e-38, tol=PTOL) + assert ae(v.imag, 2.129247592349605139e-37, tol=PTOL) + v = fp.e1((120.0 - 30.0j)) + assert ae(v, (2.3836002337480334716e-55 - 5.6704043587126198306e-55j), tol=ATOL) + assert ae(v.real, 2.3836002337480334716e-55, tol=PTOL) + assert ae(v.imag, -5.6704043587126198306e-55, tol=PTOL) + v = fp.e1((160.0 - 40.0j)) + assert ae(v, (-1.6238022898654510661e-72 + 1.104172355572287367e-72j), tol=ATOL) + assert ae(v.real, -1.6238022898654510661e-72, tol=PTOL) + assert ae(v.imag, 1.104172355572287367e-72, tol=PTOL) + v = fp.e1((200.0 - 50.0j)) + assert ae(v, (6.6800061461666228487e-90 - 1.4473816083541016115e-91j), tol=ATOL) + assert ae(v.real, 6.6800061461666228487e-90, tol=PTOL) + assert ae(v.imag, -1.4473816083541016115e-91, tol=PTOL) + v = fp.e1((320.0 - 80.0j)) + assert ae(v, (4.2737871527778786157e-143 - 3.1789935525785660314e-142j), tol=ATOL) + assert ae(v.real, 4.2737871527778786157e-143, tol=PTOL) + assert ae(v.imag, -3.1789935525785660314e-142, tol=PTOL) + v = fp.ei(1.1641532182693481445e-10) + assert ae(v, -22.296641293460247028, tol=ATOL) + assert type(v) is float + v = fp.ei(0.25) + assert ae(v, -0.54254326466191372953, tol=ATOL) + assert type(v) is float + v = fp.ei(1.0) + assert ae(v, 1.8951178163559367555, tol=ATOL) + assert type(v) is float + v = fp.ei(2.0) + assert ae(v, 4.9542343560018901634, tol=ATOL) + assert type(v) is float + v = fp.ei(5.0) + assert ae(v, 40.185275355803177455, tol=ATOL) + assert type(v) is float + v = fp.ei(20.0) + assert ae(v, 25615652.66405658882, tol=ATOL) + assert type(v) is float + v = fp.ei(30.0) + assert ae(v, 368973209407.27419706, tol=ATOL) + assert type(v) is float + v = fp.ei(40.0) + assert ae(v, 6039718263611241.5784, tol=ATOL) + assert type(v) is float + v = fp.ei(50.0) + assert ae(v, 1.0585636897131690963e+20, tol=ATOL) + assert type(v) is float + v = fp.ei(80.0) + assert ae(v, 7.0146000049047999696e+32, tol=ATOL) + assert type(v) is float + v = fp.ei((1.1641532182693481445e-10 + 0.0j)) + assert ae(v, (-22.296641293460247028 + 0.0j), tol=ATOL) + assert ae(v.real, -22.296641293460247028, tol=PTOL) + assert v.imag == 0 + v = fp.ei((0.25 + 0.0j)) + assert ae(v, (-0.54254326466191372953 + 0.0j), tol=ATOL) + assert ae(v.real, -0.54254326466191372953, tol=PTOL) + assert v.imag == 0 + v = fp.ei((1.0 + 0.0j)) + assert ae(v, (1.8951178163559367555 + 0.0j), tol=ATOL) + assert ae(v.real, 1.8951178163559367555, tol=PTOL) + assert v.imag == 0 + v = fp.ei((2.0 + 0.0j)) + assert ae(v, (4.9542343560018901634 + 0.0j), tol=ATOL) + assert ae(v.real, 4.9542343560018901634, tol=PTOL) + assert v.imag == 0 + v = fp.ei((5.0 + 0.0j)) + assert ae(v, (40.185275355803177455 + 0.0j), tol=ATOL) + assert ae(v.real, 40.185275355803177455, tol=PTOL) + assert v.imag == 0 + v = fp.ei((20.0 + 0.0j)) + assert ae(v, (25615652.66405658882 + 0.0j), tol=ATOL) + assert ae(v.real, 25615652.66405658882, tol=PTOL) + assert v.imag == 0 + v = fp.ei((30.0 + 0.0j)) + assert ae(v, (368973209407.27419706 + 0.0j), tol=ATOL) + assert ae(v.real, 368973209407.27419706, tol=PTOL) + assert v.imag == 0 + v = fp.ei((40.0 + 0.0j)) + assert ae(v, (6039718263611241.5784 + 0.0j), tol=ATOL) + assert ae(v.real, 6039718263611241.5784, tol=PTOL) + assert v.imag == 0 + v = fp.ei((50.0 + 0.0j)) + assert ae(v, (1.0585636897131690963e+20 + 0.0j), tol=ATOL) + assert ae(v.real, 1.0585636897131690963e+20, tol=PTOL) + assert v.imag == 0 + v = fp.ei((80.0 + 0.0j)) + assert ae(v, (7.0146000049047999696e+32 + 0.0j), tol=ATOL) + assert ae(v.real, 7.0146000049047999696e+32, tol=PTOL) + assert v.imag == 0 + v = fp.ei((4.6566128730773925781e-10 + 1.1641532182693481445e-10j)) + assert ae(v, (-20.880034621082893023 + 0.24497866324327947603j), tol=ATOL) + assert ae(v.real, -20.880034621082893023, tol=PTOL) + assert ae(v.imag, 0.24497866324327947603, tol=PTOL) + v = fp.ei((1.0 + 0.25j)) + assert ae(v, (1.8942716983721074932 + 0.67268237088273915854j), tol=ATOL) + assert ae(v.real, 1.8942716983721074932, tol=PTOL) + assert ae(v.imag, 0.67268237088273915854, tol=PTOL) + v = fp.ei((4.0 + 1.0j)) + assert ae(v, (14.806699492675420438 + 12.280015176673582616j), tol=ATOL) + assert ae(v.real, 14.806699492675420438, tol=PTOL) + assert ae(v.imag, 12.280015176673582616, tol=PTOL) + v = fp.ei((8.0 + 2.0j)) + assert ae(v, (-54.633252667426386294 + 416.34477429173650012j), tol=ATOL) + assert ae(v.real, -54.633252667426386294, tol=PTOL) + assert ae(v.imag, 416.34477429173650012, tol=PTOL) + v = fp.ei((20.0 + 5.0j)) + assert ae(v, (711836.97165402624643 - 24745247.798103247366j), tol=ATOL) + assert ae(v.real, 711836.97165402624643, tol=PTOL) + assert ae(v.imag, -24745247.798103247366, tol=PTOL) + v = fp.ei((80.0 + 20.0j)) + assert ae(v, (4.2139911108612653091e+32 + 5.3367124741918251637e+32j), tol=ATOL) + assert ae(v.real, 4.2139911108612653091e+32, tol=PTOL) + assert ae(v.imag, 5.3367124741918251637e+32, tol=PTOL) + v = fp.ei((120.0 + 30.0j)) + assert ae(v, (-9.7760616203707508892e+48 - 1.058257682317195792e+50j), tol=ATOL) + assert ae(v.real, -9.7760616203707508892e+48, tol=PTOL) + assert ae(v.imag, -1.058257682317195792e+50, tol=PTOL) + v = fp.ei((160.0 + 40.0j)) + assert ae(v, (-8.7065541466623638861e+66 + 1.6577106725141739889e+67j), tol=ATOL) + assert ae(v.real, -8.7065541466623638861e+66, tol=PTOL) + assert ae(v.imag, 1.6577106725141739889e+67, tol=PTOL) + v = fp.ei((200.0 + 50.0j)) + assert ae(v, (3.070744996327018106e+84 - 1.7243244846769415903e+84j), tol=ATOL) + assert ae(v.real, 3.070744996327018106e+84, tol=PTOL) + assert ae(v.imag, -1.7243244846769415903e+84, tol=PTOL) + v = fp.ei((320.0 + 80.0j)) + assert ae(v, (-9.9960598637998647276e+135 - 2.6855081527595608863e+136j), tol=ATOL) + assert ae(v.real, -9.9960598637998647276e+135, tol=PTOL) + assert ae(v.imag, -2.6855081527595608863e+136, tol=PTOL) + v = fp.ei((1.1641532182693481445e-10 + 1.1641532182693481445e-10j)) + assert ae(v, (-21.950067703180274374 + 0.78539816351386363145j), tol=ATOL) + assert ae(v.real, -21.950067703180274374, tol=PTOL) + assert ae(v.imag, 0.78539816351386363145, tol=PTOL) + v = fp.ei((0.25 + 0.25j)) + assert ae(v, (-0.21441047326710323254 + 1.0683772981589995996j), tol=ATOL) + assert ae(v.real, -0.21441047326710323254, tol=PTOL) + assert ae(v.imag, 1.0683772981589995996, tol=PTOL) + v = fp.ei((1.0 + 1.0j)) + assert ae(v, (1.7646259855638540684 + 2.3877698515105224193j), tol=ATOL) + assert ae(v.real, 1.7646259855638540684, tol=PTOL) + assert ae(v.imag, 2.3877698515105224193, tol=PTOL) + v = fp.ei((2.0 + 2.0j)) + assert ae(v, (1.8920781621855474089 + 5.3169624378326579621j), tol=ATOL) + assert ae(v.real, 1.8920781621855474089, tol=PTOL) + assert ae(v.imag, 5.3169624378326579621, tol=PTOL) + v = fp.ei((5.0 + 5.0j)) + assert ae(v, (-13.470936071475245856 - 15.322492395731230968j), tol=ATOL) + assert ae(v.real, -13.470936071475245856, tol=PTOL) + assert ae(v.imag, -15.322492395731230968, tol=PTOL) + v = fp.ei((20.0 + 20.0j)) + assert ae(v, (16589317.398788971896 + 5831705.4712368307104j), tol=ATOL) + assert ae(v.real, 16589317.398788971896, tol=PTOL) + assert ae(v.imag, 5831705.4712368307104, tol=PTOL) + v = fp.ei((30.0 + 30.0j)) + assert ae(v, (-154596484273.69322527 - 204179357834.2723043j), tol=ATOL) + assert ae(v.real, -154596484273.69322527, tol=PTOL) + assert ae(v.imag, -204179357834.2723043, tol=PTOL) + v = fp.ei((40.0 + 40.0j)) + assert ae(v, (287512180321448.45408 + 4203502407932318.1156j), tol=ATOL) + assert ae(v.real, 287512180321448.45408, tol=PTOL) + assert ae(v.imag, 4203502407932318.1156, tol=PTOL) + v = fp.ei((50.0 + 50.0j)) + assert ae(v, (36128528616649268826.0 - 64648801861338741960.0j), tol=ATOL) + assert ae(v.real, 36128528616649268826.0, tol=PTOL) + assert ae(v.imag, -64648801861338741960.0, tol=PTOL) + v = fp.ei((80.0 + 80.0j)) + assert ae(v, (-3.8674816337930010217e+32 - 3.0540709639658071041e+32j), tol=ATOL) + assert ae(v.real, -3.8674816337930010217e+32, tol=PTOL) + assert ae(v.imag, -3.0540709639658071041e+32, tol=PTOL) + v = fp.ei((1.1641532182693481445e-10 + 4.6566128730773925781e-10j)) + assert ae(v, (-20.880034621432138988 + 1.3258176641336937524j), tol=ATOL) + assert ae(v.real, -20.880034621432138988, tol=PTOL) + assert ae(v.imag, 1.3258176641336937524, tol=PTOL) + v = fp.ei((0.25 + 1.0j)) + assert ae(v, (0.59066621214766308594 + 2.3968481059377428687j), tol=ATOL) + assert ae(v.real, 0.59066621214766308594, tol=PTOL) + assert ae(v.imag, 2.3968481059377428687, tol=PTOL) + v = fp.ei((1.0 + 4.0j)) + assert ae(v, (-0.49739047283060471093 + 3.5570287076301818702j), tol=ATOL) + assert ae(v.real, -0.49739047283060471093, tol=PTOL) + assert ae(v.imag, 3.5570287076301818702, tol=PTOL) + v = fp.ei((2.0 + 8.0j)) + assert ae(v, (0.8705211147733730969 + 3.3825859385758486351j), tol=ATOL) + assert ae(v.real, 0.8705211147733730969, tol=PTOL) + assert ae(v.imag, 3.3825859385758486351, tol=PTOL) + v = fp.ei((5.0 + 20.0j)) + assert ae(v, (7.0789514293925893007 + 1.5313749363937141849j), tol=ATOL) + assert ae(v.real, 7.0789514293925893007, tol=PTOL) + assert ae(v.imag, 1.5313749363937141849, tol=PTOL) + v = fp.ei((20.0 + 80.0j)) + assert ae(v, (-5855431.4907298084434 - 720917.79156143806727j), tol=ATOL) + assert ae(v.real, -5855431.4907298084434, tol=PTOL) + assert ae(v.imag, -720917.79156143806727, tol=PTOL) + v = fp.ei((30.0 + 120.0j)) + assert ae(v, (65402491644.703470747 - 56697658396.51586764j), tol=ATOL) + assert ae(v.real, 65402491644.703470747, tol=PTOL) + assert ae(v.imag, -56697658396.51586764, tol=PTOL) + v = fp.ei((40.0 + 160.0j)) + assert ae(v, (-25504929379604.776769 + 1429035198630576.3879j), tol=ATOL) + assert ae(v.real, -25504929379604.776769, tol=PTOL) + assert ae(v.imag, 1429035198630576.3879, tol=PTOL) + v = fp.ei((50.0 + 200.0j)) + assert ae(v, (-18437746526988116954.0 - 17146362239046152342.0j), tol=ATOL) + assert ae(v.real, -18437746526988116954.0, tol=PTOL) + assert ae(v.imag, -17146362239046152342.0, tol=PTOL) + v = fp.ei((80.0 + 320.0j)) + assert ae(v, (-3.3464697299634526706e+31 - 1.6473152633843023919e+32j), tol=ATOL) + assert ae(v.real, -3.3464697299634526706e+31, tol=PTOL) + assert ae(v.imag, -1.6473152633843023919e+32, tol=PTOL) + v = fp.ei((0.0 + 1.1641532182693481445e-10j)) + assert ae(v, (-22.29664129357666235 + 1.5707963269113119411j), tol=ATOL) + assert ae(v.real, -22.29664129357666235, tol=PTOL) + assert ae(v.imag, 1.5707963269113119411, tol=PTOL) + v = fp.ei((0.0 + 0.25j)) + assert ae(v, (-0.82466306258094565309 + 1.8199298971146537833j), tol=ATOL) + assert ae(v.real, -0.82466306258094565309, tol=PTOL) + assert ae(v.imag, 1.8199298971146537833, tol=PTOL) + v = fp.ei((0.0 + 1.0j)) + assert ae(v, (0.33740392290096813466 + 2.5168793971620796342j), tol=ATOL) + assert ae(v.real, 0.33740392290096813466, tol=PTOL) + assert ae(v.imag, 2.5168793971620796342, tol=PTOL) + v = fp.ei((0.0 + 2.0j)) + assert ae(v, (0.4229808287748649957 + 3.1762093035975914678j), tol=ATOL) + assert ae(v.real, 0.4229808287748649957, tol=PTOL) + assert ae(v.imag, 3.1762093035975914678, tol=PTOL) + v = fp.ei((0.0 + 5.0j)) + assert ae(v, (-0.19002974965664387862 + 3.1207275717395707565j), tol=ATOL) + assert ae(v.real, -0.19002974965664387862, tol=PTOL) + assert ae(v.imag, 3.1207275717395707565, tol=PTOL) + v = fp.ei((0.0 + 20.0j)) + assert ae(v, (0.04441982084535331654 + 3.1190380278383364594j), tol=ATOL) + assert ae(v.real, 0.04441982084535331654, tol=PTOL) + assert ae(v.imag, 3.1190380278383364594, tol=PTOL) + v = fp.ei((0.0 + 30.0j)) + assert ae(v, (-0.033032417282071143779 + 3.1375528668252477302j), tol=ATOL) + assert ae(v.real, -0.033032417282071143779, tol=PTOL) + assert ae(v.imag, 3.1375528668252477302, tol=PTOL) + v = fp.ei((0.0 + 40.0j)) + assert ae(v, (0.019020007896208766962 + 3.157781446149681126j), tol=ATOL) + assert ae(v.real, 0.019020007896208766962, tol=PTOL) + assert ae(v.imag, 3.157781446149681126, tol=PTOL) + v = fp.ei((0.0 + 50.0j)) + assert ae(v, (-0.0056283863241163054402 + 3.122413399280832514j), tol=ATOL) + assert ae(v.real, -0.0056283863241163054402, tol=PTOL) + assert ae(v.imag, 3.122413399280832514, tol=PTOL) + v = fp.ei((0.0 + 80.0j)) + assert ae(v, (-0.012402501155070958192 + 3.1431272137073839346j), tol=ATOL) + assert ae(v.real, -0.012402501155070958192, tol=PTOL) + assert ae(v.imag, 3.1431272137073839346, tol=PTOL) + v = fp.ei((-1.1641532182693481445e-10 + 4.6566128730773925781e-10j)) + assert ae(v, (-20.880034621664969632 + 1.8157749903874220607j), tol=ATOL) + assert ae(v.real, -20.880034621664969632, tol=PTOL) + assert ae(v.imag, 1.8157749903874220607, tol=PTOL) + v = fp.ei((-0.25 + 1.0j)) + assert ae(v, (0.16868306393667788761 + 2.6557914649950505414j), tol=ATOL) + assert ae(v.real, 0.16868306393667788761, tol=PTOL) + assert ae(v.imag, 2.6557914649950505414, tol=PTOL) + v = fp.ei((-1.0 + 4.0j)) + assert ae(v, (-0.03373591813926547318 + 3.2151161058308770603j), tol=ATOL) + assert ae(v.real, -0.03373591813926547318, tol=PTOL) + assert ae(v.imag, 3.2151161058308770603, tol=PTOL) + v = fp.ei((-2.0 + 8.0j)) + assert ae(v, (0.015392833434733785143 + 3.1384179414340326969j), tol=ATOL) + assert ae(v.real, 0.015392833434733785143, tol=PTOL) + assert ae(v.imag, 3.1384179414340326969, tol=PTOL) + v = fp.ei((-5.0 + 20.0j)) + assert ae(v, (0.00024419662286542966525 + 3.1413825703601317109j), tol=ATOL) + assert ae(v.real, 0.00024419662286542966525, tol=PTOL) + assert ae(v.imag, 3.1413825703601317109, tol=PTOL) + v = fp.ei((-20.0 + 80.0j)) + assert ae(v, (-2.3255552781051330088e-11 + 3.1415926535987396304j), tol=ATOL) + assert ae(v.real, -2.3255552781051330088e-11, tol=PTOL) + assert ae(v.imag, 3.1415926535987396304, tol=PTOL) + v = fp.ei((-30.0 + 120.0j)) + assert ae(v, (2.7068919097124652332e-16 + 3.1415926535897925337j), tol=ATOL) + assert ae(v.real, 2.7068919097124652332e-16, tol=PTOL) + assert ae(v.imag, 3.1415926535897925337, tol=PTOL) + v = fp.ei((-40.0 + 160.0j)) + assert ae(v, (1.1695597827678024687e-20 + 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, 1.1695597827678024687e-20, tol=PTOL) + assert ae(v.imag, 3.1415926535897932385, tol=PTOL) + v = fp.ei((-50.0 + 200.0j)) + assert ae(v, (-9.0323746914410162531e-25 + 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -9.0323746914410162531e-25, tol=PTOL) + assert ae(v.imag, 3.1415926535897932385, tol=PTOL) + v = fp.ei((-80.0 + 320.0j)) + assert ae(v, (-3.4819106748728063576e-38 + 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -3.4819106748728063576e-38, tol=PTOL) + assert ae(v.imag, 3.1415926535897932385, tol=PTOL) + v = fp.ei((-4.6566128730773925781e-10 + 1.1641532182693481445e-10j)) + assert ae(v, (-20.880034622014215597 + 2.8966139905793444061j), tol=ATOL) + assert ae(v.real, -20.880034622014215597, tol=PTOL) + assert ae(v.imag, 2.8966139905793444061, tol=PTOL) + v = fp.ei((-1.0 + 0.25j)) + assert ae(v, (-0.19731063945004229095 + 3.0542266078154932748j), tol=ATOL) + assert ae(v.real, -0.19731063945004229095, tol=PTOL) + assert ae(v.imag, 3.0542266078154932748, tol=PTOL) + v = fp.ei((-4.0 + 1.0j)) + assert ae(v, (-0.0013106173980145506944 + 3.1381384055698581758j), tol=ATOL) + assert ae(v.real, -0.0013106173980145506944, tol=PTOL) + assert ae(v.imag, 3.1381384055698581758, tol=PTOL) + v = fp.ei((-8.0 + 2.0j)) + assert ae(v, (0.000022278049065270225945 + 3.1415634616493367169j), tol=ATOL) + assert ae(v.real, 0.000022278049065270225945, tol=PTOL) + assert ae(v.imag, 3.1415634616493367169, tol=PTOL) + v = fp.ei((-20.0 + 5.0j)) + assert ae(v, (-4.7711374515765346894e-11 + 3.1415926536726958909j), tol=ATOL) + assert ae(v.real, -4.7711374515765346894e-11, tol=PTOL) + assert ae(v.imag, 3.1415926536726958909, tol=PTOL) + v = fp.ei((-80.0 + 20.0j)) + assert ae(v, (-3.8353473865788235787e-38 + 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -3.8353473865788235787e-38, tol=PTOL) + assert ae(v.imag, 3.1415926535897932385, tol=PTOL) + v = fp.ei((-120.0 + 30.0j)) + assert ae(v, (-2.3836002337480334716e-55 + 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -2.3836002337480334716e-55, tol=PTOL) + assert ae(v.imag, 3.1415926535897932385, tol=PTOL) + v = fp.ei((-160.0 + 40.0j)) + assert ae(v, (1.6238022898654510661e-72 + 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, 1.6238022898654510661e-72, tol=PTOL) + assert ae(v.imag, 3.1415926535897932385, tol=PTOL) + v = fp.ei((-200.0 + 50.0j)) + assert ae(v, (-6.6800061461666228487e-90 + 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -6.6800061461666228487e-90, tol=PTOL) + assert ae(v.imag, 3.1415926535897932385, tol=PTOL) + v = fp.ei((-320.0 + 80.0j)) + assert ae(v, (-4.2737871527778786157e-143 + 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -4.2737871527778786157e-143, tol=PTOL) + assert ae(v.imag, 3.1415926535897932385, tol=PTOL) + v = fp.ei(-1.1641532182693481445e-10) + assert ae(v, -22.296641293693077672, tol=ATOL) + assert type(v) is float + v = fp.ei(-0.25) + assert ae(v, -1.0442826344437381945, tol=ATOL) + assert type(v) is float + v = fp.ei(-1.0) + assert ae(v, -0.21938393439552027368, tol=ATOL) + assert type(v) is float + v = fp.ei(-2.0) + assert ae(v, -0.048900510708061119567, tol=ATOL) + assert type(v) is float + v = fp.ei(-5.0) + assert ae(v, -0.0011482955912753257973, tol=ATOL) + assert type(v) is float + v = fp.ei(-20.0) + assert ae(v, -9.8355252906498816904e-11, tol=ATOL) + assert type(v) is float + v = fp.ei(-30.0) + assert ae(v, -3.0215520106888125448e-15, tol=ATOL) + assert type(v) is float + v = fp.ei(-40.0) + assert ae(v, -1.0367732614516569722e-19, tol=ATOL) + assert type(v) is float + v = fp.ei(-50.0) + assert ae(v, -3.7832640295504590187e-24, tol=ATOL) + assert type(v) is float + v = fp.ei(-80.0) + assert ae(v, -2.2285432586884729112e-37, tol=ATOL) + assert type(v) is float + v = fp.ei((-1.1641532182693481445e-10 + 0.0j)) + assert ae(v, (-22.296641293693077672 + 0.0j), tol=ATOL) + assert ae(v.real, -22.296641293693077672, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-0.25 + 0.0j)) + assert ae(v, (-1.0442826344437381945 + 0.0j), tol=ATOL) + assert ae(v.real, -1.0442826344437381945, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-1.0 + 0.0j)) + assert ae(v, (-0.21938393439552027368 + 0.0j), tol=ATOL) + assert ae(v.real, -0.21938393439552027368, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-2.0 + 0.0j)) + assert ae(v, (-0.048900510708061119567 + 0.0j), tol=ATOL) + assert ae(v.real, -0.048900510708061119567, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-5.0 + 0.0j)) + assert ae(v, (-0.0011482955912753257973 + 0.0j), tol=ATOL) + assert ae(v.real, -0.0011482955912753257973, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-20.0 + 0.0j)) + assert ae(v, (-9.8355252906498816904e-11 + 0.0j), tol=ATOL) + assert ae(v.real, -9.8355252906498816904e-11, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-30.0 + 0.0j)) + assert ae(v, (-3.0215520106888125448e-15 + 0.0j), tol=ATOL) + assert ae(v.real, -3.0215520106888125448e-15, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-40.0 + 0.0j)) + assert ae(v, (-1.0367732614516569722e-19 + 0.0j), tol=ATOL) + assert ae(v.real, -1.0367732614516569722e-19, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-50.0 + 0.0j)) + assert ae(v, (-3.7832640295504590187e-24 + 0.0j), tol=ATOL) + assert ae(v.real, -3.7832640295504590187e-24, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-80.0 + 0.0j)) + assert ae(v, (-2.2285432586884729112e-37 + 0.0j), tol=ATOL) + assert ae(v.real, -2.2285432586884729112e-37, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-4.6566128730773925781e-10 - 1.1641532182693481445e-10j)) + assert ae(v, (-20.880034622014215597 - 2.8966139905793444061j), tol=ATOL) + assert ae(v.real, -20.880034622014215597, tol=PTOL) + assert ae(v.imag, -2.8966139905793444061, tol=PTOL) + v = fp.ei((-1.0 - 0.25j)) + assert ae(v, (-0.19731063945004229095 - 3.0542266078154932748j), tol=ATOL) + assert ae(v.real, -0.19731063945004229095, tol=PTOL) + assert ae(v.imag, -3.0542266078154932748, tol=PTOL) + v = fp.ei((-4.0 - 1.0j)) + assert ae(v, (-0.0013106173980145506944 - 3.1381384055698581758j), tol=ATOL) + assert ae(v.real, -0.0013106173980145506944, tol=PTOL) + assert ae(v.imag, -3.1381384055698581758, tol=PTOL) + v = fp.ei((-8.0 - 2.0j)) + assert ae(v, (0.000022278049065270225945 - 3.1415634616493367169j), tol=ATOL) + assert ae(v.real, 0.000022278049065270225945, tol=PTOL) + assert ae(v.imag, -3.1415634616493367169, tol=PTOL) + v = fp.ei((-20.0 - 5.0j)) + assert ae(v, (-4.7711374515765346894e-11 - 3.1415926536726958909j), tol=ATOL) + assert ae(v.real, -4.7711374515765346894e-11, tol=PTOL) + assert ae(v.imag, -3.1415926536726958909, tol=PTOL) + v = fp.ei((-80.0 - 20.0j)) + assert ae(v, (-3.8353473865788235787e-38 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -3.8353473865788235787e-38, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-120.0 - 30.0j)) + assert ae(v, (-2.3836002337480334716e-55 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -2.3836002337480334716e-55, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-160.0 - 40.0j)) + assert ae(v, (1.6238022898654510661e-72 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, 1.6238022898654510661e-72, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-200.0 - 50.0j)) + assert ae(v, (-6.6800061461666228487e-90 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -6.6800061461666228487e-90, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-320.0 - 80.0j)) + assert ae(v, (-4.2737871527778786157e-143 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -4.2737871527778786157e-143, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-1.1641532182693481445e-10 - 1.1641532182693481445e-10j)) + assert ae(v, (-21.950067703413105017 - 2.3561944903087602507j), tol=ATOL) + assert ae(v.real, -21.950067703413105017, tol=PTOL) + assert ae(v.imag, -2.3561944903087602507, tol=PTOL) + v = fp.ei((-0.25 - 0.25j)) + assert ae(v, (-0.71092525792923287894 - 2.5766745291767512913j), tol=ATOL) + assert ae(v.real, -0.71092525792923287894, tol=PTOL) + assert ae(v.imag, -2.5766745291767512913, tol=PTOL) + v = fp.ei((-1.0 - 1.0j)) + assert ae(v, (-0.00028162445198141832551 - 2.9622681185504342983j), tol=ATOL) + assert ae(v.real, -0.00028162445198141832551, tol=PTOL) + assert ae(v.imag, -2.9622681185504342983, tol=PTOL) + v = fp.ei((-2.0 - 2.0j)) + assert ae(v, (0.033767089606562004246 - 3.1229932394200426965j), tol=ATOL) + assert ae(v.real, 0.033767089606562004246, tol=PTOL) + assert ae(v.imag, -3.1229932394200426965, tol=PTOL) + v = fp.ei((-5.0 - 5.0j)) + assert ae(v, (-0.0007266506660356393891 - 3.1420636813914284609j), tol=ATOL) + assert ae(v.real, -0.0007266506660356393891, tol=PTOL) + assert ae(v.imag, -3.1420636813914284609, tol=PTOL) + v = fp.ei((-20.0 - 20.0j)) + assert ae(v, (2.3824537449367396579e-11 - 3.1415926535228233653j), tol=ATOL) + assert ae(v.real, 2.3824537449367396579e-11, tol=PTOL) + assert ae(v.imag, -3.1415926535228233653, tol=PTOL) + v = fp.ei((-30.0 - 30.0j)) + assert ae(v, (-1.7316045841744061617e-15 - 3.141592653589794545j), tol=ATOL) + assert ae(v.real, -1.7316045841744061617e-15, tol=PTOL) + assert ae(v.imag, -3.141592653589794545, tol=PTOL) + v = fp.ei((-40.0 - 40.0j)) + assert ae(v, (7.4001043002899232182e-20 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, 7.4001043002899232182e-20, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-50.0 - 50.0j)) + assert ae(v, (-2.3566128324644641219e-24 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -2.3566128324644641219e-24, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-80.0 - 80.0j)) + assert ae(v, (-9.8279750572186526673e-38 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -9.8279750572186526673e-38, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-1.1641532182693481445e-10 - 4.6566128730773925781e-10j)) + assert ae(v, (-20.880034621664969632 - 1.8157749903874220607j), tol=ATOL) + assert ae(v.real, -20.880034621664969632, tol=PTOL) + assert ae(v.imag, -1.8157749903874220607, tol=PTOL) + v = fp.ei((-0.25 - 1.0j)) + assert ae(v, (0.16868306393667788761 - 2.6557914649950505414j), tol=ATOL) + assert ae(v.real, 0.16868306393667788761, tol=PTOL) + assert ae(v.imag, -2.6557914649950505414, tol=PTOL) + v = fp.ei((-1.0 - 4.0j)) + assert ae(v, (-0.03373591813926547318 - 3.2151161058308770603j), tol=ATOL) + assert ae(v.real, -0.03373591813926547318, tol=PTOL) + assert ae(v.imag, -3.2151161058308770603, tol=PTOL) + v = fp.ei((-2.0 - 8.0j)) + assert ae(v, (0.015392833434733785143 - 3.1384179414340326969j), tol=ATOL) + assert ae(v.real, 0.015392833434733785143, tol=PTOL) + assert ae(v.imag, -3.1384179414340326969, tol=PTOL) + v = fp.ei((-5.0 - 20.0j)) + assert ae(v, (0.00024419662286542966525 - 3.1413825703601317109j), tol=ATOL) + assert ae(v.real, 0.00024419662286542966525, tol=PTOL) + assert ae(v.imag, -3.1413825703601317109, tol=PTOL) + v = fp.ei((-20.0 - 80.0j)) + assert ae(v, (-2.3255552781051330088e-11 - 3.1415926535987396304j), tol=ATOL) + assert ae(v.real, -2.3255552781051330088e-11, tol=PTOL) + assert ae(v.imag, -3.1415926535987396304, tol=PTOL) + v = fp.ei((-30.0 - 120.0j)) + assert ae(v, (2.7068919097124652332e-16 - 3.1415926535897925337j), tol=ATOL) + assert ae(v.real, 2.7068919097124652332e-16, tol=PTOL) + assert ae(v.imag, -3.1415926535897925337, tol=PTOL) + v = fp.ei((-40.0 - 160.0j)) + assert ae(v, (1.1695597827678024687e-20 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, 1.1695597827678024687e-20, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-50.0 - 200.0j)) + assert ae(v, (-9.0323746914410162531e-25 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -9.0323746914410162531e-25, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-80.0 - 320.0j)) + assert ae(v, (-3.4819106748728063576e-38 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -3.4819106748728063576e-38, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((0.0 - 1.1641532182693481445e-10j)) + assert ae(v, (-22.29664129357666235 - 1.5707963269113119411j), tol=ATOL) + assert ae(v.real, -22.29664129357666235, tol=PTOL) + assert ae(v.imag, -1.5707963269113119411, tol=PTOL) + v = fp.ei((0.0 - 0.25j)) + assert ae(v, (-0.82466306258094565309 - 1.8199298971146537833j), tol=ATOL) + assert ae(v.real, -0.82466306258094565309, tol=PTOL) + assert ae(v.imag, -1.8199298971146537833, tol=PTOL) + v = fp.ei((0.0 - 1.0j)) + assert ae(v, (0.33740392290096813466 - 2.5168793971620796342j), tol=ATOL) + assert ae(v.real, 0.33740392290096813466, tol=PTOL) + assert ae(v.imag, -2.5168793971620796342, tol=PTOL) + v = fp.ei((0.0 - 2.0j)) + assert ae(v, (0.4229808287748649957 - 3.1762093035975914678j), tol=ATOL) + assert ae(v.real, 0.4229808287748649957, tol=PTOL) + assert ae(v.imag, -3.1762093035975914678, tol=PTOL) + v = fp.ei((0.0 - 5.0j)) + assert ae(v, (-0.19002974965664387862 - 3.1207275717395707565j), tol=ATOL) + assert ae(v.real, -0.19002974965664387862, tol=PTOL) + assert ae(v.imag, -3.1207275717395707565, tol=PTOL) + v = fp.ei((0.0 - 20.0j)) + assert ae(v, (0.04441982084535331654 - 3.1190380278383364594j), tol=ATOL) + assert ae(v.real, 0.04441982084535331654, tol=PTOL) + assert ae(v.imag, -3.1190380278383364594, tol=PTOL) + v = fp.ei((0.0 - 30.0j)) + assert ae(v, (-0.033032417282071143779 - 3.1375528668252477302j), tol=ATOL) + assert ae(v.real, -0.033032417282071143779, tol=PTOL) + assert ae(v.imag, -3.1375528668252477302, tol=PTOL) + v = fp.ei((0.0 - 40.0j)) + assert ae(v, (0.019020007896208766962 - 3.157781446149681126j), tol=ATOL) + assert ae(v.real, 0.019020007896208766962, tol=PTOL) + assert ae(v.imag, -3.157781446149681126, tol=PTOL) + v = fp.ei((0.0 - 50.0j)) + assert ae(v, (-0.0056283863241163054402 - 3.122413399280832514j), tol=ATOL) + assert ae(v.real, -0.0056283863241163054402, tol=PTOL) + assert ae(v.imag, -3.122413399280832514, tol=PTOL) + v = fp.ei((0.0 - 80.0j)) + assert ae(v, (-0.012402501155070958192 - 3.1431272137073839346j), tol=ATOL) + assert ae(v.real, -0.012402501155070958192, tol=PTOL) + assert ae(v.imag, -3.1431272137073839346, tol=PTOL) + v = fp.ei((1.1641532182693481445e-10 - 4.6566128730773925781e-10j)) + assert ae(v, (-20.880034621432138988 - 1.3258176641336937524j), tol=ATOL) + assert ae(v.real, -20.880034621432138988, tol=PTOL) + assert ae(v.imag, -1.3258176641336937524, tol=PTOL) + v = fp.ei((0.25 - 1.0j)) + assert ae(v, (0.59066621214766308594 - 2.3968481059377428687j), tol=ATOL) + assert ae(v.real, 0.59066621214766308594, tol=PTOL) + assert ae(v.imag, -2.3968481059377428687, tol=PTOL) + v = fp.ei((1.0 - 4.0j)) + assert ae(v, (-0.49739047283060471093 - 3.5570287076301818702j), tol=ATOL) + assert ae(v.real, -0.49739047283060471093, tol=PTOL) + assert ae(v.imag, -3.5570287076301818702, tol=PTOL) + v = fp.ei((2.0 - 8.0j)) + assert ae(v, (0.8705211147733730969 - 3.3825859385758486351j), tol=ATOL) + assert ae(v.real, 0.8705211147733730969, tol=PTOL) + assert ae(v.imag, -3.3825859385758486351, tol=PTOL) + v = fp.ei((5.0 - 20.0j)) + assert ae(v, (7.0789514293925893007 - 1.5313749363937141849j), tol=ATOL) + assert ae(v.real, 7.0789514293925893007, tol=PTOL) + assert ae(v.imag, -1.5313749363937141849, tol=PTOL) + v = fp.ei((20.0 - 80.0j)) + assert ae(v, (-5855431.4907298084434 + 720917.79156143806727j), tol=ATOL) + assert ae(v.real, -5855431.4907298084434, tol=PTOL) + assert ae(v.imag, 720917.79156143806727, tol=PTOL) + v = fp.ei((30.0 - 120.0j)) + assert ae(v, (65402491644.703470747 + 56697658396.51586764j), tol=ATOL) + assert ae(v.real, 65402491644.703470747, tol=PTOL) + assert ae(v.imag, 56697658396.51586764, tol=PTOL) + v = fp.ei((40.0 - 160.0j)) + assert ae(v, (-25504929379604.776769 - 1429035198630576.3879j), tol=ATOL) + assert ae(v.real, -25504929379604.776769, tol=PTOL) + assert ae(v.imag, -1429035198630576.3879, tol=PTOL) + v = fp.ei((50.0 - 200.0j)) + assert ae(v, (-18437746526988116954.0 + 17146362239046152342.0j), tol=ATOL) + assert ae(v.real, -18437746526988116954.0, tol=PTOL) + assert ae(v.imag, 17146362239046152342.0, tol=PTOL) + v = fp.ei((80.0 - 320.0j)) + assert ae(v, (-3.3464697299634526706e+31 + 1.6473152633843023919e+32j), tol=ATOL) + assert ae(v.real, -3.3464697299634526706e+31, tol=PTOL) + assert ae(v.imag, 1.6473152633843023919e+32, tol=PTOL) + v = fp.ei((1.1641532182693481445e-10 - 1.1641532182693481445e-10j)) + assert ae(v, (-21.950067703180274374 - 0.78539816351386363145j), tol=ATOL) + assert ae(v.real, -21.950067703180274374, tol=PTOL) + assert ae(v.imag, -0.78539816351386363145, tol=PTOL) + v = fp.ei((0.25 - 0.25j)) + assert ae(v, (-0.21441047326710323254 - 1.0683772981589995996j), tol=ATOL) + assert ae(v.real, -0.21441047326710323254, tol=PTOL) + assert ae(v.imag, -1.0683772981589995996, tol=PTOL) + v = fp.ei((1.0 - 1.0j)) + assert ae(v, (1.7646259855638540684 - 2.3877698515105224193j), tol=ATOL) + assert ae(v.real, 1.7646259855638540684, tol=PTOL) + assert ae(v.imag, -2.3877698515105224193, tol=PTOL) + v = fp.ei((2.0 - 2.0j)) + assert ae(v, (1.8920781621855474089 - 5.3169624378326579621j), tol=ATOL) + assert ae(v.real, 1.8920781621855474089, tol=PTOL) + assert ae(v.imag, -5.3169624378326579621, tol=PTOL) + v = fp.ei((5.0 - 5.0j)) + assert ae(v, (-13.470936071475245856 + 15.322492395731230968j), tol=ATOL) + assert ae(v.real, -13.470936071475245856, tol=PTOL) + assert ae(v.imag, 15.322492395731230968, tol=PTOL) + v = fp.ei((20.0 - 20.0j)) + assert ae(v, (16589317.398788971896 - 5831705.4712368307104j), tol=ATOL) + assert ae(v.real, 16589317.398788971896, tol=PTOL) + assert ae(v.imag, -5831705.4712368307104, tol=PTOL) + v = fp.ei((30.0 - 30.0j)) + assert ae(v, (-154596484273.69322527 + 204179357834.2723043j), tol=ATOL) + assert ae(v.real, -154596484273.69322527, tol=PTOL) + assert ae(v.imag, 204179357834.2723043, tol=PTOL) + v = fp.ei((40.0 - 40.0j)) + assert ae(v, (287512180321448.45408 - 4203502407932318.1156j), tol=ATOL) + assert ae(v.real, 287512180321448.45408, tol=PTOL) + assert ae(v.imag, -4203502407932318.1156, tol=PTOL) + v = fp.ei((50.0 - 50.0j)) + assert ae(v, (36128528616649268826.0 + 64648801861338741960.0j), tol=ATOL) + assert ae(v.real, 36128528616649268826.0, tol=PTOL) + assert ae(v.imag, 64648801861338741960.0, tol=PTOL) + v = fp.ei((80.0 - 80.0j)) + assert ae(v, (-3.8674816337930010217e+32 + 3.0540709639658071041e+32j), tol=ATOL) + assert ae(v.real, -3.8674816337930010217e+32, tol=PTOL) + assert ae(v.imag, 3.0540709639658071041e+32, tol=PTOL) + v = fp.ei((4.6566128730773925781e-10 - 1.1641532182693481445e-10j)) + assert ae(v, (-20.880034621082893023 - 0.24497866324327947603j), tol=ATOL) + assert ae(v.real, -20.880034621082893023, tol=PTOL) + assert ae(v.imag, -0.24497866324327947603, tol=PTOL) + v = fp.ei((1.0 - 0.25j)) + assert ae(v, (1.8942716983721074932 - 0.67268237088273915854j), tol=ATOL) + assert ae(v.real, 1.8942716983721074932, tol=PTOL) + assert ae(v.imag, -0.67268237088273915854, tol=PTOL) + v = fp.ei((4.0 - 1.0j)) + assert ae(v, (14.806699492675420438 - 12.280015176673582616j), tol=ATOL) + assert ae(v.real, 14.806699492675420438, tol=PTOL) + assert ae(v.imag, -12.280015176673582616, tol=PTOL) + v = fp.ei((8.0 - 2.0j)) + assert ae(v, (-54.633252667426386294 - 416.34477429173650012j), tol=ATOL) + assert ae(v.real, -54.633252667426386294, tol=PTOL) + assert ae(v.imag, -416.34477429173650012, tol=PTOL) + v = fp.ei((20.0 - 5.0j)) + assert ae(v, (711836.97165402624643 + 24745247.798103247366j), tol=ATOL) + assert ae(v.real, 711836.97165402624643, tol=PTOL) + assert ae(v.imag, 24745247.798103247366, tol=PTOL) + v = fp.ei((80.0 - 20.0j)) + assert ae(v, (4.2139911108612653091e+32 - 5.3367124741918251637e+32j), tol=ATOL) + assert ae(v.real, 4.2139911108612653091e+32, tol=PTOL) + assert ae(v.imag, -5.3367124741918251637e+32, tol=PTOL) + v = fp.ei((120.0 - 30.0j)) + assert ae(v, (-9.7760616203707508892e+48 + 1.058257682317195792e+50j), tol=ATOL) + assert ae(v.real, -9.7760616203707508892e+48, tol=PTOL) + assert ae(v.imag, 1.058257682317195792e+50, tol=PTOL) + v = fp.ei((160.0 - 40.0j)) + assert ae(v, (-8.7065541466623638861e+66 - 1.6577106725141739889e+67j), tol=ATOL) + assert ae(v.real, -8.7065541466623638861e+66, tol=PTOL) + assert ae(v.imag, -1.6577106725141739889e+67, tol=PTOL) + v = fp.ei((200.0 - 50.0j)) + assert ae(v, (3.070744996327018106e+84 + 1.7243244846769415903e+84j), tol=ATOL) + assert ae(v.real, 3.070744996327018106e+84, tol=PTOL) + assert ae(v.imag, 1.7243244846769415903e+84, tol=PTOL) + v = fp.ei((320.0 - 80.0j)) + assert ae(v, (-9.9960598637998647276e+135 + 2.6855081527595608863e+136j), tol=ATOL) + assert ae(v.real, -9.9960598637998647276e+135, tol=PTOL) + assert ae(v.imag, 2.6855081527595608863e+136, tol=PTOL) + +def test_fp_isfinite(): + assert fp.isfinite(1.2) + assert fp.isfinite(1+2j) + assert not fp.isfinite(fp.inf) + assert not fp.isfinite(fp.nan) + +def test_fp_nan_in_args(): + assert fp.isnan(fp.ei(fp.nan)) # issue 483 + assert fp.isnan(fp.li(fp.nan)) # issue 484 + assert fp.isnan(fp.ci(fp.nan)) # issue 480 + assert fp.isnan(fp.si(fp.nan)) # issue 481 + assert fp.isnan(fp.chi(fp.nan)) # issue 482 + assert fp.isnan(fp.shi(fp.nan)) + assert fp.isnan(fp.e1(fp.nan)) # issue 487 + assert fp.isnan(fp.chebyt(1.3, fp.nan)) # issue 478 + assert fp.isnan(fp.chebyt(13, fp.nan)) + assert fp.isnan(fp.hyp2f2(0.4, 2.5, 2.2, 0.7, fp.nan)) # issue 509 + +def test_issue_510(): + assert fp.rgamma(fp.inf) == fp.zero + +def test_issue_491(): + assert fp.appellf1(0, 0.4, 2.5, 2.2, fp.inf, 1.4) == fp.one + +def test_issue_521(): + assert fp.ff(1, -fp.inf) == 0.0 + assert fp.isnan(fp.ff(1, fp.inf)) diff --git a/mpmath/tests/test_functions.py b/mpmath/tests/test_functions.py new file mode 100644 index 0000000..8bf144f --- /dev/null +++ b/mpmath/tests/test_functions.py @@ -0,0 +1,1106 @@ +import cmath +import math +import random + +import pytest + +from mpmath import (acos, acosh, acot, acoth, acsc, acsch, arange, arg, asec, + asech, asin, asinh, atan, atan2, atanh, catalan, cbrt, + ceil, conj, cos, cos_sin, cosh, cospi, cospi_sinpi, cot, + coth, csc, csch, cyclotomic, degree, degrees, e, eps, + euler, exp, exp2, expj, expjpi, expm1, fabs, fadd, fib, + fibonacci, floor, fmod, fp, frexp, glaisher, hypot, im, + inf, isnan, j, khinchin, ldexp, linspace, ln, ln2, ln10, + log, log1p, log2, log10, mertens, mp, mpc, mpf, nan, + nthroot, phi, pi, power, powm1, radians, rand, re, root, + sec, sech, sign, sin, sinc, sincpi, sinh, sinpi, sqrt, tan, + tanh, twinprime, unitroots) +from mpmath.libmp import (MPZ, ComplexResult, from_int, mpf_gt, mpf_lt, + mpf_mul, mpf_pow_int, mpf_sqrt, round_ceiling, + round_down, round_nearest, round_up) +from mpmath.libmp.libmpf import mpf_rand + + +def mpc_ae(a, b, eps=eps): + res = True + res = res and a.real.ae(b.real, eps) + res = res and a.imag.ae(b.imag, eps) + return res + +#---------------------------------------------------------------------------- +# Constants and functions +# + +tpi = "3.1415926535897932384626433832795028841971693993751058209749445923078\ +1640628620899862803482534211706798" +te = "2.71828182845904523536028747135266249775724709369995957496696762772407\ +663035354759457138217852516642743" +tdegree = "0.017453292519943295769236907684886127134428718885417254560971914\ +4017100911460344944368224156963450948221" +teuler = "0.5772156649015328606065120900824024310421593359399235988057672348\ +84867726777664670936947063291746749516" +tln2 = "0.693147180559945309417232121458176568075500134360255254120680009493\ +393621969694715605863326996418687542" +tln10 = "2.30258509299404568401799145468436420760110148862877297603332790096\ +757260967735248023599720508959829834" +tcatalan = "0.91596559417721901505460351493238411077414937428167213426649811\ +9621763019776254769479356512926115106249" +tkhinchin = "2.6854520010653064453097148354817956938203822939944629530511523\ +4555721885953715200280114117493184769800" +tglaisher = "1.2824271291006226368753425688697917277676889273250011920637400\ +2174040630885882646112973649195820237439420646" +tapery = "1.2020569031595942853997381615114499907649862923404988817922715553\ +4183820578631309018645587360933525815" +tphi = "1.618033988749894848204586834365638117720309179805762862135448622705\ +26046281890244970720720418939113748475" +tmertens = "0.26149721284764278375542683860869585905156664826119920619206421\ +3924924510897368209714142631434246651052" +ttwinprime = "0.660161815846869573927812110014555778432623360284733413319448\ +423335405642304495277143760031413839867912" + +def test_constants(): + for prec in [3, 7, 10, 15, 20, 37, 80, 100, 29]: + mp.dps = prec + assert pi == mpf(tpi) + assert e == mpf(te) + assert degree == mpf(tdegree) + assert euler == mpf(teuler) + assert ln2 == mpf(tln2) + assert ln10 == mpf(tln10) + assert catalan == mpf(tcatalan) + assert khinchin == mpf(tkhinchin) + assert glaisher == mpf(tglaisher) + assert phi == mpf(tphi) + if prec < 50: + assert mertens == mpf(tmertens) + assert twinprime == mpf(ttwinprime) + mp.dps = 15 + assert pi >= -1 + assert pi > 2 + assert pi > 3 + assert pi < 4 + +def test_exact_sqrts(): + for i in range(20000): + assert sqrt(mpf(i*i)) == i + random.seed(1) + for prec in [100, 300, 1000, 10000]: + mp.dps = prec + for i in range(20): + A = random.randint(10**(prec//2-2), 10**(prec//2-1)) + assert sqrt(mpf(A*A)) == A + mp.dps = 15 + for i in range(100): + for a in [1, 8, 25, 112307]: + assert sqrt(mpf((a*a, 2*i))) == mpf((a, i)) + assert sqrt(mpf((a*a, -2*i))) == mpf((a, -i)) + +def test_sqrt_rounding(): + for i in [2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15]: + i = from_int(i) + for dps in [7, 15, 83, 106, 2000]: + mp.dps = dps + a = mpf_pow_int(mpf_sqrt(i, mp.prec, round_down), 2, mp.prec, round_down) + b = mpf_pow_int(mpf_sqrt(i, mp.prec, round_up), 2, mp.prec, round_up) + assert mpf_lt(a, i) + assert mpf_gt(b, i) + random.seed(1234) + prec = 100 + for rnd in [round_down, round_nearest, round_ceiling]: + for i in range(100): + a = mpf_rand(prec) + b = mpf_mul(a, a) + assert mpf_sqrt(b, prec, rnd) == a + # Test some extreme cases + mp.dps = 100 + a = mpf(9) + 1e-90 + b = mpf(9) - 1e-90 + mp.dps = 15 + assert sqrt(a, rounding='d') == 3 + assert sqrt(a, rounding='n') == 3 + assert sqrt(a, rounding='u') > 3 + assert sqrt(b, rounding='d') < 3 + assert sqrt(b, rounding='n') == 3 + assert sqrt(b, rounding='u') == 3 + # A worst case, from the MPFR test suite + assert sqrt(mpf('7.0503726185518891')) == mpf('2.655253776675949') + +def test_float_sqrt(): + # These should round identically + for x in [0, 1e-7, 0.1, 0.5, 1, 2, 3, 4, 5, 0.333, 76.19]: + assert sqrt(mpf(x)) == float(x)**0.5 + assert sqrt(-1) == 1j + assert sqrt(-2).ae(cmath.sqrt(-2)) + assert sqrt(-3).ae(cmath.sqrt(-3)) + assert sqrt(-100).ae(cmath.sqrt(-100)) + assert sqrt(1j).ae(cmath.sqrt(1j)) + assert sqrt(-1j).ae(cmath.sqrt(-1j)) + assert sqrt(math.pi + math.e*1j).ae(cmath.sqrt(math.pi + math.e*1j)) + assert sqrt(math.pi - math.e*1j).ae(cmath.sqrt(math.pi - math.e*1j)) + mp2 = mp.clone() + mp2.trap_complex = True + pytest.raises(ComplexResult, lambda: mp2.sqrt(-1)) + pytest.raises(ComplexResult, lambda: mp2.mpf(-1)**0.5) + pytest.raises(ComplexResult, lambda: mp2.mpf(-1)**mp2.mpf(0.5)) + +def test_sqrt_special(): + assert sqrt(mpc(+inf, +inf)) == mpc(inf, +inf) + assert sqrt(mpc(-inf, +inf)) == mpc(inf, +inf) + assert sqrt(mpc( nan, +inf)) == mpc(inf, +inf) + assert sqrt(mpc(+inf, -inf)) == mpc(inf, -inf) + assert sqrt(mpc(-inf, -inf)) == mpc(inf, -inf) + assert sqrt(mpc( nan, -inf)) == mpc(inf, -inf) + +def test_hypot(): + assert hypot(0, 0) == 0 + assert hypot(0, 0.33) == mpf(0.33) + assert hypot(0.33, 0) == mpf(0.33) + assert hypot(-0.33, 0) == mpf(0.33) + assert hypot(3, 4) == mpf(5) + # issue 1011 + assert hypot(1.0000044432326138, + 1.0068578402095993) == mpf('1.4190742041473763') + +def test_exact_cbrt(): + for i in range(0, 20000, 200): + assert cbrt(mpf(i*i*i)) == i + random.seed(1) + for prec in [100, 300, 1000, 10000]: + mp.dps = prec + A = random.randint(10**(prec//2-2), 10**(prec//2-1)) + assert cbrt(mpf(A*A*A)) == A + +def test_exp(): + assert exp(0) == 1 + assert exp(10000).ae(mpf('8.8068182256629215873e4342')) + assert exp(-10000).ae(mpf('1.1354838653147360985e-4343')) + a = exp(mpf((1, MPZ(8198646019315405), -53, 53))) + assert a.bc == a.man.bit_length() + mp.prec = 67 + a = exp(mpf((1, MPZ(1781864658064754565), -60, 61))) + assert a.bc == a.man.bit_length() + mp.prec = 53 + assert exp(ln2 * 10).ae(1024) + assert exp(2+2j).ae(cmath.exp(2+2j)) + +def test_issue_73(): + mp.dps = 512 + a = exp(-1) + b = exp(1) + mp.dps = 15 + assert (+a).ae(0.36787944117144233) + assert (+b).ae(2.7182818284590451) + +def test_log(): + assert log(1) == 0 + for x in [0.5, 1.5, 2.0, 3.0, 100, 10**50, 1e-50]: + assert log(x).ae(math.log(x)) + assert log(x, x) == 1 + assert log(1024, 2) == 10 + assert log(10**1234, 10) == 1234 + assert log(2+2j).ae(cmath.log(2+2j)) + # Accuracy near 1 + assert (log(0.6+0.8j).real*10**17).ae(2.2204460492503131) + assert (log(0.6-0.8j).real*10**17).ae(2.2204460492503131) + assert (log(0.8-0.6j).real*10**17).ae(2.2204460492503131) + assert (log(1+1e-8j).real*10**16).ae(0.5) + assert (log(1-1e-8j).real*10**16).ae(0.5) + assert (log(-1+1e-8j).real*10**16).ae(0.5) + assert (log(-1-1e-8j).real*10**16).ae(0.5) + assert (log(1j+1e-8).real*10**16).ae(0.5) + assert (log(1j-1e-8).real*10**16).ae(0.5) + assert (log(-1j+1e-8).real*10**16).ae(0.5) + assert (log(-1j-1e-8).real*10**16).ae(0.5) + assert (log(1+1e-40j).real*10**80).ae(0.5) + assert (log(1j+1e-40).real*10**80).ae(0.5) + # Taylor series + assert log(0.99999).ae(-1.0000050000287824e-5) + assert log(1.00001).ae(9.9999500003988414e-6) + + # Huge + assert log(ldexp(1.234,10**20)).ae(log(2)*1e20) + assert log(ldexp(1.234,10**200)).ae(log(2)*1e200) + # Some special values + assert log(mpc(0,0)) == mpc(-inf,0) + assert isnan(log(mpc(nan,0)).real) + assert isnan(log(mpc(nan,0)).imag) + assert isnan(log(mpc(0,nan)).real) + assert isnan(log(mpc(0,nan)).imag) + assert isnan(log(mpc(nan,1)).real) + assert isnan(log(mpc(nan,1)).imag) + assert isnan(log(mpc(1,nan)).real) + assert isnan(log(mpc(1,nan)).imag) + + # issue 774 + assert log(mpc(+inf, +inf)) == log1p(mpc(+inf, +inf)) == mpc(inf, +pi/4) + assert log(mpc(+inf, -inf)) == log1p(mpc(+inf, -inf)) == mpc(inf, -pi/4) + assert log(mpc(-inf, +inf)) == log1p(mpc(-inf, +inf)) == mpc(inf, +3*pi/4) + assert log(mpc(-inf, -inf)) == log1p(mpc(-inf, -inf)) == mpc(inf, -3*pi/4) + + +def test_trig_hyperb_basic(): + for x in (list(range(100)) + list(range(-100,0))): + t = x / 4.1 + assert cos(mpf(t)).ae(math.cos(t)) + assert sin(mpf(t)).ae(math.sin(t)) + assert tan(mpf(t)).ae(math.tan(t)) + assert cosh(mpf(t)).ae(math.cosh(t)) + assert sinh(mpf(t)).ae(math.sinh(t)) + assert tanh(mpf(t)).ae(math.tanh(t)) + assert sin(1+1j).ae(cmath.sin(1+1j)) + assert sin(-4-3.6j).ae(cmath.sin(-4-3.6j)) + assert cos(1+1j).ae(cmath.cos(1+1j)) + assert cos(-4-3.6j).ae(cmath.cos(-4-3.6j)) + +def test_degrees(): + assert cos(0*degree) == 1 + assert cos(90*degree).ae(0) + assert cos(180*degree).ae(-1) + assert cos(270*degree).ae(0) + assert cos(360*degree).ae(1) + assert sin(0*degree) == 0 + assert sin(90*degree).ae(1) + assert sin(180*degree).ae(0) + assert sin(270*degree).ae(-1) + assert sin(360*degree).ae(0) + +def random_complexes(N): + random.seed(1) + a = [] + for i in range(N): + x1 = random.uniform(-10, 10) + y1 = random.uniform(-10, 10) + x2 = random.uniform(-10, 10) + y2 = random.uniform(-10, 10) + z1 = complex(x1, y1) + z2 = complex(x2, y2) + a.append((z1, z2)) + return a + +def test_complex_powers(): + for dps in [15, 30, 100]: + # Check accuracy for complex square root + mp.dps = dps + a = mpc(1j)**0.5 + assert a.real == a.imag == mpf(2)**0.5 / 2 + mp.dps = 15 + random.seed(1) + for (z1, z2) in random_complexes(100): + assert (mpc(z1)**mpc(z2)).ae(z1**z2, 1e-12) + assert (e**(-pi*1j)).ae(-1) + mp.dps = 50 + assert (e**(-pi*1j)).ae(-1) + +def test_complex_sqrt_accuracy(): + def test_mpc_sqrt(lst): + for a, b in lst: + z = mpc(a + j*b) + assert mpc_ae(sqrt(z*z), z) + z = mpc(-a + j*b) + assert mpc_ae(sqrt(z*z), -z) + z = mpc(a - j*b) + assert mpc_ae(sqrt(z*z), z) + z = mpc(-a - j*b) + assert mpc_ae(sqrt(z*z), -z) + random.seed(2) + N = 10 + mp.dps = 30 + dps = mp.dps + test_mpc_sqrt([(random.uniform(0, 10),random.uniform(0, 10)) for i in range(N)]) + test_mpc_sqrt([(i + 0.1, (i + 0.2)*10**i) for i in range(N)]) + +def test_asin(): + pi4 = pi/4 + assert asin(mpc(+inf, +inf)) == mpc(+pi4, +inf) + assert asin(mpc(+inf, -inf)) == mpc(+pi4, -inf) + assert asin(mpc(-inf, +inf)) == mpc(-pi4, +inf) + assert asin(mpc(-inf, -inf)) == mpc(-pi4, -inf) + r = asin(mpc(+inf, nan)) + assert isnan(r.real) and r.imag == -inf + r = asin(mpc(-inf, nan)) + assert isnan(r.real) and r.imag == -inf + r = asin(mpc(nan, +inf)) + assert isnan(r.real) and r.imag == +inf + r = asin(mpc(nan, -inf)) + assert isnan(r.real) and r.imag == -inf + pi2 = pi/2 + assert asin(mpc(+inf, +1)) == mpc(pi2, +inf) + assert asin(mpc(+inf, -1)) == mpc(pi2, -inf) + assert asin(mpc(+inf, 0)) == mpc(pi2, -inf) + assert asin(mpc(-inf, +1)) == mpc(-pi2, +inf) + assert asin(mpc(-inf, -1)) == mpc(-pi2, -inf) + assert asin(mpc(-inf, 0)) == mpc(-pi2, inf) + assert asin(mpc(-2, 0)).ae(mpc(-pi2, -log(2 - sqrt(3)))) + assert asin(mpc(+2, 0)).ae(mpc(+pi2, -log(2 + sqrt(3)))) + assert asin(mpc(0.5, 0)).ae(pi/6) + + # issue 787 + assert asin(mpc(0, 1e-22)).ae(1e-22j) + mp.prec = 700 + assert asin(mpc(0, 1e-220)).ae(1e-220j) + mp.prec = 53 + +def test_acos(): + pi4 = pi/4 + assert acos(mpc(+inf, +inf)) == mpc(+pi4, -inf) + assert acos(mpc(+inf, -inf)) == mpc(+pi4, +inf) + assert acos(mpc(-inf, +inf)) == mpc(pi4*3, -inf) + assert acos(mpc(-inf, -inf)) == mpc(pi4*3, +inf) + r = acos(mpc(+inf, nan)) + assert isnan(r.real) and r.imag == inf + r = acos(mpc(-inf, nan)) + assert isnan(r.real) and r.imag == inf + r = acos(mpc(nan, +inf)) + assert isnan(r.real) and r.imag == -inf + r = acos(mpc(nan, -inf)) + assert isnan(r.real) and r.imag == +inf + pi2 = pi/2 + assert acos(mpc(+inf, +1)) == mpc(0.0, -inf) + assert acos(mpc(+inf, -1)) == mpc(0.0, +inf) + assert acos(mpc(+inf, 0)) == mpc(0.0, +inf) + assert acos(mpc(-inf, +1)) == mpc(pi, -inf) + assert acos(mpc(-inf, -1)) == mpc(pi, +inf) + assert acos(mpc(-inf, 0)) == mpc(pi, -inf) + assert acos(mpc(+1, +inf)) == mpc(pi2, -inf) + assert acos(mpc(-1, +inf)) == mpc(pi2, -inf) + assert acos(mpc(0, +inf)) == mpc(pi2, -inf) + assert acos(mpc(+1, -inf)) == mpc(pi2, +inf) + assert acos(mpc(-1, -inf)) == mpc(pi2, +inf) + assert acos(mpc(0, -inf)) == mpc(pi2, +inf) + assert acos(mpc(-2, 0)).ae(mpc(pi, log(2 - sqrt(3)))) + assert acos(mpc(+2, 0)).ae(mpc(0, log(2 + sqrt(3)))) + assert acos(mpc(0.5, 0)).ae(pi/3) + +def test_atan(): + assert atan(-2.3).ae(math.atan(-2.3)) + assert atan(1e-50) == 1e-50 + assert atan(1e50).ae(pi/2) + assert atan(-1e-50) == -1e-50 + assert atan(-1e50).ae(-pi/2) + assert atan(10**1000).ae(pi/2) + for dps in [25, 70, 100, 300, 1000]: + mp.dps = dps + assert (4*atan(1)).ae(pi) + mp.dps = 15 + pi2 = pi/2 + assert atan(mpc(inf,-1)).ae(pi2) + assert atan(mpc(inf,0)).ae(pi2) + assert atan(mpc(inf,1)).ae(pi2) + assert atan(mpc(1,inf)).ae(pi2) + assert atan(mpc(0,inf)).ae(pi2) + assert atan(mpc(-1,inf)).ae(-pi2) + assert atan(mpc(-inf,1)).ae(-pi2) + assert atan(mpc(-inf,0)).ae(-pi2) + assert atan(mpc(-inf,-1)).ae(-pi2) + assert atan(mpc(-1,-inf)).ae(-pi2) + assert atan(mpc(0,-inf)).ae(-pi2) + assert atan(mpc(1,-inf)).ae(pi2) + +def test_atan2(): + assert atan2(1,1).ae(pi/4) + assert atan2(1,-1).ae(3*pi/4) + assert atan2(-1,-1).ae(-3*pi/4) + assert atan2(-1,1).ae(-pi/4) + assert atan2(-1,0).ae(-pi/2) + assert atan2(1,0).ae(pi/2) + assert atan2(0,0) == 0 + assert atan2(inf,0).ae(pi/2) + assert atan2(-inf,0).ae(-pi/2) + assert atan2(inf,inf).ae(pi/4) + assert atan2(-inf,inf).ae(-pi/4) + assert atan2(inf,-inf).ae(3*pi/4) + assert atan2(-inf,-inf).ae(-3*pi/4) + assert isnan(atan2(3,nan)) + assert isnan(atan2(nan,3)) + assert isnan(atan2(0,nan)) + assert isnan(atan2(nan,0)) + assert atan2(0,inf) == 0 + assert atan2(0,-inf).ae(pi) + assert atan2(10,inf) == 0 + assert atan2(-10,inf) == 0 + assert atan2(-10,-inf).ae(-pi) + assert atan2(10,-inf).ae(pi) + assert atan2(inf,10).ae(pi/2) + assert atan2(inf,-10).ae(pi/2) + assert atan2(-inf,10).ae(-pi/2) + assert atan2(-inf,-10).ae(-pi/2) + +def test_areal_inverses(): + assert asin(mpf(0)) == 0 + assert asinh(mpf(0)) == 0 + assert acosh(mpf(1)) == 0 + assert isinstance(asin(mpf(0.5)), mpf) + assert isinstance(asin(mpf(2.0)), mpc) + assert isinstance(acos(mpf(0.5)), mpf) + assert isinstance(acos(mpf(2.0)), mpc) + assert isinstance(atanh(mpf(0.1)), mpf) + assert isinstance(atanh(mpf(1.1)), mpc) + + random.seed(1) + for i in range(50): + x = random.uniform(0, 1) + assert asin(mpf(x)).ae(math.asin(x)) + assert acos(mpf(x)).ae(math.acos(x)) + + x = random.uniform(-10, 10) + assert asinh(mpf(x)).ae(cmath.asinh(x).real) + assert isinstance(asinh(mpf(x)), mpf) + x = random.uniform(1, 10) + assert acosh(mpf(x)).ae(cmath.acosh(x).real) + assert isinstance(acosh(mpf(x)), mpf) + x = random.uniform(-10, 0.999) + assert isinstance(acosh(mpf(x)), mpc) + + x = random.uniform(-1, 1) + assert atanh(mpf(x)).ae(cmath.atanh(x).real) + assert isinstance(atanh(mpf(x)), mpf) + + dps = mp.dps + mp.dps = 300 + assert isinstance(asin(0.5), mpf) + mp.dps = 1000 + assert asin(1).ae(pi/2) + assert asin(-1).ae(-pi/2) + +def test_invhyperb_inaccuracy(): + assert (asinh(1e-5)*10**5).ae(0.99999999998333333) + assert (asinh(1e-10)*10**10).ae(1) + assert (asinh(1e-50)*10**50).ae(1) + assert (asinh(-1e-5)*10**5).ae(-0.99999999998333333) + assert (asinh(-1e-10)*10**10).ae(-1) + assert (asinh(-1e-50)*10**50).ae(-1) + assert asinh(10**20).ae(46.744849040440862) + assert asinh(-10**20).ae(-46.744849040440862) + assert (tanh(1e-10)*10**10).ae(1) + assert (tanh(-1e-10)*10**10).ae(-1) + assert (atanh(1e-10)*10**10).ae(1) + assert (atanh(-1e-10)*10**10).ae(-1) + +def test_complex_functions(): + for x in (list(range(10)) + list(range(-10,0))): + for y in (list(range(10)) + list(range(-10,0))): + z = complex(x, y)/4.3 + 0.01j + assert exp(mpc(z)).ae(cmath.exp(z)) + assert log(mpc(z)).ae(cmath.log(z)) + assert cos(mpc(z)).ae(cmath.cos(z)) + assert sin(mpc(z)).ae(cmath.sin(z)) + assert tan(mpc(z)).ae(cmath.tan(z)) + assert sinh(mpc(z)).ae(cmath.sinh(z)) + assert cosh(mpc(z)).ae(cmath.cosh(z)) + assert tanh(mpc(z)).ae(cmath.tanh(z)) + +def test_complex_inverse_functions(): + for (z1, z2) in random_complexes(30): + # apparently cmath uses a different branch, so we + # can't use it for comparison + assert sinh(asinh(z1)).ae(z1) + # + assert acosh(z1).ae(cmath.acosh(z1)) + assert atanh(z1).ae(cmath.atanh(z1)) + assert atan(z1).ae(cmath.atan(z1)) + # the reason we set a big eps here is that the cmath + # functions are inaccurate + assert asin(z1).ae(cmath.asin(z1), rel_eps=1e-12) + assert acos(z1).ae(cmath.acos(z1), rel_eps=1e-12) + one = mpf(1) + for i in range(-9, 10, 3): + for k in range(-9, 10, 3): + a = 0.9*j*10**k + 0.8*one*10**i + b = cos(acos(a)) + assert b.ae(a) + b = sin(asin(a)) + assert b.ae(a) + one = mpf(1) + err = 2*10**-15 + for i in range(-9, 9, 3): + for k in range(-9, 9, 3): + a = -0.9*10**k + j*0.8*one*10**i + b = cosh(acosh(a)) + assert b.ae(a, err) + b = sinh(asinh(a)) + assert b.ae(a, err) + +def test_reciprocal_functions(): + assert sec(3).ae(-1.01010866590799375) + assert csc(3).ae(7.08616739573718592) + assert cot(3).ae(-7.01525255143453347) + assert sech(3).ae(0.0993279274194332078) + assert csch(3).ae(0.0998215696688227329) + assert coth(3).ae(1.00496982331368917) + assert asec(3).ae(1.23095941734077468) + assert acsc(3).ae(0.339836909454121937) + assert acot(3).ae(0.321750554396642193) + assert acot(cmath.infj) == 0 + assert acot(cmath.inf) == 0 + assert asech(0.5).ae(1.31695789692481671) + assert acsch(3).ae(0.327450150237258443) + assert acoth(3).ae(0.346573590279972655) + assert acot(0).ae(1.5707963267948966192) + assert acoth(0).ae(1.5707963267948966192j) + +def test_ldexp(): + assert ldexp(mpf(2.5), 0) == 2.5 + assert ldexp(mpf(2.5), -1) == 1.25 + assert ldexp(mpf(2.5), 2) == 10 + assert ldexp(mpf('inf'), 3) == mpf('inf') + +def test_frexp(): + assert frexp(0) == (0.0, 0) + assert frexp(9) == (0.5625, 4) + assert frexp(1) == (0.5, 1) + assert frexp(0.2) == (0.8, -2) + assert frexp(1000) == (0.9765625, 10) + assert frexp(inf) == (inf, 0) + assert frexp(-inf) == (-inf, 0) + r = frexp(nan) + assert isnan(r[0]) and r[1] == 0 + +def test_aliases(): + assert ln(7) == log(7) + assert log10(3.75) == log(3.75,10) + assert log2(1.25) == log(1.25,2) + assert exp2(-0.5) == power(2, -0.5) + assert degrees(5.6) == 5.6 / degree + assert radians(5.6) == 5.6 * degree + assert power(-1,0.5) == j + assert fmod(25,7) == 4.0 and isinstance(fmod(25,7), mpf) + +def test_arg_sign(): + assert arg(3) == 0 + assert arg(-3).ae(pi) + assert arg(j).ae(pi/2) + assert arg(-j).ae(-pi/2) + assert arg(0) == 0 + assert isnan(atan2(3,nan)) + assert isnan(atan2(nan,3)) + assert isnan(atan2(0,nan)) + assert isnan(atan2(nan,0)) + assert isnan(atan2(nan,nan)) + assert arg(inf) == 0 + assert arg(-inf).ae(pi) + assert isnan(arg(nan)) + #assert arg(inf*j).ae(pi/2) + assert sign(0) == 0 + assert sign(3) == 1 + assert sign(-3) == -1 + assert sign(inf) == 1 + assert sign(-inf) == -1 + assert isnan(sign(nan)) + assert sign(j) == j + assert sign(-3*j) == -j + assert sign(1+j).ae((1+j)/sqrt(2)) + +def test_misc_bugs(): + # test that this doesn't raise an exception + mp.dps = 1000 + log(1302) + +def test_arange(): + assert arange(10) == [mpf('0.0'), mpf('1.0'), mpf('2.0'), mpf('3.0'), + mpf('4.0'), mpf('5.0'), mpf('6.0'), mpf('7.0'), + mpf('8.0'), mpf('9.0')] + assert arange(-5, 5) == [mpf('-5.0'), mpf('-4.0'), mpf('-3.0'), + mpf('-2.0'), mpf('-1.0'), mpf('0.0'), + mpf('1.0'), mpf('2.0'), mpf('3.0'), mpf('4.0')] + assert arange(0, 1, 0.1) == [mpf('0.0'), mpf('0.10000000000000001'), + mpf('0.20000000000000001'), + mpf('0.30000000000000004'), + mpf('0.40000000000000002'), + mpf('0.5'), mpf('0.60000000000000009'), + mpf('0.70000000000000007'), + mpf('0.80000000000000004'), + mpf('0.90000000000000002')] + assert arange(17, -9, -3) == [mpf('17.0'), mpf('14.0'), mpf('11.0'), + mpf('8.0'), mpf('5.0'), mpf('2.0'), + mpf('-1.0'), mpf('-4.0'), mpf('-7.0')] + assert arange(0.2, 0.1, -0.1) == [mpf('0.20000000000000001')] + assert arange(0) == [] + assert arange(1000, -1) == [] + assert arange(-1.23, 3.21, -0.0000001) == [] + +def test_linspace(): + assert linspace(2, 9, 7) == [mpf('2.0'), mpf('3.166666666666667'), + mpf('4.3333333333333339'), mpf('5.5'), mpf('6.666666666666667'), + mpf('7.8333333333333339'), mpf('9.0')] + assert linspace(2, 9, 7, endpoint=0) == [mpf('2.0'), mpf('3.0'), mpf('4.0'), + mpf('5.0'), mpf('6.0'), mpf('7.0'), mpf('8.0')] + assert linspace(2, 7, 1) == [mpf(2)] + +def test_float_cbrt(): + mp.dps = 30 + for a in arange(0,10,0.1): + assert cbrt(a*a*a).ae(a, eps) + assert cbrt(-1).ae(0.5 + j*sqrt(3)/2) + one_third = mpf(1)/3 + for a in arange(0,10,2.7) + [0.1 + 10**5]: + a = mpc(a + 1.1j) + r1 = cbrt(a) + mp.dps += 10 + r2 = pow(a, one_third) + mp.dps -= 10 + assert r1.ae(r2, eps) + mp.dps = 100 + for n in range(100, 301, 100): + w = 10**n + j*10**-3 + z = w*w*w + r = cbrt(z) + assert mpc_ae(r, w, eps) + +def test_root(): + mp.dps = 30 + random.seed(1) + a = random.randint(0, 10000) + p = a*a*a + r = nthroot(mpf(p), 3) + assert r == a + for n in range(4, 10): + p = p*a + assert nthroot(mpf(p), n) == a + mp.dps = 40 + for n in range(10, 5000, 100): + for a in [random.random()*10000, random.random()*10**100]: + r = nthroot(a, n) + r1 = pow(a, mpf(1)/n) + assert r.ae(r1) + r = nthroot(a, -n) + r1 = pow(a, -mpf(1)/n) + assert r.ae(r1) + # tests for nthroot rounding + for rnd in ['n', 'u', 'd']: + mp.rounding = rnd + for n in [-5, -3, 3, 5]: + prec = 50 + for i in range(10): + mp.prec = prec + a = rand() + mp.prec = 2*prec + b = a**n + mp.prec = prec + r = nthroot(b, n) + assert r == a + mp.rounding = 'n' + mp.dps = 30 + for n in range(3, 21): + a = (random.random() + j*random.random()) + assert nthroot(a, n).ae(pow(a, mpf(1)/n)) + assert mpc_ae(nthroot(a, n), pow(a, mpf(1)/n)) + a = (random.random()*10**100 + j*random.random()) + r = nthroot(a, n) + mp.dps += 4 + r1 = pow(a, mpf(1)/n) + mp.dps -= 4 + assert r.ae(r1) + assert mpc_ae(r, r1, eps) + r = nthroot(a, -n) + mp.dps += 4 + r1 = pow(a, -mpf(1)/n) + mp.dps -= 4 + assert r.ae(r1) + assert mpc_ae(r, r1, eps) + mp.dps = 15 + assert nthroot(4, 1) == 4 + assert nthroot(4, 0) == 1 + assert nthroot(4, -1) == 0.25 + assert nthroot(inf, 1) == inf + assert nthroot(inf, 2) == inf + assert nthroot(inf, 3) == inf + assert nthroot(inf, -1) == 0 + assert nthroot(inf, -2) == 0 + assert nthroot(inf, -3) == 0 + assert nthroot(j, 1) == j + assert nthroot(j, 0) == 1 + assert nthroot(j, -1) == -j + assert nthroot(j, 22).ae(cos(pi/44) + sin(pi/44)*1j) + assert isnan(nthroot(nan, 1)) + assert isnan(nthroot(nan, 0)) + assert isnan(nthroot(nan, -1)) + assert isnan(nthroot(inf, 0)) + assert root(2,3) == nthroot(2,3) + assert root(16,4,0) == 2 + assert root(16,4,1) == 2j + assert root(16,4,2) == -2 + assert root(16,4,3) == -2j + assert root(16,4,4) == 2 + assert root(-125,3,1) == -5 + +def test_issue_136(): + for dps in [20, 80]: + mp.dps = dps + r = nthroot(mpf('-1e-20'), 4) + assert r.ae(mpf(10)**(-5) * (1 + j) * mpf(2)**(-0.5)) + mp.dps = 80 + assert nthroot('-1e-3', 4).ae(mpf(10)**(-3./4) * (1 + j)/sqrt(2)) + assert nthroot('-1e-6', 4).ae((1 + j)/(10 * sqrt(20))) + # Check that this doesn't take eternity to compute + mp.dps = 20 + assert nthroot('-1e100000000', 4).ae((1+j)*mpf('1e25000000')/sqrt(2)) + +def test_mpcfun_real_imag(): + x = mpf(0.3) + y = mpf(0.4) + assert exp(mpc(x,0)) == exp(x) + assert exp(mpc(0,y)) == mpc(cos(y),sin(y)) + assert cos(mpc(x,0)) == cos(x) + assert sin(mpc(x,0)) == sin(x) + assert cos(mpc(0,y)) == cosh(y) + assert sin(mpc(0,y)) == mpc(0,sinh(y)) + assert cospi(mpc(x,0)) == cospi(x) + assert sinpi(mpc(x,0)) == sinpi(x) + assert cospi(mpc(0,y)).ae(cosh(pi*y)) + assert sinpi(mpc(0,y)).ae(mpc(0,sinh(pi*y))) + c, s = cospi_sinpi(mpc(x,0)) + assert c == cospi(x) + assert s == sinpi(x) + c, s = cospi_sinpi(mpc(0,y)) + assert c.ae(cosh(pi*y)) + assert s.ae(mpc(0,sinh(pi*y))) + c, s = cos_sin(mpc(x,0)) + assert c == cos(x) + assert s == sin(x) + c, s = cos_sin(mpc(0,y)) + assert c == cosh(y) + assert s == mpc(0,sinh(y)) + +def test_perturbation_rounding(): + mp.dps = 100 + a = pi/10**50 + b = -pi/10**50 + c = 1 + a + d = 1 + b + mp.dps = 15 + assert exp(a) == 1 + assert exp(a, rounding='c') > 1 + assert exp(b, rounding='c') == 1 + assert exp(a, rounding='f') == 1 + assert exp(b, rounding='f') < 1 + assert cos(a) == 1 + assert cos(a, rounding='c') == 1 + assert cos(b, rounding='c') == 1 + assert cos(a, rounding='f') < 1 + assert cos(b, rounding='f') < 1 + for f in [sin, atan, asinh, tanh]: + assert f(a) == +a + assert f(a, rounding='c') > a + assert f(a, rounding='f') < a + assert f(b) == +b + assert f(b, rounding='c') > b + assert f(b, rounding='f') < b + for f in [asin, tan, sinh, atanh]: + assert f(a) == +a + assert f(b) == +b + assert f(a, rounding='c') > a + assert f(b, rounding='c') > b + assert f(a, rounding='f') < a + assert f(b, rounding='f') < b + assert ln(c) == +a + assert ln(d) == +b + assert ln(c, rounding='c') > a + assert ln(c, rounding='f') < a + assert ln(d, rounding='c') > b + assert ln(d, rounding='f') < b + assert cosh(a) == 1 + assert cosh(b) == 1 + assert cosh(a, rounding='c') > 1 + assert cosh(b, rounding='c') > 1 + assert cosh(a, rounding='f') == 1 + assert cosh(b, rounding='f') == 1 + +def test_integer_parts(): + assert floor(3.2) == 3 + assert ceil(3.2) == 4 + assert floor(3.2+5j) == 3+5j + assert ceil(3.2+5j) == 4+5j + +def test_complex_parts(): + assert fabs('3') == 3 + assert fabs(3+4j) == 5 + assert re(3) == 3 + assert re(1+4j) == 1 + assert im(3) == 0 + assert im(1+4j) == 4 + assert conj(3) == 3 + assert conj(3+4j) == 3-4j + assert mpf(3).conjugate() == 3 + +def test_cospi_sinpi(): + assert sinpi(0) == 0 + assert sinpi(0.5) == 1 + assert sinpi(1) == 0 + assert sinpi(1.5) == -1 + assert sinpi(2) == 0 + assert sinpi(2.5) == 1 + assert sinpi(-0.5) == -1 + assert cospi(0) == 1 + assert cospi(0.5) == 0 + assert cospi(1) == -1 + assert cospi(1.5) == 0 + assert cospi(2) == 1 + assert cospi(2.5) == 0 + assert cospi(-0.5) == 0 + assert cospi(100000000000.25).ae(sqrt(2)/2) + a = cospi(2+3j) + assert a.real.ae(cos((2+3j)*pi).real) + assert a.imag == 0 + b = sinpi(2+3j) + assert b.imag.ae(sin((2+3j)*pi).imag) + assert b.real == 0 + mp.dps = 35 + x1 = mpf(10000) - mpf('1e-15') + x2 = mpf(10000) + mpf('1e-15') + x3 = mpf(10000.5) - mpf('1e-15') + x4 = mpf(10000.5) + mpf('1e-15') + x5 = mpf(10001) - mpf('1e-15') + x6 = mpf(10001) + mpf('1e-15') + x7 = mpf(10001.5) - mpf('1e-15') + x8 = mpf(10001.5) + mpf('1e-15') + mp.dps = 15 + M = 10**15 + assert (sinpi(x1)*M).ae(-pi) + assert (sinpi(x2)*M).ae(pi) + assert (cospi(x3)*M).ae(pi) + assert (cospi(x4)*M).ae(-pi) + assert (sinpi(x5)*M).ae(pi) + assert (sinpi(x6)*M).ae(-pi) + assert (cospi(x7)*M).ae(-pi) + assert (cospi(x8)*M).ae(pi) + assert 0.999 < cospi(x1, rounding='d') < 1 + assert 0.999 < cospi(x2, rounding='d') < 1 + assert 0.999 < sinpi(x3, rounding='d') < 1 + assert 0.999 < sinpi(x4, rounding='d') < 1 + assert -1 < cospi(x5, rounding='d') < -0.999 + assert -1 < cospi(x6, rounding='d') < -0.999 + assert -1 < sinpi(x7, rounding='d') < -0.999 + assert -1 < sinpi(x8, rounding='d') < -0.999 + assert (sinpi(1e-15)*M).ae(pi) + assert (sinpi(-1e-15)*M).ae(-pi) + assert cospi(1e-15) == 1 + assert cospi(1e-15, rounding='d') < 1 + +def test_expj(): + assert expj(0) == 1 + assert expj(1).ae(exp(j)) + assert expj(j).ae(exp(-1)) + assert expj(1+j).ae(exp(j*(1+j))) + assert expjpi(0) == 1 + assert expjpi(1).ae(exp(j*pi)) + assert expjpi(j).ae(exp(-pi)) + assert expjpi(1+j).ae(exp(j*pi*(1+j))) + assert expjpi(-10**15 * j).ae('2.22579818340535731e+1364376353841841') + assert expjpi(cmath.infj) == 0 + +def test_sinc(): + assert sinc(0) == sincpi(0) == 1 + assert sinc(inf) == sincpi(inf) == 0 + assert sinc(-inf) == sincpi(-inf) == 0 + assert sinc(2).ae(0.45464871341284084770) + assert sinc(2+3j).ae(0.4463290318402435457-2.7539470277436474940j) + assert sincpi(2) == 0 + assert sincpi(1.5).ae(-0.212206590789193781) + +def test_fibonacci(): + assert [fibonacci(n) for n in range(-5, 10)] == \ + [5, -3, 2, -1, 1, 0, 1, 1, 2, 3, 5, 8, 13, 21, 34] + assert fib(2.5).ae(1.4893065462657091) + assert fib(3+4j).ae(-5248.51130728372 - 14195.962288353j) + assert fib(1000).ae(4.3466557686937455e+208) + assert str(fib(10**100)) == '6.24499112864607e+2089876402499787337692720892375554168224592399182109535392875613974104853496745963277658556235103534' + mp.dps = 2100 + a = fib(10000) + assert a % 10**10 == 9947366875 + mp.dps = 15 + assert fibonacci(inf) == inf + assert fib(3+0j) == 2 + +def test_call_with_dps(): + assert abs(exp(1, dps=30)-e(dps=35)) < 1e-29 + +def test_tanh(): + assert tanh(0) == 0 + assert tanh(inf) == 1 + assert tanh(-inf) == -1 + assert isnan(tanh(nan)) + assert tanh(mpc('inf', '0')) == 1 + + assert tanh(mpc(+inf, +inf)) == mpc(+1, 0) + assert tanh(mpc(+inf, -inf)) == mpc(+1, 0) + assert tanh(mpc(-inf, +inf)) == mpc(-1, 0) + assert tanh(mpc(-inf, -inf)) == mpc(-1, 0) + assert tanh(mpc(+inf, 2)) == mpc(+1, 0) + assert tanh(mpc(-inf, 2)) == mpc(-1, 0) + r = tanh(mpc(0, -inf)) + assert r.real == 0 and isnan(r.imag) + r = tanh(mpc(2, -inf)) + assert isnan(r.real) and isnan(r.imag) + assert tanh(mpc(+inf, nan)) == mpc(+1, 0) + assert tanh(mpc(-inf, nan)) == mpc(-1, 0) + +def test_tan(): + assert tan(mpc(+inf, +inf)) == mpc(0, +1) + assert tan(mpc(+inf, -inf)) == mpc(0, -1) + assert tan(mpc(-inf, +inf)) == mpc(0, +1) + assert tan(mpc(-inf, -inf)) == mpc(0, -1) + assert tan(mpc(2, +inf)) == mpc(0, +1) + assert tan(mpc(2, -inf)) == mpc(0, -1) + r = tan(mpc(-inf, 0)) + assert isnan(r.real) and r.imag == 0 + r = tan(mpc(-inf, 2)) + assert isnan(r.real) and isnan(r.imag) + assert tan(mpc(nan, +inf)) == mpc(0, +1) + assert tan(mpc(nan, -inf)) == mpc(0, -1) + +def test_atanh(): + assert atanh(0) == 0 + assert atanh(0.5).ae(0.54930614433405484570) + assert atanh(-0.5).ae(-0.54930614433405484570) + assert atanh(1) == inf + assert atanh(-1) == -inf + assert isnan(atanh(nan)) + assert isinstance(atanh(1), mpf) + assert isinstance(atanh(-1), mpf) + # Limits at infinity + jpi2 = j*pi/2 + assert atanh(inf).ae(-jpi2) + assert atanh(-inf).ae(jpi2) + assert atanh(mpc(inf,-1)).ae(-jpi2) + assert atanh(mpc(inf,0)).ae(-jpi2) + assert atanh(mpc(inf,1)).ae(jpi2) + assert atanh(mpc(1,inf)).ae(jpi2) + assert atanh(mpc(0,inf)).ae(jpi2) + assert atanh(mpc(-1,inf)).ae(jpi2) + assert atanh(mpc(-inf,1)).ae(jpi2) + assert atanh(mpc(-inf,0)).ae(jpi2) + assert atanh(mpc(-inf,-1)).ae(-jpi2) + assert atanh(mpc(-1,-inf)).ae(-jpi2) + assert atanh(mpc(0,-inf)).ae(-jpi2) + assert atanh(mpc(1,-inf)).ae(-jpi2) + +def test_expm1(): + assert expm1(0) == 0 + assert expm1(3).ae(exp(3)-1) + assert expm1(inf) == inf + assert expm1(1e-50).ae(1e-50) + assert (expm1(1e-10)*1e10).ae(1.00000000005) + +def test_log1p(): + assert log1p(0) == 0 + assert log1p(3).ae(log(1+3)) + assert log1p(inf) == inf + assert log1p(1e-50).ae(1e-50) + assert (log1p(1e-10)*1e10).ae(0.99999999995) + # issue 790 + assert log1p(1.8370676479640493e-39-4.6885882517313053e-20j).ae(2.93621063767769e-39-4.6885882517313053e-20j, 0) + assert log1p(-2.0476815825463086e-80-2.0235857941734692e-40j).ae(-2.3184935597344513e-84-2.0235857941734692e-40j, 0) + assert log1p(-6.4922176418510124e-21+1.1394926627101214e-10j).ae(-1.4201199664289643e-37+1.1394926627101214e-10j, 0) + assert log1p(-1.430796815051627e-72+1.691624553529315e-36j).ae(4.6709293580298264e-91+1.691624553529315e-36j, 0) + assert log1p(-3.1061140011623543e-21+7.881768838480807e-11j).ae(4.3173401185459216e-38+7.881768838480807e-11j, 0) + assert log1p(-1.999999873062092e-40+1.999999936531045e-20j).ae(1.9999997461241924e-80+1.999999936531045e-20j) + # issue 853 + mp.dps = 25 + r = log1p(6e-30) + assert type(r) is type(r.real) + r = mp.log1p(7e-30) + assert type(r) is type(r.real) + r = mp.log1p(0.1 + 0j) + assert type(r) is not type(r.real) + r = mp.log1p(1e-30 + 0j) + assert type(r) is not type(r.real) + +def test_powm1(): + assert powm1(2,3) == 7 + assert powm1(-1,2) == 0 + assert powm1(-1,0) == 0 + assert powm1(-2,0) == 0 + assert powm1(3+4j,0) == 0 + assert powm1(0,1) == -1 + assert powm1(0,0) == 0 + assert powm1(1,0) == 0 + assert powm1(1,2) == 0 + assert powm1(1,3+4j) == 0 + assert powm1(1,5) == 0 + assert powm1(j,4) == 0 + assert powm1(-j,4) == 0 + assert (powm1(2,1e-100)*1e100).ae(ln2) + assert powm1(2,'1e-100000000000') != 0 + assert (powm1(fadd(1,1e-100,exact=True), 5)*1e100).ae(5) + +def test_unitroots(): + assert unitroots(1) == [1] + assert unitroots(2) == [1, -1] + a, b, c = unitroots(3) + assert a == 1 + assert b.ae(-0.5 + 0.86602540378443864676j) + assert c.ae(-0.5 - 0.86602540378443864676j) + assert unitroots(1, primitive=True) == [1] + assert unitroots(2, primitive=True) == [-1] + assert unitroots(3, primitive=True) == unitroots(3)[1:] + assert unitroots(4, primitive=True) == [j, -j] + assert len(unitroots(17, primitive=True)) == 16 + assert len(unitroots(16, primitive=True)) == 8 + +def test_cyclotomic(): + assert [cyclotomic(n,1) for n in range(31)] == [1,0,2,3,2,5,1,7,2,3,1,11,1,13,1,1,2,17,1,19,1,1,1,23,1,5,1,3,1,29,1] + assert [cyclotomic(n,-1) for n in range(31)] == [1,-2,0,1,2,1,3,1,2,1,5,1,1,1,7,1,2,1,3,1,1,1,11,1,1,1,13,1,1,1,1] + assert [cyclotomic(n,j) for n in range(21)] == [1,-1+j,1+j,j,0,1,-j,j,2,-j,1,j,3,1,-j,1,2,1,j,j,5] + assert [cyclotomic(n,-j) for n in range(21)] == [1,-1-j,1-j,-j,0,1,j,-j,2,j,1,-j,3,1,j,1,2,1,-j,-j,5] + assert cyclotomic(1624,j) == 1 + assert cyclotomic(33600,j) == 1 + u = sqrt(j, prec=500) + assert cyclotomic(8, u).ae(0) + assert cyclotomic(30, u).ae(5.8284271247461900976) + assert cyclotomic(2040, u).ae(1) + assert cyclotomic(0,2.5) == 1 + assert cyclotomic(1,2.5) == 2.5-1 + assert cyclotomic(2,2.5) == 2.5+1 + assert cyclotomic(3,2.5) == 2.5**2 + 2.5 + 1 + assert cyclotomic(7,2.5) == 406.234375 + +def test_mp_nan_in_args(): + assert mp.isnan(mp.legendre(1.2, mp.nan)) # issue 485 + assert mp.isnan(mp.hyp2f1(0.5, 0.5, 0.5, mp.nan)) + assert mp.isnan(mp.hyp2f1(0.5, 2.2, 0.5, mp.nan)) + assert mp.isnan(mp.hyp2f1(0.4, 2.2, 0.5, mp.nan)) # issue 479 + assert mp.isnan(mp.chebyt(2.3, mp.nan)) # issue 478 + assert mp.isnan(mp.chebyt(13, mp.nan)) + assert mp.isnan(mp.chebyt(17, mp.nan)) + assert mp.isnan(mp.hyp1f1(0, 1, mp.nan)) # issue 507 + assert mp.isnan(mp.hyp1f1(1, 1, mp.nan)) + assert mp.isnan(mp.hyp1f1(1, 1.1, mp.nan)) + assert mp.isnan(mp.hyp1f1(1, 2, mp.nan)) + assert mp.isnan(mp.hyp1f1(1, 3, mp.nan)) + assert mp.isnan(mp.hyp1f1(1, 4, mp.nan)) + assert mp.isnan(mp.hyp1f1(2, 1, mp.nan)) + assert mp.isnan(mp.hyp1f1(2, 2, mp.nan)) + assert mp.isnan(mp.hyp1f1(0, 2, mp.nan)) + assert mp.isnan(mp.hyp1f1(0, 4, mp.nan)) + assert mp.isnan(mp.hyp0f1(2.5, mp.nan)) # issue 489 + assert mp.isnan(mp.hyp0f1(25, mp.nan)) + assert mp.isnan(mp.hyp0f1(2513, mp.nan)) + assert mp.isnan(mp.hyp0f1(.25, mp.nan)) + assert mp.isnan(mp.hyp1f1(2.5,2.2, mp.nan)) # issue 488 + assert mp.isnan(mp.hyp1f1(1,2.2, mp.nan)) + assert mp.isnan(mp.hyp1f1(1,2002.2, mp.nan)) + assert mp.isnan(mp.hyp2f2(0.4, 2.5, 2.2, 0.7, mp.nan)) # issue 509 + assert mp.isnan(mp.gegenbauer(0, 2.5, mp.nan)) # issue 508 + assert mp.isnan(mp.gegenbauer(1, 2.5, mp.nan)) + assert mp.isnan(mp.gegenbauer(2, 2.5, mp.nan)) + assert mp.isnan(mp.gegenbauer(2, 5, mp.nan)) + assert mp.isnan(mp.laguerre(0, 2.5, mp.nan)) # issue 506 + assert mp.isnan(mp.laguerre(1, 2.5, mp.nan)) + assert mp.isnan(mp.laguerre(1, 2.5345, mp.nan)) + assert mp.isnan(mp.laguerre(2, 2, mp.nan)) + assert mp.isnan(mp.laguerre(2, 5, mp.nan)) + +def test_issue_749(): + assert mp.asinh(mp.inf) == mp.inf + assert mp.asinh(mp.mpc(mp.inf, 0)) == mp.mpc(mp.inf, 0) + assert fp.asinh(fp.mpc(fp.inf, 0)) == fp.mpc(fp.inf, 0) + +def test_issue_1035(): + assert mp.acos(1e-50j).ae(1.5707963267948966) + +def test_wrap_libmp_api(): + assert sin(1) != sin(1, prec=1000) + assert sin(1) != sin(1, dps=100) + assert sin(1, rounding='d') < sin(1, rounding='u') + pytest.raises(ValueError, lambda: sin(1, prec=123, dps=321)) + pytest.raises(TypeError, lambda: sin(1, 2)) diff --git a/mpmath/tests/test_functions2.py b/mpmath/tests/test_functions2.py new file mode 100644 index 0000000..57fe9aa --- /dev/null +++ b/mpmath/tests/test_functions2.py @@ -0,0 +1,2549 @@ +import platform +import sys + +import pytest + +from mpmath import (agm, airyai, airybi, appellf1, bei, ber, besseli, besselj, + besseljzero, besselk, bessely, besselyzero, betainc, + chebyt, chebyu, chi, ci, clsin, convert, coulombg, e, e1, + ei, ellipe, ellipk, eps, erf, erfc, erfi, erfinv, exp, + expint, extradps, fadd, fmul, foxh, fp, fraction, fresnelc, + fresnels, fsub, fsum, gamma, gammainc, gegenbauer, hankel1, + hankel2, hermite, hyp0f1, hyp1f1, hyp1f2, hyp2f0, hyp2f1, + hyp2f2, hyp2f3, hyper, hypercomb, hyperu, inf, isnan, j, + j0, j1, jacobi, kei, ker, laguerre, lambertw, ldexp, + legendre, legenp, legenq, lerchphi, li, log, lower_gamma, + meijerg, mp, mpc, mpf, nan, ncdf, npdf, nthroot, pi, + polylog, qp, quadts, shi, si, spherharm, spherical_in, + spherical_jn, spherical_kn, spherical_yn, sqrt, struveh, + struvel, upper_gamma, whitm, whitw, zeta) +from mpmath.libmp import BACKEND, NoConvergence + + +def test_bessel(): + assert j0(1).ae(0.765197686557966551) + assert j0(pi).ae(-0.304242177644093864) + assert j0(1000).ae(0.0247866861524201746) + assert j0(-25).ae(0.0962667832759581162) + assert j1(1).ae(0.440050585744933516) + assert j1(pi).ae(0.284615343179752757) + assert j1(1000).ae(0.00472831190708952392) + assert j1(-25).ae(0.125350249580289905) + assert besselj(5,1).ae(0.000249757730211234431) + assert besselj(5+0j,1).ae(0.000249757730211234431) + assert besselj(5,pi).ae(0.0521411843671184747) + assert besselj(5,1000).ae(0.00502540694523318607) + assert besselj(5,-25).ae(0.0660079953984229934) + assert besselj(-3,2).ae(-0.128943249474402051) + assert besselj(-4,2).ae(0.0339957198075684341) + assert besselj(3,3+2j).ae(0.424718794929639595942 + 0.625665327745785804812j) + assert besselj(0.25,4).ae(-0.374760630804249715) + assert besselj(1+2j,3+4j).ae(0.319247428741872131 - 0.669557748880365678j) + assert (besselj(3, 10**10) * 10**5).ae(0.76765081748139204023) + assert bessely(-0.5, 0) == 0 + assert bessely(0.5, 0) == -inf + assert bessely(1.5, 0) == -inf + assert bessely(0,0) == -inf + assert bessely(-0.4, 0) == -inf + assert bessely(-0.6, 0) == inf + assert bessely(-1, 0) == inf + assert bessely(-1.4, 0) == inf + assert bessely(-1.6, 0) == -inf + assert bessely(-1, 0) == inf + assert bessely(-2, 0) == -inf + assert bessely(-3, 0) == inf + assert bessely(0.5, 0) == -inf + assert bessely(1, 0) == -inf + assert bessely(1.5, 0) == -inf + assert bessely(2, 0) == -inf + assert bessely(2.5, 0) == -inf + assert bessely(3, 0) == -inf + assert bessely(0,0.5).ae(-0.44451873350670655715) + assert bessely(1,0.5).ae(-1.4714723926702430692) + assert bessely(-1,0.5).ae(1.4714723926702430692) + assert bessely(3.5,0.5).ae(-138.86400867242488443) + assert bessely(0,3+4j).ae(4.6047596915010138655-8.8110771408232264208j) + assert bessely(0,j).ae(-0.26803248203398854876+1.26606587775200833560j) + assert (bessely(3, 10**10) * 10**5).ae(0.21755917537013204058) + assert besseli(0,0) == 1 + assert besseli(1,0) == 0 + assert besseli(2,0) == 0 + assert besseli(-1,0) == 0 + assert besseli(-2,0) == 0 + assert besseli(0,0.5).ae(1.0634833707413235193) + assert besseli(1,0.5).ae(0.25789430539089631636) + assert besseli(-1,0.5).ae(0.25789430539089631636) + assert besseli(3.5,0.5).ae(0.00068103597085793815863) + assert besseli(0,3+4j).ae(-3.3924877882755196097-1.3239458916287264815j) + assert besseli(0,j).ae(besselj(0,1)) + assert (besseli(3, 10**10) * mpf(10)**(-4342944813)).ae(4.2996028505491271875) + assert besselk(0,0) == inf + assert besselk(1,0) == inf + assert besselk(2,0) == inf + assert besselk(-1,0) == inf + assert besselk(-2,0) == inf + assert besselk(0,0.5).ae(0.92441907122766586178) + assert besselk(1,0.5).ae(1.6564411200033008937) + assert besselk(-1,0.5).ae(1.6564411200033008937) + assert besselk(3.5,0.5).ae(207.48418747548460607) + assert besselk(0,3+4j).ae(-0.007239051213570155013+0.026510418350267677215j) + assert besselk(0,j).ae(-0.13863371520405399968-1.20196971531720649914j) + assert (besselk(3, 10**10) * mpf(10)**4342944824).ae(1.1628981033356187851) + assert besselk(1,inf) == 0 + + # Reference values for spherical_in(n, z) and spherical_kn(n, z) were + # computed with Wolfram Engine 15: + # SphericalIn[n_, z_] := BesselI[n + 1/2, z] * Sqrt[Pi / (2*z)] + # SphericalKn[n_, z_] := BesselK[n + 1/2, z] * Sqrt[Pi / (2*z)] + assert spherical_in(0, 1).ae(1.1752011936438014) + ref = 0.0014838823109673326 + 0.0008458614117247069j + assert spherical_in(6, -1.5 + 2j).ae(ref) + assert spherical_kn(0, 1).ae(0.5778636748954609) + ref = -25.42791007767947 - 13.388885300250143j + assert spherical_kn(6, -1.5 + 2j).ae(ref) + + assert spherical_jn(0, 1).ae(0.841470984807896) + assert spherical_yn(0, 1).ae(-0.54030230586814) + # test for issue 331, bug reported by Michael Hartmann + for n in range(10,100,10): + mp.dps = n + assert besseli(91.5,24.7708).ae("4.00830632138673963619656140653537080438462342928377020695738635559218797348548092636896796324190271316137982810144874264e-41") + +def test_issue_877(): + mp.dps = 64 + r = besseli(-127, 2) + assert besseli(127, 2) == r + assert r.ae("3.345358761443415013354345973251886375421555647081543375756063117036e-214") + +def test_bessel_zeros(): + assert besseljzero(0,1).ae(2.40482555769577276869) + assert besseljzero(2,1).ae(5.1356223018406825563) + assert besseljzero(1,50).ae(157.86265540193029781) + assert besseljzero(10,1).ae(14.475500686554541220) + assert besseljzero(0.5,3).ae(9.4247779607693797153) + assert besseljzero(2,1,1).ae(3.0542369282271403228) + assert besselyzero(0,1).ae(0.89357696627916752158) + assert besselyzero(2,1).ae(3.3842417671495934727) + assert besselyzero(1,50).ae(156.29183520147840108) + assert besselyzero(10,1).ae(12.128927704415439387) + assert besselyzero(0.5,3).ae(7.8539816339744830962) + assert besselyzero(2,1,1).ae(5.0025829314460639452) + +def test_hankel(): + assert hankel1(0,0.5).ae(0.93846980724081290423-0.44451873350670655715j) + assert hankel1(1,0.5).ae(0.2422684576748738864-1.4714723926702430692j) + assert hankel1(-1,0.5).ae(-0.2422684576748738864+1.4714723926702430692j) + assert hankel1(1.5,0.5).ae(0.0917016996256513026-2.5214655504213378514j) + assert hankel1(1.5,3+4j).ae(0.0066806866476728165382-0.0036684231610839127106j) + assert hankel2(0,0.5).ae(0.93846980724081290423+0.44451873350670655715j) + assert hankel2(1,0.5).ae(0.2422684576748738864+1.4714723926702430692j) + assert hankel2(-1,0.5).ae(-0.2422684576748738864-1.4714723926702430692j) + assert hankel2(1.5,0.5).ae(0.0917016996256513026+2.5214655504213378514j) + assert hankel2(1.5,3+4j).ae(14.783528526098567526-7.397390270853446512j) + +def test_struve(): + assert struveh(2,3).ae(0.74238666967748318564) + assert struveh(-2.5,3).ae(0.41271003220971599344) + assert struvel(2,3).ae(1.7476573277362782744) + assert struvel(-2.5,3).ae(1.5153394466819651377) + +def test_whittaker(): + assert whitm(2,3,4).ae(49.753745589025246591) + assert whitw(2,3,4).ae(14.111656223052932215) + +def test_kelvin(): + assert ber(2,3).ae(0.80836846563726819091) + assert ber(3,4).ae(-0.28262680167242600233) + assert ber(-3,2).ae(-0.085611448496796363669) + assert bei(2,3).ae(-0.89102236377977331571) + assert bei(-3,2).ae(-0.14420994155731828415) + assert ker(2,3).ae(0.12839126695733458928) + assert ker(-3,2).ae(-0.29802153400559142783) + assert ker(0.5,3).ae(-0.085662378535217097524) + assert kei(2,3).ae(0.036804426134164634000) + assert kei(-3,2).ae(0.88682069845786731114) + assert kei(0.5,3).ae(0.013633041571314302948) + +def test_hyper_misc(): + assert hyp0f1(1,0) == 1 + assert hyp1f1(1,2,0) == 1 + assert hyp1f2(1,2,3,0) == 1 + assert hyp2f1(1,2,3,0) == 1 + assert hyp2f2(1,2,3,4,0) == 1 + assert hyp2f3(1,2,3,4,5,0) == 1 + # Degenerate case: 0F0 + assert hyper([],[],0) == 1 + assert hyper([],[],-2).ae(exp(-2)) + # Degenerate case: 1F0 + assert hyper([2],[],1.5) == 4 + # + assert hyp2f1((1,3),(2,3),(5,6),mpf(27)/32).ae(1.6) + assert hyp2f1((1,4),(1,2),(3,4),mpf(80)/81).ae(1.8) + assert hyp2f1((2,3),(1,1),(3,2),(2+j)/3).ae(1.327531603558679093+0.439585080092769253j) + mp.dps = 25 + v = mpc('1.2282306665029814734863026', '-0.1225033830118305184672133') + assert hyper([(3,4),2+j,1],[1,5,j/3],mpf(1)/5+j/8).ae(v) + pytest.raises(ZeroDivisionError, lambda: mp.hyper([1, 2, -2], [-1, 3], 1.1)) + pytest.raises(ZeroDivisionError, lambda: fp.hyper([1, 2, -2], [-1, 3], 1.1)) + +def test_elliptic_integrals(): + assert ellipk(0).ae(pi/2) + assert ellipk(0.5).ae(gamma(0.25)**2/(4*sqrt(pi))) + assert ellipk(1) == inf + assert ellipk(1+0j) == inf + assert ellipk(-1).ae('1.3110287771460599052') + assert ellipk(-2).ae('1.1714200841467698589') + assert isinstance(ellipk(-2), mpf) + assert isinstance(ellipe(-2), mpf) + assert ellipk(-50).ae('0.47103424540873331679') + mp.dps = 30 + n1 = +fraction(99999,100000) + n2 = +fraction(100001,100000) + mp.dps = 15 + assert ellipk(n1).ae('7.1427724505817781901') + assert ellipk(n2).ae(mpc('7.1427417367963090109', '-1.5707923998261688019')) + assert ellipe(n1).ae('1.0000332138990829170') + v = ellipe(n2) + assert v.real.ae('0.999966786328145474069137') + assert (v.imag*10**6).ae('7.853952181727432') + assert ellipk(2).ae(mpc('1.3110287771460599052', '-1.3110287771460599052')) + assert ellipk(50).ae(mpc('0.22326753950210985451', '-0.47434723226254522087')) + assert ellipk(3+4j).ae(mpc('0.91119556380496500866', '0.63133428324134524388')) + assert ellipk(3-4j).ae(mpc('0.91119556380496500866', '-0.63133428324134524388')) + assert ellipk(-3+4j).ae(mpc('0.95357894880405122483', '0.23093044503746114444')) + assert ellipk(-3-4j).ae(mpc('0.95357894880405122483', '-0.23093044503746114444')) + assert isnan(ellipk(nan)) + assert isnan(ellipe(nan)) + assert ellipk(inf) == 0 + assert isinstance(ellipk(inf), mpc) + assert ellipk(-inf) == 0 + assert ellipk(1+0j) == inf + assert ellipe(0).ae(pi/2) + assert ellipe(0.5).ae(pi**(mpf(3)/2)/gamma(0.25)**2 +gamma(0.25)**2/(8*sqrt(pi))) + assert ellipe(1) == 1 + assert ellipe(1+0j) == 1 + assert ellipe(inf) == mpc(0,inf) + assert ellipe(-inf) == inf + assert ellipe(3+4j).ae(1.4995535209333469543-1.5778790079127582745j) + assert ellipe(3-4j).ae(1.4995535209333469543+1.5778790079127582745j) + assert ellipe(-3+4j).ae(2.5804237855343377803-0.8306096791000413778j) + assert ellipe(-3-4j).ae(2.5804237855343377803+0.8306096791000413778j) + assert ellipe(2).ae(0.59907011736779610372+0.59907011736779610372j) + assert ellipe('1e-1000000000').ae(pi/2) + assert ellipk('1e-1000000000').ae(pi/2) + assert ellipe(-pi).ae(2.4535865983838923) + mp.dps = 50 + assert ellipk(1/pi).ae('1.724756270009501831744438120951614673874904182624739673') + assert ellipe(1/pi).ae('1.437129808135123030101542922290970050337425479058225712') + assert ellipk(-10*pi).ae('0.5519067523886233967683646782286965823151896970015484512') + assert ellipe(-10*pi).ae('5.926192483740483797854383268707108012328213431657645509') + v = ellipk(pi) + assert v.real.ae('0.973089521698042334840454592642137667227167622330325225') + assert v.imag.ae('-1.156151296372835303836814390793087600271609993858798016') + v = ellipe(pi) + assert v.real.ae('0.4632848917264710404078033487934663562998345622611263332') + assert v.imag.ae('1.0637961621753130852473300451583414489944099504180510966') + +def test_exp_integrals(): + x = +e + z = e + sqrt(3)*j + assert ei(x).ae(8.21168165538361560) + assert li(x).ae(1.89511781635593676) + assert si(x).ae(1.82104026914756705) + assert ci(x).ae(0.213958001340379779) + assert shi(x).ae(4.11520706247846193) + assert chi(x).ae(4.09647459290515367) + assert fresnels(x).ae(0.437189718149787643) + assert fresnelc(x).ae(0.401777759590243012) + assert airyai(x).ae(0.0108502401568586681) + assert airybi(x).ae(8.98245748585468627) + assert ei(z).ae(3.72597969491314951 + 7.34213212314224421j) + assert li(z).ae(2.28662658112562502 + 1.50427225297269364j) + assert si(z).ae(2.48122029237669054 + 0.12684703275254834j) + assert ci(z).ae(0.169255590269456633 - 0.892020751420780353j) + assert shi(z).ae(1.85810366559344468 + 3.66435842914920263j) + assert chi(z).ae(1.86787602931970484 + 3.67777369399304159j) + assert fresnels(z/3).ae(0.034534397197008182 + 0.754859844188218737j) + assert fresnelc(z/3).ae(1.261581645990027372 + 0.417949198775061893j) + assert airyai(z).ae(-0.0162552579839056062 - 0.0018045715700210556j) + assert airybi(z).ae(-4.98856113282883371 + 2.08558537872180623j) + assert li(0) == 0.0 + assert li(1) == -inf + assert li(inf) == inf + assert isinstance(li(0.7), mpf) + assert si(inf).ae(pi/2) + assert si(-inf).ae(-pi/2) + assert ci(inf) == 0 + assert ci(0) == -inf + assert isinstance(ei(-0.7), mpf) + assert airyai(inf) == 0 + assert airybi(inf) == inf + assert airyai(-inf) == 0 + assert airybi(-inf) == 0 + assert fresnels(inf) == 0.5 + assert fresnelc(inf) == 0.5 + assert fresnels(-inf) == -0.5 + assert fresnelc(-inf) == -0.5 + assert shi(0) == 0 + assert shi(inf) == inf + assert shi(-inf) == -inf + assert chi(0) == -inf + assert chi(inf) == inf + +def test_ei(): + assert ei(0) == -inf + assert ei(inf) == inf + assert ei(-inf) == -0.0 + assert ei(20+70j).ae(6.1041351911152984397e6 - 2.7324109310519928872e6j) + # tests for the asymptotic expansion + # values checked with Mathematica ExpIntegralEi + mp.dps = 50 + r = ei(20000) + s = '3.8781962825045010930273870085501819470698476975019e+8681' + assert str(r) == s + r = ei(-200) + s = '-6.8852261063076355977108174824557929738368086933303e-90' + assert str(r) == s + r =ei(20000 + 10*j) + sre = '-3.255138234032069402493850638874410725961401274106e+8681' + sim = '-2.1081929993474403520785942429469187647767369645423e+8681' + assert str(r.real) == sre and str(r.imag) == sim + mp.dps = 15 + # More asymptotic expansions + assert chi(-10**6+100j).ae('1.3077239389562548386e+434288 + 7.6808956999707408158e+434287j') + assert shi(-10**6+100j).ae('-1.3077239389562548386e+434288 - 7.6808956999707408158e+434287j') + assert ei(10j).ae(-0.0454564330044553726+3.2291439210137706686j) + assert ei(100j).ae(-0.0051488251426104921+3.1330217936839529126j) + u = ei(fmul(10**20, j, exact=True)) + assert u.real.ae(-6.4525128526578084421345e-21, abs_eps=0, rel_eps=8*eps) + assert u.imag.ae(pi) + assert ei(-10j).ae(-0.0454564330044553726-3.2291439210137706686j) + assert ei(-100j).ae(-0.0051488251426104921-3.1330217936839529126j) + u = ei(fmul(-10**20, j, exact=True)) + assert u.real.ae(-6.4525128526578084421345e-21, abs_eps=0, rel_eps=8*eps) + assert u.imag.ae(-pi) + assert ei(10+10j).ae(-1576.1504265768517448+436.9192317011328140j) + u = ei(-10+10j) + assert u.real.ae(7.6698978415553488362543e-7, abs_eps=0, rel_eps=8*eps) + assert u.imag.ae(3.141595611735621062025) + +def test_e1(): + assert e1(0) == inf + assert e1(inf) == 0 + assert e1(-inf) == mpc(-inf, -pi) + assert e1(10j).ae(0.045456433004455372635 + 0.087551267423977430100j) + assert e1(100j).ae(0.0051488251426104921444 - 0.0085708599058403258790j) + assert e1(fmul(10**20, j, exact=True)).ae(6.4525128526578084421e-21 - 7.6397040444172830039e-21j, abs_eps=0, rel_eps=8*eps) + assert e1(-10j).ae(0.045456433004455372635 - 0.087551267423977430100j) + assert e1(-100j).ae(0.0051488251426104921444 + 0.0085708599058403258790j) + assert e1(fmul(-10**20, j, exact=True)).ae(6.4525128526578084421e-21 + 7.6397040444172830039e-21j, abs_eps=0, rel_eps=8*eps) + +def test_expint(): + assert expint(0,0) == inf + assert expint(0,1).ae(1/e) + assert expint(0,1.5).ae(2/exp(1.5)/3) + assert expint(1,1).ae(-ei(-1)) + assert expint(2,0).ae(1) + assert expint(3,0).ae(1/2.) + assert expint(4,0).ae(1/3.) + assert expint(-2, 0.5).ae(26/sqrt(e)) + assert expint(-1,-1) == 0 + assert expint(-2,-1).ae(-e) + assert expint(5.5, 0).ae(2/9.) + assert expint(2.00000001,0).ae(100000000./100000001) + assert expint(2+3j,4-j).ae(0.0023461179581675065414+0.0020395540604713669262j) + assert expint('1.01', '1e-1000').ae(99.9999999899412802) + assert expint('1.000000000001', 3.5).ae(0.00697013985754701819446) + assert expint(2,3).ae(3*ei(-3)+exp(-3)) + assert (expint(10,20)*10**10).ae(0.694439055541231353) + assert expint(3,inf) == 0 + assert expint(3.2,inf) == 0 + assert expint(3.2+2j,inf) == 0 + assert expint(1,3j).ae(-0.11962978600800032763 + 0.27785620120457163717j) + assert expint(1,3).ae(0.013048381094197037413) + assert expint(1,-3).ae(-ei(3)-pi*j) + #assert expint(3) == expint(1,3) + assert expint(1,-20).ae(-25615652.66405658882 - 3.1415926535897932385j) + assert expint(1000000,0).ae(1./999999) + assert expint(0,2+3j).ae(-0.025019798357114678171 + 0.027980439405104419040j) + assert expint(-1,2+3j).ae(-0.022411973626262070419 + 0.038058922011377716932j) + assert expint(-1.5,0) == inf + +def test_trig_integrals(): + mp.dps = 30 + assert si(mpf(1)/1000000).ae('0.000000999999999999944444444444446111') + assert ci(mpf(1)/1000000).ae('-13.2382948930629912435014366276') + assert si(10**10).ae('1.5707963267075846569685111517747537') + assert ci(10**10).ae('-4.87506025174822653785729773959e-11') + assert si(10**100).ae(pi/2) + assert (ci(10**100)*10**100).ae('-0.372376123661276688262086695553') + assert si(-3) == -si(3) + assert ci(-3).ae(ci(3) + pi*j) + # Test complex structure + mp.dps = 15 + assert mp.ci(50).ae(-0.0056283863241163054402) + assert mp.ci(50+2j).ae(-0.018378282946133067149+0.070352808023688336193j) + assert mp.ci(20j).ae(1.28078263320282943611e7+1.5707963267949j) + assert mp.ci(-2+20j).ae(-4.050116856873293505e6+1.207476188206989909e7j) + assert mp.ci(-50+2j).ae(-0.0183782829461330671+3.0712398455661049023j) + assert mp.ci(-50).ae(-0.0056283863241163054+3.1415926535897932385j) + assert mp.ci(-50-2j).ae(-0.0183782829461330671-3.0712398455661049023j) + assert mp.ci(-2-20j).ae(-4.050116856873293505e6-1.207476188206989909e7j) + assert mp.ci(-20j).ae(1.28078263320282943611e7-1.5707963267949j) + assert mp.ci(50-2j).ae(-0.018378282946133067149-0.070352808023688336193j) + assert mp.si(50).ae(1.5516170724859358947) + assert mp.si(50+2j).ae(1.497884414277228461-0.017515007378437448j) + assert mp.si(20j).ae(1.2807826332028294459e7j) + assert mp.si(-2+20j).ae(-1.20747603112735722103e7-4.050116856873293554e6j) + assert mp.si(-50+2j).ae(-1.497884414277228461-0.017515007378437448j) + assert mp.si(-50).ae(-1.5516170724859358947) + assert mp.si(-50-2j).ae(-1.497884414277228461+0.017515007378437448j) + assert mp.si(-2-20j).ae(-1.20747603112735722103e7+4.050116856873293554e6j) + assert mp.si(-20j).ae(-1.2807826332028294459e7j) + assert mp.si(50-2j).ae(1.497884414277228461+0.017515007378437448j) + assert mp.chi(50j).ae(-0.0056283863241163054+1.5707963267948966192j) + assert mp.chi(-2+50j).ae(-0.0183782829461330671+1.6411491348185849554j) + assert mp.chi(-20).ae(1.28078263320282943611e7+3.1415926535898j) + assert mp.chi(-20-2j).ae(-4.050116856873293505e6+1.20747571696809187053e7j) + assert mp.chi(-2-50j).ae(-0.0183782829461330671-1.6411491348185849554j) + assert mp.chi(-50j).ae(-0.0056283863241163054-1.5707963267948966192j) + assert mp.chi(2-50j).ae(-0.0183782829461330671-1.500443518771208283j) + assert mp.chi(20-2j).ae(-4.050116856873293505e6-1.20747603112735722951e7j) + assert mp.chi(20).ae(1.2807826332028294361e7) + assert mp.chi(2+50j).ae(-0.0183782829461330671+1.500443518771208283j) + assert mp.shi(50j).ae(1.5516170724859358947j) + assert mp.shi(-2+50j).ae(0.017515007378437448+1.497884414277228461j) + assert mp.shi(-20).ae(-1.2807826332028294459e7) + assert mp.shi(-20-2j).ae(4.050116856873293554e6-1.20747603112735722103e7j) + assert mp.shi(-2-50j).ae(0.017515007378437448-1.497884414277228461j) + assert mp.shi(-50j).ae(-1.5516170724859358947j) + assert mp.shi(2-50j).ae(-0.017515007378437448-1.497884414277228461j) + assert mp.shi(20-2j).ae(-4.050116856873293554e6-1.20747603112735722103e7j) + assert mp.shi(20).ae(1.2807826332028294459e7) + assert mp.shi(2+50j).ae(-0.017515007378437448+1.497884414277228461j) + def ae(x,y,tol=1e-12): + return abs(x-y) <= abs(y)*tol + assert fp.ci(fp.inf) == 0 + assert ae(fp.ci(fp.ninf), fp.pi*1j) + assert ae(fp.si(fp.inf), fp.pi/2) + assert ae(fp.si(fp.ninf), -fp.pi/2) + assert fp.si(0) == 0 + assert ae(fp.ci(50), -0.0056283863241163054402) + assert ae(fp.ci(50+2j), -0.018378282946133067149+0.070352808023688336193j) + assert ae(fp.ci(20j), 1.28078263320282943611e7+1.5707963267949j) + assert ae(fp.ci(-2+20j), -4.050116856873293505e6+1.207476188206989909e7j) + assert ae(fp.ci(-50+2j), -0.0183782829461330671+3.0712398455661049023j) + assert ae(fp.ci(-50), -0.0056283863241163054+3.1415926535897932385j) + assert ae(fp.ci(-50-2j), -0.0183782829461330671-3.0712398455661049023j) + assert ae(fp.ci(-2-20j), -4.050116856873293505e6-1.207476188206989909e7j) + assert ae(fp.ci(-20j), 1.28078263320282943611e7-1.5707963267949j) + assert ae(fp.ci(50-2j), -0.018378282946133067149-0.070352808023688336193j) + assert ae(fp.si(50), 1.5516170724859358947) + assert ae(fp.si(50+2j), 1.497884414277228461-0.017515007378437448j) + assert ae(fp.si(20j), 1.2807826332028294459e7j) + assert ae(fp.si(-2+20j), -1.20747603112735722103e7-4.050116856873293554e6j) + assert ae(fp.si(-50+2j), -1.497884414277228461-0.017515007378437448j) + assert ae(fp.si(-50), -1.5516170724859358947) + assert ae(fp.si(-50-2j), -1.497884414277228461+0.017515007378437448j) + assert ae(fp.si(-2-20j), -1.20747603112735722103e7+4.050116856873293554e6j) + assert ae(fp.si(-20j), -1.2807826332028294459e7j) + assert ae(fp.si(50-2j), 1.497884414277228461+0.017515007378437448j) + assert ae(fp.chi(50j), -0.0056283863241163054+1.5707963267948966192j) + assert ae(fp.chi(-2+50j), -0.0183782829461330671+1.6411491348185849554j) + assert ae(fp.chi(-20), 1.28078263320282943611e7+3.1415926535898j) + assert ae(fp.chi(-20-2j), -4.050116856873293505e6+1.20747571696809187053e7j) + assert ae(fp.chi(-2-50j), -0.0183782829461330671-1.6411491348185849554j) + assert ae(fp.chi(-50j), -0.0056283863241163054-1.5707963267948966192j) + assert ae(fp.chi(2-50j), -0.0183782829461330671-1.500443518771208283j) + assert ae(fp.chi(20-2j), -4.050116856873293505e6-1.20747603112735722951e7j) + assert ae(fp.chi(20), 1.2807826332028294361e7) + assert ae(fp.chi(2+50j), -0.0183782829461330671+1.500443518771208283j) + assert ae(fp.shi(50j), 1.5516170724859358947j) + assert ae(fp.shi(-2+50j), 0.017515007378437448+1.497884414277228461j) + assert ae(fp.shi(-20), -1.2807826332028294459e7) + assert ae(fp.shi(-20-2j), 4.050116856873293554e6-1.20747603112735722103e7j) + assert ae(fp.shi(-2-50j), 0.017515007378437448-1.497884414277228461j) + assert ae(fp.shi(-50j), -1.5516170724859358947j) + assert ae(fp.shi(2-50j), -0.017515007378437448-1.497884414277228461j) + assert ae(fp.shi(20-2j), -4.050116856873293554e6-1.20747603112735722103e7j) + assert ae(fp.shi(20), 1.2807826332028294459e7) + assert ae(fp.shi(2+50j), -0.017515007378437448+1.497884414277228461j) + +def test_airy(): + assert (airyai(10)*10**10).ae(1.1047532552898687) + assert (airybi(10)/10**9).ae(0.45564115354822515) + assert (airyai(1000)*10**9158).ae(9.306933063179556004) + assert (airybi(1000)/10**9154).ae(5.4077118391949465477) + assert airyai(-1000).ae(0.055971895773019918842) + assert airybi(-1000).ae(-0.083264574117080633012) + assert (airyai(100+100j)*10**188).ae(2.9099582462207032076 + 2.353013591706178756j) + assert (airybi(100+100j)/10**185).ae(1.7086751714463652039 - 3.1416590020830804578j) + +def test_hyper_0f1(): + v = 8.63911136507950465 + assert hyper([],[(1,3)],1.5).ae(v) + assert hyper([],[1/3.],1.5).ae(v) + assert hyp0f1(1/3.,1.5).ae(v) + assert hyp0f1((1,3),1.5).ae(v) + # Asymptotic expansion + assert hyp0f1(3,1e9).ae('4.9679055380347771271e+27455') + assert hyp0f1(3,1e9j).ae('-2.1222788784457702157e+19410 + 5.0840597555401854116e+19410j') + +def test_hyper_1f1(): + v = 1.2917526488617656673 + assert hyper([(1,2)],[(3,2)],0.7).ae(v) + assert hyper([(1,2)],[(3,2)],0.7+0j).ae(v) + assert hyper([0.5],[(3,2)],0.7).ae(v) + assert hyper([0.5],[1.5],0.7).ae(v) + assert hyper([0.5],[(3,2)],0.7+0j).ae(v) + assert hyper([0.5],[1.5],0.7+0j).ae(v) + assert hyper([(1,2)],[1.5+0j],0.7).ae(v) + assert hyper([0.5+0j],[1.5],0.7).ae(v) + assert hyper([0.5+0j],[1.5+0j],0.7+0j).ae(v) + assert hyp1f1(0.5,1.5,0.7).ae(v) + assert hyp1f1((1,2),1.5,0.7).ae(v) + # Asymptotic expansion + assert hyp1f1(2,3,1e10).ae('2.1555012157015796988e+4342944809') + assert (hyp1f1(2,3,1e10j)*10**10).ae(-0.97501205020039745852 - 1.7462392454512132074j) + # Shouldn't use asymptotic expansion + assert hyp1f1(-2, 1, 10000).ae(49980001) + # Bug + assert hyp1f1(1j,fraction(1,3),0.415-69.739j).ae(25.857588206024346592 + 15.738060264515292063j) + # issue 522 + assert hyp1f1(0, 1, +inf) == 1 + assert hyp1f1(0, 1, -inf) == 1 + assert hyp1f1(1, 2, -inf) == 0 + assert hyp1f1(2, 2, -inf) == 0 + assert hyp1f1(1, 5, -inf) == 0 + +def test_hyper_2f1(): + v = 1.0652207633823291032 + assert hyper([(1,2), (3,4)], [2], 0.3).ae(v) + assert hyper([(1,2), 0.75], [2], 0.3).ae(v) + assert hyper([0.5, 0.75], [2.0], 0.3).ae(v) + assert hyper([0.5, 0.75], [2.0], 0.3+0j).ae(v) + assert hyper([0.5+0j, (3,4)], [2.0], 0.3+0j).ae(v) + assert hyper([0.5+0j, (3,4)], [2.0], 0.3).ae(v) + assert hyper([0.5, (3,4)], [2.0+0j], 0.3).ae(v) + assert hyper([0.5+0j, 0.75+0j], [2.0+0j], 0.3+0j).ae(v) + v = 1.09234681096223231717 + 0.18104859169479360380j + assert hyper([(1,2),0.75+j], [2], 0.5).ae(v) + assert hyper([0.5,0.75+j], [2.0], 0.5).ae(v) + assert hyper([0.5,0.75+j], [2.0], 0.5+0j).ae(v) + assert hyper([0.5,0.75+j], [2.0+0j], 0.5+0j).ae(v) + v = 0.9625 - 0.125j + assert hyper([(3,2),-1],[4], 0.1+j/3).ae(v) + assert hyper([1.5,-1.0],[4], 0.1+j/3).ae(v) + assert hyper([1.5,-1.0],[4+0j], 0.1+j/3).ae(v) + assert hyper([1.5+0j,-1.0+0j],[4+0j], 0.1+j/3).ae(v) + v = 1.02111069501693445001 - 0.50402252613466859521j + assert hyper([(2,10),(3,10)],[(4,10)],1.5).ae(v) + assert hyper([0.2,(3,10)],[0.4+0j],1.5).ae(v) + assert hyper([0.2,(3,10)],[0.4+0j],1.5+0j).ae(v) + v = 0.76922501362865848528 + 0.32640579593235886194j + assert hyper([(2,10),(3,10)],[(4,10)],4+2j).ae(v) + assert hyper([0.2,(3,10)],[0.4+0j],4+2j).ae(v) + assert hyper([0.2,(3,10)],[(4,10)],4+2j).ae(v) + +def test_hyper_2f1_hard(): + # Singular cases + assert hyp2f1(2,-1,-1,3).ae(7) + assert hyp2f1(2,-1,-1,3,eliminate_all=True).ae(0.25) + assert hyp2f1(2,-2,-2,3).ae(34) + assert hyp2f1(2,-2,-2,3,eliminate_all=True).ae(0.25) + assert hyp2f1(2,-2,-3,3) == 14 + assert hyp2f1(2,-3,-2,3) == inf + assert hyp2f1(2,-1.5,-1.5,3) == 0.25 + assert hyp2f1(1,2,3,0) == 1 + assert hyp2f1(0,1,0,0) == 1 + assert hyp2f1(0,0,0,0) == 1 + assert isnan(hyp2f1(1,1,0,0)) + assert hyp2f1(2,-1,-5, 0.25+0.25j).ae(1.1+0.1j) + assert hyp2f1(2,-5,-5, 0.25+0.25j, eliminate=False).ae(163./128 + 125./128*j) + assert hyp2f1(0.7235, -1, -5, 0.3).ae(1.04341) + assert hyp2f1(0.7235, -5, -5, 0.3, eliminate=False).ae(1.2939225017815903812) + assert hyp2f1(-1,-2,4,1) == 1.5 + assert hyp2f1(1,2,-3,1) == inf + assert hyp2f1(-2,-2,1,1) == 6 + assert hyp2f1(1,-2,-4,1).ae(5./3) + assert hyp2f1(0,-6,-4,1) == 1 + assert hyp2f1(0,-3,-4,1) == 1 + assert hyp2f1(0,0,0,1) == 1 + assert hyp2f1(1,0,0,1,eliminate=False) == 1 + assert hyp2f1(1,1,0,1) == inf + assert hyp2f1(1,-6,-4,1) == inf + assert hyp2f1(-7.2,-0.5,-4.5,1) == 0 + assert hyp2f1(-7.2,-1,-2,1).ae(-2.6) + assert hyp2f1(1,-0.5,-4.5, 1) == inf + assert hyp2f1(1,0.5,-4.5, 1) == -inf + # Check evaluation on / close to unit circle + z = exp(j*pi/3) + w = (nthroot(2,3)+1)*exp(j*pi/12)/nthroot(3,4)**3 + assert hyp2f1('1/2','1/6','1/3', z).ae(w) + assert hyp2f1('1/2','1/6','1/3', z.conjugate()).ae(w.conjugate()) + assert hyp2f1(0.25, (1,3), 2, '0.999').ae(1.06826449496030635) + assert hyp2f1(0.25, (1,3), 2, '1.001').ae(1.06867299254830309446-0.00001446586793975874j) + assert hyp2f1(0.25, (1,3), 2, -1).ae(0.96656584492524351673) + assert hyp2f1(0.25, (1,3), 2, j).ae(0.99041766248982072266+0.03777135604180735522j) + assert hyp2f1(2,3,5,'0.99').ae(27.699347904322690602) + assert hyp2f1((3,2),-0.5,3,'0.99').ae(0.68403036843911661388) + assert hyp2f1(2,3,5,1j).ae(0.37290667145974386127+0.59210004902748285917j) + assert fsum([hyp2f1((7,10),(2,3),(-1,2), 0.95*exp(j*k)) for k in range(1,15)]).ae(52.851400204289452922+6.244285013912953225j) + assert fsum([hyp2f1((7,10),(2,3),(-1,2), 1.05*exp(j*k)) for k in range(1,15)]).ae(54.506013786220655330-3.000118813413217097j) + assert fsum([hyp2f1((7,10),(2,3),(-1,2), exp(j*k)) for k in range(1,15)]).ae(55.792077935955314887+1.731986485778500241j) + assert hyp2f1(2,2.5,-3.25,0.999).ae(218373932801217082543180041.33) + # Branches + assert hyp2f1(1,1,2,1.01).ae(4.5595744415723676911-3.1104877758314784539j) + assert hyp2f1(1,1,2,1.01+0.1j).ae(2.4149427480552782484+1.4148224796836938829j) + assert hyp2f1(1,1,2,3+4j).ae(0.14576709331407297807+0.48379185417980360773j) + assert hyp2f1(1,1,2,4).ae(-0.27465307216702742285 - 0.78539816339744830962j) + assert hyp2f1(1,1,2,-4).ae(0.40235947810852509365) + # Other: + # Cancellation with a large parameter involved (bug reported on sage-devel) + assert hyp2f1(112, (51,10), (-9,10), -0.99999).ae(-1.6241361047970862961e-24, abs_eps=0, rel_eps=eps*16) + +def test_hyper_3f2_etc(): + assert hyper([1,2,3],[1.5,8],-1).ae(0.67108992351533333030) + assert hyper([1,2,3,4],[5,6,7], -1).ae(0.90232988035425506008) + assert hyper([1,2,3],[1.25,5], 1).ae(28.924181329701905701) + assert hyper([1,2,3,4],[5,6,7],5).ae(1.5192307344006649499-1.1529845225075537461j) + assert hyper([1,2,3,4,5],[6,7,8,9],-1).ae(0.96288759462882357253) + assert hyper([1,2,3,4,5],[6,7,8,9],1).ae(1.0428697385885855841) + assert hyper([1,2,3,4,5],[6,7,8,9],5).ae(1.33980653631074769423-0.07143405251029226699j) + assert hyper([1,2.79,3.08,4.37],[5.2,6.1,7.3],5).ae(1.0996321464692607231-1.7748052293979985001j) + assert hyper([1,1,1],[1,2],1) == inf + assert hyper([1,1,1],[2,(101,100)],1).ae(100.01621213528313220) + # slow -- covered by doctests + #assert hyper([1,1,1],[2,3],0.9999).ae(1.2897972005319693905) + +def test_hyper_u(): + assert hyperu(2,-3,0).ae(0.05) + assert hyperu(2,-3.5,0).ae(4./99) + assert hyperu(2,0,0) == 0.5 + assert hyperu(-5,1,0) == -120 + assert hyperu(-5,2,0) == inf + assert hyperu(-5,-2,0) == 0 + assert hyperu(7,7,3).ae(0.00014681269365593503986) #exp(3)*upper_gamma(-6,3) + assert hyperu(2,-3,4).ae(0.011836478100271995559) + assert hyperu(3,4,5).ae(1./125) + assert hyperu(2,3,0.0625) == 256 + assert hyperu(-1,2,0.25+0.5j) == -1.75+0.5j + assert hyperu(0.5,1.5,7.25).ae(2/sqrt(29)) + assert hyperu(2,6,pi).ae(0.55804439825913399130) + assert (hyperu((3,2),8,100+201j)*10**4).ae(-0.3797318333856738798 - 2.9974928453561707782j) + assert (hyperu((5,2),(-1,2),-5000)*10**10).ae(-5.6681877926881664678j) + assert (hyperu((5,2),(-1,2),-500)*10**7).ae(-1.82526906001593252847j) + +def test_hyper_2f0(): + assert hyper([1,2],[],3) == hyp2f0(1,2,3) + assert hyp2f0(2,3,7).ae(0.0116108068639728714668 - 0.0073727413865865802130j) + assert hyp2f0(2,3,0) == 1 + assert hyp2f0(0,0,0) == 1 + assert hyp2f0(-1,-1,1).ae(2) + assert hyp2f0(-4,1,1.5).ae(62.5) + assert hyp2f0(-4,1,50).ae(147029801) + assert hyp2f0(-4,1,0.0001).ae(0.99960011997600240000) + assert hyp2f0(0.5,0.25,0.001).ae(1.0001251174078538115) + assert hyp2f0(0.5,0.25,3+4j).ae(0.85548875824755163518 + 0.21636041283392292973j) + # Important: cancellation check + assert hyp2f0((1,6),(5,6),-0.02371708245126284498).ae(0.996785723120804309) + # Should be exact; polynomial case + assert hyp2f0(-2,1,0.5+0.5j,zeroprec=200) == 0 + assert hyp2f0(1,-2,0.5+0.5j,zeroprec=200) == 0 + # There used to be a bug in thresholds that made one of the following hang + for d in [15, 50, 80]: + mp.dps = d + assert hyp2f0(1.5, 0.5, 0.009).ae('1.006867007239309717945323585695344927904000945829843527398772456281301440034218290443367270629519483 +' + ' 1.238277162240704919639384945859073461954721356062919829456053965502443570466701567100438048602352623e-46j') + +def test_hyper_1f2(): + assert hyper([1],[2,3],4) == hyp1f2(1,2,3,4) + a1,b1,b2 = (1,10),(2,3),1./16 + assert hyp1f2(a1,b1,b2,10).ae(298.7482725554557568) + assert hyp1f2(a1,b1,b2,100).ae(224128961.48602947604) + assert hyp1f2(a1,b1,b2,1000).ae(1.1669528298622675109e+27) + assert hyp1f2(a1,b1,b2,10000).ae(2.4780514622487212192e+86) + assert hyp1f2(a1,b1,b2,100000).ae(1.3885391458871523997e+274) + assert hyp1f2(a1,b1,b2,1000000).ae('9.8851796978960318255e+867') + assert hyp1f2(a1,b1,b2,10**7).ae('1.1505659189516303646e+2746') + assert hyp1f2(a1,b1,b2,10**8).ae('1.4672005404314334081e+8685') + assert hyp1f2(a1,b1,b2,10**20).ae('3.6888217332150976493e+8685889636') + assert hyp1f2(a1,b1,b2,10*j).ae(-16.163252524618572878 - 44.321567896480184312j) + assert hyp1f2(a1,b1,b2,100*j).ae(61938.155294517848171 + 637349.45215942348739j) + assert hyp1f2(a1,b1,b2,1000*j).ae(8455057657257695958.7 + 6261969266997571510.6j) + assert hyp1f2(a1,b1,b2,10000*j).ae(-8.9771211184008593089e+60 + 4.6550528111731631456e+59j) + assert hyp1f2(a1,b1,b2,100000*j).ae(2.6398091437239324225e+193 + 4.1658080666870618332e+193j) + assert hyp1f2(a1,b1,b2,1000000*j).ae('3.5999042951925965458e+613 + 1.5026014707128947992e+613j') + assert hyp1f2(a1,b1,b2,10**7*j).ae('-8.3208715051623234801e+1939 - 3.6752883490851869429e+1941j') + assert hyp1f2(a1,b1,b2,10**8*j).ae('2.0724195707891484454e+6140 - 1.3276619482724266387e+6141j') + assert hyp1f2(a1,b1,b2,10**20*j).ae('-1.1734497974795488504e+6141851462 + 1.1498106965385471542e+6141851462j') + +def test_hyper_2f3(): + assert hyper([1,2],[3,4,5],6) == hyp2f3(1,2,3,4,5,6) + a1,a2,b1,b2,b3 = (1,10),(2,3),(3,10), 2, 1./16 + # Check asymptotic expansion + assert hyp2f3(a1,a2,b1,b2,b3,10).ae(128.98207160698659976) + assert hyp2f3(a1,a2,b1,b2,b3,1000).ae(6.6309632883131273141e25) + assert hyp2f3(a1,a2,b1,b2,b3,10000).ae(4.6863639362713340539e84) + assert hyp2f3(a1,a2,b1,b2,b3,100000).ae(8.6632451236103084119e271) + assert hyp2f3(a1,a2,b1,b2,b3,10**6).ae('2.0291718386574980641e865') + assert hyp2f3(a1,a2,b1,b2,b3,10**7).ae('7.7639836665710030977e2742') + assert hyp2f3(a1,a2,b1,b2,b3,10**8).ae('3.2537462584071268759e8681') + assert hyp2f3(a1,a2,b1,b2,b3,10**20).ae('1.2966030542911614163e+8685889627') + assert hyp2f3(a1,a2,b1,b2,b3,10*j).ae(-18.551602185587547854 - 13.348031097874113552j) + assert hyp2f3(a1,a2,b1,b2,b3,100*j).ae(78634.359124504488695 + 74459.535945281973996j) + assert hyp2f3(a1,a2,b1,b2,b3,1000*j).ae(597682550276527901.59 - 65136194809352613.078j) + assert hyp2f3(a1,a2,b1,b2,b3,10000*j).ae(-1.1779696326238582496e+59 + 1.2297607505213133872e+59j) + assert hyp2f3(a1,a2,b1,b2,b3,100000*j).ae(2.9844228969804380301e+191 + 7.5587163231490273296e+190j) + assert hyp2f3(a1,a2,b1,b2,b3,1000000*j).ae('7.4859161049322370311e+610 - 2.8467477015940090189e+610j') + assert hyp2f3(a1,a2,b1,b2,b3,10**7*j).ae('-1.7477645579418800826e+1938 - 1.7606522995808116405e+1938j') + assert hyp2f3(a1,a2,b1,b2,b3,10**8*j).ae('-1.6932731942958401784e+6137 - 2.4521909113114629368e+6137j') + assert hyp2f3(a1,a2,b1,b2,b3,10**20*j).ae('-2.0988815677627225449e+6141851451 + 5.7708223542739208681e+6141851452j') + +def test_hyper_2f2(): + assert hyper([1,2],[3,4],5) == hyp2f2(1,2,3,4,5) + a1,a2,b1,b2 = (3,10),4,(1,2),1./16 + assert hyp2f2(a1,a2,b1,b2,10).ae(448225936.3377556696) + assert hyp2f2(a1,a2,b1,b2,10000).ae('1.2012553712966636711e+4358') + assert hyp2f2(a1,a2,b1,b2,-20000).ae(-0.04182343755661214626) + assert hyp2f2(a1,a2,b1,b2,10**20).ae('1.1148680024303263661e+43429448190325182840') + +def test_orthpoly(): + assert jacobi(-4,2,3,0.7).ae(22800./4913) + assert jacobi(3,2,4,5.5) == 4133.125 + assert jacobi(1.5,5/6.,4,0).ae(-1.0851951434075508417) + assert jacobi(-2, 1, 2, 4).ae(-0.16) + assert jacobi(2, -1, 2.5, 4).ae(34.59375) + #assert jacobi(2, -1, 2, 4) == 28.5 + assert legendre(5, 7) == 129367 + assert legendre(0.5,0).ae(0.53935260118837935667) + assert legendre(-1,-1) == 1 + assert legendre(0,-1) == 1 + assert legendre(0, 1) == 1 + assert legendre(1, -1) == -1 + assert legendre(7, 1) == 1 + assert legendre(7, -1) == -1 + assert legendre(8,1.5).ae(15457523./32768) + assert legendre(j,-j).ae(2.4448182735671431011 + 0.6928881737669934843j) + assert chebyu(5,1) == 6 + assert chebyt(3,2) == 26 + assert chebyu(5,inf) == inf # issue 469 + assert chebyt(5,inf) == inf + assert chebyt(10**3, 1j, force_series=False) == chebyt(10**3, 1j) + pytest.raises(NoConvergence, lambda: chebyt(10**6, 1j)) # issue 852 + assert chebyu(10**3, 1j, force_series=False) == chebyu(10**3, 1j) + assert legendre(3.5,-1) == inf + assert legendre(4.5,-1) == -inf + assert legendre(3.5+1j,-1) == mpc(inf,inf) + assert legendre(4.5+1j,-1) == mpc(-inf,-inf) + assert laguerre(4, -2, 3).ae(-1.125) + assert laguerre(3, 1+j, 0.5).ae(0.2291666666666666667 + 2.5416666666666666667j) + +def test_hermite(): + assert hermite(-2, 0).ae(0.5) + assert hermite(-1, 0).ae(0.88622692545275801365) + assert hermite(0, 0).ae(1) + assert hermite(1, 0) == 0 + assert hermite(2, 0).ae(-2) + assert hermite(0, 2).ae(1) + assert hermite(1, 2).ae(4) + assert hermite(1, -2).ae(-4) + assert hermite(2, -2).ae(14) + assert hermite(0.5, 0).ae(0.69136733903629335053) + assert hermite(9, 0) == 0 + assert hermite(4,4).ae(3340) + assert hermite(3,4).ae(464) + assert hermite(-4,4).ae(0.00018623860287512396181) + assert hermite(-3,4).ae(0.0016540169879668766270) + assert hermite(9, 2.5j).ae(13638725j) + assert hermite(9, -2.5j).ae(-13638725j) + assert hermite(9, 100).ae(511078883759363024000) + assert hermite(9, -100).ae(-511078883759363024000) + assert hermite(9, 100j).ae(512922083920643024000j) + assert hermite(9, -100j).ae(-512922083920643024000j) + assert hermite(-9.5, 2.5j).ae(-2.9004951258126778174e-6 + 1.7601372934039951100e-6j) + assert hermite(-9.5, -2.5j).ae(-2.9004951258126778174e-6 - 1.7601372934039951100e-6j) + assert hermite(-9.5, 100).ae(1.3776300722767084162e-22, abs_eps=0, rel_eps=eps) + assert hermite(-9.5, -100).ae('1.3106082028470671626e4355') + assert hermite(-9.5, 100j).ae(-9.7900218581864768430e-23 - 9.7900218581864768430e-23j, abs_eps=0, rel_eps=eps) + assert hermite(-9.5, -100j).ae(-9.7900218581864768430e-23 + 9.7900218581864768430e-23j, abs_eps=0, rel_eps=eps) + assert hermite(2+3j, -1-j).ae(851.3677063883687676 - 1496.4373467871007997j) + +def test_gegenbauer(): + assert gegenbauer(1,2,3).ae(12) + assert gegenbauer(2,3,4).ae(381) + assert gegenbauer(0,0,0) == 0 + assert gegenbauer(2,-1,3) == 0 + assert gegenbauer(-7, 0.5, 3).ae(8989) + assert gegenbauer(1, -0.5, 3).ae(-3) + assert gegenbauer(1, -1.5, 3).ae(-9) + assert gegenbauer(1, -0.5, 3).ae(-3) + assert gegenbauer(-0.5, -0.5, 3).ae(-2.6383553159023906245) + assert gegenbauer(2+3j, 1-j, 3+4j).ae(14.880536623203696780 + 20.022029711598032898j) + #assert gegenbauer(-2, -0.5, 3).ae(-12) + assert gegenbauer(0, 0, 2.2) == 0 # issue 494 + assert gegenbauer(0, 1, 2.2) == 1 + assert gegenbauer(0, 4, 2.2) == 1 + assert gegenbauer(0, 0, 1.8) == 0 + assert gegenbauer(0, 1, 1.8) == 1 + # issue 1077: odd integer n at z=0 vanishes + assert gegenbauer(1, 1, 0) == 0 + assert gegenbauer(5, 1.5, 0) == 0 + assert gegenbauer(3, 2, 0) == 0 + assert gegenbauer(3, 1, mpc(0)) == 0 + # adjacent cases must keep going through the general path + assert gegenbauer(2, 1, 0).ae(-1) + assert gegenbauer(4, 1.5, 0).ae(1.875) + assert gegenbauer(2.5, 1, 0).ae(-0.70710678118654752440) + mp.dps = 200 + assert gegenbauer(2,-1.0, 27397079.00297188) == 0 # issue 461 + +def test_legenp(): + assert legenp(2,0,4) == legendre(2,4) + assert legenp(-2, -1, 0.5).ae(0.43301270189221932338) + assert legenp(-2, -1, 0.5, type=3).ae(0.43301270189221932338j) + assert legenp(-2, 1, 0.5).ae(-0.86602540378443864676) + assert legenp(2+j, 3+4j, -j).ae(134742.98773236786148 + 429782.72924463851745j) + assert legenp(2+j, 3+4j, -j, type=3).ae(802.59463394152268507 - 251.62481308942906447j) + assert legenp(2,4,3).ae(0) + assert legenp(2,4,3,type=3).ae(0) + assert legenp(2,1,0.5).ae(-1.2990381056766579701) + assert legenp(2,1,0.5,type=3).ae(1.2990381056766579701j) + assert legenp(3,2,3).ae(-360) + assert legenp(3,3,3).ae(240j*2**0.5) + assert legenp(3,4,3).ae(0) + assert legenp(0,0.5,2).ae(0.52503756790433198939 - 0.52503756790433198939j) + assert legenp(-1,-0.5,2).ae(0.60626116232846498110 + 0.60626116232846498110j) + assert legenp(-2,0.5,2).ae(1.5751127037129959682 - 1.5751127037129959682j) + assert legenp(-2,0.5,-0.5).ae(-0.85738275810499171286) + +def test_legenq(): + f = legenq + # Evaluation at poles + assert isnan(f(3,2,1)) + assert isnan(f(3,2,-1)) + assert isnan(f(3,2,1,type=3)) + assert isnan(f(3,2,-1,type=3)) + # Evaluation at 0 + assert f(0,1,0,type=2).ae(-1) + assert f(-2,2,0,type=2,zeroprec=200).ae(0) + assert f(1.5,3,0,type=2).ae(-2.2239343475841951023) + assert f(0,1,0,type=3).ae(j) + assert f(-2,2,0,type=3,zeroprec=200).ae(0) + assert f(1.5,3,0,type=3).ae(2.2239343475841951022*(1-1j)) + # Standard case, degree 0 + assert f(0,0,-1.5).ae(-0.8047189562170501873 + 1.5707963267948966192j) + assert f(0,0,-0.5).ae(-0.54930614433405484570) + assert f(0,0,0,zeroprec=200).ae(0) + assert f(0,0,0.5).ae(0.54930614433405484570) + assert f(0,0,1.5).ae(0.8047189562170501873 - 1.5707963267948966192j) + assert f(0,0,-1.5,type=3).ae(-0.80471895621705018730) + assert f(0,0,-0.5,type=3).ae(-0.5493061443340548457 - 1.5707963267948966192j) + assert f(0,0,0,type=3).ae(-1.5707963267948966192j) + assert f(0,0,0.5,type=3).ae(0.5493061443340548457 - 1.5707963267948966192j) + assert f(0,0,1.5,type=3).ae(0.80471895621705018730) + # Standard case, degree 1 + assert f(1,0,-1.5).ae(0.2070784343255752810 - 2.3561944901923449288j) + assert f(1,0,-0.5).ae(-0.72534692783297257715) + assert f(1,0,0).ae(-1) + assert f(1,0,0.5).ae(-0.72534692783297257715) + assert f(1,0,1.5).ae(0.2070784343255752810 - 2.3561944901923449288j) + # Standard case, degree 2 + assert f(2,0,-1.5).ae(-0.0635669991240192885 + 4.5160394395353277803j) + assert f(2,0,-0.5).ae(0.81866326804175685571) + assert f(2,0,0,zeroprec=200).ae(0) + assert f(2,0,0.5).ae(-0.81866326804175685571) + assert f(2,0,1.5).ae(0.0635669991240192885 - 4.5160394395353277803j) + # Misc orders and degrees + assert f(2,3,1.5,type=2).ae(-5.7243340223994616228j) + assert f(2,3,1.5,type=3).ae(-5.7243340223994616228) + assert f(2,3,0.5,type=2).ae(-12.316805742712016310) + assert f(2,3,0.5,type=3).ae(-12.316805742712016310j) + assert f(2,3,-1.5,type=2).ae(-5.7243340223994616228j) + assert f(2,3,-1.5,type=3).ae(5.7243340223994616228) + assert f(2,3,-0.5,type=2).ae(-12.316805742712016310) + assert f(2,3,-0.5,type=3).ae(-12.316805742712016310j) + assert f(2+3j, 3+4j, 0.5, type=3).ae(0.0016119404873235186807 - 0.0005885900510718119836j) + assert f(2+3j, 3+4j, -1.5, type=3).ae(0.008451400254138808670 + 0.020645193304593235298j) + assert f(-2.5,1,-1.5).ae(3.9553395527435335749j) + assert f(-2.5,1,-0.5).ae(1.9290561746445456908) + assert f(-2.5,1,0).ae(1.2708196271909686299) + assert f(-2.5,1,0.5).ae(-0.31584812990742202869) + assert f(-2.5,1,1.5).ae(-3.9553395527435335742 + 0.2993235655044701706j) + assert f(-2.5,1,-1.5,type=3).ae(0.29932356550447017254j) + assert f(-2.5,1,-0.5,type=3).ae(-0.3158481299074220287 - 1.9290561746445456908j) + assert f(-2.5,1,0,type=3).ae(1.2708196271909686292 - 1.2708196271909686299j) + assert f(-2.5,1,0.5,type=3).ae(1.9290561746445456907 + 0.3158481299074220287j) + assert f(-2.5,1,1.5,type=3).ae(-0.29932356550447017254) + +def test_agm(): + assert agm(0,0) == 0 + assert agm(0,1) == 0 + assert agm(1,1) == 1 + assert agm(7,7) == 7 + assert agm(j,j) == j + assert (1/agm(1,sqrt(2))).ae(0.834626841674073186) + assert agm(1,2).ae(1.4567910310469068692) + assert agm(1,3).ae(1.8636167832448965424) + assert agm(1,j).ae(0.599070117367796104+0.599070117367796104j) + assert agm(2) == agm(1,2) + assert agm(-3,4).ae(0.63468509766550907+1.3443087080896272j) + +def test_gammainc(): + assert upper_gamma(2,5).ae(6*exp(-5)) + assert lower_gamma(2,5).ae(1-6*exp(-5)) + assert gammainc(2,3,5).ae(-6*exp(-5)+4*exp(-3)) + assert upper_gamma(-2.5,-0.5).ae(-0.9453087204829418812-5.3164237738936178621j) + assert gammainc(0,2,4).ae(0.045121158298212213088) + assert upper_gamma(0,3).ae(0.013048381094197037413) + assert gammainc(0,2+j,1-j).ae(0.00910653685850304839-0.22378752918074432574j) + assert upper_gamma(0,1-j).ae(0.00028162445198141833+0.17932453503935894015j) + assert gammainc(3,4,5,True).ae(0.11345128607046320253) + assert gammainc(3.5,0).ae(gamma(3.5)) + assert upper_gamma(-150.5,500).ae('6.9825435345798951153e-627') + assert upper_gamma(-150.5,800).ae('4.6885137549474089431e-788') + assert upper_gamma(-3.5,-20.5).ae(0.27008820585226911 - 1310.31447140574997636j) + assert upper_gamma(-3.5,-200.5).ae(0.27008820585226911 - 5.3264597096208368435e76j) # XXX real part + assert lower_gamma(0,2) == inf + assert gammainc(1,b=1).ae(0.6321205588285576784) + assert gammainc(3,2,2) == 0 + assert gammainc(2,3+j,3-j).ae(-0.28135485191849314194j) + assert upper_gamma(4+0j,1).ae(5.8860710587430771455) + # GH issue #301 + assert upper_gamma(-1,-1).ae(-0.8231640121031084799 + 3.1415926535897932385j) + assert upper_gamma(-2,-1).ae(1.7707229202810768576 - 1.5707963267948966192j) + assert upper_gamma(-3,-1).ae(-1.4963349162467073643 + 0.5235987755982988731j) + assert upper_gamma(-4,-1).ae(1.05365418617643814992 - 0.13089969389957471827j) + # Regularized upper gamma + assert isnan(gammainc(0, 0, regularized=True)) + assert gammainc(-1, 0, regularized=True) == inf + assert gammainc(1, 0, regularized=True) == 1 + assert upper_gamma(0,5, regularized=True) == 0 + assert upper_gamma(0,2+3j, regularized=True) == 0 + assert upper_gamma(0,5000, regularized=True) == 0 + assert gammainc(0, 10**30, regularized=True) == 0 + assert gammainc(-1, 5, regularized=True) == 0 + assert gammainc(-1, 5000, regularized=True) == 0 + assert gammainc(-1, 10**30, regularized=True) == 0 + assert gammainc(-1, -5, regularized=True) == 0 + assert gammainc(-1, -5000, regularized=True) == 0 + assert gammainc(-1, -10**30, regularized=True) == 0 + assert gammainc(-1, 3+4j, regularized=True) == 0 + assert upper_gamma(1,5, regularized=True).ae(exp(-5)) + assert upper_gamma(1,5000, regularized=True).ae(exp(-5000)) + assert gammainc(1, 10**30, regularized=True).ae(exp(-10**30)) + assert upper_gamma(1,3+4j, regularized=True).ae(exp(-3-4j)) + assert upper_gamma(-1000000,2).ae('1.3669297209397347754e-301037', abs_eps=0, rel_eps=8*eps) + assert gammainc(-1000000,2,regularized=True) == 0 + assert upper_gamma(-1000000,3+4j).ae('-1.322575609404222361e-698979 - 4.9274570591854533273e-698978j', abs_eps=0, rel_eps=8*eps) + assert gammainc(-1000000,3+4j,regularized=True) == 0 + assert upper_gamma(2+3j,4+5j, regularized=True).ae(0.085422013530993285774-0.052595379150390078503j) + assert upper_gamma(1000j,1000j, regularized=True).ae(0.49702647628921131761 + 0.00297355675013575341j) + # Generalized + assert gammainc(3,4,2) == -gammainc(3,2,4) + assert gammainc(4, 2, 3).ae(1.2593494302978947396) + assert gammainc(4, 2, 3, regularized=True).ae(0.20989157171631578993) + assert gammainc(0, 2, 3).ae(0.035852129613864082155) + assert gammainc(0, 2, 3, regularized=True) == 0 + assert gammainc(-1, 2, 3).ae(0.015219822548487616132) + assert gammainc(-1, 2, 3, regularized=True) == 0 + assert gammainc(0, 2, 3).ae(0.035852129613864082155) + assert gammainc(0, 2, 3, regularized=True) == 0 + # Should use upper gammas + assert gammainc(5, 10000, 12000).ae('1.1359381951461801687e-4327', abs_eps=0, rel_eps=8*eps) + # Should use lower gammas + assert gammainc(10000, 2, 3).ae('8.1244514125995785934e4765') + # GH issue 306 + assert upper_gamma(3,-1-1j) == 0 + assert upper_gamma(3,-1+1j) == 0 + assert upper_gamma(2,-1) == 0 + assert upper_gamma(2,-1+0j) == 0 + assert upper_gamma(2+0j,-1) == 0 + +def test_gammainc_expint_n(): + # These tests are intended to check all cases of the low-level code + # for upper gamma and expint with small integer index. + # Need to cover positive/negative arguments; small/large/huge arguments + # for both positive and negative indices, as well as indices 0 and 1 + # which may be special-cased + assert expint(-3,3.5).ae(0.021456366563296693987) + assert expint(-2,3.5).ae(0.014966633183073309405) + assert expint(-1,3.5).ae(0.011092916359219041088) + assert expint(0,3.5).ae(0.0086278238349481430685) + assert expint(1,3.5).ae(0.0069701398575483929193) + assert expint(2,3.5).ae(0.0058018939208991255223) + assert expint(3,3.5).ae(0.0049453773495857807058) + assert expint(-3,-3.5).ae(-4.6618170604073311319) + assert expint(-2,-3.5).ae(-5.5996974157555515963) + assert expint(-1,-3.5).ae(-6.7582555017739415818) + assert expint(0,-3.5).ae(-9.4615577024835182145) + assert expint(1,-3.5).ae(-13.925353995152335292 - 3.1415926535897932385j) + assert expint(2,-3.5).ae(-15.62328702434085977 - 10.995574287564276335j) + assert expint(3,-3.5).ae(-10.783026313250347722 - 19.242255003237483586j) + assert expint(-3,350).ae(2.8614825451252838069e-155, abs_eps=0, rel_eps=8*eps) + assert expint(-2,350).ae(2.8532837224504675901e-155, abs_eps=0, rel_eps=8*eps) + assert expint(-1,350).ae(2.8451316155828634555e-155, abs_eps=0, rel_eps=8*eps) + assert expint(0,350).ae(2.8370258275042797989e-155, abs_eps=0, rel_eps=8*eps) + assert expint(1,350).ae(2.8289659656701459404e-155, abs_eps=0, rel_eps=8*eps) + assert expint(2,350).ae(2.8209516419468505006e-155, abs_eps=0, rel_eps=8*eps) + assert expint(3,350).ae(2.8129824725501272171e-155, abs_eps=0, rel_eps=8*eps) + assert expint(-3,-350).ae(-2.8528796154044839443e+149) + assert expint(-2,-350).ae(-2.8610072121701264351e+149) + assert expint(-1,-350).ae(-2.8691813842677537647e+149) + assert expint(0,-350).ae(-2.8774025343659421709e+149) + u = expint(1,-350) + assert u.ae(-2.8856710698020863568e+149) + assert u.imag.ae(-3.1415926535897932385) + u = expint(2,-350) + assert u.ae(-2.8939874026504650534e+149) + assert u.imag.ae(-1099.5574287564276335) + u = expint(3,-350) + assert u.ae(-2.9023519497915044349e+149) + assert u.imag.ae(-192422.55003237483586) + assert expint(-3,350000000000000000000000).ae('2.1592908471792544286e-152003068666138139677919', abs_eps=0, rel_eps=8*eps) + assert expint(-2,350000000000000000000000).ae('2.1592908471792544286e-152003068666138139677919', abs_eps=0, rel_eps=8*eps) + assert expint(-1,350000000000000000000000).ae('2.1592908471792544286e-152003068666138139677919', abs_eps=0, rel_eps=8*eps) + assert expint(0,350000000000000000000000).ae('2.1592908471792544286e-152003068666138139677919', abs_eps=0, rel_eps=8*eps) + assert expint(1,350000000000000000000000).ae('2.1592908471792544286e-152003068666138139677919', abs_eps=0, rel_eps=8*eps) + assert expint(2,350000000000000000000000).ae('2.1592908471792544286e-152003068666138139677919', abs_eps=0, rel_eps=8*eps) + assert expint(3,350000000000000000000000).ae('2.1592908471792544286e-152003068666138139677919', abs_eps=0, rel_eps=8*eps) + assert expint(-3,-350000000000000000000000).ae('-3.7805306852415755699e+152003068666138139677871') + assert expint(-2,-350000000000000000000000).ae('-3.7805306852415755699e+152003068666138139677871') + assert expint(-1,-350000000000000000000000).ae('-3.7805306852415755699e+152003068666138139677871') + assert expint(0,-350000000000000000000000).ae('-3.7805306852415755699e+152003068666138139677871') + u = expint(1,-350000000000000000000000) + assert u.ae('-3.7805306852415755699e+152003068666138139677871') + assert u.imag.ae(-3.1415926535897932385) + u = expint(2,-350000000000000000000000) + assert u.imag.ae(-1.0995574287564276335e+24) + assert u.ae('-3.7805306852415755699e+152003068666138139677871') + u = expint(3,-350000000000000000000000) + assert u.imag.ae(-1.9242255003237483586e+47) + assert u.ae('-3.7805306852415755699e+152003068666138139677871') + # Small case; no branch cut + assert upper_gamma(-3,3.5).ae(0.00010020262545203707109) + assert upper_gamma(-2,3.5).ae(0.00040370427343557393517) + assert upper_gamma(-1,3.5).ae(0.0016576839773997501492) + assert upper_gamma(0,3.5).ae(0.0069701398575483929193) + assert upper_gamma(1,3.5).ae(0.03019738342231850074) + assert upper_gamma(2,3.5).ae(0.13588822540043325333) + assert upper_gamma(3,3.5).ae(0.64169439772426814072) + # Small case; with branch cut + assert upper_gamma(-3,-3.5).ae(0.03595832954467563286 + 0.52359877559829887308j) + assert upper_gamma(-2,-3.5).ae(-0.88024704597962022221 - 1.5707963267948966192j) + assert upper_gamma(-1,-3.5).ae(4.4637962926688170771 + 3.1415926535897932385j) + assert upper_gamma(0,-3.5).ae(-13.925353995152335292 - 3.1415926535897932385j) + assert upper_gamma(1,-3.5).ae(33.115451958692313751) + assert upper_gamma(2,-3.5).ae(-82.788629896730784377) + assert upper_gamma(3,-3.5).ae(240.08702670051927469) + # Asymptotic case; no branch cut + assert upper_gamma(-3,350).ae(6.5424095113340358813e-163, abs_eps=0, rel_eps=8*eps) + assert upper_gamma(-2,350).ae(2.296312222489899769e-160, abs_eps=0, rel_eps=8*eps) + assert upper_gamma(-1,350).ae(8.059861834133858573e-158, abs_eps=0, rel_eps=8*eps) + assert upper_gamma(0,350).ae(2.8289659656701459404e-155, abs_eps=0, rel_eps=8*eps) + assert upper_gamma(1,350).ae(9.9295903962649792963e-153, abs_eps=0, rel_eps=8*eps) + assert upper_gamma(2,350).ae(3.485286229089007733e-150, abs_eps=0, rel_eps=8*eps) + assert upper_gamma(3,350).ae(1.2233453960006379793e-147, abs_eps=0, rel_eps=8*eps) + # Asymptotic case; branch cut + u = upper_gamma(-3,-350) + assert u.ae(6.7889565783842895085e+141) + assert u.imag.ae(0.52359877559829887308) + u = upper_gamma(-2,-350) + assert u.ae(-2.3692668977889832121e+144) + assert u.imag.ae(-1.5707963267948966192) + u = upper_gamma(-1,-350) + assert u.ae(8.2685354361441858669e+146) + assert u.imag.ae(3.1415926535897932385) + u = upper_gamma(0,-350) + assert u.ae(-2.8856710698020863568e+149) + assert u.imag.ae(-3.1415926535897932385) + u = upper_gamma(1,-350) + assert u.ae(1.0070908870280797598e+152) + assert u.imag == 0 + u = upper_gamma(2,-350) + assert u.ae(-3.5147471957279983618e+154) + assert u.imag == 0 + u = upper_gamma(3,-350) + assert u.ae(1.2266568422179417091e+157) + assert u.imag == 0 + # Extreme asymptotic case + assert upper_gamma(-3,350000000000000000000000).ae('5.0362468738874738859e-152003068666138139677990', abs_eps=0, rel_eps=8*eps) + assert upper_gamma(-2,350000000000000000000000).ae('1.7626864058606158601e-152003068666138139677966', abs_eps=0, rel_eps=8*eps) + assert upper_gamma(-1,350000000000000000000000).ae('6.1694024205121555102e-152003068666138139677943', abs_eps=0, rel_eps=8*eps) + assert upper_gamma(0,350000000000000000000000).ae('2.1592908471792544286e-152003068666138139677919', abs_eps=0, rel_eps=8*eps) + assert upper_gamma(1,350000000000000000000000).ae('7.5575179651273905e-152003068666138139677896', abs_eps=0, rel_eps=8*eps) + assert upper_gamma(2,350000000000000000000000).ae('2.645131287794586675e-152003068666138139677872', abs_eps=0, rel_eps=8*eps) + assert upper_gamma(3,350000000000000000000000).ae('9.2579595072810533625e-152003068666138139677849', abs_eps=0, rel_eps=8*eps) + u = upper_gamma(-3,-350000000000000000000000) + assert u.ae('8.8175642804468234866e+152003068666138139677800') + assert u.imag.ae(0.52359877559829887308) + u = upper_gamma(-2,-350000000000000000000000) + assert u.ae('-3.0861474981563882203e+152003068666138139677824') + assert u.imag.ae(-1.5707963267948966192) + u = upper_gamma(-1,-350000000000000000000000) + assert u.ae('1.0801516243547358771e+152003068666138139677848') + assert u.imag.ae(3.1415926535897932385) + u = upper_gamma(0,-350000000000000000000000) + assert u.ae('-3.7805306852415755699e+152003068666138139677871') + assert u.imag.ae(-3.1415926535897932385) + assert upper_gamma(1,-350000000000000000000000).ae('1.3231857398345514495e+152003068666138139677895') + assert upper_gamma(2,-350000000000000000000000).ae('-4.6311500894209300731e+152003068666138139677918') + assert upper_gamma(3,-350000000000000000000000).ae('1.6209025312973255256e+152003068666138139677942') + +def test_incomplete_beta(): + assert betainc(-2,-3,0.5,0.75).ae(63.4305673311255413583969) + assert betainc(4.5,0.5+2j,2.5,6).ae(0.2628801146130621387903065 + 0.5162565234467020592855378j) + assert betainc(4,5,0,6).ae(90747.77142857142857142857) + +def test_erf(): + assert erf(0) == 0 + assert erf(1).ae(0.84270079294971486934) + assert erf(3+4j).ae(-120.186991395079444098 - 27.750337293623902498j) + assert erf(-4-3j).ae(-0.99991066178539168236 + 0.00004972026054496604j) + assert erf(pi).ae(0.99999112385363235839) + assert erf(1j).ae(1.6504257587975428760j) + assert erf(-1j).ae(-1.6504257587975428760j) + assert isinstance(erf(1), mpf) + assert isinstance(erf(-1), mpf) + assert isinstance(erf(0), mpf) + assert isinstance(erf(0j), mpc) + assert erf(inf) == 1 + assert erf(-inf) == -1 + assert erfi(0) == 0 + assert erfi(1/pi).ae(0.371682698493894314) + assert erfi(inf) == inf + assert erfi(-inf) == -inf + assert erf(1+0j) == erf(1) + assert erfc(1+0j) == erfc(1) + assert erf(0.2+0.5j).ae(1 - erfc(0.2+0.5j)) + assert erfc(0) == 1 + assert erfc(1).ae(1-erf(1)) + assert erfc(-1).ae(1-erf(-1)) + assert erfc(1/pi).ae(1-erf(1/pi)) + assert erfc(-10) == 2 + assert erfc(-1000000) == 2 + assert erfc(-inf) == 2 + assert erfc(inf) == 0 + assert isnan(erfc(nan)) + assert (erfc(10**4)*mpf(10)**43429453).ae('3.63998738656420') + assert erf(8+9j).ae(-1072004.2525062051158 + 364149.91954310255423j) + assert erfc(8+9j).ae(1072005.2525062051158 - 364149.91954310255423j) + assert erfc(-8-9j).ae(-1072003.2525062051158 + 364149.91954310255423j) + mp.dps = 50 + # This one does not use the asymptotic series + assert (erfc(10)*10**45).ae('2.0884875837625447570007862949577886115608181193212') + # This one does + assert (erfc(50)*10**1088).ae('2.0709207788416560484484478751657887929322509209954') + mp.dps = 15 + assert str(erfc(10**50)) == '3.66744826532555e-4342944819032518276511289189166050822943970058036665661144537831658646492088707747292249493384317534' + assert erfinv(0) == 0 + assert erfinv(0.5).ae(0.47693627620446987338) + assert erfinv(-0.5).ae(-0.47693627620446987338) + assert erfinv(1) == inf + assert erfinv(-1) == -inf + assert erf(erfinv(0.95)).ae(0.95) + assert erf(erfinv(0.999999999995)).ae(0.999999999995) + assert erf(erfinv(-0.999999999995)).ae(-0.999999999995) + mp.dps = 50 + assert erf(erfinv('0.99999999999999999999999999999995')).ae('0.99999999999999999999999999999995') + assert erf(erfinv('0.999999999999999999999999999999995')).ae('0.999999999999999999999999999999995') + assert erf(erfinv('-0.999999999999999999999999999999995')).ae('-0.999999999999999999999999999999995') + mp.dps = 15 + # Complex asymptotic expansions + v = erfc(50j) + assert v.real == 1 + assert v.imag.ae('-6.1481820666053078736e+1083') + assert erfc(-100+5j).ae(2) + assert (erfc(100+5j)*10**4335).ae(2.3973567853824133572 - 3.9339259530609420597j) + assert erfc(100+100j).ae(0.00065234366376857698698 - 0.0039357263629214118437j) + +def test_pdf(): + assert npdf(-inf) == 0 + assert npdf(inf) == 0 + assert npdf(5,0,2).ae(npdf(5+4,4,2)) + assert quadts(lambda x: npdf(x,-0.5,0.8), [-inf, inf]) == 1 + assert ncdf(0) == 0.5 + assert ncdf(3,3) == 0.5 + assert ncdf(-inf) == 0 + assert ncdf(inf) == 1 + assert ncdf(10) == 1 + # Verify that this is computed accurately + assert (ncdf(-10)*10**24).ae(7.619853024160526) + +def test_lambertw(): + assert lambertw(0) == 0 + assert lambertw(0+0j) == 0 + assert lambertw(inf) == inf + assert isnan(lambertw(nan)) + assert lambertw(inf,1).real == inf + assert lambertw(inf,1).imag.ae(2*pi) + assert lambertw(-inf,1).real == inf + assert lambertw(-inf,1).imag.ae(3*pi) + assert lambertw(0,-1) == -inf + assert lambertw(0,1) == -inf + assert lambertw(0,3) == -inf + assert lambertw(e).ae(1) + assert lambertw(1).ae(0.567143290409783873) + assert lambertw(-pi/2).ae(j*pi/2) + assert lambertw(-log(2)/2).ae(-log(2)) + assert lambertw(0.25).ae(0.203888354702240164) + assert lambertw(-0.25).ae(-0.357402956181388903) + assert lambertw(-1./10000,0).ae(-0.000100010001500266719) + assert lambertw(-0.25,-1).ae(-2.15329236411034965) + assert lambertw(0.25,-1).ae(-3.00899800997004620-4.07652978899159763j) + assert lambertw(-0.25,-1).ae(-2.15329236411034965) + assert lambertw(0.25,1).ae(-3.00899800997004620+4.07652978899159763j) + assert lambertw(-0.25,1).ae(-3.48973228422959210+7.41405453009603664j) + assert lambertw(-4).ae(0.67881197132094523+1.91195078174339937j) + assert lambertw(-4,1).ae(-0.66743107129800988+7.76827456802783084j) + assert lambertw(-4,-1).ae(0.67881197132094523-1.91195078174339937j) + assert lambertw(1000).ae(5.24960285240159623) + assert lambertw(1000,1).ae(4.91492239981054535+5.44652615979447070j) + assert lambertw(1000,-1).ae(4.91492239981054535-5.44652615979447070j) + assert lambertw(1000,5).ae(3.5010625305312892+29.9614548941181328j) + assert lambertw(3+4j).ae(1.281561806123775878+0.533095222020971071j) + assert lambertw(-0.4+0.4j).ae(-0.10396515323290657+0.61899273315171632j) + assert lambertw(3+4j,1).ae(-0.11691092896595324+5.61888039871282334j) + assert lambertw(3+4j,-1).ae(0.25856740686699742-3.85211668616143559j) + assert lambertw(-0.5,-1).ae(-0.794023632344689368-0.770111750510379110j) + assert lambertw(-1./10000,1).ae(-11.82350837248724344+6.80546081842002101j) + assert lambertw(-1./10000,-1).ae(-11.6671145325663544) + assert lambertw(-1./10000,-2).ae(-11.82350837248724344-6.80546081842002101j) + assert lambertw(-1./100000,4).ae(-14.9186890769540539+26.1856750178782046j) + assert lambertw(-1./100000,5).ae(-15.0931437726379218666+32.5525721210262290086j) + assert lambertw((2+j)/10).ae(0.173704503762911669+0.071781336752835511j) + assert lambertw((2+j)/10,1).ae(-3.21746028349820063+4.56175438896292539j) + assert lambertw((2+j)/10,-1).ae(-3.03781405002993088-3.53946629633505737j) + assert lambertw((2+j)/10,4).ae(-4.6878509692773249+23.8313630697683291j) + assert lambertw(-(2+j)/10).ae(-0.226933772515757933-0.164986470020154580j) + assert lambertw(-(2+j)/10,1).ae(-2.43569517046110001+0.76974067544756289j) + assert lambertw(-(2+j)/10,-1).ae(-3.54858738151989450-6.91627921869943589j) + assert lambertw(-(2+j)/10,4).ae(-4.5500846928118151+20.6672982215434637j) + mp.dps = 50 + assert lambertw(pi).ae('1.073658194796149172092178407024821347547745350410314531') + mp.dps = 15 + # Former bug in generated branch + assert lambertw(-0.5+0.002j).ae(-0.78917138132659918344 + 0.76743539379990327749j) + assert lambertw(-0.5-0.002j).ae(-0.78917138132659918344 - 0.76743539379990327749j) + assert lambertw(-0.448+0.4j).ae(-0.11855133765652382241 + 0.66570534313583423116j) + assert lambertw(-0.448-0.4j).ae(-0.11855133765652382241 - 0.66570534313583423116j) + assert lambertw(-0.65475+0.0001j).ae(-0.61053421111385310898+1.0396534993944097723803j) + # Huge branch index + w = lambertw(1,10**20) + assert w.real.ae(-47.889578926290259164) + assert w.imag.ae(6.2831853071795864769e+20) + +def test_lambertw_hard(): + def check(x,y): + y = convert(y) + type_ok = True + if isinstance(y, mpf): + type_ok = isinstance(x, mpf) + real_ok = abs(x.real-y.real) <= abs(y.real)*8*eps + imag_ok = abs(x.imag-y.imag) <= abs(y.imag)*8*eps + #print x, y, abs(x.real-y.real), abs(x.imag-y.imag) + return real_ok and imag_ok + # Evaluation near 0 + mp.dps = 15 + assert check(lambertw(1e-10), 9.999999999000000000e-11) + assert check(lambertw(-1e-10), -1.000000000100000000e-10) + assert check(lambertw(1e-10j), 9.999999999999999999733e-21 + 9.99999999999999999985e-11j) + assert check(lambertw(-1e-10j), 9.999999999999999999733e-21 - 9.99999999999999999985e-11j) + assert check(lambertw(1e-10,1), -26.303186778379041559 + 3.265093911703828397j) + assert check(lambertw(-1e-10,1), -26.326236166739163892 + 6.526183280686333315j) + assert check(lambertw(1e-10j,1), -26.312931726911421551 + 4.896366881798013421j) + assert check(lambertw(-1e-10j,1), -26.297238779529035066 + 1.632807161345576513j) + assert check(lambertw(1e-10,-1), -26.303186778379041559 - 3.265093911703828397j) + assert check(lambertw(-1e-10,-1), -26.295238819246925694) + assert check(lambertw(1e-10j,-1), -26.297238779529035028 - 1.6328071613455765135j) + assert check(lambertw(-1e-10j,-1), -26.312931726911421551 - 4.896366881798013421j) + # Test evaluation very close to the branch point -1/e + # on the -1, 0, and 1 branches + add = lambda x, y: fadd(x,y,exact=True) + sub = lambda x, y: fsub(x,y,exact=True) + addj = lambda x, y: fadd(x,fmul(y,1j,exact=True),exact=True) + subj = lambda x, y: fadd(x,fmul(y,-1j,exact=True),exact=True) + mp.dps = 1500 + a = -1/e + 10*eps + d3 = mpf('1e-3') + d10 = mpf('1e-10') + d20 = mpf('1e-20') + d40 = mpf('1e-40') + d80 = mpf('1e-80') + d300 = mpf('1e-300') + d1000 = mpf('1e-1000') + mp.dps = 15 + # ---- Branch 0 ---- + # -1/e + eps + assert check(lambertw(add(a,d3)), -0.92802015005456704876) + assert check(lambertw(add(a,d10)), -0.99997668374140088071) + assert check(lambertw(add(a,d20)), -0.99999999976683560186) + assert lambertw(add(a,d40)) == -1 + assert lambertw(add(a,d80)) == -1 + assert lambertw(add(a,d300)) == -1 + assert lambertw(add(a,d1000)) == -1 + # -1/e - eps + assert check(lambertw(sub(a,d3)), -0.99819016149860989001+0.07367191188934638577j) + assert check(lambertw(sub(a,d10)), -0.9999999998187812114595992+0.0000233164398140346109194j) + assert check(lambertw(sub(a,d20)), -0.99999999999999999998187+2.331643981597124203344e-10j) + assert check(lambertw(sub(a,d40)), -1.0+2.33164398159712420336e-20j) + assert check(lambertw(sub(a,d80)), -1.0+2.33164398159712420336e-40j) + assert check(lambertw(sub(a,d300)), -1.0+2.33164398159712420336e-150j) + assert check(lambertw(sub(a,d1000)), mpc(-1,'2.33164398159712420336e-500')) + # -1/e + eps*j + assert check(lambertw(addj(a,d3)), -0.94790387486938526634+0.05036819639190132490j) + assert check(lambertw(addj(a,d10)), -0.9999835127872943680999899+0.0000164870314895821225256j) + assert check(lambertw(addj(a,d20)), -0.999999999835127872929987+1.64872127051890935830e-10j) + assert check(lambertw(addj(a,d40)), -0.9999999999999999999835+1.6487212707001281468305e-20j) + assert check(lambertw(addj(a,d80)), -1.0 + 1.64872127070012814684865e-40j) + assert check(lambertw(addj(a,d300)), -1.0 + 1.64872127070012814684865e-150j) + assert check(lambertw(addj(a,d1000)), mpc(-1.0,'1.64872127070012814684865e-500')) + # -1/e - eps*j + assert check(lambertw(subj(a,d3)), -0.94790387486938526634-0.05036819639190132490j) + assert check(lambertw(subj(a,d10)), -0.9999835127872943680999899-0.0000164870314895821225256j) + assert check(lambertw(subj(a,d20)), -0.999999999835127872929987-1.64872127051890935830e-10j) + assert check(lambertw(subj(a,d40)), -0.9999999999999999999835-1.6487212707001281468305e-20j) + assert check(lambertw(subj(a,d80)), -1.0 - 1.64872127070012814684865e-40j) + assert check(lambertw(subj(a,d300)), -1.0 - 1.64872127070012814684865e-150j) + assert check(lambertw(subj(a,d1000)), mpc(-1.0,'-1.64872127070012814684865e-500')) + # ---- Branch 1 ---- + assert check(lambertw(addj(a,d3),1), -3.088501303219933378005990 + 7.458676867597474813950098j) + assert check(lambertw(addj(a,d80),1), -3.088843015613043855957087 + 7.461489285654254556906117j) + assert check(lambertw(addj(a,d300),1), -3.088843015613043855957087 + 7.461489285654254556906117j) + assert check(lambertw(addj(a,d1000),1), -3.088843015613043855957087 + 7.461489285654254556906117j) + assert check(lambertw(subj(a,d3),1), -1.0520914180450129534365906 + 0.0539925638125450525673175j) + assert check(lambertw(subj(a,d10),1), -1.0000164872127056318529390 + 0.000016487393927159250398333077j) + assert check(lambertw(subj(a,d20),1), -1.0000000001648721270700128 + 1.64872127088134693542628e-10j) + assert check(lambertw(subj(a,d40),1), -1.000000000000000000016487 + 1.64872127070012814686677e-20j) + assert check(lambertw(subj(a,d80),1), -1.0 + 1.64872127070012814684865e-40j) + assert check(lambertw(subj(a,d300),1), -1.0 + 1.64872127070012814684865e-150j) + assert check(lambertw(subj(a,d1000),1), mpc(-1.0, '1.64872127070012814684865e-500')) + # ---- Branch -1 ---- + # -1/e + eps + assert check(lambertw(add(a,d3),-1), -1.075608941186624989414945) + assert check(lambertw(add(a,d10),-1), -1.000023316621036696460620) + assert check(lambertw(add(a,d20),-1), -1.000000000233164398177834) + assert lambertw(add(a,d40),-1) == -1 + assert lambertw(add(a,d80),-1) == -1 + assert lambertw(add(a,d300),-1) == -1 + assert lambertw(add(a,d1000),-1) == -1 + # -1/e - eps + assert check(lambertw(sub(a,d3),-1), -0.99819016149860989001-0.07367191188934638577j) + assert check(lambertw(sub(a,d10),-1), -0.9999999998187812114595992-0.0000233164398140346109194j) + assert check(lambertw(sub(a,d20),-1), -0.99999999999999999998187-2.331643981597124203344e-10j) + assert check(lambertw(sub(a,d40),-1), -1.0-2.33164398159712420336e-20j) + assert check(lambertw(sub(a,d80),-1), -1.0-2.33164398159712420336e-40j) + assert check(lambertw(sub(a,d300),-1), -1.0-2.33164398159712420336e-150j) + assert check(lambertw(sub(a,d1000),-1), mpc(-1,'-2.33164398159712420336e-500')) + # -1/e + eps*j + assert check(lambertw(addj(a,d3),-1), -1.0520914180450129534365906 - 0.0539925638125450525673175j) + assert check(lambertw(addj(a,d10),-1), -1.0000164872127056318529390 - 0.0000164873939271592503983j) + assert check(lambertw(addj(a,d20),-1), -1.0000000001648721270700 - 1.64872127088134693542628e-10j) + assert check(lambertw(addj(a,d40),-1), -1.00000000000000000001648 - 1.6487212707001281468667726e-20j) + assert check(lambertw(addj(a,d80),-1), -1.0 - 1.64872127070012814684865e-40j) + assert check(lambertw(addj(a,d300),-1), -1.0 - 1.64872127070012814684865e-150j) + assert check(lambertw(addj(a,d1000),-1), mpc(-1.0,'-1.64872127070012814684865e-500')) + # -1/e - eps*j + assert check(lambertw(subj(a,d3),-1), -3.088501303219933378005990-7.458676867597474813950098j) + assert check(lambertw(subj(a,d10),-1), -3.088843015579260686911033-7.461489285372968780020716j) + assert check(lambertw(subj(a,d20),-1), -3.088843015613043855953708-7.461489285654254556877988j) + assert check(lambertw(subj(a,d40),-1), -3.088843015613043855957087-7.461489285654254556906117j) + assert check(lambertw(subj(a,d80),-1), -3.088843015613043855957087 - 7.461489285654254556906117j) + assert check(lambertw(subj(a,d300),-1), -3.088843015613043855957087 - 7.461489285654254556906117j) + assert check(lambertw(subj(a,d1000),-1), -3.088843015613043855957087 - 7.461489285654254556906117j) + # One more case, testing higher precision + mp.dps = 500 + x = -1/e + mpf('1e-13') + ans = "-0.99999926266961377166355784455394913638782494543377383"\ + "744978844374498153493943725364881490261187530235150668593869563"\ + "168276697689459394902153960200361935311512317183678882" + mp.dps = 15 + assert lambertw(x).ae(ans) + mp.dps = 50 + assert lambertw(x).ae(ans) + mp.dps = 150 + assert lambertw(x).ae(ans) + +def test_meijerg(): + assert meijerg([[2,3],[1]],[[0.5,2],[3,4]], 2.5).ae(4.2181028074787439386) + assert meijerg([[],[1+j]],[[1],[1]], 3+4j).ae(271.46290321152464592 - 703.03330399954820169j) + assert meijerg([[0.25],[1]],[[0.5],[2]],0) == 0 + assert meijerg([[0],[]],[[0,0,'1/3','2/3'], []], '2/27').ae(2.2019391389653314120) + # Verify 1/z series being used + assert meijerg([[-3],[-0.5]], [[-1],[-2.5]], -0.5).ae(-1.338096165935754898687431) + assert meijerg([[1-(-1)],[1-(-2.5)]], [[1-(-3)],[1-(-0.5)]], -2.0).ae(-1.338096165935754898687431) + assert meijerg([[-3],[-0.5]], [[-1],[-2.5]], -1).ae(-(pi+4)/(4*pi)) + a = 2.5 + b = 1.25 + for z in [mpf(0.25), mpf(2)]: + x1 = hyp1f1(a,b,z) + x2 = gamma(b)/gamma(a)*meijerg([[1-a],[]],[[0],[1-b]],-z) + x3 = gamma(b)/gamma(a)*meijerg([[1-0],[1-(1-b)]],[[1-(1-a)],[]],-1/z) + assert x1.ae(x2) + assert x1.ae(x3) + +def test_foxh(): + # from Mathematica, https://reference.wolfram.com/language/ref/FoxH.html + assert foxh([[(mpf('1/2'),1)],[(mpf('1/3'),2)]],[[(mpf('1/4'),3)],[(pi,4)]],mpf('0.2')).ae(0.014549867809356231) + assert foxh([[(mpf('1/10'),(6,5)), (mpf('13/10'),1)],[(mpf('17/5'),2)]],[[(mpf('7/5'),2)],[(mpf('1/5'),1)]],mpf('0.2')).ae(0.27964621202572) + # Equivalent by definition + b = 1 + B = 2 + z = mpf('0.2') + x1 = mpf(1)/B * (z ** (mpf(b)/B)) * exp(-z ** (mpf(1)/B)) + x2 = foxh([[],[]],[[(b,B)],[]],z) + x3 = meijerg([[],[]],[[b],[]],z,r=B)/B + assert x1.ae(x2) + assert x1.ae(x3) + # Test foxh with r != 1 + x2 = foxh([[],[]],[[(b,B)],[]],z,r=3) + x3 = meijerg([[],[]],[[b],[]],z,r=(3*B))/B + assert x2.ae(x3) + +def test_appellf1(): + assert appellf1(2,-2,1,1,2,3).ae(-1.75) + assert appellf1(2,1,-2,1,2,3).ae(-8) + assert appellf1(2,1,-2,1,0.5,0.25).ae(1.5) + assert appellf1(-2,1,3,2,3,3).ae(19) + assert appellf1(1,2,3,4,0.5,0.125).ae( 1.53843285792549786518) + +def test_coulomb(): + # Note: most tests are doctests + # Test for a bug: + assert coulombg(mpc(-5,0),2,3).ae(20.087729487721430394) + +def test_hyper_param_accuracy(): + As = [n+1e-10 for n in range(-5,-1)] + Bs = [n+1e-10 for n in range(-12,-5)] + assert hyper(As,Bs,10).ae(-381757055858.652671927) + assert legenp(0.5, 100, 0.25).ae(-2.4124576567211311755e+144) + assert (hyp1f1(1000,1,-100)*10**24).ae(5.2589445437370169113) + assert (hyp2f1(10, -900, 10.5, 0.99)*10**24).ae(1.9185370579660768203) + assert (hyp2f1(1000,1.5,-3.5,-1.5)*10**385).ae(-2.7367529051334000764) + assert hyp2f1(-5, 10, 3, 0.5, zeroprec=500) == 0 + assert (hyp1f1(-10000, 1000, 100)*10**424).ae(-3.1046080515824859974) + assert (hyp2f1(1000,1.5,-3.5,-0.75,maxterms=100000)*10**231).ae(-4.0534790813913998643) + assert (hyp2f1(1000,1.5,-3.5,-0.75,maxterms=10000)*10**231).ae(-4.0534790813913998643) + pytest.raises(mp.NoConvergence, lambda: mp.hyp2f1(1000,1.5,-3.5,-0.75,maxterms=10000,force_series=True)) + pytest.raises(fp.NoConvergence, lambda: fp.hyp2f1(1000,1.5,-3.5,-0.75,maxterms=10000,force_series=True)) + assert legenp(2, 3, 0.25) == 0 + pytest.raises(mp.NoConvergence, lambda: hypercomb(lambda a: [([],[],[],[],[a],[-a],0.5)], [3])) + assert hypercomb(lambda a: [([],[],[],[],[a],[-a],0.5)], [3], infprec=200) == inf + assert meijerg([[],[]],[[0,0,0,0],[]],0.1).ae(1.5680822343832351418) + assert (besselk(400,400)*10**94).ae(1.4387057277018550583) + mp.dps = 5 + (hyp1f1(-5000.5, 1500, 100)*10**185).ae(8.5185229673381935522) + (hyp1f1(-5000, 1500, 100)*10**185).ae(9.1501213424563944311) + mp.dps = 15 + (hyp1f1(-5000.5, 1500, 100)*10**185).ae(8.5185229673381935522) + (hyp1f1(-5000, 1500, 100)*10**185).ae(9.1501213424563944311) + assert hyp0f1(fadd(-20,'1e-100',exact=True), 0.25).ae(1.85014429040102783e+49) + assert hyp0f1((-20*10**100+1, 10**100), 0.25).ae(1.85014429040102783e+49) + +def test_hypercomb_zero_pow(): + # check that 0^0 = 1 + assert hypercomb(lambda a: (([0],[a],[],[],[],[],0),), [0]) == 1 + assert meijerg([[-1.5],[]],[[0],[-0.75]],0).ae(1.4464090846320771425) + +def test_spherharm(): + t = 0.5; r = 0.25 + assert spherharm(0,0,t,r).ae(0.28209479177387814347) + assert spherharm(1,-1,t,r).ae(0.16048941205971996369 - 0.04097967481096344271j) + assert spherharm(1,0,t,r).ae(0.42878904414183579379) + assert spherharm(1,1,t,r).ae(-0.16048941205971996369 - 0.04097967481096344271j) + assert spherharm(2,-2,t,r).ae(0.077915886919031181734 - 0.042565643022253962264j) + assert spherharm(2,-1,t,r).ae(0.31493387233497459884 - 0.08041582001959297689j) + assert spherharm(2,0,t,r).ae(0.41330596756220761898) + assert spherharm(2,1,t,r).ae(-0.31493387233497459884 - 0.08041582001959297689j) + assert spherharm(2,2,t,r).ae(0.077915886919031181734 + 0.042565643022253962264j) + assert spherharm(3,-3,t,r).ae(0.033640236589690881646 - 0.031339125318637082197j) + assert spherharm(3,-2,t,r).ae(0.18091018743101461963 - 0.09883168583167010241j) + assert spherharm(3,-1,t,r).ae(0.42796713930907320351 - 0.10927795157064962317j) + assert spherharm(3,0,t,r).ae(0.27861659336351639787) + assert spherharm(3,1,t,r).ae(-0.42796713930907320351 - 0.10927795157064962317j) + assert spherharm(3,2,t,r).ae(0.18091018743101461963 + 0.09883168583167010241j) + assert spherharm(3,3,t,r).ae(-0.033640236589690881646 - 0.031339125318637082197j) + assert spherharm(0,-1,t,r) == 0 + assert spherharm(0,-2,t,r) == 0 + assert spherharm(0,1,t,r) == 0 + assert spherharm(0,2,t,r) == 0 + assert spherharm(1,2,t,r) == 0 + assert spherharm(1,3,t,r) == 0 + assert spherharm(1,-2,t,r) == 0 + assert spherharm(1,-3,t,r) == 0 + assert spherharm(2,3,t,r) == 0 + assert spherharm(2,4,t,r) == 0 + assert spherharm(2,-3,t,r) == 0 + assert spherharm(2,-4,t,r) == 0 + assert spherharm(3,4.5,0.5,0.25).ae(-22.831053442240790148 + 10.910526059510013757j) + assert spherharm(2+3j, 1-j, 1+j, 3+4j).ae(-2.6582752037810116935 - 1.0909214905642160211j) + assert spherharm(-6,2.5,t,r).ae(0.39383644983851448178 + 0.28414687085358299021j) + assert spherharm(-3.5, 3, 0.5, 0.25).ae(0.014516852987544698924 - 0.015582769591477628495j) + assert spherharm(-3, 3, 0.5, 0.25) == 0 + assert spherharm(-6, 3, 0.5, 0.25).ae(-0.16544349818782275459 - 0.15412657723253924562j) + assert spherharm(-6, 1.5, 0.5, 0.25).ae(0.032208193499767402477 + 0.012678000924063664921j) + assert spherharm(3,0,0,1).ae(0.74635266518023078283) + assert spherharm(3,-2,0,1) == 0 + assert spherharm(3,-2,1,1).ae(-0.16270707338254028971 - 0.35552144137546777097j) + +def test_qfunctions(): + assert qp(2,3,100).ae('2.7291482267247332183e2391') + +def test_issue_239(): + mp.prec = 150 + x = ldexp(2476979795053773,-52) + assert betainc(206, 385, 0, 0.55, 1).ae('0.99999999999999999999996570910644857895771110649954') + mp.dps = 15 + expected_exc = ValueError + if platform.machine() == 's390x' and sys.version_info < (3, 14): + # This case has recursion depth beyond platform capabilities, that + # could be controlled with sys.setrecursionlimit(). See issue #1046 + # for details. + expected_exc = RecursionError + pytest.raises(expected_exc, lambda: hyp2f1(-5,5,0.5,0.5)) + +# Extra stress testing for Bessel functions +# Reference zeros generated with the aid of scipy.special +# jn_zero, jnp_zero, yn_zero, ynp_zero + +V = 15 +M = 15 + +jn_small_zeros = \ +[[2.4048255576957728, + 5.5200781102863106, + 8.6537279129110122, + 11.791534439014282, + 14.930917708487786, + 18.071063967910923, + 21.211636629879259, + 24.352471530749303, + 27.493479132040255, + 30.634606468431975, + 33.775820213573569, + 36.917098353664044, + 40.058425764628239, + 43.19979171317673, + 46.341188371661814], + [3.8317059702075123, + 7.0155866698156188, + 10.173468135062722, + 13.323691936314223, + 16.470630050877633, + 19.615858510468242, + 22.760084380592772, + 25.903672087618383, + 29.046828534916855, + 32.189679910974404, + 35.332307550083865, + 38.474766234771615, + 41.617094212814451, + 44.759318997652822, + 47.901460887185447], + [5.1356223018406826, + 8.4172441403998649, + 11.619841172149059, + 14.795951782351261, + 17.959819494987826, + 21.116997053021846, + 24.270112313573103, + 27.420573549984557, + 30.569204495516397, + 33.7165195092227, + 36.86285651128381, + 40.008446733478192, + 43.153453778371463, + 46.297996677236919, + 49.442164110416873], + [6.3801618959239835, + 9.7610231299816697, + 13.015200721698434, + 16.223466160318768, + 19.409415226435012, + 22.582729593104442, + 25.748166699294978, + 28.908350780921758, + 32.064852407097709, + 35.218670738610115, + 38.370472434756944, + 41.520719670406776, + 44.669743116617253, + 47.817785691533302, + 50.965029906205183], + [7.5883424345038044, + 11.064709488501185, + 14.37253667161759, + 17.615966049804833, + 20.826932956962388, + 24.01901952477111, + 27.199087765981251, + 30.371007667117247, + 33.537137711819223, + 36.699001128744649, + 39.857627302180889, + 43.01373772335443, + 46.167853512924375, + 49.320360686390272, + 52.471551398458023], + [8.771483815959954, + 12.338604197466944, + 15.700174079711671, + 18.980133875179921, + 22.217799896561268, + 25.430341154222704, + 28.626618307291138, + 31.811716724047763, + 34.988781294559295, + 38.159868561967132, + 41.326383254047406, + 44.489319123219673, + 47.649399806697054, + 50.80716520300633, + 53.963026558378149], + [9.9361095242176849, + 13.589290170541217, + 17.003819667816014, + 20.320789213566506, + 23.58608443558139, + 26.820151983411405, + 30.033722386570469, + 33.233041762847123, + 36.422019668258457, + 39.603239416075404, + 42.778481613199507, + 45.949015998042603, + 49.11577372476426, + 52.279453903601052, + 55.440592068853149], + [11.086370019245084, + 14.821268727013171, + 18.287582832481726, + 21.641541019848401, + 24.934927887673022, + 28.191188459483199, + 31.42279419226558, + 34.637089352069324, + 37.838717382853611, + 41.030773691585537, + 44.21540850526126, + 47.394165755570512, + 50.568184679795566, + 53.738325371963291, + 56.905249991978781], + [12.225092264004655, + 16.037774190887709, + 19.554536430997055, + 22.94517313187462, + 26.266814641176644, + 29.54565967099855, + 32.795800037341462, + 36.025615063869571, + 39.240447995178135, + 42.443887743273558, + 45.638444182199141, + 48.825930381553857, + 52.007691456686903, + 55.184747939289049, + 58.357889025269694], + [13.354300477435331, + 17.241220382489128, + 20.807047789264107, + 24.233885257750552, + 27.583748963573006, + 30.885378967696675, + 34.154377923855096, + 37.400099977156589, + 40.628553718964528, + 43.843801420337347, + 47.048700737654032, + 50.245326955305383, + 53.435227157042058, + 56.619580266508436, + 59.799301630960228], + [14.475500686554541, + 18.433463666966583, + 22.046985364697802, + 25.509450554182826, + 28.887375063530457, + 32.211856199712731, + 35.499909205373851, + 38.761807017881651, + 42.004190236671805, + 45.231574103535045, + 48.447151387269394, + 51.653251668165858, + 54.851619075963349, + 58.043587928232478, + 61.230197977292681], + [15.589847884455485, + 19.61596690396692, + 23.275853726263409, + 26.773322545509539, + 30.17906117878486, + 33.526364075588624, + 36.833571341894905, + 40.111823270954241, + 43.368360947521711, + 46.608132676274944, + 49.834653510396724, + 53.050498959135054, + 56.257604715114484, + 59.457456908388002, + 62.651217388202912], + [16.698249933848246, + 20.789906360078443, + 24.494885043881354, + 28.026709949973129, + 31.45996003531804, + 34.829986990290238, + 38.156377504681354, + 41.451092307939681, + 44.721943543191147, + 47.974293531269048, + 51.211967004101068, + 54.437776928325074, + 57.653844811906946, + 60.8618046824805, + 64.062937824850136], + [17.801435153282442, + 21.95624406783631, + 25.705103053924724, + 29.270630441874802, + 32.731053310978403, + 36.123657666448762, + 39.469206825243883, + 42.780439265447158, + 46.06571091157561, + 49.330780096443524, + 52.579769064383396, + 55.815719876305778, + 59.040934037249271, + 62.257189393731728, + 65.465883797232125], + [18.899997953174024, + 23.115778347252756, + 26.907368976182104, + 30.505950163896036, + 33.993184984781542, + 37.408185128639695, + 40.772827853501868, + 44.100590565798301, + 47.400347780543231, + 50.678236946479898, + 53.93866620912693, + 57.184898598119301, + 60.419409852130297, + 63.644117508962281, + 66.860533012260103]] + +jnp_small_zeros = \ +[[0.0, + 3.8317059702075123, + 7.0155866698156188, + 10.173468135062722, + 13.323691936314223, + 16.470630050877633, + 19.615858510468242, + 22.760084380592772, + 25.903672087618383, + 29.046828534916855, + 32.189679910974404, + 35.332307550083865, + 38.474766234771615, + 41.617094212814451, + 44.759318997652822], + [1.8411837813406593, + 5.3314427735250326, + 8.5363163663462858, + 11.706004902592064, + 14.863588633909033, + 18.015527862681804, + 21.16436985918879, + 24.311326857210776, + 27.457050571059246, + 30.601922972669094, + 33.746182898667383, + 36.889987409236811, + 40.033444053350675, + 43.176628965448822, + 46.319597561173912], + [3.0542369282271403, + 6.7061331941584591, + 9.9694678230875958, + 13.170370856016123, + 16.347522318321783, + 19.512912782488205, + 22.671581772477426, + 25.826037141785263, + 28.977672772993679, + 32.127327020443474, + 35.275535050674691, + 38.422654817555906, + 41.568934936074314, + 44.714553532819734, + 47.859641607992093], + [4.2011889412105285, + 8.0152365983759522, + 11.345924310743006, + 14.585848286167028, + 17.78874786606647, + 20.9724769365377, + 24.144897432909265, + 27.310057930204349, + 30.470268806290424, + 33.626949182796679, + 36.781020675464386, + 39.933108623659488, + 43.083652662375079, + 46.232971081836478, + 49.381300092370349], + [5.3175531260839944, + 9.2823962852416123, + 12.681908442638891, + 15.964107037731551, + 19.196028800048905, + 22.401032267689004, + 25.589759681386733, + 28.767836217666503, + 31.938539340972783, + 35.103916677346764, + 38.265316987088158, + 41.423666498500732, + 44.579623137359257, + 47.733667523865744, + 50.886159153182682], + [6.4156163757002403, + 10.519860873772308, + 13.9871886301403, + 17.312842487884625, + 20.575514521386888, + 23.803581476593863, + 27.01030789777772, + 30.20284907898166, + 33.385443901010121, + 36.560777686880356, + 39.730640230067416, + 42.896273163494417, + 46.058566273567043, + 49.218174614666636, + 52.375591529563596], + [7.501266144684147, + 11.734935953042708, + 15.268181461097873, + 18.637443009666202, + 21.931715017802236, + 25.183925599499626, + 28.409776362510085, + 31.617875716105035, + 34.81339298429743, + 37.999640897715301, + 41.178849474321413, + 44.352579199070217, + 47.521956905768113, + 50.687817781723741, + 53.85079463676896], + [8.5778364897140741, + 12.932386237089576, + 16.529365884366944, + 19.941853366527342, + 23.268052926457571, + 26.545032061823576, + 29.790748583196614, + 33.015178641375142, + 36.224380548787162, + 39.422274578939259, + 42.611522172286684, + 45.793999658055002, + 48.971070951900596, + 52.143752969301988, + 55.312820330403446], + [9.6474216519972168, + 14.115518907894618, + 17.774012366915256, + 21.229062622853124, + 24.587197486317681, + 27.889269427955092, + 31.155326556188325, + 34.39662855427218, + 37.620078044197086, + 40.830178681822041, + 44.030010337966153, + 47.221758471887113, + 50.407020967034367, + 53.586995435398319, + 56.762598475105272], + [10.711433970699945, + 15.28673766733295, + 19.004593537946053, + 22.501398726777283, + 25.891277276839136, + 29.218563499936081, + 32.505247352375523, + 35.763792928808799, + 39.001902811514218, + 42.224638430753279, + 45.435483097475542, + 48.636922645305525, + 51.830783925834728, + 55.01844255063594, + 58.200955824859509], + [11.770876674955582, + 16.447852748486498, + 20.223031412681701, + 23.760715860327448, + 27.182021527190532, + 30.534504754007074, + 33.841965775135715, + 37.118000423665604, + 40.371068905333891, + 43.606764901379516, + 46.828959446564562, + 50.040428970943456, + 53.243223214220535, + 56.438892058982552, + 59.628631306921512], + [12.826491228033465, + 17.600266557468326, + 21.430854238060294, + 25.008518704644261, + 28.460857279654847, + 31.838424458616998, + 35.166714427392629, + 38.460388720328256, + 41.728625562624312, + 44.977526250903469, + 48.211333836373288, + 51.433105171422278, + 54.645106240447105, + 57.849056857839799, + 61.046288512821078], + [13.878843069697276, + 18.745090916814406, + 22.629300302835503, + 26.246047773946584, + 29.72897816891134, + 33.131449953571661, + 36.480548302231658, + 39.791940718940855, + 43.075486800191012, + 46.337772104541405, + 49.583396417633095, + 52.815686826850452, + 56.037118687012179, + 59.249577075517968, + 62.454525995970462], + [14.928374492964716, + 19.88322436109951, + 23.81938909003628, + 27.474339750968247, + 30.987394331665278, + 34.414545662167183, + 37.784378506209499, + 41.113512376883377, + 44.412454519229281, + 47.688252845993366, + 50.945849245830813, + 54.188831071035124, + 57.419876154678179, + 60.641030026538746, + 63.853885828967512], + [15.975438807484321, + 21.015404934568315, + 25.001971500138194, + 28.694271223110755, + 32.236969407878118, + 35.688544091185301, + 39.078998185245057, + 42.425854432866141, + 45.740236776624833, + 49.029635055514276, + 52.299319390331728, + 55.553127779547459, + 58.793933759028134, + 62.02393848337554, + 65.244860767043859]] + +yn_small_zeros = \ +[[0.89357696627916752, + 3.9576784193148579, + 7.0860510603017727, + 10.222345043496417, + 13.361097473872763, + 16.500922441528091, + 19.64130970088794, + 22.782028047291559, + 25.922957653180923, + 29.064030252728398, + 32.205204116493281, + 35.346452305214321, + 38.487756653081537, + 41.629104466213808, + 44.770486607221993], + [2.197141326031017, + 5.4296810407941351, + 8.5960058683311689, + 11.749154830839881, + 14.897442128336725, + 18.043402276727856, + 21.188068934142213, + 24.331942571356912, + 27.475294980449224, + 30.618286491641115, + 33.761017796109326, + 36.90355531614295, + 40.045944640266876, + 43.188218097393211, + 46.330399250701687], + [3.3842417671495935, + 6.7938075132682675, + 10.023477979360038, + 13.209986710206416, + 16.378966558947457, + 19.539039990286384, + 22.69395593890929, + 25.845613720902269, + 28.995080395650151, + 32.143002257627551, + 35.289793869635804, + 38.435733485446343, + 41.581014867297885, + 44.725777117640461, + 47.870122696676504], + [4.5270246611496439, + 8.0975537628604907, + 11.396466739595867, + 14.623077742393873, + 17.81845523294552, + 20.997284754187761, + 24.166235758581828, + 27.328799850405162, + 30.486989604098659, + 33.642049384702463, + 36.794791029185579, + 39.945767226378749, + 43.095367507846703, + 46.2438744334407, + 49.391498015725107], + [5.6451478942208959, + 9.3616206152445429, + 12.730144474090465, + 15.999627085382479, + 19.22442895931681, + 22.424810599698521, + 25.610267054939328, + 28.785893657666548, + 31.954686680031668, + 35.118529525584828, + 38.278668089521758, + 41.435960629910073, + 44.591018225353424, + 47.744288086361052, + 50.896105199722123], + [6.7471838248710219, + 10.597176726782031, + 14.033804104911233, + 17.347086393228382, + 20.602899017175335, + 23.826536030287532, + 27.030134937138834, + 30.220335654231385, + 33.401105611047908, + 36.574972486670962, + 39.743627733020277, + 42.908248189569535, + 46.069679073215439, + 49.228543693445843, + 52.385312123112282], + [7.8377378223268716, + 11.811037107609447, + 15.313615118517857, + 18.670704965906724, + 21.958290897126571, + 25.206207715021249, + 28.429037095235496, + 31.634879502950644, + 34.828638524084437, + 38.013473399691765, + 41.19151880917741, + 44.364272633271975, + 47.53281875312084, + 50.697961822183806, + 53.860312300118388], + [8.919605734873789, + 13.007711435388313, + 16.573915129085334, + 19.974342312352426, + 23.293972585596648, + 26.5667563757203, + 29.809531451608321, + 33.031769327150685, + 36.239265816598239, + 39.435790312675323, + 42.623910919472727, + 45.805442883111651, + 48.981708325514764, + 52.153694518185572, + 55.322154420959698], + [9.9946283820824834, + 14.190361295800141, + 17.817887841179873, + 21.26093227125945, + 24.612576377421522, + 27.910524883974868, + 31.173701563441602, + 34.412862242025045, + 37.634648706110989, + 40.843415321050884, + 44.04214994542435, + 47.232978012841169, + 50.417456447370186, + 53.596753874948731, + 56.771765754432457], + [11.064090256031013, + 15.361301343575925, + 19.047949646361388, + 22.532765416313869, + 25.91620496332662, + 29.2394205079349, + 32.523270869465881, + 35.779715464475261, + 39.016196664616095, + 42.237627509803703, + 45.4474001519274, + 48.647941127433196, + 51.841036928216499, + 55.028034667184916, + 58.209970905250097], + [12.128927704415439, + 16.522284394784426, + 20.265984501212254, + 23.791669719454272, + 27.206568881574774, + 30.555020011020762, + 33.859683872746356, + 37.133649760307504, + 40.385117593813002, + 43.619533085646856, + 46.840676630553575, + 50.051265851897857, + 53.253310556711732, + 56.448332488918971, + 59.637507005589829], + [13.189846995683845, + 17.674674253171487, + 21.473493977824902, + 25.03913093040942, + 28.485081336558058, + 31.858644293774859, + 35.184165245422787, + 38.475796636190897, + 41.742455848758449, + 44.990096293791186, + 48.222870660068338, + 51.443777308699826, + 54.655042589416311, + 57.858358441436511, + 61.055036135780528], + [14.247395665073945, + 18.819555894710682, + 22.671697117872794, + 26.276375544903892, + 29.752925495549038, + 33.151412708998983, + 36.497763772987645, + 39.807134090704376, + 43.089121522203808, + 46.350163579538652, + 49.594769786270069, + 52.82620892320143, + 56.046916910756961, + 59.258751140598783, + 62.463155567737854], + [15.30200785858925, + 19.957808654258601, + 23.861599172945054, + 27.504429642227545, + 31.011103429019229, + 34.434283425782942, + 37.801385632318459, + 41.128514139788358, + 44.425913324440663, + 47.700482714581842, + 50.957073905278458, + 54.199216028087261, + 57.429547607017405, + 60.65008661807661, + 63.862406280068586], + [16.354034360047551, + 21.090156519983806, + 25.044040298785627, + 28.724161640881914, + 32.260472459522644, + 35.708083982611664, + 39.095820003878235, + 42.440684315990936, + 45.75353669045622, + 49.041718113283529, + 52.310408280968073, + 55.56338698149062, + 58.803488508906895, + 62.032886550960831, + 65.253280088312461]] + +ynp_small_zeros = \ +[[2.197141326031017, + 5.4296810407941351, + 8.5960058683311689, + 11.749154830839881, + 14.897442128336725, + 18.043402276727856, + 21.188068934142213, + 24.331942571356912, + 27.475294980449224, + 30.618286491641115, + 33.761017796109326, + 36.90355531614295, + 40.045944640266876, + 43.188218097393211, + 46.330399250701687], + [3.6830228565851777, + 6.9414999536541757, + 10.123404655436613, + 13.285758156782854, + 16.440058007293282, + 19.590241756629495, + 22.738034717396327, + 25.884314618788867, + 29.029575819372535, + 32.174118233366201, + 35.318134458192094, + 38.461753870997549, + 41.605066618873108, + 44.74813744908079, + 47.891014070791065], + [5.0025829314460639, + 8.3507247014130795, + 11.574195465217647, + 14.760909306207676, + 17.931285939466855, + 21.092894504412739, + 24.249231678519058, + 27.402145837145258, + 30.552708880564553, + 33.70158627151572, + 36.849213419846257, + 39.995887376143356, + 43.141817835750686, + 46.287157097544201, + 49.432018469138281], + [6.2536332084598136, + 9.6987879841487711, + 12.972409052292216, + 16.19044719506921, + 19.38238844973613, + 22.559791857764261, + 25.728213194724094, + 28.890678419054777, + 32.048984005266337, + 35.204266606440635, + 38.357281675961019, + 41.508551443818436, + 44.658448731963676, + 47.807246956681162, + 50.95515126455207], + [7.4649217367571329, + 11.005169149809189, + 14.3317235192331, + 17.58443601710272, + 20.801062338411128, + 23.997004122902644, + 27.179886689853435, + 30.353960608554323, + 33.521797098666792, + 36.685048382072301, + 39.844826969405863, + 43.001910515625288, + 46.15685955107263, + 49.310088614282257, + 52.461911043685864], + [8.6495562436971983, + 12.280868725807848, + 15.660799304540377, + 18.949739756016503, + 22.192841809428241, + 25.409072788867674, + 28.608039283077593, + 31.795195353138159, + 34.973890634255288, + 38.14630522169358, + 41.313923188794905, + 44.477791768537617, + 47.638672065035628, + 50.797131066967842, + 53.953600129601663], + [9.8147970120105779, + 13.532811875789828, + 16.965526446046053, + 20.291285512443867, + 23.56186260680065, + 26.799499736027237, + 30.015665481543419, + 33.216968050039509, + 36.407516858984748, + 39.590015243560459, + 42.766320595957378, + 45.937754257017323, + 49.105283450953203, + 52.269633324547373, + 55.431358715604255], + [10.965152105242974, + 14.765687379508912, + 18.250123150217555, + 21.612750053384621, + 24.911310600813573, + 28.171051927637585, + 31.40518108895689, + 34.621401012564177, + 37.824552065973114, + 41.017847386464902, + 44.203512240871601, + 47.3831408366063, + 50.557907466622796, + 53.728697478957026, + 56.896191727313342], + [12.103641941939539, + 15.982840905145284, + 19.517731005559611, + 22.916962141504605, + 26.243700855690533, + 29.525960140695407, + 32.778568197561124, + 36.010261572392516, + 39.226578757802172, + 42.43122493258747, + 45.626783824134354, + 48.815117837929515, + 51.997606404328863, + 55.175294723956816, + 58.348990221754937], + [13.232403808592215, + 17.186756572616758, + 20.770762917490496, + 24.206152448722253, + 27.561059462697153, + 30.866053571250639, + 34.137476603379774, + 37.385039772270268, + 40.614946085165892, + 43.831373184731238, + 47.037251786726299, + 50.234705848765229, + 53.425316228549359, + 56.610286079882087, + 59.790548623216652], + [14.35301374369987, + 18.379337301642568, + 22.011118775283494, + 25.482116178696707, + 28.865046588695164, + 32.192853922166294, + 35.483296655830277, + 38.747005493021857, + 41.990815194320955, + 45.219355876831731, + 48.435892856078888, + 51.642803925173029, + 54.84186659475857, + 58.034439083840155, + 61.221578745109862], + [15.466672066554263, + 19.562077985759503, + 23.240325531101082, + 26.746322986645901, + 30.157042415639891, + 33.507642948240263, + 36.817212798512775, + 40.097251300178642, + 43.355193847719752, + 46.596103410173672, + 49.823567279972794, + 53.040208868780832, + 56.247996968470062, + 59.448441365714251, + 62.642721301357187], + [16.574317035530872, + 20.73617763753932, + 24.459631728238804, + 27.999993668839644, + 31.438208790267783, + 34.811512070805535, + 38.140243708611251, + 41.436725143893739, + 44.708963264433333, + 47.962435051891027, + 51.201037321915983, + 54.427630745992975, + 57.644369734615238, + 60.852911791989989, + 64.054555435720397], + [17.676697936439624, + 21.9026148697762, + 25.670073356263225, + 29.244155124266438, + 32.709534477396028, + 36.105399554497548, + 39.453272918267025, + 42.766255701958017, + 46.052899215578358, + 49.319076602061401, + 52.568982147952547, + 55.805705507386287, + 59.031580956740466, + 62.248409689597653, + 65.457606670836759], + [18.774423978290318, + 23.06220035979272, + 26.872520985976736, + 30.479680663499762, + 33.971869047372436, + 37.390118854896324, + 40.757072537673599, + 44.086572292170345, + 47.387688809191869, + 50.66667461073936, + 53.928009929563275, + 57.175005343085052, + 60.410169281219877, + 63.635442539153021, + 66.85235358587768]] + +@pytest.mark.slow +def test_bessel_zeros_extra(): + for v in range(V): + for m in range(1,M+1): + # Twice to test cache (if used) + assert besseljzero(v,m).ae(jn_small_zeros[v][m-1]) + assert besseljzero(v,m).ae(jn_small_zeros[v][m-1]) + assert besseljzero(v,m,1).ae(jnp_small_zeros[v][m-1]) + assert besseljzero(v,m,1).ae(jnp_small_zeros[v][m-1]) + assert besselyzero(v,m).ae(yn_small_zeros[v][m-1]) + assert besselyzero(v,m).ae(yn_small_zeros[v][m-1]) + assert besselyzero(v,m,1).ae(ynp_small_zeros[v][m-1]) + assert besselyzero(v,m,1).ae(ynp_small_zeros[v][m-1]) + +def test_issue_569(): + r = betainc(1, 2, 1, 1) + assert isinstance(r, mp.mpf) and r == 0 + +@pytest.mark.skipif(BACKEND != 'gmpy', reason="gmpy isn't used") +def test_issue_274(): + with pytest.raises(ValueError): + mp.fraction(1, 100).func(1000, 0xdead) + +def test_issue_523(): + assert mp.hermite(0, inf) == 1.0 + +def test_issue_512(): + assert mp.hyperu(0, 1, inf) == 1.0 + assert mp.hyperu(0, 2, inf) == 1.0 + +def test_issue_251(): + assert lerchphi(1.0000000, 4.1+1j, + 1.0).ae(1.0497861493928464 - 0.053190918836910267j) + assert lerchphi(1.00000001, 4.1+1j, + 1.0).ae(1.0497861498996701 - 0.053190919646660638j) + assert zeta(4.1+1j, 1.0).ae(1.0497861493928464 - 0.053190918836910267j) + +def test_issue_505(): + assert mp.isnan(mp.polylog(mp.inf, 2.2)) + assert mp.isnan(mp.polylog(mp.ninf, 2.2)) + assert mp.isnan(mp.polylog(mp.nan, 2.2)) + +def test_issue_653(): + pytest.raises(ValueError, lambda: zeta(2, -2)) + +def test_issue_511(): + assert mp.laguerre(1, 2, mp.inf) == -mp.inf + assert mp.laguerre(1, 7.2, mp.inf) == -mp.inf + assert fp.laguerre(1, 7.2, fp.inf) == -fp.inf + +def test_issue_473(): + assert mp.polylog(1, -mp.inf) == -mp.inf + assert mp.polylog(2, -mp.inf) == -mp.inf + assert mp.polylog(3, -mp.inf) == -mp.inf + assert mp.polylog(4, -mp.inf) == -mp.inf + assert mp.polylog(5, -mp.inf) == -mp.inf + +def test_issue_1033(): + assert isnan(mp.polylog(2, mp.inf)) + assert isnan(mp.polylog(3, mp.inf)) + assert mp.polylog(2, mp.inf).real == -mp.inf + assert mp.polylog(3, mp.inf).real == -mp.inf + +def test_issue_634(): + assert mp.polylog(1+1e-15, -2).ae(mp.mpf('-1.09861228866811')) + +def test_issue_908(): + assert mp.besselj(-10+0j, 0+0j) == 0 + +def test_issue_637(): + assert hankel1(1, 1 + 30j).ae(-7.25495e-15 - 1.17346e-14j) + assert hankel2(1, 1 - 30j).ae(-7.25495e-15 + 1.17346e-14j) + +def test_issue_991(): + assert spherical_jn(0, 1.3).ae(0.74119860416707) + assert spherical_yn(0, 1.3).ae(-0.20576832971122) + +def test_issue_545(): + x = 100+j + assert erfc(x).ae(mpc('8.634691205220881e-4346', + '1.5120569745187501e-4345')) + assert erfc(-x).ae(mpc(2, '-1.5120569745187501e-4345'), + rel_eps=mpf('1e-4346')) + assert erf(x).ae(mpc(1, '-1.5120569745187501e-4345'), + rel_eps=mpf('1e-4346')) + assert erf(-x).ae(mpc(-1, '1.5120569745187501e-4345'), + rel_eps=mpf('1e-4346')) + +def test_issue_459(): + assert isnan(clsin(1, mp.inf)) + assert isnan(clsin(2, mp.inf)) + assert isnan(clsin(2, mp.nan)) + assert isnan(polylog(-2, mp.nan)) + +def test_issue_1099(): + mp.dps = 200 + z = mpf(1)/2809 + a = mpc(mpf(1)/4, pi*32/log(53)) + r1 = lerchphi(z, 2, a) + r2 = extradps(100)(lerchphi)(z, 2, a) + assert r1.ae(r2) + +def test_issue_252(): + z, s, a = 2.5, 1.5, 4 + e = 1/mpf(10**10) + # N[LerchPhi[5/2, 3/2, 4-10^-10], 17] + assert lerchphi(z, s, + a - e).ae(mpc('-0.16723817353102306-0.08686834435129020j')) + # N[LerchPhi[5/2, 3/2, 4+10^-10], 17] + assert lerchphi(z, s, + a + e).ae(mpc('-0.16723817351940769-0.08686834433537087j')) + # N[LerchPhi[5/2, 3/2, 4], 17] + assert lerchphi(z, s, + a).ae(mpc('-0.16723817352521537-0.08686834434333054j')) + # N[LerchPhi[5/2+I/4, 2, 4], 17] + assert lerchphi(2.5+0.25j, 2, + 4).ae(mpc('-0.066397419699793568+0.076201248010951803j')) + # N[LerchPhi[1/4+I/2, 5/2, 4], 17] + assert lerchphi(0.25+0.5j, 2.5, + 4).ae(mpc('0.032357329026949928+0.010945877309574764j')) + # N[LerchPhi[3/4, 5/2, 4], 17] + assert lerchphi(0.75, 2.5, 4).ae(mpf('0.058457869546642472')) + +def test_issue_496(): + assert fp.hyper([0], [0], 0.25) == 1 + assert fp.hyper([0], [0], 0.5) == 1 + assert fp.hyper([0], [0], 1.5) == 1 + assert fp.hyper([2, 0], [0, 1], 2.5) == 1 + assert fp.hyper([1, -1], [-2], 3) == 2.5 + assert fp.hyp2f1(2, -1, -1, 3) == 7 diff --git a/mpmath/tests/test_gammazeta.py b/mpmath/tests/test_gammazeta.py new file mode 100644 index 0000000..249775b --- /dev/null +++ b/mpmath/tests/test_gammazeta.py @@ -0,0 +1,707 @@ +import pytest + +from mpmath import (altzeta, apery, barnesg, bell, bernfrac, bernoulli, + bernpoly, beta, binomial, catalan, digamma, e, euler, + eulerpoly, fac, fac2, factorial, fadd, ff, findroot, fp, + fraction, gamma, gammaprod, harmonic, hyperfac, inf, isnan, + j, log, loggamma, mp, mpc, mpf, mpmathify, nan, pi, + polyexp, polylog, primezeta, psi, rf, rgamma, sech, + secondzeta, siegelz, sinc, sqrt, stieltjes, superfac, zeta) +from mpmath.libmp import from_float, round_up +from mpmath.libmp.gammazeta import mpf_zeta_int + + +def test_zeta_int_bug(): + assert mpf_zeta_int(0, 10) == from_float(-0.5) + +@pytest.mark.parametrize('plus', [True, False]) +def test_bernoulli(plus): + assert bernfrac(0, plus) == (1,1) + assert bernfrac(1, plus) == (1,2) if plus else (-1,2) + assert bernfrac(2, plus) == (1,6) + assert bernfrac(3, plus) == (0,1) + assert bernfrac(4, plus) == (-1,30) + assert bernfrac(5, plus) == (0,1) + assert bernfrac(6, plus) == (1,42) + assert bernfrac(8, plus) == (-1,30) + assert bernfrac(10, plus) == (5,66) + assert bernfrac(12, plus) == (-691,2730) + assert bernfrac(18, plus) == (43867,798) + p, q = bernfrac(228, plus) + assert p % 10**10 == 164918161 + assert q == 625170 + p, q = bernfrac(1000, plus) + assert p % 10**10 == 7950421099 + assert q == 342999030 + mp.dps = 15 + assert bernoulli(0, plus) == 1 + assert bernoulli(1, plus) == 0.5 if plus else -0.5 + assert bernoulli(2, plus).ae(1./6) + assert bernoulli(3, plus) == 0 + assert bernoulli(4, plus).ae(-1./30) + assert bernoulli(5, plus) == 0 + assert bernoulli(6, plus).ae(1./42) + assert str(bernoulli(10, plus)) == '0.0757575757575758' + assert repr(bernoulli(10, plus)) == "mpf('0.07575757575757576')" + assert str(bernoulli(234, plus)) == '7.62772793964344e+267' + assert str(bernoulli(10**5, plus)) == '-5.82229431461335e+376755' + assert str(bernoulli(10**8+2, plus)) == '1.19570355039953e+676752584' + + mp.dps = 50 + assert str(bernoulli(10, plus)) == '0.075757575757575757575757575757575757575757575757576' + assert str(bernoulli(234, plus)) == '7.6277279396434392486994969020496121553385863373331e+267' + assert str(bernoulli(10**5, plus)) == '-5.8222943146133508236497045360612887555320691004308e+376755' + assert str(bernoulli(10**8+2, plus)) == '1.1957035503995297272263047884604346914602088317782e+676752584' + + mp.dps = 1000 + assert bernoulli(10, plus).ae(mpf(5)/66) + + mp.dps = 50000 + assert bernoulli(10, plus).ae(mpf(5)/66) + + mp.dps = 15 + +def test_bernpoly_eulerpoly(): + assert bernpoly(0,-1).ae(1) + assert bernpoly(0,0).ae(1) + assert bernpoly(0,'1/2').ae(1) + assert bernpoly(0,'3/4').ae(1) + assert bernpoly(0,1).ae(1) + assert bernpoly(0,2).ae(1) + assert bernpoly(1,-1).ae('-3/2') + assert bernpoly(1,0).ae('-1/2') + assert bernpoly(1,'1/2').ae(0) + assert bernpoly(1,'3/4').ae('1/4') + assert bernpoly(1,1).ae('1/2') + assert bernpoly(1,2).ae('3/2') + assert bernpoly(2,-1).ae('13/6') + assert bernpoly(2,0).ae('1/6') + assert bernpoly(2,'1/2').ae('-1/12') + assert bernpoly(2,'3/4').ae('-1/48') + assert bernpoly(2,1).ae('1/6') + assert bernpoly(2,2).ae('13/6') + assert bernpoly(3,-1).ae(-3) + assert bernpoly(3,0).ae(0) + assert bernpoly(3,'1/2').ae(0) + assert bernpoly(3,'3/4').ae('-3/64') + assert bernpoly(3,1).ae(0) + assert bernpoly(3,2).ae(3) + assert bernpoly(4,-1).ae('119/30') + assert bernpoly(4,0).ae('-1/30') + assert bernpoly(4,'1/2').ae('7/240') + assert bernpoly(4,'3/4').ae('7/3840') + assert bernpoly(4,1).ae('-1/30') + assert bernpoly(4,2).ae('119/30') + assert bernpoly(5,-1).ae(-5) + assert bernpoly(5,0).ae(0) + assert bernpoly(5,'1/2').ae(0) + assert bernpoly(5,'3/4').ae('25/1024') + assert bernpoly(5,1).ae(0) + assert bernpoly(5,2).ae(5) + assert bernpoly(10,-1).ae('665/66') + assert bernpoly(10,0).ae('5/66') + assert bernpoly(10,'1/2').ae('-2555/33792') + assert bernpoly(10,'3/4').ae('-2555/34603008') + assert bernpoly(10,1).ae('5/66') + assert bernpoly(10,2).ae('665/66') + assert bernpoly(11,-1).ae(-11) + assert bernpoly(11,0).ae(0) + assert bernpoly(11,'1/2').ae(0) + assert bernpoly(11,'3/4').ae('-555731/4194304') + assert bernpoly(11,1).ae(0) + assert bernpoly(11,2).ae(11) + assert eulerpoly(0,-1).ae(1) + assert eulerpoly(0,0).ae(1) + assert eulerpoly(0,'1/2').ae(1) + assert eulerpoly(0,'3/4').ae(1) + assert eulerpoly(0,1).ae(1) + assert eulerpoly(0,2).ae(1) + assert eulerpoly(1,-1).ae('-3/2') + assert eulerpoly(1,0).ae('-1/2') + assert eulerpoly(1,'1/2').ae(0) + assert eulerpoly(1,'3/4').ae('1/4') + assert eulerpoly(1,1).ae('1/2') + assert eulerpoly(1,2).ae('3/2') + assert eulerpoly(2,-1).ae(2) + assert eulerpoly(2,0).ae(0) + assert eulerpoly(2,'1/2').ae('-1/4') + assert eulerpoly(2,'3/4').ae('-3/16') + assert eulerpoly(2,1).ae(0) + assert eulerpoly(2,2).ae(2) + assert eulerpoly(3,-1).ae('-9/4') + assert eulerpoly(3,0).ae('1/4') + assert eulerpoly(3,'1/2').ae(0) + assert eulerpoly(3,'3/4').ae('-11/64') + assert eulerpoly(3,1).ae('-1/4') + assert eulerpoly(3,2).ae('9/4') + assert eulerpoly(4,-1).ae(2) + assert eulerpoly(4,0).ae(0) + assert eulerpoly(4,'1/2').ae('5/16') + assert eulerpoly(4,'3/4').ae('57/256') + assert eulerpoly(4,1).ae(0) + assert eulerpoly(4,2).ae(2) + assert eulerpoly(5,-1).ae('-3/2') + assert eulerpoly(5,0).ae('-1/2') + assert eulerpoly(5,'1/2').ae(0) + assert eulerpoly(5,'3/4').ae('361/1024') + assert eulerpoly(5,1).ae('1/2') + assert eulerpoly(5,2).ae('3/2') + assert eulerpoly(10,-1).ae(2) + assert eulerpoly(10,0).ae(0) + assert eulerpoly(10,'1/2').ae('-50521/1024') + assert eulerpoly(10,'3/4').ae('-36581523/1048576') + assert eulerpoly(10,1).ae(0) + assert eulerpoly(10,2).ae(2) + assert eulerpoly(11,-1).ae('-699/4') + assert eulerpoly(11,0).ae('691/4') + assert eulerpoly(11,'1/2').ae(0) + assert eulerpoly(11,'3/4').ae('-512343611/4194304') + assert eulerpoly(11,1).ae('-691/4') + assert eulerpoly(11,2).ae('699/4') + # Potential accuracy issues + assert bernpoly(10000,10000).ae('5.8196915936323387117e+39999') + assert bernpoly(200,17.5).ae(3.8048418524583064909e244) + assert eulerpoly(200,17.5).ae(-3.7309911582655785929e275) + +def test_gamma(): + assert gamma(0.25).ae(3.6256099082219083119) + assert gamma(0.0001).ae(9999.4228832316241908) + assert gamma(300).ae('1.0201917073881354535e612') + assert gamma(-0.5).ae(-3.5449077018110320546) + assert gamma(-7.43).ae(0.00026524416464197007186) + #assert gamma(Rational(1,2)) == gamma(0.5) + #assert gamma(Rational(-7,3)).ae(gamma(mpf(-7)/3)) + assert gamma(1+1j).ae(0.49801566811835604271 - 0.15494982830181068512j) + assert gamma(-1+0.01j).ae(-0.422733904013474115 + 99.985883082635367436j) + assert gamma(20+30j).ae(-1453876687.5534810 + 1163777777.8031573j) + # Should always give exact factorials when they can + # be represented as mpfs under the current working precision + fact = 1 + for i in range(1, 18): + assert gamma(i) == fact + fact *= i + for dps in [170, 600]: + fact = 1 + mp.dps = dps + for i in range(1, 105): + assert gamma(i) == fact + fact *= i + mp.dps = 100 + assert gamma(0.5).ae(sqrt(pi)) + mp.dps = 15 + assert factorial(0) == fac(0) == 1 + assert factorial(3) == 6 + assert isnan(gamma(nan)) + assert gamma(1100).ae('4.8579168073569433667e2866') + assert rgamma(0) == 0 + assert rgamma(-1) == 0 + assert rgamma(2) == 1.0 + assert rgamma(3) == 0.5 + assert loggamma(2+8j).ae(-8.5205176753667636926 + 10.8569497125597429366j) + assert loggamma('1e10000').ae('2.302485092994045684017991e10004') + assert loggamma('1e10000j').ae(mpc('-1.570796326794896619231322e10000','2.302485092994045684017991e10004')) + +def test_fac2(): + assert [fac2(n) for n in range(10)] == [1,1,2,3,8,15,48,105,384,945] + assert fac2(-5).ae(1./3) + assert fac2(-11).ae(-1./945) + assert fac2(50).ae(5.20469842636666623e32) + assert fac2(0.5+0.75j).ae(0.81546769394688069176-0.34901016085573266889j) + assert fac2(inf) == inf + assert isnan(fac2(-inf)) + +def test_gamma_quotients(): + h = 1e-8 + ep = 1e-4 + G = gamma + assert gammaprod([-1],[-3,-4]) == 0 + assert gammaprod([-1,0],[-5]) == inf + assert abs(gammaprod([-1],[-2]) - G(-1+h)/G(-2+h)) < 1e-4 + assert abs(gammaprod([-4,-3],[-2,0]) - G(-4+h)*G(-3+h)/G(-2+h)/G(0+h)) < 1e-4 + assert rf(3,0) == 1 + assert rf(2.5,1) == 2.5 + assert rf(-5,2) == 20 + assert rf(j,j).ae(gamma(2*j)/gamma(j)) + assert rf('-255.5815971722918','-0.5119253100282322').ae('-0.1952720278805729485') # issue 421 + assert ff(-2,0) == 1 + assert ff(-2,1) == -2 + assert ff(4,3) == 24 + assert ff(3,4) == 0 + assert binomial(0,0) == 1 + assert binomial(1,0) == 1 + assert binomial(0,-1) == 0 + assert binomial(3,2) == 3 + assert binomial(5,2) == 10 + assert binomial(5,3) == 10 + assert binomial(5,5) == 1 + assert binomial(-1,0) == 1 + assert binomial(-2,-4) == 3 + assert binomial(4.5, 1.5) == 6.5625 + assert binomial(1100,1) == 1100 + assert binomial(1100,2) == 604450 + assert beta(1,1) == 1 + assert beta(0,0) == inf + assert beta(3,0) == inf + assert beta(-1,-1) == inf + assert beta(1.5,1).ae(2/3.) + assert beta(1.5,2.5).ae(pi/16) + assert (10**15*beta(10,100)).ae(2.3455339739604649879) + assert beta(inf,inf) == 0 + assert isnan(beta(-inf,inf)) + assert isnan(beta(-3,inf)) + assert isnan(beta(0,inf)) + assert beta(inf,0.5) == beta(0.5,inf) == 0 + assert beta(inf,-1.5) == inf + assert beta(inf,-0.5) == -inf + assert beta(1+2j,-1-j/2).ae(1.16396542451069943086+0.08511695947832914640j) + assert beta(-0.5,0.5) == 0 + assert beta(-3,3).ae(-1/3.) + assert beta('-255.5815971722918','-0.5119253100282322').ae('18.157330562703710339') # issue 421 + +def test_zeta(): + assert zeta(2).ae(pi**2 / 6) + assert zeta(2.0).ae(pi**2 / 6) + assert zeta(mpc(2)).ae(pi**2 / 6) + assert zeta(100).ae(1) + assert zeta(0).ae(-0.5) + assert zeta(0.5).ae(-1.46035450880958681) + assert zeta(-1).ae(-mpf(1)/12) + assert zeta(-2) == 0 + assert zeta(-3).ae(mpf(1)/120) + assert zeta(-4) == 0 + assert zeta(-100) == 0 + assert isnan(zeta(nan)) + assert zeta(1e-30).ae(-0.5) + assert zeta(-1e-30).ae(-0.5) + # Zeros in the critical strip + assert zeta(mpc(0.5, 14.1347251417346937904)).ae(0) + assert zeta(mpc(0.5, 21.0220396387715549926)).ae(0) + assert zeta(mpc(0.5, 25.0108575801456887632)).ae(0) + assert zeta(mpc(1e-30,1e-40)).ae(-0.5) + assert zeta(mpc(-1e-30,1e-40)).ae(-0.5) + mp.dps = 50 + im = '236.5242296658162058024755079556629786895294952121891237' + assert zeta(mpc(0.5, im)).ae(0, 1e-46) + mp.dps = 15 + # Complex reflection formula + assert (zeta(-60+3j) / 10**34).ae(8.6270183987866146+15.337398548226238j) + # issue #358 + assert zeta(0,0.5) == 0 + assert zeta(0,0) == 0.5 + assert zeta(0,0.5,1).ae(-0.34657359027997265) + # see issue #390 + assert zeta(-1.5,0.5j).ae(-0.13671400162512768475 + 0.11411333638426559139j) + +def test_altzeta(): + assert altzeta(-2) == 0 + assert altzeta(-4) == 0 + assert altzeta(-100) == 0 + assert altzeta(0) == 0.5 + assert altzeta(-1) == 0.25 + assert altzeta(-3) == -0.125 + assert altzeta(-5) == 0.25 + assert altzeta(-21) == 1180529130.25 + assert altzeta(1).ae(log(2)) + assert altzeta(2).ae(pi**2/12) + assert altzeta(10).ae(73*pi**10/6842880) + assert altzeta(50) < 1 + assert altzeta(60, rounding='d') < 1 + assert altzeta(60, rounding='u') == 1 + assert altzeta(10000, rounding='d') < 1 + assert altzeta(10000, rounding='u') == 1 + assert altzeta(3+0j) == altzeta(3) + s = 3+4j + assert altzeta(s).ae((1-2**(1-s))*zeta(s)) + s = -3+4j + assert altzeta(s).ae((1-2**(1-s))*zeta(s)) + assert altzeta(-100.5).ae(4.58595480083585913e+108) + assert altzeta(1.3).ae(0.73821404216623045) + assert altzeta(1e-30).ae(0.5) + assert altzeta(-1e-30).ae(0.5) + assert altzeta(mpc(1e-30,1e-40)).ae(0.5) + assert altzeta(mpc(-1e-30,1e-40)).ae(0.5) + +def test_zeta_huge(): + assert zeta(inf) == 1 + mp.dps = 50 + assert zeta(100).ae('1.0000000000000000000000000000007888609052210118073522') + assert zeta(40*pi).ae('1.0000000000000000000000000000000000000148407238666182') + mp.dps = 10000 + v = zeta(33000) + mp.dps = 15 + assert str(v-1) == '1.02363019598118e-9934' + assert zeta(pi*1000, rounding=round_up) > 1 + assert zeta(3000, rounding=round_up) > 1 + assert zeta(pi*1000) == 1 + assert zeta(3000) == 1 + +def test_zeta_negative(): + mp.dps = 150 + a = -pi*10**40 + mp.dps = 15 + assert str(zeta(a)) == '2.55880492708712e+1233536161668617575553892558646631323374078' + mp.dps = 50 + assert str(zeta(a)) == '2.5588049270871154960875033337384432038436330847333e+1233536161668617575553892558646631323374078' + +def test_polygamma(): + psi0 = lambda z: psi(0,z) + psi1 = lambda z: psi(1,z) + assert psi0(3) == psi(0,3) == digamma(3) + #assert psi2(3) == psi(2,3) == tetragamma(3) + #assert psi3(3) == psi(3,3) == pentagamma(3) + assert psi0(pi).ae(0.97721330794200673) + assert psi0(-pi).ae(7.8859523853854902) + assert psi0(-pi+1).ae(7.5676424992016996) + assert psi0(pi+j).ae(1.04224048313859376 + 0.35853686544063749j) + assert psi0(-pi-j).ae(1.3404026194821986 - 2.8824392476809402j) + assert findroot(psi0, 1).ae(1.4616321449683622) + assert psi0(1e-10).ae(-10000000000.57722) + assert psi0(1e-40).ae(-1.000000000000000e+40) + assert psi0(1e-10+1e-10j).ae(-5000000000.577215 + 5000000000.000000j) + assert psi0(1e-40+1e-40j).ae(-5.000000000000000e+39 + 5.000000000000000e+39j) + assert psi0(inf) == inf + assert psi1(inf) == 0 + assert psi(2,inf) == 0 + assert psi1(pi).ae(0.37424376965420049) + assert psi1(-pi).ae(53.030438740085385) + assert psi1(pi+j).ae(0.32935710377142464 - 0.12222163911221135j) + assert psi1(-pi-j).ae(-0.30065008356019703 + 0.01149892486928227j) + assert (10**6*psi(4,1+10*pi*j)).ae(-6.1491803479004446 - 0.3921316371664063j) + assert psi0(1+10*pi*j).ae(3.4473994217222650 + 1.5548808324857071j) + assert isnan(psi0(nan)) + assert isnan(psi0(-inf)) + assert psi0(-100.5).ae(4.615124601338064) + assert psi0(3+0j).ae(psi0(3)) + assert psi0(-100+3j).ae(4.6106071768714086321+3.1117510556817394626j) + assert isnan(psi(2,mpc(0,inf))) + assert isnan(psi(2,mpc(0,nan))) + assert isnan(psi(2,mpc(0,-inf))) + assert isnan(psi(2,mpc(1,inf))) + assert isnan(psi(2,mpc(1,nan))) + assert isnan(psi(2,mpc(1,-inf))) + assert isnan(psi(2,mpc(inf,inf))) + assert isnan(psi(2,mpc(nan,nan))) + assert isnan(psi(2,mpc(-inf,-inf))) + mp.dps = 30 + # issue #534 + assert digamma(-0.75+1j).ae(mpc('0.46317279488182026118963809283042317', '2.4821070143037957102007677817351115')) + # issue #647 + mp.prec = 42 + assert digamma(-0.5+0.5j).ae(mpc('0.131892637354523', '2.44065951997751')) + mp.prec = 53 + assert digamma(1e300+1j).ae(690.77552789821368) + +def test_polygamma_high_prec(): + mp.dps = 100 + assert str(psi(0,pi)) == "0.9772133079420067332920694864061823436408346099943256380095232865318105924777141317302075654362928734" + assert str(psi(10,pi)) == "-12.98876181434889529310283769414222588307175962213707170773803550518307617769657562747174101900659238" + +def test_polygamma_identities(): + psi0 = lambda z: psi(0,z) + psi1 = lambda z: psi(1,z) + psi2 = lambda z: psi(2,z) + assert psi0(0.5).ae(-euler-2*log(2)) + assert psi0(1).ae(-euler) + assert psi1(0.5).ae(0.5*pi**2) + assert psi1(1).ae(pi**2/6) + assert psi1(0.25).ae(pi**2 + 8*catalan) + assert psi2(1).ae(-2*apery) + mp.dps = 20 + u = -182*apery+4*sqrt(3)*pi**3 + mp.dps = 15 + assert psi(2,5/6.).ae(u) + assert psi(3,0.5).ae(pi**4) + +def test_foxtrot_identity(): + # A test of the complex digamma function. + # See http://mathworld.wolfram.com/FoxTrotSeries.html and + # http://mathworld.wolfram.com/DigammaFunction.html + psi0 = lambda z: psi(0,z) + mp.dps = 50 + a = (-1)**fraction(1,3) + b = (-1)**fraction(2,3) + x = -psi0(0.5*a) - psi0(-0.5*b) + psi0(0.5*(1+a)) + psi0(0.5*(1-b)) + y = 2*pi*sech(0.5*sqrt(3)*pi) + assert x.ae(y) + +def test_polygamma_high_order(): + mp.dps = 100 + assert str(psi(50, pi)) == "-1344100348958402765749252447726432491812.641985273160531055707095989227897753035823152397679626136483" + assert str(psi(50, pi + 14*e)) == "-0.00000000000000000189793739550804321623512073101895801993019919886375952881053090844591920308111549337295143780341396" + assert str(psi(50, pi + 14*e*j)) == ("(-0.0000000000000000522516941152169248975225472155683565752375889510631513244785" + "9377385233700094871256507814151956624433 - 0.00000000000000001813157041407010184" + "702414110218205348527862196327980417757665282244728963891298080199341480881811613j)") + mp.dps = 15 + assert str(psi(50, pi)) == "-1.34410034895841e+39" + assert str(psi(50, pi + 14*e)) == "-1.89793739550804e-18" + assert str(psi(50, pi + 14*e*j)) == "(-5.2251694115217e-17 - 1.81315704140701e-17j)" + +def test_harmonic(): + assert harmonic(0) == 0 + assert harmonic(1) == 1 + assert harmonic(2) == 1.5 + assert harmonic(3).ae(1. + 1./2 + 1./3) + assert harmonic(10**10).ae(23.603066594891989701) + assert harmonic(10**1000).ae(2303.162308658947) + assert harmonic(0.5).ae(2-2*log(2)) + assert harmonic(inf) == inf + assert harmonic(2+0j) == 1.5+0j + assert harmonic(1+2j).ae(1.4918071802755104+0.92080728264223022j) + +def test_gamma_huge_1(): + mp.dps = 500 + x = mpf(10**10) / 7 + mp.dps = 15 + assert str(gamma(x)) == "6.26075321389519e+12458010678" + mp.dps = 50 + assert str(gamma(x)) == "6.2607532138951929201303779291707455874010420783933e+12458010678" + +def test_gamma_huge_2(): + mp.dps = 500 + x = mpf(10**100) / 19 + mp.dps = 15 + assert str(gamma(x)) == (\ + "1.82341134776679e+5172997469323364168990133558175077136829182824042201886051511" + "9656908623426021308685461258226190190661") + mp.dps = 50 + assert str(gamma(x)) == (\ + "1.82341134776678875374414910350027596939980412984e+5172997469323364168990133558" + "1750771368291828240422018860515119656908623426021308685461258226190190661") + +def test_gamma_huge_3(): + mp.dps = 500 + x = 10**80 // 3 + 10**70*j / 7 + mp.dps = 15 + y = gamma(x) + assert str(y.real) == (\ + "-6.82925203918106e+2636286142112569524501781477865238132302397236429627932441916" + "056964386399485392600") + assert str(y.imag) == (\ + "8.54647143678418e+26362861421125695245017814778652381323023972364296279324419160" + "56964386399485392600") + mp.dps = 50 + y = gamma(x) + assert str(y.real) == (\ + "-6.8292520391810548460682736226799637356016538421817e+26362861421125695245017814" + "77865238132302397236429627932441916056964386399485392600") + assert str(y.imag) == (\ + "8.5464714367841748507479306948130687511711420234015e+263628614211256952450178147" + "7865238132302397236429627932441916056964386399485392600") + +def test_gamma_huge_4(): + x = 3200+11500j + assert str(gamma(x)) == \ + "(8.95783268539713e+5164 - 1.94678798329735e+5164j)" + mp.dps = 50 + assert str(gamma(x)) == (\ + "(8.9578326853971339570292952697675570822206567327092e+5164" + " - 1.9467879832973509568895402139429643650329524144794e+51" + "64j)") + +def test_gamma_huge_5(): + mp.dps = 500 + x = 10**60 * j / 3 + mp.dps = 15 + y = gamma(x) + assert str(y.real) == "-3.27753899634941e-227396058973640224580963937571892628368354580620654233316839" + assert str(y.imag) == "-7.1519888950416e-227396058973640224580963937571892628368354580620654233316841" + mp.dps = 50 + y = gamma(x) + assert str(y.real) == (\ + "-3.2775389963494132168950056995974690946983219123935e-22739605897364022458096393" + "7571892628368354580620654233316839") + assert str(y.imag) == (\ + "-7.1519888950415979749736749222530209713136588885897e-22739605897364022458096393" + "7571892628368354580620654233316841") + +def test_gamma_huge_7(): + mp.dps = 100 + a = 3 + j/mpf(10)**1000 + mp.dps = 15 + y = gamma(a) + assert str(y.real) == "2.0" + # wrong + #assert str(y.imag) == "2.16735365342606e-1000" + assert str(y.imag) == "1.84556867019693e-1000" + mp.dps = 50 + y = gamma(a) + assert str(y.real) == "2.0" + #assert str(y.imag) == "2.1673536534260596065418805612488708028522563689298e-1000" + assert str(y.imag) == "1.8455686701969342787869758198351951379156813281202e-1000" + +def test_stieltjes(): + assert stieltjes(0).ae(+euler) + mp.dps = 25 + assert stieltjes(1).ae('-0.07281584548367672486058637587') + assert stieltjes(2).ae('-0.009690363192872318484530386035') + assert stieltjes(3).ae('0.002053834420303345866160046543') + assert stieltjes(4).ae('0.002325370065467300057468170178') + mp.dps = 15 + assert stieltjes(1).ae(-0.07281584548367672486058637587) + assert stieltjes(2).ae(-0.009690363192872318484530386035) + assert stieltjes(3).ae(0.002053834420303345866160046543) + assert stieltjes(4).ae(0.0023253700654673000574681701775) + +def test_barnesg(): + assert barnesg(0) == barnesg(-1) == 0 + assert [superfac(i) for i in range(8)] == [1, 1, 2, 12, 288, 34560, 24883200, 125411328000] + assert str(superfac(1000)) == '3.24570818422368e+1177245' + assert isnan(barnesg(nan)) + assert isnan(superfac(nan)) + assert isnan(hyperfac(nan)) + assert barnesg(inf) == inf + assert superfac(inf) == inf + assert hyperfac(inf) == inf + assert isnan(superfac(-inf)) + assert barnesg(0.7).ae(0.8068722730141471) + assert barnesg(2+3j).ae(-0.17810213864082169+0.04504542715447838j) + assert [hyperfac(n) for n in range(7)] == [1, 1, 4, 108, 27648, 86400000, 4031078400000] + assert [hyperfac(n) for n in range(0,-7,-1)] == [1,1,-1,-4,108,27648,-86400000] + a = barnesg(-3+0j) + assert a == 0 and isinstance(a, mpc) + a = hyperfac(-3+0j) + assert a == -4 and isinstance(a, mpc) + +def test_polylog(): + zs = [mpmathify(z) for z in [0, 0.5, 0.99, 4, -0.5, -4, 1j, 3+4j]] + for z in zs: assert polylog(1, z).ae(-log(1-z)) + for z in zs: assert polylog(0, z).ae(z/(1-z)) + for z in zs: assert polylog(-1, z).ae(z/(1-z)**2) + for z in zs: assert polylog(-2, z).ae(z*(1+z)/(1-z)**3) + for z in zs: assert polylog(-3, z).ae(z*(1+4*z+z**2)/(1-z)**4) + assert polylog(3, 7).ae(5.3192579921456754382-5.9479244480803301023j) + assert polylog(3, -7).ae(-4.5693548977219423182) + assert polylog(2, 0.9).ae(1.2997147230049587252) + assert polylog(2, -0.9).ae(-0.75216317921726162037) + assert polylog(2, 0.9j).ae(-0.17177943786580149299+0.83598828572550503226j) + assert polylog(2, 1.1).ae(1.9619991013055685931-0.2994257606855892575j) + assert polylog(2, -1.1).ae(-0.89083809026228260587) + assert polylog(2, 1.1*sqrt(j)).ae(0.58841571107611387722+1.09962542118827026011j) + assert polylog(-2, 0.9).ae(1710) + assert polylog(-2, -0.9).ae(-90/6859.) + assert polylog(3, 0.9).ae(1.0496589501864398696) + assert polylog(-3, 0.9).ae(48690) + assert polylog(-3, -4).ae(-0.0064) + assert polylog(0.5+j/3, 0.5+j/2).ae(0.31739144796565650535 + 0.99255390416556261437j) + assert polylog(3+4j,1).ae(zeta(3+4j)) + assert polylog(3+4j,-1).ae(-altzeta(3+4j)) + # issue 390 + assert polylog(1.5, -48.910886523731889).ae(-6.272992229311817) + assert polylog(1.5, 200).ae(-8.349608319033686529 - 8.159694826434266042j) + assert polylog(-2+0j, -2).ae(mpf(1)/13.5) + assert polylog(-2+0j, 1.25).ae(-180) + +def test_bell_polyexp(): + # TODO: more tests for polyexp + assert (polyexp(0,1e-10)*10**10).ae(1.00000000005) + assert (polyexp(1,1e-10)*10**10).ae(1.0000000001) + assert polyexp(5,3j).ae(-607.7044517476176454+519.962786482001476087j) + assert polyexp(-1,3.5).ae(12.09537536175543444) + # bell(0,x) = 1 + assert bell(0,0) == 1 + assert bell(0,1) == 1 + assert bell(0,2) == 1 + assert bell(0,inf) == 1 + assert bell(0,-inf) == 1 + assert isnan(bell(0,nan)) + # bell(1,x) = x + assert bell(1,4) == 4 + assert bell(1,0) == 0 + assert bell(1,inf) == inf + assert bell(1,-inf) == -inf + assert isnan(bell(1,nan)) + # bell(2,x) = x*(1+x) + assert bell(2,-1) == 0 + assert bell(2,0) == 0 + # large orders / arguments + assert bell(10) == 115975 + assert bell(10,1) == 115975 + assert bell(10, -8) == 11054008 + assert bell(5,-50) == -253087550 + assert bell(50,-50).ae('3.4746902914629720259e74') + mp.dps = 80 + assert bell(50,-50) == 347469029146297202586097646631767227177164818163463279814268368579055777450 + assert bell(40,50) == 5575520134721105844739265207408344706846955281965031698187656176321717550 + assert bell(74) == 5006908024247925379707076470957722220463116781409659160159536981161298714301202 + mp.dps = 15 + assert bell(10,20j) == 7504528595600+15649605360020j + # continuity of the generalization + assert bell(0.5,0).ae(sinc(pi*0.5)) + +def test_primezeta(): + assert primezeta(0.9).ae(1.8388316154446882243 + 3.1415926535897932385j) + assert primezeta(4).ae(0.076993139764246844943) + assert primezeta(1) == inf + assert primezeta(inf) == 0 + assert isnan(primezeta(nan)) + +def test_secondzeta(): + assert secondzeta(2, 0.6).ae(0.022849870007492626) + +def test_rs_zeta(): + assert zeta(0.5+100000j).ae(1.0730320148577531321 + 5.7808485443635039843j) + assert zeta(0.75+100000j).ae(1.837852337251873704 + 1.9988492668661145358j) + assert zeta(0.5+1000000j, derivative=3).ae(1647.7744105852674733 - 1423.1270943036622097j) + assert zeta(1+1000000j, derivative=3).ae(3.4085866124523582894 - 18.179184721525947301j) + assert zeta(1+1000000j, derivative=1).ae(-0.10423479366985452134 - 0.74728992803359056244j) + assert zeta(0.5-1000000j, derivative=1).ae(11.636804066002521459 + 17.127254072212996004j) + # Additional sanity tests using fp arithmetic. + # Some more high-precision tests are found in the docstrings + def ae(x, y, tol=1e-6): + return abs(x-y) < tol*abs(y) + assert ae(fp.zeta(0.5-100000j), 1.0730320148577531321 - 5.7808485443635039843j) + assert ae(fp.zeta(0.75-100000j), 1.837852337251873704 - 1.9988492668661145358j) + assert ae(fp.zeta(0.5+1e6j), 0.076089069738227100006 + 2.8051021010192989554j) + assert ae(fp.zeta(0.5+1e6j, derivative=1), 11.636804066002521459 - 17.127254072212996004j) + assert ae(fp.zeta(1+1e6j), 0.94738726251047891048 + 0.59421999312091832833j) + assert ae(fp.zeta(1+1e6j, derivative=1), -0.10423479366985452134 - 0.74728992803359056244j) + assert ae(fp.zeta(0.5+100000j, derivative=1), 10.766962036817482375 - 30.92705282105996714j) + assert ae(fp.zeta(0.5+100000j, derivative=2), -119.40515625740538429 + 217.14780631141830251j) + assert ae(fp.zeta(0.5+100000j, derivative=3), 1129.7550282628460881 - 1685.4736895169690346j) + assert ae(fp.zeta(0.5+100000j, derivative=4), -10407.160819314958615 + 13777.786698628045085j) + assert ae(fp.zeta(0.75+100000j, derivative=1), -0.41742276699594321475 - 6.4453816275049955949j) + assert ae(fp.zeta(0.75+100000j, derivative=2), -9.214314279161977266 + 35.07290795337967899j) + assert ae(fp.zeta(0.75+100000j, derivative=3), 110.61331857820103469 - 236.87847130518129926j) + assert ae(fp.zeta(0.75+100000j, derivative=4), -1054.334275898559401 + 1769.9177890161596383j) + +def test_siegelz(): + assert siegelz(100000).ae(5.87959246868176504171) + assert siegelz(100000, derivative=2).ae(-54.1172711010126452832) + assert siegelz(100000, derivative=3).ae(-278.930831343966552538) + assert siegelz(100000+j,derivative=1).ae(678.214511857070283307-379.742160779916375413j) + + + +def test_zeta_near_1(): + # Test for a former bug in mpf_zeta and mpc_zeta + s1 = fadd(1, '1e-10', exact=True) + s2 = fadd(1, '-1e-10', exact=True) + s3 = fadd(1, '1e-10j', exact=True) + assert zeta(s1).ae(1.000000000057721566490881444e10) + assert zeta(s2).ae(-9.99999999942278433510574872e9) + z = zeta(s3) + assert z.real.ae(0.57721566490153286060) + assert z.imag.ae(-9.9999999999999999999927184e9) + mp.dps = 30 + s1 = fadd(1, '1e-50', exact=True) + s2 = fadd(1, '-1e-50', exact=True) + s3 = fadd(1, '1e-50j', exact=True) + assert zeta(s1).ae('1e50') + assert zeta(s2).ae('-1e50') + z = zeta(s3) + assert z.real.ae('0.57721566490153286060651209008240243104215933593992') + assert z.imag.ae('-1e50') + +def test_issue_723(): + mp.dps = 16 + assert zeta(-0.01 + 1000j).ae(-8.971459529241107 + 8.732179332810066j) + mp.dps = 15 + +def test_issue_471(): + assert bernpoly(4, inf) == inf + assert bernpoly(4, mpc(inf, 0)) == mpc(inf, 0) + assert isnan(bernpoly(4, nan)) + +def test_issue_472(): + assert bernpoly(4, mpc(inf, 1e-50)) == mpc(inf, 0) + assert mpc(inf, 2)**4 == mpc(inf, 0) diff --git a/mpmath/tests/test_hp.py b/mpmath/tests/test_hp.py new file mode 100644 index 0000000..dc29367 --- /dev/null +++ b/mpmath/tests/test_hp.py @@ -0,0 +1,288 @@ +""" +Check that the output from irrational functions is accurate for +high-precision input, from 5 to 200 digits. The reference values were +verified with Mathematica. +""" + +from mpmath import cos, e, euler, exp, log, mp, mpc, mpf, pi, sin, sqrt, tan + + +precs = [5, 15, 28, 35, 57, 80, 100, 150, 200] + +# sqrt(3) + pi/2 +a = \ +"3.302847134363773912758768033145623809041389953497933538543279275605"\ +"841220051904536395163599428307109666700184672047856353516867399774243594"\ +"67433521615861420725323528325327484262075464241255915238845599752675" + +# e + 1/euler**2 +b = \ +"5.719681166601007617111261398629939965860873957353320734275716220045750"\ +"31474116300529519620938123730851145473473708966080207482581266469342214"\ +"824842256999042984813905047895479210702109260221361437411947323431" + +# sqrt(a) +sqrt_a = \ +"1.817373691447021556327498239690365674922395036495564333152483422755"\ +"144321726165582817927383239308173567921345318453306994746434073691275094"\ +"484777905906961689902608644112196725896908619756404253109722911487" + +# sqrt(a+b*i).real +sqrt_abi_real = \ +"2.225720098415113027729407777066107959851146508557282707197601407276"\ +"89160998185797504198062911768240808839104987021515555650875977724230130"\ +"3584116233925658621288393930286871862273400475179312570274423840384" + +# sqrt(a+b*i).imag +sqrt_abi_imag = \ +"1.2849057639084690902371581529110949983261182430040898147672052833653668"\ +"0629534491275114877090834296831373498336559849050755848611854282001250"\ +"1924311019152914021365263161630765255610885489295778894976075186" + +# log(a) +log_a = \ +"1.194784864491089550288313512105715261520511949410072046160598707069"\ +"4336653155025770546309137440687056366757650909754708302115204338077595203"\ +"83005773986664564927027147084436553262269459110211221152925732612" + +# log(a+b*i).real +log_abi_real = \ +"1.8877985921697018111624077550443297276844736840853590212962006811663"\ +"04949387789489704203167470111267581371396245317618589339274243008242708"\ +"014251531496104028712866224020066439049377679709216784954509456421" + +# log(a+b*i).imag +log_abi_imag = \ +"1.0471204952840802663567714297078763189256357109769672185219334169734948"\ +"4265809854092437285294686651806426649541504240470168212723133326542181"\ +"8300136462287639956713914482701017346851009323172531601894918640" + +# exp(a) +exp_a = \ +"27.18994224087168661137253262213293847994194869430518354305430976149"\ +"382792035050358791398632888885200049857986258414049540376323785711941636"\ +"100358982497583832083513086941635049329804685212200507288797531143" + +# exp(a+b*i).real +exp_abi_real = \ +"22.98606617170543596386921087657586890620262522816912505151109385026"\ +"40160179326569526152851983847133513990281518417211964710397233157168852"\ +"4963130831190142571659948419307628119985383887599493378056639916701" + +# exp(a+b*i).imag +exp_abi_imag = \ +"-14.523557450291489727214750571590272774669907424478129280902375851196283"\ +"3377162379031724734050088565710975758824441845278120105728824497308303"\ +"6065619788140201636218705414429933685889542661364184694108251449" + +# a**b +pow_a_b = \ +"928.7025342285568142947391505837660251004990092821305668257284426997"\ +"361966028275685583421197860603126498884545336686124793155581311527995550"\ +"580229264427202446131740932666832138634013168125809402143796691154" + +# (a**(a+b*i)).real +pow_a_abi_real = \ +"44.09156071394489511956058111704382592976814280267142206420038656267"\ +"67707916510652790502399193109819563864568986234654864462095231138500505"\ +"8197456514795059492120303477512711977915544927440682508821426093455" + +# (a**(a+b*i)).imag +pow_a_abi_imag = \ +"27.069371511573224750478105146737852141664955461266218367212527612279886"\ +"9322304536553254659049205414427707675802193810711302947536332040474573"\ +"8166261217563960235014674118610092944307893857862518964990092301" + +# ((a+b*i)**(a+b*i)).real +pow_abi_abi_real = \ +"-0.15171310677859590091001057734676423076527145052787388589334350524"\ +"8084195882019497779202452975350579073716811284169068082670778986235179"\ +"0813026562962084477640470612184016755250592698408112493759742219150452"\ + +# ((a+b*i)**(a+b*i)).imag +pow_abi_abi_imag = \ +"1.2697592504953448936553147870155987153192995316950583150964099070426"\ +"4736837932577176947632535475040521749162383347758827307504526525647759"\ +"97547638617201824468382194146854367480471892602963428122896045019902" + +# sin(a) +sin_a = \ +"-0.16055653857469062740274792907968048154164433772938156243509084009"\ +"38437090841460493108570147191289893388608611542655654723437248152535114"\ +"528368009465836614227575701220612124204622383149391870684288862269631" + +# sin(1000*a) +sin_1000a = \ +"-0.85897040577443833776358106803777589664322997794126153477060795801"\ +"09151695416961724733492511852267067419573754315098042850381158563024337"\ +"216458577140500488715469780315833217177634490142748614625281171216863" + +# sin(a+b*i) +sin_abi_real = \ +"-24.4696999681556977743346798696005278716053366404081910969773939630"\ +"7149215135459794473448465734589287491880563183624997435193637389884206"\ +"02151395451271809790360963144464736839412254746645151672423256977064" + +sin_abi_imag = \ +"-150.42505378241784671801405965872972765595073690984080160750785565810981"\ +"8314482499135443827055399655645954830931316357243750839088113122816583"\ +"7169201254329464271121058839499197583056427233866320456505060735" + +# cos +cos_a = \ +"-0.98702664499035378399332439243967038895709261414476495730788864004"\ +"05406821549361039745258003422386169330787395654908532996287293003581554"\ +"257037193284199198069707141161341820684198547572456183525659969145501" + +cos_1000a = \ +"-0.51202523570982001856195696460663971099692261342827540426136215533"\ +"52686662667660613179619804463250686852463876088694806607652218586060613"\ +"951310588158830695735537073667299449753951774916401887657320950496820" + +# tan +tan_a = \ +"0.162666873675188117341401059858835168007137819495998960250142156848"\ +"639654718809412181543343168174807985559916643549174530459883826451064966"\ +"7996119428949951351938178809444268785629011625179962457123195557310" + +tan_abi_real = \ +"6.822696615947538488826586186310162599974827139564433912601918442911"\ +"1026830824380070400102213741875804368044342309515353631134074491271890"\ +"467615882710035471686578162073677173148647065131872116479947620E-6" + +tan_abi_imag = \ +"0.9999795833048243692245661011298447587046967777739649018690797625964167"\ +"1446419978852235960862841608081413169601038230073129482874832053357571"\ +"62702259309150715669026865777947502665936317953101462202542168429" + + +def test_hp(): + for dps in precs: + mp.dps = dps + 8 + aa = mpf(a) + bb = mpf(b) + a1000 = 1000*mpf(a) + abi = mpc(aa, bb) + mp.dps = dps + assert (sqrt(3) + pi/2).ae(aa) + assert (e + 1/euler**2).ae(bb) + + assert sqrt(aa).ae(mpf(sqrt_a)) + assert sqrt(abi).ae(mpc(sqrt_abi_real, sqrt_abi_imag)) + + assert log(aa).ae(mpf(log_a)) + assert log(abi).ae(mpc(log_abi_real, log_abi_imag)) + + assert exp(aa).ae(mpf(exp_a)) + assert exp(abi).ae(mpc(exp_abi_real, exp_abi_imag)) + + assert (aa**bb).ae(mpf(pow_a_b)) + assert (aa**abi).ae(mpc(pow_a_abi_real, pow_a_abi_imag)) + assert (abi**abi).ae(mpc(pow_abi_abi_real, pow_abi_abi_imag)) + + assert sin(a).ae(mpf(sin_a)) + assert sin(a1000).ae(mpf(sin_1000a)) + assert sin(abi).ae(mpc(sin_abi_real, sin_abi_imag)) + + assert cos(a).ae(mpf(cos_a)) + assert cos(a1000).ae(mpf(cos_1000a)) + + assert tan(a).ae(mpf(tan_a)) + assert tan(abi).ae(mpc(tan_abi_real, tan_abi_imag)) + + # check that complex cancellation is avoided so that both + # real and imaginary parts have high relative accuracy. + # abs_eps should be 0, but has to be set to 1e-205 to pass the + # 200-digit case, probably due to slight inaccuracy in the + # precomputed input + assert (tan(abi).real).ae(mpf(tan_abi_real), abs_eps=1e-205) + assert (tan(abi).imag).ae(mpf(tan_abi_imag), abs_eps=1e-205) + mp.dps = 460 + assert str(log(3))[-20:] == '02166121184001409826' + +# Since str(a) can differ in the last digit from rounded a, and I want +# to compare the last digits of big numbers with the results in Mathematica, +# I made this hack to get the last 20 digits of rounded a + +def last_digits(a): + r = repr(a) + s = str(a) + #dps = mp.dps + #mp.dps += 3 + m = 10 + r = r.replace(s[:-m],'') + r = r.replace("mpf('",'').replace("')",'') + num0 = 0 + for c in r: + if c == '0': + num0 += 1 + else: + break + b = float(int(r))/10**(len(r) - m) + if b >= 10**m - 0.5: # pragma: no cover + raise NotImplementedError + n = round(b) + sn = str(n) + s = s[:-m] + '0'*num0 + sn + return s[-20:] + +# values checked with Mathematica +def test_log_hp(): + mp.dps = 2000 + a = mpf(10)**15000/3 + r = log(a) + res = last_digits(r) + # Mathematica N[Log[10^15000/3], 2000] + # ...7443804441768333470331 + assert res == '43804441768333470331' + + # see issue 145 + r = log(mpf(3)/2) + # Mathematica N[Log[3/2], 2000] + # ...69653749808140753263288 + res = last_digits(r) + assert res == '53749808140753263288' + + mp.dps = 10000 + r = log(2) + res = last_digits(r) + # Mathematica N[Log[2], 10000] + # ...695615913401856601359655561 + assert res == '13401856601359655561' + r = log(mpf(10)**10/3) + res = last_digits(r) + # Mathematica N[Log[10^10/3], 10000] + # ...587087654020631943060007154 + assert res == '54020631943060007154', res + r = log(mpf(10)**100/3) + res = last_digits(r) + # Mathematica N[Log[10^100/3], 10000] + # ,,,59246336539088351652334666 + assert res == '36539088351652334666', res + mp.dps += 10 + a = 1 - mpf(1)/10**10 + mp.dps -= 10 + r = log(a) + res = last_digits(r) + # ...3310334360482956137216724048322957404 + # 372167240483229574038733026370 + # Mathematica N[Log[1 - 10^-10]*10^10, 10000] + # ...60482956137216724048322957404 + assert res == '37216724048322957404', res + mp.dps = 10000 + mp.dps += 100 + a = 1 + mpf(1)/10**100 + mp.dps -= 100 + + r = log(a) + res = last_digits(+r) + # Mathematica N[Log[1 + 10^-100]*10^10, 10030] + # ...3994733877377412241546890854692521568292338268273 10^-91 + assert res == '39947338773774122415', res + +def test_exp_hp(): + mp.dps = 4000 + r = exp(mpf(1)/10) + # IntegerPart[N[Exp[1/10] * 10^4000, 4000]] + # ...92167105162069688129 + assert int(r * 10**mp.dps) % 10**20 == 92167105162069688129 diff --git a/mpmath/tests/test_identify.py b/mpmath/tests/test_identify.py new file mode 100644 index 0000000..f69483d --- /dev/null +++ b/mpmath/tests/test_identify.py @@ -0,0 +1,22 @@ +from mpmath import e, exp, findpoly, identify, log, mp, pi, pslq, sqrt, zeta + + +def test_pslq(): + assert pslq([3*pi+4*e/7, pi, e, log(2)]) == [7, -21, -4, 0] + assert pslq([4.9999999999999991, 1]) == [1, -5] + assert pslq([2,1]) == [1, -2] + +def test_identify(): + mp.dps = 20 + assert identify(zeta(4), ['log(2)', 'pi**4']) == '((1/90)*pi**4)' + mp.dps = 15 + assert identify(exp(5)) == 'exp(5)' + assert identify(exp(4)) == 'exp(4)' + assert identify(log(5)) == 'log(5)' + assert identify(exp(3*pi), ['pi']) == 'exp((3*pi))' + assert identify(3, full=True) == ['3', '3', '1/(1/3)', 'sqrt(9)', + '1/sqrt((1/9))', '(sqrt(12)/2)**2', '1/(sqrt(12)/6)**2'] + assert identify(pi+1, {'a':+pi}) == '(1 + 1*a)' + +def test_findpoly_deprecated(): + assert findpoly(1+sqrt(2), 2, asc=False) == [1, -2, -1] diff --git a/mpmath/tests/test_interval.py b/mpmath/tests/test_interval.py new file mode 100644 index 0000000..15cc4a6 --- /dev/null +++ b/mpmath/tests/test_interval.py @@ -0,0 +1,453 @@ +import pytest + +from mpmath import inf, iv, mp, mpf, mpi, pi, sqrt, workprec + + +def test_interval_identity(): + assert mpi(2) == mpi(2, 2) + assert mpi(2) != mpi(-2, 2) + assert not (mpi(2) != mpi(2, 2)) + assert mpi(-1, 1) == mpi(-1, 1) + assert str(mpi('0.1')) == "[0.099999999999999991673, 0.10000000000000000555]" + assert repr(mpi('0.1')) == "mpi('0.099999999999999992', '0.10000000000000001')" + u = mpi(-1, 3) + assert -1 in u + assert 2 in u + assert 3 in u + assert -1.1 not in u + assert 3.1 not in u + assert mpi(-1, 3) in u + assert mpi(0, 1) in u + assert mpi(-1.1, 2) not in u + assert mpi(2.5, 3.1) not in u + w = mpi(-inf, inf) + assert mpi(-5, 5) in w + assert mpi(2, inf) in w + assert mpi(0, 2) in mpi(0, 10) + assert 3 not in mpi(-inf, 0) + +def test_interval_hash(): + assert hash(mpi(3)) == hash(3) + assert hash(mpi(3.25)) == hash(3.25) + assert hash(mpi(3,4)) == hash(mpi(3,4)) + assert hash(iv.mpc(3)) == hash(3) + assert hash(iv.mpc(3,4)) == hash(3+4j) + assert hash(iv.mpc((1,3),(2,4))) == hash(iv.mpc((1,3),(2,4))) + +def test_interval_arithmetic(): + assert mpi(2) + mpi(3,4) == mpi(5,6) + assert mpi(1, 2)**2 == mpi(1, 4) + assert mpi(1) + mpi(0, 1e-50) == mpi(1, mpf('1.0000000000000002')) + x = 1 / (1 / mpi(3)) + assert x.a < 3 < x.b + x = mpi(2) ** mpi(0.5) + iv.dps += 5 + sq = iv.sqrt(2) + iv.dps -= 5 + assert x.a < sq < x.b + assert mpi(1) / mpi(1, inf) + assert mpi(2, 3) / inf == mpi(0, 0) + assert mpi(0) / inf == 0 + assert mpi(0) / 0 == mpi(-inf, inf) + assert mpi(inf) / 0 == mpi(-inf, inf) + assert mpi(0) * inf == mpi(-inf, inf) + assert 1 / mpi(2, inf) == mpi(0, 0.5) + assert str((mpi(50, 50) * mpi(-10, -10)) / 3) == \ + '[-166.66666666666668561, -166.66666666666665719]' + assert mpi(0, 4) ** 3 == mpi(0, 64) + assert mpi(2,4).mid == 3 + iv.dps = 30 + a = mpi(iv.pi) + iv.dps = 15 + b = +a + assert b.a < a.a + assert b.b > a.b + a = mpi(iv.pi) + assert a == +a + assert abs(mpi(-1,2)) == mpi(0,2) + assert abs(mpi(0.5,2)) == mpi(0.5,2) + assert abs(mpi(-3,2)) == mpi(0,3) + assert abs(mpi(-3,-0.5)) == mpi(0.5,3) + assert mpi(0) * mpi(2,3) == mpi(0) + assert mpi(2,3) * mpi(0) == mpi(0) + assert mpi(1,3).delta == 2 + assert mpi(1,2) - mpi(3,4) == mpi(-3,-1) + assert mpi(-inf,0) - mpi(0,inf) == mpi(-inf,0) + assert mpi(-inf,0) - mpi(-inf,inf) == mpi(-inf,inf) + assert mpi(0,inf) - mpi(-inf,1) == mpi(-1,inf) + +def test_interval_mul(): + assert mpi(-1, 0) * inf == mpi(-inf, 0) + assert mpi(-1, 0) * -inf == mpi(0, inf) + assert mpi(0, 1) * inf == mpi(0, inf) + assert mpi(0, 1) * mpi(0, inf) == mpi(0, inf) + assert mpi(-1, 1) * inf == mpi(-inf, inf) + assert mpi(-1, 1) * mpi(0, inf) == mpi(-inf, inf) + assert mpi(-1, 1) * mpi(-inf, inf) == mpi(-inf, inf) + assert mpi(-inf, 0) * mpi(0, 1) == mpi(-inf, 0) + assert mpi(-inf, 0) * mpi(0, 0) * mpi(-inf, 0) + assert mpi(-inf, 0) * mpi(-inf, inf) == mpi(-inf, inf) + assert mpi(-5,0)*mpi(-32,28) == mpi(-140,160) + assert mpi(2,3) * mpi(-1,2) == mpi(-3,6) + # Should be undefined? + assert mpi(inf, inf) * 0 == mpi(-inf, inf) + assert mpi(-inf, -inf) * 0 == mpi(-inf, inf) + assert mpi(0) * mpi(-inf,2) == mpi(-inf,inf) + assert mpi(0) * mpi(-2,inf) == mpi(-inf,inf) + assert mpi(-2,inf) * mpi(0) == mpi(-inf,inf) + assert mpi(-inf,2) * mpi(0) == mpi(-inf,inf) + +def test_interval_pow(): + assert mpi(3)**2 == mpi(9, 9) + assert mpi(-3)**2 == mpi(9, 9) + assert mpi(-3, 1)**2 == mpi(0, 9) + assert mpi(-3, -1)**2 == mpi(1, 9) + assert mpi(-3, -1)**3 == mpi(-27, -1) + assert mpi(-3, 1)**3 == mpi(-27, 1) + assert mpi(-2, 3)**2 == mpi(0, 9) + assert mpi(-3, 2)**2 == mpi(0, 9) + assert mpi(4) ** -1 == mpi(0.25, 0.25) + assert mpi(-4) ** -1 == mpi(-0.25, -0.25) + assert mpi(4) ** -2 == mpi(0.0625, 0.0625) + assert mpi(-4) ** -2 == mpi(0.0625, 0.0625) + assert mpi(0, 1) ** inf == mpi(0, 1) + assert mpi(0, 1) ** -inf == mpi(1, inf) + assert mpi(0, inf) ** inf == mpi(0, inf) + assert mpi(0, inf) ** -inf == mpi(0, inf) + assert mpi(1, inf) ** inf == mpi(1, inf) + assert mpi(1, inf) ** -inf == mpi(0, 1) + assert mpi(2, 3) ** 1 == mpi(2, 3) + assert mpi(2, 3) ** 0 == 1 + assert mpi(1,3) ** mpi(2) == mpi(1,9) + +def test_interval_sqrt(): + assert mpi(4) ** 0.5 == mpi(2) + +def test_interval_div(): + assert mpi(0.5, 1) / mpi(-1, 0) == mpi(-inf, -0.5) + assert mpi(0, 1) / mpi(0, 1) == mpi(0, inf) + assert mpi(inf, inf) / mpi(inf, inf) == mpi(0, inf) + assert mpi(inf, inf) / mpi(2, inf) == mpi(0, inf) + assert mpi(inf, inf) / mpi(2, 2) == mpi(inf, inf) + assert mpi(0, inf) / mpi(2, inf) == mpi(0, inf) + assert mpi(0, inf) / mpi(2, 2) == mpi(0, inf) + assert mpi(2, inf) / mpi(2, 2) == mpi(1, inf) + assert mpi(2, inf) / mpi(2, inf) == mpi(0, inf) + assert mpi(-4, 8) / mpi(1, inf) == mpi(-4, 8) + assert mpi(-4, 8) / mpi(0.5, inf) == mpi(-8, 16) + assert mpi(-inf, 8) / mpi(0.5, inf) == mpi(-inf, 16) + assert mpi(-inf, inf) / mpi(0.5, inf) == mpi(-inf, inf) + assert mpi(8, inf) / mpi(0.5, inf) == mpi(0, inf) + assert mpi(-8, inf) / mpi(0.5, inf) == mpi(-16, inf) + assert mpi(-4, 8) / mpi(inf, inf) == mpi(0, 0) + assert mpi(0, 8) / mpi(inf, inf) == mpi(0, 0) + assert mpi(0, 0) / mpi(inf, inf) == mpi(0, 0) + assert mpi(-inf, 0) / mpi(inf, inf) == mpi(-inf, 0) + assert mpi(-inf, 8) / mpi(inf, inf) == mpi(-inf, 0) + assert mpi(-inf, inf) / mpi(inf, inf) == mpi(-inf, inf) + assert mpi(-8, inf) / mpi(inf, inf) == mpi(0, inf) + assert mpi(0, inf) / mpi(inf, inf) == mpi(0, inf) + assert mpi(8, inf) / mpi(inf, inf) == mpi(0, inf) + assert mpi(inf, inf) / mpi(inf, inf) == mpi(0, inf) + assert mpi(-1, 2) / mpi(0, 1) == mpi(-inf, +inf) + assert mpi(0, 1) / mpi(0, 1) == mpi(0.0, +inf) + assert mpi(-1, 0) / mpi(0, 1) == mpi(-inf, 0.0) + assert mpi(-0.5, -0.25) / mpi(0, 1) == mpi(-inf, -0.25) + assert mpi(0.5, 1) / mpi(0, 1) == mpi(0.5, +inf) + assert mpi(0.5, 4) / mpi(0, 1) == mpi(0.5, +inf) + assert mpi(-1, -0.5) / mpi(0, 1) == mpi(-inf, -0.5) + assert mpi(-4, -0.5) / mpi(0, 1) == mpi(-inf, -0.5) + assert mpi(-1, 2) / mpi(-2, 0.5) == mpi(-inf, +inf) + assert mpi(0, 1) / mpi(-2, 0.5) == mpi(-inf, +inf) + assert mpi(-1, 0) / mpi(-2, 0.5) == mpi(-inf, +inf) + assert mpi(-0.5, -0.25) / mpi(-2, 0.5) == mpi(-inf, +inf) + assert mpi(0.5, 1) / mpi(-2, 0.5) == mpi(-inf, +inf) + assert mpi(0.5, 4) / mpi(-2, 0.5) == mpi(-inf, +inf) + assert mpi(-1, -0.5) / mpi(-2, 0.5) == mpi(-inf, +inf) + assert mpi(-4, -0.5) / mpi(-2, 0.5) == mpi(-inf, +inf) + assert mpi(-1, 2) / mpi(-1, 0) == mpi(-inf, +inf) + assert mpi(0, 1) / mpi(-1, 0) == mpi(-inf, 0.0) + assert mpi(-1, 0) / mpi(-1, 0) == mpi(0.0, +inf) + assert mpi(-0.5, -0.25) / mpi(-1, 0) == mpi(0.25, +inf) + assert mpi(0.5, 1) / mpi(-1, 0) == mpi(-inf, -0.5) + assert mpi(0.5, 4) / mpi(-1, 0) == mpi(-inf, -0.5) + assert mpi(-1, -0.5) / mpi(-1, 0) == mpi(0.5, +inf) + assert mpi(-4, -0.5) / mpi(-1, 0) == mpi(0.5, +inf) + assert mpi(-1, 2) / mpi(0.5, 1) == mpi(-2.0, 4.0) + assert mpi(0, 1) / mpi(0.5, 1) == mpi(0.0, 2.0) + assert mpi(-1, 0) / mpi(0.5, 1) == mpi(-2.0, 0.0) + assert mpi(-0.5, -0.25) / mpi(0.5, 1) == mpi(-1.0, -0.25) + assert mpi(0.5, 1) / mpi(0.5, 1) == mpi(0.5, 2.0) + assert mpi(0.5, 4) / mpi(0.5, 1) == mpi(0.5, 8.0) + assert mpi(-1, -0.5) / mpi(0.5, 1) == mpi(-2.0, -0.5) + assert mpi(-4, -0.5) / mpi(0.5, 1) == mpi(-8.0, -0.5) + assert mpi(-1, 2) / mpi(-2, -0.5) == mpi(-4.0, 2.0) + assert mpi(0, 1) / mpi(-2, -0.5) == mpi(-2.0, 0.0) + assert mpi(-1, 0) / mpi(-2, -0.5) == mpi(0.0, 2.0) + assert mpi(-0.5, -0.25) / mpi(-2, -0.5) == mpi(0.125, 1.0) + assert mpi(0.5, 1) / mpi(-2, -0.5) == mpi(-2.0, -0.25) + assert mpi(0.5, 4) / mpi(-2, -0.5) == mpi(-8.0, -0.25) + assert mpi(-1, -0.5) / mpi(-2, -0.5) == mpi(0.25, 2.0) + assert mpi(-4, -0.5) / mpi(-2, -0.5) == mpi(0.25, 8.0) + # Should be undefined? + assert mpi(0, 0) / mpi(0, 0) == mpi(-inf, inf) + assert mpi(0, 0) / mpi(0, 1) == mpi(-inf, inf) + +def test_interval_cos_sin(): + cos = iv.cos + sin = iv.sin + tan = iv.tan + pi = iv.pi + # Around 0 + assert cos(mpi(0)) == 1 + assert sin(mpi(0)) == 0 + assert cos(mpi(0,1)) == mpi(0.54030230586813965399, 1.0) + assert sin(mpi(0,1)) == mpi(0, 0.8414709848078966159) + assert cos(mpi(1,2)) == mpi(-0.4161468365471424069, 0.54030230586813976501) + assert sin(mpi(1,2)) == mpi(0.84147098480789650488, 1.0) + assert sin(mpi(1,2.5)) == mpi(0.59847214410395643824, 1.0) + assert cos(mpi(-1, 1)) == mpi(0.54030230586813965399, 1.0) + assert cos(mpi(-1, 0.5)) == mpi(0.54030230586813965399, 1.0) + assert cos(mpi(-1, 1.5)) == mpi(0.070737201667702906405, 1.0) + assert sin(mpi(-1,1)) == mpi(-0.8414709848078966159, 0.8414709848078966159) + assert sin(mpi(-1,0.5)) == mpi(-0.8414709848078966159, 0.47942553860420300538) + assert mpi(-0.8414709848078966159, 1.00000000000000002e-100) in sin(mpi(-1,1e-100)) + assert mpi(-2.00000000000000004e-100, 1.00000000000000002e-100) in sin(mpi(-2e-100,1e-100)) + # Same interval + assert cos(mpi(2, 2.5)) + assert cos(mpi(3.5, 4)) == mpi(-0.93645668729079634129, -0.65364362086361182946) + assert cos(mpi(5, 5.5)) == mpi(0.28366218546322624627, 0.70866977429126010168) + assert mpi(0.59847214410395654927, 0.90929742682568170942) in sin(mpi(2, 2.5)) + assert sin(mpi(3.5, 4)) == mpi(-0.75680249530792831347, -0.35078322768961983646) + assert sin(mpi(5, 5.5)) == mpi(-0.95892427466313856499, -0.70554032557039181306) + # Higher roots + iv.dps = 55 + w = 4*10**50 + mpi(0.5) + for p in [15, 40, 80]: + iv.dps = p + assert 0 in sin(4*mpi(pi)) + assert 0 in sin(4*10**50*mpi(pi)) + assert 0 in cos((4+0.5)*mpi(pi)) + assert 0 in cos(w*mpi(pi)) + assert 1 in cos(4*mpi(pi)) + assert 1 in cos(4*10**50*mpi(pi)) + iv.dps = 15 + assert cos(mpi(2,inf)) == mpi(-1,1) + assert sin(mpi(2,inf)) == mpi(-1,1) + assert cos(mpi(-inf,2)) == mpi(-1,1) + assert sin(mpi(-inf,2)) == mpi(-1,1) + u = tan(mpi(0.5,1)) + assert mpf(u.a).ae(mp.tan(0.5)) + assert mpf(u.b).ae(mp.tan(1)) + v = iv.cot(mpi(0.5,1)) + assert mpf(v.a).ae(mp.cot(1)) + assert mpf(v.b).ae(mp.cot(0.5)) + # Sanity check of evaluation at n*pi and (n+1/2)*pi + for n in range(-5,7,2): + x = iv.cos(n*iv.pi) + assert -1 in x + assert x >= -1 + assert x != -1 + x = iv.sin((n+0.5)*iv.pi) + assert -1 in x + assert x >= -1 + assert x != -1 + for n in range(-6,8,2): + x = iv.cos(n*iv.pi) + assert 1 in x + assert x <= 1 + if n: + assert x != 1 + x = iv.sin((n+0.5)*iv.pi) + assert 1 in x + assert x <= 1 + assert x != 1 + for n in range(-6,7): + x = iv.cos((n+0.5)*iv.pi) + assert x.a < 0 < x.b + x = iv.sin(n*iv.pi) + if n: + assert x.a < 0 < x.b + +def test_interval_complex(): + # TODO: many more tests + assert iv.mpc(2,3) == 2+3j + assert iv.mpc(2,3) != 2+4j + assert iv.mpc(2,3) != 1+3j + assert 1+3j in iv.mpc([1,2],[3,4]) + assert 2+5j not in iv.mpc([1,2],[3,4]) + assert iv.mpc(1,2) + 1j == 1+3j + assert iv.mpc([1,2],[2,3]) + 2+3j == iv.mpc([3,4],[5,6]) + assert iv.mpc([2,4],[4,8]) / 2 == iv.mpc([1,2],[2,4]) + assert iv.mpc([1,2],[2,4]) * 2j == iv.mpc([-8,-4],[2,4]) + assert iv.mpc([2,4],[4,8]) / 2j == iv.mpc([2,4],[-2,-1]) + assert iv.exp(2+3j).ae(mp.exp(2+3j)) + assert iv.log(2+3j).ae(mp.log(2+3j)) + assert (iv.mpc(2,3) ** iv.mpc(0.5,2)).ae(mp.mpc(2,3) ** mp.mpc(0.5,2)) + assert 1j in (iv.mpf(-1) ** 0.5) + assert 1j in (iv.mpc(-1) ** 0.5) + assert abs(iv.mpc(0)) == 0 + assert abs(iv.mpc(inf)) == inf + assert abs(iv.mpc(3,4)) == 5 + assert abs(iv.mpc(4)) == 4 + assert abs(iv.mpc(0,4)) == 4 + assert abs(iv.mpc(0,[2,3])) == iv.mpf([2,3]) + assert abs(iv.mpc(0,[-3,2])) == iv.mpf([0,3]) + assert abs(iv.mpc([3,5],[4,12])) == iv.mpf([5,13]) + assert abs(iv.mpc([3,5],[-4,12])) == iv.mpf([3,13]) + assert iv.mpc(2,3) ** 0 == 1 + assert iv.mpc(2,3) ** 1 == (2+3j) + assert iv.mpc(2,3) ** 2 == (2+3j)**2 + assert iv.mpc(2,3) ** 3 == (2+3j)**3 + assert iv.mpc(2,3) ** 4 == (2+3j)**4 + assert iv.mpc(2,3) ** 5 == (2+3j)**5 + assert iv.mpc(2,2) ** (-1) == (2+2j) ** (-1) + assert iv.mpc(2,2) ** (-2) == (2+2j) ** (-2) + assert iv.cos(2).ae(mp.cos(2)) + assert iv.sin(2).ae(mp.sin(2)) + with workprec(54): + assert iv.cos(2+3j).ae(mp.cos(2+3j)) + assert iv.sin(2+3j).ae(mp.sin(2+3j)) + +def test_interval_complex_arg(): + assert iv.arg(3) == 0 + assert iv.arg(0) == 0 + assert iv.arg([0,3]) == 0 + assert iv.arg(-3).ae(pi) + assert iv.arg(2+3j).ae(iv.arg(2+3j)) + z = iv.mpc([-2,-1],[3,4]) + t = iv.arg(z) + assert t.a.ae(mp.arg(-1+4j)) + assert t.b.ae(mp.arg(-2+3j)) + z = iv.mpc([-2,1],[3,4]) + t = iv.arg(z) + assert t.a.ae(mp.arg(1+3j)) + assert t.b.ae(mp.arg(-2+3j)) + z = iv.mpc([1,2],[3,4]) + t = iv.arg(z) + assert t.a.ae(mp.arg(2+3j)) + assert t.b.ae(mp.arg(1+4j)) + z = iv.mpc([1,2],[-2,3]) + t = iv.arg(z) + assert t.a.ae(mp.arg(1-2j)) + assert t.b.ae(mp.arg(1+3j)) + z = iv.mpc([1,2],[-4,-3]) + t = iv.arg(z) + assert t.a.ae(mp.arg(1-4j)) + assert t.b.ae(mp.arg(2-3j)) + z = iv.mpc([-1,2],[-4,-3]) + t = iv.arg(z) + assert t.a.ae(mp.arg(-1-3j)) + assert t.b.ae(mp.arg(2-3j)) + z = iv.mpc([-2,-1],[-4,-3]) + t = iv.arg(z) + assert t.a.ae(mp.arg(-2-3j)) + assert t.b.ae(mp.arg(-1-4j)) + z = iv.mpc([-2,-1],[-3,3]) + t = iv.arg(z) + assert t.a.ae(-mp.pi) + assert t.b.ae(mp.pi) + z = iv.mpc([-2,2],[-3,3]) + t = iv.arg(z) + assert t.a.ae(-mp.pi) + assert t.b.ae(mp.pi) + +def test_interval_ae(): + x = iv.mpf([1,2]) + pytest.raises(ValueError, lambda: x.ae(1)) + pytest.raises(ValueError, lambda: x.ae(1.5)) + pytest.raises(ValueError, lambda: x.ae(2)) + assert x.ae(2.01) is False + assert x.ae(0.99) is False + x = iv.mpf(3.5) + assert x.ae(3.5) is True + assert x.ae(3.5+1e-15) is True + assert x.ae(3.5-1e-15) is True + assert x.ae(3.501) is False + assert x.ae(3.499) is False + pytest.raises(ValueError, lambda: x.ae(iv.mpf([3.5,3.501]))) + pytest.raises(ValueError, lambda: x.ae(iv.mpf([3.5,4.5+1e-15]))) + +def test_interval_nstr(): + iv.dps = n = 30 + x = mpi(1, 2) + assert iv.nstr(x, n, mode='plusminus') == '1.5 +- 0.5' + assert iv.nstr(x, n, mode='plusminus', use_spaces=False) == '1.5+-0.5' + assert iv.nstr(x, n, mode='percent') == '1.5 (33.33%)' + assert iv.nstr(x, n, mode='brackets', use_spaces=False) == '[1.0,2.0]' + assert iv.nstr(x, n, mode='brackets' , brackets=('<', '>')) == '<1.0, 2.0>' + x = mpi('5.2582327113062393041', '5.2582327113062749951') + assert iv.nstr(x, n, mode='diff') == '5.2582327113062[393041, 749951]' + assert iv.nstr(iv.cos(mpi(1)), n, mode='diff', use_spaces=False) == '0.54030230586813971740093660744[2955,3053]' + assert iv.nstr(mpi('1e123', '1e129'), n, mode='diff') == '[1.0e+123, 1.0e+129]' + exp = iv.exp + assert iv.nstr(iv.exp(mpi('5000.1')), n, mode='diff') == '3.2797365856787867069110487[0926, 1191]e+2171' + assert iv.nstr(iv.mpc(3, 4)) == '([3.0, 3.0] + [4.0, 4.0]*j)' + +def test_mpi_from_str(): + assert iv.convert('1.5 +- 0.5') == mpi(mpf('1.0'), mpf('2.0')) + assert mpi(1, 2) in iv.convert('1.5 (33.33333333333333333333333333333%)') + assert iv.convert('[1, 2]') == mpi(1, 2) + assert iv.convert('1[2, 3]') == mpi(12, 13) + assert iv.convert('1.[23,46]e-8') == mpi('1.23e-8', '1.46e-8') + assert iv.convert('12[3.4,5.9]e4') == mpi('123.4e+4', '125.9e4') + +def test_interval_gamma(): + # TODO: need many more tests + assert iv.rgamma(0) == 0 + assert iv.fac(0) == 1 + assert iv.fac(1) == 1 + assert iv.fac(2) == 2 + assert iv.fac(3) == 6 + assert iv.gamma(0) == [-inf,inf] + assert iv.gamma(1) == 1 + assert iv.gamma(2) == 1 + assert iv.gamma(3) == 2 + assert -3.5449077018110320546 in iv.gamma(-0.5) + assert 0.49801566811835601-0.1549498283018107j in iv.gamma(1+1j) + assert iv.loggamma(1) == 0 + assert iv.loggamma(2) == 0 + assert 0.69314718055994530942 in iv.loggamma(3) + # Test tight log-gamma endpoints based on monotonicity + xs = [iv.mpc([2,3],[1,4]), + iv.mpc([2,3],[-4,-1]), + iv.mpc([2,3],[-1,4]), + iv.mpc([2,3],[-4,1]), + iv.mpc([2,3],[-4,4]), + iv.mpc([-3,-2],[2,4]), + iv.mpc([-3,-2],[-4,-2])] + for x in xs: + ys = [mp.loggamma(mp.mpc(x.a,x.c)), + mp.loggamma(mp.mpc(x.b,x.c)), + mp.loggamma(mp.mpc(x.a,x.d)), + mp.loggamma(mp.mpc(x.b,x.d))] + if 0 in x.imag: + ys += [mp.loggamma(x.a), mp.loggamma(x.b)] + min_real = min([y.real for y in ys]) + max_real = max([y.real for y in ys]) + min_imag = min([y.imag for y in ys]) + max_imag = max([y.imag for y in ys]) + z = iv.loggamma(x) + assert z.a.ae(min_real) + assert z.b.ae(max_real) + assert z.c.ae(min_imag) + assert z.d.ae(max_imag) + +def test_interval_conversions(): + for a, b in ((-0.0, 0), (0.0, 0.5), (1.0, 1), \ + ('-inf', 20.5), ('-inf', float(sqrt(2)))): + r = mpi(a, b) + assert int(r.b) == int(b) + assert float(r.a) == float(a) + assert float(r.b) == float(b) + assert complex(r.a) == complex(a) + assert complex(r.b) == complex(b) + +def test_issue_258(): + a = iv.mpf([0, 1]) + b = 0.5 + pytest.raises(ValueError, lambda: min(a, b)) + pytest.raises(ValueError, lambda: max(a, b)) + +def test_mpi_mag(): + assert iv.mag(iv.mpc(3, 4)) == 4 diff --git a/mpmath/tests/test_levin.py b/mpmath/tests/test_levin.py new file mode 100644 index 0000000..e4c8b96 --- /dev/null +++ b/mpmath/tests/test_levin.py @@ -0,0 +1,149 @@ +from mpmath import mp + + +# Attention: +# These tests run with 15-20 decimal digits precision. For higher precision the +# working precision must be raised. + +def test_levin_0(): + mp.dps = 17 + eps = mp.mpf(mp.eps) + with mp.extraprec(2 * mp.prec): + L = mp.levin(method = "levin", variant = "u") + S, s, n = [], 0, 1 + while 1: + s += mp.one / (n * n) + n += 1 + S.append(s) + v, e = L.update_psum(S) + if e < eps: + break + if n > 1000: raise RuntimeError("iteration limit exceeded") + eps = mp.exp(0.9 * mp.log(eps)) + err = abs(v - mp.pi ** 2 / 6) + assert err < eps + w = mp.nsum(lambda n: 1/(n * n), [1, mp.inf], method = "levin", levin_variant = "u") + err = abs(v - w) + assert err < eps + +def test_levin_1(): + mp.dps = 17 + eps = mp.mpf(mp.eps) + with mp.extraprec(2 * mp.prec): + L = mp.levin(method = "levin", variant = "v") + A, n = [], 1 + while 1: + s = mp.mpf(n) ** (2 + 3j) + n += 1 + A.append(s) + v, e = L.update(A) + if e < eps: + break + if n > 1000: raise RuntimeError("iteration limit exceeded") + eps = mp.exp(0.9 * mp.log(eps)) + err = abs(v - mp.zeta(-2-3j)) + assert err < eps + w = mp.nsum(lambda n: n ** (2 + 3j), [1, mp.inf], method = "levin", levin_variant = "v") + err = abs(v - w) + assert err < eps + +def test_levin_2(): + # [2] A. Sidi - "Pratical Extrapolation Methods" p.373 + mp.dps = 17 + z=mp.mpf(10) + eps = mp.mpf(mp.eps) + with mp.extraprec(2 * mp.prec): + L = mp.levin(method = "sidi", variant = "t") + n = 0 + while 1: + s = (-1)**n * mp.fac(n) * z ** (-n) + v, e = L.step(s) + n += 1 + if e < eps: + break + if n > 1000: raise RuntimeError("iteration limit exceeded") + eps = mp.exp(0.9 * mp.log(eps)) + exact = mp.quad(lambda x: mp.exp(-x)/(1+x/z),[0,mp.inf]) + # there is also a symbolic expression for the integral: + # exact = z * mp.exp(z) * mp.expint(1,z) + err = abs(v - exact) + assert err < eps + w = mp.nsum(lambda n: (-1) ** n * mp.fac(n) * z ** (-n), [0, mp.inf], method = "sidi", levin_variant = "t") + assert err < eps + +def test_levin_3(): + mp.dps = 17 + z=mp.mpf(2) + eps = mp.mpf(mp.eps) + with mp.extraprec(7*mp.prec): # we need copious amount of precision to sum this highly divergent series + L = mp.levin(method = "levin", variant = "t") + n, s = 0, 0 + while 1: + s += (-z)**n * mp.fac(4 * n) / (mp.fac(n) * mp.fac(2 * n) * (4 ** n)) + n += 1 + v, e = L.step_psum(s) + if e < eps: + break + if n > 1000: raise RuntimeError("iteration limit exceeded") + eps = mp.exp(0.8 * mp.log(eps)) + exact = mp.quad(lambda x: mp.exp( -x * x / 2 - z * x ** 4), [0,mp.inf]) * 2 / mp.sqrt(2 * mp.pi) + # there is also a symbolic expression for the integral: + # exact = mp.exp(mp.one / (32 * z)) * mp.besselk(mp.one / 4, mp.one / (32 * z)) / (4 * mp.sqrt(z * mp.pi)) + err = abs(v - exact) + assert err < eps + w = mp.nsum(lambda n: (-z)**n * mp.fac(4 * n) / (mp.fac(n) * mp.fac(2 * n) * (4 ** n)), [0, mp.inf], + method = "levin", levin_variant = "t", workprec = 8*mp.prec, steps = [2] + [1 for x in range(1000)]) + err = abs(v - w) + assert err < eps + +def test_levin_nsum(): + mp.dps = 17 + + with mp.extraprec(mp.prec): + z = mp.mpf(10) ** (-10) + a = mp.nsum(lambda n: n**(-(1+z)), [1, mp.inf], method = "l") - 1 / z + assert abs(a - mp.euler) < 1e-10 + + eps = mp.exp(0.8 * mp.log(mp.eps)) + + a = mp.nsum(lambda n: (-1)**(n-1) / n, [1, mp.inf], method = "sidi") + assert abs(a - mp.log(2)) < eps + + z = 2 + 1j + f = lambda n: mp.rf(2 / mp.mpf(3), n) * mp.rf(4 / mp.mpf(3), n) * z**n / (mp.rf(1 / mp.mpf(3), n) * mp.fac(n)) + v = mp.nsum(f, [0, mp.inf], method = "levin", steps = [10 for x in range(1000)]) + exact = mp.hyp2f1(2 / mp.mpf(3), 4 / mp.mpf(3), 1 / mp.mpf(3), z) + assert abs(exact - v) < eps + +def test_cohen_alt_0(): + mp.dps = 17 + AC = mp.cohen_alt() + S, s, n = [], 0, 1 + while 1: + s += -((-1) ** n) * mp.one / (n * n) + n += 1 + S.append(s) + v, e = AC.update_psum(S) + if e < mp.eps: + break + if n > 1000: raise RuntimeError("iteration limit exceeded") + eps = mp.exp(0.9 * mp.log(mp.eps)) + err = abs(v - mp.pi ** 2 / 12) + assert err < eps + +def test_cohen_alt_1(): + mp.dps = 17 + A = [] + AC = mp.cohen_alt() + n = 1 + while 1: + A.append( mp.loggamma(1 + mp.one / (2 * n - 1))) + A.append(-mp.loggamma(1 + mp.one / (2 * n))) + n += 1 + v, e = AC.update(A) + if e < mp.eps: + break + if n > 1000: raise RuntimeError("iteration limit exceeded") + v = mp.exp(v) + err = abs(v - 1.06215090557106) + assert err < 1e-12 diff --git a/mpmath/tests/test_linalg.py b/mpmath/tests/test_linalg.py new file mode 100644 index 0000000..58f2906 --- /dev/null +++ b/mpmath/tests/test_linalg.py @@ -0,0 +1,380 @@ +# TODO: don't use round + +import pytest + +from mpmath import (cond, det, diag, exp, expm, extend, extradps, eye, fp, + hilbert, inf, inverse, iv, j, lu, lu_solve, matrix, mnorm, + mp, mpc, mpf, nint, norm, pi, pinv, qr, qr_solve, rand, rank, + randmatrix, residual, zeros, absmin, eps) + + +# XXX: these shouldn't be visible(?) +LU_decomp = mp.LU_decomp +L_solve = mp.L_solve +U_solve = mp.U_solve +householder = mp.householder +improve_solution = mp.improve_solution + +A1 = matrix([[3, 1, 6], + [2, 1, 3], + [1, 1, 1]]) +b1 = [2, 7, 4] + +A2 = matrix([[ 2, -1, -1, 2], + [ 6, -2, 3, -1], + [-4, 2, 3, -2], + [ 2, 0, 4, -3]]) +b2 = [3, -3, -2, -1] + +A3 = matrix([[ 1, 0, -1, -1, 0], + [ 0, 1, 1, 0, -1], + [ 4, -5, 2, 0, 0], + [ 0, 0, -2, 9,-12], + [ 0, 5, 0, 0, 12]]) +b3 = [0, 0, 0, 0, 50] + +A4 = matrix([[10.235, -4.56, 0., -0.035, 5.67], + [-2.463, 1.27, 3.97, -8.63, 1.08], + [-6.58, 0.86, -0.257, 9.32, -43.6 ], + [ 9.83, 7.39, -17.25, 0.036, 24.86], + [-9.31, 34.9, 78.56, 1.07, 65.8 ]]) +b4 = [8.95, 20.54, 7.42, 5.60, 58.43] + +A5 = matrix([[ 1, 2, -4], + [-2, -3, 5], + [ 3, 5, -8]]) + +A6 = matrix([[ 1.377360, 2.481400, 5.359190], + [ 2.679280, -1.229560, 25.560210], + [-1.225280+1.e6, 9.910180, -35.049900-1.e6]]) +b6 = [23.500000, -15.760000, 2.340000] + +A7 = matrix([[1, -0.5], + [2, 1], + [-2, 6]]) +b7 = [3, 2, -4] + +A8 = matrix([[1, 2, 3], + [-1, 0, 1], + [-1, -2, -1], + [1, 0, -1]]) +b8 = [1, 2, 3, 4] + +A9 = matrix([[ 4, 2, -2], + [ 2, 5, -4], + [-2, -4, 5.5]]) +b9 = [10, 16, -15.5] + +A10 = matrix([[1.0 + 1.0j, 2.0, 2.0], + [4.0, 5.0, 6.0], + [7.0, 8.0, 9.0]]) +b10 = [1.0, 1.0 + 1.0j, 1.0] + +A11 = matrix([[4, 0, -2], + [2, 0, -4], + [2, 0, 5.5]]) + +A12 = matrix([[1,0,0], + [0,1,0], + [0,0,1.0j]]) + +A13 = matrix([[2, 6, 4], + [3, 8, 6], + [1, 1, 2]]) + +A14 = matrix(0, 0) + +def test_LU_decomp(): + A = A3.copy() + b = b3 + A, p = LU_decomp(A) + y = L_solve(A, b, p) + x = U_solve(A, y) + assert p == [2, 1, 2, 3] + assert [round(i, 14) for i in x] == [3.78953107960742, 2.9989094874591098, + -0.081788440567070006, 3.8713195201744801, 2.9171210468920399] + A = A4.copy() + b = b4 + A, p = LU_decomp(A) + y = L_solve(A, b, p) + x = U_solve(A, y) + assert p == [0, 3, 4, 3] + assert [round(i, 14) for i in x] == [2.6383625899619201, 2.6643834462368399, + 0.79208015947958998, -2.5088376454101899, -1.0567657691375001] + A = randmatrix(3) + bak = A.copy() + LU_decomp(A, overwrite=1) + assert A != bak + + pytest.raises(ZeroDivisionError, LU_decomp, A11) + +def test_inverse(): + for A in [A1, A2, A5]: + inv = inverse(A) + assert mnorm(A*inv - eye(A.rows), 1) < 1.e-14 + +def test_pinv(): + # Test the Moore Penrose pseudoinverse for square matrices. + for A in [A1, A2, A5]: + inv = pinv(A) + assert mnorm(A*inv - eye(A.rows), 1) < 1.e-13 + + # Test the Moore Penrose pseudoinverse for non-square matrices. + A = matrix([[1, 0], [0, 1], [0, 1]]) + Aplus = matrix([[1, 0, 0], [0, 0.5, 0.5]]) + assert mnorm(pinv(A) - Aplus, 1) < 1.e-14 + + # Check with non-default tolerance. + assert mnorm(pinv(A, rtol=1e-20) - Aplus, 1) < 1.e-14 + +def test_householder(): + A, b = A8, b8 + H, p, x, r = householder(extend(A, b)) + assert H == matrix( + [[mpf('3.0'), mpf('-2.0'), mpf('-1.0'), 0], + [-1.0,mpf('3.333333333333333'),mpf('-2.9999999999999991'),mpf('2.0')], + [-1.0, mpf('-0.66666666666666674'),mpf('2.8142135623730948'), + mpf('-2.8284271247461898')], + [1.0, mpf('-1.3333333333333333'),mpf('-0.20000000000000018'), + mpf('4.2426406871192857')]]) + assert p == [-2, -2, mpf('-1.4142135623730949')] + assert round(norm(r, 2), 10) == 4.2426406870999998 + + y = [102.102, 58.344, 36.463, 24.310, 17.017, 12.376, 9.282, 7.140, 5.610, + 4.488, 3.6465, 3.003] + + def coeff(n): + # similiar to Hilbert matrix + A = [] + for i in range(1, 13): + A.append([1. / (i + j - 1) for j in range(1, n + 1)]) + return matrix(A) + + residuals = [] + refres = [] + for n in range(2, 7): + A = coeff(n) + H, p, x, r = householder(extend(A, y)) + x = matrix(x) + y = matrix(y) + residuals.append(norm(r, 2)) + refres.append(norm(residual(A, x, y), 2)) + assert [round(res, 10) for res in residuals] == [15.1733888877, + 0.82378073210000002, 0.302645887, 0.0260109244, + 0.00058653999999999998] + assert norm(matrix(residuals) - matrix(refres), inf) < 1.e-13 + + def hilbert_cmplx(n): + # Complexified Hilbert matrix + A = hilbert(2*n,n) + v = randmatrix(2*n, 2, min=-1, max=1) + v = v.apply(lambda x: exp(1J*pi()*x)) + A = diag(v[:,0])*A*diag(v[:n,1]) + return A + + residuals_cmplx = [] + refres_cmplx = [] + for n in range(2, 10): + A = hilbert_cmplx(n) + H, p, x, r = householder(A.copy()) + residuals_cmplx.append(norm(r, 2)) + refres_cmplx.append(norm(residual(A[:,:n-1], x, A[:,n-1]), 2)) + assert norm(matrix(residuals_cmplx) - matrix(refres_cmplx), inf) < 1.e-13 + +def test_factorization(): + A = randmatrix(5) + P, L, U = lu(A) + assert mnorm(P*A - L*U, 1) < 1.e-15 + +def test_solve(): + assert norm(residual(A6, lu_solve(A6, b6), b6), inf) < 1.e-10 + assert norm(residual(A7, lu_solve(A7, b7), b7), inf) < 1.5 + assert norm(residual(A8, lu_solve(A8, b8), b8), inf) <= 3 + 1.e-10 + assert norm(residual(A6, qr_solve(A6, b6)[0], b6), inf) < 1.e-10 + assert norm(residual(A7, qr_solve(A7, b7)[0], b7), inf) < 1.5 + assert norm(residual(A8, qr_solve(A8, b8)[0], b8), 2) <= 4.3 + assert norm(residual(A10, lu_solve(A10, b10), b10), 2) < 1.e-10 + assert norm(residual(A10, qr_solve(A10, b10)[0], b10), 2) < 1.e-10 + +def test_solve_overdet_complex(): + A = matrix([[1, 2j], [3, 4j], [5, 6]]) + b = matrix([1 + j, 2, -j]) + assert norm(residual(A, lu_solve(A, b), b)) < 1.0208 + +def test_qr_solve_issue_983(): + A = matrix([[1, -pi/20, (-pi/20)**2, (-pi/20)**3], + [1, 0, 0, 0], + [1, pi / 20, (pi/20)**2, (pi/20)**3], + [1, pi/10, (pi/10)**2, (pi/10)**3]]) + b = matrix([[mp.sin(-pi/20)], + [0], + [mp.sin(pi/20)], + [mp.sin(pi/20)]]) + x, _ = qr_solve(A, b) + assert norm(residual(A, x, b), inf) < 1e-14 + +def test_singular(): + A = [[5.6, 1.2], [7./15, .1]] + B = repr(zeros(2)) + b = [1, 2] + for i in ['lu_solve(%s, %s)' % (A, b), 'lu_solve(%s, %s)' % (B, b), + 'qr_solve(%s, %s)' % (A, b), 'qr_solve(%s, %s)' % (B, b)]: + pytest.raises((ZeroDivisionError, ValueError), lambda: eval(i)) + +def test_cholesky(): + assert fp.cholesky(fp.matrix(A9)) == fp.matrix([[2, 0, 0], [1, 2, 0], [-1, -3/2, 3/2]]) + x = fp.cholesky_solve(A9, b9) + assert fp.norm(fp.residual(A9, x, b9), fp.inf) == 0 + + a = fp.matrix([[1, 0.5j], [-0.5j, 0.5]]) + b = fp.ones(2, 1) + x = fp.cholesky_solve(a, b) + assert fp.norm(fp.residual(a, x, b), fp.inf) == 0 + +def test_det(): + assert det(A1) == 1 + assert round(det(A2), 14) == 8 + assert round(det(A3)) == 1834 + assert round(det(A4)) == 4443376 + assert det(A5) == 1 + assert round(det(A6)) == 78356463 + assert det(zeros(3)) == 0 + assert det(A11) == 0 + assert absmin(det(A12*1e-30) - 1e-30) < eps + assert det(A14) == 1 + +def test_cond(): + A = matrix([[1.2969, 0.8648], [0.2161, 0.1441]]) + assert cond(A, lambda x: mnorm(x,1)) == mpf('327065209.73817754') + assert cond(A, lambda x: mnorm(x,inf)) == mpf('327065209.73817754') + assert cond(A, lambda x: mnorm(x,'F')) == mpf('249729266.80008656') + +@extradps(50) +def test_precision(): + A = randmatrix(10, 10) + assert mnorm(inverse(inverse(A)) - A, 1) < 1.e-45 + +def test_interval_matrix(): + a = iv.matrix([['0.1','0.3','1.0'],['7.1','5.5','4.8'],['3.2','4.4','5.6']]) + b = iv.matrix(['4','0.6','0.5']) + c = iv.lu_solve(a, b) + assert c[0].delta < 1e-13 + assert c[1].delta < 1e-13 + assert c[2].delta < 1e-13 + assert 5.25823271130625686059275 in c[0] + assert -13.155049396267837541163 in c[1] + assert 7.42069154774972557628979 in c[2] + +def test_LU_cache(): + A = randmatrix(3) + LU = LU_decomp(A) + assert A._LU == LU_decomp(A) + A[0,0] = -1000 + assert A._LU is None + +def test_improve_solution(): + A = randmatrix(5, min=1e-20, max=1e20) + b = randmatrix(5, 1, min=-1000, max=1000) + x1 = lu_solve(A, b) + randmatrix(5, 1, min=-1e-5, max=1.e-5) + x2 = improve_solution(A, x1, b) + assert norm(residual(A, x2, b), 2) < norm(residual(A, x1, b), 2) + +def test_exp_pade(): + for i in range(3): + dps = 15 + extra = 15 + mp.dps = dps + extra + dm = 0 + N = 3 + dg = range(1,N+1) + a = diag(dg) + expa = diag([exp(x) for x in dg]) + # choose a random matrix not close to be singular + # to avoid adding too much extra precision in computing + # m**-1 * M * m + while abs(dm) < 0.01: + m = randmatrix(N) + dm = det(m) + m = m/dm + a1 = m**-1 * a * m + e2 = m**-1 * expa * m + mp.dps = dps + e1 = expm(a1, method='pade') + mp.dps = dps + extra + d = e2 - e1 + mp.dps = dps + assert norm(d, inf).ae(0) + +def test_qr(): + lowlimit = -9 # lower limit of matrix element value + uplimit = 9 # uppter limit of matrix element value + maxm = 4 # max matrix size + flg = False # toggle to create real vs complex matrix + zero = mpf('0.0') + + for k in range(10): + exdps = 0 + mode = 'full' + flg = bool(k % 2) + + # generate arbitrary matrix size (2 to maxm) + num1 = nint(maxm*rand()) + num2 = nint(maxm*rand()) + m = int(max(num1, num2)) + n = int(min(num1, num2)) + + # create matrix + A = mp.matrix(m,n) + + # populate matrix values with arbitrary integers + if flg: + flg = False + dtype = 'complex' + for j in range(n): + for i in range(m): + val = nint(lowlimit + (uplimit-lowlimit)*rand()) + val2 = nint(lowlimit + (uplimit-lowlimit)*rand()) + A[i,j] = mpc(val, val2) + else: + flg = True + dtype = 'real' + for j in range(n): + for i in range(m): + val = nint(lowlimit + (uplimit-lowlimit)*rand()) + A[i,j] = mpf(val) + + # perform A -> QR decomposition + Q, R = qr(A, mode, edps = exdps) + + maxnorm = mpf('1.0E-11') + n1 = norm(A - Q * R) + assert n1 <= maxnorm + + if dtype == 'real': + n1 = norm(eye(m) - Q.T * Q) + assert n1 <= maxnorm + + n1 = norm(eye(m) - Q * Q.T) + assert n1 <= maxnorm + + if dtype == 'complex': + n1 = norm(eye(m) - Q.T * Q.conjugate()) + assert n1 <= maxnorm + + n1 = norm(eye(m) - Q.conjugate() * Q.T) + assert n1 <= maxnorm + +def test_rank(): + assert rank(A1) == 3 + assert rank(A2) == 4 + assert rank(A3) == 5 + assert rank(A4) == 5 + assert rank(A5) == 3 + assert rank(A6) == 3 + assert rank(zeros(3)) == 0 + assert rank(A11) == 2 + assert rank(A1*A11) == 2 + assert rank(A11*A11) == 2 + iszerofunc = lambda x: not bool(x) + assert rank(A13) == 2 + assert rank(A13, iszerofunc) == 3 diff --git a/mpmath/tests/test_matrices.py b/mpmath/tests/test_matrices.py new file mode 100644 index 0000000..fee5e0c --- /dev/null +++ b/mpmath/tests/test_matrices.py @@ -0,0 +1,306 @@ +import sys + +import pytest + +from mpmath import (convert, diag, extend, eye, fp, hilbert, inf, inverse, iv, + j, matrix, mnorm, mp, mpc, mpf, mpi, norm, nstr, ones, + randmatrix, sqrt, swap_row, zeros) + + +def test_matrix_basic(): + A1 = matrix(3) + for i in range(3): + A1[i,i] = 1 + assert A1 == eye(3) + assert A1 == matrix(A1) + A2 = matrix(3, 2) + assert not A2._data + A3 = matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + assert list(A3) == list(range(1, 10)) + A3[1,1] = 0 + assert (1, 1) not in A3._data + A4 = matrix([[1, 2, 3], [4, 5, 6]]) + A5 = matrix([[6, -1], [3, 2], [0, -3]]) + assert A4 * A5 == matrix([[12, -6], [39, -12]]) + assert A1 * A3 == A3 * A1 == A3 + pytest.raises(ValueError, lambda: A2*A2) + l = [[10, 20, 30], [40, 0, 60], [70, 80, 90]] + A6 = matrix(l) + assert A6.tolist() == l + assert A6 == eval(repr(A6)) + A6 = fp.matrix(A6) + assert A6 == eval(repr(A6)) + assert A6*1j == eval(repr(A6*1j)) + assert A3 * 10 == 10 * A3 == A6 + assert A2.rows == 3 + assert A2.cols == 2 + A3.rows = 2 + A3.cols = 2 + assert len(A3._data) == 3 + assert A4 + A4 == 2*A4 + pytest.raises(ValueError, lambda: A4 + A2) + assert sum(A1 - A1) == 0 + A7 = matrix([[1, 2], [3, 4], [5, 6], [7, 8]]) + x = matrix([10, -10]) + assert A7*x == matrix([-10, -10, -10, -10]) + A8 = ones(5) + assert sum((A8 + 1) - (2 - zeros(5))) == 0 + assert (1 + ones(4)) / 2 - 1 == zeros(4) + assert eye(3)**10 == eye(3) + pytest.raises(ValueError, lambda: A7**2) + A9 = randmatrix(3) + A10 = matrix(A9) + A9[0,0] = -100 + assert A9 != A10 + assert nstr(A9) + assert A9 != None # issue 283 + pytest.raises(IndexError, lambda: zeros(1,1)[:, 1]) # issue 318 + pytest.raises(IndexError, lambda: zeros(1,1)[1, :]) + + A10 = matrix([[1,2], [3,4], [5,6]]) + assert A10[0, -1] == 2 + assert A10[-1, -1] == 6 + assert A10[1, -2] == 3 + assert A10[-3, -2] == 1 + + A10[0, -1] = 3 + assert A10[0, -1] == 3 + + A10[-1, -1] = 4 + assert A10[-1, -1] == 4 + + A10[1, -2] = 5 + assert A10[1, -2] == 5 + + A10[-3, -2] = 1 + assert A10[-3, -2] == 1 + + pytest.raises(ValueError, lambda: matrix(-2)) + pytest.raises(ValueError, lambda: matrix(2, -3)) + +def test_matmul(): + """ + Test the PEP465 "@" matrix multiplication syntax. + """ + A4 = matrix([[1, 2, 3], [4, 5, 6]]) + A5 = matrix([[6, -1], [3, 2], [0, -3]]) + assert A4 @ A5 == A4 * A5 + +def test_matrix_slices(): + A = matrix([ [1, 2, 3], + [4, 5 ,6], + [7, 8 ,9]]) + V = matrix([1,2,3,4,5]) + + # Get slice + assert A[:,:] == A + assert A[:,1] == matrix([[2],[5],[8]]) + assert A[2,:] == matrix([[7, 8 ,9]]) + assert A[1:3,1:3] == matrix([[5,6],[8,9]]) + assert A[0:2,0:2] == matrix([[1,2],[4,5]]) # issue 267 + assert A[:2,:2] == matrix([[1,2],[4,5]]) + assert V[2:4] == matrix([3,4]) + assert A[-1, :] == A[2, :] + assert A[:, -1] == A[:, 2] + + pytest.raises(IndexError, lambda: A[-4, 0]) + pytest.raises(IndexError, lambda: A[0, -4]) + pytest.raises(IndexError, lambda: A[:,1:6]) + + # Assign slice with matrix + A1 = matrix(3) + A1[:,:] = A + assert A1[:,:] == matrix([[1, 2, 3], + [4, 5 ,6], + [7, 8 ,9]]) + A1[0,:] = matrix([[10, 11, 12]]) + assert A1 == matrix([ [10, 11, 12], + [4, 5 ,6], + [7, 8 ,9]]) + A1[:,2] = matrix([[13], [14], [15]]) + assert A1 == matrix([ [10, 11, 13], + [4, 5 ,14], + [7, 8 ,15]]) + A1[:2,:2] = matrix([[16, 17], [18 , 19]]) + assert A1 == matrix([ [16, 17, 13], + [18, 19 ,14], + [7, 8 ,15]]) + V[1:3] = 10 + assert V == matrix([1,10,10,4,5]) + with pytest.raises(ValueError): + A1[2,:] = A[:,1] + + with pytest.raises(IndexError): + A1[2,1:20] = A[:,:] + + # Assign slice with scalar + A1[:,2] = 10 + assert A1 == matrix([ [16, 17, 10], + [18, 19 ,10], + [7, 8 ,10]]) + A1[:,:] = 40 + for x in A1: + assert x == 40 + + # test negative indexes + A2 = matrix(3) + A2[:,:] = A + + A2[-3, :] = matrix([[10, 11, 12]]) + assert A2 == matrix([[10, 11, 12], + [4, 5, 6], + [7, 8, 9]]) + A2[:,-1] = matrix([[13], [14], [15]]) + assert A2 == matrix([[10, 11, 13], + [4, 5, 14], + [7, 8, 15]]) + A2[:-1,:-1] = matrix([[16, 17], [18, 19]]) + assert A2 == matrix([[16, 17, 13], + [18, 19, 14], + [7, 8 ,15]]) + + with pytest.raises(IndexError): + A2[-123,1] = 123 + with pytest.raises(IndexError): + A2[1,-123] = 123 + +def test_matrix_power(): + A = matrix([[1, 2], [3, 4]]) + assert A**2 == A*A + assert A**3 == A*A*A + assert A**-1 == inverse(A) + assert A**-2 == inverse(A*A) + +def test_matrix_transform(): + A = matrix([[1, 2], [3, 4], [5, 6]]) + assert A.T == A.transpose() == matrix([[1, 3, 5], [2, 4, 6]]) + swap_row(A, 1, 2) + assert A == matrix([[1, 2], [5, 6], [3, 4]]) + l = [1, 2] + swap_row(l, 0, 1) + assert l == [2, 1] + assert extend(eye(3), [1,2,3]) == matrix([[1,0,0,1],[0,1,0,2],[0,0,1,3]]) + +def test_matrix_conjugate(): + A = matrix([[1 + j, 0], [2, j]]) + assert A.conjugate() == matrix([[mpc(1, -1), 0], [2, mpc(0, -1)]]) + assert A.transpose_conj() == A.H == matrix([[mpc(1, -1), 2], + [0, mpc(0, -1)]]) + +def test_matrix_creation(): + assert diag([1, 2, 3]) == matrix([[1, 0, 0], [0, 2, 0], [0, 0, 3]]) + A1 = ones(2, 3) + assert A1.rows == 2 and A1.cols == 3 + for a in A1: + assert a == 1 + A2 = zeros(3, 2) + assert A2.rows == 3 and A2.cols == 2 + for a in A2: + assert a == 0 + assert randmatrix(10) != randmatrix(10) + one = mpf(1) + assert hilbert(3) == matrix([[one, one/2, one/3], + [one/2, one/3, one/4], + [one/3, one/4, one/5]]) + +def test_norms(): + # matrix norms + A = matrix([[1, -2], [-3, -1], [2, 1]]) + assert mnorm(A,1) == 6 + assert mnorm(A,inf) == 4 + assert mnorm(A,'F') == sqrt(20) + # vector norms + assert norm(-3) == 3 + x = [1, -2, 7, -12] + assert norm(x, 1) == 22 + assert round(norm(x, 2), 10) == 14.0712472795 + assert round(norm(x, 10), 10) == 12.0054633727 + assert norm(x, inf) == 12 + +def test_vector(): + x = matrix([0, 1, 2, 3, 4]) + assert x == matrix([[0], [1], [2], [3], [4]]) + assert x[3] == 3 + assert len(x._data) == 4 + assert list(x) == list(range(5)) + x[0] = -10 + x[4] = 0 + assert x[0] == -10 + assert len(x) == len(x.T) == 5 + assert x.T*x == matrix([[114]]) + +def test_matrix_copy(): + A = ones(6) + B = A.copy() + C = +A + assert A == B + assert A == C + B[0,0] = 0 + assert A != B + C[0,0] = 42 + assert A != C + +def test_matrix_numpy(): + numpy = pytest.importorskip("numpy") + l = [[1, 2], [3, 4], [5, 6]] + a = numpy.array(l) + assert matrix(l) == matrix(a) + assert (numpy.array(matrix(l)) == numpy.array(matrix(l).tolist(), + dtype=object)).all() + if numpy.__version__ >= '2': + pytest.raises(ValueError, lambda: numpy.array(matrix(l), copy=False)) + +def test_interval_matrix_scalar_mult(): + """Multiplication of iv.matrix and any scalar type""" + a = mpi(-1, 1) + b = a + a * 2j + c = mpf(42) + d = c + c * 2j + e = 1.234 + f = fp.convert(e) + g = e + e * 3j + h = fp.convert(g) + M = iv.ones(1) + for x in [a, b, c, d, e, f, g, h]: + assert x * M == iv.matrix([x]) + assert M * x == iv.matrix([x]) + +@pytest.mark.xfail() +def test_interval_matrix_matrix_mult(): + """Multiplication of iv.matrix and other matrix types""" + A = ones(1) + B = fp.ones(1) + M = iv.ones(1) + for X in [A, B, M]: + assert X * M == iv.matrix(X) + assert X * M == X + assert M * X == iv.matrix(X) + assert M * X == X + +def test_matrix_conversion_to_iv(): + # Test that matrices with foreign datatypes are properly converted + for other_type_eye in [eye(3), fp.eye(3), iv.eye(3)]: + A = iv.matrix(other_type_eye) + B = iv.eye(3) + assert type(A[0,0]) == type(B[0,0]) + assert A.tolist() == B.tolist() + +def test_interval_matrix_mult_bug(): + # regression test for interval matrix multiplication: + # result must be nonzero-width and contain the exact result + x = convert('1.00000000000001') # note: this is implicitly rounded to some near mpf float value + A = matrix([[x]]) + B = iv.matrix(A) + C = iv.matrix([[x]]) + assert B == C + B = B * B + C = C * C + assert B == C + assert B[0, 0].delta > 1e-16 + assert B[0, 0].delta < 3e-16 + assert C[0, 0].delta > 1e-16 + assert C[0, 0].delta < 3e-16 + assert mp.mpf('1.00000000000001998401444325291756783368705994138804689654') in B[0, 0] + assert mp.mpf('1.00000000000001998401444325291756783368705994138804689654') in C[0, 0] + # the following caused an error before the bug was fixed + assert iv.matrix(mp.eye(2)) * (iv.ones(2) + mpi(1, 2)) == iv.matrix([[mpi(2, 3), mpi(2, 3)], [mpi(2, 3), mpi(2, 3)]]) diff --git a/mpmath/tests/test_module.py b/mpmath/tests/test_module.py new file mode 100644 index 0000000..f7618aa --- /dev/null +++ b/mpmath/tests/test_module.py @@ -0,0 +1,18 @@ +""" +Modules checks, in particular some hacks for Issue #657 +""" + +from pytest import raises + +import mpmath + + +def test_erroneous_module_setting(): + with raises(AttributeError): + mpmath.dps = 64 + with raises(AttributeError): + mpmath.prec = 192 + with raises(AttributeError): + mpmath.pretty = True + with raises(AttributeError): + mpmath.trap_complex = True diff --git a/mpmath/tests/test_mpmath.py b/mpmath/tests/test_mpmath.py new file mode 100644 index 0000000..532c8b0 --- /dev/null +++ b/mpmath/tests/test_mpmath.py @@ -0,0 +1,7 @@ +from mpmath import fp, iv, mp, mpc, mpf + + +def test_newstyle_classes(): + for cls in [mp, fp, iv, mpf, mpc]: + for s in cls.__class__.__mro__: + assert isinstance(s, type) diff --git a/mpmath/tests/test_ode.py b/mpmath/tests/test_ode.py new file mode 100644 index 0000000..cfa89c5 --- /dev/null +++ b/mpmath/tests/test_ode.py @@ -0,0 +1,71 @@ +#from mpmath.calculus import ODE_step_euler, ODE_step_rk4, odeint, arange +from mpmath import cos, mpf, odefun, sin, sinc + + +''' +solvers = [ODE_step_euler, ODE_step_rk4] + +def test_ode1(): + """ + Let's solve: + + x'' + w**2 * x = 0 + + i.e. x1 = x, x2 = x1': + + x1' = x2 + x2' = -x1 + """ + def derivs((x1, x2), t): + return x2, -x1 + + for solver in solvers: + t = arange(0, 3.1415926, 0.005) + sol = odeint(derivs, (0., 1.), t, solver) + x1 = [a[0] for a in sol] + x2 = [a[1] for a in sol] + # the result is x1 = sin(t), x2 = cos(t) + # let's just check the end points for t = pi + assert abs(x1[-1]) < 1e-2 + assert abs(x2[-1] - (-1)) < 1e-2 + +def test_ode2(): + """ + Let's solve: + + x' - x = 0 + + i.e. x = exp(x) + + """ + def derivs((x), t): + return x + + for solver in solvers: + t = arange(0, 1, 1e-3) + sol = odeint(derivs, (1.,), t, solver) + x = [a[0] for a in sol] + # the result is x = exp(t) + # let's just check the end point for t = 1, i.e. x = e + assert abs(x[-1] - 2.718281828) < 1e-2 +''' + +def test_odefun_rational(): + # A rational function + f = lambda t: 1/(1+mpf(t)**2) + g = odefun(lambda x, y: [-2*x*y[0]**2], 0, [f(0)]) + assert f(2).ae(g(2)[0]) + +def test_odefun_sinc_large(): + # Sinc function; test for large x + f = sinc + g = odefun(lambda x, y: [(cos(x)-y[0])/x], 1, [f(1)], tol=0.01, degree=5) + assert abs(f(100) - g(100)[0])/f(100) < 0.01 + +def test_odefun_harmonic(): + # Harmonic oscillator + f = odefun(lambda x, y: [-y[1], y[0]], 0, [1, 0]) + for x in [0, 1, 2.5, 8, 3.7]: # we go back to 3.7 to check caching + c, s = f(x) + assert c.ae(cos(x)) + assert s.ae(sin(x)) diff --git a/mpmath/tests/test_pickle.py b/mpmath/tests/test_pickle.py new file mode 100644 index 0000000..1cd500a --- /dev/null +++ b/mpmath/tests/test_pickle.py @@ -0,0 +1,12 @@ +import pickle + +import pytest + +from mpmath import matrix, mpc, mpf, mpi, sin + + +@pytest.mark.parametrize('protocol', range(pickle.HIGHEST_PROTOCOL + 1)) +@pytest.mark.parametrize('obj', [mpf('0.5'), mpc('0.5','0.2'), mpi(10, 30), + matrix([1, sin(1)]), matrix([[1, 2], [3, 4]])]) +def test_pickle(obj, protocol): + assert obj == pickle.loads(pickle.dumps(obj, protocol)) diff --git a/mpmath/tests/test_power.py b/mpmath/tests/test_power.py new file mode 100644 index 0000000..deef399 --- /dev/null +++ b/mpmath/tests/test_power.py @@ -0,0 +1,155 @@ +import random + +from mpmath import make_mpf, mp, mpf +from mpmath.libmp import (from_int, mpf_pow, mpf_pow_int, round_ceiling, + round_down, round_floor, round_up, to_int) + + +def test_fractional_pow(): + assert mpf(16) ** 2.5 == 1024 + assert mpf(64) ** 0.5 == 8 + assert mpf(64) ** -0.5 == 0.125 + assert mpf(16) ** -2.5 == 0.0009765625 + assert (mpf(10) ** 0.5).ae(3.1622776601683791) + assert (mpf(10) ** 2.5).ae(316.2277660168379) + assert (mpf(10) ** -0.5).ae(0.31622776601683794) + assert (mpf(10) ** -2.5).ae(0.0031622776601683794) + assert (mpf(10) ** 0.3).ae(1.9952623149688795) + assert (mpf(10) ** -0.3).ae(0.50118723362727224) + +def test_pow_integer_direction(): + """ + Test that inexact integer powers are rounded in the right + direction. + """ + random.seed(1234) + for prec in [10, 53, 200]: + for i in range(50): + a = random.randint(1<<(prec-1), 1< ab + + +def test_pow_epsilon_rounding(): + """ + Stress test directed rounding for powers with integer exponents. + Basically, we look at the following cases: + + >>> 1.0001 ** -5 + 0.999500149965007 + >>> 0.9999 ** -5 + 1.000500150035007 + >>> (-1.0001) ** -5 + -0.999500149965007 + >>> (-0.9999) ** -5 + -1.000500150035007 + + >>> 1.0001 ** -6 + 0.9994002099440127 + >>> 0.9999 ** -6 + 1.0006002100560125 + >>> (-1.0001) ** -6 + 0.9994002099440127 + >>> (-0.9999) ** -6 + 1.0006002100560125 + + etc. + + We run the tests with values a very small epsilon away from 1: + small enough that the result is indistinguishable from 1 when + rounded to nearest at the output precision. We check that the + result is not erroneously rounded to 1 in cases where the + rounding should be done strictly away from 1. + """ + + def powr(x, n, r): + return make_mpf(mpf_pow_int(x._mpf_, n, mp.prec, r)) + + for (inprec, outprec) in [(100, 20), (5000, 3000)]: + + mp.prec = inprec + + pos10001 = mpf(1) + mpf(2)**(-inprec+5) + pos09999 = mpf(1) - mpf(2)**(-inprec+5) + neg10001 = -pos10001 + neg09999 = -pos09999 + + mp.prec = outprec + r = round_up + assert powr(pos10001, 5, r) > 1 + assert powr(pos09999, 5, r) == 1 + assert powr(neg10001, 5, r) < -1 + assert powr(neg09999, 5, r) == -1 + assert powr(pos10001, 6, r) > 1 + assert powr(pos09999, 6, r) == 1 + assert powr(neg10001, 6, r) > 1 + assert powr(neg09999, 6, r) == 1 + + assert powr(pos10001, -5, r) == 1 + assert powr(pos09999, -5, r) > 1 + assert powr(neg10001, -5, r) == -1 + assert powr(neg09999, -5, r) < -1 + assert powr(pos10001, -6, r) == 1 + assert powr(pos09999, -6, r) > 1 + assert powr(neg10001, -6, r) == 1 + assert powr(neg09999, -6, r) > 1 + + r = round_down + assert powr(pos10001, 5, r) == 1 + assert powr(pos09999, 5, r) < 1 + assert powr(neg10001, 5, r) == -1 + assert powr(neg09999, 5, r) > -1 + assert powr(pos10001, 6, r) == 1 + assert powr(pos09999, 6, r) < 1 + assert powr(neg10001, 6, r) == 1 + assert powr(neg09999, 6, r) < 1 + + assert powr(pos10001, -5, r) < 1 + assert powr(pos09999, -5, r) == 1 + assert powr(neg10001, -5, r) > -1 + assert powr(neg09999, -5, r) == -1 + assert powr(pos10001, -6, r) < 1 + assert powr(pos09999, -6, r) == 1 + assert powr(neg10001, -6, r) < 1 + assert powr(neg09999, -6, r) == 1 + + r = round_ceiling + assert powr(pos10001, 5, r) > 1 + assert powr(pos09999, 5, r) == 1 + assert powr(neg10001, 5, r) == -1 + assert powr(neg09999, 5, r) > -1 + assert powr(pos10001, 6, r) > 1 + assert powr(pos09999, 6, r) == 1 + assert powr(neg10001, 6, r) > 1 + assert powr(neg09999, 6, r) == 1 + + assert powr(pos10001, -5, r) == 1 + assert powr(pos09999, -5, r) > 1 + assert powr(neg10001, -5, r) > -1 + assert powr(neg09999, -5, r) == -1 + assert powr(pos10001, -6, r) == 1 + assert powr(pos09999, -6, r) > 1 + assert powr(neg10001, -6, r) == 1 + assert powr(neg09999, -6, r) > 1 + + r = round_floor + assert powr(pos10001, 5, r) == 1 + assert powr(pos09999, 5, r) < 1 + assert powr(neg10001, 5, r) < -1 + assert powr(neg09999, 5, r) == -1 + assert powr(pos10001, 6, r) == 1 + assert powr(pos09999, 6, r) < 1 + assert powr(neg10001, 6, r) == 1 + assert powr(neg09999, 6, r) < 1 + + assert powr(pos10001, -5, r) < 1 + assert powr(pos09999, -5, r) == 1 + assert powr(neg10001, -5, r) == -1 + assert powr(neg09999, -5, r) < -1 + assert powr(pos10001, -6, r) < 1 + assert powr(pos09999, -6, r) == 1 + assert powr(neg10001, -6, r) < 1 + assert powr(neg09999, -6, r) == 1 diff --git a/mpmath/tests/test_quad.py b/mpmath/tests/test_quad.py new file mode 100644 index 0000000..7989c00 --- /dev/null +++ b/mpmath/tests/test_quad.py @@ -0,0 +1,98 @@ +import pytest + +from mpmath import (airyai, airyaizero, atan, cos, cosh, e, euler, exp, inf, j, + log, mp, pi, quad, quadgl, quadosc, quadts, sign, sin, + sinh, sqrt, tan) + + +def ae(a, b): + return abs(a-b) < 10**(-mp.dps+5) + +def test_basic_integrals(): + for prec in [15, 30, 100]: + mp.dps = prec + assert ae(quadts(lambda x: x**3 - 3*x**2, [-2, 4]), -12) + assert ae(quadgl(lambda x: x**3 - 3*x**2, [-2, 4]), -12) + assert ae(quadts(sin, [0, pi]), 2) + assert ae(quadts(sin, [0, 2*pi]), 0) + assert ae(quadts(exp, [-inf, -1]), 1/e) + assert ae(quadts(lambda x: exp(-x), [0, inf]), 1) + assert ae(quadts(lambda x: exp(-x*x), [-inf, inf]), sqrt(pi)) + assert ae(quadts(lambda x: 1/(1+x*x), [-1, 1]), pi/2) + assert ae(quadts(lambda x: 1/(1+x*x), [-inf, inf]), pi) + assert ae(quadts(lambda x: 2*sqrt(1-x*x), [-1, 1]), pi) + +def test_multiple_intervals(): + y,err = quad(lambda x: sign(x), [-0.5, 0.9, 1], maxdegree=2, error=True) + assert abs(y-0.5) < 2*err + +def test_quad_symmetry(): + assert quadts(sin, [-1, 1]) == 0 + assert quadgl(sin, [-1, 1]) == 0 + +def test_quad_infinite_mirror(): + # Check mirrored infinite interval + assert ae(quad(lambda x: exp(-x*x), [inf,-inf]), -sqrt(pi)) + assert ae(quad(lambda x: exp(x), [0,-inf]), -1) + +def test_quadgl_linear(): + assert quadgl(lambda x: x, [0, 1], maxdegree=1).ae(0.5) + +def test_complex_integration(): + assert quadts(lambda x: x, [0, 1+j]).ae(j) + +def test_quadosc(): + assert quadosc(lambda x: sin(x)/x, [0, inf], period=2*pi).ae(pi/2) + # issue #652 + assert ae(quadosc(airyai, [-inf, 0], zeros=lambda n: -airyaizero(-n)), 2/3) + +# Double integrals +def test_double_trivial(): + assert ae(quadts(lambda x, y: x, [0, 1], [0, 1]), 0.5) + assert ae(quadts(lambda x, y: x, [-1, 1], [-1, 1]), 0.0) + +def test_double_1(): + assert ae(quadts(lambda x, y: cos(x+y/2), [-pi/2, pi/2], [0, pi]), 4) + +def test_double_2(): + assert ae(quadts(lambda x, y: (x-1)/((1-x*y)*log(x*y)), [0, 1], [0, 1]), euler) + +def test_double_3(): + assert ae(quadts(lambda x, y: 1/sqrt(1+x*x+y*y), [-1, 1], [-1, 1]), 4*log(2+sqrt(3))-2*pi/3) + +def test_double_4(): + assert ae(quadts(lambda x, y: 1/(1-x*x * y*y), [0, 1], [0, 1]), pi**2 / 8) + +def test_double_5(): + assert ae(quadts(lambda x, y: 1/(1-x*y), [0, 1], [0, 1]), pi**2 / 6) + +def test_double_6(): + assert ae(quadts(lambda x, y: exp(-(x+y)), [0, inf], [0, inf]), 1) + +def test_double_7(): + assert ae(quadts(lambda x, y: exp(-x*x-y*y), [-inf, inf], [-inf, inf]), pi) + + +# Test integrals from "Experimentation in Mathematics" by Borwein, +# Bailey & Girgensohn +def test_expmath_integrals(): + for prec in [15, 30, 50]: + mp.dps = prec + assert ae(quadts(lambda x: x/sinh(x), [0, inf]), pi**2 / 4) + assert ae(quadts(lambda x: log(x)**2 / (1+x**2), [0, inf]), pi**3 / 8) + assert ae(quadts(lambda x: (1+x**2)/(1+x**4), [0, inf]), pi/sqrt(2)) + assert ae(quadts(lambda x: log(x)/cosh(x)**2, [0, inf]), log(pi)-2*log(2)-euler) + assert ae(quadts(lambda x: log(1+x**3)/(1-x+x**2), [0, inf]), 2*pi*log(3)/sqrt(3)) + assert ae(quadts(lambda x: log(x)**2 / (x**2+x+1), [0, 1]), 8*pi**3 / (81*sqrt(3))) + assert ae(quadts(lambda x: log(cos(x))**2, [0, pi/2]), pi/2 * (log(2)**2+pi**2/12)) + assert ae(quadts(lambda x: x**2 / sin(x)**2, [0, pi/2]), pi*log(2)) + assert ae(quadts(lambda x: x**2/sqrt(exp(x)-1), [0, inf]), 4*pi*(log(2)**2 + pi**2/12)) + assert ae(quadts(lambda x: x*exp(-x)*sqrt(1-exp(-2*x)), [0, inf]), pi*(1+2*log(2))/8) + +# Do not reach full accuracy +@pytest.mark.xfail +def test_expmath_fail(): + assert ae(quadts(lambda x: sqrt(tan(x)), [0, pi/2]), pi*sqrt(2)/2) + assert ae(quadts(lambda x: atan(x)/(x*sqrt(1-x**2)), [0, 1]), pi*log(1+sqrt(2))/2) + assert ae(quadts(lambda x: log(1+x**2)/x**2, [0, 1]), pi/2-log(2)) + assert ae(quadts(lambda x: x**2/((1+x**4)*sqrt(1-x**4)), [0, 1]), pi/8) diff --git a/mpmath/tests/test_rootfinding.py b/mpmath/tests/test_rootfinding.py new file mode 100644 index 0000000..9d1b062 --- /dev/null +++ b/mpmath/tests/test_rootfinding.py @@ -0,0 +1,176 @@ +import pytest + +from mpmath import (cos, eps, findroot, fp, inf, iv, jacobian, matrix, mnorm, + mp, mpc, mpf, multiplicity, norm, pi, polyval, sin, sqrt, + workprec) +from mpmath.calculus.optimization import (Anderson, ANewton, Bisection, + Illinois, MDNewton, MNewton, Muller, + Newton, Pegasus, Ridder, Secant, ModAB, Brent) + + +def test_findroot(): + # old tests, assuming secant + assert findroot(lambda x: 4*x-3, mpf(5)).ae(0.75) + assert findroot(sin, mpf(3)).ae(pi) + assert findroot(sin, (mpf(3), mpf(3.14))).ae(pi) + assert findroot(lambda x: x*x+1, mpc(2+2j)).ae(1j) + # test all solvers with 1 starting point + f = lambda x: cos(x) + for solver in [Newton, Secant, MNewton, Muller, ANewton]: + x = findroot(f, 2., solver=solver) + assert abs(f(x)) < eps + # test all solvers with interval of 2 points + for solver in [Secant, Muller, Bisection, Illinois, Pegasus, Anderson, + Ridder, ModAB, Brent]: + x = findroot(f, (1., 2.), solver=solver) + assert abs(f(x)) < eps + # test types + f = lambda x: (x - 2)**2 + + assert isinstance(findroot(f, 1, tol=1e-10), mpf) + assert isinstance(iv.findroot(f, 1., tol=1e-10), iv.mpf) + assert isinstance(fp.findroot(f, 1, tol=1e-10), float) + assert isinstance(fp.findroot(f, 1+0j, tol=1e-10), complex) + + # issue 401 + with pytest.raises(ValueError): + with workprec(2): + findroot(lambda x: x**2 - 4456178*x + 60372201703370, + mpc(real='5.278e+13', imag='-5.278e+13')) + + # issue 192 + with pytest.raises(ValueError): + findroot(lambda x: -1, 0) + + # issue 387 + with pytest.raises(ValueError): + findroot(lambda p: (1 - p)**30 - 1, 0.9) + +def test_bisection(): + # issue 273 + assert findroot(lambda x: x**2-1,(0,2),solver='bisect') == 1 + + with pytest.raises(ValueError): + findroot(lambda x: x**2-1, (4, 2), solver='bisect') == 1 + + # issue 285 + mp.dps = 240 + sol = -mp.ceil(mp.log(abs(findroot(lambda x: mp.sign(x - 3), (1, 4), + solver='bisect', verify=False, + tol=1e-200) - 3))/mp.log(10)) + assert sol.ae(200) + + # issue 339 + mp.dps = 15 + res = mpf('0.73908513321516064') + for dps in [100, 200, 300, 1000]: + with mp.workdps(dps): + sol = findroot(lambda x: cos(x) - x, [0, 1], solver='bisect') + assert (+sol).ae(res) + +def test_mnewton(): + f = lambda x: polyval([1, 3, 3, 1], x) + x = findroot(f, -0.9, solver='mnewton') + assert abs(f(x)) < eps + +def test_anewton(): + f = lambda x: (x - 2)**100 + x = findroot(f, 1., solver=ANewton) + assert abs(f(x)) < eps + +def test_muller(): + f = lambda x: (2 + x)**3 + 2 + x = findroot(f, 1., solver=Muller) + assert abs(f(x)) < eps + +def test_ridder(): + f = lambda x: cos(x)/x + x = findroot(f, (1, 2), solver='ridder') + assert abs(f(x)) < eps + +def test_brent(): + f = lambda x: cos(x)/x + x = findroot(f, (1, 2), solver='brent') + assert abs(f(x)) < eps + + with pytest.raises(ValueError, match="expected interval of 2 points"): + findroot(lambda x: x**2 - 1, (0,), solver='brent') + + with pytest.raises(ValueError, match="Function must have opposite signs"): + findroot(lambda x: x**2 - 1, (2, 4), solver='brent') + + assert findroot(lambda x: x, (-1, 2), solver='brent') == 0.0 + + assert findroot(lambda x: x, (-1, 1), solver='brent') == 0.0 + +def test_modAB(): + assert findroot(lambda x: x**2 - 1, (0, 2), solver='modAB') == 1 + + # test ordering + assert findroot(lambda x: x**2 - 1, (2, 0), solver='modAB') == 1 + + with pytest.raises(ValueError, match="expected interval of 2 points"): + findroot(lambda x: x**2 - 1, (0,), solver='modAB') + + with pytest.raises(ValueError, match="Function must have opposite signs"): + findroot(lambda x: x**2 - 1, (2, 4), solver='modAB') + + # test exact zero hit + assert findroot(lambda x: x, (-1, 1), solver='modAB') == 0.0 + + # test bisection to secant switch for a purely linear function + f_linear = lambda x: 2*x - 4 + assert mp.almosteq(findroot(f_linear, (0, 5), solver='modAB'), 2.0) + + f_convex = lambda x: x**10 - 1 + assert mp.almosteq(findroot(f_convex, (0.1, 2.0), solver='modAB'), 1.0) + + f_concave = lambda x: 1 - x**10 + assert mp.almosteq(findroot(f_concave, (2.0, 0.1), solver='modAB'), 1.0) + + f_cubic_inflection = lambda x: x**3 - 3*x + 3 + root = findroot(f_cubic_inflection, (-3, 2), solver='modAB') + assert abs(f_cubic_inflection(root)) < eps + + # test reset to Bisection if the interval width exceeds the threshold + f_step = lambda x: mp.sin(x) if x > 1 else x - 1 + assert mp.almosteq(findroot(f_step, (0.4, 3.0), solver='modAB'), 1.0) + +def test_multiplicity(): + for i in range(1, 5): + assert multiplicity(lambda x: (x - 1)**i, 1) == i + assert multiplicity(lambda x: x**2, 1) == 0 + +def test_multidimensional(capsys): + def f(*x): + return [3*x[0]**2-2*x[1]**2-1, x[0]**2-2*x[0]+x[1]**2+2*x[1]-8] + assert mnorm(jacobian(f, (1,-2)) - matrix([[6,8],[0,-2]]),1) < 1.e-7 + for x, error in MDNewton(mp, f, (1,-2), verbose=0, + norm=lambda x: norm(x, inf)): + pass + assert norm(f(*x), 2) < 1e-14 + for x, error in MDNewton(mp, f, (1,-2), verbose=1, + norm=lambda x: norm(x, inf)): + pass + assert norm(f(*x), 2) < 1e-14 + captured = capsys.readouterr() + assert captured.out.find("canceled, won't get more exact") >= 0 + # The Chinese mathematician Zhu Shijie was the very first to solve this + # nonlinear system 700 years ago + f1 = lambda x, y: -x + 2*y + f2 = lambda x, y: (x**2 + x*(y**2 - 2) - 4*y) / (x + 4) + f3 = lambda x, y: sqrt(x**2 + y**2) + def f(x, y): + f1x = f1(x, y) + return (f2(x, y) - f1x, f3(x, y) - f1x) + x = findroot(f, (10, 10)) + assert [round(i) for i in x] == [3, 4] + +def test_trivial(): + assert findroot(lambda x: 0, 1) == 1 + assert findroot(lambda x: x, 0) == 0 + #assert findroot(lambda x, y: x + y, (1, -1)) == (1, -1) + +def test_issue_869(): + f = [lambda x: sqrt(x) + 1] + pytest.raises(mp.ComplexResult, lambda: findroot(f, [-1])) diff --git a/mpmath/tests/test_special.py b/mpmath/tests/test_special.py new file mode 100644 index 0000000..c66d181 --- /dev/null +++ b/mpmath/tests/test_special.py @@ -0,0 +1,115 @@ +from mpmath import atan, exp, inf, isinf, isnan, log, mpf, nan, pi, sin, sqrt + + +def test_special(): + assert inf == inf + assert inf != -inf + assert -inf == -inf + assert inf != nan + assert nan != nan + assert isnan(nan) + assert --inf == inf + assert abs(inf) == inf + assert abs(-inf) == inf + assert abs(nan) != abs(nan) + + assert isnan(inf - inf) + assert isnan(inf + (-inf)) + assert isnan(-inf - (-inf)) + + assert isnan(inf + nan) + assert isnan(-inf + nan) + + assert mpf(2) + inf == inf + assert 2 + inf == inf + assert mpf(2) - inf == -inf + assert 2 - inf == -inf + + assert inf > 3 + assert 3 < inf + assert 3 > -inf + assert -inf < 3 + assert inf > mpf(3) + assert mpf(3) < inf + assert mpf(3) > -inf + assert -inf < mpf(3) + + assert not (nan < 3) + assert not (nan > 3) + + assert isnan(inf * 0) + assert isnan(-inf * 0) + assert inf * 3 == inf + assert inf * -3 == -inf + assert -inf * 3 == -inf + assert -inf * -3 == inf + assert inf * inf == inf + assert -inf * -inf == inf + + assert isnan(nan / 3) + assert inf / -3 == -inf + assert inf / 3 == inf + assert 3 / inf == 0 + assert -3 / inf == 0 + assert 0 / inf == 0 + assert isnan(inf / inf) + assert isnan(inf / -inf) + assert isnan(inf / nan) + + assert mpf('inf') == mpf('+inf') == inf + assert mpf('-inf') == -inf + assert isnan(mpf('nan')) + + assert isinf(inf) + assert isinf(-inf) + assert not isinf(mpf(0)) + assert not isinf(nan) + +def test_special_powers(): + assert inf**3 == inf + assert inf**0 == 1 + assert inf**-3 == 0 + assert (-inf)**2 == inf + assert (-inf)**3 == -inf + assert (-inf)**0 == 1 + assert (-inf)**-2 == 0 + assert (-inf)**-3 == 0 + assert isnan(nan**5) + assert nan**0 == 1 + assert 1**inf == 1 + +def test_functions_special(): + assert exp(inf) == inf + assert exp(-inf) == 0 + assert isnan(exp(nan)) + assert log(inf) == inf + assert isnan(log(nan)) + assert isnan(sin(inf)) + assert isnan(sin(nan)) + assert atan(inf).ae(pi/2) + assert atan(-inf).ae(-pi/2) + assert isnan(sqrt(nan)) + assert sqrt(inf) == inf + +def test_convert_special(): + float_inf = 1e300 * 1e300 + float_ninf = -float_inf + float_nan = float_inf/float_ninf + assert mpf(3) * float_inf == inf + assert mpf(3) * float_ninf == -inf + assert isnan(mpf(3) * float_nan) + assert not (mpf(3) < float_nan) + assert not (mpf(3) > float_nan) + assert not (mpf(3) <= float_nan) + assert not (mpf(3) >= float_nan) + assert float(mpf('1e1000')) == float_inf + assert float(mpf('-1e1000')) == float_ninf + assert float(mpf('1e100000000000000000')) == float_inf + assert float(mpf('-1e100000000000000000')) == float_ninf + assert float(mpf('1e-100000000000000000')) == 0.0 + +def test_div_bug(): + assert isnan(nan/1) + assert isnan(nan/2) + assert inf/2 == inf + assert (-inf)/2 == -inf diff --git a/mpmath/tests/test_str.py b/mpmath/tests/test_str.py new file mode 100644 index 0000000..86ed99b --- /dev/null +++ b/mpmath/tests/test_str.py @@ -0,0 +1,52 @@ +from mpmath import inf, matrix, mpc, nstr + + +A1 = matrix([]) +A2 = matrix([[]]) +A3 = matrix(2) +A4 = matrix([1, 2, 3]) + + +def test_nstr(): + m = matrix([[0.75, 0.190940654, -0.0299195971], + [0.190940654, 0.65625, 0.205663228], + [-0.0299195971, 0.205663228, 0.64453125e-20]]) + assert nstr(m, 4, min_fixed=-inf) == \ + '''[ 0.75 0.1909 -0.02992] +[ 0.1909 0.6562 0.2057] +[-0.02992 0.2057 0.000000000000000000006445]''' + assert nstr(m, 4) == \ + '''[ 0.75 0.1909 -0.02992] +[ 0.1909 0.6562 0.2057] +[-0.02992 0.2057 6.445e-21]''' + # Check that kwargs works properly for mpc + assert nstr(mpc(1.23e-4+4.56e-4j)) == '(0.000123 + 0.000456j)' + assert nstr(mpc(1.23e-4+4.56e-4j), min_fixed=-4) == '(1.23e-4 + 4.56e-4j)' + +def test_matrix_repr(): + assert repr(A1) == \ + '''matrix( +[])''' + assert repr(A2) == \ + '''matrix( +[[]])''' + assert repr(A3) == \ + '''matrix( +[['0.0', '0.0'], + ['0.0', '0.0']])''' + assert repr(A4) == \ + '''matrix( +[['1.0'], + ['2.0'], + ['3.0']])''' + +def test_matrix_str(): + assert str(A1) == '' + assert str(A2) == '[]' + assert str(A3) == \ + '''[0.0 0.0] +[0.0 0.0]''' + assert str(A4) == \ +'''[1.0] +[2.0] +[3.0]''' diff --git a/mpmath/tests/test_summation.py b/mpmath/tests/test_summation.py new file mode 100644 index 0000000..54d017c --- /dev/null +++ b/mpmath/tests/test_summation.py @@ -0,0 +1,50 @@ +from mpmath import (e, exp, fac, factorial, fp, fprod, fsum, inf, isnan, iv, j, + log, mpi, nprod, nsum, pi, sumem) + + +def test_sumem(): + assert sumem(lambda k: 1/k**2.5, [50, 100]).ae(0.0012524505324784962) + assert sumem(lambda k: k**4 + 3*k + 1, [10, 100]).ae(2050333103) + +def test_nsum(): + assert nsum(lambda x: x**2, [1, 3]) == 14 + assert nsum(lambda k: 1/factorial(k), [0, inf]).ae(e) + assert nsum(lambda k: (-1)**(k+1) / k, [1, inf]).ae(log(2)) + assert nsum(lambda k: (-1)**(k+1) / k**2, [1, inf]).ae(pi**2 / 12) + assert nsum(lambda k: (-1)**k / log(k), [2, inf]).ae(0.9242998972229388) + assert nsum(lambda k: 1/k**2, [1, inf]).ae(pi**2 / 6) + assert nsum(lambda k: 2**k/fac(k), [0, inf]).ae(exp(2)) + assert nsum(lambda k: 1/k**2, [4, inf], method='e').ae(0.2838229557371153) + assert abs(fp.nsum(lambda k: 1/k**4, [1, fp.inf]) - 1.082323233711138) < 1e-5 + assert abs(fp.nsum(lambda k: 1/k**4, [1, fp.inf], method='e') - 1.082323233711138) < 1e-4 + +def test_nprod(): + assert nprod(lambda k: exp(1/k**2), [1,inf], method='r').ae(exp(pi**2/6)) + assert nprod(lambda x: x**2, [1, 3]) == 36 + +def test_fsum(): + assert fsum([]) == 0 + assert fsum([-4]) == -4 + assert fsum([2,3]) == 5 + assert fsum([1e-100,1]) == 1 + assert fsum([1,1e-100]) == 1 + assert fsum([1e100,1]) == 1e100 + assert fsum([1,1e100]) == 1e100 + assert fsum([1e-100,0]) == 1e-100 + assert fsum([1e-100,1e100,1e-100]) == 1e100 + assert fsum([2,1+1j,1]) == 4+1j + assert fsum([2,inf,3]) == inf + assert fsum([2,-1], absolute=1) == 3 + assert fsum([2,-1], squared=1) == 5 + assert fsum([1,1+j], squared=1) == 1+2j + assert fsum([1,3+4j], absolute=1) == 6 + assert fsum([1,2+3j], absolute=1, squared=1) == 14 + assert isnan(fsum([inf,-inf])) + assert fsum([inf,-inf], absolute=1) == inf + assert fsum([inf,-inf], squared=1) == inf + assert fsum([inf,-inf], absolute=1, squared=1) == inf + assert iv.fsum([1,mpi(2,3)]) == mpi(3,4) + +def test_fprod(): + assert fprod([]) == 1 + assert fprod([2,3]) == 6 diff --git a/mpmath/tests/test_torture.py b/mpmath/tests/test_torture.py new file mode 100644 index 0000000..6f87370 --- /dev/null +++ b/mpmath/tests/test_torture.py @@ -0,0 +1,181 @@ +""" +Torture tests for asymptotics and high precision evaluation of +special functions. + +(Other torture tests may also be placed here.) + +Running this file (gmpy recommended!) takes several CPU minutes. +The multiprocessing module is used automatically to run tests +in parallel if many cores are available. (A single test may take between +a second and several minutes; possibly more.) + +The idea: + +* We evaluate functions at positive, negative, imaginary, 45- and 135-degree + complex values with magnitudes between 10^-20 to 10^20, at precisions between + 5 and 150 digits (we can go even higher for fast functions). + +* Comparing the result from two different precision levels provides + a strong consistency check (particularly for functions that use + different algorithms at different precision levels). + +* That the computation finishes at all (without failure), within reasonable + time, provides a check that evaluation works at all: that the code runs, + that it doesn't get stuck in an infinite loop, and that it doesn't use + some extremely slowly algorithm where it could use a faster one. + +TODO: + +* Speed up those functions that take long to finish! +* Generalize to test more cases; more options. +* Implement a timeout mechanism. +* Some functions are notably absent, including the following: + * inverse trigonometric functions (some become inaccurate for complex arguments) + * ci, si (not implemented properly for large complex arguments) + * zeta functions (need to modify test not to try too large imaginary values) + * and others... + +""" + +import pytest + +from mpmath import (agm, airyai, airybi, apery, barnesg, bernfrac, bernoulli, + besseli, besselj, besselk, bessely, catalan, cbrt, chi, ci, + cos, cosh, coulombf, coulombg, e, e1, ei, ellipe, ellipk, + erf, erfc, erfi, euler, exp, expint, expm1, gamma, + gammainc, glaisher, hermite, hyp0f1, hyp1f1, hyp1f2, + hyp2f0, hyp2f1, hyp2f2, hyp2f3, hyperu, j, jtheta, + khinchin, lambertw, legendre, legenp, legenq, li, ln, ln2, + ln10, loggamma, mertens, mp, mpf, phi, pi, polylog, power, + root, shi, si, sin, sinh, sqrt, stieltjes, tan, tanh, + twinprime, workprec) + + +a1, a2, a3, a4, a5 = 1.5, -2.25, 3.125, 4, 2 + +@pytest.mark.parametrize('f,maxdps,huge_range', + [(lambda z: +pi, 10000, False), + (lambda z: +e, 10000, False), + (lambda z: +ln2, 10000, False), + (lambda z: +ln10, 10000, False), + (lambda z: +phi, 10000, False), + (lambda z: +catalan, 5000, False), + (lambda z: +euler, 5000, False), + (lambda z: +glaisher, 1000, False), + (lambda z: +khinchin, 1000, False), + (lambda z: +twinprime, 150, False), + (lambda z: stieltjes(2), 150, False), + (lambda z: +mertens, 150, False), + (lambda z: +apery, 5000, False), + (sqrt, 10000, True), + (cbrt, 5000, True), + (lambda z: root(z,4), 5000, True), + (lambda z: root(z,-5), 5000, True), + (exp, 5000, True), + (expm1, 1500, False), + (ln, 5000, True), + (cosh, 5000, False), + (sinh, 5000, False), + (tanh, 1500, False), + (sin, 5000, True), + (cos, 5000, True), + (tan, 1500, False), + (agm, 1500, True), + (ellipk, 1500, False), + (ellipe, 1500, False), + (lambertw, 150, True), + (lambda z: lambertw(z,-1), 150, False), + (lambda z: lambertw(z,1), 150, False), + (lambda z: lambertw(z,4), 150, False), + (gamma, 150, False), + (loggamma, 150, False), # True ? + (ei, 150, False), + (e1, 150, False), + (li, 150, True), + (ci, 150, False), + (si, 150, False), + (chi, 150, False), + (shi, 150, False), + (erf, 150, False), + (erfc, 150, False), + (erfi, 150, False), + (lambda z: besselj(2, z), 150, False), + (lambda z: bessely(2, z), 150, False), + (lambda z: besseli(2, z), 150, False), + (lambda z: besselk(2, z), 150, False), + (lambda z: besselj(-2.25, z), 150, False), + (lambda z: bessely(-2.25, z), 150, False), + (lambda z: besseli(-2.25, z), 150, False), + (lambda z: besselk(-2.25, z), 150, False), + (airyai, 150, False), + (airybi, 150, False), + (lambda z: hyp0f1(a1, z), 150, False), + (lambda z: hyp1f1(a1, a2, z), 150, False), + (lambda z: hyp1f2(a1, a2, a3, z), 150, False), + (lambda z: hyp2f0(a1, a2, z), 150, False), + (lambda z: hyperu(a1, a2, z), 150, False), + (lambda z: hyp2f1(a1, a2, a3, z), 150, False), + (lambda z: hyp2f2(a1, a2, a3, a4, z), 150, False), + (lambda z: hyp2f3(a1, a2, a3, a4, a5, z), 150, False), + (lambda z: coulombf(a1, a2, z), 150, False), + (lambda z: coulombg(a1, a2, z), 150, False), + (lambda z: polylog(2,z), 150, False), + (lambda z: polylog(3,z), 150, False), + (lambda z: polylog(-2,z), 150, False), + (lambda z: expint(4, z), 150, False), + (lambda z: expint(-4, z), 150, False), + (lambda z: expint(2.25, z), 150, False), + (lambda z: gammainc(2.5, z, 5), 150, False), + (lambda z: gammainc(2.5, 5, z), 150, False), + (lambda z: hermite(3, z), 150, False), + (lambda z: hermite(2.5, z), 150, False), + (lambda z: legendre(3, z), 150, False), + (lambda z: legendre(4, z), 150, False), + (lambda z: legendre(2.5, z), 150, False), + (lambda z: legenp(a1, a2, z), 150, False), + (lambda z: legenq(a1, a2, z), 90, False), # abnormally slow + (lambda z: jtheta(1, z, 0.5), 150, False), + (lambda z: jtheta(2, z, 0.5), 150, False), + (lambda z: jtheta(3, z, 0.5), 150, False), + (lambda z: jtheta(4, z, 0.5), 150, False), + (lambda z: jtheta(1, z, 0.5, 1), 150, False), + (lambda z: jtheta(2, z, 0.5, 1), 150, False), + (lambda z: jtheta(3, z, 0.5, 1), 150, False), + (lambda z: jtheta(4, z, 0.5, 1), 150, False), + (barnesg, 90, False)]) +def test_asymp(f, maxdps, huge_range): + dps = [5,15,25,50,90,150,500,1500,5000,10000] + dps = [p for p in dps if p <= maxdps] + def check(x,y,p,inpt): + assert abs(x-y)/abs(y) < workprec(20)(power)(10, -p+1) + exponents = list(range(-20,20)) + if huge_range: + exponents += [-1000, -100, -50, 50, 100, 1000] + for n in exponents: + mp.dps = 25 + xpos = mpf(10)**n / 1.1287 + xneg = -xpos + ximag = xpos*j + xcomplex1 = xpos*(1+j) + xcomplex2 = xpos*(-1+j) + for i in range(len(dps)): + mp.dps = dps[i] + new = f(xpos), f(xneg), f(ximag), f(xcomplex1), f(xcomplex2) + if i != 0: + p = dps[i-1] + check(prev[0], new[0], p, xpos) + check(prev[1], new[1], p, xneg) + check(prev[2], new[2], p, ximag) + check(prev[3], new[3], p, xcomplex1) + check(prev[4], new[4], p, xcomplex2) + prev = new + + +def test_bernoulli_huge(): + p, q = bernfrac(9000) + assert p % 10**10 == 9636701091 + assert q == 4091851784687571609141381951327092757255270 + mp.dps = 15 + assert str(bernoulli(10**100)) == '-2.58183325604736e+987675256497386331227838638980680030172857347883537824464410652557820800494271520411283004120790908623' + mp.dps = 50 + assert str(bernoulli(10**100)) == '-2.5818332560473632073252488656039475548106223822913e+987675256497386331227838638980680030172857347883537824464410652557820800494271520411283004120790908623' diff --git a/mpmath/tests/test_trig.py b/mpmath/tests/test_trig.py new file mode 100644 index 0000000..2d2e60f --- /dev/null +++ b/mpmath/tests/test_trig.py @@ -0,0 +1,131 @@ +from mpmath import cos, ldexp, mp, mpf, pi, sin, tan +from mpmath.libmp import (round_ceiling, round_down, round_floor, + round_nearest, round_up) + + +def test_trig_misc_hard(): + # Worst-case input for an IEEE double, from a paper by Kahan + x = ldexp(6381956970095103,797) + assert cos(x) == mpf('-4.6871659242546277e-19') + assert sin(x) == 1 + + mp.prec = 150 + a = mpf(10**50) + mp.prec = 53 + assert sin(a).ae(-0.7896724934293100827) + assert cos(a).ae(-0.6135286082336635622) + + # Check relative accuracy close to x = zero + assert sin(1e-100) == 1e-100 # when rounding to nearest + assert sin(1e-6).ae(9.999999999998333e-007, rel_eps=2e-15, abs_eps=0) + assert sin(1e-6j).ae(1.0000000000001666e-006j, rel_eps=2e-15, abs_eps=0) + assert sin(-1e-6j).ae(-1.0000000000001666e-006j, rel_eps=2e-15, abs_eps=0) + assert cos(1e-100) == 1 + assert cos(1e-6).ae(0.9999999999995) + assert cos(-1e-6j).ae(1.0000000000005) + assert tan(1e-100) == 1e-100 + assert tan(1e-6).ae(1.0000000000003335e-006, rel_eps=2e-15, abs_eps=0) + assert tan(1e-6j).ae(9.9999999999966644e-007j, rel_eps=2e-15, abs_eps=0) + assert tan(-1e-6j).ae(-9.9999999999966644e-007j, rel_eps=2e-15, abs_eps=0) + +def test_trig_near_zero(): + for r in [round_nearest, round_down, round_up, round_floor, round_ceiling]: + assert sin(0, rounding=r) == 0 + assert cos(0, rounding=r) == 1 + + a = mpf('1e-100') + b = mpf('-1e-100') + + assert sin(a, rounding=round_nearest) == a + assert sin(a, rounding=round_down) < a + assert sin(a, rounding=round_floor) < a + assert sin(a, rounding=round_up) >= a + assert sin(a, rounding=round_ceiling) >= a + assert sin(b, rounding=round_nearest) == b + assert sin(b, rounding=round_down) > b + assert sin(b, rounding=round_floor) <= b + assert sin(b, rounding=round_up) <= b + assert sin(b, rounding=round_ceiling) > b + + assert cos(a, rounding=round_nearest) == 1 + assert cos(a, rounding=round_down) < 1 + assert cos(a, rounding=round_floor) < 1 + assert cos(a, rounding=round_up) == 1 + assert cos(a, rounding=round_ceiling) == 1 + assert cos(b, rounding=round_nearest) == 1 + assert cos(b, rounding=round_down) < 1 + assert cos(b, rounding=round_floor) < 1 + assert cos(b, rounding=round_up) == 1 + assert cos(b, rounding=round_ceiling) == 1 + + +def test_trig_near_n_pi(): + a = [n*pi for n in [1, 2, 6, 11, 100, 1001, 10000, 100001]] + mp.dps = 135 + a.append(10**100 * pi) + mp.dps = 15 + + assert sin(a[0]) == mpf('1.2246467991473531772e-16') + assert sin(a[1]) == mpf('-2.4492935982947063545e-16') + assert sin(a[2]) == mpf('-7.3478807948841190634e-16') + assert sin(a[3]) == mpf('4.8998251578625894243e-15') + assert sin(a[4]) == mpf('1.9643867237284719452e-15') + assert sin(a[5]) == mpf('-8.8632615209684813458e-15') + assert sin(a[6]) == mpf('-4.8568235395684898392e-13') + assert sin(a[7]) == mpf('3.9087342299491231029e-11') + assert sin(a[8]) == mpf('-1.369235466754566993528e-36') + + r = round_nearest + assert cos(a[0], rounding=r) == -1 + assert cos(a[1], rounding=r) == 1 + assert cos(a[2], rounding=r) == 1 + assert cos(a[3], rounding=r) == -1 + assert cos(a[4], rounding=r) == 1 + assert cos(a[5], rounding=r) == -1 + assert cos(a[6], rounding=r) == 1 + assert cos(a[7], rounding=r) == -1 + assert cos(a[8], rounding=r) == 1 + + r = round_up + assert cos(a[0], rounding=r) == -1 + assert cos(a[1], rounding=r) == 1 + assert cos(a[2], rounding=r) == 1 + assert cos(a[3], rounding=r) == -1 + assert cos(a[4], rounding=r) == 1 + assert cos(a[5], rounding=r) == -1 + assert cos(a[6], rounding=r) == 1 + assert cos(a[7], rounding=r) == -1 + assert cos(a[8], rounding=r) == 1 + + r = round_down + assert cos(a[0], rounding=r) > -1 + assert cos(a[1], rounding=r) < 1 + assert cos(a[2], rounding=r) < 1 + assert cos(a[3], rounding=r) > -1 + assert cos(a[4], rounding=r) < 1 + assert cos(a[5], rounding=r) > -1 + assert cos(a[6], rounding=r) < 1 + assert cos(a[7], rounding=r) > -1 + assert cos(a[8], rounding=r) < 1 + + r = round_floor + assert cos(a[0], rounding=r) == -1 + assert cos(a[1], rounding=r) < 1 + assert cos(a[2], rounding=r) < 1 + assert cos(a[3], rounding=r) == -1 + assert cos(a[4], rounding=r) < 1 + assert cos(a[5], rounding=r) == -1 + assert cos(a[6], rounding=r) < 1 + assert cos(a[7], rounding=r) == -1 + assert cos(a[8], rounding=r) < 1 + + r = round_ceiling + assert cos(a[0], rounding=r) > -1 + assert cos(a[1], rounding=r) == 1 + assert cos(a[2], rounding=r) == 1 + assert cos(a[3], rounding=r) > -1 + assert cos(a[4], rounding=r) == 1 + assert cos(a[5], rounding=r) > -1 + assert cos(a[6], rounding=r) == 1 + assert cos(a[7], rounding=r) > -1 + assert cos(a[8], rounding=r) == 1 diff --git a/mpmath/tests/test_version_frozen.sh b/mpmath/tests/test_version_frozen.sh new file mode 100755 index 0000000..69dc546 --- /dev/null +++ b/mpmath/tests/test_version_frozen.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# +# Test that the version number is provided correctly in a frozen (bundled) +# executable (see #1044). + +set -e + +# Get the directory of the current repository +MPMATH_DIR="$(realpath "$(dirname "$0")/../../")" +echo "Repo mpmath directory: $MPMATH_DIR" + +echo "Install requirements..." +python3 -m pip install build pyinstaller + +echo "Building the source distribution from the local repo..." +python3 -m build --sdist + +# Find and install the generated tarball +TARBALL=$(ls -t dist/*.tar.gz | head -1) +echo "Generated tarball: $TARBALL" + +echo "Installing mpmath from tarball..." +pip install dist/"$(basename $TARBALL)" + +TEMP_DIR=$(mktemp -d) +echo "Created temporary directory: $TEMP_DIR" +cd "$TEMP_DIR" + +# Create version_script.py that prints the package version +cat << EOF > version_script.py +import mpmath +print(mpmath.__version__) +EOF + +# Save local version for later comparison +DIRECT_VERSION="$(python3 -m mpmath --version)" + +echo "Building version_script with PyInstaller..." +pyinstaller --onefile --clean version_script.py + +echo "Run frozen executable and extract the version from the output..." +FROZEN_VERSION="$(./dist/version_script 2>&1)" + +if [ "$DIRECT_VERSION" == "$FROZEN_VERSION" ]; then + echo "Test passed: Version matches in frozen (bundled) executable." +else + echo "Test failed: Version mismatch." + echo "Direct version: $DIRECT_VERSION" + echo "Frozen version: $FROZEN_VERSION" + exit 1 +fi diff --git a/mpmath/tests/test_visualization.py b/mpmath/tests/test_visualization.py new file mode 100644 index 0000000..5255f6b --- /dev/null +++ b/mpmath/tests/test_visualization.py @@ -0,0 +1,70 @@ +""" +Limited tests of the visualization module. Right now it just makes +sure that passing custom Axes works. + +""" + +import pytest + +from mpmath import fp, mp + + +def test_axes(): + try: + import matplotlib + version = matplotlib.__version__.split("-")[0] + version = version.split(".")[:2] + if [int(_) for _ in version] < [0,99]: + raise ImportError + import pylab + except ImportError: + pytest.skip("\nSkipping test (pylab not available or too old version)\n") + fig = pylab.figure() + axes = fig.add_subplot(111) + for ctx in [mp, fp]: + ctx.plot(lambda x: x**2, [0, 3], axes=axes) + assert axes.get_xlabel() == 'x' + assert axes.get_ylabel() == 'f(x)' + + fig = pylab.figure() + axes = fig.add_subplot(111) + for ctx in [mp, fp]: + ctx.cplot(lambda z: z, [-2, 2], [-10, 10], axes=axes) + assert axes.get_xlabel() == 'Re(z)' + assert axes.get_ylabel() == 'Im(z)' + + +def test_issue_1007(): + # plot(), cplot() and splot() must not leave a stale figure open + # when the user-supplied function raises an unexpected exception; + # otherwise that blank figure lingers and is shown on the next call. + try: + import matplotlib + version = matplotlib.__version__.split("-")[0] + version = version.split(".")[:2] + if [int(_) for _ in version] < [0,99]: + raise ImportError + import pylab + except ImportError: + pytest.skip("\nSkipping test (pylab not available or too old version)\n") + + class Boom(Exception): + pass + + def bad(*args): + # An error that is not in plot_ignore, so it propagates out of + # plot()/cplot()/splot() instead of being silently skipped. + raise Boom + + for ctx in [mp, fp]: + pylab.close("all") + pytest.raises(Boom, lambda: ctx.plot(bad, [0, 2])) + assert pylab.get_fignums() == [] + + pylab.close("all") + pytest.raises(Boom, lambda: ctx.cplot(bad, [-2, 2], [-2, 2])) + assert pylab.get_fignums() == [] + + pylab.close("all") + pytest.raises(Boom, lambda: ctx.splot(bad, [-1, 1], [-1, 1])) + assert pylab.get_fignums() == [] diff --git a/mpmath/usertools.py b/mpmath/usertools.py new file mode 100644 index 0000000..e7c0adc --- /dev/null +++ b/mpmath/usertools.py @@ -0,0 +1,95 @@ + +def monitor(f, input='print', output='print'): + """ + Returns a wrapped copy of *f* that monitors evaluation by calling + *input* with every input (*args*, *kwargs*) passed to *f* and + *output* with every value returned from *f*. The default action + (specify using the special string value ``'print'``) is to print + inputs and outputs to stdout, along with the total evaluation + count:: + + >>> from mpmath import mp, diff, monitor, exp, findroot, sin + >>> mp.dps = 5 + >>> diff(monitor(exp), 1) # diff will eval f(x-h) and f(x+h) + in 0 (mpf('0.99999999906867742538452148'),) {} + out 0 mpf('2.7182818259274480055282064') + in 1 (mpf('1.0000000009313225746154785'),) {} + out 1 mpf('2.7182818309906424675501024') + mpf('2.7182808') + + To disable either the input or the output handler, you may + pass *None* as argument. + + Custom input and output handlers may be used e.g. to store + results for later analysis:: + + >>> mp.dps = 15 + >>> input = [] + >>> output = [] + >>> findroot(monitor(sin, input.append, output.append), 3.0) + mpf('3.1415926535897932') + >>> len(input) # Count number of evaluations + 9 + >>> print(input[3]) + ((mpf('3.1415076583334066'),), {}) + >>> print(output[3]) + 8.49952562843408e-5 + >>> print(input[4]) + ((mpf('3.1415928201669122'),), {}) + >>> print(output[4]) + -1.66577118985331e-7 + + """ + if not input: + input = lambda v: None + elif input == 'print': + incount = [0] + def input(value): + args, kwargs = value + print("in %s %r %r" % (incount[0], args, kwargs)) + incount[0] += 1 + if not output: + output = lambda v: None + elif output == 'print': + outcount = [0] + def output(value): + print("out %s %r" % (outcount[0], value)) + outcount[0] += 1 + def f_monitored(*args, **kwargs): + input((args, kwargs)) + v = f(*args, **kwargs) + output(v) + return v + return f_monitored + +def timing(f, *args, **kwargs): + """ + Returns time elapsed for evaluating ``f()``. Optionally arguments + may be passed to time the execution of ``f(*args, **kwargs)``. + + If the first call is very quick, ``f`` is called + repeatedly and the best time is returned. + """ + once = kwargs.get('once') + if 'once' in kwargs: + del kwargs['once'] + if args or kwargs: + if len(args) == 1 and not kwargs: + arg = args[0] + g = lambda: f(arg) + else: + g = lambda: f(*args, **kwargs) + else: + g = f + from timeit import default_timer as clock + t1=clock(); v=g(); t2=clock(); t=t2-t1 + if t > 0.05 or once: + return t + for i in range(3): + t1=clock() + # Evaluate multiple times because the timer function + # has a significant overhead + g();g();g();g();g();g();g();g();g();g() + t2=clock() + t=min(t,(t2-t1)/10) + return t diff --git a/mpmath/visualization.py b/mpmath/visualization.py new file mode 100644 index 0000000..266bd8a --- /dev/null +++ b/mpmath/visualization.py @@ -0,0 +1,321 @@ +""" +Plotting (requires matplotlib) +""" + +from colorsys import hsv_to_rgb, hls_to_rgb +from .libmp import NoConvergence + +class VisualizationMethods: + plot_ignore = (ValueError, ArithmeticError, ZeroDivisionError, NoConvergence) + +def plot(ctx, f, xlim=[-5,5], ylim=None, points=200, file=None, dpi=None, + singularities=[], axes=None, plot_kwargs={}): + r""" + Shows a simple 2D plot of a function `f(x)` or list of functions + `[f_0(x), f_1(x), \ldots, f_n(x)]` over a given interval + specified by *xlim*. Some examples:: + + plot(lambda x: exp(x)*li(x), [1, 4]) + plot([cos, sin], [-4, 4]) + plot([fresnels, fresnelc], [-4, 4]) + plot([sqrt, cbrt], [-4, 4]) + plot(lambda t: zeta(0.5+t*j), [-20, 20]) + plot([floor, ceil, abs, sign], [-5, 5]) + + Points where the function raises a numerical exception or + returns an infinite value are removed from the graph. + Singularities can also be excluded explicitly + as follows (useful for removing erroneous vertical lines):: + + plot(cot, ylim=[-5, 5]) # bad + plot(cot, ylim=[-5, 5], singularities=[-pi, 0, pi]) # good + + For parts where the function assumes complex values, the + real part is plotted with dashes and the imaginary part + is plotted with dots. + + .. note :: This function requires matplotlib (pylab). + """ + if file: + axes = None + fig = None + if not axes: + import pylab + fig = pylab.figure() + axes = fig.add_subplot(111) + if not isinstance(f, (tuple, list)): + f = [f] + a, b = xlim + colors = ['b', 'r', 'g', 'm', 'k'] + for n, func in enumerate(f): + x = ctx.arange(a, b, (b-a)/float(points)) + segments = [] + segment = [] + in_complex = False + for i in range(len(x)): + try: + if i != 0: + for sing in singularities: + if x[i-1] <= sing and x[i] >= sing: + raise ValueError + v = func(x[i]) + if ctx.isnan(v) or abs(v) > 1e300: + raise ValueError + if hasattr(v, "imag") and v.imag: + re = float(v.real) + im = float(v.imag) + if not in_complex: + in_complex = True + segments.append(segment) + segment = [] + segment.append((float(x[i]), re, im)) + else: + if in_complex: + in_complex = False + segments.append(segment) + segment = [] + if hasattr(v, "real"): + v = v.real + segment.append((float(x[i]), v)) + except ctx.plot_ignore: + if segment: + segments.append(segment) + segment = [] + except Exception: + pylab.close(fig) + raise + if segment: + segments.append(segment) + for segment in segments: + x = [s[0] for s in segment] + y = [s[1] for s in segment] + if not x: + continue + c = colors[n % len(colors)] + if len(segment[0]) == 3: + z = [s[2] for s in segment] + axes.plot(x, y, '--'+c, linewidth=3, **plot_kwargs) + axes.plot(x, z, ':'+c, linewidth=3, **plot_kwargs) + else: + axes.plot(x, y, c, linewidth=3, **plot_kwargs) + axes.set_xlim([float(_) for _ in xlim]) + if ylim: + axes.set_ylim([float(_) for _ in ylim]) + axes.set_xlabel('x') + axes.set_ylabel('f(x)') + axes.grid(True) + if fig: + if file: + pylab.savefig(file, dpi=dpi) + else: + pylab.show() + +def default_color_function(ctx, z): + if ctx.isinf(z): + return (1.0, 1.0, 1.0) + if ctx.isnan(z): + return (0.5, 0.5, 0.5) + pi = 3.1415926535898 + a = (float(ctx.arg(z)) + ctx.pi) / (2*ctx.pi) + a = (a + 0.5) % 1.0 + b = 1.0 - float(1/(1.0+abs(z)**0.3)) + return hls_to_rgb(a, b, 0.8) + +blue_orange_colors = [ + (-1.0, (0.0, 0.0, 0.0)), + (-0.95, (0.1, 0.2, 0.5)), # dark blue + (-0.5, (0.0, 0.5, 1.0)), # blueish + (-0.05, (0.4, 0.8, 0.8)), # cyanish + ( 0.0, (1.0, 1.0, 1.0)), + ( 0.05, (1.0, 0.9, 0.3)), # yellowish + ( 0.5, (0.9, 0.5, 0.0)), # orangeish + ( 0.95, (0.7, 0.1, 0.0)), # redish + ( 1.0, (0.0, 0.0, 0.0)), + ( 2.0, (0.0, 0.0, 0.0)), +] + +def phase_color_function(ctx, z): + if ctx.isinf(z): + return (1.0, 1.0, 1.0) + if ctx.isnan(z): + return (0.5, 0.5, 0.5) + pi = 3.1415926535898 + w = float(ctx.arg(z)) / pi + w = max(min(w, 1.0), -1.0) + for i in range(1,len(blue_orange_colors)): + if blue_orange_colors[i][0] > w: + a, (ra, ga, ba) = blue_orange_colors[i-1] + b, (rb, gb, bb) = blue_orange_colors[i] + s = (w-a) / (b-a) + return ra+(rb-ra)*s, ga+(gb-ga)*s, ba+(bb-ba)*s + +def cplot(ctx, f, re=[-5,5], im=[-5,5], points=2000, color=None, + verbose=False, file=None, dpi=None, axes=None, imshow_kwargs={}): + """ + Plots the given complex-valued function *f* over a rectangular part + of the complex plane specified by the pairs of intervals *re* and *im*. + For example:: + + cplot(lambda z: z, [-2, 2], [-10, 10]) + cplot(exp) + cplot(zeta, [0, 1], [0, 50]) + + By default, the complex argument (phase) is shown as color (hue) and + the magnitude is show as brightness. You can also supply a + custom color function (*color*). This function should take a + complex number as input and return an RGB 3-tuple containing + floats in the range 0.0-1.0. + + Alternatively, you can select a builtin color function by passing + a string as *color*: + + * "default" - default color scheme + * "phase" - a color scheme that only renders the phase of the function, + with white for positive reals, black for negative reals, gold in the + upper half plane, and blue in the lower half plane. + + To obtain a sharp image, the number of points may need to be + increased to 100,000 or thereabout. Since evaluating the + function that many times is likely to be slow, the 'verbose' + option is useful to display progress. + + .. note :: This function requires matplotlib (pylab). + """ + if color is None or color == "default": + color = ctx.default_color_function + if color == "phase": + color = ctx.phase_color_function + import pylab + if file: + axes = None + fig = None + if not axes: + fig = pylab.figure() + axes = fig.add_subplot(111) + rea, reb = re + ima, imb = im + dre = reb - rea + dim = imb - ima + M = int(ctx.sqrt(points*dre/dim)+1) + N = int(ctx.sqrt(points*dim/dre)+1) + x = pylab.linspace(rea, reb, M) + y = pylab.linspace(ima, imb, N) + # Note: we have to be careful to get the right rotation. + # Test with these plots: + # cplot(lambda z: z if z.real < 0 else 0) + # cplot(lambda z: z if z.imag < 0 else 0) + w = pylab.zeros((N, M, 3)) + for n in range(N): + for m in range(M): + z = ctx.mpc(x[m], y[n]) + try: + v = color(f(z)) + except ctx.plot_ignore: + v = (0.5, 0.5, 0.5) + except Exception: + pylab.close(fig) + raise + w[n,m] = v + if verbose: + print(str(n) + ' of ' + str(N)) + rea, reb, ima, imb = [float(_) for _ in [rea, reb, ima, imb]] + axes.imshow(w, extent=(rea, reb, ima, imb), origin='lower', **imshow_kwargs) + axes.set_xlabel('Re(z)') + axes.set_ylabel('Im(z)') + if fig: + if file: + pylab.savefig(file, dpi=dpi) + else: + pylab.show() + +def splot(ctx, f, u=[-5,5], v=[-5,5], points=100, keep_aspect=True, + wireframe=False, file=None, dpi=None, axes=None, plot3d_kwargs={}): + """ + Plots the surface defined by `f`. + + If `f` returns a single component, then this plots the surface + defined by `z = f(x,y)` over the rectangular domain with + `x = u` and `y = v`. + + If `f` returns three components, then this plots the parametric + surface `x, y, z = f(u,v)` over the pairs of intervals `u` and `v`. + + For example, to plot a simple function:: + + >>> from mpmath import sin, cos, pi, splot + >>> f = lambda x, y: sin(x+y)*cos(y) + >>> splot(f, [-pi,pi], [-pi,pi]) # doctest: +SKIP + + Plotting a donut:: + + >>> r, R = 1, 2.5 + >>> f = lambda u, v: [r*cos(u), (R+r*sin(u))*cos(v), (R+r*sin(u))*sin(v)] + >>> splot(f, [0, 2*pi], [0, 2*pi]) # doctest: +SKIP + + .. note :: This function requires matplotlib (pylab) 0.98.5.3 or higher. + """ + import matplotlib.pyplot as plt + import numpy as np + if file: + axes = None + fig = None + if not axes: + fig, axes = plt.subplots(subplot_kw={'projection': '3d'}) + ua, ub = map(float, u) + va, vb = map(float, v) + du = ub - ua + dv = vb - va + if not isinstance(points, (list, tuple)): + points = [points, points] + M, N = points + u = np.linspace(ua, ub, M) + v = np.linspace(va, vb, N) + x, y, z = [np.zeros((M, N)) for i in range(3)] + xab, yab, zab = [[0, 0] for i in range(3)] + for n in range(N): + for m in range(M): + try: + fdata = f(ctx.convert(u[m]), ctx.convert(v[n])) + except Exception: + plt.close(fig) + raise + try: + x[m,n], y[m,n], z[m,n] = fdata + except TypeError: + x[m,n], y[m,n], z[m,n] = u[m], v[n], fdata + for c, cab in [(x[m,n], xab), (y[m,n], yab), (z[m,n], zab)]: + if c < cab[0]: + cab[0] = c + if c > cab[1]: + cab[1] = c + if wireframe: + axes.plot_wireframe(x, y, z, rstride=4, cstride=4, **plot3d_kwargs) + else: + axes.plot_surface(x, y, z, rstride=4, cstride=4, **plot3d_kwargs) + axes.set_xlabel('x') + axes.set_ylabel('y') + axes.set_zlabel('z') + if keep_aspect: + dx, dy, dz = [cab[1] - cab[0] for cab in [xab, yab, zab]] + maxd = max(dx, dy, dz) + if dx < maxd: + delta = maxd - dx + axes.set_xlim3d(xab[0] - delta / 2.0, xab[1] + delta / 2.0) + if dy < maxd: + delta = maxd - dy + axes.set_ylim3d(yab[0] - delta / 2.0, yab[1] + delta / 2.0) + if dz < maxd: + delta = maxd - dz + axes.set_zlim3d(zab[0] - delta / 2.0, zab[1] + delta / 2.0) + if fig: + if file: + plt.savefig(file, dpi=dpi) + else: + plt.show() + + +VisualizationMethods.plot = plot +VisualizationMethods.default_color_function = default_color_function +VisualizationMethods.phase_color_function = phase_color_function +VisualizationMethods.cplot = cplot +VisualizationMethods.splot = splot diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4de56b9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,84 @@ +[build-system] +requires = ['setuptools>=77', 'setuptools-scm>=8'] +build-backend = 'setuptools.build_meta' + +[project] +name = 'mpmath' +description = 'Python library for arbitrary-precision floating-point arithmetic' +authors = [{name = 'Fredrik Johansson', email = 'fredrik.johansson@gmail.com'}] +license = 'BSD-3-Clause' +classifiers = ['Topic :: Scientific/Engineering :: Mathematics', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', + 'Programming Language :: Python :: 3.15', + 'Programming Language :: Python :: Free Threading :: 2 - Beta', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy'] +dynamic = ['version'] +requires-python = '>=3.10' +readme = 'README.rst' + +[project.urls] +Homepage = 'https://mpmath.org/' +'Source Code' = 'https://github.com/mpmath/mpmath' +'Bug Tracker' = 'https://github.com/mpmath/mpmath/issues' +Documentation = 'http://mpmath.org/doc/current/' + +[project.optional-dependencies] +tests = ['pytest>=6', 'numpy; python_version<"3.15"', 'packaging', 'pytest-timeout', + 'matplotlib; python_version<"3.15"', 'pexpect', 'ipython', 'hypothesis'] +develop = ['mpmath[tests]', 'flake518>=1.5', 'pytest-cov>=7', 'wheel', 'build'] +gmpy2 = ['gmpy2>=2.3'] +gmpy = ['mpmath[gmpy2]'] +gmp = ['python-gmp'] +docs = ['sphinx', 'matplotlib', 'sphinxcontrib-autoprogram'] +ci = ['pytest-xdist', 'diff_cover'] + +[tool.setuptools] +zip-safe = true + +[tool.setuptools.packages] +find = {namespaces = false} + +[tool.flake8] +select = ['E101', 'E111', 'E112', 'E113', 'E501', 'E703', 'E712', 'E713', + 'W191', 'W291', 'W292', 'W293', 'W391'] +exclude = ['.eggs', '.git'] +max_line_length = 200 + +[tool.setuptools_scm] +version_file = "mpmath/_version.py" + +[tool.pytest.ini_options] +testpaths = ['mpmath', 'docs'] +doctest_optionflags = ['IGNORE_EXCEPTION_DETAIL', 'ELLIPSIS'] +addopts = "--doctest-modules --doctest-glob='*.rst'" +norecursedirs = ['docs/plots', 'demo', '.eggs', '.git', '.hypothesis'] +filterwarnings = ['error::DeprecationWarning'] +xfail_strict = true +timeout = 600 + +[tool.coverage.run] +branch = true +omit = ['mpmath/tests/*', 'mpmath/_version.py'] +patch = ["subprocess"] + +[tool.coverage.html] +directory = 'build/coverage/html' +[tool.coverage.report] +exclude_lines = ['pragma: no cover', + 'raise NotImplementedError', + 'return NotImplemented', + 'if __name__ == .__main__.:'] +show_missing = true + +[tool.isort] +lines_after_imports = 2 +atomic = true