diff --git a/.circleci/config.yml b/.circleci/config.yml index 0b532ae2fcbf..ee71c40d84c0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -76,14 +76,14 @@ jobs: command: | pip install numpy cython pybind11 pythran ninja meson pip install -r requirements/doc.txt - pip install mpmath gmpy2 "asv>=0.6.0" click rich-click doit pydevtool pooch threadpoolctl + pip install mpmath gmpy2 "asv>=0.6.0" pooch threadpoolctl spin # extra benchmark deps pip install pyfftw cffi pytest - run: name: build SciPy command: | - python dev.py build -j2 + spin build -j2 - save_cache: key: deps_ccache-{{ .Branch }} @@ -115,8 +115,8 @@ jobs: name: build docs no_output_timeout: 25m command: | - export PYTHONPATH=$PWD/build-install/lib/python3.11/site-packages - python dev.py --no-build doc -j2 2>&1 | tee sphinx_log.txt + export PYTHONPATH=$PWD/build-install/usr/lib/python3.11/site-packages + spin docs -j2 2>&1 | tee sphinx_log.txt - run: name: Check sphinx log for warnings (which are treated as errors) @@ -148,7 +148,7 @@ jobs: name: run asv no_output_timeout: 30m command: | - export PYTHONPATH=$PWD/build-install/lib/python3.11/site-packages + export PYTHONPATH=$PWD/build-install/usr/lib/python3.11/site-packages cd benchmarks asv machine --machine CircleCI export SCIPY_GLOBAL_BENCH_NUMTRIALS=1 @@ -176,8 +176,8 @@ jobs: no_output_timeout: 25m command: | sudo apt-get install -y wamerican-small - export PYTHONPATH=$PWD/build-install/lib/python3.11/site-packages - python dev.py --no-build refguide-check + export PYTHONPATH=$PWD/build-install/usr/lib/python3.11/site-packages + spin refguide-check - run: name: smoke-docs @@ -185,13 +185,13 @@ jobs: command: | pip install matplotlib hypothesis pip install scipy-doctest - python dev.py smoke-docs + spin smoke-docs - run: name: smoke-tutorials no_output_timeout: 10m command: | - python dev.py smoke-tutorials + spin smoke-tutorials # Upload build output to scipy/devdocs repository, using SSH deploy keys. # The keys are only available for builds on main branch. diff --git a/.gitattributes b/.gitattributes index 7f967de4f0d1..6e8481e9c91f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,6 +3,11 @@ # Note: when adding to this list, be aware that you need to commit your changes # before they take effect (can be confusing during testing) +# +# Note: git submodules cannot be handled in this file. For removing +# files and directories from git submodules, see `tools/trim_sdist_content.py` +# This is a Meson "dist script", run during sdist generation only. + .circleci/* export-ignore .github/* export-ignore ci/* export-ignore diff --git a/.github/workflows/array_api.yml b/.github/workflows/array_api.yml index 88e99e87265b..af4c488e838a 100644 --- a/.github/workflows/array_api.yml +++ b/.github/workflows/array_api.yml @@ -60,7 +60,7 @@ jobs: - name: Install Python packages run: | - python -m pip install numpy cython pytest pytest-xdist pytest-timeout pybind11 mpmath gmpy2 pythran ninja meson click rich-click doit pydevtool pooch hypothesis array-api-strict + python -m pip install numpy cython pytest pytest-xdist pytest-timeout pybind11 mpmath gmpy2 pythran ninja meson click pooch hypothesis array-api-strict spin - name: Install PyTorch CPU run: | @@ -98,14 +98,15 @@ jobs: - name: Setup build and install scipy run: | - python dev.py build + spin build - name: Test SciPy run: | export OMP_NUM_THREADS=2 - python dev.py --no-build test -b all $XP_TESTS -- --durations 3 --timeout=60 + # expand as more modules are supported by adding to `XP_TESTS` above + spin test -b all $XP_TESTS -- --durations 3 --timeout=60 - name: Test SciPy with torch/float32 run: | export SCIPY_DEFAULT_DTYPE="float32" - python dev.py --no-build test -b torch $XP_TESTS -- --durations 3 --timeout=60 + spin test -b torch $XP_TESTS -- --durations 3 --timeout=60 diff --git a/.github/workflows/circle_artifacts.yml b/.github/workflows/circle_artifacts.yml index b51dac868fdf..9f6df27e8e15 100644 --- a/.github/workflows/circle_artifacts.yml +++ b/.github/workflows/circle_artifacts.yml @@ -1,6 +1,10 @@ name: Redirect circleci artifacts on: [status] + +permissions: + contents: read # to fetch code (actions/checkout) + jobs: circleci_artifacts_redirector_job: runs-on: ubuntu-22.04 diff --git a/.github/workflows/commit_message.yml b/.github/workflows/commit_message.yml index 05a3ad9dc02f..24026f1fb301 100644 --- a/.github/workflows/commit_message.yml +++ b/.github/workflows/commit_message.yml @@ -23,6 +23,7 @@ jobs: # Gets the correct commit message for pull request with: ref: ${{ github.event.pull_request.head.sha }} + - name: Check for skips id: skip_check # the lint workflow is not currently skipped with [docs only]. @@ -40,4 +41,4 @@ jobs: fi fi echo "message=$RUN" >> $GITHUB_OUTPUT - echo github.ref ${{ github.ref }} + echo github.ref $GITHUB_REF diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 641f9e5db5c7..a93da7403cd7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -39,19 +39,17 @@ jobs: - name: Install Python packages run: | - python -m pip install ruff cython-lint packaging tach + python -m pip install ruff cython-lint packaging tach spin - name: Lint run: | set -euo pipefail - python tools/lint.py --diff-against origin/$GITHUB_BASE_REF - python tools/check_unicode.py - python tools/check_test_name.py + spin lint --diff-against origin/$GITHUB_BASE_REF - name: Check that Python.h is first in any file including it. shell: bash run: | - python tools/check_python_h_first.py + python tools/check_python_h_first.py - name: Check module interdependencies run: | diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index b8faec729b26..84981c4d6bf8 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -26,7 +26,7 @@ jobs: uses: ./.github/workflows/commit_message.yml test_meson: - name: mypy (py3.11) & dev deps (py3.13), fast, dev.py + name: mypy (py3.11) & dev deps (py3.13), fast, spin needs: get_commit_message # If using act to run CI locally the github object does not exist and # the usual skipping should not be enforced @@ -65,13 +65,13 @@ jobs: - name: Install Python packages if: matrix.python-version == '3.11' run: | - python -m pip install numpy cython pytest pytest-xdist pytest-timeout pybind11 mpmath gmpy2 pythran ninja meson click rich-click doit pydevtool pooch hypothesis + python -m pip install numpy cython pytest pytest-xdist pytest-timeout pybind11 mpmath gmpy2 pythran ninja meson pooch hypothesis spin - name: Install Python packages from repositories if: matrix.python-version == '3.13-dev' # this run will use python dev versions when available run: | python -m pip install git+https://github.com/numpy/numpy.git - python -m pip install ninja cython pytest pybind11 pytest-xdist pytest-timeout click rich-click doit pydevtool pooch hypothesis "setuptools<67.3" meson + python -m pip install ninja cython pytest pybind11 pytest-xdist pytest-timeout spin pooch hypothesis "setuptools<67.3" meson python -m pip install git+https://github.com/serge-sans-paille/pythran.git # Disable Meson master testing until upstream option handling is fixed, see scipy#22534 # python -m pip install git+https://github.com/mesonbuild/meson.git @@ -103,7 +103,7 @@ jobs: - name: Setup build and install scipy run: | - python dev.py build --werror + spin build --werror - name: Ccache performance shell: bash -l {0} @@ -119,10 +119,10 @@ jobs: - name: Check usage of install tags run: | rm -r ${{ env.INSTALLDIR }} - python dev.py build --tags=runtime,python-runtime,devel + spin build --tags=runtime,python-runtime,devel python tools/check_installation.py ${{ env.INSTALLDIR }} --no-tests rm -r ${{ env.INSTALLDIR }} - python dev.py build --tags=runtime,python-runtime,devel,tests + spin build --tags=runtime,python-runtime,devel,tests python tools/check_installation.py ${{ env.INSTALLDIR }} - name: Check build-internal dependencies @@ -135,12 +135,12 @@ jobs: python -m pip install mypy==1.10.0 types-psutil typing_extensions python -m pip install pybind11 sphinx - python -u dev.py mypy + spin mypy - name: Test SciPy run: | export OMP_NUM_THREADS=2 - python dev.py --no-build test -j2 -- --durations 10 --timeout=60 + spin test -j2 -- --durations 10 --timeout=60 ################################################################################# test_venv_install: @@ -213,6 +213,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: recursive + - name: Configuring Test Environment run: | sudo apt-get update @@ -233,7 +234,7 @@ jobs: ################################################################################# gcc9: # Purpose is to examine builds with oldest-supported gcc and test with pydata/sparse. - name: Oldest GCC & pydata/sparse, fast, py3.11/npMin, pip+pytest + name: Oldest GCC & pydata/sparse, full, py3.11/npMin, pip+pytest needs: get_commit_message if: > needs.get_commit_message.outputs.message == 1 @@ -285,7 +286,7 @@ jobs: ################################################################################# prerelease_deps_coverage_64bit_blas: # TODO: re-enable ILP64 build. - name: Prerelease deps & coverage report, full, py3.11/npMin & py3.11/npPre, dev.py, SCIPY_ARRAY_API=1 + name: Prerelease deps & coverage report, full, py3.11/npMin & py3.11/npPre, spin, SCIPY_ARRAY_API=1 needs: get_commit_message if: > needs.get_commit_message.outputs.message == 1 @@ -321,7 +322,7 @@ jobs: - name: Install Python packages run: | - python -m pip install cython pythran ninja meson-python pybind11 click rich_click pydevtool + python -m pip install cython pythran ninja meson-python pybind11 spin python -m pip install --pre --upgrade pytest pytest-cov pytest-xdist mpmath gmpy2 threadpoolctl pooch hypothesis matplotlib python -m pip install -r requirements/openblas.txt # Install numpy last, to ensure we get nightly (avoid possible <2.0 constraints). @@ -348,7 +349,7 @@ jobs: - name: Build and install SciPy run: | - python dev.py build --gcov --with-scipy-openblas + spin build --gcov --with-scipy-openblas - name: Ccache performance shell: bash -l {0} @@ -363,11 +364,11 @@ jobs: run: | export OPENBLAS_NUM_THREADS=1 export SCIPY_ARRAY_API=1 - python dev.py --no-build test --coverage -j2 --mode full -- --cov --cov-report term-missing + spin test --no-build --coverage -j2 --mode full -- --cov --cov-report term-missing ################################################################################# linux_32bit: - name: 32-bit, fast, py3.11/npMin, dev.py + name: 32-bit, fast, py3.11/npMin, spin needs: get_commit_message if: > needs.get_commit_message.outputs.message == 1 @@ -389,7 +390,7 @@ jobs: uname -a && \ python3.11 -m venv test && \ source test/bin/activate && \ - python -m pip install doit click rich_click pydevtool meson ninja && \ + python -m pip install spin meson ninja && \ python -m pip install -r requirements/openblas.txt && \ # Ensure that scipy-openblas is picked up by the numpy<1.26 build cat > \$HOME/.numpy-site.cfg < @@ -467,6 +468,7 @@ jobs: with: submodules: recursive fetch-tags: true + - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: python-version: '3.13t' @@ -476,19 +478,11 @@ jobs: sudo apt-get update sudo apt-get install -y gfortran - - name: Install nightly Cython - run: | - pip install -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple cython - - name: Install Python dependencies run: | - pip install numpy ninja meson-python pybind11 click rich_click pydevtool pytest pytest-xdist threadpoolctl pooch hypothesis + pip install -r requirements/build.txt pip install -r requirements/openblas.txt - - - name: Install Pythran master - run: | - # 0.17.0 doesn't support free-threading, update once the next release is out - python -m pip install git+https://github.com/serge-sans-paille/pythran.git + pip install spin pytest pytest-xdist threadpoolctl pooch hypothesis - name: Install pytest-run-parallel if: ${{ matrix.parallel == '1'}} @@ -498,12 +492,12 @@ jobs: - name: Build SciPy run: | - python dev.py build --with-scipy-openblas + spin build --with-scipy-openblas - name: Run tests (full) if: ${{ matrix.parallel == '0'}} run: | - python dev.py --no-build test -j2 -m full --durations=10 + spin test -j2 -m full --durations=10 - name: Run tests (fast, with pytest-run-parallel) if: ${{ matrix.parallel == '1'}} @@ -511,28 +505,28 @@ jobs: # Excluded modules: # - scipy.spatial has multiple issues in kdtree/qhull, and gh-20655 is pending. TEST_SUBMODULES: >- - -t scipy.cluster - -t scipy.constants - -t scipy.datasets - -t scipy.differentiate - -t scipy.fft - -t scipy.fftpack - -t scipy.integrate - -t scipy.interpolate - -t scipy.io - -t scipy.linalg - -t scipy.misc - -t scipy.ndimage - -t scipy.odr - -t scipy.optimize - -t scipy.signal - -t scipy.sparse - -t scipy.special - -t scipy.stats + scipy.cluster + scipy.constants + scipy.datasets + scipy.differentiate + scipy.fft + scipy.fftpack + scipy.integrate + scipy.interpolate + scipy.io + scipy.linalg + scipy.misc + scipy.ndimage + scipy.odr + scipy.optimize + scipy.signal + scipy.sparse + scipy.special + scipy.stats run: | # Note: only fast tests; full test suite is unlikely to uncover anything more, # and it'll be quite slow with pytest-run-parallel - python dev.py --no-build test $TEST_SUBMODULES -- --parallel-threads=4 + spin test -t $TEST_SUBMODULES -- --parallel-threads=4 ################################################################################# clang-17-build-only: @@ -574,7 +568,7 @@ jobs: ################################################################################# test_aarch64: - name: aarch64, fast, py3.12/npAny, pip+pytest + name: aarch64, fast, fail slow, py3.12/npAny, pip+pytest needs: get_commit_message if: > needs.get_commit_message.outputs.message == 1 @@ -598,6 +592,10 @@ jobs: - name: Install Python packages run: | python -m pip install -r requirements/build.txt -r requirements/test.txt + # We want to check for test timing only in a single job, on Linux, running the + # fast test suite. This is that job. See gh-20806 for previous issues + # after running this job on Windows and in multiple jobs. + python -m pip install pytest-fail-slow - name: Install SciPy run: | @@ -607,4 +605,4 @@ jobs: run: | export OMP_NUM_THREADS=2 cd .. - pytest --pyargs scipy -m 'not slow' + pytest --pyargs scipy -m 'not slow' --durations=0 --durations-min=0.5 --fail-slow=1.0 diff --git a/.github/workflows/linux_intel_oneAPI.yml b/.github/workflows/linux_intel_oneAPI.yml index 8d7cf44c842a..ae227bc14720 100644 --- a/.github/workflows/linux_intel_oneAPI.yml +++ b/.github/workflows/linux_intel_oneAPI.yml @@ -30,7 +30,7 @@ jobs: uses: ./.github/workflows/commit_message.yml icx_icpx_ifx_mkl: - name: py3.12, dev.py + name: py3.12, spin needs: get_commit_message # Ensure (a) this doesn't run on forks by default, and # (b) it does run with Act locally (`github` doesn't exist there) @@ -87,7 +87,7 @@ jobs: shell: bash -l {0} run: | conda activate scipy-dev - conda install -c conda-forge pkg-config meson meson-python ninja numpy cython pybind11 pytest pytest-xdist pytest-timeout pytest-fail-slow pooch rich-click click doit pydevtool hypothesis + conda install -c conda-forge pkg-config meson meson-python ninja numpy cython pybind11 pytest pytest-xdist pytest-timeout pooch hypothesis spin - name: Initialise Intel oneAPI and Build SciPy shell: bash -l {0} @@ -99,11 +99,11 @@ jobs: # xref https://github.com/conda-forge/pythran-feedstock/issues/77). pip install pythran unset FFLAGS - CC=icx CXX=icpx FC=ifx python dev.py build -C-Dblas=mkl-dynamic-lp64-iomp -C-Dlapack=mkl-dynamic-lp64-iomp + CC=icx CXX=icpx FC=ifx spin build -- -Dblas=mkl-dynamic-lp64-iomp -Dlapack=mkl-dynamic-lp64-iomp - name: Test scipy shell: bash -l {0} run: | . /opt/intel/oneapi/setvars.sh --force conda activate scipy-dev - python dev.py test + spin test diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index cc2f83189737..4b23c3b57ae1 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -115,14 +115,24 @@ jobs: # optional test dependencies mamba install scikit-umfpack scikit-sparse - CC="ccache $CC" python dev.py build + # configure system boost + mamba install libboost-headers=1.88.0 + export BOOST_INCLUDEDIR=${{ env.CONDA }}/envs/scipy-dev/include/boost + export BOOST_LIBRARYDIR=${{ env.CONDA }}/envs/scipy-dev/lib + rm -rf subprojects/boost_math # so will fail if system boost doesn't work + + # configure system qhull + mamba install qhull=2020.2 + rm -rf subprojects/qhull_r # so will fail if system qhull doesn't work + + CC="ccache $CC" spin build --use-system-libraries - name: Test SciPy shell: bash -l {0} run: | conda activate scipy-dev export OMP_NUM_THREADS=2 - python dev.py -n test -j2 + spin test -j2 - name: Ccache statistics shell: bash -l {0} @@ -131,7 +141,7 @@ jobs: test_scipy_openblas: - name: M1 & OpenBLAS, fast, py3.11/npAny, dev.py + name: M1 & OpenBLAS, fast, py3.11/npAny, spin needs: get_commit_message # If using act to run CI locally the github object does not exist and # the usual skipping should not be enforced @@ -147,7 +157,6 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: recursive - - name: Setup Python uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: @@ -157,7 +166,7 @@ jobs: - name: Build and Install SciPy run: | sudo xcode-select -s /Applications/Xcode_15.2.app - + git submodule update --init GFORTRAN_LOC=$(which gfortran-13) ln -s $GFORTRAN_LOC gfortran @@ -166,17 +175,17 @@ jobs: # Ensure we have gfortran dylib GFORTRAN_LIB=$(dirname `gfortran --print-file-name libgfortran.dylib`) export DYLD_LIBRARY_PATH=$GFORTRAN_LIB - - pip install click doit pydevtool rich_click meson cython pythran pybind11 ninja numpy + + pip install meson cython pythran pybind11 ninja numpy spin pip install -r requirements/openblas.txt - python dev.py build --with-scipy-openblas + spin build --with-scipy-openblas pip install pooch pytest hypothesis - python dev.py -n test + spin test test_meson_accelerate: - name: Accelerate, fast, py3.11/npAny, dev.py + name: Accelerate, fast, py3.11/npAny, spin needs: get_commit_message # If using act to run CI locally the github object does not exist and # the usual skipping should not be enforced @@ -212,8 +221,8 @@ jobs: GFORTRAN_LIB=$(dirname `gfortran --print-file-name libgfortran.dylib`) export DYLD_LIBRARY_PATH=$GFORTRAN_LIB - pip install click doit pydevtool rich_click meson cython pythran pybind11 ninja numpy - python dev.py build --with-accelerate + pip install meson cython pythran pybind11 ninja numpy spin + spin build --with-accelerate pip install pooch pytest hypothesis - python dev.py -n test + spin test diff --git a/.github/workflows/musllinux.yml b/.github/workflows/musllinux.yml index 104ced857c26..f39c25bed119 100644 --- a/.github/workflows/musllinux.yml +++ b/.github/workflows/musllinux.yml @@ -24,7 +24,7 @@ jobs: uses: ./.github/workflows/commit_message.yml musllinux_x86_64: - name: musl Ubuntu-latest, fast, py3.11/npAny, dev.py + name: musl Ubuntu-latest, fast, py3.11/npAny, spin needs: get_commit_message runs-on: ubuntu-latest # If using act to run CI locally the github object does not exist and @@ -40,17 +40,17 @@ jobs: steps: - name: Get source - run: | + run: | apk update --quiet apk add build-base gfortran git - git config --global --add safe.directory $PWD - + git config --global --add safe.directory $PWD + if [ $GITHUB_EVENT_NAME != pull_request ]; then git clone --recursive --branch=$GITHUB_REF_NAME https://github.com/${GITHUB_REPOSITORY}.git $GITHUB_WORKSPACE git reset --hard $GITHUB_SHA - else + else git clone --recursive https://github.com/${GITHUB_REPOSITORY}.git $GITHUB_WORKSPACE git fetch origin $GITHUB_REF:my_ref_name git checkout $GITHUB_BASE_REF @@ -62,7 +62,7 @@ jobs: - name: prep build environment - run: | + run: | cd $RUNNER_TEMP python -m venv test_env source test_env/bin/activate @@ -70,12 +70,11 @@ jobs: python -m pip install cython numpy # python -m pip install --upgrade --pre -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy - python -m pip install meson ninja pybind11 pythran pytest hypothesis - python -m pip install click rich_click doit pydevtool pooch + python -m pip install meson ninja pybind11 pythran pytest hypothesis spin pooch python -m pip install -r requirements/openblas.txt - + chmod +x tools/wheels/cibw_before_build_linux.sh - tools/wheels/cibw_before_build_linux.sh --nightly . + tools/wheels/cibw_before_build_linux.sh --nightly . - name: test run: | @@ -84,4 +83,4 @@ jobs: source test_env/bin/activate cd $GITHUB_WORKSPACE export PKG_CONFIG_PATH=$PWD - python dev.py test + spin test diff --git a/.github/workflows/pixi.lock b/.github/workflows/pixi.lock index 6778e2f838ea..02d032b5278b 100644 --- a/.github/workflows/pixi.lock +++ b/.github/workflows/pixi.lock @@ -1,124 +1,124 @@ -version: 5 +version: 6 environments: array-api: channels: - url: https://conda.anaconda.org/conda-forge/ packages: linux-64: - - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/array-api-strict-2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-3_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/array-api-strict-2.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_hcf00494_mkl.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.4-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.8-py312hd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.10-py312hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.16.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2024.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jax-0.4.35-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.4.35-cpu_py312h7d5f655_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jax-0.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.5.2-cpu_py312h860c521_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.67.1-h25350d4_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250127.1-cxx17_hbbce691_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_hfdb39a5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_h372d94f_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.71.0-h8e591d7_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.2-default_h0d58e46_1001.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_hc41d3b0_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_hbc6e62b_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.3-h6128344_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hbbce691_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.5.1-cpu_mkl_he8ec5d7_108.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.29.3-h501fc15_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hba17884_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.7.0-cpu_mkl_hf6ddc5a_100.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.49.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.50.0-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.5-h0d44e9d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.8-h4bc477f_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-19.1.6-h024ca30_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-20.1.4-h024ca30_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2024.2.2-ha957f24_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-devel-2024.2.2-ha770c72_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-include-2024.2.2-ha957f24_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.0-py312hf9745cd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.1-py312hf9745cd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.4.2-pyh267e887_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/opt-einsum-3.4.0-hd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/opt_einsum-3.4.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.15.0-py312h68727a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda @@ -127,199 +127,202 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.5.1-cpu_mkl_py312_heeca0f5_108.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-cpu-2.5.1-cpu_mkl_hc60beec_108.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.7.0-cpu_mkl_py312_h6a7998d_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-cpu-2.7.0-cpu_mkl_hc60beec_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.0-py312h180e4f1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.7-h1b44611_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.13.3-pyh2585a3b_105.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py312ha707e6e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.8-h1b44611_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-hceb3a55_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.13.2-h0e9735f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda array-api-cuda: channels: - url: https://conda.anaconda.org/conda-forge/ packages: linux-64: - - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/array-api-strict-2.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-3_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/array-api-strict-2.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_hcf00494_mkl.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.4-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.8-py312hd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.6.77-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-crt-dev_linux-64-12.6.85-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-crt-tools-12.6.85-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-12.6.77-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-dev_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-static_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-12.6.80-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-dev-12.6.80-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvcc-tools-12.6.85-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.6.85-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvtx-12.6.77-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-12.6.85-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.6-h7480c83_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cudnn-9.3.0.75-h62a6f1c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-13.3.0-py312h7d319b9_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-core-13.3.0-py312h1acd1a8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.10-py312hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.9.27-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-crt-dev_linux-64-12.9.41-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-crt-tools-12.9.41-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-12.9.37-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-dev_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-static_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cuobjdump-12.9.26-hbd13f7d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-12.9.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-dev-12.9.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvcc-tools-12.9.41-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvdisasm-12.9.19-hbd13f7d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.9.41-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvtx-12.9.19-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-12.9.41-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.9-h4f385c5_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cudnn-9.8.0.87-h81d5506_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-13.4.1-py312h78400a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-core-13.4.1-py312h007fbcc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/fastrlock-0.8.3-py312h6edf5ed_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.16.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2024.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jax-0.4.35-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.4.35-cuda126py312hd27b167_200.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jax-0.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.5.2-cuda126py312hbc630d6_201.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcap-2.71-h39aace5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.6.4.1-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-dev-12.6.4.1-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.3.0.4-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-dev-11.3.0.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufile-1.11.1.6-h12f29b5_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.7.77-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-dev-10.3.7.77-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.1.2-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-dev-11.7.1.2-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.4.2-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-dev-12.5.4.2-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250127.1-cxx17_hbbce691_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_hfdb39a5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcap-2.75-h39aace5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_h372d94f_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.9.0.13-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-dev-12.9.0.13-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcudss-0.5.0.16-h14340ca_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.4.0.6-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-dev-11.4.0.6-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufile-1.14.0.30-h628e99a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.10.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-dev-10.3.10.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.4.40-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-dev-11.7.4.40-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.9.5-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-dev-12.5.9.5-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-lib-1.11.0-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.51-hbd13f7d_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.67.1-hc2c308b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.55-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.71.0-h8e591d7_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.2-default_h0d58e46_1001.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libmagma-2.8.0-h566cb83_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libmagma_sparse-2.8.0-h0af6554_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_hc41d3b0_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_hbc6e62b_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmagma-2.9.0-h19665d7_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnl-3.11.0-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.6.85-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.2-h5b01275_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hbbce691_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-256.9-h0b6a36f_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.5.1-cuda126_hebb32c0_306.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libudev1-256.9-h9a4d06a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.9.41-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.29.3-h501fc15_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hba17884_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-257.4-h4e0b6ca_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.7.0-cuda126_mkl_h99b69db_300.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libudev1-257.4-hbe16f8c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.49.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.50.0-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.5-h0d44e9d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.8-h4bc477f_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-19.1.6-h024ca30_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-20.1.4-h024ca30_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2024.2.2-ha957f24_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-devel-2024.2.2-ha770c72_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-include-2024.2.2-ha957f24_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.0-py312hf9745cd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.1-py312hf9745cd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nccl-2.23.4.1-h2b5d15b_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nccl-2.26.5.1-ha44e49d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.4.2-pyh267e887_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/opt-einsum-3.4.0-hd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/opt_einsum-3.4.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.15.0-py312h68727a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda @@ -328,40 +331,42 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.5.1-cuda126_py312h1763f6d_306.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-gpu-2.5.1-cuda126ha999a5f_306.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/rdma-core-55.0-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.7.0-cuda126_mkl_py312_h30b5a27_300.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-gpu-2.7.0-cuda126_mkl_ha999a5f_300.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rdma-core-57.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.0-py312h180e4f1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.7-h1b44611_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.13.3-pyh2585a3b_105.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py312ha707e6e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.8-h1b44611_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-hceb3a55_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/triton-3.3.0-cuda126py312hebffaa9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.13.2-h0e9735f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda array-api-strict: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -369,95 +374,95 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/array-api-strict-2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/array-api-strict-2.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_h1ea3ea9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_h59b9bed_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_he106b2a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_h7ac8fdf_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_he2f377e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.29-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.28-pthreads_h6ec200e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.29-pthreads_h6ec200e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda @@ -466,32 +471,32 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.1.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda cupy: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -499,109 +504,109 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_h1ea3ea9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.6.77-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-dev_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-static_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.6.85-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.6-h7480c83_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-13.3.0-py312h7d319b9_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-core-13.3.0-py312h1acd1a8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.9.27-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-dev_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-static_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.9.41-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.9-h4f385c5_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-13.4.1-py312h78400a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-core-13.4.1-py312h007fbcc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/fastrlock-0.8.3-py312h6edf5ed_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.6.4.1-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.3.0.4-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.7.77-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.1.2-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.4.2-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_h59b9bed_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_he106b2a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.9.0.13-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.4.0.6-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.10.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.4.40-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.9.5-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_h7ac8fdf_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_he2f377e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.6.85-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.9.41-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.29-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.28-pthreads_h6ec200e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.29-pthreads_h6ec200e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda @@ -610,32 +615,32 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.1.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda default: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -643,94 +648,94 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_h1ea3ea9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_h59b9bed_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_he106b2a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_h7ac8fdf_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_he2f377e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.29-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.28-pthreads_h6ec200e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.29-pthreads_h6ec200e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda @@ -739,32 +744,32 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.1.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda jax: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -772,104 +777,103 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_h1ea3ea9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.4-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jax-0.4.35-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.4.35-cpu_py312h7d5f655_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jax-0.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.5.2-cpu_py312h860c521_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.67.1-h25350d4_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250127.1-cxx17_hbbce691_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_h59b9bed_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_he106b2a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.71.0-h8e591d7_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_h7ac8fdf_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_he2f377e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.3-h6128344_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hbbce691_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.29-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.29.3-h501fc15_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hba17884_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.0-py312hf9745cd_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.1-py312hf9745cd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.28-pthreads_h6ec200e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/opt-einsum-3.4.0-hd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.29-pthreads_h6ec200e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/opt_einsum-3.4.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda @@ -878,34 +882,34 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.0-py312h180e4f1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py312ha707e6e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.1.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda jax-cuda: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -913,131 +917,130 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_h1ea3ea9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.4-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.6.77-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-crt-dev_linux-64-12.6.85-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-crt-tools-12.6.85-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-12.6.77-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-dev_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-static_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-12.6.80-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-dev-12.6.80-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvcc-tools-12.6.85-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.6.85-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvtx-12.6.77-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-12.6.85-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.6-h7480c83_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cudnn-9.3.0.75-h62a6f1c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.9.27-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-crt-dev_linux-64-12.9.41-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-crt-tools-12.9.41-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-12.9.37-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-dev_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-static_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-12.9.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-dev-12.9.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvcc-tools-12.9.41-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.9.41-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvtx-12.9.19-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-12.9.41-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.9-h4f385c5_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cudnn-9.8.0.87-h81d5506_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jax-0.4.35-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.4.35-cuda126py312hd27b167_200.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jax-0.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.5.2-cuda126py312hbc630d6_201.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.6.4.1-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-dev-12.6.4.1-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.3.0.4-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-dev-11.3.0.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.7.77-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-dev-10.3.7.77-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.1.2-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-dev-11.7.1.2-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.4.2-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-dev-12.5.4.2-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.67.1-h25350d4_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250127.1-cxx17_hbbce691_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_h59b9bed_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_he106b2a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.9.0.13-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-dev-12.9.0.13-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.4.0.6-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-dev-11.4.0.6-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.10.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-dev-10.3.10.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.4.40-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-dev-11.7.4.40-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.9.5-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-dev-12.5.9.5-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.71.0-h8e591d7_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_h7ac8fdf_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_he2f377e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.6.85-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.3-h6128344_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hbbce691_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.9.41-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.29-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.29.3-h501fc15_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hba17884_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.0-py312hf9745cd_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.1-py312hf9745cd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nccl-2.23.4.1-h2b5d15b_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.28-pthreads_h6ec200e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/opt-einsum-3.4.0-hd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nccl-2.26.5.1-ha44e49d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.29-pthreads_h6ec200e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/opt_einsum-3.4.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda @@ -1046,144 +1049,145 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.0-py312h180e4f1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py312ha707e6e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.1.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda torch: channels: - url: https://conda.anaconda.org/conda-forge/ packages: linux-64: - - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-3_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_hcf00494_mkl.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.8-py312hd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.10-py312hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.16.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2024.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250127.1-cxx17_hbbce691_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_hfdb39a5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_h372d94f_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.2-default_h0d58e46_1001.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_hc41d3b0_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_hbc6e62b_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.3-h6128344_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.5.1-cpu_mkl_he8ec5d7_108.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.29.3-h501fc15_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.7.0-cpu_mkl_hf6ddc5a_100.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.49.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.50.0-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.5-h0d44e9d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.8-h4bc477f_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-19.1.6-h024ca30_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-20.1.4-h024ca30_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2024.2.2-ha957f24_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-devel-2024.2.2-ha770c72_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-include-2024.2.2-ha957f24_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.4.2-pyh267e887_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.15.0-py312h68727a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda @@ -1192,171 +1196,179 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.5.1-cpu_mkl_py312_heeca0f5_108.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-cpu-2.5.1-cpu_mkl_hc60beec_108.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.7.0-cpu_mkl_py312_h6a7998d_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-cpu-2.7.0-cpu_mkl_hc60beec_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.7-h1b44611_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.13.3-pyh2585a3b_105.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.8-h1b44611_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-hceb3a55_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.13.2-h0e9735f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda torch-cuda: channels: - url: https://conda.anaconda.org/conda-forge/ packages: linux-64: - - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-3_kmp_llvm.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_hcf00494_mkl.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.8-py312hd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-12.6.77-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.6.85-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvtx-12.6.77-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.6-h7480c83_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cudnn-9.3.0.75-h62a6f1c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.10-py312hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-crt-tools-12.9.41-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-12.9.37-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cuobjdump-12.9.26-hbd13f7d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-12.9.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvcc-tools-12.9.41-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvdisasm-12.9.19-hbd13f7d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.9.41-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvtx-12.9.19-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-12.9.41-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.9-h4f385c5_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cudnn-9.8.0.87-h81d5506_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.16.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2024.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcap-2.71-h39aace5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.6.4.1-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.3.0.4-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufile-1.11.1.6-h12f29b5_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.7.77-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.1.2-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.4.2-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250127.1-cxx17_hbbce691_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_hfdb39a5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcap-2.75-h39aace5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_h372d94f_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.9.0.13-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcudss-0.5.0.16-h14340ca_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.4.0.6-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufile-1.14.0.30-h628e99a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.10.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.4.40-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.9.5-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-lib-1.11.0-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.51-hbd13f7d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.55-h3f2d84a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.2-default_h0d58e46_1001.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libmagma-2.8.0-h566cb83_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libmagma_sparse-2.8.0-h0af6554_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_hc41d3b0_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_hbc6e62b_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmagma-2.9.0-h19665d7_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnl-3.11.0-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.6.85-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.2-h5b01275_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-256.9-h0b6a36f_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.5.1-cuda126_hebb32c0_306.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libudev1-256.9-h9a4d06a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.9.41-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.29.3-h501fc15_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-257.4-h4e0b6ca_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.7.0-cuda126_mkl_h99b69db_300.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libudev1-257.4-hbe16f8c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.49.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.50.0-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.5-h0d44e9d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.8-h4bc477f_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-19.1.6-h024ca30_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-20.1.4-h024ca30_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2024.2.2-ha957f24_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-devel-2024.2.2-ha770c72_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-include-2024.2.2-ha957f24_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nccl-2.23.4.1-h2b5d15b_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nccl-2.26.5.1-ha44e49d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.4.2-pyh267e887_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.15.0-py312h68727a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda @@ -1365,57 +1377,49 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.5.1-cuda126_py312h1763f6d_306.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-gpu-2.5.1-cuda126ha999a5f_306.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/rdma-core-55.0-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.7.0-cuda126_mkl_py312_h30b5a27_300.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-gpu-2.7.0-cuda126_mkl_ha999a5f_300.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rdma-core-57.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.7-h1b44611_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.13.3-pyh2585a3b_105.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.8-h1b44611_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-hceb3a55_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/triton-3.3.0-cuda126py312hebffaa9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.13.2-h0e9735f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda packages: -- kind: conda - name: _libgcc_mutex - version: '0.1' - build: conda_forge - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 md5: d7c89558ba9fa0495403155b64376d81 license: None size: 2562 timestamp: 1578324546067 -- kind: conda - name: _openmp_mutex - version: '4.5' - build: 2_gnu +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 build_number: 16 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 sha256: fbe2c5e56a653bebb982eda4876a9178aedfc2b545f25d0ce9c4c0b508253d22 md5: 73aaf86a425cc6e73fcf236a5a46396d depends: @@ -1427,46 +1431,27 @@ packages: license_family: BSD size: 23621 timestamp: 1650670423406 -- kind: conda - name: _openmp_mutex - version: '4.5' - build: 2_kmp_llvm - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2 - sha256: 84a66275da3a66e3f3e70e9d8f10496d807d01a9e4ec16cd2274cc5e28c478fc - md5: 562b26ba2e19059551a811e72ab7f793 +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-3_kmp_llvm.conda + build_number: 3 + sha256: cec7343e76c9da6a42c7e7cba53391daa6b46155054ef61a5ef522ea27c5a058 + md5: ee5c2118262e30b972bc0b4db8ef0ba5 depends: - - _libgcc_mutex 0.1 conda_forge - llvm-openmp >=9.0.1 license: BSD-3-Clause license_family: BSD - size: 5744 - timestamp: 1650742457817 -- kind: conda - name: array-api-strict - version: '2.2' - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/array-api-strict-2.2-pyhd8ed1ab_1.conda - sha256: 79bf4d2b5f55c816f832cd7180e66ca527b55a8353a3014fe3084690a8c7f6aa - md5: 02e7a32986412d3aaf97095d17120757 + size: 7649 + timestamp: 1741390353130 +- conda: https://conda.anaconda.org/conda-forge/noarch/array-api-strict-2.3.1-pyhd8ed1ab_0.conda + sha256: fda42d9e952c4c39354e31d43f1b7e7708a2e66c386074cd995097fe98be9150 + md5: 11107d0aeb8c590a34fee0894909816b depends: - numpy - python >=3.9 license: BSD-3-Clause license_family: BSD - size: 53675 - timestamp: 1734907462139 -- kind: conda - name: attr - version: 2.5.1 - build: h166bdaf_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2 + size: 56647 + timestamp: 1742521671631 +- conda: https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2 sha256: 82c13b1772c21fc4a17441734de471d3aabf82b61db9b11f4a1bd04a9c4ac324 md5: d9c69a24ad678ffce24c6543a0176b00 depends: @@ -1475,29 +1460,16 @@ packages: license_family: GPL size: 71042 timestamp: 1660065501192 -- kind: conda - name: attrs - version: 24.3.0 - build: pyh71513ae_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda - sha256: 750186af694a7130eaf7119fbb56db0d2326d8995ad5b8eae23c622b85fea29a - md5: 356927ace43302bf6f5926e2a58dae6a +- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda + sha256: 99c53ffbcb5dc58084faf18587b215f9ac8ced36bbfb55fa807c00967e419019 + md5: a10d11958cadc13fdb43df75f8b1903f depends: - python >=3.9 license: MIT license_family: MIT - size: 56354 - timestamp: 1734348889193 -- kind: conda - name: beniget - version: 0.4.2.post1 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda + size: 57181 + timestamp: 1741918625732 +- conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda sha256: f1d0f6d3170524357cdc5c05594d3f10dde6308cd11b6d7a321221677223260e md5: a4c67d46a8662fc39609648dee4938c3 depends: @@ -1507,98 +1479,64 @@ packages: license_family: BSD size: 21702 timestamp: 1733845912915 -- kind: conda - name: binutils - version: '2.43' - build: h4852527_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - sha256: 92be0f8ccd501ceeb3c782e2182e6ea04dca46799038176de40a57bca45512c5 - md5: 348619f90eee04901f4a70615efff35b +- conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + sha256: 99a94eead18e7704225ac43682cce3f316fd33bc483749c093eaadef1d31de75 + md5: 29782348a527eda3ecfc673109d28e93 depends: - binutils_impl_linux-64 >=2.43,<2.44.0a0 license: GPL-3.0-only license_family: GPL - size: 33876 - timestamp: 1729655402186 -- kind: conda - name: binutils_impl_linux-64 - version: '2.43' - build: h4bf12b8_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - sha256: 267e78990247369b13234bda270f31beb56a600b4851a8244e31dd9ad85b3b17 - md5: cf0c5521ac2a20dfa6c662a4009eeef6 - depends: - - ld_impl_linux-64 2.43 h712a8e2_2 + size: 34646 + timestamp: 1740155498138 +- conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + sha256: 194d771be287dc973f6057c0747010ce28adf960f38d6e03ce3e828d7b74833e + md5: ef67db625ad0d2dce398837102f875ed + depends: + - ld_impl_linux-64 2.43 h712a8e2_4 - sysroot_linux-64 license: GPL-3.0-only license_family: GPL - size: 5682777 - timestamp: 1729655371045 -- kind: conda - name: binutils_linux-64 - version: '2.43' - build: h4852527_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - sha256: df52bd8b8b2a20a0c529d9ad08aaf66093ac318aa8a33d270f18274341a77062 - md5: 18aba879ddf1f8f28145ca6fcb873d8c - depends: - - binutils_impl_linux-64 2.43 h4bf12b8_2 + size: 6111717 + timestamp: 1740155471052 +- conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + sha256: fe662a038dc14334617940f42ede9ba26d4160771255057cb14fb1a81ee12ac1 + md5: c87e146f5b685672d4aa6b527c6d3b5e + depends: + - binutils_impl_linux-64 2.43 h4bf12b8_4 license: GPL-3.0-only license_family: GPL - size: 34945 - timestamp: 1729655404893 -- kind: conda - name: blas-devel - version: 3.9.0 - build: 26_linux64_mkl - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_mkl.conda - sha256: 24d5023acabe1e20fcaa00ff59fbddc944722f33e690068e4a8f3e8f25e63d68 - md5: 261acc954f47b7bf11d841ad8dd91d08 - depends: - - libblas 3.9.0 26_linux64_mkl - - libcblas 3.9.0 26_linux64_mkl - - liblapack 3.9.0 26_linux64_mkl - - liblapacke 3.9.0 26_linux64_mkl + size: 35657 + timestamp: 1740155500723 +- conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_h1ea3ea9_openblas.conda + build_number: 31 + sha256: afb47fb55992d68c216dfc11436db48feb7df40da469ecb92ce9fc3aa8bc1c55 + md5: ba652ee0576396d4765e567f043c57f9 + depends: + - libblas 3.9.0 31_h59b9bed_openblas + - libcblas 3.9.0 31_he106b2a_openblas + - liblapack 3.9.0 31_h7ac8fdf_openblas + - liblapacke 3.9.0 31_he2f377e_openblas + - openblas 0.3.29.* + license: BSD-3-Clause + license_family: BSD + size: 16818 + timestamp: 1740088024940 +- conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_hcf00494_mkl.conda + build_number: 31 + sha256: 4639197a36d61f0a252ffaf8432fa13cb1809a0a34146e44b5ef1ea9c91553a5 + md5: 368c93bde87a67d24a74de15bf4c49fd + depends: + - libblas 3.9.0 31_hfdb39a5_mkl + - libcblas 3.9.0 31_h372d94f_mkl + - liblapack 3.9.0 31_hc41d3b0_mkl + - liblapacke 3.9.0 31_hbc6e62b_mkl - mkl >=2024.2.2,<2025.0a0 - mkl-devel 2024.2.* license: BSD-3-Clause license_family: BSD - size: 16148 - timestamp: 1734432567576 -- kind: conda - name: blas-devel - version: 3.9.0 - build: 26_linux64_openblas - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_openblas.conda - sha256: 500680680cbe1eb4983e871ecd5b11b08dad1a7540252941711c754d8affe530 - md5: da61c3ef2fbe100b0613cbc2b01b502d - depends: - - libblas 3.9.0 26_linux64_openblas - - libcblas 3.9.0 26_linux64_openblas - - liblapack 3.9.0 26_linux64_openblas - - liblapacke 3.9.0 26_linux64_openblas - - openblas 0.3.28.* - license: BSD-3-Clause - license_family: BSD - size: 16274 - timestamp: 1734432589013 -- kind: conda - name: brotli-python - version: 1.1.0 - build: py312h2ec8cdc_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda + size: 16624 + timestamp: 1740087754 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda sha256: f2a59ccd20b4816dea9a2a5cb917eb69728271dbf1aeab4e1b7e609330a50b6f md5: b0b867af6fc74b2a0aa206da29c0f3cf depends: @@ -1613,13 +1551,7 @@ packages: license_family: MIT size: 349867 timestamp: 1725267732089 -- kind: conda - name: bzip2 - version: 1.0.8 - build: h4bc722e_7 - build_number: 7 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda sha256: 5ced96500d945fb286c9c838e54fa759aa04a7129c59800f0846b4335cee770d md5: 62ee74e96c5ebb0af99386de58cf9553 depends: @@ -1629,87 +1561,58 @@ packages: license_family: BSD size: 252783 timestamp: 1720974456583 -- kind: conda - name: c-ares - version: 1.34.4 - build: hb9d3cd8_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.4-hb9d3cd8_0.conda - sha256: d4f28d87b6339b94f74762c0076e29c8ef8ddfff51a564a92da2843573c18320 - md5: e2775acf57efd5af15b8e3d1d74d72d3 +- conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda + sha256: f8003bef369f57396593ccd03d08a8e21966157269426f71e943f96e4b579aeb + md5: f7f0d6cc2dc986d42ac2689ec88192be depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 license: MIT license_family: MIT - size: 206085 - timestamp: 1734208189009 -- kind: conda - name: c-compiler - version: 1.8.0 - build: h2b85faf_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - sha256: 009fced27be14e5ac750a04111a07eda79d73f80009300c1538cb83d5da71879 - md5: fa7b3bf2965b9d74a81a0702d9bb49ee + size: 206884 + timestamp: 1744127994291 +- conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + sha256: 1e4b86b0f3d4ce9f3787b8f62e9f2c5683287f19593131640eed01cbdad38168 + md5: 3cb814f83f1f71ac1985013697f80cc1 depends: - binutils - gcc - gcc_linux-64 13.* license: BSD-3-Clause license_family: BSD - size: 6085 - timestamp: 1728985300402 -- kind: conda - name: ca-certificates - version: 2024.12.14 - build: hbcca054_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - sha256: 1afd7274cbc9a334d6d0bc62fa760acc7afdaceb0b91a8df370ec01fd75dc7dd - md5: 720523eb0d6a9b0f6120c16b2aa4e7de + size: 6196 + timestamp: 1736437002021 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + sha256: 2a70ed95ace8a3f8a29e6cd1476a943df294a7111dfb3e152e3478c4c889b7ac + md5: 95db94f75ba080a22eb623590993167b + depends: + - __unix license: ISC - size: 157088 - timestamp: 1734208393264 -- kind: conda - name: ccache - version: 4.10.1 - build: h065aff2_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - sha256: 8ca3531bde782746a388f2e6193c090fa6e4afcdf2054f595e33937148560d85 - md5: d6b48c138e0c8170a6fe9c136e063540 + size: 152283 + timestamp: 1745653616541 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + sha256: ac9464a60a7b085b5a999aaf33d882705390d7749b35e320f639614ae0cc9474 + md5: eb517c6a2b960c3ccb6f1db1005f063a depends: + - libgcc >=13 + - libstdcxx >=13 + - libgcc >=13 - __glibc >=2.17,<3.0.a0 - - libgcc-ng >=12 + - zstd >=1.5.7,<1.6.0a0 - libhiredis >=1.0.2,<1.1.0a0 - - libstdcxx-ng >=12 - - zstd >=1.5.6,<1.6.0a0 license: GPL-3.0-only license_family: GPL - size: 627561 - timestamp: 1719847277140 -- kind: conda - name: certifi - version: 2024.12.14 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda - sha256: 048c16a9cbcb1fbad02083414d3bc7c1d0eea4b39aee6aa6bf8d1d5089ca8bad - md5: 6feb87357ecd66733be3279f16a8c400 + size: 708908 + timestamp: 1746271484780 +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda + sha256: 52aa837642fd851b3f7ad3b1f66afc5366d133c1d452323f786b0378a391915c + md5: c33eeaaa33f45031be34cda513df39b6 depends: - python >=3.9 license: ISC - size: 161642 - timestamp: 1734380604767 -- kind: conda - name: cffi - version: 1.17.1 - build: py312h06ac9bb_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda + size: 157200 + timestamp: 1746569627830 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda sha256: cba6ea83c4b0b4f5b5dc59cb19830519b28f95d7ebef7c9c5cf1c14843621457 md5: a861504bbea4161a9170b85d4d2be840 depends: @@ -1723,28 +1626,16 @@ packages: license_family: MIT size: 294403 timestamp: 1725560714366 -- kind: conda - name: charset-normalizer - version: 3.4.1 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda - sha256: 4e0ee91b97e5de3e74567bdacea27f0139709fceca4db8adffbe24deffccb09b - md5: e83a31202d1c0a000fce3e9cf3825875 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda + sha256: 535ae5dcda8022e31c6dc063eb344c80804c537a5a04afba43a845fa6fa130f5 + md5: 40fe4284b8b5835a9073a645139f35af depends: - python >=3.9 license: MIT license_family: MIT - size: 47438 - timestamp: 1735929811779 -- kind: conda - name: click - version: 8.1.8 - build: pyh707e725_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda + size: 50481 + timestamp: 1746214981991 +- conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda sha256: c920d23cd1fcf565031c679adb62d848af60d6fbb0edc2d50ba475cea4f0d8ab md5: f22f4d4970e09d68a10b922cbb0408d3 depends: @@ -1754,30 +1645,16 @@ packages: license_family: BSD size: 84705 timestamp: 1734858922844 -- kind: conda - name: cloudpickle - version: 3.1.0 - build: pyhd8ed1ab_2 - build_number: 2 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda - sha256: 918151ad25558a37721055a02c0357ce9a2f51f07da1b238608e48ef17d35260 - md5: 1f76b7e2b3ab88def5aa2f158322c7e6 +- conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda + sha256: 21ecead7268241007bf65691610cd7314da68c1f88113092af690203b5780db5 + md5: 364ba6c9fb03886ac979b482f39ebb92 depends: - python >=3.9 license: BSD-3-Clause license_family: BSD - size: 25975 - timestamp: 1735328713686 -- kind: conda - name: colorama - version: 0.4.6 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + size: 25870 + timestamp: 1736947650712 +- conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 md5: 962b9857ee8e7018c22f2776ffa0b2d7 depends: @@ -1786,14 +1663,7 @@ packages: license_family: BSD size: 27011 timestamp: 1733218222191 -- kind: conda - name: colorlog - version: 6.9.0 - build: pyh707e725_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda sha256: 9a0dc9a0611d3ad33846a52b913346a5ca5cd9f0aa67a53fd89386652d07874b md5: f00fc375bd02bdbbf791f9fe26ae96ec depends: @@ -1803,295 +1673,217 @@ packages: license_family: MIT size: 15522 timestamp: 1733258500721 -- kind: conda - name: compilers - version: 1.8.0 - build: ha770c72_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - sha256: d2fa2f8cb3df79f543758c8e288f1a74a2acca57245f1e03919bffa3e40aad2d - md5: 061e111d02f33a99548f0de07169d9fb - depends: - - c-compiler 1.8.0 h2b85faf_1 - - cxx-compiler 1.8.0 h1a2810e_1 - - fortran-compiler 1.8.0 h36df796_1 +- conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + sha256: 97d90aeba05089bbf3124030ebd96890754b8c8dc2c880490d38a3075941de28 + md5: 5859096e397aba423340d0bbbb11ec64 + depends: + - c-compiler 1.9.0 h2b85faf_0 + - cxx-compiler 1.9.0 h1a2810e_0 + - fortran-compiler 1.9.0 h36df796_0 license: BSD-3-Clause license_family: BSD - size: 6932 - timestamp: 1728985303287 -- kind: conda - name: cpython - version: 3.12.8 - build: py312hd8ed1ab_1 - build_number: 1 - subdir: noarch + size: 7014 + timestamp: 1736437002774 +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.10-py312hd8ed1ab_0.conda noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.8-py312hd8ed1ab_1.conda - sha256: 05413d84485086301e5bd7c03fca2caae91f75474d99d9fc815cec912332452b - md5: caa04d37126e82822468d6bdf50f5ebd + sha256: acb47715abf1cd8177a5c20f42a34555b5d9cebb68ff39a58706e84effe218e2 + md5: 7584a4b1e802afa25c89c0dcc72d0826 depends: - - python 3.12.8.* + - python >=3.12,<3.13.0a0 - python_abi * *_cp312 license: Python-2.0 - size: 44751 - timestamp: 1733407917248 -- kind: conda - name: cuda-cccl_linux-64 - version: 12.6.77 - build: ha770c72_0 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.6.77-ha770c72_0.conda - sha256: 00a7de1d084896758dc2d24b1faf4bf59e596790b22a3a08bf163a810bbacde8 - md5: 365a924cf93535157d61debac807e9e4 + size: 45861 + timestamp: 1744323195619 +- conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.9.27-ha770c72_0.conda + sha256: 2ee3b9564ca326226e5cda41d11b251482df8e7c757e333d28ec75213c75d126 + md5: 87ff6381e33b76e5b9b179a2cdd005ec depends: - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 1067930 - timestamp: 1727807050610 -- kind: conda - name: cuda-crt-dev_linux-64 - version: 12.6.85 - build: ha770c72_0 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/cuda-crt-dev_linux-64-12.6.85-ha770c72_0.conda - sha256: 2515c1bddde769ad8628411e08deb31a7eafe6ace9e46bea33a3a99fbb95aea0 - md5: 4b14e78e12daa061dcdbe3ceed95cb57 + size: 1150650 + timestamp: 1746189825236 +- conda: https://conda.anaconda.org/conda-forge/noarch/cuda-crt-dev_linux-64-12.9.41-ha770c72_0.conda + sha256: 54e00942d92e21c35adcd2c55af7987719a48b01975abcefe0f936f3e2995e17 + md5: 1b8184d441b383f0b1cf36005598fc05 depends: - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 88743 - timestamp: 1732132177211 -- kind: conda - name: cuda-crt-tools - version: 12.6.85 - build: ha770c72_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cuda-crt-tools-12.6.85-ha770c72_0.conda - sha256: 83b6f3332a17bc891f2ecdc9b1424658009e37e14e888d0bd0458b6aa4db59a2 - md5: 4ab193b5fcdcf8d7b094221e3977a112 - depends: - - cuda-version >=12.6,<12.7.0a0 + size: 93781 + timestamp: 1746198278062 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-crt-tools-12.9.41-ha770c72_0.conda + sha256: e291e3468396ab2dc9fc17607754fed19eac6cdcb3a5f30cf9063c18916ec491 + md5: 452ec0ccbf67954a0a03c4ec0b1fa7a5 + depends: + - cuda-version >=12.9,<12.10.0a0 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 27135 - timestamp: 1732132181193 -- kind: conda - name: cuda-cudart - version: 12.6.77 - build: h5888daf_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-12.6.77-h5888daf_0.conda - sha256: e7a256a61d5b8c9d7d31932b5f4f35a8fda5a18c789cb971d98dca266fdd8792 - md5: feb533cb1e5f7ffbbb82d8465e0adaad + size: 28214 + timestamp: 1746198287537 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-12.9.37-h5888daf_0.conda + sha256: 5bf59a9cb7d581339daa291e2cb8d541a6c2bf264ae71dc516fa38720bc11ab4 + md5: d874c87fba16e4ddf005f7e191da0775 depends: - __glibc >=2.17,<3.0.a0 - - cuda-cudart_linux-64 12.6.77 h3f2d84a_0 - - cuda-version >=12.6,<12.7.0a0 + - cuda-cudart_linux-64 12.9.37 h3f2d84a_0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 22397 - timestamp: 1727810461651 -- kind: conda - name: cuda-cudart-dev_linux-64 - version: 12.6.77 - build: h3f2d84a_0 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-dev_linux-64-12.6.77-h3f2d84a_0.conda - sha256: 60847bd8c74b02ca17d68d742fe545db84a18bf808344eb99929f32f79bffcf9 - md5: f967e2449b6c066f6d09497fff12d803 + size: 23165 + timestamp: 1746194366557 +- conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-dev_linux-64-12.9.37-h3f2d84a_0.conda + sha256: 369bf15b6ab428279620fa9a806db6e6adb7987c6137654054b07a192b8a8252 + md5: 9ae200ef917b953d39c60d45ba78bebb depends: - cuda-cccl_linux-64 - cuda-cudart-static_linux-64 - cuda-cudart_linux-64 - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 365370 - timestamp: 1727810466552 -- kind: conda - name: cuda-cudart-static_linux-64 - version: 12.6.77 - build: h3f2d84a_0 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-static_linux-64-12.6.77-h3f2d84a_0.conda - sha256: aefed29499bdbe5d0c65ca44ef596929cf34cc3014f0ae225cdd45a0e66f2660 - md5: 3ad8eacbf716ddbca1b5292a3668c821 + size: 388621 + timestamp: 1746194374721 +- conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-static_linux-64-12.9.37-h3f2d84a_0.conda + sha256: 47f9c7f8c946b9e6e2c7c616d9c59acf59ea96cf64f1e0a5c090f63b456ab1fc + md5: bc0e5f61bfea338148d265fe9bbbacae depends: - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 762328 - timestamp: 1727810443982 -- kind: conda - name: cuda-cudart_linux-64 - version: 12.6.77 - build: h3f2d84a_0 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.6.77-h3f2d84a_0.conda - sha256: cf8433afa236108dba2a94ea5d4f605c50f0e297ee54eb6cb37175fd84ced907 - md5: 314908ad05e2c4833475a7d93f4149ca + size: 1148263 + timestamp: 1746194340428 +- conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.9.37-h3f2d84a_0.conda + sha256: 5d3da5b258785cb7aa593363518d11e7b5580373d612faba43a72c9c9db941f9 + md5: 05c9f71dede6cfae29dfc1141128e717 depends: - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 188616 - timestamp: 1727810451690 -- kind: conda - name: cuda-cupti - version: 12.6.80 - build: hbd13f7d_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-12.6.80-hbd13f7d_0.conda - sha256: 41cef2d389f5e467de25446aa0d856d9f3bb358d9671db3d4a06ecdb5802a317 - md5: 85e9354a9e32f7526d2451ed2bb93347 + size: 197833 + timestamp: 1746194349673 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cuobjdump-12.9.26-hbd13f7d_0.conda + sha256: 873d7f722904b104cbc31402380c0749cecf83e8ee270e4277e97975c4170793 + md5: 9f83ac9b3dcc0401bb19a546af50bd47 depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 + - cuda-nvdisasm + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 1999085 - timestamp: 1727807734169 -- kind: conda - name: cuda-cupti-dev - version: 12.6.80 - build: h5888daf_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-dev-12.6.80-h5888daf_0.conda - sha256: f06ea656216d331c333889f1c020b385ada748f2dd5b0a36326cc8935a7b8d8c - md5: ed37a8cad974fed39334d096f3b18d81 + size: 244544 + timestamp: 1746193903455 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-12.9.19-h9ab20c4_0.conda + sha256: 19ca76b00200608775c97579ac0be54e767a86dd6b614d0b001d1bad8007f1fb + md5: 2ccc05e957d8f6a9e3d5d35b0847f0b2 depends: - - __glibc >=2.17,<3.0.a0 - - cuda-cupti 12.6.80 hbd13f7d_0 - - cuda-version >=12.6,<12.7.0a0 + - __glibc >=2.28,<3.0.a0 + - cuda-version >=12.9,<12.10.0a0 + - libgcc >=13 + - libstdcxx >=13 + license: LicenseRef-NVIDIA-End-User-License-Agreement + size: 1844732 + timestamp: 1746192697291 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-dev-12.9.19-h9ab20c4_0.conda + sha256: 611ec4743bfc27cf21d5529611a384a6621a9600a8d036299fab198625465b51 + md5: 359a97d37351c1f1795155508a5337fc + depends: + - __glibc >=2.28,<3.0.a0 + - cuda-cupti 12.9.19 h9ab20c4_0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 constrains: - - cuda-cupti-static >=12.6.80 + - cuda-cupti-static >=12.9.19 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 3533128 - timestamp: 1727807797633 -- kind: conda - name: cuda-nvcc-tools - version: 12.6.85 - build: he02047a_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvcc-tools-12.6.85-he02047a_0.conda - sha256: 0f8cc474130f9654cacc6e5ff4b62b731da28019c5e28ca318a3e38a84e3b1a8 - md5: 30b272fa555944cb44f8d4dc9244abb5 + size: 4614575 + timestamp: 1746192761574 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvcc-tools-12.9.41-he02047a_0.conda + sha256: e8784400792235d24e1e743a2678885ca631ec81dbf392a7c56511abe5efceec + md5: a53cbad5c98447d550b1740d0001cdc4 depends: - __glibc >=2.17,<3.0.a0 - - cuda-crt-tools 12.6.85 ha770c72_0 - - cuda-nvvm-tools 12.6.85 he02047a_0 - - cuda-version >=12.6,<12.7.0a0 + - cuda-crt-tools 12.9.41 ha770c72_0 + - cuda-nvvm-tools 12.9.41 he02047a_0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=12 - libstdcxx >=12 constrains: - - gcc_impl_linux-64 >=6,<14.0a0 + - gcc_impl_linux-64 >=6,<15.0a0 + license: LicenseRef-NVIDIA-End-User-License-Agreement + size: 27425139 + timestamp: 1746198424385 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvdisasm-12.9.19-hbd13f7d_0.conda + sha256: d3846331680396c3adf9adee7f0db9fbfb39b20c06c4235fc687489cced8b9b7 + md5: 8138274dcbaab5489b3e43b33d7825e9 + depends: + - __glibc >=2.17,<3.0.a0 + - cuda-version >=12.9,<12.10.0a0 + - libgcc >=13 + - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 24082529 - timestamp: 1732132231855 -- kind: conda - name: cuda-nvrtc - version: 12.6.85 - build: hbd13f7d_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.6.85-hbd13f7d_0.conda - sha256: 3ddec2c3b68cea5edba728ffc61a2257300d401d428b9d60aca7363c0c0d4ad5 - md5: 9d9909844a0133153d54b6f07283da8c + size: 5517513 + timestamp: 1746189877059 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.9.41-h5888daf_0.conda + sha256: 67d17fe3ca19ad30d3f5c885da1b509c2372ba865e6ace4074ddd3a4d89ff525 + md5: 57ea71a617e163f0b36512a5c9edd0bc depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 18138390 - timestamp: 1732133174552 -- kind: conda - name: cuda-nvtx - version: 12.6.77 - build: hbd13f7d_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvtx-12.6.77-hbd13f7d_0.conda - sha256: 98bdf2e5017069691e8b807e0ceba4327d427b57147249ca0a505b8ad6844148 - md5: 3fe3afe309918465f82f984b3a1a85e9 + size: 67173643 + timestamp: 1746190515836 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvtx-12.9.19-h5888daf_0.conda + sha256: cccfc520ef222303de0fc94dd951b6c356d25f46eee450b17d853078afb6956c + md5: eeba52bd19d561f6b0be3bfcf4e292af depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 31364 - timestamp: 1727816542389 -- kind: conda - name: cuda-nvvm-tools - version: 12.6.85 - build: he02047a_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-12.6.85-he02047a_0.conda - sha256: 5c7ab2b1367cefaa15a8d8880e9985ed2753a990765d047df23fa8ddb2ba9e7a - md5: 0919bdf9454da5eb974e98dd79bf38fe + size: 29222 + timestamp: 1746195676216 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-12.9.41-he02047a_0.conda + sha256: c0da297dc963cd4d1d333815189c4a60360a7bcb8d3905fb37c208326bda1dc4 + md5: e3310ca76e355bdb2b9589edc8fd6083 depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=12 - libstdcxx >=12 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 10880815 - timestamp: 1732132210850 -- kind: conda - name: cuda-version - version: '12.6' - build: h7480c83_3 - build_number: 3 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.6-h7480c83_3.conda - sha256: fd9104d73199040285b6a6ad56322b38af04828fabbac1f5a268a83509358425 - md5: 1c8b99e65a4423b1e4ac2e4c76fb0978 + size: 24248207 + timestamp: 1746198369570 +- conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.9-h4f385c5_3.conda + sha256: 5f5f428031933f117ff9f7fcc650e6ea1b3fef5936cf84aa24af79167513b656 + md5: b6d5d7f1c171cbd228ea06b556cfa859 constrains: - - cudatoolkit 12.6|12.6.* + - cudatoolkit 12.9|12.9.* - __cuda >=12 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 20940 - timestamp: 1722603990914 -- kind: conda - name: cudnn - version: 9.3.0.75 - build: h62a6f1c_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cudnn-9.3.0.75-h62a6f1c_2.conda - sha256: e723324f64a9e3b10c91893aa1594e94427f54d2489ff0edf3b9296b5d6c5733 - md5: eca29a76544ab11bb6d78e4d836df7b4 + size: 21578 + timestamp: 1746134436166 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cudnn-9.8.0.87-h81d5506_1.conda + sha256: 88fd0bd4ad77f126d8b4d89a9d1a661f8be322c8a1ae9da28a89fb7373b5d4ca + md5: c87536f2e5d0740f4193625eb00fab7e depends: - __glibc >=2.28,<3.0.a0 - cuda-nvrtc - cuda-version >=12,<13.0a0 - libcublas - - libgcc >=12 - - libstdcxx >=12 + - libgcc >=13 + - libstdcxx >=13 - libzlib >=1.3.1,<2.0a0 license: LicenseRef-cuDNN-Software-License-Agreement - size: 401805073 - timestamp: 1735784276169 -- kind: conda - name: cupy - version: 13.3.0 - build: py312h7d319b9_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cupy-13.3.0-py312h7d319b9_2.conda - sha256: 9e7a612a4b7f1bf58176816e50e30d3112724d318d884f3453c0edb44b4570ce - md5: 009ef049020fef7d1541183d52fab5a9 + size: 490227234 + timestamp: 1743628408368 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-13.4.1-py312h78400a1_0.conda + sha256: 4b5a53348865f90c743b68131ced2a361274fdeafc2e38d28ffd13c39dd9f907 + md5: 7e38588cf4ce6207bccd51dbfe647024 depends: - cuda-cudart-dev_linux-64 - cuda-nvrtc - - cuda-version >=12.0,<13.0a0 - - cupy-core 13.3.0 py312h1acd1a8_2 + - cuda-version >=12,<13.0a0 + - cupy-core 13.4.1 py312h007fbcc_0 - libcublas - libcufft - libcurand @@ -2101,69 +1893,51 @@ packages: - python_abi 3.12.* *_cp312 license: MIT license_family: MIT - size: 355525 - timestamp: 1729280147659 -- kind: conda - name: cupy-core - version: 13.3.0 - build: py312h1acd1a8_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cupy-core-13.3.0-py312h1acd1a8_2.conda - sha256: 7a7354a58863bef6bb11f77de42620f5b0965a0d11576fe0673f6b02dc034b6d - md5: 15e9530e87664584a6b409ecdf5c9264 + size: 357651 + timestamp: 1742853581416 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-core-13.4.1-py312h007fbcc_0.conda + sha256: 7f2e3de74eb716156f35c74f7f380d23d9218f1f0b100b6d957847155bf7ad2a + md5: 724129d9d097c42880f3e3b8f2cfbebb depends: - __glibc >=2.17,<3.0.a0 - - fastrlock >=0.8.2,<0.9.0a0 - - libgcc >=12 - - libstdcxx >=12 + - fastrlock >=0.8.3,<0.9.0a0 + - libgcc >=13 + - libstdcxx >=13 - numpy >=1.22,<3.0.0a0 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 constrains: - - scipy ~=1.7 - - optuna ~=3.0 - - __cuda >=12.0 - - cutensor >=2.0.2.5,<3.0a0 - - libcurand >=10,<11.0a0 - - cuda-version >=12.0,<13 - - cupy >=13.3.0,<13.4.0a0 - cuda-nvrtc >=12,<13.0a0 - - nccl >=2.23.4.1,<3.0a0 - - libcublas >=12,<13.0a0 - - libcusparse >=12,<13.0a0 - libcufft >=11,<12.0a0 + - __cuda >=12.0 - libcusolver >=11,<12.0a0 + - libcusparse >=12,<13.0a0 + - cutensor >=2.2.0.0,<3.0a0 + - libcublas >=12,<13.0a0 + - optuna ~=3.0 + - scipy ~=1.7 + - cuda-version >=12,<13.0a0 + - nccl >=2.26.2.1,<3.0a0 + - libcurand >=10,<11.0a0 + - cupy >=13.4.1,<13.5.0a0 license: MIT license_family: MIT - size: 41249386 - timestamp: 1729280040168 -- kind: conda - name: cxx-compiler - version: 1.8.0 - build: h1a2810e_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - sha256: cca0450bbc0d19044107d0f90fa36126a11b007fbfb62bd2a1949b2bb59a21a4 - md5: 3bb4907086d7187bf01c8bec397ffa5e - depends: - - c-compiler 1.8.0 h2b85faf_1 + size: 49529735 + timestamp: 1742853454294 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + sha256: 5efc51b8e7d87fc5380f00ace9f9c758142eade520a63d3631d2616d1c1b25f9 + md5: 1ce8b218d359d9ed0ab481f2a3f3c512 + depends: + - c-compiler 1.9.0 h2b85faf_0 - gxx - gxx_linux-64 13.* license: BSD-3-Clause license_family: BSD - size: 6059 - timestamp: 1728985302835 -- kind: conda - name: cython - version: 3.0.11 - build: py312h8fd2918_3 - build_number: 3 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - sha256: 7a888ddda463a3146949540229c70625fbefb05bcb1352cbff990f205b8392b0 - md5: 21e433caf1bb1e4c95832f8bb731d64c + size: 6168 + timestamp: 1736437002465 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + sha256: de815476da537b911e2ceeb7f76b445d0c76b3d5fad35600ed28bc8d19302127 + md5: e5d2a28866ee990a340bde1eabde587a depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 @@ -2172,32 +1946,18 @@ packages: - python_abi 3.12.* *_cp312 license: Apache-2.0 license_family: APACHE - size: 3752086 - timestamp: 1727456382070 -- kind: conda - name: decorator - version: 5.1.1 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda - sha256: 84e5120c97502a3785e8c3241c3bf51f64b4d445f13b4d2445db00d9816fe479 - md5: d622d8d7ee8868870f9cbe259f381181 + size: 3766553 + timestamp: 1739228870146 +- conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + sha256: c17c6b9937c08ad63cb20a26f403a3234088e57d4455600974a0ce865cb14017 + md5: 9ce473d1d1be1cc3810856a48b3fab32 depends: - python >=3.9 license: BSD-2-Clause license_family: BSD - size: 14068 - timestamp: 1733236549190 -- kind: conda - name: doit - version: 0.36.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda + size: 14129 + timestamp: 1740385067843 +- conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda sha256: 8925dc378a2d5533905b478c69fd7ea7c72c664aa4b37075a2711079bc9222e3 md5: 18d4243b3d30352f9dea8e522f6ff4d1 depends: @@ -2208,14 +1968,7 @@ packages: license_family: MIT size: 73790 timestamp: 1734618648157 -- kind: conda - name: exceptiongroup - version: 1.2.2 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda sha256: cbde2c64ec317118fc06b223c5fd87c8a680255e7348dd60e7b292d2e103e701 md5: a16662747cdeb9abbac74d0057cc976e depends: @@ -2223,14 +1976,7 @@ packages: license: MIT and PSF-2.0 size: 20486 timestamp: 1733208916977 -- kind: conda - name: execnet - version: 2.1.1 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda sha256: 9abc6c128cd40733e9b24284d0462e084d4aff6afe614f0754aa8533ebe505e4 md5: a71efeae2c160f6789900ba2631a2c90 depends: @@ -2239,13 +1985,7 @@ packages: license_family: MIT size: 38835 timestamp: 1733231086305 -- kind: conda - name: fastrlock - version: 0.8.3 - build: py312h6edf5ed_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/fastrlock-0.8.3-py312h6edf5ed_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/fastrlock-0.8.3-py312h6edf5ed_1.conda sha256: 260589d271cfdd4bf04d084084123be3e49e9017da159f27bea5dc8617eaada6 md5: 2e401040f77cf54d8d5e1f0417dcf0b2 depends: @@ -2259,61 +1999,36 @@ packages: license_family: MIT size: 41705 timestamp: 1734873425804 -- kind: conda - name: filelock - version: 3.16.1 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/filelock-3.16.1-pyhd8ed1ab_1.conda - sha256: 18dca6e2194732df7ebf824abaefe999e4765ebe8e8a061269406ab88fc418b9 - md5: d692e9ba6f92dc51484bf3477e36ce7c +- conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda + sha256: de7b6d4c4f865609ae88db6fa03c8b7544c2452a1aa5451eb7700aad16824570 + md5: 4547b39256e296bb758166893e909a7c depends: - python >=3.9 license: Unlicense - size: 17441 - timestamp: 1733240909987 -- kind: conda - name: fortran-compiler - version: 1.8.0 - build: h36df796_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda - sha256: a713ede383b34fb46e73e00fc6b556a7446eae43f9d312c104678658ea463ea4 - md5: 6b57750841d53ade8d3b47eafe53dd9f + size: 17887 + timestamp: 1741969612334 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda + sha256: ca857e7b91eee0d33aa3f6cdebac36a8699ab3f37efbb717df409ae9b8decb34 + md5: cc0cf942201f9d3b0e9654ea02e12486 depends: - binutils - - c-compiler 1.8.0 h2b85faf_1 + - c-compiler 1.9.0 h2b85faf_0 - gfortran - gfortran_linux-64 13.* license: BSD-3-Clause license_family: BSD - size: 6095 - timestamp: 1728985303064 -- kind: conda - name: fsspec - version: 2024.12.0 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/fsspec-2024.12.0-pyhd8ed1ab_0.conda - sha256: 3320970c4604989eadf908397a9475f9e6a96a773c185915111399cbfbe47817 - md5: e041ad4c43ab5e10c74587f95378ebc7 + size: 6184 + timestamp: 1736437002625 +- conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.3.2-pyhd8ed1ab_0.conda + sha256: 2040d4640708bd6ab9ed6cb9901267441798c44974bc63c9b6c1cb4c1891d825 + md5: 9c40692c3d24c7aaf335f673ac09d308 depends: - python >=3.9 license: BSD-3-Clause license_family: BSD - size: 137756 - timestamp: 1734650349242 -- kind: conda - name: gast - version: 0.5.5 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda + size: 142117 + timestamp: 1743437355974 +- conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda sha256: b0527039bb19aeb5636ecb1512378e4109b945bc99f409977bda3022485c526f md5: ebc1dc871c48673a0a922023a2e1eee2 depends: @@ -2324,85 +2039,55 @@ packages: license_family: BSD size: 24016 timestamp: 1719403213917 -- kind: conda - name: gcc - version: 13.3.0 - build: h9576a4e_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - sha256: d0161362430183cbdbc3db9cf95f9a1af1793027f3ab8755b3d3586deb28bf84 - md5: 606924335b5bcdf90e9aed9a2f5d22ed +- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + sha256: 300f077029e7626d69cc250a69acd6018c1fced3f5bf76adf37854f3370d2c45 + md5: d92e51bf4b6bdbfe45e5884fb0755afe depends: - gcc_impl_linux-64 13.3.0.* license: BSD-3-Clause license_family: BSD - size: 53864 - timestamp: 1724801360210 -- kind: conda - name: gcc_impl_linux-64 - version: 13.3.0 - build: hfea6d02_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - sha256: 998ade1d487e93fc8a7a16b90e2af69ebb227355bf4646488661f7ae5887873c - md5: 0d043dbc126b64f79d915a0e96d3a1d5 + size: 55246 + timestamp: 1740240578937 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + sha256: c3e9f243ea8292eecad78bb200d8f5b590e0f82bf7e7452a3a7c8df4eea6f774 + md5: f46cf0acdcb6019397d37df1e407ab91 depends: - binutils_impl_linux-64 >=2.40 - libgcc >=13.3.0 - - libgcc-devel_linux-64 13.3.0 h84ea5a7_101 + - libgcc-devel_linux-64 13.3.0 hc03c837_102 - libgomp >=13.3.0 - - libsanitizer 13.3.0 heb74ff8_1 + - libsanitizer 13.3.0 he8ea267_2 - libstdcxx >=13.3.0 - sysroot_linux-64 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL - size: 67464415 - timestamp: 1724801227937 -- kind: conda - name: gcc_linux-64 - version: 13.3.0 - build: hc28eda2_7 - build_number: 7 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - sha256: 1e5ac50580a68fdc7d2f5722abcf1a87898c24b1ab6eb5ecd322634742d93645 - md5: ac23afbf5805389eb771e2ad3b476f75 + size: 66770653 + timestamp: 1740240400031 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + sha256: 2526f358e0abab84d6f93b2bae932e32712025a3547400393a1cfa6240257323 + md5: d151142bbafe5e68ec7fc065c5e6f80c depends: - binutils_linux-64 - gcc_impl_linux-64 13.3.0.* - sysroot_linux-64 license: BSD-3-Clause license_family: BSD - size: 32005 - timestamp: 1731939593317 -- kind: conda - name: gfortran - version: 13.3.0 - build: h9576a4e_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - sha256: fc711e4a5803c4052b3b9d29788f5256f5565f4609f7688268e89cbdae969f9b - md5: 5e5e3b592d5174eb49607a973c77825b + size: 32570 + timestamp: 1745040775220 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + sha256: 9425741d57bbcf918fe98cb375508f31de0daa655f3cebe100a43cf7cb46ec39 + md5: 19e6d3c9cde10a0a9a170a684082588e depends: - gcc 13.3.0.* - gcc_impl_linux-64 13.3.0.* - gfortran_impl_linux-64 13.3.0.* license: BSD-3-Clause license_family: BSD - size: 53341 - timestamp: 1724801488689 -- kind: conda - name: gfortran_impl_linux-64 - version: 13.3.0 - build: h10434e7_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - sha256: 9439e1f01d328d4cbdfbb2c8579b83619a694ad114ddf671fb9971ebf088d267 - md5: 6709e113709b6ba67cc0f4b0de58ef7f + size: 54740 + timestamp: 1740240701423 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + sha256: 03a45f58b41909d4189fb81ec7f971b60aaccf95a3953049d93ae7d06eb19000 + md5: 4e21ed177b76537067736f20f54fee0a depends: - gcc_impl_linux-64 >=13.3.0 - libgcc >=13.3.0 @@ -2411,33 +2096,21 @@ packages: - sysroot_linux-64 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL - size: 15894110 - timestamp: 1724801415339 -- kind: conda - name: gfortran_linux-64 - version: 13.3.0 - build: hb919d3a_7 - build_number: 7 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda - sha256: 73ba4c14b6b372385b0cb8e06c45a7df5ffc0ca688bd10180c0a3459ab71390d - md5: 0b8e7413559c4c892a37c35de4559969 + size: 15923784 + timestamp: 1740240635243 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda + sha256: e94192917326a726046e9e83f72d091e550224e7908af82dff2498f98886ebb6 + md5: 7ce070e3329cd10bf79dbed562a21bd4 depends: - binutils_linux-64 - - gcc_linux-64 13.3.0 hc28eda2_7 + - gcc_linux-64 13.3.0 hc28eda2_10 - gfortran_impl_linux-64 13.3.0.* - sysroot_linux-64 license: BSD-3-Clause license_family: BSD - size: 30355 - timestamp: 1731939610282 -- kind: conda - name: gmp - version: 6.3.0 - build: hac33072_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda + size: 30847 + timestamp: 1745040792044 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda sha256: 309cf4f04fec0c31b6771a5809a1909b4b3154a2208f52351e1ada006f4c750c md5: c94a5994ef49749880a8139cf9afcbe1 depends: @@ -2446,15 +2119,9 @@ packages: license: GPL-2.0-or-later OR LGPL-3.0-or-later size: 460055 timestamp: 1718980856608 -- kind: conda - name: gmpy2 - version: 2.1.5 - build: py312h7201bc8_3 - build_number: 3 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - sha256: addd0bc226ca86c11f1223ab322d12b67501c2b3d93749bdab2068ccaedd8ef0 - md5: 673ef4d6611f5b4ca7b5c1f8c65a38dc +- conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + sha256: 92cd104e06fafabc5a0da93ad16a18a7e33651208901bdb0ecd89d10c846e43a + md5: c539cba0be444c6cefcb853987187d9e depends: - __glibc >=2.17,<3.0.a0 - gmp >=6.3.0,<7.0a0 @@ -2465,119 +2132,74 @@ packages: - python_abi 3.12.* *_cp312 license: LGPL-3.0-or-later license_family: LGPL - size: 209631 - timestamp: 1733462668219 -- kind: conda - name: gxx - version: 13.3.0 - build: h9576a4e_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - sha256: 5446f5d1d609d996579f706d2020e83ef48e086d943bfeef7ab807ea246888a0 - md5: 209182ca6b20aeff62f442e843961d81 + size: 213405 + timestamp: 1745509508879 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + sha256: fa9d0171c17e4c4203a4199fcc35571a25c1f16c0ad992080d4f0ced53bf5aa5 + md5: 07e8df00b7cd3084ad3ef598ce32a71c depends: - gcc 13.3.0.* - gxx_impl_linux-64 13.3.0.* license: BSD-3-Clause license_family: BSD - size: 53338 - timestamp: 1724801498389 -- kind: conda - name: gxx_impl_linux-64 - version: 13.3.0 - build: hdbfa832_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - sha256: 746dff24bb1efc89ab0ec108838d0711683054e3bbbcb94d042943410a98eca1 - md5: 806367e23a0a6ad21e51875b34c57d7e - depends: - - gcc_impl_linux-64 13.3.0 hfea6d02_1 - - libstdcxx-devel_linux-64 13.3.0 h84ea5a7_101 + size: 54718 + timestamp: 1740240712365 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + sha256: 7cb36526a5c3e75ae07452aee5c9b6219f62fad9f85cc6d1dab5b21d1c4cc996 + md5: b55f02540605c322a47719029f8404cc + depends: + - gcc_impl_linux-64 13.3.0 h1e990d8_2 + - libstdcxx-devel_linux-64 13.3.0 hc03c837_102 - sysroot_linux-64 - tzdata license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL - size: 13337720 - timestamp: 1724801455825 -- kind: conda - name: gxx_linux-64 - version: 13.3.0 - build: h6834431_7 - build_number: 7 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - sha256: a9b1ffea76f2cc5aedeead4793fcded7a687cce9d5e3f4fe93629f1b1d5043a6 - md5: 7c82ca9bda609b6f72f670e4219d3787 + size: 13362974 + timestamp: 1740240672045 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + sha256: 03de108ca10b693a1b03e7d5cf9173837281d15bc5da7743ffba114fa9389476 + md5: 9a8ebde471cec5cc9c48f8682f434f92 depends: - binutils_linux-64 - - gcc_linux-64 13.3.0 hc28eda2_7 + - gcc_linux-64 13.3.0 hc28eda2_10 - gxx_impl_linux-64 13.3.0.* - sysroot_linux-64 license: BSD-3-Clause license_family: BSD - size: 30356 - timestamp: 1731939612705 -- kind: conda - name: h2 - version: 4.1.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - sha256: 843ddad410c370672a8250470697027618f104153612439076d4d7b91eeb7b5c - md5: 825927dc7b0f287ef8d4d0011bb113b1 - depends: - - hpack >=4.0,<5 - - hyperframe >=6.0,<7 + size: 30904 + timestamp: 1745040794452 +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + sha256: 0aa1cdc67a9fe75ea95b5644b734a756200d6ec9d0dff66530aec3d1c1e9df75 + md5: b4754fb1bdcb70c8fd54f918301582c6 + depends: + - hpack >=4.1,<5 + - hyperframe >=6.1,<7 - python >=3.9 license: MIT license_family: MIT - size: 52000 - timestamp: 1733298867359 -- kind: conda - name: hpack - version: 4.0.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - sha256: ec89b7e5b8aa2f0219f666084446e1fb7b54545861e9caa892acb24d125761b5 - md5: 2aa5ff7fa34a81b9196532c84c10d865 + size: 53888 + timestamp: 1738578623567 +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba + md5: 0a802cb9888dd14eeefc611f05c40b6e depends: - python >=3.9 license: MIT license_family: MIT - size: 29412 - timestamp: 1733299296857 -- kind: conda - name: hyperframe - version: 6.0.1 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - sha256: e91c6ef09d076e1d9a02819cd00fa7ee18ecf30cdd667605c853980216584d1b - md5: 566e75c90c1d0c8c459eb0ad9833dc7a + size: 30731 + timestamp: 1737618390337 +- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 + md5: 8e6923fc12f1fe8f8c4e5c9f343256ac depends: - python >=3.9 license: MIT license_family: MIT - size: 17239 - timestamp: 1733298862681 -- kind: conda - name: hypothesis - version: 6.123.7 - build: pyha770c72_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda - sha256: 72a4bef11cecacb9dd4a460569724c2a7a026db3220c2532d85e564341ea3735 - md5: a1d0e1fa77d48bacb60d916d886ec63a + size: 17397 + timestamp: 1737618427549 +- conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda + sha256: 38b97ec0b35070ac915a6732b920076d7bad376f9746375045ce31313c758fed + md5: 42127e8b4b57e92f097c74a396ae6511 depends: - attrs >=22.2.0 - click >=7.0 @@ -2586,16 +2208,20 @@ packages: - setuptools - sortedcontainers >=2.1.0,<3.0.0 license: MPL-2.0 - size: 344302 - timestamp: 1736235054115 -- kind: conda - name: idna - version: '3.10' - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda + size: 357607 + timestamp: 1746683820692 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda + sha256: 71e750d509f5fa3421087ba88ef9a7b9be11c53174af3aa4d06aff4c18b38e8e + md5: 8b189310083baabfb622af68fd9d3ae3 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc-ng >=12 + - libstdcxx-ng >=12 + license: MIT + license_family: MIT + size: 12129203 + timestamp: 1720853576813 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda sha256: d7a472c9fd479e2e8dcb83fb8d433fce971ea369d704ece380e876f9c3494e87 md5: 39a4f67be3286c86d696df570b1201b7 depends: @@ -2604,31 +2230,17 @@ packages: license_family: BSD size: 49765 timestamp: 1733211921194 -- kind: conda - name: importlib-metadata - version: 8.5.0 - build: pyha770c72_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda - sha256: 13766b88fc5b23581530d3a0287c0c58ad82f60401afefab283bf158d2be55a9 - md5: 315607a3030ad5d5227e76e0733798ff +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda + sha256: 598951ebdb23e25e4cec4bbff0ae369cec65ead80b50bc08b441d8e54de5cf03 + md5: f4b39bf00c69f56ac01e020ebfac066c depends: - python >=3.9 - zipp >=0.5 license: Apache-2.0 license_family: APACHE - size: 28623 - timestamp: 1733223207185 -- kind: conda - name: iniconfig - version: 2.0.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda + size: 29141 + timestamp: 1737420302391 +- conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda sha256: 0ec8f4d02053cd03b0f3e63168316530949484f80e16f5e2fb199a1d117a89ca md5: 6837f3eff7dcea42ecd714ce1ac2b108 depends: @@ -2637,67 +2249,49 @@ packages: license_family: MIT size: 11474 timestamp: 1733223232820 -- kind: conda - name: jax - version: 0.4.35 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/jax-0.4.35-pyhd8ed1ab_1.conda - sha256: 665e96d8a8144f33ea9733746ee3a9c913dd5fa460fb2095592f935cab0753a8 - md5: 8fe7d2b5328189557c539e8a82af00e9 +- conda: https://conda.anaconda.org/conda-forge/noarch/jax-0.5.2-pyhd8ed1ab_0.conda + sha256: be7644c955cd4be330a13a8f64c0b73d520f8b3ab6bb64b8b1d3a17945345684 + md5: f19f3d281603af8e67d533dbeac279ce depends: - importlib-metadata >=4.6 - - jaxlib >=0.4.34,<=0.4.35 + - jaxlib >=0.5.1,<=0.5.2 - ml_dtypes >=0.4.0 - - numpy >=1.24 - - opt-einsum + - numpy >=1.25 + - opt_einsum - python >=3.10 - - scipy >=1.10 + - scipy >=1.11.1 constrains: - - cudnn >=9.2.1.18 + - cudnn >=9.2.1.18,<10.0 license: Apache-2.0 license_family: APACHE - size: 1430482 - timestamp: 1733731330348 -- kind: conda - name: jaxlib - version: 0.4.35 - build: cpu_py312h7d5f655_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.4.35-cpu_py312h7d5f655_0.conda - sha256: 4832163194f53de12d44446dc15226295fed77fbce5e5b8f1bbe22d8f4c1600f - md5: 8e8963097493140ce218084632be7424 + size: 1556886 + timestamp: 1741182198677 +- conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.5.2-cpu_py312h860c521_1.conda + sha256: 1fcc1bf0bef2ff4a072744d57e4f9cb5b7a4c75191d2a18767b4fcfbac76fc8c + md5: 338663f410794bf924e6264060071cfb depends: - __glibc >=2.17,<3.0.a0 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250127.0,<20250128.0a0 - libgcc >=13 - - libgrpc >=1.67.1,<1.68.0a0 + - libgrpc >=1.71.0,<1.72.0a0 - libstdcxx >=13 - libzlib >=1.3.1,<2.0a0 - ml_dtypes >=0.2.0 - numpy >=1.19,<3 - - openssl >=3.4.0,<4.0a0 + - openssl >=3.4.1,<4.0a0 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - scipy >=1.9 constrains: - - jax >=0.4.35 + - jax >=0.5.2 license: Apache-2.0 license_family: APACHE - size: 58146105 - timestamp: 1733957097919 -- kind: conda - name: jaxlib - version: 0.4.35 - build: cuda126py312hd27b167_200 - build_number: 200 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.4.35-cuda126py312hd27b167_200.conda - sha256: 789319c6c97420714cc00b464eeec3f7feb3bdd5481efc607c2d42dcfe3a2574 - md5: e0fd05b260c335750c151466b645254d + size: 69281416 + timestamp: 1741977510646 +- conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.5.2-cuda126py312hbc630d6_201.conda + sha256: 783146a85c17ebca17e05397fd8a739344d6bdb7b322ce206a72e4a737ef2df7 + md5: a914a0aa5e18831a85722d8fc8fa731d depends: - __cuda - __glibc >=2.17,<3.0.a0 @@ -2707,9 +2301,9 @@ packages: - cuda-nvcc-tools - cuda-nvtx >=12.6.77,<13.0a0 - cuda-version >=12.6,<13 - - cudnn >=9.3.0.75,<10.0a0 + - cudnn >=9.8.0.87,<10.0a0 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250127.0,<20250128.0a0 - libcublas >=12.6.4.1,<13.0a0 - libcublas-dev - libcufft >=11.3.0.4,<12.0a0 @@ -2720,47 +2314,34 @@ packages: - libcusolver-dev - libcusparse >=12.5.4.2,<13.0a0 - libcusparse-dev - - libgcc >=12 - - libgrpc >=1.67.1,<1.68.0a0 - - libstdcxx >=12 + - libgcc >=13 + - libgrpc >=1.71.0,<1.72.0a0 + - libstdcxx >=13 - libzlib >=1.3.1,<2.0a0 - ml_dtypes >=0.2.0 - - nccl >=2.23.4.1,<3.0a0 + - nccl >=2.25.1.1,<3.0a0 - numpy >=1.19,<3 - - openssl >=3.4.0,<4.0a0 + - openssl >=3.4.1,<4.0a0 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - scipy >=1.9 constrains: - - jax >=0.4.35 + - jax >=0.5.2 license: Apache-2.0 license_family: APACHE - size: 135857260 - timestamp: 1733960818430 -- kind: conda - name: jinja2 - version: 3.1.5 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.5-pyhd8ed1ab_0.conda - sha256: 98977694b9ecaa3218662f843425f39501f81973c450f995eec68f1803ed71c3 - md5: 2752a6ed44105bfb18c9bef1177d9dcd + size: 150896132 + timestamp: 1741987809555 +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda + sha256: f1ac18b11637ddadc05642e8185a851c7fab5998c6f5470d716812fae943b2af + md5: 446bd6c8cb26050d528881df495ce646 depends: - markupsafe >=2.0 - python >=3.9 license: BSD-3-Clause license_family: BSD - size: 112561 - timestamp: 1734824044952 -- kind: conda - name: kernel-headers_linux-64 - version: 3.10.0 - build: he073ed8_18 - build_number: 18 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda + size: 112714 + timestamp: 1741263433881 +- conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda sha256: a922841ad80bd7b222502e65c07ecb67e4176c4fa5b03678a005f39fcc98be4b md5: ad8527bf134a90e1c9ed35fa0b64318c constrains: @@ -2769,432 +2350,319 @@ packages: license_family: GPL size: 943486 timestamp: 1729794504440 -- kind: conda - name: ld_impl_linux-64 - version: '2.43' - build: h712a8e2_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - sha256: 7c91cea91b13f4314d125d1bedb9d03a29ebbd5080ccdea70260363424646dbe - md5: 048b02e3962f066da18efe3a21b77672 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + sha256: db73f38155d901a610b2320525b9dd3b31e4949215c870685fd92ea61b5ce472 + md5: 01f8d123c96816249efd255a31ad7712 depends: - __glibc >=2.17,<3.0.a0 constrains: - binutils_impl_linux-64 2.43 license: GPL-3.0-only license_family: GPL - size: 669211 - timestamp: 1729655358674 -- kind: conda - name: libabseil - version: '20240722.0' - build: cxx17_hbbce691_4 - build_number: 4 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - sha256: 143a586aa67d50622ef703de57b9d43f44945836d6568e0e7aa174bd8c45e0d4 - md5: 488f260ccda0afaf08acb286db439c2f + size: 671240 + timestamp: 1740155456116 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250127.1-cxx17_hbbce691_0.conda + sha256: 65d5ca837c3ee67b9d769125c21dc857194d7f6181bb0e7bd98ae58597b457d0 + md5: 00290e549c5c8a32cc271020acc9ec6b depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 - libstdcxx >=13 constrains: - - libabseil-static =20240722.0=cxx17* - - abseil-cpp =20240722.0 + - abseil-cpp =20250127.1 + - libabseil-static =20250127.1=cxx17* license: Apache-2.0 license_family: Apache - size: 1311599 - timestamp: 1736008414161 -- kind: conda - name: libblas - version: 3.9.0 - build: 26_linux64_mkl - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_mkl.conda - sha256: 11cc33993e1865e6caa3e05f117effb3f7cbacc632e5adc572ffd36b4fa47241 - md5: 60463d3ec26e0860bfc7fc1547e005ef + size: 1325007 + timestamp: 1742369558286 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_h59b9bed_openblas.conda + build_number: 31 + sha256: 9839fc4ac0cbb0aa3b9eea520adfb57311838959222654804e58f6f2d1771db5 + md5: 728dbebd0f7a20337218beacffd37916 + depends: + - libopenblas >=0.3.29,<0.3.30.0a0 + - libopenblas >=0.3.29,<1.0a0 + constrains: + - liblapacke =3.9.0=31*_openblas + - liblapack =3.9.0=31*_openblas + - blas =2.131=openblas + - mkl <2025 + - libcblas =3.9.0=31*_openblas + license: BSD-3-Clause + license_family: BSD + size: 16859 + timestamp: 1740087969120 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_hfdb39a5_mkl.conda + build_number: 31 + sha256: 862289f2cfb84bb6001d0e3569e908b8c42d66b881bd5b03f730a3924628b978 + md5: bdf4a57254e8248222cb631db4393ff1 depends: - mkl >=2024.2.2,<2025.0a0 constrains: - - liblapack 3.9.0 26_linux64_mkl - - blas * mkl - - libcblas 3.9.0 26_linux64_mkl - - liblapacke 3.9.0 26_linux64_mkl + - liblapack =3.9.0=31*_mkl + - liblapacke =3.9.0=31*_mkl + - blas =2.131=mkl + - libcblas =3.9.0=31*_mkl track_features: - blas_mkl license: BSD-3-Clause license_family: BSD - size: 16766 - timestamp: 1734432542498 -- kind: conda - name: libblas - version: 3.9.0 - build: 26_linux64_openblas - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_openblas.conda - sha256: 30bd658682b124243f8e52d8edf8a19e7be1bc31e4fe4baec30a64002dc8cd0c - md5: ac52800af2e0c0e7dac770b435ce768a - depends: - - libopenblas >=0.3.28,<0.3.29.0a0 - - libopenblas >=0.3.28,<1.0a0 - constrains: - - libcblas 3.9.0 26_linux64_openblas - - liblapack 3.9.0 26_linux64_openblas - - liblapacke 3.9.0 26_linux64_openblas - - blas * openblas - license: BSD-3-Clause - license_family: BSD - size: 16393 - timestamp: 1734432564346 -- kind: conda - name: libcap - version: '2.71' - build: h39aace5_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcap-2.71-h39aace5_0.conda - sha256: 2bbefac94f4ab8ff7c64dc843238b6c8edcc9ff1f2b5a0a48407a904dc7ccfb2 - md5: dd19e4e3043f6948bd7454b946ee0983 + size: 17259 + timestamp: 1740087718283 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcap-2.75-h39aace5_0.conda + sha256: 9c84448305e7c9cc44ccec7757cf5afcb5a021f4579aa750a1fa6ea398783950 + md5: c44c16d6976d2aebbd65894d7741e67e depends: - __glibc >=2.17,<3.0.a0 - attr >=2.5.1,<2.6.0a0 - libgcc >=13 license: BSD-3-Clause license_family: BSD - size: 102268 - timestamp: 1729940917945 -- kind: conda - name: libcblas - version: 3.9.0 - build: 26_linux64_mkl - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_mkl.conda - sha256: 23866eb509e5896b8fcf647e9cef8f0923d5bb378c0dd14b44b94abe1b24c4d7 - md5: 760c109bfe25518d6f9af51d7af8b9f3 - depends: - - libblas 3.9.0 26_linux64_mkl + size: 120375 + timestamp: 1741176638215 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_h372d94f_mkl.conda + build_number: 31 + sha256: 2ee3ab2b6eeb59f2d3c6f933fa0db28f1b56f0bc543ed2c0f6ec04060e4b6ec0 + md5: 2a06a6c16b45bd3d10002927ca204b67 + depends: + - libblas 3.9.0 31_hfdb39a5_mkl constrains: - - liblapack 3.9.0 26_linux64_mkl - - blas * mkl - - liblapacke 3.9.0 26_linux64_mkl + - liblapack =3.9.0=31*_mkl + - liblapacke =3.9.0=31*_mkl + - blas =2.131=mkl track_features: - blas_mkl license: BSD-3-Clause license_family: BSD - size: 16269 - timestamp: 1734432548754 -- kind: conda - name: libcblas - version: 3.9.0 - build: 26_linux64_openblas - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_openblas.conda - sha256: 9c74e536c9bc868e356ffd43f81c2cb398aec84b40fcadc312315b164a5500ee - md5: ebcc5f37a435aa3c19640533c82f8d76 - depends: - - libblas 3.9.0 26_linux64_openblas + size: 16724 + timestamp: 1740087727554 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_he106b2a_openblas.conda + build_number: 31 + sha256: ede8545011f5b208b151fe3e883eb4e31d495ab925ab7b9ce394edca846e0c0d + md5: abb32c727da370c481a1c206f5159ce9 + depends: + - libblas 3.9.0 31_h59b9bed_openblas constrains: - - liblapack 3.9.0 26_linux64_openblas - - liblapacke 3.9.0 26_linux64_openblas - - blas * openblas + - liblapacke =3.9.0=31*_openblas + - liblapack =3.9.0=31*_openblas + - blas =2.131=openblas license: BSD-3-Clause license_family: BSD - size: 16336 - timestamp: 1734432570482 -- kind: conda - name: libcublas - version: 12.6.4.1 - build: hbd13f7d_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.6.4.1-hbd13f7d_0.conda - sha256: 99ac5f733effaabf30db0f9bf69f8969597834251cbe2ecff4b682806c0ad97b - md5: c7124adbde472a7052dc42e3fc8310db + size: 16796 + timestamp: 1740087984429 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.9.0.13-h9ab20c4_0.conda + sha256: 18dc7b16b5ab5f397222566b20c450ade1a16f1f2639991cbfe91eef6960ad62 + md5: 9c1477b1793b43fd128dffd240286e98 depends: - - __glibc >=2.17,<3.0.a0 + - __glibc >=2.28,<3.0.a0 - cuda-nvrtc - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 267981139 - timestamp: 1732133541796 -- kind: conda - name: libcublas-dev - version: 12.6.4.1 - build: h5888daf_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcublas-dev-12.6.4.1-h5888daf_0.conda - sha256: 764f69865e71721be8b1f9fe641aa743bef256e67a2d91f3297c3da6bfdb500e - md5: 4f9c150a55906bb20d02010b2011bb87 + size: 467452297 + timestamp: 1746202246998 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-dev-12.9.0.13-h9ab20c4_0.conda + sha256: 2ace6dd4b60212b3870dfefc63010c77cb486da06aadc46a4426ab340f032689 + md5: fdf825f59f01293b8e335e536296478e depends: - - __glibc >=2.17,<3.0.a0 + - __glibc >=2.28,<3.0.a0 - cuda-crt-dev_linux-64 - cuda-cudart-dev_linux-64 - - cuda-version >=12.6,<12.7.0a0 - - libcublas 12.6.4.1 hbd13f7d_0 + - cuda-version >=12.9,<12.10.0a0 + - libcublas 12.9.0.13 h9ab20c4_0 + - libgcc >=13 + - libstdcxx >=13 + constrains: + - libcublas-static >=12.9.0.13 + license: LicenseRef-NVIDIA-End-User-License-Agreement + size: 91998 + timestamp: 1746203009003 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcudss-0.5.0.16-h14340ca_1.conda + sha256: 0fb14ae71efe11429c24b2fa7d82e718fb52f4cf9cad9379dd7c0302e4294373 + md5: 290a26e7caf9bcbdde629db6612e212e + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + - cuda-version >=12,<13.0a0 + - libcublas - libgcc >=13 - libstdcxx >=13 constrains: - - libcublas-static >=12.6.4.1 + - libcudss-commlayer-nccl 0.5.0.16 hb92ee24_1 + - libcudss-commlayer-mpi 0.5.0.16 h2f16e9f_1 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 89823 - timestamp: 1732134221381 -- kind: conda - name: libcufft - version: 11.3.0.4 - build: hbd13f7d_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.3.0.4-hbd13f7d_0.conda - sha256: fc64a2611a15db7baef61efee2059f090b8f866d06b8f65808c8d2ee191cf7db - md5: a296940fa2e0448d066d03bf6b586772 + size: 32293521 + timestamp: 1739909124258 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.4.0.6-h5888daf_0.conda + sha256: 09689f760978a77d18bc393ce749b539e1fcc870c0e41f666993be26b0296314 + md5: 498af0c40a20ee97db04d51269f2fd87 depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 163772747 - timestamp: 1727808246058 -- kind: conda - name: libcufft-dev - version: 11.3.0.4 - build: h5888daf_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcufft-dev-11.3.0.4-h5888daf_0.conda - sha256: 6e102281119d38eef0fee707eaa51254db7e9a76c4a9cec6c4b3a6260a4929fa - md5: e51d70f74e9e5241a0bf33fb866e2476 + size: 161845949 + timestamp: 1746193474688 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-dev-11.4.0.6-h5888daf_0.conda + sha256: 4966ea4478e602583f8af1ee68e549abd77e9c014302f3ccc11e0cf6b6174275 + md5: 67dc1b5160e2fd24446b8355f3a0f175 depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 - - libcufft 11.3.0.4 hbd13f7d_0 + - cuda-version >=12.9,<12.10.0a0 + - libcufft 11.4.0.6 h5888daf_0 - libgcc >=13 - libstdcxx >=13 constrains: - - libcufft-static >=11.3.0.4 + - libcufft-static >=11.4.0.6 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 33554 - timestamp: 1727808683502 -- kind: conda - name: libcufile - version: 1.11.1.6 - build: h12f29b5_4 - build_number: 4 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcufile-1.11.1.6-h12f29b5_4.conda - sha256: 9ecee7787519cb3591188f3ac02b65f61775e7c790ca11690f3f35b4e1f89721 - md5: 44fd967c18c41e4e5822f339621a47b4 + size: 34188 + timestamp: 1746193845048 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcufile-1.14.0.30-h628e99a_0.conda + sha256: 59807deae0844774301acc8d03d78dbaae8718ab69faca7d203dc689be06d952 + md5: 248bb7bf66da6f601ee99fd24892380c depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 - rdma-core >=55.0 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 921236 - timestamp: 1734164180458 -- kind: conda - name: libcurand - version: 10.3.7.77 - build: hbd13f7d_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.7.77-hbd13f7d_0.conda - sha256: 58ee962804a9df475638e0e83f1116bfbf00a5e4681ed180eb872990d49d7902 - md5: d8b8a1e6e6205447289cd09212c914ac + size: 971139 + timestamp: 1746193260621 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.10.19-h9ab20c4_0.conda + sha256: c4576976b8b5ceb060b32d24fc08db5253606256c3c99b42ace343e9be2229db + md5: c745bc0dd1f066e6752c8b2909216b62 depends: - - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 + - __glibc >=2.28,<3.0.a0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 41790488 - timestamp: 1727807993172 -- kind: conda - name: libcurand-dev - version: 10.3.7.77 - build: h5888daf_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcurand-dev-10.3.7.77-h5888daf_0.conda - sha256: 409d598d56536bb23b944dff81508496835ff9f04858cc3c608ba3e34bffb3af - md5: 83a87ce38925eb22b509a8aba3ba3aaf + size: 46161381 + timestamp: 1746193213392 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-dev-10.3.10.19-h9ab20c4_0.conda + sha256: 1d59e844f3a79c19040efc1f15f23e33bb6b13df19bb63714e9b34515fc9d8fc + md5: 9a7e41b2c3cf57f6a3a1aeac35ebebc0 depends: - - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 - - libcurand 10.3.7.77 hbd13f7d_0 + - __glibc >=2.28,<3.0.a0 + - cuda-version >=12.9,<12.10.0a0 + - libcurand 10.3.10.19 h9ab20c4_0 - libgcc >=13 - libstdcxx >=13 constrains: - - libcurand-static >=10.3.7.77 + - libcurand-static >=10.3.10.19 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 268460 - timestamp: 1727808054226 -- kind: conda - name: libcusolver - version: 11.7.1.2 - build: hbd13f7d_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.1.2-hbd13f7d_0.conda - sha256: 3de5457807dd30f9509863324cfbe9d74d20f477dfeb5ed7de68bbb3da4064bd - md5: 035db50d5e949de81e015df72a834e79 + size: 253530 + timestamp: 1746193336357 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.4.40-h9ab20c4_0.conda + sha256: 4148415e990c51e5e396ea24869415de3996527f92b0e4dc625aa6bcccd50f87 + md5: 9b693f50985ce248765108972099fe55 depends: - - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 - - libcublas >=12.6.3.3,<12.7.0a0 - - libcusparse >=12.5.4.2,<12.6.0a0 + - __glibc >=2.28,<3.0.a0 + - cuda-version >=12.9,<12.10.0a0 + - libcublas >=12.9.0.13,<12.10.0a0 + - libcusparse >=12.5.9.5,<12.6.0a0 - libgcc >=13 - - libnvjitlink >=12.6.77,<12.7.0a0 + - libnvjitlink >=12.9.41,<12.10.0a0 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 100482680 - timestamp: 1727816156921 -- kind: conda - name: libcusolver-dev - version: 11.7.1.2 - build: h5888daf_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-dev-11.7.1.2-h5888daf_0.conda - sha256: 91270bb03306d89aef2be679c0743c9b2ec6bcbc79dcce2df3f5267aafaeb247 - md5: 9e972a58dc8fc72fb39a0d8e7fc151d6 + size: 201753979 + timestamp: 1746205898951 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-dev-11.7.4.40-h9ab20c4_0.conda + sha256: d6811f35727a6cedc4f6dec20584bcd775fe1cdb367b8cf3e7fd01d2c4439313 + md5: 416a81027b133a2cff0585e31d9dcafe depends: - - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 - - libcusolver 11.7.1.2 hbd13f7d_0 + - __glibc >=2.28,<3.0.a0 + - cuda-version >=12.9,<12.10.0a0 + - libcusolver 11.7.4.40 h9ab20c4_0 - libgcc >=13 - libstdcxx >=13 constrains: - - libcusolver-static >=11.7.1.2 + - libcusolver-static >=11.7.4.40 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 60630 - timestamp: 1727816304318 -- kind: conda - name: libcusparse - version: 12.5.4.2 - build: hbd13f7d_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.4.2-hbd13f7d_0.conda - sha256: 9db5d983d102c20f2cecc494ea22d84c44df37d373982815fc2eb669bf0bd376 - md5: 8186e9de34f321aa34965c1cb72c0c26 + size: 60998 + timestamp: 1746206190695 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.9.5-h5888daf_0.conda + sha256: 2ae08171a1d207af2046951177f09f771a4ca76e757b8ce4020fa559524800d2 + md5: 2b89788a46b00abd59ffab688868c321 depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - - libnvjitlink >=12.6.77,<12.7.0a0 + - libnvjitlink >=12.9.41,<12.10.0a0 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 124403455 - timestamp: 1727811455861 -- kind: conda - name: libcusparse-dev - version: 12.5.4.2 - build: h5888daf_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-dev-12.5.4.2-h5888daf_0.conda - sha256: 9db5e524f101b005c0ada807df1109055285f564e78b19aad87e1db46cb13c9f - md5: 48de133da2c0d116b3e7053b8c8dff89 + size: 208851709 + timestamp: 1746195989263 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-dev-12.5.9.5-h5888daf_0.conda + sha256: 82aef570f27ec0770477b841e16e70db352db7253425818c60d91dddf34f16f2 + md5: 7580baba0294656dda948344452e51c0 depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 - - libcusparse 12.5.4.2 hbd13f7d_0 + - cuda-version >=12.9,<12.10.0a0 + - libcusparse 12.5.9.5 h5888daf_0 - libgcc >=13 - - libnvjitlink >=12.6.77,<12.7.0a0 + - libnvjitlink >=12.9.41,<12.10.0a0 - libstdcxx >=13 constrains: - - libcusparse-static >=12.5.4.2 + - libcusparse-static >=12.5.9.5 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 51848 - timestamp: 1727811705461 -- kind: conda - name: libexpat - version: 2.6.4 - build: h5888daf_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - sha256: 56541b98447b58e52d824bd59d6382d609e11de1f8adf20b23143e353d2b8d26 - md5: db833e03127376d461e1e13e76f09b6c + size: 52753 + timestamp: 1746196334627 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + sha256: 33ab03438aee65d6aa667cf7d90c91e5e7d734c19a67aa4c7040742c0a13d505 + md5: db0bfbe7dd197b68ad5f30333bae6ce0 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 constrains: - - expat 2.6.4.* + - expat 2.7.0.* license: MIT license_family: MIT - size: 73304 - timestamp: 1730967041968 -- kind: conda - name: libffi - version: 3.4.2 - build: h7f98852_5 - build_number: 5 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - sha256: ab6e9856c21709b7b517e940ae7028ae0737546122f83c2aa5d692860c3b149e - md5: d645c6d2ac96843a2bfaccd2d62b3ac3 + size: 74427 + timestamp: 1743431794976 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + sha256: 764432d32db45466e87f10621db5b74363a9f847d2b8b1f9743746cd160f06ab + md5: ede4673863426c0883c0063d853bbd85 depends: - - libgcc-ng >=9.4.0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 license: MIT license_family: MIT - size: 58292 - timestamp: 1636488182923 -- kind: conda - name: libgcc - version: 14.2.0 - build: h77fa898_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - sha256: 53eb8a79365e58849e7b1a068d31f4f9e718dc938d6f2c03e960345739a03569 - md5: 3cb76c3f10d3bc7f1105b2fc9db984df + size: 57433 + timestamp: 1743434498161 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + sha256: 0024f9ab34c09629621aefd8603ef77bf9d708129b0dd79029e502c39ffc2195 + md5: ea8ac52380885ed41c1baa8f1d6d2b93 depends: - - _libgcc_mutex 0.1 conda_forge + - __glibc >=2.17,<3.0.a0 - _openmp_mutex >=4.5 constrains: - - libgomp 14.2.0 h77fa898_1 - - libgcc-ng ==14.2.0=*_1 + - libgcc-ng ==15.1.0=*_2 + - libgomp 15.1.0 h767d61c_2 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 848745 - timestamp: 1729027721139 -- kind: conda - name: libgcc-devel_linux-64 - version: 13.3.0 - build: h84ea5a7_101 - build_number: 101 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - sha256: 027cfb011328a108bc44f512a2dec6d954db85709e0b79b748c3392f85de0c64 - md5: 0ce69d40c142915ac9734bc6134e514a + size: 829108 + timestamp: 1746642191935 +- conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + sha256: 538544a2e0651bfeb0348ca6469b6b608606f6080a0b5a531af3a3852fec0215 + md5: 4c1d6961a6a54f602ae510d9bf31fa60 depends: - __unix license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL - size: 2598313 - timestamp: 1724801050802 -- kind: conda - name: libgcc-ng - version: 14.2.0 - build: h69a702a_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda - sha256: 3a76969c80e9af8b6e7a55090088bc41da4cffcde9e2c71b17f44d37b7cb87f7 - md5: e39480b9ca41323497b05492a63bc35b - depends: - - libgcc 14.2.0 h77fa898_1 + size: 2597400 + timestamp: 1740240211859 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda + sha256: 0ab5421a89f090f3aa33841036bb3af4ed85e1f91315b528a9d75fab9aad51ae + md5: ddca86c7040dd0e73b2b69bd7833d225 + depends: + - libgcc 15.1.0 h767d61c_2 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 54142 - timestamp: 1729027726517 -- kind: conda - name: libgcrypt-lib - version: 1.11.0 - build: hb9d3cd8_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-lib-1.11.0-hb9d3cd8_2.conda + size: 34586 + timestamp: 1746642200749 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-lib-1.11.0-hb9d3cd8_2.conda sha256: ffc3602f9298da248786f46b00d0594d26a18feeb1b07ce88f3d7d61075e39e6 md5: e55712ff40a054134d51b89afca57dbc depends: @@ -3204,146 +2672,76 @@ packages: license: LGPL-2.1-or-later size: 586185 timestamp: 1732523190369 -- kind: conda - name: libgfortran - version: 14.2.0 - build: h69a702a_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - sha256: fc9e7f22a17faf74da904ebfc4d88699013d2992e55505e4aa0eb01770290977 - md5: f1fd30127802683586f768875127a987 - depends: - - libgfortran5 14.2.0 hd5240d6_1 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + sha256: 914daa4f632b786827ea71b5e07cd00d25fc6e67789db2f830dc481eec660342 + md5: f92e6e0a3c0c0c85561ef61aa59d555d + depends: + - libgfortran5 15.1.0 hcea5267_2 constrains: - - libgfortran-ng ==14.2.0=*_1 + - libgfortran-ng ==15.1.0=*_2 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 53997 - timestamp: 1729027752995 -- kind: conda - name: libgfortran-ng - version: 14.2.0 - build: h69a702a_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - sha256: 423f1e2403f0c665748e42d335e421e53fd03c08d457cfb6f360d329d9459851 - md5: 0a7f4cd238267c88e5d69f7826a407eb - depends: - - libgfortran 14.2.0 h69a702a_1 + size: 34541 + timestamp: 1746642233221 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + sha256: 0665170a98c8ec586352929d45a9c833c0dcdbead38b0b8f3af7a0deee2af755 + md5: a483a87b71e974bb75d1b9413d4436dd + depends: + - libgfortran 15.1.0 h69a702a_2 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 54106 - timestamp: 1729027945817 -- kind: conda - name: libgfortran5 - version: 14.2.0 - build: hd5240d6_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - sha256: d149a37ca73611e425041f33b9d8dbed6e52ec506fe8cc1fc0ee054bddeb6d5d - md5: 9822b874ea29af082e5d36098d25427d - depends: - - libgcc >=14.2.0 + size: 34616 + timestamp: 1746642441079 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + sha256: be23750f3ca1a5cb3ada858c4f633effe777487d1ea35fddca04c0965c073350 + md5: 01de444988ed960031dbe84cf4f9b1fc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15.1.0 constrains: - - libgfortran 14.2.0 + - libgfortran 15.1.0 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 1462645 - timestamp: 1729027735353 -- kind: conda - name: libgomp - version: 14.2.0 - build: h77fa898_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda - sha256: 1911c29975ec99b6b906904040c855772ccb265a1c79d5d75c8ceec4ed89cd63 - md5: cc3573974587f12dda90d96e3e55a702 + size: 1569986 + timestamp: 1746642212331 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda + sha256: 05fff3dc7e80579bc28de13b511baec281c4343d703c406aefd54389959154fb + md5: fbe7d535ff9d3a168c148e07358cd5b1 depends: - - _libgcc_mutex 0.1 conda_forge + - __glibc >=2.17,<3.0.a0 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 460992 - timestamp: 1729027639220 -- kind: conda - name: libgpg-error - version: '1.51' - build: hbd13f7d_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.51-hbd13f7d_1.conda - sha256: 9e0c09c1faf2151ade3ccb64e52d3c1f2dde85c00e37c6a3e6a8bced2aba68be - md5: 168cc19c031482f83b23c4eebbb94e26 + size: 452635 + timestamp: 1746642113092 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.55-h3f2d84a_0.conda + sha256: 697334de4786a1067ea86853e520c64dd72b11a05137f5b318d8a444007b5e60 + md5: 2bd47db5807daade8500ed7ca4c512a4 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - libstdcxx >=13 - license: LGPL-2.1-only - license_family: GPL - size: 268740 - timestamp: 1731920927644 -- kind: conda - name: libgrpc - version: 1.67.1 - build: h25350d4_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.67.1-h25350d4_1.conda - sha256: 014627485b3cf0ea18e04c0bab07be7fb98722a3aeeb58477acc7e1c3d2f911e - md5: 0c6497a760b99a926c7c12b74951a39c - depends: + - libgcc >=13 - __glibc >=2.17,<3.0.a0 - - c-ares >=1.34.4,<2.0a0 - - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 - libgcc >=13 - - libprotobuf >=5.28.3,<5.28.4.0a0 - - libre2-11 >=2024.7.2 - - libstdcxx >=13 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.4.0,<4.0a0 - - re2 - constrains: - - grpc-cpp =1.67.1 - license: Apache-2.0 - license_family: APACHE - size: 7792251 - timestamp: 1735584856826 -- kind: conda - name: libgrpc - version: 1.67.1 - build: hc2c308b_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.67.1-hc2c308b_0.conda - sha256: 870550c1faf524e9a695262cd4c31441b18ad542f16893bd3c5dbc93106705f7 - md5: 4606a4647bfe857e3cfe21ca12ac3afb + license: LGPL-2.1-only + size: 312184 + timestamp: 1745575272035 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.71.0-h8e591d7_1.conda + sha256: 37267300b25f292a6024d7fd9331085fe4943897940263c3a41d6493283b2a18 + md5: c3cfd72cbb14113abee7bbd86f44ad69 depends: - __glibc >=2.17,<3.0.a0 - - c-ares >=1.32.3,<2.0a0 + - c-ares >=1.34.5,<2.0a0 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250127.1,<20250128.0a0 - libgcc >=13 - - libprotobuf >=5.28.2,<5.28.3.0a0 + - libprotobuf >=5.29.3,<5.29.4.0a0 - libre2-11 >=2024.7.2 - libstdcxx >=13 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.3.2,<4.0a0 + - openssl >=3.5.0,<4.0a0 - re2 constrains: - - grpc-cpp =1.67.1 + - grpc-cpp =1.71.0 license: Apache-2.0 license_family: APACHE - size: 7362336 - timestamp: 1730236333879 -- kind: conda - name: libhiredis - version: 1.0.2 - build: h2cc385e_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 + size: 7920187 + timestamp: 1745229332239 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 sha256: ee39c69df4fb39cfe1139ac4f7405bb066eba773e11ba3ab7c33835be00c2e48 md5: b34907d3a81a3cd8095ee83d174c074a depends: @@ -3355,142 +2753,102 @@ packages: license_family: BSD size: 147325 timestamp: 1633982069195 -- kind: conda - name: libhwloc - version: 2.11.2 - build: default_h0d58e46_1001 - build_number: 1001 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.2-default_h0d58e46_1001.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.2-default_h0d58e46_1001.conda sha256: d14c016482e1409ae1c50109a9ff933460a50940d2682e745ab1c172b5282a69 md5: 804ca9e91bcaea0824a341d55b1684f2 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 - libstdcxx >=13 - - libxml2 >=2.13.4,<3.0a0 + - libxml2 >=2.13.4,<2.14.0a0 license: BSD-3-Clause license_family: BSD size: 2423200 timestamp: 1731374922090 -- kind: conda - name: libiconv - version: '1.17' - build: hd590300_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-hd590300_2.conda - sha256: 8ac2f6a9f186e76539439e50505d98581472fedb347a20e7d1f36429849f05c9 - md5: d66573916ffcf376178462f1b61c941e +- conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda + sha256: 18a4afe14f731bfb9cf388659994263904d20111e42f841e9eea1bb6f91f4ab4 + md5: e796ff8ddc598affdf7c173d6145f087 depends: - - libgcc-ng >=12 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 license: LGPL-2.1-only - size: 705775 - timestamp: 1702682170569 -- kind: conda - name: liblapack - version: 3.9.0 - build: 26_linux64_mkl - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_mkl.conda - sha256: 4ab8f00c325e1aacb6edc881b39c7c294adafc9d485cdde82979d1617fcd1e6f - md5: 84112111a50db59ca64153e0054fa73e - depends: - - libblas 3.9.0 26_linux64_mkl + size: 713084 + timestamp: 1740128065462 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_h7ac8fdf_openblas.conda + build_number: 31 + sha256: f583661921456e798aba10972a8abbd9d33571c655c1f66eff450edc9cbefcf3 + md5: 452b98eafe050ecff932f0ec832dd03f + depends: + - libblas 3.9.0 31_h59b9bed_openblas constrains: - - blas * mkl - - libcblas 3.9.0 26_linux64_mkl - - liblapacke 3.9.0 26_linux64_mkl - track_features: - - blas_mkl + - libcblas =3.9.0=31*_openblas + - liblapacke =3.9.0=31*_openblas + - blas =2.131=openblas license: BSD-3-Clause license_family: BSD - size: 16302 - timestamp: 1734432554916 -- kind: conda - name: liblapack - version: 3.9.0 - build: 26_linux64_openblas - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_openblas.conda - sha256: b76458c36331376911e0f98fa68109e02f4d5e5ebfffa79587ac69cef748bba1 - md5: 3792604c43695d6a273bc5faaac47d48 - depends: - - libblas 3.9.0 26_linux64_openblas + size: 16790 + timestamp: 1740087997375 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_hc41d3b0_mkl.conda + build_number: 31 + sha256: a2d20845d916ac8fba09376cd791136a9b4547afb2131bc315178adfc87bb4ca + md5: 10d012ddd7cc1c7ff9093d4974a34e53 + depends: + - libblas 3.9.0 31_hfdb39a5_mkl constrains: - - libcblas 3.9.0 26_linux64_openblas - - liblapacke 3.9.0 26_linux64_openblas - - blas * openblas + - liblapacke =3.9.0=31*_mkl + - blas =2.131=mkl + - libcblas =3.9.0=31*_mkl + track_features: + - blas_mkl license: BSD-3-Clause license_family: BSD - size: 16338 - timestamp: 1734432576650 -- kind: conda - name: liblapacke - version: 3.9.0 - build: 26_linux64_mkl - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_mkl.conda - sha256: 53b722b298222d1f3f1e3ad06c6fb4f0adb81b06e7ec2bd858627b2c79b0aea5 - md5: ffd5d8a606a1bd0e914f276dc44b42ee - depends: - - libblas 3.9.0 26_linux64_mkl - - libcblas 3.9.0 26_linux64_mkl - - liblapack 3.9.0 26_linux64_mkl + size: 16760 + timestamp: 1740087736615 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_hbc6e62b_mkl.conda + build_number: 31 + sha256: 3be711aadec095377094f861574d9327d98a6ffabb54ef48bb6669f63b128c61 + md5: 562026e418363dc346ad5a9e18cce73c + depends: + - libblas 3.9.0 31_hfdb39a5_mkl + - libcblas 3.9.0 31_h372d94f_mkl + - liblapack 3.9.0 31_hc41d3b0_mkl constrains: - - blas * mkl + - blas =2.131=mkl track_features: - blas_mkl license: BSD-3-Clause license_family: BSD - size: 16281 - timestamp: 1734432561155 -- kind: conda - name: liblapacke - version: 3.9.0 - build: 26_linux64_openblas - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_openblas.conda - sha256: 90807199c20500d959cc37ca666f74286b20637d9d1de53cb0730802ed459c9a - md5: 7b8b7732fb4786c00cf9b67d1d69445c - depends: - - libblas 3.9.0 26_linux64_openblas - - libcblas 3.9.0 26_linux64_openblas - - liblapack 3.9.0 26_linux64_openblas + size: 16780 + timestamp: 1740087745326 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_he2f377e_openblas.conda + build_number: 31 + sha256: 510bfe8717ab6e7a19e2b0985c27629ddf89270dbd38def8c821f7f683a369a3 + md5: 7e5fff7d0db69be3a266f7e79a3bb0e2 + depends: + - libblas 3.9.0 31_h59b9bed_openblas + - libcblas 3.9.0 31_he106b2a_openblas + - liblapack 3.9.0 31_h7ac8fdf_openblas constrains: - - blas * openblas + - blas =2.131=openblas license: BSD-3-Clause license_family: BSD - size: 16332 - timestamp: 1734432582792 -- kind: conda - name: liblzma - version: 5.6.3 - build: hb9d3cd8_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda - sha256: e6e425252f3839e2756e4af1ea2074dffd3396c161bf460629f9dfd6a65f15c6 - md5: 2ecf2f1c7e4e21fcfe6423a51a992d84 + size: 16819 + timestamp: 1740088012246 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda + sha256: eeff241bddc8f1b87567dd6507c9f441f7f472c27f0860a07628260c000ef27c + md5: a76fd702c93cd2dfd89eff30a5fd45a8 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 + constrains: + - xz 5.8.1.* + - xz ==5.8.1=*_1 license: 0BSD - size: 111132 - timestamp: 1733407410083 -- kind: conda - name: libmagma - version: 2.8.0 - build: h566cb83_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libmagma-2.8.0-h566cb83_2.conda - sha256: b8999f6dfdcdd3d0531271bd6f45e4842561d44018c9e34f24d31d6d0c73c4d2 - md5: b6818d8ad575df8faace47ee560e0630 + size: 112845 + timestamp: 1746531470399 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmagma-2.9.0-h19665d7_1.conda + sha256: 13d50a4f7da02e6acce4b5b6df82072c0f447a2c5ba1f4a3190dfec3a9174965 + md5: 38b3447782263c96b0c0a7b92c97575e depends: - __glibc >=2.17,<3.0.a0 - _openmp_mutex >=4.5 @@ -3504,39 +2862,9 @@ packages: - libstdcxx >=13 license: BSD-3-Clause license_family: BSD - size: 296058740 - timestamp: 1734990709538 -- kind: conda - name: libmagma_sparse - version: 2.8.0 - build: h0af6554_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libmagma_sparse-2.8.0-h0af6554_0.conda - sha256: f20a4cc53548c2cf8a4cc36502f294aa5e37c4ec3d2930ebd80a7e51d0c851b7 - md5: f506a12b434490e2368a9f2b70b10053 - depends: - - __glibc >=2.17,<3.0.a0 - - _openmp_mutex >=4.5 - - cuda-cudart >=12.0.107,<13.0a0 - - cuda-version >=12.0,<13 - - libblas >=3.9.0,<4.0a0 - - libcublas >=12.0.1.189,<13.0a0 - - libcusparse >=12.0.0.76,<13.0a0 - - libgcc-ng >=12 - - liblapack >=3.9.0,<4.0a0 - - libmagma 2.8.0.* - - libmagma >=2.8.0,<2.8.1.0a0 - - libstdcxx-ng >=12 - license: BSD-3-Clause - license_family: BSD - size: 6731088 - timestamp: 1721861326572 -- kind: conda - name: libnl - version: 3.11.0 - build: hb9d3cd8_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libnl-3.11.0-hb9d3cd8_0.conda + size: 371275523 + timestamp: 1739994057566 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnl-3.11.0-hb9d3cd8_0.conda sha256: ba7c5d294e3d80f08ac5a39564217702d1a752e352e486210faff794ac5001b4 md5: db63358239cbe1ff86242406d440e44a depends: @@ -3546,12 +2874,7 @@ packages: license_family: LGPL size: 741323 timestamp: 1731846827427 -- kind: conda - name: libnsl - version: 2.0.1 - build: hd590300_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda sha256: 26d77a3bb4dceeedc2a41bd688564fe71bf2d149fdcf117049970bc02ff1add6 md5: 30fd6e37fe21f86f4bd26d6ee73eeec7 depends: @@ -3560,294 +2883,201 @@ packages: license_family: GPL size: 33408 timestamp: 1697359010159 -- kind: conda - name: libnvjitlink - version: 12.6.85 - build: hbd13f7d_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.6.85-hbd13f7d_0.conda - sha256: f8af058f7ba2e436f6bbeaabe273a6e88d6193028572769c8402bbee2bdfa95d - md5: dca2d62b3812922e6976f76c0a401918 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.9.41-h5888daf_0.conda + sha256: 363335da59cb71e6576087c98b13e7e13289b8c05b140b09de2e5e9bd06e675b + md5: fa47324d7e1e78492c2f17f0ce67e906 depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12,<12.7.0a0 + - cuda-version >=12,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 15590703 - timestamp: 1732133239776 -- kind: conda - name: libopenblas - version: 0.3.28 - build: pthreads_h94d23a6_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_1.conda - sha256: 99ba271d8a80a1af2723f2e124ffd91d850074c0389c067e6d96d72a2dbfeabe - md5: 62857b389e42b36b686331bec0922050 + size: 30491008 + timestamp: 1746190924588 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.29-pthreads_h94d23a6_0.conda + sha256: cc5389ea254f111ef17a53df75e8e5209ef2ea6117e3f8aced88b5a8e51f11c4 + md5: 0a4d0252248ef9a0f88f2ba8b8a08e12 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - libgfortran - libgfortran5 >=14.2.0 constrains: - - openblas >=0.3.28,<0.3.29.0a0 - license: BSD-3-Clause - license_family: BSD - size: 5578513 - timestamp: 1730772671118 -- kind: conda - name: libprotobuf - version: 5.28.2 - build: h5b01275_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.2-h5b01275_0.conda - sha256: 5e8fd4aa00193c85602ce6101dd28fe31306dff85c9725048f6dc828dfa7c421 - md5: ab0bff36363bec94720275a681af8b83 - depends: - - __glibc >=2.17,<3.0.a0 - - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 - - libgcc >=13 - - libstdcxx >=13 - - libzlib >=1.3.1,<2.0a0 + - openblas >=0.3.29,<0.3.30.0a0 license: BSD-3-Clause license_family: BSD - size: 2945348 - timestamp: 1728565355702 -- kind: conda - name: libprotobuf - version: 5.28.3 - build: h6128344_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.3-h6128344_1.conda - sha256: 51125ebb8b7152e4a4e69fd2398489c4ec8473195c27cde3cbdf1cb6d18c5493 - md5: d8703f1ffe5a06356f06467f1d0b9464 + size: 5919288 + timestamp: 1739825731827 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.29.3-h501fc15_1.conda + sha256: 691af28446345674c6b3fb864d0e1a1574b6cc2f788e0f036d73a6b05dcf81cf + md5: edb86556cf4a0c133e7932a1597ff236 depends: - __glibc >=2.17,<3.0.a0 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250127.1,<20250128.0a0 - libgcc >=13 - libstdcxx >=13 - libzlib >=1.3.1,<2.0a0 license: BSD-3-Clause license_family: BSD - size: 2960815 - timestamp: 1735577210663 -- kind: conda - name: libre2-11 - version: 2024.07.02 - build: hbbce691_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hbbce691_2.conda - sha256: 4420f8362c71251892ba1eeb957c5e445e4e1596c0c651c28d0d8b415fe120c7 - md5: b2fede24428726dd867611664fb372e8 + size: 3358788 + timestamp: 1745159546868 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hba17884_3.conda + sha256: 392ec1e49370eb03270ffd4cc8d727f8e03e1e3a92b12f10c53f396ae4554668 + md5: 545e93a513c10603327c76c15485e946 depends: - __glibc >=2.17,<3.0.a0 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250127.0,<20250128.0a0 - libgcc >=13 - libstdcxx >=13 constrains: - re2 2024.07.02.* license: BSD-3-Clause license_family: BSD - size: 209793 - timestamp: 1735541054068 -- kind: conda - name: libsanitizer - version: 13.3.0 - build: heb74ff8_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - sha256: c86d130f0a3099e46ff51aa7ffaab73cb44fc420d27a96076aab3b9a326fc137 - md5: c4cb22f270f501f5c59a122dc2adf20a + size: 210073 + timestamp: 1741121121238 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + sha256: 27c4c8bf8e2dd60182d47274389be7c70446df6ed5344206266321ee749158b4 + md5: 2b6cdf7bb95d3d10ef4e38ce0bc95dba depends: + - __glibc >=2.17,<3.0.a0 - libgcc >=13.3.0 - libstdcxx >=13.3.0 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL - size: 4133922 - timestamp: 1724801171589 -- kind: conda - name: libsqlite - version: 3.47.2 - build: hee588c1_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - sha256: 48af21ebc2cbf358976f1e0f4a0ab9e91dfc83d0ef337cf3837c6f5bc22fb352 - md5: b58da17db24b6e08bcbf8fed2fb8c915 + size: 4155341 + timestamp: 1740240344242 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + sha256: 525d4a0e24843f90b3ff1ed733f0a2e408aa6dd18b9d4f15465595e078e104a2 + md5: 93048463501053a00739215ea3f36324 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 - libzlib >=1.3.1,<2.0a0 license: Unlicense - size: 873551 - timestamp: 1733761824646 -- kind: conda - name: libstdcxx - version: 14.2.0 - build: hc0a3c3a_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - sha256: 4661af0eb9bdcbb5fb33e5d0023b001ad4be828fccdcc56500059d56f9869462 - md5: 234a5554c53625688d51062645337328 - depends: - - libgcc 14.2.0 h77fa898_1 + size: 916313 + timestamp: 1746637007836 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + sha256: 6ae3d153e78f6069d503d9309f2cac6de5b93d067fc6433160a4c05226a5dad4 + md5: 1cb1c67961f6dd257eae9e9691b341aa + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc 15.1.0 h767d61c_2 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 3893695 - timestamp: 1729027746910 -- kind: conda - name: libstdcxx-devel_linux-64 - version: 13.3.0 - build: h84ea5a7_101 - build_number: 101 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - sha256: 0a9226c1b994f996229ffb54fa40d608cd4e4b48e8dc73a66134bea8ce949412 - md5: 29b5a4ed4613fa81a07c21045e3f5bf6 + size: 3902355 + timestamp: 1746642227493 +- conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + sha256: abc89056d4ca7debe938504b3b6d9ccc6d7a0f0b528fe3409230636a21e81002 + md5: aa38de2738c5f4a72a880e3d31ffe8b4 depends: - __unix license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL - size: 14074676 - timestamp: 1724801075448 -- kind: conda - name: libstdcxx-ng - version: 14.2.0 - build: h4852527_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda - sha256: 25bb30b827d4f6d6f0522cc0579e431695503822f144043b93c50237017fffd8 - md5: 8371ac6457591af2cf6159439c1fd051 - depends: - - libstdcxx 14.2.0 hc0a3c3a_1 + size: 12873130 + timestamp: 1740240239655 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda + sha256: 11bea86e11de7d6bce87589197a383344df3fa0a3552dab7e931785ff1159a5b + md5: 9d2072af184b5caa29492bf2344597bb + depends: + - libstdcxx 15.1.0 h8f9b012_2 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 54105 - timestamp: 1729027780628 -- kind: conda - name: libsystemd0 - version: '256.9' - build: h0b6a36f_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-256.9-h0b6a36f_2.conda - sha256: 28e1a3c4bd242e7eb3bd0bcd35e558680d186e7a1d61482d371dde2a0f1bfb42 - md5: 135bbeb376345b6847c065115be4221a + size: 34647 + timestamp: 1746642266826 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-257.4-h4e0b6ca_1.conda + sha256: 5aa2ba63747ad3b6e717f025c9d2ab4bb32c0d366e1ef81669ffa73b1d9af4a2 + md5: 04bcf3055e51f8dde6fab9672fb9fca0 depends: - __glibc >=2.17,<3.0.a0 - - libcap >=2.71,<2.72.0a0 + - libcap >=2.75,<2.76.0a0 - libgcc >=13 - libgcrypt-lib >=1.11.0,<2.0a0 - - liblzma >=5.6.3,<6.0a0 + - liblzma >=5.6.4,<6.0a0 - lz4-c >=1.10.0,<1.11.0a0 - - zstd >=1.5.6,<1.6.0a0 + - zstd >=1.5.7,<1.6.0a0 license: LGPL-2.1-or-later - size: 410566 - timestamp: 1733679350245 -- kind: conda - name: libtorch - version: 2.5.1 - build: cpu_mkl_he8ec5d7_108 - build_number: 108 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.5.1-cpu_mkl_he8ec5d7_108.conda - sha256: 96e04252aa1a64c8a50fcccb6e36a0f53f54b7eb9a61b2e1930191b67cce655c - md5: a070bb62918bea542fbb092c2abd7004 + size: 488733 + timestamp: 1741629468703 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.7.0-cpu_mkl_hf6ddc5a_100.conda + sha256: 7b6178464b02d65c4af92086c71b79e5c2b7fc1500c1547334a4755e6e92d8a9 + md5: 6bdda0b10852c6d03b030bab7ec251f0 depends: - __glibc >=2.17,<3.0.a0 + - _openmp_mutex * *_llvm - _openmp_mutex >=4.5 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250127.1,<20250128.0a0 + - libblas * *mkl - libcblas >=3.9.0,<4.0a0 - libgcc >=13 - - libprotobuf >=5.28.3,<5.28.4.0a0 + - libprotobuf >=5.29.3,<5.29.4.0a0 - libstdcxx >=13 - - libuv >=1.49.2,<2.0a0 + - libuv >=1.50.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - llvm-openmp >=20.1.4 - mkl >=2024.2.2,<2025.0a0 - - sleef >=3.7,<4.0a0 + - sleef >=3.8,<4.0a0 constrains: - - pytorch-cpu ==2.5.1 - - pytorch 2.5.1 cpu_mkl_*_108 - - pytorch-gpu ==99999999 + - pytorch-gpu <0.0a0 + - pytorch 2.7.0 cpu_mkl_*_100 + - pytorch-cpu 2.7.0 license: BSD-3-Clause license_family: BSD - size: 53384470 - timestamp: 1736088424107 -- kind: conda - name: libtorch - version: 2.5.1 - build: cuda126_hebb32c0_306 - build_number: 306 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.5.1-cuda126_hebb32c0_306.conda - sha256: 8f358f3d4c019c63e091c29060608119076a21d997b8d89e57238d271737416a - md5: 70a1cc9baa999d6e4465ca4626e093ab + size: 55565925 + timestamp: 1746256872466 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.7.0-cuda126_mkl_h99b69db_300.conda + sha256: b4e8c062ddc343be1ff84346ef4f90b258a87d67e747e50a3644a81d1978eb40 + md5: 67d004faec95b8fff704681eae9ccf40 depends: - __glibc >=2.17,<3.0.a0 + - _openmp_mutex * *_llvm - _openmp_mutex >=4.5 - cuda-cudart >=12.6.77,<13.0a0 + - cuda-cupti >=12.6.80,<13.0a0 - cuda-nvrtc >=12.6.85,<13.0a0 - cuda-nvtx >=12.6.77,<13.0a0 - cuda-version >=12.6,<13 - - cudnn >=9.3.0.75,<10.0a0 + - cudnn >=9.8.0.87,<10.0a0 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250127.1,<20250128.0a0 + - libblas * *mkl - libcblas >=3.9.0,<4.0a0 - libcublas >=12.6.4.1,<13.0a0 + - libcudss >=0.5.0.16,<0.5.1.0a0 - libcufft >=11.3.0.4,<12.0a0 - libcufile >=1.11.1.6,<2.0a0 - libcurand >=10.3.7.77,<11.0a0 - libcusolver >=11.7.1.2,<12.0a0 - libcusparse >=12.5.4.2,<13.0a0 - - libgcc >=12 - - libmagma >=2.8.0,<2.8.1.0a0 - - libmagma_sparse >=2.8.0,<2.8.1.0a0 - - libprotobuf >=5.28.2,<5.28.3.0a0 - - libstdcxx >=12 - - libuv >=1.49.2,<2.0a0 + - libgcc >=13 + - libmagma >=2.9.0,<2.9.1.0a0 + - libprotobuf >=5.29.3,<5.29.4.0a0 + - libstdcxx >=13 + - libuv >=1.50.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - llvm-openmp >=20.1.4 - mkl >=2024.2.2,<2025.0a0 - - nccl >=2.23.4.1,<3.0a0 - - sleef >=3.7,<4.0a0 + - nccl >=2.26.5.1,<3.0a0 + - sleef >=3.8,<4.0a0 constrains: - - pytorch-gpu ==2.5.1 - - pytorch-cpu ==99999999 - - sysroot_linux-64 >=2.17 - - pytorch 2.5.1 cuda126_*_306 + - pytorch-gpu 2.7.0 + - pytorch-cpu <0.0a0 + - pytorch 2.7.0 cuda126_mkl_*_300 license: BSD-3-Clause license_family: BSD - size: 514038936 - timestamp: 1733651457474 -- kind: conda - name: libudev1 - version: '256.9' - build: h9a4d06a_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libudev1-256.9-h9a4d06a_2.conda - sha256: aa38d000c5963f22f34ba4a73b5311a9d36da452ae34825ca6a4243138d5aab2 - md5: ae7750de534f1a10832bb08ade6f66dd + size: 594396124 + timestamp: 1746283375271 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libudev1-257.4-hbe16f8c_1.conda + sha256: 56e55a7e7380a980b418c282cb0240b3ac55ab9308800823ff031a9529e2f013 + md5: d6716795cd81476ac2f5465f1b1cde75 depends: - __glibc >=2.17,<3.0.a0 - - libcap >=2.71,<2.72.0a0 + - libcap >=2.75,<2.76.0a0 - libgcc >=13 license: LGPL-2.1-or-later - size: 141055 - timestamp: 1733679357863 -- kind: conda - name: libuuid - version: 2.38.1 - build: h0b41bf4_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda + size: 144039 + timestamp: 1741629479455 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda sha256: 787eb542f055a2b3de553614b25f09eefb0a0931b0c87dbcce6efdfd92f04f18 md5: 40b61aab5c7ba9ff276c41cfffe6b80b depends: @@ -3856,28 +3086,17 @@ packages: license_family: BSD size: 33601 timestamp: 1680112270483 -- kind: conda - name: libuv - version: 1.49.2 - build: hb9d3cd8_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.49.2-hb9d3cd8_0.conda - sha256: a35cd81cd1a9add11024097da83cc06b0aae83186fe4124b77710876f37d8f31 - md5: 070e3c9ddab77e38799d5c30b109c633 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.50.0-hb9d3cd8_0.conda + sha256: b4a8890023902aef9f1f33e3e35603ad9c2f16c21fdb58e968fa6c1bd3e94c0b + md5: 771ee65e13bc599b0b62af5359d80169 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 license: MIT license_family: MIT - size: 884647 - timestamp: 1729322566955 -- kind: conda - name: libxcrypt - version: 4.4.36 - build: hd590300_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + size: 891272 + timestamp: 1737016632446 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c md5: 5aa797f8787fe7a17d1b0821485b5adc depends: @@ -3885,34 +3104,21 @@ packages: license: LGPL-2.1-or-later size: 100393 timestamp: 1702724383534 -- kind: conda - name: libxml2 - version: 2.13.5 - build: h0d44e9d_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.5-h0d44e9d_1.conda - sha256: 306e18aa647d8208ad2cd0e62d84933222b2fbe93d2d53cd5283d2256b1d54de - md5: f5b05674697ae7d2c5932766695945e1 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.8-h4bc477f_0.conda + sha256: b0b3a96791fa8bb4ec030295e8c8bf2d3278f33c0f9ad540e73b5e538e6268e7 + md5: 14dbe05b929e329dbaa6f2d0aa19466d depends: - __glibc >=2.17,<3.0.a0 + - icu >=75.1,<76.0a0 - libgcc >=13 - - libiconv >=1.17,<2.0a0 - - liblzma >=5.6.3,<6.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.1,<6.0a0 - libzlib >=1.3.1,<2.0a0 - constrains: - - icu <0.0a0 license: MIT license_family: MIT - size: 689993 - timestamp: 1733443678322 -- kind: conda - name: libzlib - version: 1.3.1 - build: hb9d3cd8_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + size: 690864 + timestamp: 1746634244154 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 md5: edb0dca6bc32e4f4789199455a1dbeb8 depends: @@ -3924,29 +3130,18 @@ packages: license_family: Other size: 60963 timestamp: 1727963148474 -- kind: conda - name: llvm-openmp - version: 19.1.6 - build: h024ca30_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-19.1.6-h024ca30_0.conda - sha256: 9e385c2a8169d951cf153221fb7fbb3dc8f1e5ac77371edee7329f8721dbe1ae - md5: 96e42ccbd3c067c1713ff5f2d2169247 +- conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-20.1.4-h024ca30_0.conda + sha256: 5b39cdde3457e41b133d6f1fe53095c7fd3951bbdab46580098ccbf5ee9c99f7 + md5: 4fc395cda27912a7d904b86b5dbf3a4d depends: - __glibc >=2.17,<3.0.a0 constrains: - - openmp 19.1.6|19.1.6.* + - openmp 20.1.4|20.1.4.* license: Apache-2.0 WITH LLVM-exception license_family: APACHE - size: 3201572 - timestamp: 1734520399290 -- kind: conda - name: lz4-c - version: 1.10.0 - build: h5888daf_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda + size: 3322195 + timestamp: 1746134424442 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda sha256: 47326f811392a5fd3055f0f773036c392d26fdb32e4d8e7a8197eed951489346 md5: 9de5350a85c4a20c685259b889aa6393 depends: @@ -3957,14 +3152,7 @@ packages: license_family: BSD size: 167055 timestamp: 1733741040117 -- kind: conda - name: markdown-it-py - version: 3.0.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda sha256: 0fbacdfb31e55964152b24d5567e9a9996e1e7902fb08eb7d91b5fd6ce60803a md5: fee3164ac23dfca50cfcc8b85ddefb81 depends: @@ -3974,13 +3162,7 @@ packages: license_family: MIT size: 64430 timestamp: 1733250550053 -- kind: conda - name: markupsafe - version: 3.0.2 - build: py312h178313f_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_1.conda sha256: 4a6bf68d2a2b669fecc9a4a009abd1cf8e72c2289522ff00d81b5a6e51ae78f5 md5: eb227c3e0bf58f5bd69c0532b157975b depends: @@ -3994,14 +3176,7 @@ packages: license_family: BSD size: 24604 timestamp: 1733219911494 -- kind: conda - name: mdurl - version: 0.1.2 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda sha256: 78c1bbe1723449c52b7a9df1af2ee5f005209f67e40b6e1d3c7619127c43b1c7 md5: 592132998493b3ff25fd7479396e8351 depends: @@ -4010,51 +3185,32 @@ packages: license_family: MIT size: 14465 timestamp: 1733255681319 -- kind: conda - name: meson - version: 1.6.1 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - sha256: 879bb593f409858158fd246e17d02c5a126fab46c9b9d4daa0d455f8516c6d7a - md5: 0062fb0a7f5da474705d0ce626de12f4 +- conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + sha256: 702c8b92e7d33463b2c3ed226c1d7c3e745d9dedb3d35d0fdca6a47ef32d7e78 + md5: 8e25221b702272394b86b0f4d7217f77 depends: - - ninja >=1.8.2 - python >=3.9 - - setuptools + - ninja >=1.8.2 + - python license: Apache-2.0 license_family: APACHE - size: 657135 - timestamp: 1734395829616 -- kind: conda - name: meson-python - version: 0.17.1 - build: pyh70fd9c4_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda - sha256: 819692fa23d1cfdc05a4106789b413c83de2d0506df2e872c0a705b0df42bc43 - md5: 7a02679229c6c2092571b4c025055440 - depends: - - meson >=0.63.3 + size: 726539 + timestamp: 1745942023589 +- conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda + sha256: e4866b9d6609cc69ac01822ae92caee8ec6533a1b770baadc26157f24e363de3 + md5: 576c04b9d9f8e45285fb4d9452c26133 + depends: + - meson >=1.2.3 - ninja - - packaging >=19.0 - - pyproject-metadata >=0.7.1 + - packaging >=23.2 + - pyproject-metadata >=0.9.0 - python >=3.9 - tomli >=1.0.0 license: MIT license_family: MIT - size: 74270 - timestamp: 1733419510995 -- kind: conda - name: mkl - version: 2024.2.2 - build: ha957f24_16 - build_number: 16 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/mkl-2024.2.2-ha957f24_16.conda + size: 81997 + timestamp: 1746449677114 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2024.2.2-ha957f24_16.conda sha256: 77906b0acead8f86b489da46f53916e624897338770dbf70b04b8f673c9273c1 md5: 1459379c79dda834673426504d52b319 depends: @@ -4066,13 +3222,7 @@ packages: license_family: Proprietary size: 124718448 timestamp: 1730231808335 -- kind: conda - name: mkl-devel - version: 2024.2.2 - build: ha770c72_16 - build_number: 16 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/mkl-devel-2024.2.2-ha770c72_16.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-devel-2024.2.2-ha770c72_16.conda sha256: d2c6fb42c36f1ec48fd34192827dee619791b2a4ee73798cbf3cd0e6195ceff4 md5: 140891ea14285fc634353b31e9e40a95 depends: @@ -4082,27 +3232,16 @@ packages: license_family: Proprietary size: 35857 timestamp: 1730232581563 -- kind: conda - name: mkl-include - version: 2024.2.2 - build: ha957f24_16 - build_number: 16 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/mkl-include-2024.2.2-ha957f24_16.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-include-2024.2.2-ha957f24_16.conda sha256: 4b72b3acd46c69a8fb56d9f5d8240da811820a18be40765df6e2bd8ea859fbc7 md5: 42b0d14354b5910a9f41e29289914f6b license: LicenseRef-IntelSimplifiedSoftwareOct2022 license_family: Proprietary size: 716756 timestamp: 1730232137421 -- kind: conda - name: ml_dtypes - version: 0.5.0 - build: py312hf9745cd_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.0-py312hf9745cd_0.conda - sha256: 559c14640ce8e3f2270da6130ba50ae624f3db56176fad29a5436b2dec3fc3b2 - md5: 8ca779f3f30b00181aeee820fe8b22d5 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.1-py312hf9745cd_0.conda + sha256: 87928a36d350c470455a322c4c2b82266b88322d0fd5187ae8cc6fb5e3aad61f + md5: c45ac8395a27736c27b2e50b53ffe62c depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 @@ -4111,15 +3250,9 @@ packages: - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 license: MPL-2.0 AND Apache-2.0 - size: 290054 - timestamp: 1726376440408 -- kind: conda - name: mpc - version: 1.3.1 - build: h24ddda3_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda + size: 290991 + timestamp: 1736538940686 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda sha256: 1bf794ddf2c8b3a3e14ae182577c624fa92dea975537accff4bc7e5fea085212 md5: aa14b9a5196a6d8dd364164b7ce56acf depends: @@ -4131,13 +3264,7 @@ packages: license_family: LGPL size: 116777 timestamp: 1725629179524 -- kind: conda - name: mpfr - version: 4.2.1 - build: h90cbb55_3 - build_number: 3 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda sha256: f25d2474dd557ca66c6231c8f5ace5af312efde1ba8290a6ea5e1732a4e669c0 md5: 2eeb50cab6652538eee8fc0bc3340c81 depends: @@ -4148,14 +3275,7 @@ packages: license_family: LGPL size: 634751 timestamp: 1725746740014 -- kind: conda - name: mpmath - version: 1.3.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda sha256: 7d7aa3fcd6f42b76bd711182f3776a02bef09a68c5f117d66b712a6d81368692 md5: 3585aa87c43ab15b167b574cd73b057b depends: @@ -4164,47 +3284,28 @@ packages: license_family: BSD size: 439705 timestamp: 1733302781386 -- kind: conda - name: nccl - version: 2.23.4.1 - build: h2b5d15b_3 - build_number: 3 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/nccl-2.23.4.1-h2b5d15b_3.conda - sha256: fb327a4623d8d7b967a49104a1a4b111efbfdff6d4a1619d93aa9f3244395b73 - md5: da69647cf84be91a201e2c4138e676ae +- conda: https://conda.anaconda.org/conda-forge/linux-64/nccl-2.26.5.1-ha44e49d_0.conda + sha256: 3a715dab311d045ecd5811b06012ebc7a1b8ce9c899d40952d834bd713fe9ac9 + md5: 45823c363ce0803d29c4a444e4309634 depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.0,<13.0a0 - - libgcc >=12 - - libstdcxx >=12 + - cuda-version >=12,<13.0a0 + - libgcc >=13 + - libstdcxx >=13 license: BSD-3-Clause license_family: BSD - size: 124592707 - timestamp: 1732655186186 -- kind: conda - name: ncurses - version: '6.5' - build: he02047a_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - sha256: 6a1d5d8634c1a07913f1c525db6455918cbc589d745fac46d9d6e30340c8731a - md5: 70caf8bb6cf39a0b6b7efc885f51c0fe + size: 180506014 + timestamp: 1746010496065 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 + md5: 47e340acb35de30501a76c7c799c41d7 depends: - __glibc >=2.17,<3.0.a0 - - libgcc-ng >=12 + - libgcc >=13 license: X11 AND BSD-3-Clause - size: 889086 - timestamp: 1724658547447 -- kind: conda - name: networkx - version: 3.4.2 - build: pyh267e887_2 - build_number: 2 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/networkx-3.4.2-pyh267e887_2.conda + size: 891641 + timestamp: 1738195959188 +- conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.4.2-pyh267e887_2.conda sha256: 39625cd0c9747fa5c46a9a90683b8997d8b9649881b3dc88336b13b7bdd60117 md5: fd40bf7f7f4bc4b647dc8512053d9873 depends: @@ -4219,29 +3320,20 @@ packages: license_family: BSD size: 1265008 timestamp: 1731521053408 -- kind: conda - name: ninja - version: 1.12.1 - build: h297d8ca_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - sha256: 40f7b76b07067935f8a5886aab0164067b7aa71eb5ad20b7278618c0c2c98e06 - md5: 3aa1c7e292afeff25a0091ddd7c69b72 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + sha256: 1f2f7e26084971e87bfbb733f17d824ff3323ee9618fb713ae9932386da76aed + md5: 2322531904f27501ee19847b87ba7c64 depends: - - libgcc-ng >=12 - - libstdcxx-ng >=12 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=13 + - libgcc >=13 license: Apache-2.0 - license_family: Apache - size: 2198858 - timestamp: 1715440571685 -- kind: conda - name: numpy - version: 2.2.1 - build: py312h7e784f5_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - sha256: d8ccc46c3d2149a1479b660f5c04acad45766ed04ef961ef022d71122ec2bb91 - md5: 6159cab400b61f38579a7692be5e630a + license_family: APACHE + size: 161883 + timestamp: 1745526264371 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + sha256: af293ba6f715983f71543ed0111e6bb77423d409c1d13062474601257c2eebca + md5: 505bcfc142b97010c162863c38d90016 depends: - __glibc >=2.17,<3.0.a0 - libblas >=3.9.0,<4.0a0 @@ -4255,64 +3347,29 @@ packages: - numpy-base <0a0 license: BSD-3-Clause license_family: BSD - size: 8483009 - timestamp: 1734904730019 -- kind: conda - name: openblas - version: 0.3.28 - build: pthreads_h6ec200e_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.28-pthreads_h6ec200e_1.conda - sha256: c558f49a262f43b0c5b6f9feb75b631d0b1eeba53579fd2bbce0df37f1884ef0 - md5: 8fe5d50db07e92519cc639cb0aef9b1b - depends: - - libopenblas 0.3.28 pthreads_h94d23a6_1 + size: 8543883 + timestamp: 1745119461819 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.29-pthreads_h6ec200e_0.conda + sha256: 49814ee7c946fc583ea9217dfaad23d6bb75868988062fdd921cedc79affd07a + md5: 7e4d48870b3258bea920d51b7f495a81 + depends: + - libopenblas 0.3.29 pthreads_h94d23a6_0 license: BSD-3-Clause license_family: BSD - size: 5727592 - timestamp: 1730772687576 -- kind: conda - name: openssl - version: 3.4.0 - build: h7b32b05_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - sha256: f62f6bca4a33ca5109b6d571b052a394d836956d21b25b7ffd03376abf7a481f - md5: 4ce6875f75469b2757a65e10a5d05e31 + size: 6054007 + timestamp: 1739825745796 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda + sha256: b4491077c494dbf0b5eaa6d87738c22f2154e9277e5293175ec187634bd808a0 + md5: de356753cfdbffcde5bb1e86e3aa6cd0 depends: - __glibc >=2.17,<3.0.a0 - ca-certificates - libgcc >=13 license: Apache-2.0 license_family: Apache - size: 2937158 - timestamp: 1736086387286 -- kind: conda - name: opt-einsum - version: 3.4.0 - build: hd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/opt-einsum-3.4.0-hd8ed1ab_1.conda - sha256: 8db3d841c72f184de69e1237b900a2d79c742e30e8378973814543bf987b6bc6 - md5: b94f689d8b1ce7dd212946e0331037ad - depends: - - opt_einsum >=3.4.0,<3.4.1.0a0 - license: MIT - license_family: MIT - size: 6558 - timestamp: 1733688054327 -- kind: conda - name: opt_einsum - version: 3.4.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/opt_einsum-3.4.0-pyhd8ed1ab_1.conda + size: 3117410 + timestamp: 1746223723843 +- conda: https://conda.anaconda.org/conda-forge/noarch/opt_einsum-3.4.0-pyhd8ed1ab_1.conda sha256: af71aabb2bfa4b2c89b7b06403e5cec23b418452cae9f9772bd7ac3f9ea1ff44 md5: 52919815cd35c4e1a0298af658ccda04 depends: @@ -4321,47 +3378,42 @@ packages: license_family: MIT size: 62479 timestamp: 1733688053334 -- kind: conda - name: packaging - version: '24.2' - build: pyhd8ed1ab_2 - build_number: 2 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - sha256: da157b19bcd398b9804c5c52fc000fcb8ab0525bdb9c70f95beaa0bb42f85af1 - md5: 3bfed7e6228ebf2f7b9eaa47f1b4e2aa +- conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.15.0-py312h68727a3_0.conda + sha256: b21dd4f339084a1db068b89cbc894954a3eb12f170ad646f3e8e3567438f4585 + md5: 9664a035da52d38121b1f75b27f2c471 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + - typing-extensions >=4.5 + license: Apache-2.0 + license_family: Apache + size: 413963 + timestamp: 1744034409842 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + sha256: 289861ed0c13a15d7bbb408796af4de72c2fe67e2bcb0de98f4c3fce259d7991 + md5: 58335b26c38bf4a20f399384c33cbcf9 depends: - python >=3.8 + - python license: Apache-2.0 license_family: APACHE - size: 60164 - timestamp: 1733203368787 -- kind: conda - name: pip - version: 24.3.1 - build: pyh8b19718_2 - build_number: 2 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda - sha256: da8c8888de10c1e4234ebcaa1550ac2b4b5408ac20f093fe641e4bc8c9c9f3eb - md5: 04e691b9fadd93a8a9fad87a81d4fd8f + size: 62477 + timestamp: 1745345660407 +- conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda + sha256: ebfa591d39092b111b9ebb3210eb42251be6da89e26c823ee03e5e838655a43e + md5: 32d0781ace05105cc99af55d36cbec7c depends: - python >=3.9,<3.13.0a0 - setuptools - wheel license: MIT license_family: MIT - size: 1245116 - timestamp: 1734466348103 -- kind: conda - name: pkg-config - version: 0.29.2 - build: h4bc722e_1009 - build_number: 1009 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda + size: 1242995 + timestamp: 1746249983238 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda sha256: c9601efb1af5391317e04eca77c6fe4d716bf1ca1ad8da2a05d15cb7c28d7d4e md5: 1bee70681f504ea424fb07cdb090c001 depends: @@ -4371,30 +3423,17 @@ packages: license_family: GPL size: 115175 timestamp: 1720805894943 -- kind: conda - name: platformdirs - version: 4.3.6 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda - sha256: bb50f6499e8bc1d1a26f17716c97984671121608dc0c3ecd34858112bce59a27 - md5: 577852c7e53901ddccc7e6a9959ddebe +- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda + sha256: ae7d3e58224d53d6b59e1f5ac5809803bb1972f0ac4fb10cd9b8c87d4122d3e0 + md5: e57da6fe54bb3a5556cf36d199ff07d8 depends: - python >=3.9 + - python license: MIT license_family: MIT - size: 20448 - timestamp: 1733232756001 -- kind: conda - name: pluggy - version: 1.5.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda + size: 23291 + timestamp: 1742485085457 +- conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda sha256: 122433fc5318816b8c69283aaf267c73d87aa2d09ce39f64c9805c9a3b264819 md5: e9dcbce5f45f9ee500e728ae58b605b6 depends: @@ -4403,14 +3442,7 @@ packages: license_family: MIT size: 23595 timestamp: 1733222855563 -- kind: conda - name: ply - version: '3.11' - build: pyhd8ed1ab_3 - build_number: 3 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda sha256: bae453e5cecf19cab23c2e8929c6e30f4866d996a8058be16c797ed4b935461f md5: fd5062942bfa1b0bd5e0d2a4397b099e depends: @@ -4419,14 +3451,7 @@ packages: license_family: BSD size: 49052 timestamp: 1733239818090 -- kind: conda - name: pooch - version: 1.8.2 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda sha256: bedda6b36e8e42b0255179446699a0cf08051e6d9d358dd0dd0e787254a3620e md5: b3e783e8e8ed7577cf0b6dee37d1fbac depends: @@ -4438,14 +3463,7 @@ packages: license_family: BSD size: 54116 timestamp: 1733421432357 -- kind: conda - name: pybind11 - version: 2.13.6 - build: pyh1ec8472_2 - build_number: 2 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyh1ec8472_2.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyh1ec8472_2.conda sha256: 27f888492af3d5ab19553f263b0015bf3766a334668b5b3a79c7dc0416e603c1 md5: 8088a5e7b2888c780738c3130f2a969d depends: @@ -4457,14 +3475,7 @@ packages: license_family: BSD size: 186375 timestamp: 1730237816231 -- kind: conda - name: pybind11-global - version: 2.13.6 - build: pyh415d2e4_2 - build_number: 2 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh415d2e4_2.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh415d2e4_2.conda sha256: 9ff0d61d86878f81779bdb7e47656a75feaab539893462cff29b8ec353026d81 md5: 120541563e520d12d8e39abd7de9092c depends: @@ -4476,14 +3487,7 @@ packages: license_family: BSD size: 179139 timestamp: 1730237481227 -- kind: conda - name: pycparser - version: '2.22' - build: pyh29332c3_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda sha256: 79db7928d13fab2d892592223d7570f5061c192f27b9febd1a418427b719acc6 md5: 12c566707c80111f9799308d9e265aef depends: @@ -4493,13 +3497,7 @@ packages: license_family: BSD size: 110100 timestamp: 1733195786147 -- kind: conda - name: pydevtool - version: 0.3.0 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 sha256: 4171325c030b75bba1f5f83465a2ed0f71bcfbcf90f779844a71a54d18bd407c md5: e357fb5ecdaae7474f523094e8339dc5 depends: @@ -4509,45 +3507,26 @@ packages: license_family: MIT size: 15067 timestamp: 1659947454808 -- kind: conda - name: pygments - version: 2.19.1 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda sha256: 28a3e3161390a9d23bc02b4419448f8d27679d9e2c250e29849e37749c8de86b md5: 232fb4577b6687b2d503ef8e254270c9 depends: - python >=3.9 license: BSD-2-Clause + license_family: BSD size: 888600 timestamp: 1736243563082 -- kind: conda - name: pyproject-metadata - version: 0.9.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda - sha256: 5429009e692778a3a67e4435df5266f1d7bdb9477091d2742a232c9697b25316 - md5: 1239146a53a383a84633800294120f17 +- conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda + sha256: 7eea506a4296ff86ccd1f3f07dfd262b2ee1970886d53185b2b975abc6b506b5 + md5: 22ae7c6ea81e0c8661ef32168dda929b depends: - packaging >=19.0 - python >=3.9 license: MIT license_family: MIT - size: 21364 - timestamp: 1733316711254 -- kind: conda - name: pyproject_hooks - version: 1.2.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda + size: 21982 + timestamp: 1741654784592 +- conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda sha256: 065ac44591da9abf1ff740feb25929554b920b00d09287a551fcced2c9791092 md5: d4582021af437c931d7d77ec39007845 depends: @@ -4557,14 +3536,7 @@ packages: license_family: MIT size: 15528 timestamp: 1733710122949 -- kind: conda - name: pysocks - version: 1.7.1 - build: pyha55dd90_7 - build_number: 7 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 md5: 461219d1a5bd61342293efa2c0c90eac depends: @@ -4574,16 +3546,9 @@ packages: license_family: BSD size: 21085 timestamp: 1733217331982 -- kind: conda - name: pytest - version: 8.3.4 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - sha256: 75245ca9d0cbd6d38bb45ec02430189a9d4c21c055c5259739d738a2298d61b3 - md5: 799ed216dc6af62520f32aa39bc1c2bb +- conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda + sha256: 963524de7340c56615583ba7b97a6beb20d5c56a59defb59724dc2a3105169c9 + md5: c3c9316209dec74a705a36797970c6be depends: - colorama - exceptiongroup >=1.0.0rc8 @@ -4596,16 +3561,9 @@ packages: - pytest-faulthandler >=2 license: MIT license_family: MIT - size: 259195 - timestamp: 1733217599806 -- kind: conda - name: pytest-xdist - version: 3.6.1 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda + size: 259816 + timestamp: 1740946648058 +- conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda sha256: fb35da93084d653b86918c200abb2f0b88aceb3b0526c6aaa21b844f565ae237 md5: 59aad4fb37cabc0bacc73cf344612ddd depends: @@ -4618,46 +3576,33 @@ packages: license_family: MIT size: 38147 timestamp: 1733240891538 -- kind: conda - name: python - version: 3.12.8 - build: h9e4cc4f_1_cpython - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda - sha256: 3f0e0518c992d8ccfe62b189125721309836fe48a010dc424240583e157f9ff0 - md5: 7fd2fd79436d9b473812f14e86746844 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda + sha256: 4dc1da115805bd353bded6ab20ff642b6a15fcc72ac2f3de0e1d014ff3612221 + md5: a41d26cd4d47092d683915d058380dec depends: - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 - ld_impl_linux-64 >=2.36.1 - - libexpat >=2.6.4,<3.0a0 - - libffi >=3.4,<4.0a0 + - libexpat >=2.7.0,<3.0a0 + - libffi >=3.4.6,<3.5.0a0 - libgcc >=13 - - liblzma >=5.6.3,<6.0a0 + - liblzma >=5.8.1,<6.0a0 - libnsl >=2.0.1,<2.1.0a0 - - libsqlite >=3.47.0,<4.0a0 + - libsqlite >=3.49.1,<4.0a0 - libuuid >=2.38.1,<3.0a0 - libxcrypt >=4.4.36 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.4.0,<4.0a0 + - openssl >=3.5.0,<4.0a0 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata constrains: - python_abi 3.12.* *_cp312 license: Python-2.0 - size: 31565686 - timestamp: 1733410597922 -- kind: conda - name: python-build - version: 1.2.2.post1 - build: pyhff2d567_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda + size: 31279179 + timestamp: 1744325164633 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda sha256: da40ab7413029351852268ca479e5cc642011c72317bd02dba28235c5c5ec955 md5: 0903621fe8a9f37286596529528f4f74 depends: @@ -4673,29 +3618,17 @@ packages: license_family: MIT size: 25108 timestamp: 1733230700715 -- kind: conda - name: python_abi - version: '3.12' - build: 5_cp312 - build_number: 5 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda - sha256: d10e93d759931ffb6372b45d65ff34d95c6000c61a07e298d162a3bc2accebb0 - md5: 0424ae29b104430108f5218a66db7260 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda + build_number: 7 + sha256: a1bbced35e0df66cc713105344263570e835625c28d1bdee8f748f482b2d7793 + md5: 0dfcdc155cf23812a0c9deada86fb723 constrains: - python 3.12.* *_cpython license: BSD-3-Clause license_family: BSD - size: 6238 - timestamp: 1723823388266 -- kind: conda - name: pythran - version: 0.17.0 - build: pyh47d16e9_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda + size: 6971 + timestamp: 1745258861359 +- conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda sha256: 80abbf25ed5a7f3a45e62b174d02cd9c72566161bacc385796a58e511eb3a6ee md5: 97a9ef430424566db8115934440a99d0 depends: @@ -4712,189 +3645,155 @@ packages: license_family: BSD size: 1948748 timestamp: 1732322355621 -- kind: conda - name: pytorch - version: 2.5.1 - build: cpu_mkl_py312_heeca0f5_108 - build_number: 108 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.5.1-cpu_mkl_py312_heeca0f5_108.conda - sha256: 47d6733d7d23e8d719636a901f08362f08cb7d39ca435fa9762dae29b8daa0f8 - md5: d2dc4c7e49475cb141cb14e8329bb005 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.7.0-cpu_mkl_py312_h6a7998d_100.conda + sha256: 5c4a340f7a729bcdc19c530b25ed71ed5239f5ad0e907c49f03d88efd5b3be75 + md5: c67501107a48c049f18e8cb7c7e800b2 depends: - __glibc >=2.17,<3.0.a0 + - _openmp_mutex * *_llvm - _openmp_mutex >=4.5 - filelock - fsspec - jinja2 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250127.1,<20250128.0a0 + - libblas * *mkl - libcblas >=3.9.0,<4.0a0 - libgcc >=13 - - libprotobuf >=5.28.3,<5.28.4.0a0 + - libprotobuf >=5.29.3,<5.29.4.0a0 - libstdcxx >=13 - - libtorch 2.5.1.* - - libuv >=1.49.2,<2.0a0 + - libtorch 2.7.0 cpu_mkl_hf6ddc5a_100 + - libuv >=1.50.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - llvm-openmp >=20.1.4 - mkl >=2024.2.2,<2025.0a0 - networkx - numpy >=1.19,<3 + - optree >=0.13.0 + - pybind11 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - - setuptools - - sleef >=3.7,<4.0a0 - - sympy >=1.13.1,!=1.13.2 - - typing_extensions + - setuptools <76 + - sleef >=3.8,<4.0a0 + - sympy >=1.13.3 + - typing_extensions >=4.10.0 constrains: - - pytorch-cpu ==2.5.1 - - pytorch-gpu ==99999999 + - pytorch-gpu <0.0a0 + - pytorch-cpu 2.7.0 license: BSD-3-Clause license_family: BSD - size: 37116444 - timestamp: 1736091774147 -- kind: conda - name: pytorch - version: 2.5.1 - build: cuda126_py312h1763f6d_306 - build_number: 306 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.5.1-cuda126_py312h1763f6d_306.conda - sha256: b614a2929a40191b0b333510401d11a3e78d7cccd9a4dafc223f4c45510d7d8e - md5: 48e79c8ee23c4527cbbea4c9ee8665c3 + size: 28982129 + timestamp: 1746260259104 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.7.0-cuda126_mkl_py312_h30b5a27_300.conda + sha256: f47c03eed5ead66344b80e71a8d87902c36ad32f2c8b19b793cc39995d1180f8 + md5: f2d5af2065419f03f2e393d640096efb depends: - __cuda - __glibc >=2.17,<3.0.a0 + - _openmp_mutex * *_llvm - _openmp_mutex >=4.5 - cuda-cudart >=12.6.77,<13.0a0 + - cuda-cupti >=12.6.80,<13.0a0 - cuda-nvrtc >=12.6.85,<13.0a0 - cuda-nvtx >=12.6.77,<13.0a0 - cuda-version >=12.6,<13 - - cudnn >=9.3.0.75,<10.0a0 + - cudnn >=9.8.0.87,<10.0a0 - filelock - fsspec - jinja2 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250127.1,<20250128.0a0 + - libblas * *mkl - libcblas >=3.9.0,<4.0a0 - libcublas >=12.6.4.1,<13.0a0 + - libcudss >=0.5.0.16,<0.5.1.0a0 - libcufft >=11.3.0.4,<12.0a0 - libcufile >=1.11.1.6,<2.0a0 - libcurand >=10.3.7.77,<11.0a0 - libcusolver >=11.7.1.2,<12.0a0 - libcusparse >=12.5.4.2,<13.0a0 - - libgcc >=12 - - libmagma >=2.8.0,<2.8.1.0a0 - - libmagma_sparse >=2.8.0,<2.8.1.0a0 - - libprotobuf >=5.28.2,<5.28.3.0a0 - - libstdcxx >=12 - - libtorch 2.5.1.* - - libuv >=1.49.2,<2.0a0 + - libgcc >=13 + - libmagma >=2.9.0,<2.9.1.0a0 + - libprotobuf >=5.29.3,<5.29.4.0a0 + - libstdcxx >=13 + - libtorch 2.7.0 cuda126_mkl_h99b69db_300 + - libuv >=1.50.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - llvm-openmp >=20.1.4 - mkl >=2024.2.2,<2025.0a0 - - nccl >=2.23.4.1,<3.0a0 + - nccl >=2.26.5.1,<3.0a0 - networkx - numpy >=1.19,<3 + - optree >=0.13.0 + - pybind11 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - - setuptools - - sleef >=3.7,<4.0a0 - - sympy >=1.13.1,!=1.13.2 - - typing_extensions + - setuptools <76 + - sleef >=3.8,<4.0a0 + - sympy >=1.13.3 + - triton 3.3.0.* + - typing_extensions >=4.10.0 constrains: - - pytorch-gpu ==2.5.1 - - pytorch-cpu ==99999999 + - pytorch-gpu 2.7.0 + - pytorch-cpu <0.0a0 license: BSD-3-Clause license_family: BSD - size: 36996719 - timestamp: 1733652371588 -- kind: conda - name: pytorch-cpu - version: 2.5.1 - build: cpu_mkl_hc60beec_108 - build_number: 108 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/pytorch-cpu-2.5.1-cpu_mkl_hc60beec_108.conda - sha256: bbb8a46767dc12fb35423aa561e02389aef87656721fbd3f87969d40f03e4664 - md5: 8135dc47e3dcbd4b3d83ad5b48e50ecb - depends: - - pytorch 2.5.1 cpu_mkl*108 + size: 29145540 + timestamp: 1746284384314 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-cpu-2.7.0-cpu_mkl_hc60beec_100.conda + sha256: f972760cda01fff159c56925036b8e6e2c39a8a8414b973ab5303912b3ff3f3a + md5: 20b3051f55ad823a27818dfa46a41c8f + depends: + - pytorch 2.7.0 cpu_mkl*100 track_features: - pytorch-cpu license: BSD-3-Clause license_family: BSD - size: 31886 - timestamp: 1736092702692 -- kind: conda - name: pytorch-gpu - version: 2.5.1 - build: cuda126ha999a5f_306 - build_number: 306 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/pytorch-gpu-2.5.1-cuda126ha999a5f_306.conda - sha256: 080567e694289a7ecb7d62507e077c5db136bc7dc76bf083c1860eab2e1e4b41 - md5: 7690f1240c476f529f8e3f9ffc79ca8e - depends: - - pytorch 2.5.1 cuda*306 + size: 47101 + timestamp: 1746261172719 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-gpu-2.7.0-cuda126_mkl_ha999a5f_300.conda + sha256: e1162a51e77491abae15f6b651ba8f064870181d57d40f9168747652d0f70cb0 + md5: 84ecafc34c6f8933c2c9b00204832e38 + depends: + - pytorch 2.7.0 cuda*_mkl*300 license: BSD-3-Clause license_family: BSD - size: 24610 - timestamp: 1733656490429 -- kind: conda - name: rdma-core - version: '55.0' - build: h5888daf_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/rdma-core-55.0-h5888daf_0.conda - sha256: 3715a51f1ea6e3765f19b6db90a7edb77a3b5aa201a4f09cbd51a678e8609a88 - md5: fd94951ea305bdfe6fb3939db3fb7ce2 + size: 47219 + timestamp: 1746288556375 +- conda: https://conda.anaconda.org/conda-forge/linux-64/rdma-core-57.0-h5888daf_0.conda + sha256: fbb4599ba969c49d2280c84af196c514c49a3ad1529c693f4b6ac6c705998ec8 + md5: e5be997517f19a365b8b111b888be426 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 - libnl >=3.11.0,<4.0a0 - libstdcxx >=13 - - libsystemd0 >=256.9 - - libudev1 >=256.9 + - libsystemd0 >=257.4 + - libudev1 >=257.4 license: Linux-OpenIB license_family: BSD - size: 1223940 - timestamp: 1734115241096 -- kind: conda - name: re2 - version: 2024.07.02 - build: h9925aae_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_2.conda - sha256: d213c44958d49ce7e0d4d5b81afec23640cce5016685dbb2d23571a99caa4474 - md5: e84ddf12bde691e8ec894b00ea829ddf - depends: - - libre2-11 2024.07.02 hbbce691_2 + size: 1238038 + timestamp: 1745325325058 +- conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_3.conda + sha256: 66d34e3b4881f856486d11914392c585713100ca547ccfc0947f3a4765c2c486 + md5: 6f445fb139c356f903746b2b91bbe786 + depends: + - libre2-11 2024.07.02 hba17884_3 license: BSD-3-Clause license_family: BSD - size: 26786 - timestamp: 1735541074034 -- kind: conda - name: readline - version: '8.2' - build: h8228510_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda - sha256: 5435cf39d039387fbdc977b0a762357ea909a7694d9528ab40f005e9208744d7 - md5: 47d31b792659ce70f470b5c82fdfb7a4 + size: 26811 + timestamp: 1741121137599 +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda + sha256: 2d6d0c026902561ed77cd646b5021aef2d4db22e57a5b0178dfc669231e06d2c + md5: 283b96675859b20a825f8fa30f311446 depends: - - libgcc-ng >=12 - - ncurses >=6.3,<7.0a0 + - libgcc >=13 + - ncurses >=6.5,<7.0a0 license: GPL-3.0-only license_family: GPL - size: 281456 - timestamp: 1679532220005 -- kind: conda - name: requests - version: 2.32.3 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda + size: 282480 + timestamp: 1740379431762 +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda sha256: d701ca1136197aa121bbbe0e8c18db6b5c94acbd041c2b43c70e5ae104e1d8ad md5: a9b9368f3701a417eac9edbcae7cb737 depends: @@ -4909,50 +3808,33 @@ packages: license_family: APACHE size: 58723 timestamp: 1733217126197 -- kind: conda - name: rich - version: 13.9.4 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - sha256: 06a760c5ae572e72e865d5a87e9fe3cc171e1a9c996e63daf3db52ff1a0b4457 - md5: 7aed65d4ff222bfb7335997aa40b7da5 +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + sha256: d10e2b66a557ec6296844e04686db87818b0df87d73c06388f2332fda3f7d2d5 + md5: 202f08242192ce3ed8bdb439ba40c0fe depends: - markdown-it-py >=2.2.0 - pygments >=2.13.0,<3.0.0 - python >=3.9 - typing_extensions >=4.0.0,<5.0.0 + - python license: MIT license_family: MIT - size: 185646 - timestamp: 1733342347277 -- kind: conda - name: rich-click - version: 1.8.5 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - sha256: 2650d0d3423637fefe4b4b21a061ef6c78d62a571f92c6e51f5c665b499a8012 - md5: 6fe1844fe7a1f819966d86b415e2e721 + size: 200323 + timestamp: 1743371105291 +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + sha256: 9f3f08cb40d67e35199509ef8ff0045edc81c749e0c162c0da7390e974a3b7fc + md5: 9ade2651d83e7be4fa663adacaad984c depends: - click >=7,<9 - python >=3.9 - rich >=10.7 license: MIT license_family: MIT - size: 33968 - timestamp: 1734498970328 -- kind: conda - name: scipy - version: 1.15.0 - build: py312h180e4f1_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.0-py312h180e4f1_0.conda - sha256: 1b15a1ca749d5d6d9ff5e2f380f3a72c23edc316abdaa094b1578e4275146636 - md5: 66004839e9394a241b483436a9742845 + size: 34369 + timestamp: 1741676747646 +- conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py312ha707e6e_0.conda + sha256: b9faaa024b77a3678a988c5a490f02c4029c0d5903998b585100e05bc7d4ff36 + md5: 00b999c5f9d01fb633db819d79186bd4 depends: - __glibc >=2.17,<3.0.a0 - libblas >=3.9.0,<4.0a0 @@ -4962,73 +3844,56 @@ packages: - libgfortran5 >=13.3.0 - liblapack >=3.9.0,<4.0a0 - libstdcxx >=13 - - numpy <2.3 + - numpy <2.5 - numpy >=1.19,<3 - numpy >=1.23.5 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD - size: 18664052 - timestamp: 1736010660548 -- kind: conda - name: setuptools - version: 75.6.0 - build: pyhff2d567_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - sha256: abb12e1dd515b13660aacb5d0fd43835bc2186cab472df25b7716cd65e095111 - md5: fc80f7995e396cbaeabd23cf46c413dc + size: 17064784 + timestamp: 1739791925628 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda + sha256: 91d664ace7c22e787775069418daa9f232ee8bafdd0a6a080a5ed2395a6fa6b2 + md5: 9bddfdbf4e061821a1a443f93223be61 + depends: + - python >=3.9 + license: MIT + license_family: MIT + size: 777736 + timestamp: 1740654030775 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.1.0-pyhff2d567_0.conda + sha256: 777d34ed359cedd5a5004c930077c101bb3b70e5fbb04d29da5058d75b0ba487 + md5: f6f72d0837c79eaec77661be43e8a691 depends: - python >=3.9 license: MIT license_family: MIT - size: 774252 - timestamp: 1732632769210 -- kind: conda - name: sleef - version: '3.7' - build: h1b44611_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.7-h1b44611_2.conda - sha256: 38ad951d30052522693d21b247105744c7c6fb7cefcf41edca36f0688322e76d - md5: 4792f3259c6fdc0b730563a85b211dc0 + size: 778484 + timestamp: 1746085063737 +- conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.8-h1b44611_0.conda + sha256: c998d5a29848ce9ff1c53ba506e7d01bbd520c39bbe72e2fb7cdf5a53bad012f + md5: aec4dba5d4c2924730088753f6fa164b depends: - __glibc >=2.17,<3.0.a0 - _openmp_mutex >=4.5 - libgcc >=13 - libstdcxx >=13 license: BSL-1.0 - size: 1919287 - timestamp: 1731180933533 -- kind: conda - name: sortedcontainers - version: 2.4.0 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - sha256: 0cea408397d50c2afb2d25e987ebac4546ae11e549d65b1403d80dc368dfaaa6 - md5: 6d6552722448103793743dabfbda532d - depends: - - python >=2.7 + size: 1920152 + timestamp: 1738089391074 +- conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + sha256: d1e3e06b5cf26093047e63c8cc77b70d970411c5cbc0cb1fad461a8a8df599f7 + md5: 0401a17ae845fa72c7210e206ec5647d + depends: + - python >=3.9 license: Apache-2.0 license_family: APACHE - size: 26314 - timestamp: 1621217159824 -- kind: conda - name: sympy - version: 1.13.3 - build: pyh2585a3b_105 - build_number: 105 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/sympy-1.13.3-pyh2585a3b_105.conda - sha256: 929d939c5a8bcdc10a17501890918da68cf14a5883b36fddf77b8f0fbf040be2 - md5: 254cd5083ffa04d96e3173397a3d30f4 + size: 28657 + timestamp: 1738440459037 +- conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_105.conda + sha256: 09d3b6ac51d437bc996ad006d9f749ca5c645c1900a854a6c8f193cbd13f03a8 + md5: 8c09fac3785696e1c477156192d64b91 depends: - __unix - cpython @@ -5037,16 +3902,9 @@ packages: - python >=3.9 license: BSD-3-Clause license_family: BSD - size: 4523617 - timestamp: 1736248315124 -- kind: conda - name: sysroot_linux-64 - version: '2.17' - build: h0157908_18 - build_number: 18 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda + size: 4616621 + timestamp: 1745946173026 +- conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda sha256: 69ab5804bdd2e8e493d5709eebff382a72fab3e9af6adf93a237ccf8f7dbd624 md5: 460eba7851277ec1fd80a1a24080787a depends: @@ -5056,13 +3914,7 @@ packages: license_family: GPL size: 15166921 timestamp: 1735290488259 -- kind: conda - name: tbb - version: 2021.13.0 - build: hceb3a55_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-hceb3a55_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-hceb3a55_1.conda sha256: 65463732129899770d54b1fbf30e1bb82fdebda9d7553caf08d23db4590cd691 md5: ba7726b8df7b9d34ea80e82b097a4893 depends: @@ -5074,28 +3926,16 @@ packages: license_family: APACHE size: 175954 timestamp: 1732982638805 -- kind: conda - name: threadpoolctl - version: 3.5.0 - build: pyhc1e730c_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda - sha256: 45e402941f6bed094022c5726a2ca494e6224b85180d2367fb6ddd9aea68079d - md5: df68d78237980a159bd7149f33c0e8fd +- conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda + sha256: 6016672e0e72c4cf23c0cf7b1986283bd86a9c17e8d319212d78d8e9ae42fdfd + md5: 9d64911b31d57ca443e9f1e36b04385f depends: - - python >=3.8 + - python >=3.9 license: BSD-3-Clause license_family: BSD - size: 23548 - timestamp: 1714400228771 -- kind: conda - name: tk - version: 8.6.13 - build: noxft_h4845f30_101 - build_number: 101 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda + size: 23869 + timestamp: 1741878358548 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda sha256: e0569c9caa68bf476bead1bed3d79650bb080b532c64a4af7d8ca286c08dea4e md5: d453b98d9c83e71da0741bb0ff4d76bc depends: @@ -5105,14 +3945,7 @@ packages: license_family: BSD size: 3318875 timestamp: 1699202167581 -- kind: conda - name: tomli - version: 2.2.1 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda sha256: 18636339a79656962723077df9a56c0ac7b8a864329eb8f847ee3d38495b863e md5: ac944244f1fed2eb49bae07193ae8215 depends: @@ -5121,43 +3954,56 @@ packages: license_family: MIT size: 19167 timestamp: 1733256819729 -- kind: conda - name: typing_extensions - version: 4.12.2 - build: pyha770c72_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - sha256: 337be7af5af8b2817f115b3b68870208b30c31d3439bec07bfb2d8f4823e3568 - md5: d17f13df8b65464ca316cbc000a3cb64 +- conda: https://conda.anaconda.org/conda-forge/linux-64/triton-3.3.0-cuda126py312hebffaa9_1.conda + sha256: 7089c27a38fc3ec199af4d51fcbba33720281f3098e984c49a9f010805d2de84 + md5: a05b9a73fe6a9be82a2fc4af2b01e95f + depends: + - python + - setuptools + - cuda-nvcc-tools + - cuda-cuobjdump + - cuda-cudart + - cuda-cupti + - cuda-version >=12.6,<13 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=13 + - libgcc >=13 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.12.* *_cp312 + - libzlib >=1.3.1,<2.0a0 + - cuda-cupti >=12.6.80,<13.0a0 + license: MIT + license_family: MIT + size: 163144991 + timestamp: 1746164460128 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.13.2-h0e9735f_0.conda + sha256: 4865fce0897d3cb0ffc8998219157a8325f6011c136e6fd740a9a6b169419296 + md5: 568ed1300869dca0ba09fb750cda5dbb + depends: + - typing_extensions ==4.13.2 pyh29332c3_0 + license: PSF-2.0 + license_family: PSF + size: 89900 + timestamp: 1744302253997 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + sha256: a8aaf351e6461de0d5d47e4911257e25eec2fa409d71f3b643bb2f748bde1c08 + md5: 83fc6ae00127671e301c9f44254c31b8 depends: - python >=3.9 + - python license: PSF-2.0 license_family: PSF - size: 39637 - timestamp: 1733188758212 -- kind: conda - name: tzdata - version: 2024b - build: hc8b5060_0 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - sha256: 4fde5c3008bf5d2db82f2b50204464314cc3c91c1d953652f7bd01d9e52aefdf - md5: 8ac3367aafb1cc0a068483c580af8015 + size: 52189 + timestamp: 1744302253997 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + sha256: 5aaa366385d716557e365f0a4e9c3fca43ba196872abbbe3d56bb610d131e192 + md5: 4222072737ccff51314b5ece9c7d6f5a license: LicenseRef-Public-Domain - size: 122354 - timestamp: 1728047496079 -- kind: conda - name: urllib3 - version: 2.3.0 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda - sha256: 114919ffa80c328127dab9c8e7a38f9d563c617691fb81fccb11c1e86763727e - md5: 32674f8dbfb7b26410ed580dd3c10a29 + size: 122968 + timestamp: 1742727099393 +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda + sha256: a25403b76f7f03ca1a906e1ef0f88521edded991b9897e7fed56a3e334b3db8c + md5: c1e349028e0052c4eea844e94f773065 depends: - brotli-python >=1.0.9 - h2 >=4,<5 @@ -5166,16 +4012,9 @@ packages: - zstandard >=0.18.0 license: MIT license_family: MIT - size: 100102 - timestamp: 1734859520452 -- kind: conda - name: wheel - version: 0.45.1 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda + size: 100791 + timestamp: 1744323705540 +- conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda sha256: 1b34021e815ff89a4d902d879c3bd2040bc1bd6169b32e9427497fa05c55f1ce md5: 75cb7132eb58d97896e173ef12ac9986 depends: @@ -5184,14 +4023,7 @@ packages: license_family: MIT size: 62931 timestamp: 1733130309598 -- kind: conda - name: zipp - version: 3.21.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda sha256: 567c04f124525c97a096b65769834b7acb047db24b15a56888a322bf3966c3e1 md5: 0c3cc595284c5e8f0f9900a9b228a332 depends: @@ -5200,40 +4032,28 @@ packages: license_family: MIT size: 21809 timestamp: 1732827613585 -- kind: conda - name: zstandard - version: 0.23.0 - build: py312hef9b889_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - sha256: b97015e146437283f2213ff0e95abdc8e2480150634d81fbae6b96ee09f5e50b - md5: 8b7069e9792ee4e5b4919a7a306d2e67 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + sha256: ff62d2e1ed98a3ec18de7e5cf26c0634fd338cb87304cf03ad8cbafe6fe674ba + md5: 630db208bc7bbb96725ce9832c7423bb depends: - __glibc >=2.17,<3.0.a0 - cffi >=1.11 - libgcc >=13 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - - zstd >=1.5.6,<1.5.7.0a0 - - zstd >=1.5.6,<1.6.0a0 license: BSD-3-Clause license_family: BSD - size: 419552 - timestamp: 1725305670210 -- kind: conda - name: zstd - version: 1.5.6 - build: ha6fb4c9_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda - sha256: c558b9cc01d9c1444031bd1ce4b9cff86f9085765f17627a6cd85fc623c8a02b - md5: 4d056880988120e29d75bfff282e0f45 + size: 732224 + timestamp: 1745869780524 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda + sha256: a4166e3d8ff4e35932510aaff7aa90772f84b4d07e9f6f83c614cba7ceefe0eb + md5: 6432cb5d4ac0046c3ac0a8a0f95842f9 depends: - - libgcc-ng >=12 - - libstdcxx-ng >=12 - - libzlib >=1.2.13,<2.0.0a0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + - libzlib >=1.3.1,<2.0a0 license: BSD-3-Clause license_family: BSD - size: 554846 - timestamp: 1714722996770 + size: 567578 + timestamp: 1742433379869 diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 8c8c18f80661..194aaf5fb06b 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -46,6 +46,7 @@ jobs: # Gets the correct commit message for pull request with: ref: ${{ github.event.pull_request.head.sha }} + - name: Get commit message id: commit_message run: | @@ -56,7 +57,6 @@ jobs: RUN="1" fi echo "message=$RUN" >> $GITHUB_OUTPUT - echo github.ref ${{ github.ref }} build_wheels: name: Wheel, ${{ matrix.python[0] }}-${{ matrix.buildplat[1] }} diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index b1d04d64df2a..19b848c047b8 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -21,8 +21,8 @@ jobs: name: Get commit message uses: ./.github/workflows/commit_message.yml - fast_dev_py_fail_slow: - name: fail slow, fast, py3.12/npAny, dev.py + fast_spin: + name: fast, py3.12/npAny, spin needs: get_commit_message # Ensure (a) this doesn't run on forks by default, and # (b) it does run with Act locally (`github` doesn't exist there) @@ -35,6 +35,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: recursive + - name: Setup Python uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: @@ -49,23 +50,23 @@ jobs: - name: pip-packages run: | - pip install numpy cython pybind11 pythran meson ninja pytest pytest-xdist pytest-timeout pytest-fail-slow pooch rich_click click doit pydevtool hypothesis + pip install numpy cython pybind11 pythran meson ninja pytest pytest-xdist pytest-timeout pooch spin hypothesis python -m pip install -r requirements/openblas.txt - name: Build run: | - python dev.py build --with-scipy-openblas + spin build --with-scipy-openblas - name: Test run: | # test runner parallel clashes with OpenBLAS multithreading $env:OPENBLAS_NUM_THREADS=1 - python dev.py test -j2 -- --durations=0 --durations-min=0.25 --fail-slow=1.0 + spin test -j2 -- --durations=25 ############################################################################# - full_dev_py_min_numpy_fail_slow: - name: fail slow, full, py3.11/npMin, dev.py + full_spin_min_numpy: + name: full, py3.11/npMin, spin needs: get_commit_message if: > needs.get_commit_message.outputs.message == 1 @@ -76,6 +77,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: recursive + - name: Setup Python uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: @@ -91,18 +93,18 @@ jobs: - name: pip-packages run: | # 1.25.2 is currently our oldest supported NumPy version - python -m pip install numpy==1.25.2 cython pybind11 pythran meson-python meson ninja pytest pytest-xdist pytest-timeout pytest-fail-slow pooch rich_click click doit pydevtool hypothesis + python -m pip install numpy==1.25.2 cython pybind11 pythran meson-python meson ninja pytest pytest-xdist pytest-timeout pooch spin hypothesis python -m pip install -r requirements/openblas.txt - name: Build run: | - python dev.py build --with-scipy-openblas + spin build --with-scipy-openblas - name: Test run: | # test runner parallel clashes with OpenBLAS multithreading $env:OPENBLAS_NUM_THREADS=1 - python dev.py test -j2 --mode full -- --durations=0 --durations-min=1.0 --timeout=60 --fail-slow=5.0 + spin test -j2 --mode full -- --durations=25 --timeout=60 ############################################################################# diff --git a/.github/workflows/windows_intel_oneAPI.yml b/.github/workflows/windows_intel_oneAPI.yml index e0cf0ed6d5d3..c7c37bb09819 100644 --- a/.github/workflows/windows_intel_oneAPI.yml +++ b/.github/workflows/windows_intel_oneAPI.yml @@ -85,7 +85,7 @@ jobs: - name: Install packages from conda shell: cmd /C call {0} run: | - conda install -c conda-forge pkg-config meson meson-python ninja openblas libblas=*=*openblas numpy==2.0 cython pybind11 pytest pytest-xdist pytest-timeout pytest-fail-slow pooch rich-click click doit pydevtool hypothesis + conda install -c conda-forge pkg-config meson meson-python ninja openblas libblas=*=*openblas numpy==2.0 cython pybind11 pytest pytest-xdist pytest-timeout pooch spin hypothesis # MSVC is unable to compile Pythran code, therefore we need to use # -C-Duse-pythran=false while building SciPy. @@ -96,7 +96,7 @@ jobs: call "C:\Program Files (x86)\Intel\oneAPI\setvars.bat" set FC=ifx call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" - python dev.py build -C-Duse-pythran=false -C--vsenv + spin build -C-Duse-pythran=false -C--vsenv # "import scipy; scipy.test();" fails because # scipy/sparse/linalg/_eigen/arpack crashes. @@ -104,4 +104,4 @@ jobs: - name: Test scipy.datasets shell: cmd /C call {0} run: | - python dev.py test -s datasets + spin test -s datasets diff --git a/.gitignore b/.gitignore index e3be9776ed22..5f24740d8948 100644 --- a/.gitignore +++ b/.gitignore @@ -56,8 +56,9 @@ _configtest.c # Python files # ################ -# build directory +# build directories build +build-* # sphinx build directory doc/_build # cython files @@ -85,7 +86,6 @@ pip-wheel-metadata ######### .mesonpy-native-file.ini installdir/ -build-install/ .mesonpy/ # doit @@ -153,6 +153,7 @@ benchmarks/html benchmarks/scipy-benchmarks .github/workflows/.pixi .openblas +scipy-openblas.pc scipy/_distributor_init_local.py scipy/__config__.py scipy/_lib/_ccallback_c.c diff --git a/.gitmodules b/.gitmodules index 47bbe5273170..3bc6eb422cc3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,10 +9,6 @@ path = scipy/_lib/unuran url = https://github.com/scipy/unuran.git shallow = true -[submodule "scipy/_lib/boost_math"] - path = scipy/_lib/boost_math - url = https://github.com/boostorg/math.git - shallow = true [submodule "scipy/_lib/array_api_compat"] path = scipy/_lib/array_api_compat url = https://github.com/data-apis/array-api-compat.git @@ -32,3 +28,12 @@ path = subprojects/highs url = https://github.com/scipy/HiGHs shallow = true + +[submodule "subprojects/boost_math/math"] + path = subprojects/boost_math/math + url = https://github.com/boostorg/math.git + shallow = true + +[submodule "subprojects/xsf"] + path = subprojects/xsf + url = https://github.com/scipy/xsf.git diff --git a/.mailmap b/.mailmap index 69f516715896..3655c37384fa 100644 --- a/.mailmap +++ b/.mailmap @@ -207,7 +207,8 @@ Franziska Horn cod3licious levelfour G Young gfyoung G Young gfyoung -Gagandeep Singh czgdp1807 +ਗਗਨਦੀਪ ਸਿੰਘ (Gagandeep Singh) czgdp1807 +ਗਗਨਦੀਪ ਸਿੰਘ (Gagandeep Singh) Gagandeep Singh Ganesh Kathiresan ganesh-k13 Garrett Reynolds Garrett-R Gaël Varoquaux Gael varoquaux @@ -300,6 +301,7 @@ Jonathan Sutton Jonathan Sutton Jonathan Tammo Siebert jotasi Jonathan Taylor jonathan.taylor Jordão Bragantini Jordão Bragantini +Joren Hammudoglu jorenham Joris Vankerschaver Joris Vankerschaver Joscha Reimer jor Josef Perktold josef-pktd @@ -456,6 +458,8 @@ Raphael Wettinger raphaelw Reidar Kind <53039431+reidarkind@users.noreply.github.com> reidarkind <53039431+reidarkind@users.noreply.github.com> Renee Otten reneeotten Reshama Shaikh reshamas +Richard Strong Bowen rsbowen +Richard Strong Bowen Richard Strong Bowen Richard Gowers richardjgowers Rick Paris rparis Rob Falck rob.falck diff --git a/.spin/cmds.py b/.spin/cmds.py new file mode 100644 index 000000000000..f2a2be7588ab --- /dev/null +++ b/.spin/cmds.py @@ -0,0 +1,1088 @@ +import contextlib +import os +import sys +import importlib +import importlib.util +import json +import traceback +import warnings +import math +import subprocess +from concurrent.futures.process import _MAX_WINDOWS_WORKERS + +import spin +import click +from spin import util +from spin.cmds import meson + +from pathlib import Path + +PROJECT_MODULE = "scipy" + +@click.option( + '--werror', default=False, is_flag=True, + help="Treat warnings as errors") +@click.option( + '--asan', default=False, is_flag=True, + help=("Build and run with AddressSanitizer support. " + "Note: the build system doesn't check whether " + "the project is already compiled with ASan. " + "If not, you need to do a clean build (delete " + "build and build-install directories).")) +@click.option( + '--debug', '-d', default=False, is_flag=True, help="Debug build") +@click.option( + '--release', '-r', default=False, is_flag=True, help="Release build") +@click.option( + '--setup-args', '-C', default=[], multiple=True, + help=("Pass along one or more arguments to `meson setup` " + "Repeat the `-C` in case of multiple arguments.")) +@click.option( + '--show-build-log', default=False, is_flag=True, + help="Show build output rather than using a log file") +@click.option( + '--with-scipy-openblas', default=False, is_flag=True, + help=("If set, use the `scipy-openblas32` wheel installed into the " + "current environment as the BLAS/LAPACK to build against.")) +@click.option( + '--with-accelerate', default=False, is_flag=True, + help=("If set, use `Accelerate` as the BLAS/LAPACK to build against." + " Takes precedence over -with-scipy-openblas (macOS only)") +) +@click.option( + '--use-system-libraries', default=False, is_flag=True, + help=("If set, use system libraries" + "if they are available for subprojects.")) +@click.option( + '--tags', default="runtime,python-runtime,tests,devel", + show_default=True, help="Install tags to be used by meson." +) +@spin.util.extend_command(spin.cmds.meson.build) +def build(*, parent_callback, meson_args, jobs, verbose, werror, asan, debug, + release, setup_args, show_build_log, + with_scipy_openblas, with_accelerate, use_system_libraries, + tags, **kwargs): + """🔧 Build package with Meson/ninja and install + + MESON_ARGS are passed through e.g.: + + spin build -- -Dpkg_config_path=/lib64/pkgconfig + + The package is installed to build-install + + By default builds for release, to be able to use a debugger set CFLAGS + appropriately. For example, for linux use + + CFLAGS="-O0 -g" spin build + """ + MESON_ARGS = "meson_args" + MESON_COMPILE_ARGS = "meson_compile_args" + MESON_INSTALL_ARGS = "meson_install_args" + + meson_compile_args = tuple() + meson_install_args = tuple() + + if sys.platform == "cygwin": + # Cygwin only has netlib lapack, but can link against + # OpenBLAS rather than netlib blas at runtime. There is + # no libopenblas-devel to enable linking against + # openblas-specific functions or OpenBLAS Lapack + meson_args = meson_args + ("-Dlapack=lapack", "-Dblas=blas") + + if werror: + meson_args = meson_args + ("--werror", ) + + if debug or release: + if debug and release: + raise ValueError("Set at most one of `--debug` and `--release`!") + if debug: + buildtype = 'debug' + cflags_unwanted = ('-O1', '-O2', '-O3') + elif release: + buildtype = 'release' + cflags_unwanted = ('-O0', '-O1', '-O2') + meson_args = meson_args + (f"-Dbuildtype={buildtype}", ) + if 'CFLAGS' in os.environ.keys(): + # Check that CFLAGS doesn't contain something that supercedes -O0 + # for a plain debug build (conda envs tend to set -O2) + cflags = os.environ['CFLAGS'].split() + for flag in cflags_unwanted: + if flag in cflags: + raise ValueError(f"A {buildtype} build isn't possible, " + f"because CFLAGS contains `{flag}`." + "Please also check CXXFLAGS and FFLAGS.") + + if asan: + meson_args = meson_args + ('-Db_sanitize=address,undefined', ) + + if setup_args: + meson_args = meson_args + tuple([str(arg) for arg in setup_args]) + + if with_accelerate: + # on a mac you probably want to use accelerate over scipy_openblas + meson_args = meson_args + ("-Dblas=accelerate", ) + elif with_scipy_openblas: + configure_scipy_openblas() + os.environ['PKG_CONFIG_PATH'] = os.pathsep.join([ + os.getcwd(), + os.environ.get('PKG_CONFIG_PATH', '') + ]) + + if use_system_libraries: + meson_args = meson_args + ("-Duse-system-libraries=auto",) + + if jobs is None: + # Use number of physical cores rather than ninja's default of 2N+2, + # to avoid out of memory issues (see gh-17941 and gh-18443) + n_cores = cpu_count(only_physical_cores=True) + jobs = n_cores + + meson_install_args = meson_install_args + ("--tags=" + tags, ) + + if show_build_log: + verbose = show_build_log + + parent_callback(**{MESON_ARGS: meson_args, + MESON_COMPILE_ARGS: meson_compile_args, + MESON_INSTALL_ARGS: meson_install_args, + "jobs": jobs, + "verbose": verbose, + **kwargs}) + +@click.option( + '--durations', '-d', default=None, metavar="NUM_TESTS", + help="Show timing for the given number of slowest tests" +) +@click.option( + '--submodule', '-s', default=None, metavar='MODULE_NAME', + help="Submodule whose tests to run (cluster, constants, ...)") +@click.option( + '--mode', '-m', default='not slow', metavar='MODE', show_default=True, + help=("'fast', 'full', or something that could be passed to " + "`pytest -m` as a marker expression")) +@click.option( + '--array-api-backend', '-b', default=None, metavar='ARRAY_BACKEND', + multiple=True, + help=( + "Array API backend " + "('all', 'numpy', 'torch', 'cupy', 'array_api_strict', " + "'jax.numpy', 'dask.array')." + ) +) +@spin.util.extend_command(spin.cmds.meson.test) +def test(*, parent_callback, pytest_args, tests, coverage, + durations, submodule, mode, array_api_backend, **kwargs): + """🔧 Run tests + + PYTEST_ARGS are passed through directly to pytest, e.g.: + + spin test -- --pdb + + To run tests on a directory or file: + + \b + spin test scipy/linalg + + To report the durations of the N slowest tests: + + spin test -- --durations=N + + To run tests that match a given pattern: + + \b + spin test -- -k "geometric" + spin test -- -k "geometric and not rgeometric" + + By default, spin will run `-m 'not slow'`. To run the full test suite, use + `spin test -m full` + + For more, see `pytest --help`. + """ # noqa: E501 + + build_dir = os.path.abspath(kwargs['build_dir']) + site_package_dir = get_site_packages(build_dir) + + if coverage: + if is_editable_install(): + click.secho( + "Error: cannot generate coverage report for editable installs", + fg="bright_red", + ) + raise SystemExit(1) + elif site_package_dir is None: + raise FileNotFoundError( + "SciPy build not found, please execute " + "``spin build`` before calling ``spin test --coverage``. " + "We need it to figure out whether ``lcov`` can be called or not.") + else: + # Check needed to ensure gcov functions correctly. + with working_dir(site_package_dir): + sys.path.insert(0, site_package_dir) + os.environ['PYTHONPATH'] = os.pathsep.join( + (site_package_dir, os.environ.get('PYTHONPATH', ''))) + was_built_with_gcov_flag = len(list( + Path(build_dir).rglob("*.gcno"))) > 0 + if was_built_with_gcov_flag: + config = importlib.import_module( + "scipy.__config__").show(mode='dicts') + compilers_config = config['Compilers'] + cpp = compilers_config['c++']['name'] + c = compilers_config['c']['name'] + fortran = compilers_config['fortran']['name'] + if not (c == 'gcc' and cpp == 'gcc' and fortran == 'gcc'): + print("SciPy was built with --gcov flag which requires " + "LCOV while running tests.\nFurther, LCOV usage " + "requires GCC for C, C++ and Fortran codes in SciPy.\n" + "Compilers used currently are:\n" + f" C: {c}\n C++: {cpp}\n Fortran: {fortran}\n" + "Therefore, exiting without running tests.") + exit(1) # Exit because tests will give missing symbol error + + if submodule: + tests = PROJECT_MODULE + "." + submodule + + markexpr = mode + if (not pytest_args) and (not tests): + pytest_args = ('scipy',) + + if '-m' not in pytest_args: + if len(pytest_args) == 1 and not tests: + tests = pytest_args[0] + pytest_args = () + if markexpr != "full": + pytest_args = ('-m', markexpr) + pytest_args + + if durations: + pytest_args += ('--durations', durations) + + if len(array_api_backend) != 0: + os.environ['SCIPY_ARRAY_API'] = json.dumps(list(array_api_backend)) + + parent_callback(**{"pytest_args": pytest_args, "tests": tests, + "coverage": coverage, **kwargs}) + +@click.option( + '--list-targets', '-t', default=False, is_flag=True, + help='List doc targets', + ) +@click.option( + '--no-cache', default=False, is_flag=True, + help="Forces a full rebuild of the docs. Note that this may be " + \ + "needed in order to make docstring changes in C/Cython files " + \ + "show up." +) +@spin.util.extend_command(spin.cmds.meson.docs) +def docs(*, parent_callback, sphinx_target, clean, jobs, + list_targets, no_cache, **kwargs): + """📖 Build Sphinx documentation + + By default, SPHINXOPTS="-W", raising errors on warnings. + To build without raising on warnings: + + SPHINXOPTS="" spin docs + + To list all Sphinx targets: + + spin docs targets + + To build another Sphinx target: + + spin docs TARGET + + E.g., to build a zipfile of the html docs for distribution: + + spin docs dist + + """ + meson.docs.ignore_unknown_options = True + + if clean: # SciPy has its own mechanism to clear the previous docs build + cwd = os.getcwd() + os.chdir(os.path.join(cwd, "doc")) + subprocess.call(["make", "clean"], cwd=os.getcwd()) + clean = False + os.chdir(cwd) + + SPHINXOPTS = "-W" + if no_cache: + SPHINXOPTS += " -E" + + SPHINXOPTS = os.environ.get("SPHINXOPTS", "") + SPHINXOPTS + os.environ["SPHINXOPTS"] = SPHINXOPTS + + sphinx_target = "html" + + parent_callback(**{"sphinx_target": sphinx_target, + "clean": clean, "jobs": jobs, **kwargs}) + +def _set_pythonpath(pythonpath): + env = os.environ + env['PYTHONWARNINGS'] = env.get('PYTHONWARNINGS', 'all') + + if pythonpath: + for p in reversed(pythonpath.split(os.pathsep)): + sys.path.insert(0, p) + +@click.option( + '--pythonpath', '-p', metavar='PYTHONPATH', default=None, + help='Paths to prepend to PYTHONPATH') +@spin.util.extend_command(spin.cmds.meson.python) +def python(*, parent_callback, pythonpath, **kwargs): + """🐍 Launch Python shell with PYTHONPATH set + + OPTIONS are passed through directly to Python, e.g.: + + spin python -c 'import sys; print(sys.path)' + """ + _set_pythonpath(pythonpath) + parent_callback(**kwargs) + +@click.option( + '--pythonpath', '-p', metavar='PYTHONPATH', default=None, + help='Paths to prepend to PYTHONPATH') +@spin.util.extend_command(spin.cmds.meson.ipython) +def ipython(*, parent_callback, pythonpath, **kwargs): + """💻 Launch IPython shell with PYTHONPATH set + + OPTIONS are passed through directly to IPython, e.g.: + + spin ipython -i myscript.py + """ + _set_pythonpath(pythonpath) + parent_callback(**kwargs) + +@click.option( + '--pythonpath', '-p', metavar='PYTHONPATH', default=None, + help='Paths to prepend to PYTHONPATH') +@spin.util.extend_command(spin.cmds.meson.shell) +def shell(*, parent_callback, pythonpath, **kwargs): + """💻 Launch shell with PYTHONPATH set + + SHELL_ARGS are passed through directly to the shell, e.g.: + + spin shell -- -c 'echo $PYTHONPATH' + + Ensure that your shell init file (e.g., ~/.zshrc) does not override + the PYTHONPATH. + """ + _set_pythonpath(pythonpath) + parent_callback(**kwargs) + +@contextlib.contextmanager +def working_dir(new_dir): + current_dir = os.getcwd() + try: + os.chdir(new_dir) + yield + finally: + os.chdir(current_dir) + +@click.command(context_settings={"ignore_unknown_options": True}) +@meson.build_dir_option +@click.pass_context +def mypy(ctx, build_dir=None): + """🦆 Run Mypy tests for SciPy + """ + if is_editable_install(): + click.secho( + "Error: Mypy does not work (well) for editable installs", + fg="bright_red", + ) + raise SystemExit(1) + else: + click.secho( + "Invoking `build` prior to running mypy tests:", + bold=True, fg="bright_green" + ) + ctx.invoke(build) + + try: + import mypy.api + except ImportError as e: + raise RuntimeError( + "Mypy not found. Please install it by running " + "pip install -r mypy_requirements.txt from the repo root" + ) from e + + build_dir = os.path.abspath(build_dir) + root = Path(build_dir).parent + config = os.path.join(root, "mypy.ini") + check_path = PROJECT_MODULE + install_dir = meson._get_site_packages(build_dir) + + with working_dir(install_dir): + os.environ['MYPY_FORCE_COLOR'] = '1' + click.secho(f"mypy.api.run --config-file {config} {check_path}", + bold=True, fg="bright_blue") + report, errors, status = mypy.api.run([ + "--config-file", + str(config), + check_path, + ]) + print(report, end='') + print(errors, end='', file=sys.stderr) + +@spin.util.extend_command(test, doc='') +def smoke_docs(*, parent_callback, pytest_args, **kwargs): + """🔧 Run doctests of objects in the public API. + + PYTEST_ARGS are passed through directly to pytest, e.g.: + + spin smoke-docs -- --pdb + + To run tests on a directory: + + \b + spin smoke-docs scipy/linalg + + To report the durations of the N slowest doctests: + + spin smoke-docs -- --durations=N + + To run doctests that match a given pattern: + + \b + spin smoke-docs -- -k "slogdet" + spin smoke-docs scipy/linalg -- -k "det and not slogdet" + + \b + Note: + ----- + + \b + - This command only runs doctests and skips everything under tests/ + - This command only doctests public objects: those which are accessible + from the top-level `__init__.py` file. + + """ # noqa: E501 + # prevent obscure error later; cf https://github.com/numpy/numpy/pull/26691/ + if not importlib.util.find_spec("scipy_doctest"): + raise ModuleNotFoundError("Please install scipy-doctest") + + tests = kwargs["tests"] + if kwargs["submodule"]: + tests = PROJECT_MODULE + "." + kwargs["submodule"] + + if not pytest_args and not tests: + pytest_args = ('scipy', ) + + # turn doctesting on: + doctest_args = ( + '--doctest-modules', + '--doctest-collect=api' + ) + + if not tests: + doctest_args += ('--doctest-collect=api', ) + + pytest_args = pytest_args + doctest_args + + parent_callback(**{"pytest_args": pytest_args, **kwargs}) + +@click.command() +@click.option( + '--verbose', '-v', default=False, is_flag=True, + help="more verbosity") +@click.option( + '--submodule', '-s', default=None, metavar='MODULE_NAME', + help="Submodule whose tests to run (cluster, constants, ...)") +@meson.build_dir_option +@click.pass_context +def refguide_check(ctx, build_dir=None, *args, **kwargs): + """🔧 Run refguide check.""" + click.secho( + "Invoking `build` prior to running refguide-check:", + bold=True, fg="bright_green" + ) + ctx.invoke(build) + + build_dir = os.path.abspath(build_dir) + root = Path(build_dir).parent + install_dir = meson._get_site_packages(build_dir) + + cmd = [f'{sys.executable}', + os.path.join(root, 'tools', 'refguide_check.py')] + + if ctx.params["verbose"]: + cmd += ['-vvv'] + + if ctx.params["submodule"]: + cmd += [ctx.params["submodule"]] + + os.environ['PYTHONPATH'] = install_dir + util.run(cmd) + +@click.command() +@click.argument( + 'pytest_args', nargs=-1, metavar='PYTEST-ARGS', required=False +) +@click.option( + '--tests', '-t', default=None, multiple=True, metavar='TESTS', + help='Specify *rst files to smoke test') +@click.option( + '--verbose', '-v', default=False, is_flag=True, help="verbosity") +@meson.build_dir_option +@click.pass_context +def smoke_tutorials(ctx, pytest_args, tests, verbose, build_dir, *args, **kwargs): + """🔧 Run doctests of user-facing rst tutorials. + + To test all tutorials in the scipy doc/source/tutorial directory, use + + spin smoke-tutorials + + To run tests on a specific RST file: + + \b + spin smoke-tutorials doc/source/reference/stats.rst + spin smoke-tutorials -t doc/source/reference/stats.rst + + \b + Note: + ----- + + \b + - This command only runs doctests and skips everything under tests/ + - This command only doctests public objects: those which are accessible + from the top-level `__init__.py` file. + + """ # noqa: E501 + + click.secho( + "Invoking `build` prior to running tests for tutorials:", + bold=True, fg="bright_green" + ) + ctx.invoke(build) + + meson._set_pythonpath(build_dir) + + cmd = ['pytest'] + if tests: + cmd += list(tests) + else: + cmd += ['doc/source/tutorial', '--doctest-glob=*rst'] + if verbose: + cmd += ['-v'] + + extra_argv = list(pytest_args[:]) if pytest_args else [] + if extra_argv and extra_argv[0] == '--': + extra_argv = extra_argv[1:] + cmd += extra_argv + + cmd_str = ' '.join(cmd) + click.secho(cmd_str, bold=True, fg="bright_blue") + util.run(cmd) + +@click.command() +@click.argument('version_args', nargs=2) +@click.pass_context +def notes(ctx_obj, version_args): + """Release notes and log generation. + + Example: + + spin notes v1.7.0 v1.8.0 + """ + if version_args: + sys.argv = version_args + log_start = sys.argv[0] + log_end = sys.argv[1] + cmd = ["python", "tools/write_release_and_log.py", f"{log_start}", f"{log_end}"] + click.secho(' '.join(cmd), bold=True, fg="bright_blue") + util.run(cmd) + +@click.command() +@click.argument('revision_args', nargs=2) +@click.pass_context +def authors(ctx_obj, revision_args): + """Generate list of authors who contributed within revision + interval. + + Example: + + spin authors v1.7.0 v1.8.0 + """ + if revision_args: + sys.argv = revision_args + start_revision = sys.argv[0] + end_revision = sys.argv[1] + cmd = ["python", "tools/authors.py", f"{start_revision}..{end_revision}"] + click.secho(' '.join(cmd), bold=True, fg="bright_blue") + util.run(cmd) + +@click.command() +@click.option( + '--fix', default=False, is_flag=True, + help='Attempt to auto-fix errors') +@click.option("--diff-against", default="main", help="Diff against " + "this branch and lint modified files. Use either " + "`--diff-against` or `--files`, but not both.") +@click.option("--files", default="", + help="Lint these files or directories; " + "use **/*.py to lint all files") +@click.option("--all", default=False, is_flag=True, + help="This overrides `--diff-against` and `--files` " + "to lint all local files (excluding subprojects).") +@click.option("--no-cython", default=True, is_flag=True, + help="Do not run cython-lint.") +@click.pass_context +def lint(ctx, fix, diff_against, files, all, no_cython): + """🔦 Run linter on modified files and check for + disallowed Unicode characters and possibly-invalid test names.""" + cmd_prefix = [sys.executable] if sys.platform == "win32" else [] + + cmd_lint = cmd_prefix + [ + os.path.join('tools', 'lint.py'), + f'--diff-against={diff_against}' + ] + if files != "": + cmd_lint += [f'--files={files}'] + if all: + cmd_lint += ['--all'] + if no_cython: + cmd_lint += ['--no-cython'] + if fix: + cmd_lint += ['--fix'] + util.run(cmd_lint) + + cmd_unicode = cmd_prefix + [ + os.path.join('tools', 'check_unicode.py') + ] + util.run(cmd_unicode) + + cmd_check_test_name = cmd_prefix + [ + os.path.join('tools', 'check_test_name.py') + ] + util.run(cmd_check_test_name) + +# From scipy: benchmarks/benchmarks/common.py +def _set_mem_rlimit(max_mem=None): + """ + Set address space rlimit + """ + import resource + import psutil + + mem = psutil.virtual_memory() + + if max_mem is None: + max_mem = int(mem.total * 0.7) + cur_limit = resource.getrlimit(resource.RLIMIT_AS) + if cur_limit[0] > 0: + max_mem = min(max_mem, cur_limit[0]) + + try: + resource.setrlimit(resource.RLIMIT_AS, (max_mem, cur_limit[1])) + except ValueError: + # on macOS may raise: current limit exceeds maximum limit + pass + +def _run_asv(cmd): + # Always use ccache, if installed + PATH = os.environ['PATH'] + EXTRA_PATH = os.pathsep.join([ + '/usr/lib/ccache', '/usr/lib/f90cache', + '/usr/local/lib/ccache', '/usr/local/lib/f90cache' + ]) + env = os.environ + env['PATH'] = f'{EXTRA_PATH}{os.pathsep}{PATH}' + + # Control BLAS/LAPACK threads + env['OPENBLAS_NUM_THREADS'] = '1' + env['MKL_NUM_THREADS'] = '1' + + # Limit memory usage + try: + _set_mem_rlimit() + except (ImportError, RuntimeError): + pass + + util.run(cmd, cwd='benchmarks', env=env) + +def _commit_to_sha(commit): + p = util.run(['git', 'rev-parse', commit], output=False, echo=False) + if p.returncode != 0: + raise( + click.ClickException( + f'Could not find SHA matching commit `{commit}`' + ) + ) + + return p.stdout.decode('ascii').strip() + + +def _dirty_git_working_dir(): + # Changes to the working directory + p0 = util.run(['git', 'diff-files', '--quiet']) + + # Staged changes + p1 = util.run(['git', 'diff-index', '--quiet', '--cached', 'HEAD']) + + return (p0.returncode != 0 or p1.returncode != 0) + +@click.command() +@click.option( + '--tests', '-t', + default=None, metavar='TESTS', multiple=True, + help="Which tests to run" +) +@click.option( + '--submodule', '-s', default=None, metavar='SUBMODULE', + help="Submodule whose tests to run (cluster, constants, ...)") +@click.option( + '--compare', '-c', + is_flag=True, + default=False, + help="Compare benchmarks between the current branch and main " + "(unless other branches specified). " + "The benchmarks are each executed in a new isolated " + "environment." +) +@click.option( + '--verbose', '-v', is_flag=True, default=False +) +@click.option( + '--quick', '-q', is_flag=True, default=False, + help="Run each benchmark only once (timings won't be accurate)" +) +@click.argument( + 'commits', metavar='', + required=False, + nargs=-1 +) +@meson.build_dir_option +@click.pass_context +def bench(ctx, tests, submodule, compare, verbose, quick, + commits, build_dir=None, *args, **kwargs): + """🔧 Run benchmarks. + + \b + ```python + Examples: + + $ spin bench -t integrate.SolveBVP + $ spin bench -t linalg.Norm + $ spin bench --compare main + ``` + """ + build_dir = os.path.abspath(build_dir) + if not commits: + commits = ('main', 'HEAD') + elif len(commits) == 1: + commits = commits + ('HEAD',) + elif len(commits) > 2: + raise click.ClickException( + 'Need a maximum of two revisions to compare' + ) + + bench_args = [] + if submodule: + submodule = (submodule, ) + else: + submodule = tuple() + for t in tests + submodule: + bench_args += ['--bench', t] + + if verbose: + bench_args = ['-v'] + bench_args + + if quick: + bench_args = ['--quick'] + bench_args + + if not compare: + # No comparison requested; we build and benchmark the current version + + click.secho( + "Invoking `build` prior to running benchmarks:", + bold=True, fg="bright_green" + ) + ctx.invoke(build) + + meson._set_pythonpath(build_dir) + + p = util.run( + ['python', '-c', 'import scipy as sp; print(sp.__version__)'], + cwd='benchmarks', + echo=False, + output=False + ) + os.chdir('..') + + np_ver = p.stdout.strip().decode('ascii') + click.secho( + f'Running benchmarks on SciPy {np_ver}', + bold=True, fg="bright_green" + ) + cmd = [ + 'asv', 'run', '--dry-run', + '--show-stderr', '--python=same', + '--quick'] + bench_args + _run_asv(cmd) + else: + # Ensure that we don't have uncommited changes + commit_a, commit_b = [_commit_to_sha(c) for c in commits] + + if commit_b == 'HEAD' and _dirty_git_working_dir(): + click.secho( + "WARNING: you have uncommitted changes --- " + "these will NOT be benchmarked!", + fg="red" + ) + + cmd_compare = [ + 'asv', 'continuous', '--factor', '1.05', '--quick' + ] + bench_args + [commit_a, commit_b] + _run_asv(cmd_compare) + + +def configure_scipy_openblas(blas_variant='32'): + """Create scipy-openblas.pc and scipy/_distributor_init_local.py + + Requires a pre-installed scipy-openblas32 wheel from PyPI. + """ + basedir = os.getcwd() + pkg_config_fname = os.path.join(basedir, "scipy-openblas.pc") + + if os.path.exists(pkg_config_fname): + return None + + module_name = f"scipy_openblas{blas_variant}" + try: + openblas = importlib.import_module(module_name) + except ModuleNotFoundError: + raise RuntimeError(f"Importing '{module_name}' failed. " + "Make sure it is installed and reachable " + "by the current Python executable. You can " + f"install it via 'pip install {module_name}'.") + + local = os.path.join(basedir, "scipy", "_distributor_init_local.py") + with open(local, "w", encoding="utf8") as fid: + fid.write(f"import {module_name}\n") + + with open(pkg_config_fname, "w", encoding="utf8") as fid: + fid.write(openblas.get_pkg_config()) + +physical_cores_cache = None + +def _cpu_count_cgroup(os_cpu_count): + # Cgroup CPU bandwidth limit available in Linux since 2.6 kernel + cpu_max_fname = "/sys/fs/cgroup/cpu.max" + cfs_quota_fname = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us" + cfs_period_fname = "/sys/fs/cgroup/cpu/cpu.cfs_period_us" + if os.path.exists(cpu_max_fname): + # cgroup v2 + # https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html + with open(cpu_max_fname) as fh: + cpu_quota_us, cpu_period_us = fh.read().strip().split() + elif os.path.exists(cfs_quota_fname) and os.path.exists(cfs_period_fname): + # cgroup v1 + # https://www.kernel.org/doc/html/latest/scheduler/sched-bwc.html#management + with open(cfs_quota_fname) as fh: + cpu_quota_us = fh.read().strip() + with open(cfs_period_fname) as fh: + cpu_period_us = fh.read().strip() + else: + # No Cgroup CPU bandwidth limit (e.g. non-Linux platform) + cpu_quota_us = "max" + cpu_period_us = 100_000 # unused, for consistency with default values + + if cpu_quota_us == "max": + # No active Cgroup quota on a Cgroup-capable platform + return os_cpu_count + else: + cpu_quota_us = int(cpu_quota_us) + cpu_period_us = int(cpu_period_us) + if cpu_quota_us > 0 and cpu_period_us > 0: + return math.ceil(cpu_quota_us / cpu_period_us) + else: # pragma: no cover + # Setting a negative cpu_quota_us value is a valid way to disable + # cgroup CPU bandwidth limits + return os_cpu_count + + +def _cpu_count_affinity(os_cpu_count): + # Number of available CPUs given affinity settings + if hasattr(os, "sched_getaffinity"): + try: + return len(os.sched_getaffinity(0)) + except NotImplementedError: + pass + + # On PyPy and possibly other platforms, os.sched_getaffinity does not exist + # or raises NotImplementedError, let's try with the psutil if installed. + try: + import psutil + + p = psutil.Process() + if hasattr(p, "cpu_affinity"): + return len(p.cpu_affinity()) + + except ImportError: # pragma: no cover + if ( + sys.platform == "linux" + and os.environ.get("LOKY_MAX_CPU_COUNT") is None + ): + # PyPy does not implement os.sched_getaffinity on Linux which + # can cause severe oversubscription problems. Better warn the + # user in this particularly pathological case which can wreck + # havoc, typically on CI workers. + warnings.warn( + "Failed to inspect CPU affinity constraints on this system. " + "Please install psutil or explicitly set LOKY_MAX_CPU_COUNT.", + stacklevel=4 + ) + + # This can happen for platforms that do not implement any kind of CPU + # infinity such as macOS-based platforms. + return os_cpu_count + + +def _cpu_count_user(os_cpu_count): + """Number of user defined available CPUs""" + cpu_count_affinity = _cpu_count_affinity(os_cpu_count) + + cpu_count_cgroup = _cpu_count_cgroup(os_cpu_count) + + # User defined soft-limit passed as a loky specific environment variable. + cpu_count_loky = int(os.environ.get("LOKY_MAX_CPU_COUNT", os_cpu_count)) + + return min(cpu_count_affinity, cpu_count_cgroup, cpu_count_loky) + +def _count_physical_cores(): + """Return a tuple (number of physical cores, exception) + + If the number of physical cores is found, exception is set to None. + If it has not been found, return ("not found", exception). + + The number of physical cores is cached to avoid repeating subprocess calls. + """ + exception = None + + # First check if the value is cached + global physical_cores_cache + if physical_cores_cache is not None: + return physical_cores_cache, exception + + # Not cached yet, find it + try: + if sys.platform == "linux": + cpu_info = subprocess.run( + "lscpu --parse=core".split(), capture_output=True, text=True + ) + cpu_info = cpu_info.stdout.splitlines() + cpu_info = {line for line in cpu_info if not line.startswith("#")} + cpu_count_physical = len(cpu_info) + elif sys.platform == "win32": + cpu_info = subprocess.run( + "wmic CPU Get NumberOfCores /Format:csv".split(), + capture_output=True, + text=True, + ) + cpu_info = cpu_info.stdout.splitlines() + cpu_info = [ + l.split(",")[1] + for l in cpu_info + if (l and l != "Node,NumberOfCores") + ] + cpu_count_physical = sum(map(int, cpu_info)) + elif sys.platform == "darwin": + cpu_info = subprocess.run( + "sysctl -n hw.physicalcpu".split(), + capture_output=True, + text=True, + ) + cpu_info = cpu_info.stdout + cpu_count_physical = int(cpu_info) + else: + raise NotImplementedError(f"unsupported platform: {sys.platform}") + + # if cpu_count_physical < 1, we did not find a valid value + if cpu_count_physical < 1: + raise ValueError(f"found {cpu_count_physical} physical cores < 1") + + except Exception as e: + exception = e + cpu_count_physical = "not found" + + # Put the result in cache + physical_cores_cache = cpu_count_physical + + return cpu_count_physical, exception + +def cpu_count(only_physical_cores=False): + """Return the number of CPUs the current process can use. + + The returned number of CPUs accounts for: + * the number of CPUs in the system, as given by + ``multiprocessing.cpu_count``; + * the CPU affinity settings of the current process + (available on some Unix systems); + * Cgroup CPU bandwidth limit (available on Linux only, typically + set by docker and similar container orchestration systems); + * the value of the LOKY_MAX_CPU_COUNT environment variable if defined. + and is given as the minimum of these constraints. + + If ``only_physical_cores`` is True, return the number of physical cores + instead of the number of logical cores (hyperthreading / SMT). Note that + this option is not enforced if the number of usable cores is controlled in + any other way such as: process affinity, Cgroup restricted CPU bandwidth + or the LOKY_MAX_CPU_COUNT environment variable. If the number of physical + cores is not found, return the number of logical cores. + + Note that on Windows, the returned number of CPUs cannot exceed 61, see: + https://bugs.python.org/issue26903. + + It is also always larger or equal to 1. + """ + # Note: os.cpu_count() is allowed to return None in its docstring + os_cpu_count = os.cpu_count() or 1 + if sys.platform == "win32": + # On Windows, attempting to use more than 61 CPUs would result in a + # OS-level error. See https://bugs.python.org/issue26903. According to + # https://learn.microsoft.com/en-us/windows/win32/procthread/processor-groups + # it might be possible to go beyond with a lot of extra work but this + # does not look easy. + os_cpu_count = min(os_cpu_count, _MAX_WINDOWS_WORKERS) + + cpu_count_user = _cpu_count_user(os_cpu_count) + aggregate_cpu_count = max(min(os_cpu_count, cpu_count_user), 1) + + if not only_physical_cores: + return aggregate_cpu_count + + if cpu_count_user < os_cpu_count: + # Respect user setting + return max(cpu_count_user, 1) + + cpu_count_physical, exception = _count_physical_cores() + if cpu_count_physical != "not found": + return cpu_count_physical + + # Fallback to default behavior + if exception is not None: + # warns only the first time + warnings.warn( + "Could not find the number of physical cores for the " + f"following reason:\n{exception}\n" + "Returning the number of logical cores instead. You can " + "silence this warning by setting LOKY_MAX_CPU_COUNT to " + "the number of cores you want to use.", + stacklevel=2 + ) + traceback.print_tb(exception.__traceback__) + + return aggregate_cpu_count + +def get_site_packages(build_dir): + """site-packages directory is path to installed in-tree build. + + Returns None if `scipy` wasn't build at all. + Returns an empty string (from spin.meson call) for an editable install. + """ + try: + return meson._get_site_packages(build_dir) + except FileNotFoundError: + return None + + +def is_editable_install(): + return meson._is_editable_install_of_same_source('scipy') diff --git a/LICENSES_bundled.txt b/LICENSES_bundled.txt index f32f9f112ddb..477033fcdaa3 100644 --- a/LICENSES_bundled.txt +++ b/LICENSES_bundled.txt @@ -87,122 +87,15 @@ License: 3-clause BSD For details, see scipy/sparse/linalg/eigen/arpack/ARPACK/COPYING Name: Qhull -Files: scipy/spatial/qhull/* +Files: subprojects/qhull_r/libqhull_r/* License: Qhull license (BSD-like) - For details, see scipy/spatial/qhull/COPYING.txt + For details, see subprojects/qhull_r/libqhull_r/COPYING.txt -Name: Cephes -Files: scipy/special/cephes/* -License: 3-clause BSD - Distributed under 3-clause BSD license with permission from the author, - see https://lists.debian.org/debian-legal/2004/12/msg00295.html - - Cephes Math Library Release 2.8: June, 2000 - Copyright 1984, 1995, 2000 by Stephen L. Moshier - - This software is derived from the Cephes Math Library and is - incorporated herein by permission of the author. - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * 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. - * Neither the name of the 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 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. - -Name: Faddeeva -Files: scipy/special/Faddeeva.* -License: MIT - Copyright (c) 2012 Massachusetts Institute of Technology - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Name: qd -Files: scipy/special/cephes/dd_*.[ch] -License: modified BSD license ("BSD-LBNL-License.doc") - This work was supported by the Director, Office of Science, Division - of Mathematical, Information, and Computational Sciences of the - U.S. Department of Energy under contract numbers DE-AC03-76SF00098 and - DE-AC02-05CH11231. - - Copyright (c) 2003-2009, The Regents of the University of California, - through Lawrence Berkeley National Laboratory (subject to receipt of - any required approvals from U.S. Dept. of Energy) All rights reserved. - - 1. Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the following - conditions are met: - - (1) Redistributions of source code must retain the copyright - notice, this list of conditions and the following disclaimer. - - (2) Redistributions in binary form must reproduce the copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - (3) Neither the name of the University of California, Lawrence - Berkeley National Laboratory, U.S. Dept. of Energy nor the names - of its contributors may be used to endorse or promote products - derived from this software without specific prior written - permission. - - 2. 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 COPYRIGHT - OWNER 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. - - 3. You are under no obligation whatsoever to provide any bug fixes, - patches, or upgrades to the features, functionality or performance of - the source code ("Enhancements") to anyone; however, if you choose to - make your Enhancements available either publicly, or directly to - Lawrence Berkeley National Laboratory, without imposing a separate - written license agreement for such Enhancements, then you hereby grant - the following license: a non-exclusive, royalty-free perpetual license - to install, use, modify, prepare derivative works, incorporate into - other computer software, distribute, and sublicense such enhancements - or derivative works thereof, in binary and source code form. +Name: xsf +Files: scipy/subprojects/xsf/* +License: 3-Clause BSD + For details, see scipy/subprojects/xsf/LICENSE and + scipy/subprojects/xsf/LICENSES_bundled.txt Name: pypocketfft Files: scipy/fft/_pocketfft/[pocketfft.h, pypocketfft.cxx] @@ -257,9 +150,9 @@ License: MIT For details, see scipy/optimize/_highs/LICENCE Name: Boost -Files: scipy/_lib/boost_math/* +Files: subprojects/boost_math/math/* License: Boost Software License - Version 1.0 - For details, see scipy/_lib/boost_math/LICENSE.txt + For details, see subprojects/boost_math/math/LICENSE Name: Biasedurn Files: scipy/stats/biasedurn/* @@ -278,20 +171,15 @@ License 3-Clause BSD and scipy/stats/_rcont/logfactorial.c Name: array-api-compat -Files: scipy/_lib/array-api-compat/* +Files: scipy/_lib/array_api_compat/* License: MIT - For details, see scipy/_lib/array-api-compat/LICENCE + For details, see scipy/_lib/array_api_compat/LICENCE Name: Tempita Files: scipy/_build_utils/tempita/* License: MIT For details, see scipy/_build_utils/tempita/LICENCE.txt -Name: mdspan -Files: scipy/special/special/third_party/kokkos/mdspan.hpp -License: Apache License v2.0 with LLVM Exceptions - For details, see scipy/special/special/third_party/kokkos/mdspan.hpp - Name: Chebfun Files: scipy/interpolate/[_aaa.py, tests/test_aaa.py] License 3-Clause BSD diff --git a/benchmarks/README.rst b/benchmarks/README.rst index 9f844e47cad2..b1b817178445 100644 --- a/benchmarks/README.rst +++ b/benchmarks/README.rst @@ -12,21 +12,21 @@ Usage Airspeed Velocity manages building and Python environments by itself, unless told otherwise. Some of the benchmarking features in -``dev.py`` also tell ASV to use the SciPy compiled by -``dev.py``. To run the benchmarks, you do not need to install a +``spin`` also tell ASV to use the SciPy compiled by +``spin``. To run the benchmarks, you do not need to install a development version of SciPy to your current Python environment. Run a benchmark against currently checked-out SciPy version (don't record the result):: - python dev.py bench --submodule sparse.Arithmetic + spin bench --submodule sparse.Arithmetic Compare change in benchmark results with another branch:: - python dev.py bench --compare main --submodule sparse.Arithmetic + spin bench --compare main --submodule sparse.Arithmetic Run ASV commands directly (note, this will not set env vars for ``ccache`` -and disabling BLAS/LAPACK multi-threading, as ``dev.py`` does):: +and disabling BLAS/LAPACK multi-threading, as ``spin`` does):: cd benchmarks asv run --skip-existing-commits --steps 10 ALL diff --git a/benchmarks/benchmarks/optimize.py b/benchmarks/benchmarks/optimize.py index e48c845f1f76..0607df8b53e5 100644 --- a/benchmarks/benchmarks/optimize.py +++ b/benchmarks/benchmarks/optimize.py @@ -479,7 +479,7 @@ def track_all(self, problem_name, result_type): # `export SCIPY_GLOBAL_BENCH=AMGM,Adjiman,...` to run specific tests # `export SCIPY_GLOBAL_BENCH_NUMTRIALS=10` to specify n_iterations, default 100 # -# then run `python dev.py bench -S optimize.BenchGlobal` +# then run `spin bench -s optimize.BenchGlobal` # Note that it can take several hours to run; intermediate output # can be found under benchmarks/global-bench-results.json diff --git a/doc/Makefile b/doc/Makefile index 64a55bc028fa..43e7efdf59a6 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -54,7 +54,7 @@ UPLOAD_DIR=/srv/docs_scipy_org/doc/scipy-$(RELEASE) # `set -o pipefail` is specific to bash SHELL = /bin/bash -SCIPYVER:=$(shell $(PYTHON) -c "import scipy; print(scipy.version.git_revision[:10])" 2>/dev/null) +SCIPYVER:=$(shell $(PYTHON) -c "import scipy; print(scipy.version.git_revision[:10])" | tail -c11 2>/dev/null) GITVER ?= $(shell (cd ..; set -o pipefail && git rev-parse HEAD 2>/dev/null | cut -c1-10) || echo Unknown) version-check: diff --git a/doc/source/_static/gitpod/github-gitpod.png b/doc/source/_static/gitpod/github-gitpod.png deleted file mode 100644 index 54a78b016f3e..000000000000 Binary files a/doc/source/_static/gitpod/github-gitpod.png and /dev/null differ diff --git a/doc/source/_static/gitpod/gitpod-edit-permissions-gh.png b/doc/source/_static/gitpod/gitpod-edit-permissions-gh.png deleted file mode 100644 index 3abae4e64b18..000000000000 Binary files a/doc/source/_static/gitpod/gitpod-edit-permissions-gh.png and /dev/null differ diff --git a/doc/source/_static/gitpod/gitpod-workspace.png b/doc/source/_static/gitpod/gitpod-workspace.png deleted file mode 100644 index c88c752122e6..000000000000 Binary files a/doc/source/_static/gitpod/gitpod-workspace.png and /dev/null differ diff --git a/doc/source/_static/gitpod/gitpod_ci_build_flow.png b/doc/source/_static/gitpod/gitpod_ci_build_flow.png deleted file mode 100644 index 971e89d022a5..000000000000 Binary files a/doc/source/_static/gitpod/gitpod_ci_build_flow.png and /dev/null differ diff --git a/doc/source/_static/gitpod/installing-gitpod-io.png b/doc/source/_static/gitpod/installing-gitpod-io.png deleted file mode 100644 index 97319a7293ce..000000000000 Binary files a/doc/source/_static/gitpod/installing-gitpod-io.png and /dev/null differ diff --git a/doc/source/_static/gitpod/rst-rendering.png b/doc/source/_static/gitpod/rst-rendering.png deleted file mode 100644 index 41cc305f3a33..000000000000 Binary files a/doc/source/_static/gitpod/rst-rendering.png and /dev/null differ diff --git a/doc/source/_static/gitpod/scipy-github.png b/doc/source/_static/gitpod/scipy-github.png deleted file mode 100644 index 063194dbee1b..000000000000 Binary files a/doc/source/_static/gitpod/scipy-github.png and /dev/null differ diff --git a/doc/source/_static/gitpod/scipy-gitpod-branches.png b/doc/source/_static/gitpod/scipy-gitpod-branches.png deleted file mode 100644 index b4d46a811013..000000000000 Binary files a/doc/source/_static/gitpod/scipy-gitpod-branches.png and /dev/null differ diff --git a/doc/source/_static/gitpod/vscode-rst.png b/doc/source/_static/gitpod/vscode-rst.png deleted file mode 100644 index 5b574c115a2b..000000000000 Binary files a/doc/source/_static/gitpod/vscode-rst.png and /dev/null differ diff --git a/doc/source/_static/gitpod/vscode-statusbar.png b/doc/source/_static/gitpod/vscode-statusbar.png deleted file mode 100644 index 3febbcee0ee5..000000000000 Binary files a/doc/source/_static/gitpod/vscode-statusbar.png and /dev/null differ diff --git a/doc/source/_static/version_switcher.json b/doc/source/_static/version_switcher.json index 657a982d236e..730cd0dba021 100644 --- a/doc/source/_static/version_switcher.json +++ b/doc/source/_static/version_switcher.json @@ -5,9 +5,14 @@ "url": "https://scipy.github.io/devdocs/" }, { - "name": "1.15.2 (stable)", - "version":"1.15.2", + "name": "1.15.3 (stable)", + "version":"1.15.3", "preferred": true, + "url": "https://docs.scipy.org/doc/scipy-1.15.3/" + }, + { + "name": "1.15.2", + "version":"1.15.2", "url": "https://docs.scipy.org/doc/scipy-1.15.2/" }, { diff --git a/doc/source/building/blas_lapack.rst b/doc/source/building/blas_lapack.rst index 284c3672bb1e..0a7b5b5d1ad5 100644 --- a/doc/source/building/blas_lapack.rst +++ b/doc/source/building/blas_lapack.rst @@ -16,7 +16,7 @@ BLAS/LAPACK on Linux distros, and can be dynamically switched between implementations on conda-forge), use:: $ # for a development build - $ python dev.py build -C-Dblas=blas -C-Dlapack=lapack + $ spin build -C-Dblas=blas -C-Dlapack=lapack $ # to build and install a wheel $ python -m build -Csetup-args=-Dblas=blas -Csetup-args=-Dlapack=lapack @@ -29,11 +29,11 @@ Other options that should work (as long as they're installed with ``pkg-config`` or CMake support) include ``mkl``, ``atlas``, ``blis`` and ``accelerate``. -Note that both Accelerate and ``scipy-openblas`` have flags in ``dev.py`` +Note that both Accelerate and ``scipy-openblas`` have flags in ``spin`` that are easier to remember, since they're commonly used for development:: - $ python dev.py build --with-accelerate - $ python dev.py build --with-scipy-openblas + $ spin build --with-accelerate + $ spin build --with-scipy-openblas The ``-Dlapack`` flag isn't needed for Accelerate, MKL or ``scipy-openblas``, since we can be sure that BLAS and LAPACK are the same for those options. @@ -93,7 +93,7 @@ user wants to override this autodetection mechanism for building against plain ``libblas``/``liblapack`` (this is what conda-forge does for example), use the ``-Duse-g77-abi=true`` build option. E.g.,:: - $ python -m build -C-Duse-g77-abi=true -Csetup-args=-Dblas=blas -Csetup-args=-Dlapack=lapack + $ python -m build -C-Duse-g77-abi=true -Csetup-args=-Dblas=blas -Csetup-args=-Dlapack=lapack Work-in-progress @@ -107,4 +107,3 @@ of the box: LP64 (32-bit integer size) BLAS/LAPACK. - Automatically selecting from multiple possible BLAS and LAPACK options, with a user-provided order of precedence - diff --git a/doc/source/building/compilers_and_options.rst b/doc/source/building/compilers_and_options.rst index 53c89a5082ae..e76ef7be801e 100644 --- a/doc/source/building/compilers_and_options.rst +++ b/doc/source/building/compilers_and_options.rst @@ -52,10 +52,10 @@ you can configure the build as following to use the ``debug`` build type:: meson setup build --buildtype debug --prefix=$PWD/build-install -Now, you can use the ``dev.py`` interface for further building, installing and +Now, you can use the ``spin`` interface for further building, installing and testing SciPy:: - python dev.py -s linalg + spin -s linalg This will work because after initial configuration, Meson will remember the config options. @@ -75,7 +75,7 @@ such that you have at least 2 GB RAM per job. For example, to launch 6 jobs:: or:: - python dev.py build -j6 + spin build -j6 Use GCC and Clang builds in parallel @@ -89,14 +89,14 @@ For example, let us build using GCC and Clang. 1. Build with GCC:: - python dev.py build + spin build Using the above command, meson will build with the (default) GCC compilers in the ``build`` directory, and install to the ``build-install`` directory. 2. Build with Clang:: - CC=clang CXX=clang++ FC=gfortran python dev.py --build-dir=build-clang build + CC=clang CXX=clang++ FC=gfortran spin --build-dir=build-clang build Using the above commands, Meson will build with the Clang, Clang++ and Gfortran compilers in the ``build-clang`` directory, and then install SciPy into @@ -104,16 +104,14 @@ compilers in the ``build-clang`` directory, and then install SciPy into Meson will remember the compiler selection for the ``build-clang`` directory and it cannot be changed, so each future invocation of -``python dev.py --build-dir=build-clang `` it will automatically use Clang. +``spin --build-dir=build-clang `` it will automatically use Clang. Tip: use an alias to make this easier to use, e.g., -``alias dev-clang="python dev.py --build-dir=build-clang"`` and then +``alias dev-clang="spin --build-dir=build-clang"`` and then ``dev-clang build``. A common reason to have two builds is to compare between them. For example, to run the ``scipy.linalg`` tests for builds with both compilers, do:: - python dev.py -s linalg # run tests for the GCC build - python dev.py --build-dir build-clang -s linalg # run tests for the Clang build - - + spin -s linalg # run tests for the GCC build + spin --build-dir build-clang -s linalg # run tests for the Clang build diff --git a/doc/source/building/distutils_equivalents.rst b/doc/source/building/distutils_equivalents.rst index 440eef9215b2..de1fd88aa427 100644 --- a/doc/source/building/distutils_equivalents.rst +++ b/doc/source/building/distutils_equivalents.rst @@ -5,7 +5,7 @@ Meson and ``distutils`` ways of doing things *Old workflows (numpy.distutils based):* -The `runtests.py` file was removed in commit `0f73f92255253ec5dff2de5ca45d8d3bdda03f92` [^1^_]. +The `runtests.py` file was removed in commit `0f73f92255253ec5dff2de5ca45d8d3bdda03f92` [#]_. 1. ``python runtests.py`` 2. ``python setup.py build_ext -i`` + ``export @@ -22,11 +22,11 @@ The `runtests.py` file was removed in commit `0f73f92255253ec5dff2de5ca45d8d3bdd *New workflows (Meson and meson-python based):* -1. ``python dev.py`` +1. ``spin`` 2. ``pip install -e . --no-build-isolation`` (see the ``meson-python`` docs) 3. the same as (2) 4. ``python -m build --no-isolation`` + ``pip install dist/scipy*.whl`` - see `pypa/build `_. 5. ``pip install .`` -[^1^_]: [Commit 0f73f92255253ec5dff2de5ca45d8d3bdda03f92 on GitHub](https://github.com/scipy/scipy/commit/0f73f92255253ec5dff2de5ca45d8d3bdda03f92). +.. [#] `Commit 0f73f92255253ec5dff2de5ca45d8d3bdda03f92 on GitHub `_. diff --git a/doc/source/building/index.rst b/doc/source/building/index.rst index 5453efe2c9c8..e7edf0593394 100644 --- a/doc/source/building/index.rst +++ b/doc/source/building/index.rst @@ -295,8 +295,8 @@ Then you want to do the following: 1. Create a dedicated development environment (virtual environment or conda environment), 2. Install all needed dependencies (*build*, and also *test*, *doc* and - *optional* dependencies), -3. Build SciPy with our ``dev.py`` developer interface. + *optional* dependencies), +3. Build SciPy with our ``spin`` developer interface. Step (3) is always the same, steps (1) and (2) are different between conda and virtual environments: @@ -363,43 +363,43 @@ virtual environments: # Alternatively, you can install just the dependencies for certain # development tasks: - # Build and dev dependencies (for `python dev.py {build, lint, mypy}`) + # Build and dev dependencies (for `spin {build, lint, mypy}`) python -m pip install -r requirements/build.txt -r requirements/dev.txt - # Doc dependencies (for `python dev.py {doc, refguide-check}`) + # Doc dependencies (for `spin {doc, refguide-check}`) python -m pip install -r requirements/doc.txt - # Test dependencies (for `python dev.py {test, bench, refguide-check}`) + # Test dependencies (for `spin {test, bench, refguide-check}`) python -m pip install -r requirements/test.txt To build SciPy in an activated development environment, run:: - python dev.py build + spin build This will install SciPy inside the repository (by default in a -``build-install`` directory). You can then run tests (``python dev.py test``), -drop into IPython (``python dev.py ipython``), or take other development steps -like build the html documentation or running benchmarks. The ``dev.py`` -interface is self-documenting, so please see ``python dev.py --help`` and -``python dev.py --help`` for detailed guidance. +``build-install`` directory). You can then run tests (``spin test``), +drop into IPython (``spin ipython``), or take other development steps +like build the html documentation or running benchmarks. The ``spin`` +interface is self-documenting, so please see ``spin --help`` and +``spin --help`` for detailed guidance. .. admonition:: IDE support & editable installs - While the ``dev.py`` interface is our recommended way of working on SciPy, + While the ``spin`` interface is our recommended way of working on SciPy, it has one limitation: because of the custom install location, SciPy - installed using ``dev.py`` will not be recognized automatically within an + installed using ``spin`` will not be recognized automatically within an IDE (e.g., for running a script via a "run" button, or setting breakpoints visually). This will work better with an *in-place build* (or "editable install"). Editable installs are supported. It is important to understand that **you - may use either an editable install or dev.py in a given repository clone, + may use either an editable install or spin in a given repository clone, but not both**. If you use editable installs, you have to use ``pytest`` - and other development tools directly instead of using ``dev.py``. + and other development tools directly instead of using ``spin``. To use an editable install, ensure you start from a clean repository (run - ``git clean -xdf`` if you've built with ``dev.py`` before) and have all + ``git clean -xdf`` if you've built with ``spin`` before) and have all dependencies set up correctly as described higher up on this page. Then do:: diff --git a/doc/source/building/redistributable_binaries.rst b/doc/source/building/redistributable_binaries.rst index 9951f487e135..65aab3e83283 100644 --- a/doc/source/building/redistributable_binaries.rst +++ b/doc/source/building/redistributable_binaries.rst @@ -55,6 +55,40 @@ More detailed information on these build dependencies can be found in :ref:`toolchain-roadmap`. +Using system dependencies instead of vendored sources +----------------------------------------------------- + +SciPy contains a *lot* of code that has been vendored from libraries written in +C, C++ and Fortran. This is done for a mix of historical and robustness +reasons. Distro packagers sometimes have reasons to want to *unvendor* this +code, ensuring that they only have a single version of a particular library in +use. We offer a build option, ``-Duse-system-libraries``, to allow them to do +so, e.g.: + +.. code:: + + $ python -m build -wnx -Duse-system-libraries=boost.math + +The build option takes the following values: + +- ``none``: Use the source code shipped as part of SciPy, do not look for + external dependencies (default). +- ``all``: Use external libraries for all components controlled by + ``use-system-libraries``, and error out if they are not found. Detecting is + done by Meson, typically first through the ``pkg-config`` and then the + ``cmake`` method of the ``dependency()`` function. +- ``auto``: Try detecting external libraries, and if they are not found then + fall back onto vendored sources. +- A comma-separated list of dependency names (e.g., ``boost.math,qhull``). If + given, uses the ``all`` behavior for the named dependencies (and ``none`` for + the rest). See ``meson.options`` in the root of the SciPy repository or sdist + for all supported options. + +It's also valid to combine the generic and named-library options above, for +example ``boost.math,auto`` will apply the ``auto`` behavior to all +dependencies other than ``boost.math``. + + Stripping the test suite from a wheel or installed package ---------------------------------------------------------- diff --git a/doc/source/building/understanding_meson.rst b/doc/source/building/understanding_meson.rst index d862a3a609ef..a84e2acca507 100644 --- a/doc/source/building/understanding_meson.rst +++ b/doc/source/building/understanding_meson.rst @@ -49,8 +49,7 @@ Explanation of build stages --------------------------- *This is for teaching purposes only; there should be no need to execute these -stages separately. The dev.py scripts in the root of the repo also contains -these steps and may be studied for insights.* +stages separately.* Assume we're starting from a clean repo and a fully set up conda environment:: diff --git a/doc/source/conf.py b/doc/source/conf.py index 22f1b4e0a2c7..201c2def2b52 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -360,7 +360,6 @@ plot_pre_code = """ import warnings for key in ( - 'interp2d` is deprecated', # Deprecation of scipy.interpolate.interp2d '`kurtosistest` p-value may be', # intentionally "bad" example in docstring ): warnings.filterwarnings(action='ignore', message='.*' + key + '.*') diff --git a/doc/source/dev/api-dev/array_api.rst b/doc/source/dev/api-dev/array_api.rst index 11ffa7130dc9..28b2e67f2f1c 100644 --- a/doc/source/dev/api-dev/array_api.rst +++ b/doc/source/dev/api-dev/array_api.rst @@ -101,48 +101,39 @@ variable is set: - `scipy.fft` - `scipy.io` - `scipy.ndimage` +- `scipy.special` +- `scipy.stats` -Support is provided in `scipy.special` for the following functions: -`scipy.special.log_ndtr`, `scipy.special.ndtr`, `scipy.special.ndtri`, -`scipy.special.erf`, `scipy.special.erfc`, `scipy.special.i0`, -`scipy.special.i0e`, `scipy.special.i1`, `scipy.special.i1e`, -`scipy.special.gammaln`, `scipy.special.gammainc`, `scipy.special.gammaincc`, -`scipy.special.logit`, `scipy.special.expit`, `scipy.special.entr`, -`scipy.special.rel_entr`, `scipy.special.rel_entr`, `scipy.special.xlogy`, -and `scipy.special.chdtrc`. - -Support is provided in `scipy.stats` for the following functions: -`scipy.stats.describe`, `scipy.stats.moment`, `scipy.stats.skew`, -`scipy.stats.kurtosis`, `scipy.stats.kstat`, `scipy.stats.kstatvar`, -`scipy.stats.circmean`, `scipy.stats.circvar`, `scipy.stats.circstd`, -`scipy.stats.entropy`, `scipy.stats.variation` , `scipy.stats.sem`, -`scipy.stats.ttest_1samp`, `scipy.stats.pearsonr`, `scipy.stats.chisquare`, -`scipy.stats.skewtest`, `scipy.stats.kurtosistest`, `scipy.stats.normaltest`, -`scipy.stats.jarque_bera`, `scipy.stats.bartlett`, `scipy.stats.power_divergence`, -and `scipy.stats.monte_carlo_test`. - -Some features provide a capability table in the documentation like this: - -+---------+-------------+-------------+ -| Library | CPU | GPU | -+=========+=============+=============+ -| NumPy | ✓ | n/a | -+---------+-------------+-------------+ -| CuPy | n/a | ✓ | -+---------+-------------+-------------+ -| PyTorch | ✓ | ✗ | -+---------+-------------+-------------+ -| JAX | ✓ | ✓ | -+---------+-------------+-------------+ -| Dask | ✗ | ✗ | -+---------+-------------+-------------+ +Individual functions in the above modules provide a capability table in the +documentation like the one below. If the table is absent, the function does not +yet support backends other than NumPy. + + +Example capabilities table +-------------------------- + +========= ========= ========= +Library CPU GPU +========= ========= ========= +NumPy ✅ n/a +CuPy n/a ✅ +PyTorch ✅ ✅ +JAX ⚠️ no JIT ⛔ +Dask ⛔ n/a +========= ========= ========= In the example above, the feature has some support for NumPy, CuPy, PyTorch, and JAX arrays, but no support for Dask arrays. Some backends, like JAX and PyTorch, natively support multiple devices (CPU and GPU), but SciPy support for such arrays may be -limited; for instance, this SciPy feature is only expected to work with PyTorch arrays -located on the CPU. While the elements of the table marked with "n/a" are inherently -out of scope, we are continually working on filling in the rest. +limited; for instance, this SciPy feature is only expected to work with JAX arrays +located on the CPU. Additionally, some backends can have major caveats; in the example +the function will fail when running inside ``jax.jit``. +Additional caveats may be listed in the docstring of the function. + +While the elements of the table marked with "n/a" are inherently out of scope, we are +continually working on filling in the rest. +Dask wrapping around backends other than NumPy (notably, CuPy) is currently out of scope +but it may change in the future. Please see `the tracker issue`_ for updates. @@ -260,7 +251,7 @@ Adding tests ------------ To run a test on multiple array backends, you should add the ``xp`` fixture to it, -which is valued to the currently tested array namespace. +which is valued to the currently tested array namespace. The following pytest markers are available: @@ -287,7 +278,7 @@ The following pytest markers are available: * ``array_api_backends``: this marker is automatically added by the ``xp`` fixture to all tests that use it. This is useful e.g. to select all and only such tests:: - python dev.py test -b all -m array_api_backends + spin test -b all -m array_api_backends ``scipy._lib._array_api`` contains array-agnostic assertions such as ``xp_assert_close`` which can be used to replace assertions from `numpy.testing`. @@ -321,7 +312,7 @@ The following examples demonstrate how to use the markers:: Passing names of backends into ``exceptions`` means that they will not be skipped by ``cpu_only=True`` or ``eager_only=True``. This is useful when delegation -is implemented for some, but not all, non-CPU backends, and the CPU code path +is implemented for some, but not all, non-CPU backends, and the CPU code path requires conversion to NumPy for compiled code:: # array-api-strict and CuPy will always be skipped, for the given reasons. @@ -333,17 +324,17 @@ requires conversion to NumPy for compiled code:: def test_toto(self, xp): ... -After applying these markers, ``dev.py test`` can be used with the new option +After applying these markers, ``spin test`` can be used with the new option ``-b`` or ``--array-api-backend``:: - python dev.py test -b numpy -b torch -s cluster + spin test -b numpy -b torch -s cluster This automatically sets ``SCIPY_ARRAY_API`` appropriately. To test a library that has multiple devices with a non-default device, a second environment variable (``SCIPY_DEVICE``, only used in the test suite) can be set. Valid values depend on the array library under test, e.g. for PyTorch, valid values are ``"cpu", "cuda", "mps"``. To run the test suite with the PyTorch MPS -backend, use: ``SCIPY_DEVICE=mps python dev.py test -b torch``. +backend, use: ``SCIPY_DEVICE=mps spin test -b torch``. Note that there is a GitHub Actions workflow which tests with array-api-strict, PyTorch, and JAX on CPU. diff --git a/doc/source/dev/contributor/benchmarking.rst b/doc/source/dev/contributor/benchmarking.rst index 1826aff57914..d512856dabd4 100644 --- a/doc/source/dev/contributor/benchmarking.rst +++ b/doc/source/dev/contributor/benchmarking.rst @@ -92,7 +92,7 @@ submitting a pull request. To run all benchmarks, navigate to the root SciPy directory at the command line and execute:: - python dev.py bench + spin bench where ``bench`` activates the benchmark suite instead of the test suite. This builds SciPy and runs the benchmarks. (*Note: this could @@ -104,17 +104,17 @@ To run benchmarks from a particular benchmark module, such as ``optimize_linprog.py``, simply append the filename without the extension:: - python dev.py bench -t optimize_linprog + spin bench -t optimize_linprog To run a benchmark defined in a class, such as ``KleeMinty`` from ``optimize_linprog.py``:: - python dev.py bench -t optimize_linprog.KleeMinty + spin bench -t optimize_linprog.KleeMinty To compare benchmark results between the active branch and another, such as ``main``:: - python dev.py bench --compare main # select again by `-t optimize_linprog` + spin bench --compare main # select again by `-t optimize_linprog` All of the commands above display the results in plain text in the console, and the results are not saved for comparison with future diff --git a/doc/source/dev/contributor/compiled_code.rst b/doc/source/dev/contributor/compiled_code.rst index a9f9cb4c2831..813c4e876314 100644 --- a/doc/source/dev/contributor/compiled_code.rst +++ b/doc/source/dev/contributor/compiled_code.rst @@ -41,11 +41,11 @@ invokes the C code whose execution you want to debug. For instance Build SciPy in debug mode:: - python dev.py build -d + spin build -d Now, you can run:: - gdb --args python dev.py python mytest.py + gdb --args spin python mytest.py If you didn't compile with debug symbols enabled before, remove the ``build`` directory first. While in the debugger:: @@ -56,4 +56,4 @@ If you didn't compile with debug symbols enabled before, remove the The execution will now stop at the corresponding C function and you can step through it as usual. Instead of plain ``gdb`` you can, of course, use your favorite alternative debugger; run it on the -``python`` binary with arguments ``python dev.py python mytest.py``. +``python`` binary with arguments ``spin python mytest.py``. diff --git a/doc/source/dev/contributor/contributor_toc.rst b/doc/source/dev/contributor/contributor_toc.rst index c9dff5abf232..46d59bbf3a6d 100644 --- a/doc/source/dev/contributor/contributor_toc.rst +++ b/doc/source/dev/contributor/contributor_toc.rst @@ -15,7 +15,7 @@ though*). - :ref:`building-from-source` - how to set up a development environment, including installing compilers and SciPy dependencies, cloning the SciPy - repository on GitHub and updating git submodules, and using the ``dev.py`` + repository on GitHub and updating git submodules, and using the ``spin`` interface for building and running tests. - :ref:`editing-scipy` - how to edit SciPy Python code, with tips on finding which module contains SciPy functionality to be edited, adding new modules to @@ -52,9 +52,9 @@ Testing - :doc:`numpy:reference/testing` is the definitive guide to writing unit tests of NumPy or SciPy code (part of the NumPy documentation) - :ref:`writing-test-tips` contains tips for writing units tests -- :ref:`devpy-test` documents ``dev.py test``, the command to build SciPy and +- :ref:`devpy-test` documents ``spin test``, the command to build SciPy and run tests locally -- :ref:`debugging-linalg-issues` +- :ref:`debugging-linalg-issues` .. _docs: diff --git a/doc/source/dev/contributor/cython.rst b/doc/source/dev/contributor/cython.rst index 5c30131d3cfc..979db068fedc 100644 --- a/doc/source/dev/contributor/cython.rst +++ b/doc/source/dev/contributor/cython.rst @@ -120,7 +120,7 @@ Exercise #. Rebuild SciPy. Note that an extension module (a ``.so`` or ``.pyd`` file) has been added to the ``build/scipy/optimize/`` directory. -#. Time it, e.g. by dropping into IPython with ``python dev.py ipython`` and then: +#. Time it, e.g. by dropping into IPython with ``spin ipython`` and then: :: diff --git a/doc/source/dev/contributor/debugging_linalg_issues.rst b/doc/source/dev/contributor/debugging_linalg_issues.rst index a32e3b9700c0..31e95878bfb1 100644 --- a/doc/source/dev/contributor/debugging_linalg_issues.rst +++ b/doc/source/dev/contributor/debugging_linalg_issues.rst @@ -11,7 +11,7 @@ runtime dependency - and we support a significant number of BLAS/LAPACK libraries. This document aims to provide guidance about how to go about debugging linear -algebra issues. +algebra issues. If there is a real bug, it can be in one of three places: @@ -150,7 +150,7 @@ MKL is as simple as:: libblas 3.9.0-21_linux64_openblas --> 3.9.0-5_h92ddd45_netlib libcblas 3.9.0-21_linux64_openblas --> 3.9.0-5_h92ddd45_netlib liblapack 3.9.0-21_linux64_openblas --> 3.9.0-5_h92ddd45_netlib - + $ mamba install "libblas=*=*mkl" ... libblas 3.9.0-5_h92ddd45_netlib --> 3.9.0-21_linux64_mkl @@ -162,7 +162,7 @@ MKL is as simple as:: "user_api": "blas", "internal_api": "mkl", -This can be done for development builds as well, when building via ``dev.py`` +This can be done for development builds as well, when building via ``spin`` in the exact same way as in `SciPy's conda-forge build recipe `__ (outputs omitted for brevity, they're similar to the ones above):: @@ -170,10 +170,10 @@ in the exact same way as in `SciPy's conda-forge build recipe $ mamba env create -f environment.yml $ mamba activate scipy-dev $ mamba install "libblas=*=*netlib" # necessary, we need to build against blas/lapack - $ python dev.py build -C-Dblas=blas -C-Dlapack=lapack -C-Duse-g77-abi=true - $ python dev.py test -s linalg # run tests to verify + $ spin build -C-Dblas=blas -C-Dlapack=lapack -C-Duse-g77-abi=true + $ spin test -s linalg # run tests to verify $ mamba install "libblas=*=*mkl" - $ python dev.py test -s linalg + $ spin test -s linalg $ mamba install "libblas=*=*openblas" @@ -222,9 +222,9 @@ source. Once you have everything set up, the development experience is:: - $ python dev.py build -C-Dblas=flexiblas -C-Dlapack=flexiblas - $ FLEXIBLAS=NETLIB python dev.py test -s linalg - $ FLEXIBLAS=OpenBLAS python dev.py test -s linalg + $ spin build -C-Dblas=flexiblas -C-Dlapack=flexiblas + $ FLEXIBLAS=NETLIB spin test -s linalg + $ FLEXIBLAS=OpenBLAS spin test -s linalg # Or export the environment variable to make the selection stick: $ export FLEXIBLAS=OpenBLAS @@ -277,7 +277,7 @@ We're now ready to build SciPy against FlexiBLAS:: $ export PKG_CONFIG_PATH=$PWD/flexiblas-setup/built-libs/lib/pkgconfig/ $ cd scipy - $ python dev.py build -C-Dblas=flexiblas -C-Dlapack=flexiblas + $ spin build -C-Dblas=flexiblas -C-Dlapack=flexiblas ... Run-time dependency flexiblas found: YES 3.4.2 @@ -285,9 +285,9 @@ Now we can run the tests. Note that the ``NETLIB`` option is built without having to specify it; it's the default in FlexiBLAS and sources are included in its repository:: - $ FLEXIBLAS=OpenBLAS python dev.py test -s linalg - $ FLEXIBLAS=NETLIB python dev.py test -s linalg - $ python dev.py test -s linalg # uses the default (NETLIB) + $ FLEXIBLAS=OpenBLAS spin test -s linalg + $ FLEXIBLAS=NETLIB spin test -s linalg + $ spin test -s linalg # uses the default (NETLIB) This backend switching can also be done inside a Python interpreter with ``threadpoolctl`` (see `its README @@ -442,7 +442,7 @@ file to automate this and avoid the manual paths: $ ./build/repro_c # output may vary info = 0 - Re(eigv) = 4.000000 , 8.000000 , inf , -inf , + Re(eigv) = 4.000000 , 8.000000 , inf , -inf , Im(eigv = 0.000000 , 0.000000 , -nan , -nan , .. tab-item:: Fortran @@ -464,8 +464,8 @@ file to automate this and avoid the manual paths: info = 0 alphar = 1.0204501477442456 11.707793036240817 3.7423579363517347E-014 -1.1492523608519701E-014 - alphai = 0.0000000000000000 0.0000000000000000 0.0000000000000000 0.0000000000000000 - beta = 0.25511253693606051 1.4634741295300704 0.0000000000000000 0.0000000000000000 + alphai = 0.0000000000000000 0.0000000000000000 0.0000000000000000 0.0000000000000000 + beta = 0.25511253693606051 1.4634741295300704 0.0000000000000000 0.0000000000000000 Re(eigv) = 4.0000000000000142 8.0000000000001741 Infinity -Infinity Im(eigv) = 0.0000000000000000 0.0000000000000000 NaN NaN @@ -531,7 +531,7 @@ Here is an example ``gdb`` session:: (gdb) s # step through the C function Single stepping until exit from function dpotrf_, which has no line number information. - f2py_rout__flapack_dpotrf (capi_self=, capi_args=, + f2py_rout__flapack_dpotrf (capi_self=, capi_args=, capi_keywds=, f2py_func=0x7ffff4c48820 ) at scipy/linalg/_flapackmodule.c:63281 .... diff --git a/doc/source/dev/contributor/development_workflow.rst b/doc/source/dev/contributor/development_workflow.rst index 82d41c40c03c..9eb386c58c59 100644 --- a/doc/source/dev/contributor/development_workflow.rst +++ b/doc/source/dev/contributor/development_workflow.rst @@ -109,11 +109,11 @@ when it's time to submit a pull request. It's also a good idea to build this branch and run tests before continuing. Assuming you've followed one of the :ref:`building-from-source` pages to set up your development environment, you'll need to activate your development -environment and then run tests (note that the ``dev.py test`` command will +environment and then run tests (note that the ``spin test`` command will perform a build automatically if needed):: conda activate scipy-dev - python dev.py test -v + spin test -v .. _editing-workflow: diff --git a/doc/source/dev/contributor/devpy_test.rst b/doc/source/dev/contributor/devpy_test.rst index d98b9f2f7677..9cc1789acd3e 100644 --- a/doc/source/dev/contributor/devpy_test.rst +++ b/doc/source/dev/contributor/devpy_test.rst @@ -7,24 +7,24 @@ Running SciPy Tests Locally Basic test writing and execution from within the Python interpreter is documented in the :doc:`NumPy/SciPy testing guidelines `. This page -includes information about running tests from the command line using SciPy's -``dev.py`` command line tool. *Note: Before beginning, ensure that* |pytest|_ +includes information about running tests from the command line using the +``spin`` command line tool. *Note: Before beginning, ensure that* |pytest|_ *is installed.* .. note:: - The ``dev.py`` interface is self-documenting, in the sense that everything on + The ``spin`` interface is self-documenting, in the sense that everything on this page and more (including usage examples for each command) can be - accessed with ``python dev.py --help`` and for individual commands like - ``python dev.py --help``. In this case, you can check - ``python dev.py test --help``. + accessed with ``spin --help`` and for individual commands like + ``spin --help``. In this case, you can check + ``spin test --help``. To run all tests, navigate to the root SciPy directory at the command line and execute :: - python dev.py test + spin test This builds SciPy (or updates an existing build) and runs the tests. @@ -33,60 +33,60 @@ To run tests on a particular submodule, such as ``optimize``, use the :: - python dev.py test -s optimize + spin test -s optimize To run a particular test module, use the Pytest syntax of ``--test`` (or ``-t``):: - python dev.py test -t scipy..tests. + spin test -t scipy..tests. Example for |test-linprog|_ file tests, run: :: - python dev.py test -t scipy.optimize.tests.test_linprog + spin test -t scipy.optimize.tests.test_linprog To run a test class: :: - python dev.py test -t scipy..tests.:: + spin test -t scipy..tests.:: Example for ``TestLinprogRSCommon`` class from ``test_linprog.py``: :: - python dev.py test -t scipy.optimize.tests.test_linprog::TestLinprogRSCommon + spin test -t scipy.optimize.tests.test_linprog::TestLinprogRSCommon To run a particular test: :: - python dev.py test -t scipy..tests.:: + spin test -t scipy..tests.:: Example for ``test_unknown_solvers_and_options`` from ``test_linprog.py``: :: - python dev.py test -t scipy.optimize.tests.test_linprog::test_unknown_solvers_and_options + spin test -t scipy.optimize.tests.test_linprog::test_unknown_solvers_and_options For tests within a class, you need to specify the class name and the test name: :: - python dev.py test -t scipy..tests.:::: + spin test -t scipy..tests.:::: Example: :: - python dev.py test -t scipy.optimize.tests.test_linprog::TestLinprogRSCommon::test_nontrivial_problem_with_guess + spin test -t scipy.optimize.tests.test_linprog::TestLinprogRSCommon::test_nontrivial_problem_with_guess Other useful options include: - ``-v`` or ``--verbose``, which activates the verbose option for more - detailed output. + detailed output. - ``-b`` or ``--array-api-backend`` *backend* to include alternative array backends in array-api-compatible tests. See :ref:`dev-arrayapi` for details. @@ -96,15 +96,15 @@ Other useful options include: - ``-n`` or ``--no-build`` to prevent SciPy from updating the build before testing - ``-j`` or ``--parallel`` *n* to engage *n* cores when building SciPy; - e.g. \ ``python dev.py test -j 4`` engages four cores. As of `#10172`_ + e.g. \ ``spin test -j 4`` engages four cores. As of `#10172`_ this also runs the tests on four cores if |pytest-xdist|_ is installed. - ``-m full`` or ``--mode full`` to run the "full" test suite, including tests marked ``slow`` (e.g. with ``@pytest.mark.slow``). Note that this does not *run* tests marked ``xslow``; see Tips below. - ``--`` to send remaining command line arguments to ``pytest`` instead of - ``dev.py test``. For instance, while ``-n`` sent to ``pytest.py`` activates + ``spin test``. For instance, while ``-n`` sent to ``pytest.py`` activates the ``--no-build`` option, ``-n`` sent to ``pytest`` runs the tests on - multiple cores; e.g. \ ``python dev.py test -- -n 4`` runs tests using + multiple cores; e.g. \ ``spin test -- -n 4`` runs tests using four cores. *Note:* |pytest-xdist|_ *must be installed for testing on multiple cores.* Common command line arguments for ``pytest`` include: @@ -124,11 +124,11 @@ Tips: If you built SciPy from source but are having trouble running tests after a change to the codebase, try deleting the ``scipy/build`` -directory. This forces ``dev.py`` to completely rebuild SciPy before +directory. This forces ``spin`` to completely rebuild SciPy before performing tests. There is an additional level of very slow tests (several minutes), -which are disabled even when calling ``python dev.py test -m full``. +which are disabled even when calling ``spin test -m full``. They can be enabled by setting the environment variable ``SCIPY_XSLOW=1`` before running the test suite. diff --git a/doc/source/dev/contributor/pep8.rst b/doc/source/dev/contributor/pep8.rst index d89a213fe2b2..8408fc5b2f65 100644 --- a/doc/source/dev/contributor/pep8.rst +++ b/doc/source/dev/contributor/pep8.rst @@ -39,7 +39,7 @@ compliance before pushing your code: Alternatively, you can run the check manually from the SciPy root directory:: - python dev.py lint + spin lint You can also run the linter on specific files, using the ``--files`` option:: diff --git a/doc/source/dev/contributor/rendering_documentation.rst b/doc/source/dev/contributor/rendering_documentation.rst index 8b46aaa9eb36..2000be38f671 100644 --- a/doc/source/dev/contributor/rendering_documentation.rst +++ b/doc/source/dev/contributor/rendering_documentation.rst @@ -62,7 +62,7 @@ with Sphinx`_ \ *.* To render the documentation on your own machine: 0. Ensure that you have a working SciPy build (see :ref:`building-from-source`). -#. Then run ``python dev.py doc`` to build the documentation. +#. Then run ``spin docs`` to build the documentation. This can take a while the first time, but subsequent documentation builds are typically much faster. #. View the documentation in ``doc/build/html/``. You can start @@ -74,7 +74,7 @@ To render the documentation on your own machine: - Changes to certain documents do not take effect when Sphinx documentation is rebuilt. In this case, you can build from scratch by deleting the directories ``scipy/doc/build`` and ``source/reference/generated``, or by - running ``python dev.py doc clean`` then building the docs again. + running ``spin docs clean`` then building the docs again. - In case the SciPy version found by the above command is different from that of the latest commit in the repo, you will see a message like:: diff --git a/doc/source/dev/contributor/reviewing_prs.rst b/doc/source/dev/contributor/reviewing_prs.rst index 535a239d998c..051de88f80f5 100644 --- a/doc/source/dev/contributor/reviewing_prs.rst +++ b/doc/source/dev/contributor/reviewing_prs.rst @@ -72,9 +72,9 @@ Assuming you set up your development environment according to build the code and test it:: - python dev.py test -v + spin test -v -and if you ``import`` SciPy from within IPython (start it with ``python dev.py +and if you ``import`` SciPy from within IPython (start it with ``spin ipython``), you'll be importing the author's modified version of SciPy. If you want to collaborate with the author on their PR, you might instead diff --git a/doc/source/dev/core-dev/releasing.rst.inc b/doc/source/dev/core-dev/releasing.rst.inc index 8f3f6f430877..63336b726c50 100644 --- a/doc/source/dev/core-dev/releasing.rst.inc +++ b/doc/source/dev/core-dev/releasing.rst.inc @@ -148,7 +148,7 @@ Here is a complete list of artifacts created for a release: An ``sdist`` is generated by running ``python -m build --sdist`` (note: we still need to move this into a CI job!), and the Changelog and README are built -by running ``python dev.py notes`` (with tags, see ``python dev.py notes +by running ``spin notes`` (with tags, see ``spin notes --help``) in the repo root, and end up in ``REPO_ROOT/release/``. Do this after you've created the signed tag locally. If this completes without issues, push the release commit (not the tag, see section above) to the scipy repo. @@ -170,7 +170,7 @@ done in an automated fashion using ``tools/download-wheels.py``:: $ python tools/download-wheels.py 1.5.0rc1 -w REPO_ROOT/release/installers After this, we want to regenerate the README file, in order to have the MD5 and -SHA256 checksums of the just downloaded wheels in it. Run ``python dev.py +SHA256 checksums of the just downloaded wheels in it. Run ``spin notes`` again. diff --git a/doc/source/dev/core-dev/vendored-code.rst.inc b/doc/source/dev/core-dev/vendored-code.rst.inc index b2966e8b13c2..be0920079b65 100644 --- a/doc/source/dev/core-dev/vendored-code.rst.inc +++ b/doc/source/dev/core-dev/vendored-code.rst.inc @@ -3,29 +3,47 @@ Vendored Code ============= Many parts of the SciPy codebase are maintained elsewhere, and vendored in SciPy. -Some of these parts are vendored as git submodules, for example, ``boost_math``. +Some of these parts are vendored as git submodules, for example, ``xsf``, +or are placed under the ``subprojects`` directory (or both). -Other parts are not vendored as git submodules, despite having a maintained upstream. -This is mainly for historical reasons, and it is possible that some of these parts -will see patches contributed upstream and become git submodules in the future. +Other parts are not vendored as git submodules or under the ``subprojects`` directory, +despite having a maintained upstream. This is usually either because: + +1. a subset of the upstream repo is vendored with a script. + (It is possible that these parts will be moved under ``subprojects`` in the future.) + +2. Code has been copied into SciPy and modified since. + (It is possible that some of these parts will see patches contributed upstream and + become git submodules or be moved under ``subprojects`` in the future.) Maintainers should be careful to *not* accept contributions (especially trivial changes) into parts of SciPy where the code is actively maintained upstream. Instead, they should direct contributors to the upstream repo. -Currently, this includes the following parts of the codebase: + +Parts of the codebase which are vendored with a script include: + +- PRIMA_, at ``scipy/_lib/pyprima`` + +Parts of the codebase which contain code copied from an upstream include: - DIRECT_, at ``scipy/optimize/_direct`` - ARPACK_, at ``scipy/sparse/linalg/_eigen/arpack/ARPACK`` - SuperLU_, at ``scipy/sparse/linalg/_dsolve/SuperLU`` -- QHull_, at ``scipy/spatial/qhull_src`` - trlib_, at ``scipy/optimize/_trlib`` - UNU.RAN_, at ``scipy/stats/_unuran`` -- PRIMA_, at ``scipy/_lib/pyprima`` +- `Cython/Tempita`_, at ``scipy/_build_utils/tempita`` +- `fast_matrix_market`_, at ``scipy/io/_fast_matrix_market`` +- `numpydoc/docscrape`_, at ``scipy/_lib/_docscrape.py`` .. _ARPACK: https://github.com/opencollab/arpack-ng .. _SuperLU: https://github.com/xiaoyeli/superlu -.. _QHull: https://github.com/qhull/qhull .. _trlib: https://github.com/felixlen/trlib .. _UNU.RAN: https://statmath.wu.ac.at/unuran/ .. _DIRECT: https://github.com/stevengj/nlopt/tree/master/src/algs/direct .. _PRIMA: https://github.com/libprima/prima +.. _`Cython/Tempita`: https://github.com/cython/cython/tree/master/Cython/Tempita +.. _`fast_matrix_market`: https://github.com/alugowski/fast_matrix_market +.. _`numpydoc/docscrape`: https://github.com/numpy/numpydoc/ + +Please refer to https://github.com/scipy/scipy/issues/21232 for further details and +tracking of vendored code in the repository. diff --git a/doc/source/dev/hacking.rst b/doc/source/dev/hacking.rst index 60075ce5d4cb..e675987310a0 100644 --- a/doc/source/dev/hacking.rst +++ b/doc/source/dev/hacking.rst @@ -94,7 +94,7 @@ tests, benchmarks, and correct code style. Alternatively, you may run the linter manually:: - python dev.py lint + spin lint Most IDEs and text editors also have settings that can help you follow PEP8, for example by translating tabs by four spaces. More diff --git a/doc/source/dev/roadmap-detailed.rst b/doc/source/dev/roadmap-detailed.rst index b1ebf31edb5c..495d49772b5c 100644 --- a/doc/source/dev/roadmap-detailed.rst +++ b/doc/source/dev/roadmap-detailed.rst @@ -234,6 +234,8 @@ Ideas for new features: - Add type-generic wrappers in the Cython BLAS and LAPACK - Make many of the linear algebra routines into gufuncs +- Complete support for batched operations (see + `gh-21466 `__) **BLAS and LAPACK** @@ -241,20 +243,15 @@ The Python and Cython interfaces to BLAS and LAPACK in ``scipy.linalg`` are one of the most important things that SciPy provides. In general ``scipy.linalg`` is in good shape, however we can make a number of improvements: -1. Library support. Our released wheels now ship with OpenBLAS, which is - currently the only feasible performant option (ATLAS is too slow, MKL cannot - be the default due to licensing issues, Accelerate support is dropped - because Apple doesn't update Accelerate anymore). OpenBLAS isn't very stable - though, sometimes its releases break things and it has issues with threading - (currently the only issue for using SciPy with PyPy3). We need at the very - least better support for debugging OpenBLAS issues, and better documentation - on how to build SciPy with it. An option is to use BLIS for a BLAS - interface (see `numpy gh-7372 `__). - -2. Support for newer LAPACK features. In SciPy 1.2.0 we increased the minimum - supported version of LAPACK to 3.4.0. Now that we dropped Python 2.7, we - can increase that version further (MKL + Python 2.7 was the blocker for - >3.4.0 previously) and start adding support for new features in LAPACK. +1. Add support for ILP64 (64-bit) BLAS and LAPACK (see + `gh-21889 `__) +2. Unify the two sets of low-level BLAS/LAPACK wrappers, probably dropping the + ``f2py``-based ones (see + `gh-20682 `__) +3. Improve and document the various ways we link to BLAS and LAPACK from C + and C++ code internally in SciPy (see + `gh-20002 `__ and + `gh-21130 `__) misc diff --git a/doc/source/dev/toolchain.rst b/doc/source/dev/toolchain.rst index 7d4e7ce05cf9..01b08f88105c 100644 --- a/doc/source/dev/toolchain.rst +++ b/doc/source/dev/toolchain.rst @@ -59,6 +59,7 @@ mid-year release of SciPy. ================ ======================================================================= Date Pythons supported ================ ======================================================================= + 2025 Py3.11+ 2024 Py3.10+ 2023 Py3.9+ 2022 Py3.8+ @@ -80,13 +81,16 @@ needs to be written using what is common in all of those 4 `NumPy releases`_. .. dropdown:: Python and NumPy version support per SciPy version - The table shows the NumPy versions suitable for each major Python version. - This table does not distinguish SciPy patch versions (e.g. when a new Python - version is released, SciPy will generally issue a compatible patch version). + The table shows the NumPy and Python versions suitable for each minor SciPy + version. Note that not all patch versions for a particular minor version of + SciPy support all listed versions of Python. Only the most recent patch version + within each minor version is guaranteed to support all listed Python versions. ================= ======================== ======================= SciPy version Python versions NumPy versions ================= ======================== ======================= + 1.15 >=3.10, <3.14 >=1.23.5, <2.5.0 + 1.14 >=3.10, <3.14 >=1.23.5, <2.3.0 1.13 >=3.9, <3.13 >=1.22.4, <2.3.0 1.12 >=3.9, <3.13 >=1.22.4, <2.0.0 1.11 >=3.9, <3.13 >=1.21.6, <1.27.0 diff --git a/doc/source/reference/sparse.migration_to_sparray.rst b/doc/source/reference/sparse.migration_to_sparray.rst index f169a23f4a9a..7093cf3ab200 100644 --- a/doc/source/reference/sparse.migration_to_sparray.rst +++ b/doc/source/reference/sparse.migration_to_sparray.rst @@ -43,7 +43,7 @@ Overview and big picture ``scipy.sparse.linalg.matrix_power(A, n)``. - When index arrays are provided to the constructor functions, spmatrix selects a dtype based on dtype and values of the incoming arrays, while - sparray only bases on the dtype of the incoming arrays. For example, + sparray only considers the dtype of the incoming arrays. For example, ``M=csr_matrix((data, indices, indptr))`` results in ``int32`` dtype for ``M.indices`` so long as the values in ``indices`` and ``indptr`` are small, even if the ``dtype`` of the incoming arrays are ``int64``. In contrast, @@ -162,15 +162,15 @@ Their signatures are:: def random_array(shape, density=0.01, format='coo', dtype=None, rng=None, data_sampler=None): The ``random_array`` function has a ``shape`` (2-tuple) arg rather than -two integers. And the ``random_state`` arg defaults to NumPy's new ``default_rng()``. +two integers. And the ``rng`` arg defaults to NumPy's new ``default_rng()``. This differs from the spmatrix ``rand`` and ``random`` which default to the global RandomState instance. If you don't care much about these things, leaving it as the default should work fine. If you care about seeding your -random numbers, you should probably add a ``random_state=...`` keyword argument +random numbers, you should probably add a ``rng=...`` keyword argument to this call when you switch functions. In summary, to migrate to ``random_array`` change the function name, switch the shape argument to a single tuple argument, leave any other parameters as before, and think about what -sort of ``random_state=`` argument should be used, if any. +sort of ``rng=`` argument should be used, if any. The `diags_array` function uses keyword-only rules for arguments. So you have to type the `offsets=` in front of the offsets arguments. That seems like a pain @@ -239,8 +239,8 @@ Details: shape changes and reductions - Reduction operations along an axis reduce the shape: - - ``M.sum(axis=1)`` returns a 2D row matrix by summing along axis 1. - - ``A.sum(axis=1)`` returns a 1D ``coo_array`` summing along axis 1. + - ``M.min(axis=1)`` returns a 2D row matrix of the min along axis 1. + - ``A.min(axis=1)`` returns a 1D ``coo_array`` of the min along axis 1. Some reductions return dense arrays/matrices instead of sparse ones: ============ ========= @@ -261,7 +261,7 @@ Details: shape changes and reductions - Some reductions return a scalar. Those should behave as they did before and shouldn’t need to be considered during migration. E.g. - ``A.sum()`` + ``A.min()`` .. _sparse-migration-removed-methods: @@ -341,17 +341,28 @@ Use tests to find * and ** spots you change when you shouldn't have. So the test suite with this monkey-patch checks the corrections too. - Add the following code to your ``conftest.py`` file near the top. + Add the following code to your ``conftest.py`` file. Then run your tests locally. If there are many matrix expressions, you might want to test one section of your codebase at a time. A quick read of the code shows that it raises a ``ValueError`` whenever ``*`` is used between two matrix-like objects (sparse or dense), - and whenever ``**`` is used for matrix power. + and whenever ``**`` is used for matrix power. It also produces a warning + whenever sum/mean/min/max/argmin/argmax are used with an axis so the + output will be 2D with spmatrix and 1D with sparray. That means you + check that the code will handle either 1D or 2D output via + ``flatten``/``ravel``, ``np.atleast_2d`` or indexing. .. code-block:: python + #================== Added to check spmatrix usage ======================== import scipy + from warnings import warn + def flag_this_call(*args, **kwds): + raise ValueError("Old spmatrix function names for rand/spdiags called") + + scipy.sparse._construct.rand = flag_this_call + scipy.sparse._construct.spdiags = flag_this_call class _strict_mul_mixin: def __mul__(self, other): @@ -372,53 +383,143 @@ Use tests to find * and ** spots def __pow__(self, *args, **kwargs): raise ValueError('spmatrix ** found! Use linalg.matrix_power?') - class _strict_coo_matrix(_strict_mul_mixin, scipy.sparse.coo_matrix): + @property + def A(self): + raise TypeError('spmatrix A property found! Use .toarray()') + + @property + def H(self): + raise TypeError('spmatrix H property found! Use .conjugate().T') + + def asfptype(self): + raise TypeError('spmatrix asfptype found! rewrite needed') + + def get_shape(self): + raise TypeError('spmatrix get_shape found! Use .shape') + + def getformat(self): + raise TypeError('spmatrix getformat found! Use .format') + + def getmaxprint(self): + raise TypeError('spmatrix getmaxprint found! Use .shape') + + def getnnz(self): + raise TypeError('spmatrix getnnz found! Use .nnz') + + def getH(self): + raise TypeError('spmatrix getH found! Use .conjugate().T') + + def getrow(self): + raise TypeError('spmatrix getrow found! Use .row') + + def getcol(self): + raise TypeError('spmatrix getcol found! Use .col') + + def sum(self, *args, **kwds): + axis = args[0] if len(args)==1 else args if args else kwds.get("axis", None) + if axis is not None: + warn(f"\nMIGRATION WARNING: spmatrix sum found using axis={axis}. " + "\nsparray with a single axis will produce 1D output. " + "\nCheck nearby to ensure 1D output is handled OK in this spot.\n") + print(f"{args=} {axis=} {kwds=}") + return super().sum(*args, **kwds) + + def mean(self, *args, **kwds): + axis = args[0] if len(args)==1 else args if args else kwds.get("axis", None) + if axis is not None: + warn(f"\nMIGRATION WARNING: spmatrix mean found using axis={axis}." + "\nsparray with a single axis will produce 1D output.\n" + "Check nearby to ensure 1D output is handled OK in this spot.\n") + return super().mean(*args, **kwds) + + def min(self, *args, **kwds): + axis = args[0] if len(args)==1 else args if args else kwds.get("axis", None) + if axis is not None: + warn(f"\nMIGRATION WARNING: spmatrix min found using axis={axis}." + "\nsparray with a single axis will produce 1D output. " + "Check nearby to ensure 1D output is handled OK in this spot.\n") + return super().min(*args, **kwds) + + def max(self, *args, **kwds): + axis = args[0] if len(args)==1 else args if args else kwds.get("axis", None) + if axis is not None: + warn(f"\nMIGRATION WARNING: spmatrix max found using axis={axis}." + "\nsparray with a single axis will produce 1D output. " + "Check nearby to ensure 1D output is handled OK in this spot.\n") + return super().max(*args, **kwds) + + def argmin(self, *args, **kwds): + axis = args[0] if len(args)==1 else args if args else kwds.get("axis", None) + if axis is not None: + warn(f"\nMIGRATION WARNING: spmatrix argmin found using axis={axis}." + "\nsparray with a single axis will produce 1D output. " + "Check nearby to ensure 1D output is handled OK in this spot.\n") + return super().argmin(*args, **kwds) + + def argmax(self, *args, **kwds): + axis = args[0] if len(args)==1 else args if args else kwds.get("axis", None) + if axis is not None: + warn(f"\nMIGRATION WARNING: spmatrix argmax found using axis={axis}." + "\nsparray with a single axis will produce 1D output. " + "Check nearby to ensure 1D output is handled OK in this spot.\n") + return super().argmax(*args, **kwds) + + + class coo_matrix_strict(_strict_mul_mixin, scipy.sparse.coo_matrix): pass - class _strict_bsr_matrix(_strict_mul_mixin, scipy.sparse.bsr_matrix): + class bsr_matrix_strict(_strict_mul_mixin, scipy.sparse.bsr_matrix): pass - class _strict_csr_matrix(_strict_mul_mixin, scipy.sparse.csr_matrix): + class csr_matrix_strict(_strict_mul_mixin, scipy.sparse.csr_matrix): pass - class _strict_csc_matrix(_strict_mul_mixin, scipy.sparse.csc_matrix): + class csc_matrix_strict(_strict_mul_mixin, scipy.sparse.csc_matrix): pass - class _strict_dok_matrix(_strict_mul_mixin, scipy.sparse.dok_matrix): + class dok_matrix_strict(_strict_mul_mixin, scipy.sparse.dok_matrix): pass - class _strict_lil_matrix(_strict_mul_mixin, scipy.sparse.lil_matrix): + class lil_matrix_strict(_strict_mul_mixin, scipy.sparse.lil_matrix): pass - class _strict_dia_matrix(_strict_mul_mixin, scipy.sparse.dia_matrix): + class dia_matrix_strict(_strict_mul_mixin, scipy.sparse.dia_matrix): pass - scipy.sparse.coo_matrix = scipy.sparse._coo.coo_matrix = _strict_coo_matrix - scipy.sparse.bsr_matrix = scipy.sparse._bsr.bsr_matrix = _strict_bsr_matrix - scipy.sparse.csr_matrix = scipy.sparse._csr.csr_matrix = _strict_csr_matrix - scipy.sparse.csc_matrix = scipy.sparse._csc.csc_matrix = _strict_csc_matrix - scipy.sparse.dok_matrix = scipy.sparse._dok.dok_matrix = _strict_dok_matrix - scipy.sparse.lil_matrix = scipy.sparse._lil.lil_matrix = _strict_lil_matrix - scipy.sparse.dia_matrix = scipy.sparse._dia.dia_matrix = _strict_dia_matrix - - scipy.sparse._compressed.csr_matrix = _strict_csr_matrix - - scipy.sparse._construct.bsr_matrix = _strict_bsr_matrix - scipy.sparse._construct.coo_matrix = _strict_coo_matrix - scipy.sparse._construct.csc_matrix = _strict_csc_matrix - scipy.sparse._construct.csr_matrix = _strict_csr_matrix - scipy.sparse._construct.dia_matrix = _strict_dia_matrix - - scipy.sparse._extract.coo_matrix = _strict_coo_matrix - - scipy.sparse._matrix.bsr_matrix = _strict_bsr_matrix - scipy.sparse._matrix.coo_matrix = _strict_coo_matrix - scipy.sparse._matrix.csc_matrix = _strict_csc_matrix - scipy.sparse._matrix.csr_matrix = _strict_csr_matrix - scipy.sparse._matrix.dia_matrix = _strict_dia_matrix - scipy.sparse._matrix.dok_matrix = _strict_dok_matrix - scipy.sparse._matrix.lil_matrix = _strict_lil_matrix - + scipy.sparse.coo_matrix = scipy.sparse._coo.coo_matrix = coo_matrix_strict + scipy.sparse.bsr_matrix = scipy.sparse._bsr.bsr_matrix = bsr_matrix_strict + scipy.sparse.csr_matrix = scipy.sparse._csr.csr_matrix = csr_matrix_strict + scipy.sparse.csc_matrix = scipy.sparse._csc.csc_matrix = csc_matrix_strict + scipy.sparse.dok_matrix = scipy.sparse._dok.dok_matrix = dok_matrix_strict + scipy.sparse.lil_matrix = scipy.sparse._lil.lil_matrix = lil_matrix_strict + scipy.sparse.dia_matrix = scipy.sparse._dia.dia_matrix = dia_matrix_strict + + scipy.sparse._compressed.csr_matrix = csr_matrix_strict + + scipy.sparse._construct.bsr_matrix = bsr_matrix_strict + scipy.sparse._construct.coo_matrix = coo_matrix_strict + scipy.sparse._construct.csc_matrix = csc_matrix_strict + scipy.sparse._construct.csr_matrix = csr_matrix_strict + scipy.sparse._construct.dia_matrix = dia_matrix_strict + + scipy.sparse._extract.coo_matrix = coo_matrix_strict + + scipy.sparse._matrix.bsr_matrix = bsr_matrix_strict + scipy.sparse._matrix.coo_matrix = coo_matrix_strict + scipy.sparse._matrix.csc_matrix = csc_matrix_strict + scipy.sparse._matrix.csr_matrix = csr_matrix_strict + scipy.sparse._matrix.dia_matrix = dia_matrix_strict + scipy.sparse._matrix.dok_matrix = dok_matrix_strict + scipy.sparse._matrix.lil_matrix = lil_matrix_strict + + del coo_matrix_strict + del bsr_matrix_strict + del csr_matrix_strict + del csc_matrix_strict + del dok_matrix_strict + del lil_matrix_strict + del dia_matrix_strict + #========================================== .. _sparse-migration-index-array-dtypes: diff --git a/doc/source/release.rst b/doc/source/release.rst index 7313e38c5f3a..915e1dfc4457 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -8,7 +8,9 @@ see the `commit logs `_. .. toctree:: :maxdepth: 1 + release/1.17.0-notes release/1.16.0-notes + release/1.15.3-notes release/1.15.2-notes release/1.15.1-notes release/1.15.0-notes diff --git a/doc/source/release/1.15.3-notes.rst b/doc/source/release/1.15.3-notes.rst new file mode 100644 index 000000000000..b7ff4c70343d --- /dev/null +++ b/doc/source/release/1.15.3-notes.rst @@ -0,0 +1,108 @@ +========================== +SciPy 1.15.3 Release Notes +========================== + +.. contents:: + +SciPy 1.15.3 is a bug-fix release with no new features +compared to 1.15.2. + + + +Authors +======= +* Name (commits) +* aiudirog (1) + +* Nickolai Belakovski (1) +* Florian Bourgey (1) + +* Richard Strong Bowen (2) + +* Jake Bowhay (1) +* Dietrich Brunn (2) +* Evgeni Burovski (1) +* Lucas Colley (1) +* Ralf Gommers (1) +* Saarthak Gupta (1) + +* Matt Haberland (4) +* Chengyu Han (1) + +* Lukas Huber (1) + +* Nick ODell (2) +* Ilhan Polat (4) +* Tyler Reddy (52) +* Neil Schemenauer (1) + +* Dan Schult (1) +* sildater (1) + +* Gagandeep Singh (4) +* Albert Steppi (2) +* Matthias Urlichs (1) + +* David Varela (1) + +* ਗਗਨਦੀਪ ਸਿੰਘ (Gagandeep Singh) (3) + +A total of 24 people contributed to this release. +People with a "+" by their names contributed a patch for the first time. +This list of names is automatically generated, and may not be fully complete. + + +Issues closed for 1.15.3 +------------------------ + +* `#10634 `__: BUG: optimize: ``least_squares`` with ``'trf'`` and ``'trf_sover=lsmr'``... +* `#18146 `__: BUG: scipy.sparse.linalg.expm_multiply fails with sparse matrices +* `#19418 `__: BUG: integrate.solve_ivp fails for some step sizes if dense_output=True... +* `#19865 `__: BUG: HalfspaceIntersection.add_halfspaces() does not seem to... +* `#20988 `__: BUG: special.hyp2f1: wrong result for extreme inputs +* `#22236 `__: BUG: scipy v1.15 breaking for pytest when assert-rewrite is on +* `#22400 `__: BUG: stats.genextreme.stats: Spurious warning from ``genextreme.stats(0.0,``... +* `#22451 `__: BUG: interpolative svd broken for non-square linear operators +* `#22515 `__: CI: Some GitHub workflows failing due to check on ``actions/cache``... +* `#22547 `__: BUG: _lib: Data race reported by TSAN in ``ccallback`` mechanism +* `#22558 `__: BUG: linalg.expm: bug on Windows / conda +* `#22574 `__: CI: benchmark job on CircleCI is failing on ``io.mmread`` memory... +* `#22586 `__: BUG: ndimage.median_filter: additional hard crashes +* `#22589 `__: BUG: spatial: ``Rotation`` no longer supports zero-length collections +* `#22599 `__: DOC: sparse.linalg.ArpackError: entire default ``infodict`` displayed +* `#22615 `__: CI: oneAPI job: ``Not enough disk space.`` +* `#22637 `__: BUG: Transposed LinearOperator fails on vector multiplication +* `#22655 `__: BUG: optimize.linprog: 40x slower in v1.15 compared to v1.14 +* `#22681 `__: DOC: integrate.tanhsinh: documentation refers to non-existent... +* `#22684 `__: BUG: signal.resample_poly: dtype not preserved +* `#22720 `__: MAINT, CI: floating point exceptions activated in NumPy +* `#22868 `__: BUG: re-importing ``scipy`` fails +* `#22903 `__: BUG: special.logsumexp: nan in 1.15 + + +Pull requests for 1.15.3 +------------------------ + +* `#20035 `__: BUG: spatial.HalfspaceIntersection: raise on non-feasible half... +* `#22502 `__: BUG: special: Fix typo in specfun::chgu +* `#22517 `__: CI: Use actions/cache 4.2.0 +* `#22532 `__: BUG: Remove warning for genextreme.stats(0.0, moments='mvsk') +* `#22543 `__: REL, MAINT: prep for 1.15.3 +* `#22555 `__: BUG: ``scipy.sparse.linalg``\ : Fix ``expm_multiply`` if both... +* `#22561 `__: BUG: _lib: Fix data race found by TSAN, use SCIPY_TLS. +* `#22567 `__: BUG: optimize: Fix ``bracket_root`` termination check and default... +* `#22582 `__: BUG: ``integrate.solve_ivp``\ : Avoid duplicate time stamps in... +* `#22587 `__: BUG: Pin jupyterlite-sphinx to >= 0.19.1 +* `#22588 `__: BUG/BLD: xsf: force defining the mdspan parenthesis operator... +* `#22590 `__: BENCH: remove triple run of mmread/mmwrite benchmark, limit sizes +* `#22600 `__: BUG: Fix ArpackError default argument +* `#22608 `__: BUG: ndimage.median_filter: fix segfault when using ``mode='mirror'`` +* `#22617 `__: CI: minimise disk space usage for oneAPI jobs +* `#22642 `__: BUG: sparse: sparse sum/mean out parameter shape not enforced... +* `#22643 `__: BUG: spatial.transform.Rotation: support 0-length rotations +* `#22660 `__: BUG: optimize: avoid expensive access of ``basis.col_status``... +* `#22689 `__: BUG: signal.resample_poly: fix dtype preservation +* `#22690 `__: MAINT/DOC: integrate.tanhsinh: lightly refactor error estimate... +* `#22693 `__: BUG: spatial.HalfspaceIntersection: fix ``add_halfspaces`` batch... +* `#22726 `__: MAINT: compensate for dot exceptions +* `#22763 `__: BUG: sparse: Remove reference cycle to improve memory use +* `#22772 `__: BUG: sparse.linalg: Transposed ``LinearOperator`` multiplication... +* `#22784 `__: BUG: signal._short_time_fft: incorrect index computation in ``upper_border_begin``... +* `#22792 `__: BUG: signal.ShortTimeFFT.upper_border_begin: Document parameter... +* `#22801 `__: BUG: ``signal.windows._windows.kaiser_bessel_derived``\ : use... +* `#22810 `__: BUG: special.hyp2f1: fix for extreme inputs +* `#22822 `__: BUG: linalg.expm: Fix noncompliant compiler branch typos in C... +* `#22828 `__: BUG: add workaround for pytest assertion rewriting overreach +* `#22834 `__: BUG: linalg: Fix shape mismatch in interpolative.svd +* `#22869 `__: BUG: optimize._highspy: don't import from inside a C module +* `#22910 `__: MAINT: special.logsumexp: improvement when weight of largest... diff --git a/doc/source/release/1.16.0-notes.rst b/doc/source/release/1.16.0-notes.rst index 5b66a25ba1a0..7b3296a3544b 100644 --- a/doc/source/release/1.16.0-notes.rst +++ b/doc/source/release/1.16.0-notes.rst @@ -6,7 +6,7 @@ SciPy 1.16.0 Release Notes .. contents:: -SciPy 1.16.0 is the culmination of X months of hard work. It contains +SciPy 1.16.0 is the culmination of 6 months of hard work. It contains many new features, numerous bug-fixes, improved test coverage and better documentation. There have been a number of deprecations and API changes in this release, which are documented below. All users are encouraged to @@ -17,100 +17,278 @@ run your code with ``python -Wd`` and check for ``DeprecationWarning`` s). Our development attention will now shift to bug-fix releases on the 1.16.x branch, and on adding new features on the main branch. -This release requires Python 3.11-3.13 and NumPy 1.26.4 or greater. +This release requires Python 3.11-3.13 and NumPy 1.25.2 or greater. ************************** Highlights of this release ************************** +- Improved experimental support for the Python array API standard, including + new support in `scipy.signal`, and additional support in `scipy.stats` and + `scipy.special`. Improved support for JAX and Dask backends has been added, + with notable support in `scipy.cluster.hierarchy`, many functions in + `scipy.special`, and many of the trimmed ``stats`` functions. - ``scipy.optimize`` now uses the new Python implementation from the `PRIMA `_ package for COBYLA. The PRIMA implementation `fixes many bugs `_ in the old Fortran 77 implementation with `a better performance on average `_. +- ``scipy.sparse.coo_array`` now supports nD arrays with reshaping, arithmetic and + reduction operations like sum/mean/min/max. No nD indexing or ``random_array`` + support yet. +- Updated guide and tools for migration from sparse matrices to sparse arrays. +- All functions in the `scipy.linalg` namespace that accept array arguments + now support N-dimensional arrays to be processed as a batch. +- Two new `scipy.signal` functions, ``firwin_2d`` and + ``closest_STFT_dual_window``, for creation of a 2-D FIR filter and + ``ShortTimeFFT`` dual window calculation, respectivly. +- A new class, ``RigidTransform``, is available in the ``transform`` submodule. + It provides functionality to convert between different representations of + rigid transforms in 3D space. ************ New features ************ -``scipy.cluster`` improvements +``scipy.io`` improvements ============================== +- ``savemat`` now provides informative warnings for invalid field names. +- `scipy.io.mmread` now provides a clearer error message when provided with + a source file path that does not exist. +- `scipy.io.wavfile.read` can now read non-seekable files. -``scipy.differentiate`` improvements -==================================== - +``scipy.integrate`` improvements +================================== +- Improved the error estimate of `scipy.integrate.tanhsinh` ``scipy.interpolate`` improvements ================================== - +- Added batch support to `scipy.interpolate.make_smoothing_spline` ``scipy.linalg`` improvements ============================= - +- All functions in the `scipy.linalg` namespace that accept array arguments + now support N-dimensional arrays to be processed as a batch. + See :ref:`linalg_batch` for details. +- ``linalg.sqrtm`` is rewritten in C and its performance is improved. It also + tries harder to return real-valued results for real-valued inputs if + possible. See the function docstring for more details. In this version the + input argument ``disp`` and the optional output argument ``errest`` are + deprecated and will be removed four versions later. Similarly, after + changing the underlying algorithm to recursion, the ``blocksize`` keyword + argument has no effect and will be removed two versions later. +- Added wrappers for ``?stevd``, ``?langb``, ``?sytri``, ``?hetri`` and + ``?gbcon`` to `scipy.linalg.lapack`. +- Improved default driver of `scipy.linalg.eigh_tridiagonal`. +- `scipy.linalg.solve` can now estimate the reciprocal condition number and + the matrix norm calculation is more efficient. ``scipy.ndimage`` improvements ============================== - +- Added `scipy.ndimage.vectorized_filter` for generic filters that take advantage + of a vectorized Python callable. +- `scipy.ndimage.rotate` has improved performance, especially on ARM platforms. ``scipy.optimize`` improvements =============================== - COBYLA was updated to use the new Python implementation from the `PRIMA `_ package. - The PRIMA implementation `fixes many bugs `_ + The PRIMA implementation + `fixes many bugs `_ in the old Fortran 77 implementation. In addition, it results in `fewer function evaluations on average `_, but it depends on the problem and for some problems it can result in more function evaluations or a less optimal result. For those cases the user can try modifying the initial and final - trust region radii given by `rhobeg` and `tol` respectively. A larger `rhobeg` - can help the algorithm take bigger steps initially, while a smaller `tol` - can help it for keep going and find a better solution. + trust region radii given by ``rhobeg`` and ``tol`` respectively. A larger + ``rhobeg`` can help the algorithm take bigger steps initially, while a + smaller ``tol`` can help it continue and find a better solution. For more information, see the `PRIMA documentation `_. +- Several of the ``minimize`` methods, and the ``least_squares`` function, + have been given a ``workers`` keyword. This allows parallelization of some + calculations via a map-like callable, such as ``multiprocessing.Pool``. These + parallelization opportunities typically occur during numerical + differentiation. This can greatly speed-up minimization when the objective + function is expensive to calculate. +- The ``lm`` method of ``least_squares`` can now accept ``3-point`` and ``cs`` + for the ``jac`` keyword. +- SLSQP Fortran77 code ported to C. When this method is used now the constraint + multipliers are exposed to the user through the ``multiplier`` keyword of + the returned ``OptimizeResult`` object. +- NNLS code has been corrected and rewritten in C to address the performance + regression introduced in 1.15.x +- ``optimize.root`` now warns for invalid inner parameters when using the + ``newton_krylov`` method +- The return value of minimization with ``method='L-BFGS-B'`` now has + a faster ``hess_inv.todense()`` implementation. Time complexity has improved + from cubic to quadratic. +- ``optimize.least_squares`` has a new ``callback`` argument that is applicable + to ``trf`` and ``dogbox`` methods. ``callback`` may be used to track + optimization results at each step or to provide custom conditions for + stopping. ``scipy.signal`` improvements ============================= +- A new function ``firwin_2d`` for the creation of a 2-D FIR Filter using the + 1-D window method was added. +- ``signal.cspline1d_eval`` and ``signal.qspline1d_eval`` now provide an + informative error on empty input rather than hitting the recursion limit. +- A new function `scipy.signal.closest_STFT_dual_window` to calculate the + ``ShortTimeFFT`` dual window of a given window closest to a desired dual + window. +- A new classmethod `scipy.signal.ShortTimeFFT.from_win_equals_dual` to + create a ``ShortTimeFFT`` instance where the window and its dual are equal + up to a scaling factor. It allows to create short-time Fourier transforms + which are unitary mappings. +- The performance of ``signal.convolve2d`` has improved. ``scipy.sparse`` improvements ============================= +- ``coo_array`` supports nD arrays using binary and reduction operations. +- Math is faster between two DIA arrays/matrices: add, sub, multiply, matmul. +- ``csgraph.dijkstra`` shortest_path is more efficient. +- ``csgraph.yen`` has performance improvements. +- Import of ``_lib.issparse`` allows checking for ``scipy.sparse`` object + without full import of ``scipy``. +- add lazy loading of ``sparse.csgraph`` and ``sparse.linalg``. ``scipy.spatial`` improvements ============================== - - -``scipy.special`` improvements -============================== +- A new class ``RigidTransform`` is available in the ``transform`` submodule. It + provides functionality to convert between different representations of rigid + transforms in 3D space, its application to vectors and transform composition. + It follows the same design approach as `scipy.spatial.transform.Rotation`. +- `scipy.spatial.transform.Rotation` now has an appropriate ``__repr__`` method. +- ``Rotation.apply`` has improved performance. ``scipy.stats`` improvements ============================ +- Added `scipy.stats.quantile`, an array API compatible function for quantile + estimation. +- Extended `scipy.stats.make_distribution` to: + - work with existing discrete distributions and + - facilitate the creation of custom distributions + in the new random variable infrastructure. -******************* -Deprecated features -******************* +- Added `scipy.stats.Binomial`. +- Added ``equal_var`` keyword to: -``scipy.linalg`` deprecations -============================= + - `scipy.stats.tukey_hsd` (enables the Games-Howell test) and + - `scipy.stats.f_oneway` (enables Welch ANOVA). +- Improved moment calculation for `scipy.stats.gennorm`. +- Added native vectorization to `scipy.stats.mode` for faster batch calculation. +- Added ``axis``, ``nan_policy``, and ``keepdims`` to `scipy.stats.power_divergence`, + `scipy.stats.chisquare`, `scipy.stats.pointbiserialr`, `scipy.stats.kendalltau`, + `scipy.stats.weightedtau`, `scipy.stats.theilslopes`, `scipy.stats.siegelslopes`, + and `scipy.stats.boxcox_llf`. +- Improved speed of `scipy.stats.special_ortho_group`. +- Improved speed of `scipy.stats.pearsonr`. + + +************************** +Array API Standard Support +************************** + +Experimental support for array libraries other than NumPy has been added to +existing sub-packages in recent versions of SciPy. Please consider testing +these features by setting an environment variable ``SCIPY_ARRAY_API=1`` and +providing PyTorch, JAX, or CuPy arrays as array arguments. Many functions +in `scipy.stats`, `scipy.special`, `scipy.optimize`, and `scipy.constants` +now provide tables documenting compatible array and device types as well as +support for lazy arrays and JIT compilation. New features with support and +old features with support added for SciPy 1.16.0 include: + +- Most features of `scipy.signal` +- `scipy.ndimage.vectorized_filter` +- `scipy.special.stdtrit` +- `scipy.special.softmax` +- `scipy.special.log_softmax` +- `scipy.stats.quantile` +- `scipy.stats.gstd` +- `scipy.stats.rankdata` + +Features with extended array API support (generally, improved support +for JAX and Dask) in SciPy 1.16.0 include: + +- many of the `scipy.cluster.hierarchy` functions +- many functions in `scipy.special` +- many of the trimmed `stats` functions -``scipy.spatial`` deprecations -============================== +******************* +Deprecated features +******************* +- The unused ``atol`` argument of `scipy.optimize.nnls` is deprecated and will + be removed in SciPy 1.18.0. +- The ``disp`` argument of `scipy.linalg.signm`, `scipy.linalg.logm`, and + `scipy.linalg.sqrtm` will be removed in SciPy 1.18.0. +- `scipy.stats.multinomial` now emits a ``FutureWarning`` if the rows of ``p`` + do not sum to ``1.0``. This condition will produce NaNs beginning in SciPy + 1.18.0. + +******************** +Expired Deprecations +******************** +- ``scipy.sparse.conjtransp`` has been removed. Use ``.T.conj()`` instead. +- The ``quadrature='trapz'`` option has been removed from + `scipy.integrate.quad_vec` and ``scipy.stats.trapz`` has been removed. Use + ``trapezoid`` in both instances instead. +- `scipy.special.comb` and `scipy.special.perm` now raise when ``exact=True`` + and arguments are non-integral. +- Support for inference of the two sets of measurements from the single + argument ``x`` has been removed from `scipy.stats.linregress`. The data + must be specified separately as ``x`` and ``y``. +- Support for NumPy masked arrays has been removed from + `scipy.stats.power_divergence` and `scipy.stats.chisquare`. + ****************************** Backwards incompatible changes ****************************** +- Several of the `scipy.linalg` functions for solving a linear system (e.g. + `scipy.linalg.solve`) documented that the RHS argument must be either 1-D or + 2-D but did not always raise an error when the RHS argument had more the + two dimensions. Now, many-dimensional right hand sides are treated according + to the rules specified in :ref:`linalg_batch`. +- `scipy.stats.bootstrap` now explicitly broadcasts elements of ``data`` to the + same shape (ignoring ``axis``) before performing the calculation. ************* Other changes ************* +- The ``lm`` method of ``least_squares`` function now has a different behavior + for the maximum number of function evaluations, ``max_nfev``. The default for + the ``lm`` method is changed to ``100 * n``, for both a callable and a + numerically estimated jacobian. This limit on function evaluations excludes + those used for any numerical estimation of the Jacobian. Previously the + default when using an estimated jacobian was ``100 * n * (n + 1)``, because + the method included evaluations used in the estimation. In addition, for the + ``lm`` method the number of function calls used in Jacobian approximation + is no longer included in ``OptimizeResult.nfev``. This brings the behavior + of ``lm``, ``trf``, and ``dogbox`` into line. +- For ``Cython>=3.1.0b1``, SciPy now uses the new + ``cython --generate-shared`` functionality, which reduces the total size of + SciPy's wheels and on-disk installations significantly. +- The output of `scipy.stats.wrapcauchy` ``rvs`` is now mapped to the unit circle + between 0 and ``2 * pi``. +- The vendored Qhull library was upgraded from version 2019.1 to 2020.2. +- A new build option ``-Duse-system-libraries`` has been added. It allows + opting in to using system libraries instead of using vendored sources. + Currently ``Boost.Math`` and ``Qhull`` are supported as system build + dependencies. +- The ``namedtuple``-like bunch objects returned by some SciPy functions + now have improved compatibility with the ``polars`` library. +- The testsuite thread safety with free-threaded CPython has improved. @@ -118,13 +296,756 @@ Other changes Authors ******* +* Name (commits) +* h-vetinari (4) +* aiudirog (1) + +* Anton Akhmerov (2) +* Thorsten Alteholz (1) + +* Gabriel Augusto (1) + +* Backfisch263 (1) + +* Nickolai Belakovski (5) +* Peter Bell (1) +* Benoît W. (1) + +* Maxwell Bileschi (1) + +* Sam Birch (1) + +* Florian Bourgey (3) + +* Charles Bousseau (2) + +* Richard Strong Bowen (2) + +* Jake Bowhay (126) +* Matthew Brett (1) +* Dietrich Brunn (52) +* Evgeni Burovski (252) +* Christine P. Chai (12) + +* Saransh Chopra (2) + +* Omer Cohen (1) + +* Lucas Colley (91) +* crusaderky (56) + +* Yahya Darman (3) + +* dartvader316 (2) + +* Benjamin Eisele (1) + +* Donnie Erb (1) +* Evandro (1) +* Sagi Ezri (57) + +* Alexander Fabisch (2) + +* Matthew H Flamm (1) +* Gautzilla (1) + +* Neil Girdhar (1) +* Ralf Gommers (149) +* Rohit Goswami (4) +* Saarthak Gupta (4) + +* Matt Haberland (320) +* Sasha Hafner (1) + +* Joren Hammudoglu (9) +* Chengyu Han (1) + +* Charles Harris (1) +* Kim Hsieh (4) + +* Lukas Huber (1) + +* Guido Imperiale (47) + +* Jigyasu (1) + +* karthik-ganti-2025 (1) + +* Robert Kern (2) +* Harin Khakhi (2) + +* Agriya Khetarpal (4) +* Tetsuo Koyama (1) +* David Kun (1) + +* Eric Larson (3) +* lciti (1) +* Antony Lee (1) +* Kieran Leschinski (1) + +* Thomas Li (2) + +* Christian Lorentzen (2) +* Loïc Estève (4) +* Panos Mavrogiorgos (1) + +* Nikolay Mayorov (2) +* Melissa Weber Mendonça (10) +* Miguel Cárdenas (2) + +* MikhailRyazanov (6) + +* Swastik Mishra (1) + +* Sturla Molden (2) +* Andreas Nazlidis (1) + +* Andrew Nelson (209) +* Parth Nobel (1) + +* Nick ODell (9) +* Giacomo Petrillo (1) +* pmav99 (1) + +* Ilhan Polat (72) +* pratham-mcw (3) + +* Tyler Reddy (73) +* redpinecube (1) + +* Érico Nogueira Rolim (1) + +* Pamphile Roy (10) +* sagi-ezri (1) + +* Atsushi Sakai (9) +* Marco Salathe (1) + +* sanvi (1) + +* Neil Schemenauer (2) + +* Daniel Schmitz (20) +* Martin Schuck (1) + +* Dan Schult (32) +* Tomer Sery (19) +* Adrian Seyboldt (1) + +* Scott Shambaugh (4) +* ShannonS00 (1) + +* sildater (3) + +* PARAM SINGH (1) + +* G Sreeja (7) + +* Albert Steppi (133) +* Kai Striega (3) +* Anushka Suyal (2) +* Julia Tatz (1) + +* Tearyt (1) + +* Elia Tomasi (1) + +* Jamie Townsend (2) + +* Edgar Andrés Margffoy Tuay (4) +* Matthias Urlichs (1) + +* Jacob Vanderplas (2) +* David Varela (2) + +* Christian Veenhuis (3) +* vfdev (1) +* vpecanins (10) + +* vrossum (1) + +* Stefan van der Walt (2) +* Warren Weckesser (5) +* Jason N. White (1) + +* windows-server-2003 (5) +* Zhiqing Xiao (1) +* Pavadol Yamsiri (1) +* YongcaiHuang (2) + +* Rory Yorke (3) +* yuzie007 (2) + +* Irwin Zaid (4) +* zaikunzhang (1) + +* Austin Zhang (1) + +* William Zijie Zhang (1) + +* Eric Zitong Zhou (5) + +* zitongzhoueric (6) + +* Case Zumbrum (2) + +* ਗਗਨਦੀਪ ਸਿੰਘ (Gagandeep Singh) (45) + + A total of 124 people contributed to this release. + People with a "+" by their names contributed a patch for the first time. + This list of names is automatically generated, and may not be fully complete. ************************ Issues closed for 1.16.0 ************************ +* `#4800 `__: ENH: ndimage.median_filter: behavior with NaNs +* `#4878 `__: ENH: ndimage.median_filter: excessive memory usage +* `#5137 `__: ENH: ndimage.generic_filter: function to return higher-dimensional... +* `#5435 `__: savemat silently drops entries starting with "_" +* `#5451 `__: ENH: linalg.solve: support broadcasting +* `#6052 `__: savemat does not save keys starting with underscore +* `#6606 `__: BUG: signal.bilinear: can't handle leading zeros +* `#6689 `__: ENH: optimize: consider using NLopt's version of ``slsqp`` +* `#6755 `__: ENH: ndimage.percentile_filter: take multiple percentiles +* `#7518 `__: DOC: optimize: meaning of accuracy in ``fmin_slsqp`` undocumented +* `#7818 `__: ENH: ndimage.uniform_filter: expands NaNs all the way to the... +* `#8140 `__: sparse LU decomposition does not solve with complex right-hand... +* `#8367 `__: ENH: stats.mvndst: make thread-safe +* `#8411 `__: nan with betainc for a=0, b=3 and x=0.5 +* `#8916 `__: ENH: ndimage.generic_filter: slow on large images +* `#9077 `__: maximum_filter is not symetrical with nans +* `#9841 `__: ENH: linalg: 0-th dimension must be fixed to 1 but got 2 (real... +* `#9873 `__: ENH: ndimage: majority voting filter +* `#10416 `__: ENH: optimize.minimize: slsqp: give better error when work array... +* `#10793 `__: BUG: integrate: ``solve_ivp`` and ``odeint`` with ``lsoda`` have... +* `#11312 `__: BUG: signal.cont2discrete not handling lti instances as documented +* `#11328 `__: Scipy unable to read piped wav file +* `#12133 `__: How to define new distributions? +* `#12544 `__: signal.spectral._triage_segments doesn't support window as tuple... +* `#12994 `__: ENH: linalg.sqrtm: efficiently process upper triangular matrices +* `#13577 `__: Split scipy.signal.spectral._spectral_helper into two to support... +* `#13666 `__: ENH: invgauss.pdf should return correct output when mu=infinity +* `#13788 `__: Documentation for scipy.signal.resample should say what to use... +* `#13789 `__: Documentation for scipy.signal.decimate doesn't say what to use... +* `#13823 `__: BUG: signal.bilinear: doesn't work for complex valued arrays +* `#13914 `__: DOC: sparse.csgraph.shortest_path: predecessors array contains... +* `#13952 `__: fmin_cobyla result violates constraint +* `#13982 `__: ENH: linalg.eigh_tridiagonal: divide and conquer option +* `#14394 `__: ENH: optmize.slsqp: return Lagrange multipliers +* `#14569 `__: BUG: signal.resample: inconsistency across dtypes +* `#14915 `__: BUG: optimize.minimize: corruption/segfault with constraints +* `#15153 `__: BUG: signal.resample: incorrect with ``datetime[ns]`` for ``t``... +* `#15527 `__: BUG: optimize: COBYLA hangs on some CPUs +* `#16009 `__: BUG: ``act`` fails for local GitHub Actions CI run +* `#16142 `__: ENH: Fix the random state in ``scipy.stats.multivariate_normal.cdf()`` +* `#16203 `__: BUG: scipy.io.savemat discards nested names with a leading digit +* `#16234 `__: BUG: Memory leak in _superluobject.c when ``ENUM_CHECK`` is not... +* `#16452 `__: doit based dev interface garbles pdb command history (in some... +* `#17546 `__: ENH: Adding 'valid' mode to ndimage.generic_filter +* `#17787 `__: BUG: Erratic results from RectBivariateSpline when smoothing... +* `#17891 `__: BUG: inconsistent checks for integrality in several distributions +* `#17968 `__: ENH: creation of a 2-D FIR Filter using 1-D window method +* `#18046 `__: BUG: dev.py does not work in a Windows CI environment on GHA... +* `#18105 `__: ENH: optimize ``LbfgsInvHessProduct.todense()``\ , 10x speed... +* `#18118 `__: ENH: The Fortran 77 implementation of COBYLA is buggy and challenging... +* `#18214 `__: DOC: inconsistent definitions of "OP" and "OPinv" in eigsh +* `#18346 `__: DOC: optimize: l_bfgs_b: sets ``maxiter`` and ``maxfun`` to the... +* `#18437 `__: ENH: ndimage.generic_filter: support complex input +* `#18740 `__: BUG: scipy.optimize.bisect gives incorrect results for very small... +* `#18866 `__: MAINT: follow-up actions for array API support in ``cluster`` +* `#18951 `__: ENH: improve ``python dev.py test`` experience caused by imp... +* `#18998 `__: BUG: dev.py has issues with site-packages and Python installed... +* `#19254 `__: ENH: spatial.transform: cover proper rigid transformations with... +* `#19362 `__: BUG: optimize: warning generated by SLSQP is useless +* `#19415 `__: BUG: linalg.sqrtm results different between version 1.11.1 and... +* `#19459 `__: BUG: optimize.least_squares giving poor result compared to optimize.leastsq... +* `#20219 `__: BUG: failing ``sqrtm`` regression test +* `#20366 `__: ENH: Yens algorithm improvements and enhancements +* `#20608 `__: BUG: ``refguide-check`` incorrectly flags references to equations... +* `#20622 `__: DOC: signal: add an example cross-spectrogram application +* `#20806 `__: Failures for new ``pytest-fail-slow`` check in Windows CI jobs +* `#20972 `__: BUG: special.chdtrc: returns 1.0 when both degrees of freedom... +* `#20999 `__: BUG: ndimage.zoom: wrong output with zoom factor of 1 +* `#21020 `__: DOC: signal: Use ``where='post'`` when plotting discrete response +* `#21095 `__: DOC: ``RegularGridInterpolator`` uses half down rounding instead... +* `#21102 `__: RFC/ENH?: ``optimize.curve_fit``\ : option to use global optimization... +* `#21293 `__: DOC: stats.qmc.discrepancy: clarify deviation from reference +* `#21317 `__: BUG: ``special.gammainc``\ : returns finite results with NaN... +* `#21323 `__: DOC: build fails with Sphinx 8 +* `#21341 `__: DOC: signal.correlate: formula doesn't match behavior when ``x``... +* `#21484 `__: DEP: optimize.nnls: deprecate atol parameter which does nothing +* `#21531 `__: MAINT: ``stats.dirichlet_multinomial``\ : relax ``n`` to ``>=0`` +* `#21547 `__: STY/DEV: fix and enable lint rule UP038 +* `#21606 `__: ENH: stats: generic power law with negative index +* `#21649 `__: RFC: Splitting off special function scalar kernels into separate... +* `#21692 `__: BUG: optimize.shgo: not working with ``jac=True`` +* `#21717 `__: DOC: ``assert_allclose`` instead of ``xp_assert_close`` is recommended... +* `#21740 `__: CI: adding a GPU-enabled CI job +* `#21764 `__: ENH: linalg.lapack: add symmetric solvers +* `#21844 `__: ENH: linalg: wrap ?gbcon/?langb and use in linalg.solve +* `#21879 `__: BUG: ``scipy.datasets`` failing with Error 403 for readthedocs... +* `#21971 `__: ENH: ``ndimage.median_filter``\ : extended ``dtype`` support? +* `#21972 `__: STY: fix and enable lint rule UP031 +* `#21986 `__: ENH: optimize.root: warn when inner parameters are ignored with... +* `#21995 `__: BUG: ``optimize.curve_fit`` with ``method='lm'`` fails to determine... +* `#21999 `__: ENH: ``io.mmread``\ : Provide better error message when loading... +* `#22000 `__: DOC: ``ndimage.median_filter``\ : document behaviour with ``nan``\... +* `#22011 `__: BUG: interpolate.Akima1DInterpolator: different values on subsequent... +* `#22044 `__: TST: ``optimize.elementwise.bracket_minimum``\ : CuPy failure +* `#22045 `__: DOC: stats: clarify the support of a distribution is unaffected... +* `#22051 `__: BUG: AttributeError: module 'numpy' has no attribute 'AxisError'... +* `#22054 `__: BUG: ndimage, array types: ``minimum_position`` and ``extrema``... +* `#22055 `__: DOC: ndimage.minimum and maximum: incorrect return type +* `#22057 `__: DOC: ``stats.order_statistic``\ : docstring missing the "Returns"... +* `#22065 `__: DOC: sparse: Several functions are missing the 'Returns' section... +* `#22072 `__: DOC: PchipInterpolator: missing integrate function +* `#22086 `__: MAINT: signal: build warning (``sprintf``\ ) on macOS +* `#22093 `__: DOC: integrate.quad: uses Gauss-Kronrod not Curtis-Clenshaw? +* `#22136 `__: DOC: linalg.matrix_balance: equation does not render +* `#22144 `__: Query: optimize.minimize: trust_constr does not avoid Nonlinear... +* `#22163 `__: DOC: update ``scipy`` module docstring for lazy loading +* `#22164 `__: MAINT: undo ignored errors in mypy +* `#22195 `__: Query: optimize.basinhopping: lowest minimum not accepted if... +* `#22224 `__: MAINT: remove end year from copyright +* `#22252 `__: MAINT: Fix a dtype check in ``scipy.signal._waveforms.py`` +* `#22258 `__: BUG: Constructing sparse matrix with big-endian float32/64 raises... +* `#22263 `__: BUG: linalg.solve doesn't raise an error when A is a singular... +* `#22265 `__: BUG: linalg: ``hecon`` returns NaN incorrectly with some lower... +* `#22271 `__: Query: empty ``Rotation`` is not allowed in scipy=1.15 +* `#22282 `__: QUERY/DEV: test failure in IDE with ``SCIPY_ARRAY_API`` +* `#22288 `__: QUERY: Pyright raises error/warning in IDE +* `#22303 `__: ENH: stats.special_ortho_group: improve and simplify +* `#22309 `__: DOC: optimize.elementwise.find_minimum: harmonize documented/implemented... +* `#22328 `__: QUERY: stats.beta.fit: ``FitError`` on reasonable data +* `#22338 `__: QUERY: Intellisense Autocomplete Not Working for ``spatial.transform.Rotation`` +* `#22361 `__: BUG: interpolation test TestSmoothingSpline.test_compare_with_GCVSPL... +* `#22363 `__: BUG: special test TestHyp2f1.test_region3[hyp2f1_test_case23]... +* `#22367 `__: QUERY/TYP: sparse: Pylance reports unreachable after ``toarray()`` +* `#22378 `__: DOC/TST: interpolate, signal: ``smoke-docs`` failures +* `#22382 `__: ENH: sparse.spmatrix: allow fast import +* `#22395 `__: BUG: special: failure of TestSystematic.test_besselj_complex... +* `#22403 `__: DOC: ``gaussian_kde``\ 's ``bw_method='silverman'`` deviates... +* `#22415 `__: Two ``TestBatch`` failures in macOS x86-64 Accelerate wheel build... +* `#22429 `__: DOC: integrate: missing bold font for a vector in tutorial +* `#22437 `__: DOC: The code of conduct link is dead +* `#22449 `__: BUG: sparse.csgraph.construct_dist_matrix: buffer dtype mismatch +* `#22450 `__: QUERY: difference between ``namedtuple``\ s and objects produced... +* `#22461 `__: DOC: freqz_sos: claims that it was introduced in 0.19; no mention... +* `#22470 `__: BUG: ``lfiltic``\ 's handling of ``a[0] != 1`` differs from ``lfilter``\... +* `#22485 `__: DOC: remove links to the reference guide in the tutorials page +* `#22488 `__: DOC: interpolate.lagrange: the Lagrange function is using the... +* `#22495 `__: BUG: special test TestHyp2f1.test_region4[hyp2f1_test_case42]... +* `#22501 `__: BUG: ``min_weight_full_bipartite_matching`` fails for ``coo_matrix``... +* `#22508 `__: DOC: Inconsistent notation in Linear algebra (scipy.linalg) page +* `#22534 `__: CI: failures ``*/tests/test_extending`` due to a regression in... +* `#22559 `__: BUG: ``ndimage``\ : Numerical regressions in Dask 2025.2.0 +* `#22565 `__: BUG: stats.multinomial.pmf: inconsistent results? +* `#22581 `__: DOC: stats.gaussian_kde: clarify the meaning of ``factor`` +* `#22591 `__: BUG: sparse.coo: ``ImportError`` for ``upcast`` +* `#22601 `__: BUG: special.logsumexp: inconsistency in phase when one element... +* `#22626 `__: BUG: scipy.stats: tmin/tmax: loss of precision for large integers +* `#22646 `__: CI/DOC: CloughTocher2DInterpolator: ``UserWarning`` in docs build +* `#22659 `__: BUG: spatial: ``RigidTransform`` does not support zero-length... +* `#22692 `__: DOC: interpolate.make_smoothing_spline: example plot uses the... +* `#22700 `__: CI: new failures: segfault in free-threaded, ``linprog`` invalid... +* `#22703 `__: DOC: integrate: ``quad_vec`` info return type is ``_Bunch`` not... +* `#22767 `__: BUG: test_cython Failing on Windows on ARM64 with clang-cl +* `#22768 `__: DOC/DEV: outdated references to Cirrus CI +* `#22769 `__: ENH: optimize: Return bound multiplier for SLSQP +* `#22775 `__: ENH: Use cython shared utility module +* `#22791 `__: BUG: optimize.nnls: unstable on i686 (32-bit) machine +* `#22800 `__: BUG: ``signal.windows.kaiser_bessel_derived`` uses ``array``... +* `#22881 `__: DOC: Update minimum NumPy and Python in toolchain roadmap +* `#22904 `__: BUG: Wrong use of ``__builtin_prefetch()`` +* `#22912 `__: BUG: optimize: ``SyntaxWarning: 'break' in a 'finally' block``... +* `#22920 `__: BUG: ``check_test_name`` fails with ``UnicodeDecodeError``\ ? +* `#22921 `__: DOC: clarify the status of Apple's Accelerate Framework support +* `#22931 `__: BUG: interpolate._dierckx: ``check_array()`` can crash if the... +* `#22942 `__: TST: ``special``\ : ``test_compiles_in_cupy`` is broken +* `#22945 `__: TST: Nested arrays failing in array-api-strict git tip +* `#22951 `__: BUG: stats.wrapcauchy: output isn't wrapped around the unit circle +* `#22956 `__: BUG: special._ufuncs._ncx2_pdf: interpreter crash with extreme... +* `#22965 `__: BUG: The attribute "nit" is not found when using the callback... +* `#22981 `__: Bug with freqz when specifying worN after #22886 ************************ Pull requests for 1.16.0 ************************ + +* `#18375 `__: ENH: signal: Add ``firwin_2d`` filter +* `#20610 `__: ENH: signal.ShortTimeFFT: determine arbitrary dual windows +* `#20639 `__: ENH: stats.rankdata: add array API standard support +* `#20717 `__: ENH: Speed up sparse.csgraph.dijkstra 2.0 +* `#20772 `__: ENH: array types, signal: delegate to CuPy and JAX for correlations... +* `#20950 `__: ENH: spatial: speed up ``Rotation.apply`` by replacing ``np.einsum``... +* `#21180 `__: ENH: sparse: efficient arithmetic operations for DIA format +* `#21233 `__: ENH: ``stats.boxcox_llf``\ : vectorize for n-D arrays +* `#21270 `__: MAINT: make ``boost_math`` a ``subproject`` +* `#21462 `__: ENH: linalg.eig: support batched input +* `#21482 `__: MAINT/DEV: use Sphinx 8 for documentation builds +* `#21557 `__: ENH: ``stats._continued_fraction``\ : elementwise, Array API... +* `#21628 `__: BUG:signal: Fix passing lti as system to cont2discrete +* `#21674 `__: DEV: use ``spin`` +* `#21684 `__: MAINT: ``stats.dirichlet_multinomial`` relax ``n`` to ``>= 0`` +* `#21713 `__: ENH: signal: add array API support / delegation to lfilter et... +* `#21783 `__: ENH: signal.windows: add array API support (take 2) +* `#21863 `__: CI: use macos-15 for a macOS run +* `#21987 `__: STY: fix lint rule UP031 +* `#22008 `__: ENH: signal.vectorstrength: add array API standard support +* `#22010 `__: REL: set version to 1.16.0.dev0 +* `#22012 `__: MAINT: bump min NumPy to 1.25.2, min Python to 3.11 +* `#22013 `__: DEV: ``gh_lists``\ : fix asterisk sanitisation +* `#22015 `__: DEV: lint: add option to lint all files +* `#22019 `__: MAINT: signal: remove tempita templating +* `#22042 `__: DOC, MAINT: Add a ``"jupyterlite_sphinx_strip"`` tag to the ``scipy.stats``... +* `#22046 `__: TST: optimize: fix CuPy failure for ``bracket_minimum`` +* `#22052 `__: DOC: sparse.linalg: add note about complex matrices to ``splu``... +* `#22056 `__: MAINT: stats.wilcoxon: fix attempt to access np.AxisError +* `#22061 `__: BUG: ndimage: convert array scalars on return +* `#22062 `__: MAINT: ``_lib``\ : co-vendor array-api-extra and array-api-compat +* `#22064 `__: MAINT: ``sparse.linalg._isolve``\ : Remove postprocess function +* `#22068 `__: ENH: optimize: migrate to use sparray +* `#22070 `__: ENH: ``_lib``\ : JAX support (non-jitted) +* `#22071 `__: MAINT: Use ``ENUM_CHECK_NAME`` for avoiding memory leaks in ``_superluobject.c`` +* `#22073 `__: DEP: sparse: remove conjtransp +* `#22074 `__: DEP: remove remaining trapz references +* `#22075 `__: DEP: stats.linregress: remove one arg use +* `#22076 `__: BUG: datasets: add headers to fetchers to avoid 403 errors +* `#22079 `__: DEP: stats: remove support for masked arrays from ``power_divergence``... +* `#22087 `__: DEP: special: raise error for non-integer types with exact=True... +* `#22088 `__: TST: optimize.elementwise.find_root: refactor tests to use ``find_root``... +* `#22089 `__: TST: optimize: suppress incorrect sparray warning from scikit-sparse +* `#22090 `__: ENH: optimize: migrate to sparray (docs) +* `#22092 `__: MAINT: signal: fixed build warning (``sprintf``\ ) on MacOS +* `#22100 `__: DEP: signal.spline: use standard submodule deprecation machinery +* `#22101 `__: DOC: update ``stats``\ , ``integrate``\ , ``optimize``\ , and... +* `#22108 `__: CI: Run 'Checkout scipy' and 'Check for skips' only on Github... +* `#22110 `__: TST: linalg: use infinity norm of matrix when norm='I' +* `#22115 `__: DOC: release notes: ensure TOC links to headings below +* `#22116 `__: DOC: update the interpolate roadmap +* `#22122 `__: MAINT: signal.oaconvolve: avoid xp <-> numpy conversions +* `#22125 `__: TST: stats: ensure tests are thread-safe +* `#22127 `__: ENH: linalg: add batch support for matrix -> scalar funcs +* `#22130 `__: TST: ndimage: array API-related cosmetic tweaks in tests +* `#22131 `__: TST: ``skip|xfail_xp_backends`` disregards ``reason=`` +* `#22132 `__: TST: array types: enforce namespace in tests +* `#22133 `__: ENH: linalg: add batch support for functions that accept a single... +* `#22140 `__: DOC: linalg.matrix_balance: move math to notes; ensure that it... +* `#22142 `__: ENH: signal: add CuPy/JAX delegation to scipy.signal +* `#22148 `__: TST: ndimage: fix test skip typo +* `#22152 `__: ENH: stats.f_oneway: add ``equal_var`` for Welch ANOVA +* `#22154 `__: ENH: linalg.clarkson_woodruff_transform: add batch support +* `#22155 `__: ENH: stats: add axis/nan_policy/keepdims/etc. support to correlation... +* `#22157 `__: ENH: linalg: add batch support for remaining cholesky functions +* `#22160 `__: DEP: interpolate: remove incidental imports from private modules +* `#22161 `__: DOC, MAINT: Add updates for interactive notebooks via ``jupyterlite-sphinx``... +* `#22165 `__: ENH: linalg: add batch support to remaining eigenvalue functions +* `#22166 `__: ENH: linalg.block_diag: add batch support +* `#22169 `__: MAINT: sparse: refactor CSC to use CSR sparsetools +* `#22170 `__: ENH: signal: convert ``symiirorder`` and related filters to work... +* `#22172 `__: MAINT: improve overflow handling in factorial functions +* `#22173 `__: DOC: interpolate: add missing method ``integrate`` for ``PchipInterpolator`` +* `#22174 `__: MAINT: optimize: switch suppress_warnings to catch_warnings +* `#22176 `__: MAINT: special: Move Faddeeva into xsf +* `#22179 `__: DOC/DEV: mention ``scipy-stubs`` in building from source guide +* `#22182 `__: TST: ndimage: cupy tweaks for inplace out= +* `#22185 `__: ENH: stats.tukey_hsd: ``equal_var=False`` option to perform Games-Howell... +* `#22186 `__: DOC: interpolate: add a note about rounding rule of the ``nearest``... +* `#22190 `__: MAINT: special: Migrate remaining exp and log functions to xsf +* `#22192 `__: ENH: linalg: add batch support to linear system solvers +* `#22196 `__: DOC: update scipy module docstring for lazy loading +* `#22197 `__: ENH: linalg.cossin: add batch support +* `#22198 `__: DOC: basinhopping, clarify when lowest_optimization_result is... +* `#22201 `__: DOC: Clarify support behavior in rv_continuous documentation +* `#22208 `__: ENH: io.wavfile: read unseekable files +* `#22211 `__: DOC: interpolate: add missed ``integrate`` doc link for ``Akima1DInterpolator`` +* `#22212 `__: ENH: linalg: wrap ?gbcon +* `#22213 `__: BUG: zpk2tf works correctly with complex k, real p, z +* `#22214 `__: TST: make torch default dtype configurable +* `#22215 `__: ENH: io: throw ``FileNotFoundError`` exception when the source... +* `#22216 `__: TST: TestBracketMinimum MPS shims +* `#22217 `__: ENH: linalg: wrap ?langb +* `#22219 `__: ENH: ``_lib``\ : deobfuscate ``jax.jit`` crash in ``_asarray`` +* `#22220 `__: MAINT: stats: replace nonstandard calls in (mostly) array API... +* `#22221 `__: MAINT: linalg.leslie: use _apply_over_batch +* `#22222 `__: ENH: ``special``\ /``stats``\ : implement xp-compatible ``stdtrit``... +* `#22226 `__: ENH: signal.upfirdn: array API standard support +* `#22227 `__: TST: linalg: add missing lower arguments in test_sy_hetrs +* `#22228 `__: ENH: linalg.lapack: wrap ?sytri and ?hetri +* `#22229 `__: MAINT: cluster: remove unnecessary namespace changes +* `#22231 `__: ENH: add ``callback`` to ``optimize.least_squares`` +* `#22234 `__: MAINT: forward port 1.15.0 relnotes +* `#22237 `__: BENCH: sparse.csgraph.dijkstra: add benchmark +* `#22240 `__: ENH: array types: add dask.array support +* `#22242 `__: MAINT: integrate.cubature: fix undefined ``asarray`` use +* `#22243 `__: DOC: sparse: docstring example of random_array with uint32 data_sampler +* `#22251 `__: ENH: linalg.solve: use langb +* `#22255 `__: EHN: cluster: JAX support (non-jitted) +* `#22256 `__: ENH: special: JAX support (non-jitted) +* `#22259 `__: TST: signal: fix symiir tests +* `#22260 `__: TST: Make ``@pytest.mark.usefixtures("skip_xp_backends")`` redundant +* `#22261 `__: TST: dev.py quietly ignores user markers +* `#22262 `__: TST: Mark with ``xp`` all tests in Array API-compatible modules +* `#22264 `__: MAINT: interpolate: make BSpline allocate out arrays in C +* `#22266 `__: MAINT: linalg.solve: raise when diagonal matrix is exactly singular +* `#22267 `__: ENH: spatial.transform: baseline implementation of ``RigidTransform`` +* `#22268 `__: TST: clean up obsolete Array API fixtures +* `#22269 `__: DOC: optimize.curve_fit: add note about more advanced curve fitting +* `#22273 `__: ENH: linalg.solve: use gbcon +* `#22274 `__: ENH: ``_contains_nan`` for lazy arrays +* `#22275 `__: CI: add a GPU CI job +* `#22278 `__: BUG: Fix ``Akima1DInterpolator`` by returning linear interpolant... +* `#22279 `__: TST: Add skips for GPU CI failures +* `#22280 `__: TST: ``_lib``\ : more idiomatic conditional skips +* `#22281 `__: TST: special: better skip message for stdtrit on JAX +* `#22283 `__: BUG: Fix banded Jacobian for lsoda: ``ode`` and ``solve_ivp`` +* `#22284 `__: BUG: sparse: better error message for unsupported dtypes +* `#22289 `__: CI: fix skip/trigger condition of GPU CI job +* `#22293 `__: ENH: Add __repr__ method to scipy.spatial.transform.Rotation +* `#22295 `__: DOC: signal.ShortTimeFFT.nearest_k_p: fix typo +* `#22298 `__: MAINT: stats: remove ``mvn`` fortran calls from ``multivariate_normal.cdf`` +* `#22300 `__: MAINT: remove end year from copyright +* `#22302 `__: MAINT: remove unused library import +* `#22304 `__: ENH: stats.special_ortho_group: speed up, allow 1x1 and 0x0 ortho... +* `#22305 `__: MAINT, DOC: forward port 1.15.1 relnotes +* `#22308 `__: TST: ``_lib``\ : run tests with ``@jax.jit`` +* `#22311 `__: TST: replace ``pytest.xfail`` with ``skip/xfail_xp_backends`` +* `#22312 `__: ENH: stats.Binomial: add binomial distribution with new infrastructure +* `#22313 `__: BUG: signal.bilinear handles complex input, and strips leading... +* `#22320 `__: TST: array types: wrap namespaces centrally +* `#22324 `__: ENH: io: add invalid field name warning for ``savemat`` +* `#22330 `__: ENH: sparse.csgraph.yen: performance improvements +* `#22340 `__: MAINT: linalg: reorganize tridiagonal eigenvalue routines +* `#22342 `__: ENH: cluster: ``linkage`` support for jax.jit and dask +* `#22343 `__: ENH: ``signal.{envelope,resample,resample_poly}``\ : array API... +* `#22344 `__: BUG: Fix bug with dpss degenerate case +* `#22348 `__: DOC: Harmonize summary line of docstrings of iterative sparse... +* `#22350 `__: ENH: Replace Fortran COBYLA with Python version from PRIMA +* `#22351 `__: DOC: sparse.linalg.eigsh: fix inconsistent definitions of OP... +* `#22352 `__: ENH: stats.quantile: add array API compatible quantile function +* `#22358 `__: MAINT: ``special.nctdtrit``\ : migrate to boost +* `#22359 `__: MAINT: remove temporary ``# type: ignore``\ 's from #22162 +* `#22364 `__: TST: bump tolerance on TestHyp2f1.test_region3[hyp2f1_test_case23] +* `#22366 `__: DOC: integrate: fix quad documentation to correctly describe... +* `#22371 `__: ENH: stats.make_distribution: allow definition of custom distributions +* `#22375 `__: DOC: sparse.linalg: fix doctest in scipy.sparse.linalg._norm.py +* `#22376 `__: DOC: sparse.linalg: sparray updates in doc_strings and Sakurai... +* `#22379 `__: DOC: interpolate.AAA: add may vary to example +* `#22380 `__: DOC: Replace link to X in header with link to scientific python... +* `#22381 `__: MAINT: special: A bit of clean up in stirling2.h +* `#22386 `__: DEP: optimize.nnls: deprecate unused atol parameter +* `#22387 `__: DOC: Add example to show usage of ``predecessors`` matrix returned... +* `#22388 `__: DOC: Fix documentation for ``predecessors`` matrix in ``shortest_path``\... +* `#22389 `__: DOC: Add "Assert function selection guideline" doc in the new... +* `#22393 `__: TST: stats: test support for array API compatible masked arrays +* `#22396 `__: DOC: signal: Use where='post' when plotting discrete response... +* `#22397 `__: DOC: spatial: Added mention of Davenport Angles to Rotation class... +* `#22398 `__: MAINT: special: clean up os/warnings modules exposed in special... +* `#22399 `__: TST: remove thread-unsafe skips for a now fixed Cython fused... +* `#22401 `__: TYP: Runtime-subscriptable ``sparray`` and ``spmatrix`` types +* `#22406 `__: ENH: linalg: Rewrite ``sqrtm`` in C with low-level nD support +* `#22407 `__: MAINT: remove ``_lib``\ ->``sparse`` dependency +* `#22411 `__: DOC: stats.gaussian_kde: clarify Silverman method +* `#22413 `__: DOC: stats: Edited the NIST Handbook reference +* `#22416 `__: TST: linalg: bump tolerances in two TestBatch tests +* `#22419 `__: MAINT: special: Remove ``libsf_error_state`` shared library in... +* `#22420 `__: TST: use singular ``reason=`` in ``skip_xp_backends`` +* `#22421 `__: BUG: ndimage: ``binary_erosion`` vs. broadcasted input +* `#22422 `__: MAINT: ``_lib``\ : adapt ``array_namespace`` to accept scalars... +* `#22425 `__: MAINT: special: Update handling of ``betainc`` and ``betaincc``... +* `#22426 `__: ENH: linalg: wrap ?stevd +* `#22427 `__: DEP: linalg: deprecate disp argument for signm, logm, sqrtm +* `#22428 `__: DOC: add note on getting the version switcher to behave to release... +* `#22430 `__: MAINT: cluster: vectorize tests in ``is_valid_linkage`` +* `#22431 `__: DOC: integrate: correct tutorial formatting +* `#22433 `__: BUG: interpolate.RectBivariateSpline: fix ``NaN`` output when... +* `#22434 `__: DOC: integrate.tanhsinh: remove incorrect reference to _differentiate +* `#22435 `__: MAINT: bump to array-api-extra git tip +* `#22439 `__: MAINT: special: Add ``log1mexp`` for ``log(1 - exp(x))`` +* `#22440 `__: DOC: Fix year of publication in ``_dual_annealing.py`` +* `#22441 `__: BUG: special: Fix incorrect handling of ``nan`` input in ``gammainc``... +* `#22442 `__: DOC: Modified Link for code of conduct documentation +* `#22443 `__: DOC: Corrected Path +* `#22445 `__: CI: avoid mpmath pre-release version that's failing in CI +* `#22448 `__: DOC: optimize.elementwise.find_minimum: fix documented termination... +* `#22452 `__: ENH: linalg.eigh_tridiagonal: add stevd as a driver and make... +* `#22453 `__: DOC: Improve docstrs of ``dlsim``\ , ``dimpulse``\ , ``dstep``\... +* `#22454 `__: BUG: signal.ShortTimeFFT: make attributes ``win`` and ``dual_win``... +* `#22455 `__: ENH: stats.gstd: add array API support +* `#22456 `__: ENH: stats: add nan_policy support to power_divergence, chisquare +* `#22457 `__: TST: sparse: add tests for subscriptable types +* `#22459 `__: DOC: ndimage: fix wrong return type doc for ``ndimage.minimum``... +* `#22460 `__: MAINT: signal.csd: port away from using ``_spectral_helper`` +* `#22462 `__: ENH: stats.pearsonr: two simple (but substantial) efficiency... +* `#22463 `__: DOC: update Halton docs +* `#22464 `__: DOC: Prevent A@x=b from becoming a URL +* `#22467 `__: MAINT/TST: address nits from Dask PR +* `#22469 `__: TST: stats: improve JAX test coverage +* `#22475 `__: BUG: optimize.shgo: delegate ``options['jac']`` to ``minimizer_kwargs['jac']`` +* `#22478 `__: ENH: optimize: add ``workers`` kwarg to BFGS, SLSQP, trust-constr +* `#22480 `__: CI: use mpmath pre-release again +* `#22481 `__: BUG: fix ``make_lsq_spline`` with a non-default axis +* `#22483 `__: MAINT: spatial: missing Cython type in build +* `#22484 `__: ENH: allow batching in ``make_smoothing_spline`` +* `#22489 `__: MAINT: simplifications related to NumPy bounds +* `#22490 `__: ENH: stats: add ``marray`` support to most remaining array API... +* `#22491 `__: DOC: stats: resampling tutorial fixups +* `#22493 `__: DOC: Add a docstring to OptimizeWarning +* `#22494 `__: ENH: _lib._make_tuple_bunch: pretend to be namedtuple even more +* `#22496 `__: MAINT: ``stats.invgauss``\ : return correct result when ``mu=inf`` +* `#22498 `__: TST: bump tolerance in TestHyp2f1.test_region4[hyp2f1_test_case42] +* `#22499 `__: DOC: remove links to the reference guide in the tutorials page +* `#22504 `__: BLD: bump min version of Clang to 15.0, and macOS min version... +* `#22505 `__: ENH: stats.quantile: add discontinuous (HF 1-3) and Harrell-Davis... +* `#22507 `__: BENCH: make Benchmark.change_dimensionality a class variable +* `#22509 `__: DOC: sparse.linalg: add explanation for ``MatrixRankWarning`` +* `#22511 `__: BUG: sparse.csgraph: Added support for casting coo array to csc/csr... +* `#22514 `__: TST: special: Add edgecase tests for gammainc and friends +* `#22516 `__: STY: enable lint rule UP038 and fix instances in violation of... +* `#22518 `__: DOC: interpolate.FloaterHormannInterpolator: fix typos +* `#22519 `__: ENH: add workers to least_squares +* `#22520 `__: MAINT: Remove an extraneous dtype check in ``scipy/signal/_waveforms.py`` +* `#22524 `__: ENH:MAINT:optimize: Rewrite SLSQP and NNLS in C +* `#22526 `__: DOC: interpolate: reorganize the API listing +* `#22527 `__: DOC: sparse: add returns sections to some ``_construct.py`` functions +* `#22528 `__: DOC: interpolate: improve visibility of univariate interpolator... +* `#22529 `__: DOC: Update a link in SciPy Core Developer Guide +* `#22530 `__: DOC: interpolate: improve one-line descriptions +* `#22531 `__: DOC: batching in 1D/ND interpolation/smoothing routines +* `#22535 `__: DOC: update roadmap sparse +* `#22536 `__: DOC: io: link to netcdf4-python +* `#22537 `__: DOC: linalg: fix inconsistent notation +* `#22541 `__: Interpolate tutorial: discuss the bases and interconversions +* `#22542 `__: MAINT, DOC: forward port 1.15.2 release notes +* `#22546 `__: DOC: Add docstring for QhullError in _qhull.pyx [docs only] +* `#22548 `__: DOC: interpolate.lagrange: add notes / references; recommend... +* `#22549 `__: ENH: use ``workers`` keyword in ``optimize._differentiable_functions.VectorFunct``... +* `#22552 `__: MAINT: sparse.csgraph: Raise error if ``predecessors.dtype !=``... +* `#22554 `__: BUG: ``lfiltic``\ 's handling of ``a[0] != 1`` differs from ``lfilter``\... +* `#22556 `__: ENH: optimize: speed up ``LbfgsInvHessProduct.todense`` on large... +* `#22557 `__: ENH: Replace ``_lazywhere`` with ``xpx.apply_where`` +* `#22560 `__: ENH: Allow endpoints of custom distributions created with ``stats.make_distribut``... +* `#22562 `__: DOC: Correct a typo: MATLAB(R) -> MATLAB® +* `#22564 `__: TST: add missing custom markers to pytest.ini +* `#22566 `__: TST: ``skip_xp_backends(eager_only=True)`` +* `#22569 `__: CI: fix dev-deps job by not testing Meson master +* `#22572 `__: TST: skip two ndimage tests that are failing for Dask +* `#22573 `__: DOC: sparse: Add docstrings to warnings in ``scipy.sparse`` +* `#22575 `__: ENH: ``ndimage.vectorized_filter``\ : ``generic_filter`` with... +* `#22579 `__: DOC: signal.correlate: improve notes section +* `#22584 `__: TST: ndimage: tidy ``skip_xp_backends`` +* `#22585 `__: MAINT: stats.multinomial: ``FutureWarning`` about normalization... +* `#22593 `__: TST: add one more missing custom marker (``fail_slow``\ ) to... +* `#22597 `__: ENH: stats.make_distribution: improve interface for overriding... +* `#22598 `__: MAINT: stats.bootstrap: broadcast like other stats functions +* `#22602 `__: DOC: stats.pearsonr: add tutorial +* `#22603 `__: MAINT: _lib: bump version array_api_compat to 1.11 +* `#22605 `__: MAINT: signal: clean up unnecessary shims +* `#22606 `__: DOC: Ignore dict subclass docstring warning +* `#22607 `__: MAINT: special.logsumexp: improve behavior with complex infinities +* `#22609 `__: ENH: stats: shared array api support information to generate... +* `#22610 `__: ENH: _lib.doccer: Simplify and optimize indentation loop +* `#22611 `__: MAINT: stats: rewrite ``gaussian_kde.integrate_box``\ , remove... +* `#22614 `__: MAINT: linalg: fix cython lint failures in build output +* `#22616 `__: ENH: stats: use ``vecdot`` and ``nonzero`` where appropriate +* `#22618 `__: BUG: Fix dual quaternion normalization procedure +* `#22619 `__: DOC: stats.gaussian_kde: clarify the meaning of ``factor`` +* `#22621 `__: MAINT: sparse: remove incidental imports from private modules +* `#22623 `__: ENH: signal.convolve2d: Performance Enhancement on WoA +* `#22624 `__: BUG: stats: ``kde.integrate_box`` was missing an ``rng`` parameter +* `#22625 `__: MAINT: Bump array-api-compat and array-api-strict +* `#22628 `__: MAINT: stats.tmin/tmax: ensure exact results with unreasonably... +* `#22630 `__: MAINT: stats: tmin/tmax tweaks +* `#22631 `__: DOC: interpolate.BarycentricInterpolator: documentation improvements +* `#22632 `__: MAINT: stats.multinomial: use dtype-dependent tolerance +* `#22633 `__: ENH: special: ``softmax`` / ``log_softmax`` Array API support +* `#22634 `__: TST: special: cosmetic nits +* `#22636 `__: MAINT: fix domain check for ``ncfdtri`` +* `#22639 `__: ENH: special: ``support_alternative_backends`` on Dask and jax.jit +* `#22641 `__: ENH: special: add Dask support to ``rel_entr`` +* `#22645 `__: DOC: stats.special_ortho_group: update algorithm description +* `#22647 `__: MAINT: sparse: rewrite ``sparse._sputils.validateaxis`` to centralize... +* `#22648 `__: MAINT: stats.quantile: fixup quantile for p < minimum plotting... +* `#22649 `__: DOC, CI: Fix legend warning for CloughTocher2DInterpolator docstring +* `#22650 `__: TST: stats: mark ``nct`` fit xslow +* `#22651 `__: MAINT: ndimage.zoom: eliminate noise when ``zoom=1`` +* `#22653 `__: DOC: add COBYQA to local optimizer comparison table +* `#22658 `__: CI: clean up free-threading job, add new job using pytest-run-parallel +* `#22661 `__: TST: fix some test failures and excessive memory use on Guix +* `#22666 `__: MAINT: interpolate: move NdBSpline evaluations to C +* `#22667 `__: DEV: cap Sphinx version in environment.yml +* `#22668 `__: DOC: document Array API support for the constants module and... +* `#22669 `__: TST: constants: tidy up tests +* `#22671 `__: MAINT: enforce modularity with ``tach`` +* `#22675 `__: ENH: stats: Improvements to support/domain endpoints in custom... +* `#22676 `__: ENH: stats.mode: vectorize implementation +* `#22677 `__: MAINT: use function handles rather than custom strings in ``xp_capabilities_tabl``... +* `#22683 `__: MAINT: remove outdated ``xp_`` functions, ``xp.asarray`` on elementwise... +* `#22686 `__: TST/DOC: ``lazy_xp_backends`` in ``xp_capabilities`` +* `#22687 `__: MAINT: Bump Array API to 2024.12 +* `#22691 `__: DOC: signal: fix ``freqz_sos`` and ``sosfreqz`` docstrings +* `#22694 `__: DOC: interpolate.make_smoothing_spline: improve example visibility +* `#22695 `__: MAINT: improve dtype handling now that ``xp.result_type`` accepts... +* `#22696 `__: MAINT: spatial: support empty case in ``RigidTransform`` +* `#22698 `__: MAINT/DOC: Update incomplete examples of ``expectile()`` +* `#22701 `__: TST: optimize: add more tests +* `#22710 `__: DOC: integrate.quad_vec: returned object is not a dictionary +* `#22711 `__: DOC: stats: Extend documentation of random_correlation matrix +* `#22712 `__: MAINT: bump array-api-extra to 0.7.0 +* `#22713 `__: DOC: linalg.solve: clarify symmetry requirement +* `#22714 `__: MAINT: ndimage.maximum_filter: recommend ``vectorized_filter``... +* `#22715 `__: ENH: ndimage.vectorized_filter: make CuPy-compatible +* `#22716 `__: DOC: optimize: Clarify use of ``xtol`` in 1D rootfinder docstrings +* `#22718 `__: TST: special: overhaul test_support_alternative_backends +* `#22719 `__: TST: add tests for ``ncfdtri`` +* `#22722 `__: DOC: ndimage.affine_transformation: add examples to docstring +* `#22723 `__: DOC: fft.dst: add example to docstring +* `#22725 `__: MAINT: ndimage.affine_transform: remove outdated and unhelpful... +* `#22729 `__: DOC: datasets.download_all: add examples to docstring +* `#22735 `__: ENH: stats: lazy trimmed stats for Dask and JAX +* `#22738 `__: DOC: PRIMA licence and reference fix +* `#22740 `__: TST: special: remove test skips due to array-api-strict#131 +* `#22741 `__: CI: fix crash of free-threading job in ``sparse``\ , bump GHA... +* `#22742 `__: CI/MAINT: make special.errstate thread-safe and run pytest-run-parallel... +* `#22745 `__: DOC: fft.rfft2: add example to docstring +* `#22749 `__: ENH: stats: add support for multiple parameterizations for custom... +* `#22750 `__: DOC: fft.hfft2: added example +* `#22751 `__: TST: linalg.test_batch: minor tolerance bumps +* `#22755 `__: MAINT: special: refine ``logsumexp`` writeback behaviour +* `#22756 `__: BUG/TST: ``special.logsumexp`` on non-default device +* `#22759 `__: TST: weightedtau rng thread safety +* `#22760 `__: BUG: optimize: ``VectorFunction.f_updated`` wasn't being set... +* `#22761 `__: DOC: optimize: l-bfgs-b: clarify what is meant by ``maxfun``\... +* `#22764 `__: MAINT: optimize: ``VectorFunction``\ : remove reference cycle +* `#22766 `__: DOC: improve docstrings of boxcox and yeojohnson +* `#22770 `__: TST: stats: add marray tests for _length_nonmasked directly +* `#22771 `__: TST: stats: don't encapsulate ``pytest.warns`` +* `#22778 `__: MAINT: switch to vendoring libprima/prima +* `#22779 `__: MAINT: optimize: ``VectorFunction``\ : fix array copy for sparse +* `#22782 `__: MAINT: fix failures in free-threading(parallel=1) job +* `#22783 `__: TST/MAINT: signal.symiirorder2: r, omega, precision are floats;... +* `#22785 `__: DOC/DEV: remove references to CirrusCI in skipping CI doc +* `#22787 `__: DOC: optimize: Add the multiplier details to SLSQP funcs +* `#22788 `__: TST: stats.quantile: add edge test case for axis=None && keepdims=True +* `#22790 `__: MAINT: optimize.least_squares: change ``x_scale`` default +* `#22796 `__: ENH/BLD: cython: share memoryview utility between extension modules +* `#22798 `__: TST: stats: mark some tests as slow +* `#22802 `__: BUG: optimize: Fix instability with NNLS on 32bit systems +* `#22803 `__: MAINT: use ``xp.asarray`` instead of ``xp.array`` +* `#22805 `__: CI: start using the ``CIBW_ENABLE`` env var +* `#22807 `__: TST: fix issue with ``cython_special`` test which was missing... +* `#22808 `__: BUG: ``special.logsumexp`` device propagation on PyTorch +* `#22809 `__: ENH: ``optimize.root``\ : add warning for invalid inner parameters... +* `#22811 `__: ENH: ndimage.rotate: performance enhancement on WoA +* `#22814 `__: BUG: signal.resample: Fix bug for parameter num=2 (including... +* `#22815 `__: MAINT: sparse: add lazy loading for csgraph and linalg +* `#22818 `__: DEV: add ``.editorconfig`` +* `#22820 `__: MAINT: signal: consolidate ``order_filter`` tests +* `#22821 `__: ENH: signal.lp2{lp,hp,bp,bs}: add array API standard support +* `#22823 `__: MAINT: integrate.tanhsinh: simplify error estimate +* `#22829 `__: DOC: stats.qmc.discrepancy: clarify definitions +* `#22832 `__: DOC: interpolate: remove outdated deprecation notices +* `#22833 `__: DOC: special.comb: remove missed deprecation notice +* `#22835 `__: MAINT: stats.boxcox_llf: refactor for simplicity +* `#22842 `__: MAINT: bump boost_math to 1.88.0 +* `#22843 `__: DOC: ``special``\ : add ``xp_capabilities`` to logsumexp +* `#22844 `__: TST: ``stats``\ : minor nits to test_stats.py +* `#22845 `__: TST: ``stats``\ : reorder tests to match ``xp_capabilities`` +* `#22846 `__: MAINT: _lib/differentiate: update EIM with ``at.set`` +* `#22848 `__: MAINT: _lib: eliminate try/excepts in EIM +* `#22850 `__: TST: optimize ``VectorFunction`` add test for J0=None branch... +* `#22852 `__: TST: fix ``boxcox_llf`` test failure on main +* `#22854 `__: MAINT: special: Add ``xsf`` as a submodule of SciPy +* `#22855 `__: MAINT: spatial.pdist: make dimensionality error more descriptive +* `#22858 `__: DOC: Fix typo in ``ndimage.generic_gradient_magnitude()`` +* `#22859 `__: DOC: rewording of "ties" into "tied pairs" for clearer meaning +* `#22862 `__: TST: integrate/spatial: make fail_slow allowances +* `#22863 `__: TST: reintroduce ``eager_warns`` and fix free-threading test... +* `#22864 `__: MAINT: linalg.svd: raise correct error message for GESDD when... +* `#22873 `__: ENH: sparse: Support nD sum/mean/min/max/argmin for sparse arrays +* `#22875 `__: CI: limit pytest-fail-slow usage to a single CI job +* `#22886 `__: ENH: signal: filter design functions array API standard support +* `#22891 `__: DOC: Document allowed NumPy / Python versions +* `#22893 `__: MAINT: vendor qhull as subproject and add ``-Duse-system-libraries`` +* `#22895 `__: MAINT: signal: correct the ``get_window`` delegator +* `#22896 `__: ENH: signal: ``tf2zpk`` et al Array API +* `#22897 `__: ENH: sparse: ND binary operations support +* `#22898 `__: DEV: add editable install support for ``spin`` +* `#22899 `__: MAINT: bump array-api submodules +* `#22900 `__: MAINT: fix ``np.copyto`` warnings on Dask +* `#22908 `__: MAINT: bump qhull to 2020.2 +* `#22909 `__: TST: Use ``jax_autojit`` +* `#22913 `__: BUG: fix syntax warning break in finally block under 3.14 +* `#22915 `__: BLD: optimize sdist contents through a dist script +* `#22916 `__: DOC: integrate.solve_bvp: add missing reference details +* `#22917 `__: DEV: fix invocation of linter on Windows +* `#22918 `__: TST: ``linalg`` add test coverage to exception handling for invalid... +* `#22926 `__: MAINT: spatial.cKDTree: remove software prefetching and software... +* `#22927 `__: MAINT: tools/check_test_name: specify encoding +* `#22930 `__: DOC: linalg: update roadmap entry for BLAS/LAPACK bindings +* `#22932 `__: BUG: interpolate: do not call PyArray macros on non-arrays +* `#22934 `__: MAINT: optimize.zeros: fix error message +* `#22939 `__: TST: spatial.transform: Add array API standard support for testing +* `#22941 `__: MAINT: stats.qmc.Sobol: fix stacklevel of warning +* `#22944 `__: MAINT: fix regressions in array-api-strict after disabling np.float64 +* `#22946 `__: ENH: ``special``\ : add ``xp_capabilities`` +* `#22947 `__: MAINT: avoid nested ``asarray`` calls +* `#22949 `__: MAINT: mass rename ``make_skip_xp_backends`` to ``make_xp_test_case`` +* `#22950 `__: MAINT: refresh gpu-ci pixi.lock +* `#22952 `__: MAINT, DOC: forward port 1.15.3 release notes +* `#22955 `__: MAINT: wheel downloader +* `#22959 `__: ENH: ``cluster``\ : more lazy functions +* `#22960 `__: DOC/TST: ``cluster.hierarchy``\ : use ``xp_capabilities`` +* `#22961 `__: TST: ``cluster``\ : reduce test reliance from linkage +* `#22963 `__: MAINT: wrap wrapcauchy samples around the circle +* `#22967 `__: CI: address some potential vulnerabilities +* `#22968 `__: DOC: outline that not all attributes of OptimizeResult may be... +* `#22969 `__: MAINT: stats.make_distribution: fix most remaining discrete distribution... +* `#22970 `__: MAINT: stats.DiscreteDistribution: fix inversion methods +* `#22971 `__: MAINT: fix skellam distribution tests +* `#22973 `__: BUG: interpolate.make_splrep: raise error when ``residuals.sum()``... +* `#22976 `__: ENH: stats: Implement _munp for gennorm +* `#22982 `__: BUG: signal: fix incorrect vendoring of ``npp_polyval`` +* `#22984 `__: MAINT: special: remove test_compiles_in_cupy +* `#22987 `__: DOC: sparse: sparray migration guide updates +* `#22992 `__: ENH: ``signal.cspline1d_eval,qspline1d_eval`` throw exception... +* `#22994 `__: DOC: signal.csd: Small fixes to docstr +* `#22997 `__: CI: temporarily disable free-threaded job with parallel-threads +* `#22998 `__: BUG: Fix duplicate ``--pyargs`` due to double addition in SciPy... +* `#22999 `__: MAINT: bump up array-api-compat and array-api-extra +* `#23000 `__: ENH/DOC/TST: ``cluster.vq``\ : use ``xp_capabilities`` +* `#23001 `__: DOC: ``special``\ : update top-level docs to reflect ``xp_capabilities`` +* `#23005 `__: BUG: sparse: fix mean/sum change in return of np.matrix for sparse... diff --git a/doc/source/release/1.17.0-notes.rst b/doc/source/release/1.17.0-notes.rst new file mode 100644 index 000000000000..ca6cd6edefca --- /dev/null +++ b/doc/source/release/1.17.0-notes.rst @@ -0,0 +1,112 @@ +========================== +SciPy 1.17.0 Release Notes +========================== + +.. note:: SciPy 1.17.0 is not released yet! + +.. contents:: + +SciPy 1.17.0 is the culmination of X months of hard work. It contains +many new features, numerous bug-fixes, improved test coverage and better +documentation. There have been a number of deprecations and API changes +in this release, which are documented below. All users are encouraged to +upgrade to this release, as there are a large number of bug-fixes and +optimizations. Before upgrading, we recommend that users check that +their own code does not use deprecated SciPy functionality (to do so, +run your code with ``python -Wd`` and check for ``DeprecationWarning`` s). +Our development attention will now shift to bug-fix releases on the +1.17.x branch, and on adding new features on the main branch. + +This release requires Python 3.11-3.14 and NumPy 1.25.2 or greater. + + +************************** +Highlights of this release +************************** + + +************ +New features +************ + +`scipy.cluster` improvements +============================ + + +`scipy.interpolate` improvements +================================ + + +`scipy.linalg` improvements +=========================== + + +`scipy.ndimage` improvements +============================ + + +`scipy.optimize` improvements +============================= + + +`scipy.signal` improvements +=========================== + + +`scipy.sparse` improvements +=========================== + + + +`scipy.spatial` improvements +============================ + + +`scipy.special` improvements +============================ + + +`scipy.stats` improvements +========================== + + + +******************* +Deprecated features +******************* + +`scipy.linalg` deprecations +=========================== + + +`scipy.spatial` deprecations +============================ + + + +****************************** +Backwards incompatible changes +****************************** + +************* +Other changes +************* + + + +******* +Authors +******* + + + +************************ +Issues closed for 1.17.0 +************************ + + +************************ +Pull requests for 1.17.0 +************************ + + diff --git a/environment.yml b/environment.yml index 5a6fe3f43d6c..59d632d59608 100644 --- a/environment.yml +++ b/environment.yml @@ -30,7 +30,7 @@ dependencies: - asv >=0.6 - conda-build - hypothesis - - array-api-strict<2.1.1 + - array-api-strict>=2.3.1 # For type annotations - mypy - typing_extensions @@ -55,6 +55,7 @@ dependencies: - gmpy2 - threadpoolctl # For CLI + - spin - rich-click - click - doit>=0.36.0 diff --git a/meson.build b/meson.build index 371d983a59d1..0ddafd08003d 100644 --- a/meson.build +++ b/meson.build @@ -14,6 +14,8 @@ project( ], ) +meson.add_dist_script('tools/trim_sdist_content.py') + py3 = import('python').find_installation(pure: false) py3_dep = py3.dependency() @@ -124,8 +126,8 @@ elif ff.get_id() in ['intel-cl', 'intel-llvm-cl'] '/assume:minus0' ) endif -add_project_arguments(_intel_cflags, language: ['c', 'cpp']) -add_project_arguments(_intel_fflags, language: 'fortran') +add_global_arguments(_intel_cflags, language: ['c', 'cpp']) +add_global_arguments(_intel_fflags, language: 'fortran') # Hide symbols when building on Linux with GCC. For Python extension modules, # we only need `PyInit_*` to be public, anything else may cause problems. So we @@ -153,4 +155,48 @@ if use_pythran xsimd_dep = dependency('xsimd', required: false) endif +fs = import('fs') +if not fs.exists('subprojects/xsf/README.md') + error('Missing the `xsf` submodule! Run `git submodule update --init` to fix this.') +endif + +xsf = subproject('xsf') +xsf_dep = xsf.get_variable('xsf_dep') + +use_system_libraries = get_option('use-system-libraries') +all_system_libraries = false +auto_system_libraries = false + +if use_system_libraries.contains('none') + use_system_libraries = ['none'] +elif use_system_libraries.contains('all') + all_system_libraries = true +elif use_system_libraries.contains('auto') + auto_system_libraries = true +endif + +if all_system_libraries or use_system_libraries.contains('boost.math') + boost_math_dep = dependency('boost', version : '1.88.0') +elif auto_system_libraries + boost_math_dep = dependency( + 'boost', version : '1.88.0', + fallback : ['boost_math', 'boost_math_dep'] + ) +else + boost_math = subproject('boost_math', version : '1.88.0') + boost_math_dep = boost_math.get_variable('boost_math_dep') +endif + +if all_system_libraries or use_system_libraries.contains('qhull') + qhull_r_dep = dependency('qhull_r', version : '8.0.2') +elif auto_system_libraries + qhull_r_dep = dependency( + 'qhull_r', version : '8.0.2', + fallback : ['qhull_r', 'qhull_r_dep'] + ) +else + qhull_r = subproject('qhull_r', version : '8.0.2') + qhull_r_dep = qhull_r.get_variable('qhull_r_dep') +endif + subdir('scipy') diff --git a/meson.options b/meson.options index 3257cb8a8ff5..65aac3557612 100644 --- a/meson.options +++ b/meson.options @@ -10,3 +10,7 @@ option('use-pythran', type: 'boolean', value: true, description: 'If set to false, disables using Pythran (it falls back ' + 'to either pure Python code or Cython code, depending on ' + 'the implementation).') +option('use-system-libraries', type: 'array', + choices : ['none', 'all', 'auto', 'boost.math', 'qhull'], value : ['none'], + description: 'Choose which system libraries for subprojects ' + + 'if they are available.') diff --git a/pyproject.toml b/pyproject.toml index 1181de3fe34f..b94c25ca9202 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ requires = [ [project] name = "scipy" -version = "1.16.0.dev0" +version = "1.17.0.dev0" # TODO: add `license-files` once PEP 639 is accepted (see meson-python#88) # at that point, no longer include them in `py3.install_sources()` license = { file = "LICENSE.txt" } @@ -124,6 +124,9 @@ tracker = "https://github.com/scipy/scipy/issues" [tool.doit] dodoFile = "dev.py" +[tool.spin] +package = 'scipy' + [tool.meson-python.args] install = ['--skip-subprojects'] @@ -166,3 +169,31 @@ repair-wheel-command = "bash ./tools/wheels/repair_windows.sh {wheel} {dest_dir} PKG_CONFIG_PATH = "{project}" # do this instead (which will override this setting) # set CIBW_ENVIRONMENT_WINDOWS=PKG_CONFIG_PATH=PWD.replace('\\', '/') + +[tool.spin.commands] +"Build" = [ + ".spin/cmds.py:build", + ".spin/cmds.py:test", + ".spin/cmds.py:mypy", + ".spin/cmds.py:lint", + "spin.cmds.pip.install" +] +"Environments" = [ + "spin.cmds.meson.run", + ".spin/cmds.py:python", + ".spin/cmds.py:ipython", + ".spin/cmds.py:shell", + "spin.cmds.meson.gdb", + "spin.cmds.meson.lldb" +] +"Documentation" = [ + ".spin/cmds.py:docs", + ".spin/cmds.py:smoke_docs", + ".spin/cmds.py:refguide_check", + ".spin/cmds.py:smoke_tutorials" +] +"Release" = [ + ".spin/cmds.py:notes", + ".spin/cmds.py:authors" +] +"Metrics" = [".spin/cmds.py:bench"] diff --git a/pytest.ini b/pytest.ini index 5228e5ea6e22..01641cee4231 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,7 +1,14 @@ [pytest] addopts = -l -norecursedirs = doc tools scipy/_lib/array_api_compat scipy/_lib/cobyqa scipy/_lib/highs junit_family=xunit2 +norecursedirs = + doc + tools + scipy/_lib/array_api_compat + scipy/_lib/array_api_extra + scipy/_lib/cobyqa + scipy/_lib/highs + scipy/_lib/pyprima filterwarnings = error diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index a724ff74144a..f69a47f9d6c3 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -6,12 +6,13 @@ The SciPy use case of the Array API is described on the following page: https://data-apis.org/array-api/latest/use_cases.html#use-case-scipy """ -import os +import operator +import contextlib import dataclasses import functools import textwrap -from collections.abc import Generator, Iterable, Iterator +from collections.abc import Generator from contextlib import contextmanager from contextvars import ContextVar from types import ModuleType @@ -20,7 +21,6 @@ import numpy as np import numpy.typing as npt -from scipy._lib import array_api_compat from scipy._lib.array_api_compat import ( is_array_api_obj, is_lazy_array, @@ -34,13 +34,17 @@ is_dask_namespace as is_dask, is_array_api_strict_namespace as is_array_api_strict ) -from scipy._lib._sparse import issparse +from scipy._lib.array_api_extra.testing import lazy_xp_function +from scipy._lib._array_api_override import ( + array_namespace, SCIPY_ARRAY_API, SCIPY_DEVICE +) from scipy._lib._docscrape import FunctionDoc __all__ = [ '_asarray', 'array_namespace', 'assert_almost_equal', 'assert_array_almost_equal', - 'default_xp', 'is_lazy_array', 'is_marray', - 'is_array_api_strict', 'is_complex', 'is_cupy', 'is_jax', 'is_numpy', 'is_torch', + 'default_xp', 'eager_warns', 'is_lazy_array', 'is_marray', + 'is_array_api_strict', 'is_complex', 'is_cupy', 'is_jax', 'is_numpy', 'is_torch', + 'np_compat', 'SCIPY_ARRAY_API', 'SCIPY_DEVICE', 'scipy_namespace_for', 'xp_assert_close', 'xp_assert_equal', 'xp_assert_less', 'xp_copy', 'xp_device', 'xp_ravel', 'xp_size', @@ -49,123 +53,16 @@ ] -# To enable array API and strict array-like input validation -SCIPY_ARRAY_API: str | bool = os.environ.get("SCIPY_ARRAY_API", False) -# To control the default device - for use in the test suite only -SCIPY_DEVICE = os.environ.get("SCIPY_DEVICE", "cpu") - -_GLOBAL_CONFIG = { - "SCIPY_ARRAY_API": SCIPY_ARRAY_API, - "SCIPY_DEVICE": SCIPY_DEVICE, -} - - Array: TypeAlias = Any # To be changed to a Protocol later (see array-api#589) ArrayLike: TypeAlias = Array | npt.ArrayLike -def _compliance_scipy(arrays: Iterable[ArrayLike]) -> Iterator[Array]: - """Raise exceptions on known-bad subclasses. Discard 0-dimensional ArrayLikes - and convert 1+-dimensional ArrayLikes to numpy. - - The following subclasses are not supported and raise and error: - - `numpy.ma.MaskedArray` - - `numpy.matrix` - - NumPy arrays which do not have a boolean or numerical dtype - - Any array-like which is neither array API compatible nor coercible by NumPy - - Any array-like which is coerced by NumPy to an unsupported dtype - """ - for array in arrays: - if array is None: - continue - - # this comes from `_util._asarray_validated` - if issparse(array): - msg = ('Sparse arrays/matrices are not supported by this function. ' - 'Perhaps one of the `scipy.sparse.linalg` functions ' - 'would work instead.') - raise ValueError(msg) - - if isinstance(array, np.ma.MaskedArray): - raise TypeError("Inputs of type `numpy.ma.MaskedArray` are not supported.") - - if isinstance(array, np.matrix): - raise TypeError("Inputs of type `numpy.matrix` are not supported.") - - if isinstance(array, np.ndarray | np.generic): - dtype = array.dtype - if not (np.issubdtype(dtype, np.number) or np.issubdtype(dtype, np.bool_)): - raise TypeError(f"An argument has dtype `{dtype!r}`; " - f"only boolean and numerical dtypes are supported.") - - if is_array_api_obj(array): - yield array - else: - try: - array = np.asanyarray(array) - except TypeError: - raise TypeError("An argument is neither array API compatible nor " - "coercible by NumPy.") - dtype = array.dtype - if not (np.issubdtype(dtype, np.number) or np.issubdtype(dtype, np.bool_)): - message = ( - f"An argument was coerced to an unsupported dtype `{dtype!r}`; " - f"only boolean and numerical dtypes are supported." - ) - raise TypeError(message) - # Ignore 0-dimensional arrays, coherently with array-api-compat. - # Raise if there are 1+-dimensional array-likes mixed with non-numpy - # Array API objects. - if array.ndim: - yield array - - def _check_finite(array: Array, xp: ModuleType) -> None: """Check for NaNs or Infs.""" if not xp.all(xp.isfinite(array)): msg = "array must not contain infs or NaNs" raise ValueError(msg) - -def array_namespace(*arrays: Array) -> ModuleType: - """Get the array API compatible namespace for the arrays xs. - - Parameters - ---------- - *arrays : sequence of array_like - Arrays used to infer the common namespace. - - Returns - ------- - namespace : module - Common namespace. - - Notes - ----- - Thin wrapper around `array_api_compat.array_namespace`. - - 1. Check for the global switch: SCIPY_ARRAY_API. This can also be accessed - dynamically through ``_GLOBAL_CONFIG['SCIPY_ARRAY_API']``. - 2. `_compliance_scipy` raise exceptions on known-bad subclasses. See - its definition for more details. - - When the global switch is False, it defaults to the `numpy` namespace. - In that case, there is no compliance check. This is a convenience to - ease the adoption. Otherwise, arrays must comply with the new rules. - """ - if not _GLOBAL_CONFIG["SCIPY_ARRAY_API"]: - # here we could wrap the namespace if needed - return np_compat - - api_arrays = list(_compliance_scipy(arrays)) - # In case of a mix of array API compliant arrays and scalars, return - # the array API namespace. If there are only ArrayLikes (e.g. lists), - # return NumPy (wrapped by array-api-compat). - if api_arrays: - return array_api_compat.array_namespace(*api_arrays) - return np_compat - - def _asarray( array: ArrayLike, dtype: Any = None, @@ -246,7 +143,7 @@ def xp_copy(x: Array, *, xp: ModuleType | None = None) -> Array: @contextmanager def default_xp(xp: ModuleType) -> Generator[None, None, None]: """In all ``xp_assert_*`` and ``assert_*`` function calls executed within this - context manager, test by default that the array namespace is + context manager, test by default that the array namespace is the provided across all arrays, unless one explicitly passes the ``xp=`` parameter or ``check_namespace=False``. @@ -260,6 +157,17 @@ def default_xp(xp: ModuleType) -> Generator[None, None, None]: _default_xp_ctxvar.reset(token) +def eager_warns(x, warning_type, match=None): + """pytest.warns context manager, but only if x is not a lazy array.""" + import pytest + # This attribute is interpreted by pytest-run-parallel, ensuring that tests that use + # `eager_warns` aren't run in parallel (since pytest.warns isn't thread-safe). + __thread_safe__ = False # noqa: F841 + if is_lazy_array(x): + return contextlib.nullcontext() + return pytest.warns(warning_type, match=match) + + def _strict_check(actual, desired, xp, *, check_namespace=True, check_dtype=True, check_shape=True, check_0d=True): @@ -270,7 +178,7 @@ def _strict_check(actual, desired, xp, *, xp = _default_xp_ctxvar.get() except LookupError: xp = array_namespace(desired) - + if check_namespace: _assert_matching_namespace(actual, desired, xp) @@ -478,6 +386,15 @@ def xp_ravel(x: Array, /, *, xp: ModuleType | None = None) -> Array: return xp.reshape(x, (-1,)) +def xp_swapaxes(a, axis1, axis2, xp=None): + # Equivalent of np.swapaxes written in terms of array API + xp = array_namespace(a) if xp is None else xp + axes = list(range(a.ndim)) + axes[axis1], axes[axis2] = axes[axis2], axes[axis1] + a = xp.permute_dims(a, axes) + return a + + # utility to find common dtype with option to force floating def xp_result_type(*args, force_floating=False, xp): """ @@ -486,7 +403,7 @@ def xp_result_type(*args, force_floating=False, xp): standard `result_type` in a few ways: - There is a `force_floating` argument that ensures that the result type - is floating point, even when all args are integer. + is floating point, even when all args are integer. - When a TypeError is raised (e.g. due to an unsupported promotion) and `force_floating=True`, we define a custom rule: use the result type of the default float and any other floats passed. See @@ -542,7 +459,7 @@ def xp_promote(*args, broadcast=False, force_floating=False, xp): This function accepts array-like iterables, which are immediately converted to the namespace's arrays before result type calculation. Consequently, the result dtype may be different when an argument is `1.` vs `[1.]`. - + See Also -------- xp_result_type @@ -616,149 +533,315 @@ def xp_default_dtype(xp): return xp.float64 +### MArray Helpers ### +def xp_result_device(*args): + """Return the device of an array in `args`, for the purpose of + input-output device propagation. + If there are multiple devices, return an arbitrary one. + If there are no arrays, return None (this typically happens only on NumPy). + """ + for arg in args: + # Do not do a duck-type test for the .device attribute, as many backends today + # don't have it yet. See workarouunds in array_api_compat.device(). + if is_array_api_obj(arg): + return xp_device(arg) + return None + + def is_marray(xp): """Returns True if `xp` is an MArray namespace; False otherwise.""" return "marray" in xp.__name__ - -def _make_capabilities(skip_backends=None, cpu_only=False, np_only=False, - exceptions=None, reason=None): - skip_backends = [] if skip_backends is None else skip_backends - exceptions = [] if exceptions is None else exceptions - - capabilities = dataclasses.make_dataclass('capabilities', ['cpu', 'gpu']) +def _length_nonmasked(x, axis, keepdims=False, xp=None): + xp = array_namespace(x) if xp is None else xp + if is_marray(xp): + if np.iterable(axis): + message = '`axis` must be an integer or None for use with `MArray`.' + raise NotImplementedError(message) + return xp.astype(xp.count(x, axis=axis, keepdims=keepdims), x.dtype) + return (xp_size(x) if axis is None else + # compact way to deal with axis tuples or ints + int(np.prod(np.asarray(x.shape)[np.asarray(axis)]))) + + +def _share_masks(*args, xp): + if is_marray(xp): + mask = functools.reduce(operator.or_, (arg.mask for arg in args)) + args = [xp.asarray(arg.data, mask=mask) for arg in args] + return args[0] if len(args) == 1 else args + +### End MArray Helpers ### + + +@dataclasses.dataclass(repr=False) +class _XPSphinxCapability: + cpu: bool | None # None if not applicable + gpu: bool | None + warnings: list[str] = dataclasses.field(default_factory=list) + + def _render(self, value): + if value is None: + return "n/a" + if not value: + return "⛔" + if self.warnings: + res = "⚠️ " + '; '.join(self.warnings) + assert len(res) <= 20, "Warnings too long" + return res + return "✅" + + def __str__(self): + cpu = self._render(self.cpu) + gpu = self._render(self.gpu) + return f"{cpu:20} {gpu:20}" + + +def _make_sphinx_capabilities( + # lists of tuples [(module name, reason), ...] + skip_backends=(), xfail_backends=(), + # @pytest.mark.skip/xfail_xp_backends kwargs + cpu_only=False, np_only=False, exceptions=(), + # xpx.lazy_xp_backends kwargs + allow_dask_compute=False, jax_jit=True, + # list of tuples [(module name, reason), ...] + warnings = (), + # unused in documentation + reason=None, +): + exceptions = set(exceptions) # Default capabilities - numpy = capabilities(cpu=True, gpu=False) - strict = capabilities(cpu=True, gpu=False) - cupy = capabilities(cpu=False, gpu=True) - torch = capabilities(cpu=True, gpu=True) - jax = capabilities(cpu=True, gpu=True) - dask = capabilities(cpu=True, gpu=True) - - capabilities = dict(numpy=numpy, array_api_strict=strict, cupy=cupy, - torch=torch, jax=jax, dask=dask) - - for backend, _ in skip_backends: # ignoring the reason - setattr(capabilities[backend], 'cpu', False) - setattr(capabilities[backend], 'gpu', False) - - other_backends = {'cupy', 'torch', 'jax', 'dask'} - - if cpu_only: - for backend in other_backends - set(exceptions): - setattr(capabilities[backend], 'gpu', False) - - if np_only: - for backend in other_backends: - setattr(capabilities[backend], 'cpu', False) - setattr(capabilities[backend], 'gpu', False) + capabilities = { + "numpy": _XPSphinxCapability(cpu=True, gpu=None), + "array_api_strict": _XPSphinxCapability(cpu=True, gpu=None), + "cupy": _XPSphinxCapability(cpu=None, gpu=True), + "torch": _XPSphinxCapability(cpu=True, gpu=True), + "jax.numpy": _XPSphinxCapability(cpu=True, gpu=True, + warnings=[] if jax_jit else ["no JIT"]), + # Note: Dask+CuPy is currently untested and unsupported + "dask.array": _XPSphinxCapability(cpu=True, gpu=None, + warnings=["computes graph"] if allow_dask_compute else []), + } + + # documentation doesn't display the reason + for module, _ in list(skip_backends) + list(xfail_backends): + backend = capabilities[module] + if backend.cpu is not None: + backend.cpu = False + if backend.gpu is not None: + backend.gpu = False + + for module, backend in capabilities.items(): + if np_only and module not in exceptions | {"numpy"}: + if backend.cpu is not None: + backend.cpu = False + if backend.gpu is not None: + backend.gpu = False + elif cpu_only and module not in exceptions and backend.gpu is not None: + backend.gpu = False + + for module, warning in warnings: + backend = capabilities[module] + backend.warnings.append(warning) return capabilities -def _make_capabilities_table(capabilities): - numpy = capabilities['numpy'] - cupy = capabilities['cupy'] - torch = capabilities['torch'] - jax = capabilities['jax'] - dask = capabilities['dask'] - - for backend in [numpy, cupy, torch, jax, dask]: - for attr in ['cpu', 'gpu']: - val = "✓" if getattr(backend, attr) else "✗" - val += " " * (len('{numpy.}') + len(attr) - 1) - setattr(backend, attr, val) - - table = f""" - +---------+-------------+-------------+ - | Library | CPU | GPU | - +=========+=============+=============+ - | NumPy | {numpy.cpu} | n/a | - +---------+-------------+-------------+ - | CuPy | n/a | {cupy.gpu } | - +---------+-------------+-------------+ - | PyTorch | {torch.cpu} | {torch.gpu} | - +---------+-------------+-------------+ - | JAX | {jax.cpu } | {jax.gpu } | - +---------+-------------+-------------+ - | Dask | {dask.cpu } | {dask.gpu } | - +---------+-------------+-------------+ - """ - return table - - def _make_capabilities_note(fun_name, capabilities): - table = _make_capabilities_table(capabilities) + # Note: deliberately not documenting array-api-strict note = f""" `{fun_name}` has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable ``SCIPY_ARRAY_API=1`` and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported. - {table} + + ==================== ==================== ==================== + Library CPU GPU + ==================== ==================== ==================== + NumPy {capabilities['numpy'] } + CuPy {capabilities['cupy'] } + PyTorch {capabilities['torch'] } + JAX {capabilities['jax.numpy'] } + Dask {capabilities['dask.array'] } + ==================== ==================== ==================== + See :ref:`dev-arrayapi` for more information. """ return textwrap.dedent(note) -def xp_capabilities(capabilities_table=None, **kwargs): +def xp_capabilities( + *, + # Alternative capabilities table. + # Used only for testing this decorator. + capabilities_table=None, + # Generate pytest.mark.skip/xfail_xp_backends. + # See documentation in conftest.py. + # lists of tuples [(module name, reason), ...] + skip_backends=(), xfail_backends=(), + cpu_only=False, np_only=False, reason=None, exceptions=(), + # lists of tuples [(module name, reason), ...] + warnings=(), + # xpx.testing.lazy_xp_function kwargs. + # Refer to array-api-extra documentation. + allow_dask_compute=False, jax_jit=True, +): + """Decorator for a function that states its support among various + Array API compatible backends. + + This decorator has two effects: + 1. It allows tagging tests with ``@make_xp_test_case`` or + ``make_xp_pytest_param`` (see below) to automatically generate + SKIP/XFAIL markers and perform additional backend-specific + testing, such as extra validation for Dask and JAX; + 2. It automatically adds a note to the function's docstring, containing + a table matching what has been tested. + + See Also + -------- + make_xp_test_case + make_xp_pytest_param + array_api_extra.testing.lazy_xp_function + """ capabilities_table = (xp_capabilities_table if capabilities_table is None else capabilities_table) - def decorator(f): - @functools.wraps(f) - def wrapper(*args, **kwargs): - return f(*args, **kwargs) - - capabilities_table[wrapper] = kwargs - capabilities = _make_capabilities(**capabilities_table[wrapper]) + capabilities = dict( + skip_backends=skip_backends, + xfail_backends=xfail_backends, + cpu_only=cpu_only, + np_only=np_only, + reason=reason, + exceptions=exceptions, + allow_dask_compute=allow_dask_compute, + jax_jit=jax_jit, + warnings=warnings, + ) + sphinx_capabilities = _make_sphinx_capabilities(**capabilities) - note = _make_capabilities_note(f.__name__, capabilities) - doc = FunctionDoc(wrapper) + def decorator(f): + # Don't use a wrapper, as in some cases @xp_capabilities is + # applied to a ufunc + capabilities_table[f] = capabilities + note = _make_capabilities_note(f.__name__, sphinx_capabilities) + doc = FunctionDoc(f) doc['Notes'].append(note) - wrapper.__doc__ = str(doc).split("\n", 1)[1] # remove signature + doc = str(doc).split("\n", 1)[1] # remove signature + try: + f.__doc__ = doc + except AttributeError: + # Can't update __doc__ on ufuncs if SciPy + # was compiled against NumPy < 2.2. + pass - return wrapper + return f return decorator -def make_skip_xp_backends(*funs, capabilities_table=None): +def _make_xp_pytest_marks(*funcs, capabilities_table=None): capabilities_table = (xp_capabilities_table if capabilities_table is None else capabilities_table) + import pytest + + marks = [] + for func in funcs: + capabilities = capabilities_table[func] + exceptions = capabilities['exceptions'] + reason = capabilities['reason'] + + if capabilities['cpu_only']: + marks.append(pytest.mark.skip_xp_backends( + cpu_only=True, exceptions=exceptions, reason=reason)) + if capabilities['np_only']: + marks.append(pytest.mark.skip_xp_backends( + np_only=True, exceptions=exceptions, reason=reason)) + + for mod_name, reason in capabilities['skip_backends']: + marks.append(pytest.mark.skip_xp_backends(mod_name, reason=reason)) + for mod_name, reason in capabilities['xfail_backends']: + marks.append(pytest.mark.xfail_xp_backends(mod_name, reason=reason)) + + lazy_kwargs = {k: capabilities[k] + for k in ('allow_dask_compute', 'jax_jit')} + lazy_xp_function(func, **lazy_kwargs) + + return marks + + +def make_xp_test_case(*funcs, capabilities_table=None): + capabilities_table = (xp_capabilities_table if capabilities_table is None + else capabilities_table) + """Generate pytest decorator for a test function that tests functionality + of one or more Array API compatible functions. + + Read the parameters of the ``@xp_capabilities`` decorator applied to the + listed functions and: + - Generate the ``@pytest.mark.skip_xp_backends`` and + ``@pytest.mark.xfail_xp_backends`` decorators + for the decorated test function + - Tag the function with `xpx.testing.lazy_xp_function` + + See Also + -------- + xp_capabilities + make_xp_pytest_param + array_api_extra.testing.lazy_xp_function + """ + marks = _make_xp_pytest_marks(*funcs, capabilities_table=capabilities_table) + return lambda func: functools.reduce(lambda f, g: g(f), marks, func) + + +def make_xp_pytest_param(func, *args, capabilities_table=None): + """Variant of ``make_xp_test_case`` that returns a pytest.param for a function, + with all necessary skip_xp_backends and xfail_xp_backends marks applied:: + + @pytest.mark.parametrize( + "func", [make_xp_pytest_param(f1), make_xp_pytest_param(f2)] + ) + def test(func, xp): + ... + + The above is equivalent to:: + + @pytest.mark.parametrize( + "func", [ + pytest.param(f1, marks=[ + pytest.mark.skip_xp_backends(...), + pytest.mark.xfail_xp_backends(...), ...]), + pytest.param(f2, marks=[ + pytest.mark.skip_xp_backends(...), + pytest.mark.xfail_xp_backends(...), ...]), + ) + def test(func, xp): + ... + + Parameters + ---------- + func : Callable + Function to be tested. It must be decorated with ``@xp_capabilities``. + *args : Any, optional + Extra pytest parameters for the use case, e.g.:: + + @pytest.mark.parametrize("func,verb", [ + make_xp_pytest_param(f1, "hello"), + make_xp_pytest_param(f2, "world")]) + def test(func, verb, xp): + # iterates on (func=f1, verb="hello") + # and (func=f2, verb="world") + + See Also + -------- + xp_capabilities + make_xp_test_case + array_api_extra.testing.lazy_xp_function + """ import pytest - skip_backends = [] - cpu_only = False - cpu_only_reason = set() - np_only = False - exceptions = [] - - for fun in funs: - skip_backends += capabilities_table[fun].get('skip_backends', []) - cpu_only |= capabilities_table[fun].get('cpu_only', False) - # Empty reason causes the decorator to have no effect - cpu_only_reason.add(capabilities_table[fun].get('reason', "No reason given.")) - np_only |= capabilities_table[fun].get('np_only', False) - exceptions += capabilities_table[fun].get('exceptions', []) - - decorators = [] - if cpu_only: - kwargs = dict(cpu_only=True, exceptions=exceptions) - kwargs |= {'reason': "\n".join(cpu_only_reason)} - decorators.append(pytest.mark.skip_xp_backends(**kwargs)) - - if np_only: - decorators.append(pytest.mark.skip_xp_backends(np_only=True)) - - for backend, reason in skip_backends: - backends = {'dask': 'dask.array', 'jax': 'jax.numpy'} - backend = backends.get(backend, backend) - decorators.append(pytest.mark.skip_xp_backends(backend, reason=reason)) - - return lambda fun: functools.reduce(lambda f, g: g(f), decorators, fun) + marks = _make_xp_pytest_marks(func, capabilities_table=capabilities_table) + return pytest.param(func, *args, marks=marks, id=func.__name__) # Is it OK to have a dictionary that is mutated (once upon import) in many places? diff --git a/scipy/_lib/_array_api_compat_vendor.py b/scipy/_lib/_array_api_compat_vendor.py index 85c720f57a90..64d844ff4641 100644 --- a/scipy/_lib/_array_api_compat_vendor.py +++ b/scipy/_lib/_array_api_compat_vendor.py @@ -3,7 +3,7 @@ # to override functions of array_api_compat. from .array_api_compat import * # noqa: F403 -from ._array_api import array_namespace as scipy_array_namespace +from ._array_api_override import array_namespace as scipy_array_namespace # overrides array_api_compat.array_namespace inside array-api-extra array_namespace = scipy_array_namespace # type: ignore[assignment] diff --git a/scipy/_lib/_array_api_override.py b/scipy/_lib/_array_api_override.py new file mode 100644 index 000000000000..7eb204099e73 --- /dev/null +++ b/scipy/_lib/_array_api_override.py @@ -0,0 +1,122 @@ +""" +Override functions from array_api_compat, for use by array-api-extra +and internally. + +See also _array_api_compat_vendor.py +""" +import os + +from collections.abc import Iterable, Iterator +from types import ModuleType +from typing import Any, TypeAlias + +import numpy as np +import numpy.typing as npt + +from scipy._lib import array_api_compat +import scipy._lib.array_api_compat.numpy as np_compat +from scipy._lib.array_api_compat import is_array_api_obj +from scipy._lib._sparse import issparse + + +Array: TypeAlias = Any # To be changed to a Protocol later (see array-api#589) +ArrayLike: TypeAlias = Array | npt.ArrayLike + +# To enable array API and strict array-like input validation +SCIPY_ARRAY_API: str | bool = os.environ.get("SCIPY_ARRAY_API", False) +# To control the default device - for use in the test suite only +SCIPY_DEVICE = os.environ.get("SCIPY_DEVICE", "cpu") + + +def _compliance_scipy(arrays: Iterable[ArrayLike]) -> Iterator[Array]: + """Raise exceptions on known-bad subclasses. Discard 0-dimensional ArrayLikes + and convert 1+-dimensional ArrayLikes to numpy. + + The following subclasses are not supported and raise and error: + - `numpy.ma.MaskedArray` + - `numpy.matrix` + - NumPy arrays which do not have a boolean or numerical dtype + - Any array-like which is neither array API compatible nor coercible by NumPy + - Any array-like which is coerced by NumPy to an unsupported dtype + """ + for array in arrays: + if array is None: + continue + + # this comes from `_util._asarray_validated` + if issparse(array): + msg = ('Sparse arrays/matrices are not supported by this function. ' + 'Perhaps one of the `scipy.sparse.linalg` functions ' + 'would work instead.') + raise ValueError(msg) + + if isinstance(array, np.ma.MaskedArray): + raise TypeError("Inputs of type `numpy.ma.MaskedArray` are not supported.") + + if isinstance(array, np.matrix): + raise TypeError("Inputs of type `numpy.matrix` are not supported.") + + if isinstance(array, np.ndarray | np.generic): + dtype = array.dtype + if not (np.issubdtype(dtype, np.number) or np.issubdtype(dtype, np.bool_)): + raise TypeError(f"An argument has dtype `{dtype!r}`; " + f"only boolean and numerical dtypes are supported.") + + if is_array_api_obj(array): + yield array + else: + try: + array = np.asanyarray(array) + except TypeError: + raise TypeError("An argument is neither array API compatible nor " + "coercible by NumPy.") + dtype = array.dtype + if not (np.issubdtype(dtype, np.number) or np.issubdtype(dtype, np.bool_)): + message = ( + f"An argument was coerced to an unsupported dtype `{dtype!r}`; " + f"only boolean and numerical dtypes are supported." + ) + raise TypeError(message) + # Ignore 0-dimensional arrays, coherently with array-api-compat. + # Raise if there are 1+-dimensional array-likes mixed with non-numpy + # Array API objects. + if array.ndim: + yield array + + +def array_namespace(*arrays: Array) -> ModuleType: + """Get the array API compatible namespace for the arrays xs. + + Parameters + ---------- + *arrays : sequence of array_like + Arrays used to infer the common namespace. + + Returns + ------- + namespace : module + Common namespace. + + Notes + ----- + Thin wrapper around `array_api_compat.array_namespace`. + + 1. Check for the global switch: SCIPY_ARRAY_API. + 2. `_compliance_scipy` raise exceptions on known-bad subclasses. See + its definition for more details. + + When the global switch is False, it defaults to the `numpy` namespace. + In that case, there is no compliance check. This is a convenience to + ease the adoption. Otherwise, arrays must comply with the new rules. + """ + if not SCIPY_ARRAY_API: + # here we could wrap the namespace if needed + return np_compat + + api_arrays = list(_compliance_scipy(arrays)) + # In case of a mix of array API compliant arrays and scalars, return + # the array API namespace. If there are only ArrayLikes (e.g. lists), + # return NumPy (wrapped by array-api-compat). + if api_arrays: + return array_api_compat.array_namespace(*api_arrays) + return np_compat diff --git a/scipy/_lib/_boost_utils.py b/scipy/_lib/_boost_utils.py deleted file mode 100644 index 2e5231fbaa81..000000000000 --- a/scipy/_lib/_boost_utils.py +++ /dev/null @@ -1,9 +0,0 @@ -'''Helper functions to get location of header files.''' - -import pathlib - - -def _boost_dir(ret_path: bool = False) -> pathlib.Path | str: - '''Directory where root Boost/ directory lives.''' - p = pathlib.Path(__file__).parent / 'boost_math/include' - return p if ret_path else str(p) diff --git a/scipy/_lib/_elementwise_iterative_method.py b/scipy/_lib/_elementwise_iterative_method.py index 82910af1bf54..14c5c1949ddf 100644 --- a/scipy/_lib/_elementwise_iterative_method.py +++ b/scipy/_lib/_elementwise_iterative_method.py @@ -290,14 +290,10 @@ def _check_termination(work, res, res_work_pairs, active, check_termination, if not preserve_shape: # compress the arrays to avoid unnecessary computation for key, val in work.items(): - # Need to find a better way than these try/excepts - # Somehow need to keep compressible numerical args separate - if key == 'args': + # `continued_fraction` hacks `n`; improve if this becomes a problem + if key in {'args', 'n'}: continue - try: - work[key] = val[proceed] - except (IndexError, TypeError, KeyError): # not a compressible array - work[key] = val + work[key] = val[proceed] if getattr(val, 'ndim', 0) > 0 else val work.args = [arg[proceed] for arg in work.args] return active @@ -317,24 +313,17 @@ def _update_active(work, res, res_work_pairs, active, mask, preserve_shape, xp): active_mask = xpx.at(active_mask)[active].set(True) active_mask = active_mask & mask for key, val in update_dict.items(): - try: - res[key] = xpx.at(res[key])[active_mask].set(val[active_mask]) - except (IndexError, TypeError, KeyError): - res[key] = xpx.at(res[key])[active_mask].set(val) + val = val[active_mask] if getattr(val, 'ndim', 0) > 0 else val + res[key] = xpx.at(res[key])[active_mask].set(val) else: active_mask = active[mask] for key, val in update_dict.items(): - try: - res[key] = xpx.at(res[key])[active_mask].set(val[mask]) - except (IndexError, TypeError, KeyError): - res[key] = xpx.at(res[key])[active_mask].set(val) + val = val[mask] if getattr(val, 'ndim', 0) > 0 else val + res[key] = xpx.at(res[key])[active_mask].set(val) else: for key, val in update_dict.items(): - if preserve_shape: - try: - val = val[active] - except (IndexError, TypeError, KeyError): - pass + if preserve_shape and getattr(val, 'ndim', 0) > 0: + val = val[active] res[key] = xpx.at(res[key])[active].set(val) diff --git a/scipy/_lib/_unuran_utils.py b/scipy/_lib/_unuran_utils.py deleted file mode 100644 index 7b6ffbdda8c4..000000000000 --- a/scipy/_lib/_unuran_utils.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Helper functions to get location of UNU.RAN source files.""" - -import pathlib - - -def _unuran_dir(ret_path: bool = False) -> pathlib.Path | str: - """Directory where root unuran/ directory lives.""" - p = pathlib.Path(__file__).parent / "unuran" - return p if ret_path else str(p) diff --git a/scipy/_lib/_util.py b/scipy/_lib/_util.py index 2de7552b45d9..79b916757e8e 100644 --- a/scipy/_lib/_util.py +++ b/scipy/_lib/_util.py @@ -12,7 +12,8 @@ import numpy as np from scipy._lib._array_api import (Array, array_namespace, is_lazy_array, - is_numpy, is_marray, xp_size, xp_result_type) + is_numpy, is_marray, xp_result_device, + xp_size, xp_result_type) from scipy._lib._docscrape import FunctionDoc, Parameter from scipy._lib._sparse import issparse @@ -1009,11 +1010,14 @@ def _rng_spawn(rng, n_children): return child_rngs -def _get_nan(*data, xp=None): +def _get_nan(*data, shape=(), xp=None): xp = array_namespace(*data) if xp is None else xp # Get NaN of appropriate dtype for data dtype = xp_result_type(*data, force_floating=True, xp=xp) - res = xp.asarray(xp.nan, dtype=dtype)[()] + device = xp_result_device(*data) + res = xp.full(shape, xp.nan, dtype=dtype, device=device) + if not shape: + res = res[()] # whenever mdhaber/marray#89 is resolved, could just return `res` return res.data if is_marray(xp) else res diff --git a/scipy/_lib/array_api_compat b/scipy/_lib/array_api_compat index 621494be1bd8..8005d6d02c0f 160000 --- a/scipy/_lib/array_api_compat +++ b/scipy/_lib/array_api_compat @@ -1 +1 @@ -Subproject commit 621494be1bd8682f1d76ae874272c12464953d3d +Subproject commit 8005d6d02c0f1717881de37a710871bb955eb5cd diff --git a/scipy/_lib/array_api_extra b/scipy/_lib/array_api_extra index 0d26a7462a3f..28a364d961df 160000 --- a/scipy/_lib/array_api_extra +++ b/scipy/_lib/array_api_extra @@ -1 +1 @@ -Subproject commit 0d26a7462a3fbf5ed9e42e261bdb3b39f25e2faf +Subproject commit 28a364d961dfe506877bd111f9d4e503a9fafa16 diff --git a/scipy/_lib/meson.build b/scipy/_lib/meson.build index ceb85728d4a5..56ef7393f91c 100644 --- a/scipy/_lib/meson.build +++ b/scipy/_lib/meson.build @@ -1,7 +1,4 @@ fs = import('fs') -if not fs.exists('boost_math/README.md') - error('Missing the `boost` submodule! Run `git submodule update --init` to fix this.') -endif if not fs.exists('unuran/README.md') error('Missing the `unuran` submodule! Run `git submodule update --init` to fix this.') endif @@ -115,6 +112,7 @@ python_sources = [ '_array_api.py', '_array_api_compat_vendor.py', '_array_api_no_0d.py', + '_array_api_override.py', '_bunch.py', '_ccallback.py', '_disjoint_set.py', diff --git a/scipy/_lib/tests/test__util.py b/scipy/_lib/tests/test__util.py index 4c4e61ce8c49..d0480907da67 100644 --- a/scipy/_lib/tests/test__util.py +++ b/scipy/_lib/tests/test__util.py @@ -21,7 +21,7 @@ from scipy import cluster, interpolate, linalg, optimize, sparse, spatial, stats -lazy_xp_function(_contains_nan, static_argnames=("nan_policy", "xp_omit_okay", "xp")) +lazy_xp_function(_contains_nan) @pytest.mark.slow diff --git a/scipy/_lib/tests/test_array_api.py b/scipy/_lib/tests/test_array_api.py index 663ee0ac0024..1a633a968ef5 100644 --- a/scipy/_lib/tests/test_array_api.py +++ b/scipy/_lib/tests/test_array_api.py @@ -4,7 +4,7 @@ import pytest from scipy._lib._array_api import ( - _GLOBAL_CONFIG, array_namespace, _asarray, xp_copy, xp_assert_equal, is_numpy, + SCIPY_ARRAY_API, array_namespace, _asarray, xp_copy, xp_assert_equal, is_numpy, np_compat, xp_default_dtype, xp_result_type, is_torch ) from scipy._lib import array_api_extra as xpx @@ -12,12 +12,11 @@ from scipy._lib.array_api_extra.testing import lazy_xp_function -lazy_xp_function(_asarray, static_argnames=( - "dtype", "order", "copy", "xp", "check_finite", "subok")) -lazy_xp_function(xp_copy, static_argnames=("xp", )) +lazy_xp_function(_asarray) +lazy_xp_function(xp_copy) -@pytest.mark.skipif(not _GLOBAL_CONFIG["SCIPY_ARRAY_API"], +@pytest.mark.skipif(not SCIPY_ARRAY_API, reason="Array API test; set environment variable SCIPY_ARRAY_API=1 to run it") class TestArrayAPI: @@ -26,11 +25,6 @@ def test_array_namespace(self): xp = array_namespace(x, y) assert 'array_api_compat.numpy' in xp.__name__ - _GLOBAL_CONFIG["SCIPY_ARRAY_API"] = False - xp = array_namespace(x, y) - assert 'array_api_compat.numpy' in xp.__name__ - _GLOBAL_CONFIG["SCIPY_ARRAY_API"] = True - def test_asarray(self, xp): x, y = _asarray([0, 1, 2], xp=xp), _asarray(np.arange(3), xp=xp) ref = xp.asarray([0, 1, 2]) diff --git a/scipy/_lib/tests/test_public_api.py b/scipy/_lib/tests/test_public_api.py index f120b74a1830..b78e9d9d25db 100644 --- a/scipy/_lib/tests/test_public_api.py +++ b/scipy/_lib/tests/test_public_api.py @@ -360,6 +360,7 @@ def check_importable(module_name): ('scipy.interpolate.dfitpack', None), ('scipy.interpolate.fitpack', None), ('scipy.interpolate.fitpack2', None), + ('scipy.interpolate.interpnd', None), ('scipy.interpolate.interpolate', None), ('scipy.interpolate.ndgriddata', None), ('scipy.interpolate.polyint', None), diff --git a/scipy/_lib/tests/test_warnings.py b/scipy/_lib/tests/test_warnings.py index f200b1a6e975..0a5f5cfbc55d 100644 --- a/scipy/_lib/tests/test_warnings.py +++ b/scipy/_lib/tests/test_warnings.py @@ -120,6 +120,7 @@ def test_warning_calls_filters(warning_calls): os.path.join('stats', '_continuous_distns.py'), os.path.join('stats', '_binned_statistic.py'), # gh-19345 os.path.join('stats', '_stats_py.py'), # gh-20743 + os.path.join('stats', '_variation.py'), # gh-22827 os.path.join('stats', 'tests', 'test_axis_nan_policy.py'), # gh-20694 os.path.join('_lib', '_util.py'), # gh-19341 os.path.join('sparse', 'linalg', '_dsolve', 'linsolve.py'), # gh-17924 diff --git a/scipy/cluster/hierarchy.py b/scipy/cluster/hierarchy.py index 56620ad9a8d9..1a7d212355db 100644 --- a/scipy/cluster/hierarchy.py +++ b/scipy/cluster/hierarchy.py @@ -134,8 +134,8 @@ import numpy as np from . import _hierarchy, _optimal_leaf_ordering import scipy.spatial.distance as distance -from scipy._lib._array_api import (_asarray, array_namespace, is_dask, is_jax, - is_lazy_array, xp_copy) +from scipy._lib._array_api import (_asarray, array_namespace, is_dask, + is_lazy_array, xp_capabilities, xp_copy) from scipy._lib._disjoint_set import DisjointSet import scipy._lib.array_api_extra as xpx @@ -165,9 +165,15 @@ def _warning(s): def int_floor(arr, xp): # array_api_strict is strict about not allowing `int()` on a float array. # That's typically not needed, here it is - so explicitly convert - return int(xp.astype(xp.asarray(arr), xp.int64)) + return int(xp.asarray(arr, dtype=xp.int64)) +lazy_cython = xp_capabilities( + cpu_only=True, reason="Cython code", + warnings=[("dask.array", "merges chunks")]) + + +@lazy_cython def single(y): """ Perform single/min/nearest linkage on the condensed distance matrix ``y``. @@ -246,6 +252,7 @@ def single(y): return linkage(y, method='single', metric='euclidean') +@lazy_cython def complete(y): """ Perform complete/max/farthest point linkage on a condensed distance matrix. @@ -328,6 +335,7 @@ def complete(y): return linkage(y, method='complete', metric='euclidean') +@lazy_cython def average(y): """ Perform average/UPGMA linkage on a condensed distance matrix. @@ -410,6 +418,7 @@ def average(y): return linkage(y, method='average', metric='euclidean') +@lazy_cython def weighted(y): """ Perform weighted/WPGMA linkage on the condensed distance matrix. @@ -495,6 +504,7 @@ def weighted(y): return linkage(y, method='weighted', metric='euclidean') +@lazy_cython def centroid(y): """ Perform centroid/UPGMC linkage. @@ -597,6 +607,7 @@ def centroid(y): return linkage(y, method='centroid', metric='euclidean') +@lazy_cython def median(y): """ Perform median/WPGMC linkage. @@ -699,6 +710,7 @@ def median(y): return linkage(y, method='median', metric='euclidean') +@lazy_cython def ward(y): """ Perform Ward's linkage on a condensed distance matrix. @@ -798,6 +810,7 @@ def ward(y): return linkage(y, method='ward', metric='euclidean') +@lazy_cython def linkage(y, method='single', metric='euclidean', optimal_ordering=False): """ Perform hierarchical/agglomerative clustering. @@ -1049,7 +1062,8 @@ def cy_linkage(y, validate): return _hierarchy.fast_linkage(y, n, method_code) result = xpx.lazy_apply(cy_linkage, y, validate=lazy, - shape=(n - 1, 4), dtype=xp.float64, as_numpy=True) + shape=(n - 1, 4), dtype=xp.float64, + as_numpy=True, xp=xp) if optimal_ordering: return optimal_leaf_ordering(result, y) @@ -1290,6 +1304,7 @@ def _order_cluster_tree(Z): return nodes +@xp_capabilities(np_only=True, reason="non-standard indexing") def cut_tree(Z, n_clusters=None, height=None): """ Given a linkage matrix Z, return the cut tree. @@ -1377,6 +1392,7 @@ def cut_tree(Z, n_clusters=None, height=None): return groups.T +@xp_capabilities(jax_jit=False, allow_dask_compute=True) def to_tree(Z, rd=False): """ Convert a linkage matrix into an easy-to-use tree object. @@ -1438,8 +1454,8 @@ def to_tree(Z, rd=False): """ xp = array_namespace(Z) - Z = _asarray(Z, order='c', xp=xp) - is_valid_linkage(Z, throw=True, name='Z') + Z = _asarray(Z, order='C', xp=xp) + _is_valid_linkage(Z, throw=True, name='Z', materialize=True, xp=xp) # Number of original objects is equal to the number of rows plus 1. n = Z.shape[0] + 1 @@ -1480,6 +1496,7 @@ def to_tree(Z, rd=False): return nd +@lazy_cython def optimal_leaf_ordering(Z, y, metric='euclidean'): """ Given a linkage matrix Z and distance, reorder the cut tree. @@ -1523,7 +1540,7 @@ def optimal_leaf_ordering(Z, y, metric='euclidean'): Z = _asarray(Z, order='C', xp=xp) y = _asarray(y, order='C', dtype=xp.float64, xp=xp) lazy = is_lazy_array(Z) - _is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) if y.ndim == 1: distance.is_valid_y(y, throw=True, name='y') @@ -1544,18 +1561,19 @@ def optimal_leaf_ordering(Z, y, metric='euclidean'): # The function name is prominently visible on the user-facing Dask dashboard; # make sure it is meaningful. - def optimal_leaf_ordering_(Z, y, validate): + def cy_optimal_leaf_ordering(Z, y, validate): if validate: - is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', xp=np) if not np.all(np.isfinite(y)): raise ValueError("The condensed distance matrix must contain only " "finite values.") return _optimal_leaf_ordering.optimal_leaf_ordering(Z, y) - return xpx.lazy_apply(optimal_leaf_ordering_, Z, y, validate=lazy, - shape=Z.shape, dtype=Z.dtype, as_numpy=True) + return xpx.lazy_apply(cy_optimal_leaf_ordering, Z, y, validate=lazy, + shape=Z.shape, dtype=Z.dtype, as_numpy=True, xp=xp) +@lazy_cython def cophenet(Z, Y=None): """ Calculate the cophenetic distances between each observation in @@ -1666,13 +1684,21 @@ def cophenet(Z, Y=None): xp = array_namespace(Z, Y) # Ensure float64 C-contiguous array. Cython code doesn't deal with striding. Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp) - is_valid_linkage(Z, throw=True, name='Z') - n = Z.shape[0] + 1 - zz = np.zeros((n * (n-1)) // 2, dtype=np.float64) + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) - Z = np.asarray(Z) - _hierarchy.cophenetic_distances(Z, zz, int(n)) - zz = xp.asarray(zz) + def cy_cophenet(Z, validate): + if validate: + _is_valid_linkage(Z, throw=True, name='Z', xp=np) + n = Z.shape[0] + 1 + zz = np.zeros((n * (n-1)) // 2, dtype=np.float64) + _hierarchy.cophenetic_distances(Z, zz, n) + return zz + + n = Z.shape[0] + 1 + zz = xpx.lazy_apply(cy_cophenet, Z, validate=is_lazy_array(Z), + shape=((n * (n-1)) // 2, ), dtype=xp.float64, + as_numpy=True, xp=xp) + if Y is None: return zz @@ -1690,6 +1716,7 @@ def cophenet(Z, Y=None): return (c, zz) +@lazy_cython def inconsistent(Z, d=2): r""" Calculate inconsistency statistics on a linkage matrix. @@ -1747,21 +1774,26 @@ def inconsistent(Z, d=2): """ xp = array_namespace(Z) Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp) - is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) - if (not d == np.floor(d)) or d < 0: + if d != np.floor(d) or d < 0: raise ValueError('The second argument d must be a nonnegative ' 'integer value.') - n = Z.shape[0] + 1 - R = np.zeros((n - 1, 4), dtype=np.float64) + def cy_inconsistent(Z, d, validate): + if validate: + _is_valid_linkage(Z, throw=True, name='Z', xp=np) + R = np.zeros((Z.shape[0], 4), dtype=np.float64) + n = Z.shape[0] + 1 + _hierarchy.inconsistent(Z, R, n, d) + return R - Z = np.asarray(Z) - _hierarchy.inconsistent(Z, R, int(n), int(d)) - R = xp.asarray(R) - return R + return xpx.lazy_apply(cy_inconsistent, Z, d=int(d), validate=is_lazy_array(Z), + shape=(Z.shape[0], 4), dtype=xp.float64, + as_numpy=True, xp=xp) +@lazy_cython def from_mlab_linkage(Z): """ Convert a linkage matrix generated by MATLAB(TM) to a new @@ -1834,34 +1866,48 @@ def from_mlab_linkage(Z): """ xp = array_namespace(Z) Z = _asarray(Z, dtype=xp.float64, order='C', xp=xp) - Zs = Z.shape # If it's empty, return it. - if len(Zs) == 0 or (len(Zs) == 1 and Zs[0] == 0): + if Z.shape in ((), (0, )): return xp_copy(Z, xp=xp) - if len(Zs) != 2: + if Z.ndim != 2: raise ValueError("The linkage array must be rectangular.") # If it contains no rows, return it. - if Zs[0] == 0: + n = Z.shape[0] + if n == 0: return xp_copy(Z, xp=xp) - if xp.min(Z[:, 0:2]) != 1.0 and xp.max(Z[:, 0:2]) != 2 * Zs[0]: + lazy = is_lazy_array(Z) + + if not lazy and xp.min(Z[:, :2]) != 1.0 and xp.max(Z[:, :2]) != 2 * n: raise ValueError('The format of the indices is not 1..N') - Zpart = xp.concat((Z[:, 0:2] - 1.0, Z[:, 2:]), axis=1) - CS = np.zeros((Zs[0],), dtype=np.float64) - if is_jax(xp): - # calculate_cluster_sizes doesn't accept read-only arrays - Zpart = np.array(Zpart, copy=True) - else: - Zpart = np.asarray(Zpart) - _hierarchy.calculate_cluster_sizes(Zpart, CS, int(Zs[0]) + 1) - res = np.hstack([Zpart, CS.reshape(Zs[0], 1)]) - return xp.asarray(res) + res = xp.empty((Z.shape[0], Z.shape[1] + 1), dtype=Z.dtype) + res = xpx.at(res)[:, :2].set(Z[:, :2] - 1.0) + res = xpx.at(res)[:, 2:-1].set(Z[:, 2:]) + + def cy_from_mlab_linkage(Zpart, validate): + n = Zpart.shape[0] + if validate and np.min(Zpart[:, :2]) != 0.0 and np.max(Zpart[:, :2]) != 2 * n: + raise ValueError('The format of the indices is not 1..N') + + if not Zpart.flags.writeable: + Zpart = Zpart.copy() # xp=jax.numpy + + CS = np.zeros((n,)) + _hierarchy.calculate_cluster_sizes(Zpart, CS, n + 1) + return CS + CS = xpx.lazy_apply(cy_from_mlab_linkage, res[:, :-1], validate=lazy, + shape=(res.shape[0],), dtype=xp.float64, + as_numpy=True, xp=xp) + return xpx.at(res)[:, -1].set(CS) + + +@xp_capabilities() def to_mlab_linkage(Z): """ Convert a linkage matrix to a MATLAB(TM) compatible one. @@ -1938,14 +1984,15 @@ def to_mlab_linkage(Z): """ xp = array_namespace(Z) - Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp) - if Z.ndim == 0 or (Z.ndim == 1 and Z.shape[0] == 0): + Z = _asarray(Z, dtype=xp.float64, xp=xp) + if Z.shape in ((), (0, )): return xp_copy(Z, xp=xp) - _is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) return xp.concat((Z[:, :2] + 1.0, Z[:, 2:3]), axis=1) +@xp_capabilities() def is_monotonic(Z): """ Return True if the linkage passed is monotonic. @@ -2025,13 +2072,14 @@ def is_monotonic(Z): """ xp = array_namespace(Z) - Z = _asarray(Z, order='c', xp=xp) - _is_valid_linkage(Z, throw=True, name='Z') + Z = _asarray(Z, xp=xp) + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) # We expect the i'th value to be greater than its successor. return xp.all(Z[1:, 2] >= Z[:-1, 2]) +@xp_capabilities(warnings=[("dask.array", "see notes"), ("jax.numpy", "see notes")]) def is_valid_im(R, warning=False, throw=False, name=None): """Return True if the inconsistency matrix passed is valid. @@ -2125,16 +2173,17 @@ def is_valid_im(R, warning=False, throw=False, name=None): False """ - return _is_valid_im(R, warning=warning, throw=throw, name=name, materialize=True) + xp = array_namespace(R) + R = _asarray(R, xp=xp) + return _is_valid_im(R, warning=warning, throw=throw, name=name, + materialize=True, xp=xp) -def _is_valid_im(R, warning=False, throw=False, name=None, materialize=False): +def _is_valid_im(R, warning=False, throw=False, name=None, materialize=False, *, xp): """Variant of `is_valid_im` to be called internally by other scipy functions, which by default does not materialize lazy input arrays (Dask, JAX, etc.) when warning=True or throw=True. """ - xp = array_namespace(R) - R = _asarray(R, xp=xp) name_str = f"{name!r} " if name else '' try: if R.dtype != xp.float64: @@ -2168,6 +2217,7 @@ def _is_valid_im(R, warning=False, throw=False, name=None, materialize=False): ) +@xp_capabilities(warnings=[("dask.array", "see notes"), ("jax.numpy", "see notes")]) def is_valid_linkage(Z, warning=False, throw=False, name=None): """ Check the validity of a linkage matrix. @@ -2259,17 +2309,18 @@ def is_valid_linkage(Z, warning=False, throw=False, name=None): False """ + xp = array_namespace(Z) + Z = _asarray(Z, xp=xp) return _is_valid_linkage(Z, warning=warning, throw=throw, - name=name, materialize=True) + name=name, materialize=True, xp=xp) -def _is_valid_linkage(Z, warning=False, throw=False, name=None, materialize=False): +def _is_valid_linkage(Z, warning=False, throw=False, name=None, + materialize=False, *, xp): """Variant of `is_valid_linkage` to be called internally by other scipy functions, which by default does not materialize lazy input arrays (Dask, JAX, etc.) when warning=True or throw=True. """ - xp = array_namespace(Z) - Z = _asarray(Z, xp=xp) name_str = f"{name!r} " if name else '' try: if Z.dtype != xp.float64: @@ -2363,6 +2414,7 @@ def _lazy_valid_checks(*args, throw=False, warning=False, materialize=False, xp) return not any(conds) +@xp_capabilities() def num_obs_linkage(Z): """ Return the number of original observations of the linkage matrix passed. @@ -2397,11 +2449,12 @@ def num_obs_linkage(Z): """ xp = array_namespace(Z) - Z = _asarray(Z, order='c', xp=xp) - _is_valid_linkage(Z, throw=True, name='Z') + Z = _asarray(Z, xp=xp) + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) return Z.shape[0] + 1 +@xp_capabilities() def correspond(Z, Y): """ Check for correspondence between linkage and condensed distance matrices. @@ -2451,14 +2504,16 @@ def correspond(Z, Y): True """ - _is_valid_linkage(Z, throw=True) - distance.is_valid_y(Y, throw=True) xp = array_namespace(Z, Y) - Z = _asarray(Z, order='c', xp=xp) - Y = _asarray(Y, order='c', xp=xp) + Z = _asarray(Z, xp=xp) + Y = _asarray(Y, xp=xp) + _is_valid_linkage(Z, throw=True, xp=xp) + distance.is_valid_y(Y, throw=True) return distance.num_obs_y(Y) == num_obs_linkage(Z) +@xp_capabilities(cpu_only=True, reason="Cython code", + jax_jit=False, allow_dask_compute=True) def fcluster(Z, t, criterion='inconsistent', depth=2, R=None, monocrit=None): """ Form flat clusters from the hierarchical clustering defined by @@ -2612,7 +2667,7 @@ def fcluster(Z, t, criterion='inconsistent', depth=2, R=None, monocrit=None): """ xp = array_namespace(Z) Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp) - is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', materialize=True, xp=xp) n = Z.shape[0] + 1 T = np.zeros((n,), dtype='i') @@ -2627,7 +2682,7 @@ def fcluster(Z, t, criterion='inconsistent', depth=2, R=None, monocrit=None): R = inconsistent(Z, depth) else: R = _asarray(R, order='C', dtype=xp.float64, xp=xp) - is_valid_im(R, throw=True, name='R') + _is_valid_im(R, throw=True, name='R', materialize=True, xp=xp) # Since the C code does not support striding using strides. # The dimensions are used instead. R = np.asarray(R) @@ -2645,6 +2700,8 @@ def fcluster(Z, t, criterion='inconsistent', depth=2, R=None, monocrit=None): return xp.asarray(T) +@xp_capabilities(cpu_only=True, reason="Cython code", + jax_jit=False, allow_dask_compute=True) def fclusterdata(X, t, criterion='inconsistent', metric='euclidean', depth=2, method='single', R=None): """ @@ -2741,11 +2798,12 @@ def fclusterdata(X, t, criterion='inconsistent', if R is None: R = inconsistent(Z, d=depth) else: - R = _asarray(R, order='c', xp=xp) + R = _asarray(R, order='C', xp=xp) T = fcluster(Z, criterion=criterion, depth=depth, R=R, t=t) return T +@lazy_cython def leaves_list(Z): """ Return a list of leaf node ids. @@ -2796,12 +2854,20 @@ def leaves_list(Z): """ xp = array_namespace(Z) Z = _asarray(Z, order='C', xp=xp) - is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) + + def cy_leaves_list(Z, validate): + if validate: + _is_valid_linkage(Z, throw=True, name='Z', xp=np) + n = Z.shape[0] + 1 + ML = np.zeros((n,), dtype=np.int32) + _hierarchy.prelist(Z, ML, n) + return ML + n = Z.shape[0] + 1 - ML = np.zeros((n,), dtype='i') - Z = np.asarray(Z) - _hierarchy.prelist(Z, ML, n) - return xp.asarray(ML) + return xpx.lazy_apply(cy_leaves_list, Z, validate=is_lazy_array(Z), + shape=(n, ), dtype=xp.int32, + as_numpy=True, xp=xp) # Maps number of leaves to text size. @@ -3059,6 +3125,7 @@ def set_link_color_palette(palette): _link_line_colors = palette +@xp_capabilities(cpu_only=True, jax_jit=False, allow_dask_compute=True) def dendrogram(Z, p=30, truncate_mode=None, color_threshold=None, get_leaves=True, orientation='top', labels=None, count_sort=False, distance_sort=False, show_leaf_counts=True, @@ -3335,7 +3402,7 @@ def llf(id): # None orders leaf nodes based on the order they appear in the # pre-order traversal. xp = array_namespace(Z) - Z = _asarray(Z, order='c', xp=xp) + Z = _asarray(Z, order='C', xp=xp) if orientation not in ["top", "left", "bottom", "right"]: raise ValueError("orientation must be one of 'top', 'left', " @@ -3349,7 +3416,7 @@ def llf(id): if Z.shape[0] + 1 != len_labels: raise ValueError("Dimensions of Z and labels must be consistent.") - is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', materialize=True, xp=xp) Zs = Z.shape n = Zs[0] + 1 if isinstance(p, int | float): @@ -3742,6 +3809,8 @@ def _dendrogram_calculate_info(Z, p, truncate_mode, return (((uiva + uivb) / 2), uwa + uwb, h, max_dist) +@xp_capabilities(cpu_only=True, + warnings=[("dask.array", "see notes"), ("jax.numpy", "see notes")]) def is_isomorphic(T1, T2): """ Determine if two different cluster assignments are equivalent. @@ -3759,6 +3828,11 @@ def is_isomorphic(T1, T2): Whether the flat cluster assignments `T1` and `T2` are equivalent. + Notes + ----- + *Array API support (experimental):* If the input is a lazy Array (e.g. Dask + or JAX), the return value will be a 0-dimensional bool Array. + See Also -------- linkage : for a description of what a linkage matrix is. @@ -3773,7 +3847,7 @@ def is_isomorphic(T1, T2): Two flat cluster assignments can be isomorphic if they represent the same cluster assignment, with different labels. - For example, we can use the `scipy.cluster.hierarchy.single`: method + For example, we can use the `scipy.cluster.hierarchy.single` method and flatten the output to four clusters: >>> X = [[0, 0], [0, 1], [1, 0], @@ -3803,35 +3877,40 @@ def is_isomorphic(T1, T2): True """ - T1 = np.asarray(T1, order='c') - T2 = np.asarray(T2, order='c') + xp = array_namespace(T1, T2) + T1 = _asarray(T1, xp=xp) + T2 = _asarray(T2, xp=xp) - T1S = T1.shape - T2S = T2.shape - - if len(T1S) != 1: + if T1.ndim != 1: raise ValueError('T1 must be one-dimensional.') - if len(T2S) != 1: + if T2.ndim != 1: raise ValueError('T2 must be one-dimensional.') - if T1S[0] != T2S[0]: + if T1.shape != T2.shape: raise ValueError('T1 and T2 must have the same number of elements.') - n = T1S[0] - d1 = {} - d2 = {} - for i in range(0, n): - if T1[i] in d1: - if T2[i] not in d2: - return False - if d1[T1[i]] != T2[i] or d2[T2[i]] != T1[i]: + + def py_is_isomorphic(T1, T2): + d1 = {} + d2 = {} + for t1, t2 in zip(T1, T2): + if t1 in d1: + if t2 not in d2: + return False + if d1[t1] != t2 or d2[t2] != t1: + return False + elif t2 in d2: return False - elif T2[i] in d2: - return False - else: - d1[T1[i]] = T2[i] - d2[T2[i]] = T1[i] - return True + else: + d1[t1] = t2 + d2[t2] = t1 + return True + + res = xpx.lazy_apply(py_is_isomorphic, T1, T2, + shape=(), dtype=xp.bool, + as_numpy=True, xp=xp) + return res if is_lazy_array(res) else bool(res) +@lazy_cython def maxdists(Z): """ Return the maximum distance between any non-singleton cluster. @@ -3907,16 +3986,21 @@ def maxdists(Z): """ xp = array_namespace(Z) Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp) - is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) - n = Z.shape[0] + 1 - MD = np.zeros((n - 1,)) - Z = np.asarray(Z) - _hierarchy.get_max_dist_for_each_cluster(Z, MD, int(n)) - MD = xp.asarray(MD) - return MD + def cy_maxdists(Z, validate): + if validate: + _is_valid_linkage(Z, throw=True, name='Z', xp=np) + MD = np.zeros((Z.shape[0],)) + _hierarchy.get_max_dist_for_each_cluster(Z, MD, Z.shape[0] + 1) + return MD + + return xpx.lazy_apply(cy_maxdists, Z, validate=is_lazy_array(Z), + shape=(Z.shape[0], ), dtype=xp.float64, + as_numpy=True, xp=xp) +@lazy_cython def maxinconsts(Z, R): """ Return the maximum inconsistency coefficient for each @@ -3995,21 +4079,28 @@ def maxinconsts(Z, R): xp = array_namespace(Z, R) Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp) R = _asarray(R, order='C', dtype=xp.float64, xp=xp) - is_valid_linkage(Z, throw=True, name='Z') - is_valid_im(R, throw=True, name='R') + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) + _is_valid_im(R, throw=True, name='R', xp=xp) - n = Z.shape[0] + 1 if Z.shape[0] != R.shape[0]: raise ValueError("The inconsistency matrix and linkage matrix each " "have a different number of rows.") - MI = np.zeros((n - 1,)) - Z = np.asarray(Z) - R = np.asarray(R) - _hierarchy.get_max_Rfield_for_each_cluster(Z, R, MI, int(n), 3) - MI = xp.asarray(MI) - return MI + + def cy_maxinconsts(Z, R, validate): + if validate: + _is_valid_linkage(Z, throw=True, name='Z', xp=np) + _is_valid_im(R, throw=True, name='R', xp=np) + n = Z.shape[0] + 1 + MI = np.zeros((n - 1,)) + _hierarchy.get_max_Rfield_for_each_cluster(Z, R, MI, n, 3) + return MI + + return xpx.lazy_apply(cy_maxinconsts, Z, R, validate=is_lazy_array(Z), + shape=(Z.shape[0], ), dtype=xp.float64, + as_numpy=True, xp=xp) +@lazy_cython def maxRstat(Z, R, i): """ Return the maximum statistic for each non-singleton cluster and its @@ -4090,8 +4181,8 @@ def maxRstat(Z, R, i): xp = array_namespace(Z, R) Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp) R = _asarray(R, order='C', dtype=xp.float64, xp=xp) - is_valid_linkage(Z, throw=True, name='Z') - is_valid_im(R, throw=True, name='R') + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) + _is_valid_im(R, throw=True, name='R', xp=xp) if not isinstance(i, int): raise TypeError('The third argument must be an integer.') @@ -4103,15 +4194,23 @@ def maxRstat(Z, R, i): raise ValueError("The inconsistency matrix and linkage matrix each " "have a different number of rows.") - n = Z.shape[0] + 1 - MR = np.zeros((n - 1,)) - Z = np.asarray(Z) - R = np.asarray(R) - _hierarchy.get_max_Rfield_for_each_cluster(Z, R, MR, int(n), i) - MR = xp.asarray(MR) - return MR + def cy_maxRstat(Z, R, i, validate): + if validate: + _is_valid_linkage(Z, throw=True, name='Z', xp=np) + _is_valid_im(R, throw=True, name='R', xp=np) + MR = np.zeros((Z.shape[0],)) + n = Z.shape[0] + 1 + _hierarchy.get_max_Rfield_for_each_cluster(Z, R, MR, n, i) + return MR + + return xpx.lazy_apply(cy_maxRstat, Z, R, i=i, validate=is_lazy_array(Z), + shape=(Z.shape[0], ), dtype=xp.float64, + as_numpy=True, xp=xp) +# Data-dependent output shape makes it impossible to use jax.jit +@xp_capabilities(cpu_only=True, reason="Cython code", jax_jit=False, + warnings=[("dask.array", "merges chunks")]) def leaders(Z, T): """ Return the root nodes in a hierarchical clustering. @@ -4222,7 +4321,7 @@ def leaders(Z, T): xp = array_namespace(Z, T) Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp) T = _asarray(T, order='C', xp=xp) - _is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) if T.dtype != xp.int32: raise TypeError('T must be a 1-D array of dtype int32.') @@ -4232,9 +4331,9 @@ def leaders(Z, T): n_obs = Z.shape[0] + 1 - def leaders_(Z, T, validate): + def cy_leaders(Z, T, validate): if validate: - is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', xp=np) n_clusters = int(xpx.nunique(T)) L = np.zeros(n_clusters, dtype=np.int32) M = np.zeros(n_clusters, dtype=np.int32) @@ -4244,6 +4343,6 @@ def leaders_(Z, T, validate): f'when examining linkage node {s} (< 2n-1).') return L, M - return xpx.lazy_apply(leaders_, Z, T, validate=is_lazy_array(Z), + return xpx.lazy_apply(cy_leaders, Z, T, validate=is_lazy_array(Z), shape=((None,), (None, )), dtype=(xp.int32, xp.int32), - as_numpy=True) + as_numpy=True, xp=xp) diff --git a/scipy/cluster/tests/test_hierarchy.py b/scipy/cluster/tests/test_hierarchy.py index 50622ace2f93..7cab57ba2fd0 100644 --- a/scipy/cluster/tests/test_hierarchy.py +++ b/scipy/cluster/tests/test_hierarchy.py @@ -32,12 +32,10 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import numpy as np -from numpy.testing import (assert_allclose, assert_equal, assert_array_equal, assert_, - assert_warns) +from numpy.testing import assert_allclose, assert_equal, assert_array_equal, assert_ import pytest from pytest import raises as assert_raises -import scipy.cluster.hierarchy from scipy.cluster.hierarchy import ( ClusterWarning, linkage, from_mlab_linkage, to_mlab_linkage, num_obs_linkage, inconsistent, cophenet, fclusterdata, fcluster, @@ -46,16 +44,22 @@ is_valid_linkage, is_valid_im, to_tree, leaves_list, dendrogram, set_link_color_palette, cut_tree, optimal_leaf_ordering, _order_cluster_tree, _hierarchy, _EUCLIDEAN_METHODS, _LINKAGE_METHODS) -from scipy.spatial.distance import pdist from scipy.cluster._hierarchy import Heap -from scipy._lib._array_api import xp_assert_close, xp_assert_equal +from scipy.spatial.distance import pdist +from scipy._lib._array_api import (eager_warns, make_xp_test_case, + xp_assert_close, xp_assert_equal) import scipy._lib.array_api_extra as xpx -from scipy._lib.array_api_extra.testing import lazy_xp_function from threading import Lock from . import hierarchy_test_data +class eager: + # Bypass xpx.testing.lazy_xp_function when calling + # these functions from this namespace + is_valid_im = is_valid_im + is_valid_linkage = is_valid_linkage + # Matplotlib is not a scipy dependency but is optionally used in dendrogram, so # check if it's available @@ -70,47 +74,9 @@ have_matplotlib = False skip_xp_backends = pytest.mark.skip_xp_backends -xfail_xp_backends = pytest.mark.xfail_xp_backends -use_linkage = skip_xp_backends(cpu_only=True, exceptions=["jax.numpy"], - reason="linkage() invokes Cython code") - -lazy_xp_function(single) -lazy_xp_function(ward) -lazy_xp_function(linkage, static_argnames=('method', 'metric', 'optimal_ordering')) -lazy_xp_function(cut_tree, static_argnames=('n_clusters', 'height')) -lazy_xp_function(to_tree, jax_jit=False, allow_dask_compute=999, - static_argnames=('rd', )) -lazy_xp_function(optimal_leaf_ordering, static_argnames=('metric',)) -lazy_xp_function(cophenet, jax_jit=False, allow_dask_compute=2) -lazy_xp_function(inconsistent, jax_jit=False, allow_dask_compute=2, - static_argnames=('d',)) -lazy_xp_function(from_mlab_linkage, jax_jit=False, allow_dask_compute=2) -lazy_xp_function(to_mlab_linkage, jax_jit=False, allow_dask_compute=1) -lazy_xp_function(is_monotonic) - -# Note: these functions materialize lazy arrays when warning=True or throw=True -lazy_xp_function(is_valid_im, static_argnames=("warning", "throw", "name")) -lazy_xp_function(is_valid_linkage, static_argnames=("warning", "throw", "name")) - -lazy_xp_function(num_obs_linkage) -lazy_xp_function(correspond) -lazy_xp_function(fcluster, jax_jit=False, allow_dask_compute=999, - static_argnames=('criterion', 'depth')) -lazy_xp_function(fclusterdata, jax_jit=False, allow_dask_compute=999, - static_argnames=('criterion', 'metric', 'depth', 'method')) -lazy_xp_function(leaves_list, jax_jit=False, allow_dask_compute=2) -lazy_xp_function(dendrogram, jax_jit=False, allow_dask_compute=999) -lazy_xp_function(is_isomorphic, jax_jit=False, allow_dask_compute=2) -lazy_xp_function(maxdists, jax_jit=False, allow_dask_compute=999) -lazy_xp_function(maxinconsts, jax_jit=False, allow_dask_compute=999) -lazy_xp_function(maxRstat, jax_jit=False, allow_dask_compute=999, - static_argnames=('i',)) - -# Returns data-dependent shape -lazy_xp_function(leaders, jax_jit=False) - - -@use_linkage + + +@make_xp_test_case(linkage) class TestLinkage: @skip_xp_backends("jax.numpy", reason="Can't raise inside jax.pure_callback") @@ -166,40 +132,54 @@ def test_optimal_leaf_ordering(self, xp): expectedZ = getattr(hierarchy_test_data, 'linkage_ytdist_single_olo') xp_assert_close(Z, xp.asarray(expectedZ), atol=1e-10) - -@use_linkage -class TestLinkageTies: - - _expectations = { - 'single': np.array([[0, 1, 1.41421356, 2], - [2, 3, 1.41421356, 3]]), - 'complete': np.array([[0, 1, 1.41421356, 2], - [2, 3, 2.82842712, 3]]), - 'average': np.array([[0, 1, 1.41421356, 2], - [2, 3, 2.12132034, 3]]), - 'weighted': np.array([[0, 1, 1.41421356, 2], - [2, 3, 2.12132034, 3]]), - 'centroid': np.array([[0, 1, 1.41421356, 2], - [2, 3, 2.12132034, 3]]), - 'median': np.array([[0, 1, 1.41421356, 2], - [2, 3, 2.12132034, 3]]), - 'ward': np.array([[0, 1, 1.41421356, 2], - [2, 3, 2.44948974, 3]]), - } - - def test_linkage_ties(self, xp): - for method in ['single', 'complete', 'average', 'weighted', - 'centroid', 'median', 'ward']: - self.check_linkage_ties(method, xp) - - def check_linkage_ties(self, method, xp): + @pytest.mark.parametrize("method,expect", [ + ('single', [[0, 1, 1.41421356, 2], + [2, 3, 1.41421356, 3]]), + ('complete', [[0, 1, 1.41421356, 2], + [2, 3, 2.82842712, 3]]), + ('average', [[0, 1, 1.41421356, 2], + [2, 3, 2.12132034, 3]]), + ('weighted', [[0, 1, 1.41421356, 2], + [2, 3, 2.12132034, 3]]), + ('centroid', [[0, 1, 1.41421356, 2], + [2, 3, 2.12132034, 3]]), + ('median', [[0, 1, 1.41421356, 2], + [2, 3, 2.12132034, 3]]), + ('ward', [[0, 1, 1.41421356, 2], + [2, 3, 2.44948974, 3]]), + ]) + def test_linkage_ties(self, method, expect, xp): X = xp.asarray([[-1, -1], [0, 0], [1, 1]]) Z = linkage(X, method=method) - expectedZ = self._expectations[method] - xp_assert_close(Z, xp.asarray(expectedZ), atol=1e-06) + expect = xp.asarray(expect, dtype=xp.float64) + xp_assert_close(Z, expect, atol=1e-06) + + def test_unsupported_uncondensed_distance_matrix_linkage_warning(self, xp): + X = xp.asarray([[0, 1], [1, 0]]) + with eager_warns(X, ClusterWarning): + linkage(X) + @pytest.mark.parametrize("method", _EUCLIDEAN_METHODS) + def test_euclidean_linkage_value_error(self, method, xp): + X = xp.asarray([[1, 1], [1, 1]]) + with pytest.raises(ValueError): + linkage(X, method=method, metric='cityblock') -@skip_xp_backends(cpu_only=True) + def test_2x2_linkage(self, xp): + Z1 = linkage(xp.asarray([1]), method='single', metric='euclidean') + Z2 = linkage(xp.asarray([[0, 1], [0, 0]]), method='single', metric='euclidean') + xp_assert_close(Z1, Z2, rtol=1e-15) + + @skip_xp_backends("jax.numpy", reason="Can't raise inside jax.pure_callback") + def test_centroid_neg_distance(self, xp): + # gh-21011 + values = xp.asarray([0, 0, -1]) + with pytest.raises(ValueError): + # This is just checking that this doesn't crash + linkage(values, method='centroid') + + +@make_xp_test_case(inconsistent) class TestInconsistent: def test_inconsistent_tdist(self, xp): @@ -212,7 +192,7 @@ def check_inconsistent_tdist(self, depth, xp): xp.asarray(hierarchy_test_data.inconsistent_ytdist[depth])) -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(cophenet) class TestCopheneticDistance: def test_linkage_cophenet_tdist_Z(self, xp): @@ -233,6 +213,7 @@ def test_linkage_cophenet_tdist_Z_Y(self, xp): xp_assert_close(c, expectedc, atol=1e-10) xp_assert_close(M, expectedM, atol=1e-10) + @skip_xp_backends("jax.numpy", reason="Can't raise inside jax.pure_callback") def test_gh_22183(self, xp): # check for lack of segfault # (out of bounds memory access) @@ -253,6 +234,7 @@ def test_gh_22183(self, xp): cophenet(xp.asarray(arr)) +@make_xp_test_case(from_mlab_linkage, to_mlab_linkage) class TestMLabLinkageConversion: def test_mlab_linkage_conversion_empty(self, xp): @@ -261,7 +243,6 @@ def test_mlab_linkage_conversion_empty(self, xp): xp_assert_equal(from_mlab_linkage(X), X) xp_assert_equal(to_mlab_linkage(X), X) - @skip_xp_backends(cpu_only=True) def test_mlab_linkage_conversion_single_row(self, xp): # Tests from/to_mlab_linkage on linkage array with single row. Z = xp.asarray([[0., 1., 3., 2.]]) @@ -271,7 +252,6 @@ def test_mlab_linkage_conversion_single_row(self, xp): xp_assert_close(to_mlab_linkage(Z), xp.asarray(Zm, dtype=xp.float64), rtol=1e-15) - @skip_xp_backends(cpu_only=True) def test_mlab_linkage_conversion_multiple_rows(self, xp): # Tests from/to_mlab_linkage on linkage array with multiple rows. Zm = xp.asarray([[3, 6, 138], [4, 5, 219], @@ -287,57 +267,56 @@ def test_mlab_linkage_conversion_multiple_rows(self, xp): rtol=1e-15) -@skip_xp_backends(cpu_only=True) -class TestFcluster: - - def test_fclusterdata(self, xp): - for t in hierarchy_test_data.fcluster_inconsistent: - self.check_fclusterdata(t, 'inconsistent', xp) - for t in hierarchy_test_data.fcluster_distance: - self.check_fclusterdata(t, 'distance', xp) - for t in hierarchy_test_data.fcluster_maxclust: - self.check_fclusterdata(t, 'maxclust', xp) +@make_xp_test_case(fclusterdata) +class TestFclusterData: - def check_fclusterdata(self, t, criterion, xp): + @make_xp_test_case(is_isomorphic) + @pytest.mark.parametrize("criterion,t", + [("inconsistent", t) for t in hierarchy_test_data.fcluster_inconsistent] + + [("distance", t) for t in hierarchy_test_data.fcluster_distance] + + [("maxclust", t) for t in hierarchy_test_data.fcluster_maxclust] + ) + def test_fclusterdata(self, t, criterion, xp): # Tests fclusterdata(X, criterion=criterion, t=t) on a random 3-cluster data set expectedT = xp.asarray(getattr(hierarchy_test_data, 'fcluster_' + criterion)[t]) X = xp.asarray(hierarchy_test_data.Q_X) T = fclusterdata(X, criterion=criterion, t=t) - assert_(is_isomorphic(T, expectedT)) + assert is_isomorphic(T, expectedT) + - def test_fcluster(self, xp): - for t in hierarchy_test_data.fcluster_inconsistent: - self.check_fcluster(t, 'inconsistent', xp) - for t in hierarchy_test_data.fcluster_distance: - self.check_fcluster(t, 'distance', xp) - for t in hierarchy_test_data.fcluster_maxclust: - self.check_fcluster(t, 'maxclust', xp) +@make_xp_test_case(fcluster) +class TestFcluster: - def check_fcluster(self, t, criterion, xp): + @make_xp_test_case(single, is_isomorphic) + @pytest.mark.parametrize("criterion,t", + [("inconsistent", t) for t in hierarchy_test_data.fcluster_inconsistent] + + [("distance", t) for t in hierarchy_test_data.fcluster_distance] + + [("maxclust", t) for t in hierarchy_test_data.fcluster_maxclust] + ) + def test_fcluster(self, t, criterion, xp): # Tests fcluster(Z, criterion=criterion, t=t) on a random 3-cluster data set. expectedT = xp.asarray(getattr(hierarchy_test_data, 'fcluster_' + criterion)[t]) Z = single(xp.asarray(hierarchy_test_data.Q_X)) T = fcluster(Z, criterion=criterion, t=t) assert_(is_isomorphic(T, expectedT)) - def test_fcluster_monocrit(self, xp): - for t in hierarchy_test_data.fcluster_distance: - self.check_fcluster_monocrit(t, xp) - for t in hierarchy_test_data.fcluster_maxclust: - self.check_fcluster_maxclust_monocrit(t, xp) - - def check_fcluster_monocrit(self, t, xp): + @make_xp_test_case(single, is_isomorphic, maxdists) + @pytest.mark.parametrize("t", hierarchy_test_data.fcluster_distance) + def test_fcluster_monocrit(self, t, xp): expectedT = xp.asarray(hierarchy_test_data.fcluster_distance[t]) Z = single(xp.asarray(hierarchy_test_data.Q_X)) T = fcluster(Z, t, criterion='monocrit', monocrit=maxdists(Z)) assert_(is_isomorphic(T, expectedT)) - def check_fcluster_maxclust_monocrit(self, t, xp): + @make_xp_test_case(single, is_isomorphic, maxdists) + @pytest.mark.parametrize("t", hierarchy_test_data.fcluster_maxclust) + def test_fcluster_maxclust_monocrit(self, t, xp): expectedT = xp.asarray(hierarchy_test_data.fcluster_maxclust[t]) Z = single(xp.asarray(hierarchy_test_data.Q_X)) T = fcluster(Z, t, criterion='maxclust_monocrit', monocrit=maxdists(Z)) assert_(is_isomorphic(T, expectedT)) + @make_xp_test_case(single) def test_fcluster_maxclust_gh_12651(self, xp): y = xp.asarray([[1], [4], [5]]) Z = single(y) @@ -351,26 +330,26 @@ def test_fcluster_maxclust_gh_12651(self, xp): xp.asarray([1, 2, 3])) -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(leaders) class TestLeaders: def test_leaders_single(self, xp): # Tests leaders using a flat clustering generated by single linkage. - X = xp.asarray(hierarchy_test_data.Q_X) + X = hierarchy_test_data.Q_X Y = pdist(X) Z = linkage(Y) T = fcluster(Z, criterion='maxclust', t=3) - Lright = (xp.asarray([53, 55, 56]), xp.asarray([2, 3, 1])) + Z = xp.asarray(Z) T = xp.asarray(T, dtype=xp.int32) L = leaders(Z, T) - assert_allclose(np.concatenate(L), np.concatenate(Lright), rtol=1e-15) + expect = xp.asarray([53, 55, 56, 2, 3, 1], dtype=xp.int32) + xp_assert_close(xp.concat(L), expect, rtol=1e-15) -@skip_xp_backends(np_only=True, - reason='`is_isomorphic` only supports NumPy backend') +@make_xp_test_case(is_isomorphic) class TestIsIsomorphic: - def test_array_like(self, xp): + def test_array_like(self): assert is_isomorphic([1, 1, 1], [2, 2, 2]) assert is_isomorphic([], []) @@ -418,18 +397,18 @@ def test_is_isomorphic_4C(self, xp): assert is_isomorphic(a, b) assert is_isomorphic(b, a) - def test_is_isomorphic_5(self, xp): + @pytest.mark.parametrize("nclusters", [2, 3, 5]) + def test_is_isomorphic_5(self, nclusters, xp): # Tests is_isomorphic on test case #5 (1000 observations, 2/3/5 random # clusters, random permutation of the labeling). - for nc in [2, 3, 5]: - self.help_is_isomorphic_randperm(1000, nc, xp=xp) + self.is_isomorphic_randperm(1000, nclusters, xp=xp) - def test_is_isomorphic_6(self, xp): + @pytest.mark.parametrize("nclusters", [2, 3, 5]) + def test_is_isomorphic_6(self, nclusters, xp): # Tests is_isomorphic on test case #5A (1000 observations, 2/3/5 random # clusters, random permutation of the labeling, slightly # nonisomorphic.) - for nc in [2, 3, 5]: - self.help_is_isomorphic_randperm(1000, nc, True, 5, xp=xp) + self.is_isomorphic_randperm(1000, nclusters, True, 5, xp=xp) def test_is_isomorphic_7(self, xp): # Regression test for gh-6271 @@ -437,9 +416,8 @@ def test_is_isomorphic_7(self, xp): b = xp.asarray([1, 1, 1]) assert not is_isomorphic(a, b) - def help_is_isomorphic_randperm(self, nobs, nclusters, noniso=False, nerrors=0, - *, xp): - for k in range(3): + def is_isomorphic_randperm(self, nobs, nclusters, noniso=False, nerrors=0, *, xp): + for _ in range(3): a = (np.random.rand(nobs) * nclusters).astype(int) b = np.zeros(a.size, dtype=int) P = np.random.permutation(nclusters) @@ -449,10 +427,13 @@ def help_is_isomorphic_randperm(self, nobs, nclusters, noniso=False, nerrors=0, Q = np.random.permutation(nobs) b[Q[0:nerrors]] += 1 b[Q[0:nerrors]] %= nclusters + a = xp.asarray(a) + b = xp.asarray(b) assert is_isomorphic(a, b) == (not noniso) assert is_isomorphic(b, a) == (not noniso) +@make_xp_test_case(is_valid_linkage) class TestIsValidLinkage: @pytest.mark.parametrize("nrow, ncol, valid", [(2, 5, False), (2, 3, False), @@ -479,72 +460,65 @@ def test_is_valid_linkage_empty(self, xp): xp_assert_equal(is_valid_linkage(Z), False, check_namespace=False) assert_raises(ValueError, is_valid_linkage, Z, throw=True) - @use_linkage def test_is_valid_linkage_4_and_up(self, xp): # Tests is_valid_linkage(Z) on linkage on observation sets between # sizes 4 and 15 (step size 3). for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) + Z = xp.asarray(linkage(y)) y = xp.asarray(y) - Z = linkage(y) xp_assert_equal(is_valid_linkage(Z), True, check_namespace=False) - @use_linkage def test_is_valid_linkage_4_and_up_neg_index_left(self, xp): # Tests is_valid_linkage(Z) on linkage on observation sets between # sizes 4 and 15 (step size 3) with negative indices (left). for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) + Z = xp.asarray(linkage(y)) y = xp.asarray(y) - Z = linkage(y) Z = xpx.at(Z)[i//2, 0].set(-2) xp_assert_equal(is_valid_linkage(Z), False, check_namespace=False) - # Use fully-qualified function name to bypass lazy_xp_function(), - # because `is_valid_*` materializes. with pytest.raises(ValueError): - scipy.cluster.hierarchy.is_valid_linkage(Z, throw=True) + eager.is_valid_linkage(Z, throw=True) - @use_linkage def test_is_valid_linkage_4_and_up_neg_index_right(self, xp): # Tests is_valid_linkage(Z) on linkage on observation sets between # sizes 4 and 15 (step size 3) with negative indices (right). for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) + Z = xp.asarray(linkage(y)) y = xp.asarray(y) - Z = linkage(y) Z = xpx.at(Z)[i//2, 1].set(-2) xp_assert_equal(is_valid_linkage(Z), False, check_namespace=False) with pytest.raises(ValueError): - scipy.cluster.hierarchy.is_valid_linkage(Z, throw=True) - + eager.is_valid_linkage(Z, throw=True) - @use_linkage def test_is_valid_linkage_4_and_up_neg_dist(self, xp): # Tests is_valid_linkage(Z) on linkage on observation sets between # sizes 4 and 15 (step size 3) with negative distances. for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) + Z = xp.asarray(linkage(y)) y = xp.asarray(y) - Z = linkage(y) Z = xpx.at(Z)[i//2, 2].set(-0.5) xp_assert_equal(is_valid_linkage(Z), False, check_namespace=False) with pytest.raises(ValueError): - scipy.cluster.hierarchy.is_valid_linkage(Z, throw=True) + eager.is_valid_linkage(Z, throw=True) - @use_linkage def test_is_valid_linkage_4_and_up_neg_counts(self, xp): # Tests is_valid_linkage(Z) on linkage on observation sets between # sizes 4 and 15 (step size 3) with negative counts. for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) + Z = xp.asarray(linkage(y)) y = xp.asarray(y) - Z = linkage(y) Z = xpx.at(Z)[i//2, 3].set(-2) xp_assert_equal(is_valid_linkage(Z), False, check_namespace=False) with pytest.raises(ValueError): - scipy.cluster.hierarchy.is_valid_linkage(Z, throw=True) + eager.is_valid_linkage(Z, throw=True) +@make_xp_test_case(is_valid_im) class TestIsValidInconsistent: def test_is_valid_im_int_type(self, xp): @@ -571,60 +545,54 @@ def test_is_valid_im_empty(self, xp): xp_assert_equal(is_valid_im(R), False, check_namespace=False) assert_raises(ValueError, is_valid_im, R, throw=True) - @use_linkage def test_is_valid_im_4_and_up(self, xp): # Tests is_valid_im(R) on im on observation sets between sizes 4 and 15 # (step size 3). for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) - y = xp.asarray(y) Z = linkage(y) R = inconsistent(Z) + R = xp.asarray(R) xp_assert_equal(is_valid_im(R), True, check_namespace=False) - @use_linkage def test_is_valid_im_4_and_up_neg_index_left(self, xp): # Tests is_valid_im(R) on im on observation sets between sizes 4 and 15 # (step size 3) with negative link height means. for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) - y = xp.asarray(y) Z = linkage(y) R = inconsistent(Z) R = xpx.at(R)[i//2 , 0].set(-2.0) + R = xp.asarray(R) xp_assert_equal(is_valid_im(R), False, check_namespace=False) - # Use fully-qualified function name to bypass lazy_xp_function(), - # because `is_valid_*`materializes. with pytest.raises(ValueError): - scipy.cluster.hierarchy.is_valid_im(R, throw=True) + eager.is_valid_im(R, throw=True) - @use_linkage def test_is_valid_im_4_and_up_neg_index_right(self, xp): # Tests is_valid_im(R) on im on observation sets between sizes 4 and 15 # (step size 3) with negative link height standard deviations. for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) - y = xp.asarray(y) Z = linkage(y) R = inconsistent(Z) R = xpx.at(R)[i//2 , 1].set(-2.0) + R = xp.asarray(R) xp_assert_equal(is_valid_im(R), False, check_namespace=False) with pytest.raises(ValueError): - scipy.cluster.hierarchy.is_valid_im(R, throw=True) + eager.is_valid_im(R, throw=True) - @use_linkage def test_is_valid_im_4_and_up_neg_dist(self, xp): # Tests is_valid_im(R) on im on observation sets between sizes 4 and 15 # (step size 3) with negative link counts. for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) - y = xp.asarray(y) Z = linkage(y) R = inconsistent(Z) R = xpx.at(R)[i//2, 2].set(-0.5) + R = xp.asarray(R) xp_assert_equal(is_valid_im(R), False, check_namespace=False) with pytest.raises(ValueError): - scipy.cluster.hierarchy.is_valid_im(R, throw=True) + eager.is_valid_im(R, throw=True) class TestNumObsLinkage: @@ -645,27 +613,24 @@ def test_num_obs_linkage_2x4(self, xp): [3, 2, 4.0, 3]], dtype=xp.float64) assert num_obs_linkage(Z) == 3 - @use_linkage def test_num_obs_linkage_4_and_up(self, xp): # Tests num_obs_linkage(Z) on linkage on observation sets between sizes # 4 and 15 (step size 3). for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) - y = xp.asarray(y) - Z = linkage(y) + Z = xp.asarray(linkage(y)) assert num_obs_linkage(Z) == i - @use_linkage def test_num_obs_linkage_multi_matrix(self, xp): # Tests num_obs_linkage with observation matrices of multiple sizes. for n in range(2, 10): - X = xp.asarray(np.random.rand(n, 4)) + X = np.random.rand(n, 4) Y = pdist(X) - Z = linkage(Y) + Z = xp.asarray(linkage(Y)) assert num_obs_linkage(Z) == n -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(leaves_list, to_tree) class TestLeavesList: def test_leaves_list_1x4(self, xp): @@ -681,28 +646,26 @@ def test_leaves_list_2x4(self, xp): to_tree(Z) assert_allclose(leaves_list(Z), [0, 1, 2], rtol=1e-15) - def test_leaves_list_Q(self, xp): - for method in ['single', 'complete', 'average', 'weighted', 'centroid', - 'median', 'ward']: - self.check_leaves_list_Q(method, xp) - - def check_leaves_list_Q(self, method, xp): + @pytest.mark.parametrize("method", + ['single', 'complete', 'average', 'weighted', 'centroid', 'median', 'ward']) + def test_leaves_list_Q(self, method, xp): # Tests leaves_list(Z) on the Q data set - X = xp.asarray(hierarchy_test_data.Q_X) - Z = linkage(X, method) + X = hierarchy_test_data.Q_X + Z = xp.asarray(linkage(X, method)) node = to_tree(Z) assert_allclose(node.pre_order(), leaves_list(Z), rtol=1e-15) def test_Q_subtree_pre_order(self, xp): # Tests that pre_order() works when called on sub-trees. - X = xp.asarray(hierarchy_test_data.Q_X) - Z = linkage(X, 'single') + X = hierarchy_test_data.Q_X + Z = xp.asarray(linkage(X, 'single')) node = to_tree(Z) - assert_allclose(node.pre_order(), (node.get_left().pre_order() - + node.get_right().pre_order()), + assert_allclose(node.pre_order(), + (node.get_left().pre_order() + node.get_right().pre_order()), rtol=1e-15) +@make_xp_test_case(correspond) class TestCorrespond: def test_correspond_empty(self, xp): @@ -711,22 +674,20 @@ def test_correspond_empty(self, xp): Z = xp.zeros((0,4), dtype=xp.float64) assert_raises(ValueError, correspond, Z, y) - @use_linkage def test_correspond_2_and_up(self, xp): # Tests correspond(Z, y) on linkage and CDMs over observation sets of # different sizes. for i in range(2, 4): y = np.random.rand(i*(i-1)//2) + Z = xp.asarray(linkage(y)) y = xp.asarray(y) - Z = linkage(y) assert_(correspond(Z, y)) for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) + Z = xp.asarray(linkage(y)) y = xp.asarray(y) - Z = linkage(y) assert_(correspond(Z, y)) - @use_linkage def test_correspond_4_and_up(self, xp): # Tests correspond(Z, y) on linkage and CDMs over observation sets of # different sizes. Correspondence should be false. @@ -734,14 +695,13 @@ def test_correspond_4_and_up(self, xp): list(zip(list(range(3, 5)), list(range(2, 4))))): y = np.random.rand(i*(i-1)//2) y2 = np.random.rand(j*(j-1)//2) + Z = xp.asarray(linkage(y)) + Z2 = xp.asarray(linkage(y2)) y = xp.asarray(y) y2 = xp.asarray(y2) - Z = linkage(y) - Z2 = linkage(y2) assert not correspond(Z, y2) assert not correspond(Z2, y) - @use_linkage def test_correspond_4_and_up_2(self, xp): # Tests correspond(Z, y) on linkage and CDMs over observation sets of # different sizes. Correspondence should be false. @@ -749,14 +709,15 @@ def test_correspond_4_and_up_2(self, xp): list(zip(list(range(2, 7)), list(range(16, 21))))): y = np.random.rand(i*(i-1)//2) y2 = np.random.rand(j*(j-1)//2) + Z = xp.asarray(linkage(y)) + Z2 = xp.asarray(linkage(y2)) y = xp.asarray(y) y2 = xp.asarray(y2) - Z = linkage(y) - Z2 = linkage(y2) assert not correspond(Z, y2) assert not correspond(Z2, y) +@make_xp_test_case(is_monotonic) class TestIsMonotonic: def test_is_monotonic_empty(self, xp): @@ -809,31 +770,28 @@ def test_is_monotonic_3x4_F3(self, xp): [4, 5, 0.2, 4]], dtype=xp.float64) assert not is_monotonic(Z) - @use_linkage def test_is_monotonic_tdist_linkage1(self, xp): # Tests is_monotonic(Z) on clustering generated by single linkage on # tdist data set. Expecting True. - Z = linkage(xp.asarray(hierarchy_test_data.ytdist), 'single') + Z = xp.asarray(linkage(hierarchy_test_data.ytdist, 'single')) assert is_monotonic(Z) - @use_linkage def test_is_monotonic_tdist_linkage2(self, xp): # Tests is_monotonic(Z) on clustering generated by single linkage on # tdist data set. Perturbing. Expecting False. - Z = linkage(xp.asarray(hierarchy_test_data.ytdist), 'single') + Z = xp.asarray(linkage(hierarchy_test_data.ytdist, 'single')) Z = xpx.at(Z)[2, 2].set(0.0) assert not is_monotonic(Z) - @use_linkage def test_is_monotonic_Q_linkage(self, xp): # Tests is_monotonic(Z) on clustering generated by single linkage on # Q data set. Expecting True. - X = xp.asarray(hierarchy_test_data.Q_X) - Z = linkage(X, 'single') + X = hierarchy_test_data.Q_X + Z = xp.asarray(linkage(X, 'single')) assert is_monotonic(Z) -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(maxdists) class TestMaxDists: def test_maxdists_empty_linkage(self, xp): @@ -848,22 +806,20 @@ def test_maxdists_one_cluster_linkage(self, xp): expectedMD = calculate_maximum_distances(Z, xp) xp_assert_close(MD, expectedMD, atol=1e-15) - def test_maxdists_Q_linkage(self, xp): - for method in ['single', 'complete', 'ward', 'centroid', 'median']: - self.check_maxdists_Q_linkage(method, xp) - - def check_maxdists_Q_linkage(self, method, xp): + @pytest.mark.parametrize( + "method", ['single', 'complete', 'ward', 'centroid', 'median']) + def test_maxdists_Q_linkage(self, method, xp): # Tests maxdists(Z) on the Q data set - X = xp.asarray(hierarchy_test_data.Q_X) - Z = linkage(X, method) + X = hierarchy_test_data.Q_X + Z = xp.asarray(linkage(X, method)) MD = maxdists(Z) expectedMD = calculate_maximum_distances(Z, xp) xp_assert_close(MD, expectedMD, atol=1e-15) +@make_xp_test_case(maxinconsts) class TestMaxInconsts: - @skip_xp_backends(cpu_only=True) def test_maxinconsts_empty_linkage(self, xp): # Tests maxinconsts(Z, R) on empty linkage. Expecting exception. Z = xp.zeros((0, 4), dtype=xp.float64) @@ -878,7 +834,6 @@ def test_maxinconsts_difrow_linkage(self, xp): R = xp.asarray(R) assert_raises(ValueError, maxinconsts, Z, R) - @skip_xp_backends(cpu_only=True, reason="implicit device->host transfer") def test_maxinconsts_one_cluster_linkage(self, xp): # Tests maxinconsts(Z, R) on linkage with one cluster. Z = xp.asarray([[0, 1, 0.3, 4]], dtype=xp.float64) @@ -887,52 +842,42 @@ def test_maxinconsts_one_cluster_linkage(self, xp): expectedMD = calculate_maximum_inconsistencies(Z, R, xp=xp) xp_assert_close(MD, expectedMD, atol=1e-15) - @skip_xp_backends(cpu_only=True, reason="implicit device->host transfer") - def test_maxinconsts_Q_linkage(self, xp): - for method in ['single', 'complete', 'ward', 'centroid', 'median']: - self.check_maxinconsts_Q_linkage(method, xp) - - def check_maxinconsts_Q_linkage(self, method, xp): + @pytest.mark.parametrize( + "method", ['single', 'complete', 'ward', 'centroid', 'median']) + def test_maxinconsts_Q_linkage(self, method, xp): # Tests maxinconsts(Z, R) on the Q data set - X = xp.asarray(hierarchy_test_data.Q_X) + X = hierarchy_test_data.Q_X Z = linkage(X, method) - R = inconsistent(Z) + R = xp.asarray(inconsistent(Z)) + Z = xp.asarray(Z) MD = maxinconsts(Z, R) expectedMD = calculate_maximum_inconsistencies(Z, R, xp=xp) xp_assert_close(MD, expectedMD, atol=1e-15) +@make_xp_test_case(maxRstat) class TestMaxRStat: def test_maxRstat_invalid_index(self, xp): - for i in [3.3, -1, 4]: - self.check_maxRstat_invalid_index(i, xp) - - def check_maxRstat_invalid_index(self, i, xp): # Tests maxRstat(Z, R, i). Expecting exception. Z = xp.asarray([[0, 1, 0.3, 4]], dtype=xp.float64) R = xp.asarray([[0, 0, 0, 0.3]], dtype=xp.float64) - if isinstance(i, int): - assert_raises(ValueError, maxRstat, Z, R, i) - else: - assert_raises(TypeError, maxRstat, Z, R, i) - - @skip_xp_backends(cpu_only=True) - def test_maxRstat_empty_linkage(self, xp): - for i in range(4): - self.check_maxRstat_empty_linkage(i, xp) - - def check_maxRstat_empty_linkage(self, i, xp): + with pytest.raises(TypeError): + maxRstat(Z, R, 3.3) + with pytest.raises(ValueError): + maxRstat(Z, R, -1) + with pytest.raises(ValueError): + maxRstat(Z, R, 4) + + @pytest.mark.parametrize("i", range(4)) + def test_maxRstat_empty_linkage(self, i, xp): # Tests maxRstat(Z, R, i) on empty linkage. Expecting exception. Z = xp.zeros((0, 4), dtype=xp.float64) R = xp.zeros((0, 4), dtype=xp.float64) assert_raises(ValueError, maxRstat, Z, R, i) - def test_maxRstat_difrow_linkage(self, xp): - for i in range(4): - self.check_maxRstat_difrow_linkage(i, xp) - - def check_maxRstat_difrow_linkage(self, i, xp): + @pytest.mark.parametrize("i", range(4)) + def test_maxRstat_difrow_linkage(self, i, xp): # Tests maxRstat(Z, R, i) on linkage and inconsistency matrices with # different numbers of clusters. Expecting exception. Z = xp.asarray([[0, 1, 0.3, 4]], dtype=xp.float64) @@ -940,12 +885,7 @@ def check_maxRstat_difrow_linkage(self, i, xp): R = xp.asarray(R) assert_raises(ValueError, maxRstat, Z, R, i) - @skip_xp_backends(cpu_only=True, reason="implicit device->host transfer") def test_maxRstat_one_cluster_linkage(self, xp): - for i in range(4): - self.check_maxRstat_one_cluster_linkage(i, xp) - - def check_maxRstat_one_cluster_linkage(self, i, xp): # Tests maxRstat(Z, R, i) on linkage with one cluster. Z = xp.asarray([[0, 1, 0.3, 4]], dtype=xp.float64) R = xp.asarray([[0, 0, 0, 0.3]], dtype=xp.float64) @@ -953,39 +893,36 @@ def check_maxRstat_one_cluster_linkage(self, i, xp): expectedMD = calculate_maximum_inconsistencies(Z, R, 1, xp) xp_assert_close(MD, expectedMD, atol=1e-15) - @skip_xp_backends(cpu_only=True, reason="implicit device->host transfer") - def test_maxRstat_Q_linkage(self, xp): - for method in ['single', 'complete', 'ward', 'centroid', 'median']: - for i in range(4): - self.check_maxRstat_Q_linkage(method, i, xp) - - def check_maxRstat_Q_linkage(self, method, i, xp): - # Tests maxRstat(Z, R, i) on the Q data set - X = xp.asarray(hierarchy_test_data.Q_X) + @pytest.mark.parametrize( + "method", ['single', 'complete', 'ward', 'centroid', 'median']) + def test_maxRstat_Q_linkage(self, method, xp): + # Tests maxRstat(Z, R, 1) on the Q data set + X = hierarchy_test_data.Q_X Z = linkage(X, method) - R = inconsistent(Z) + R = xp.asarray(inconsistent(Z)) + Z = xp.asarray(Z) MD = maxRstat(Z, R, 1) expectedMD = calculate_maximum_inconsistencies(Z, R, 1, xp) xp_assert_close(MD, expectedMD, atol=1e-15) -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(dendrogram) class TestDendrogram: def test_dendrogram_single_linkage_tdist(self, xp): # Tests dendrogram calculation on single linkage of the tdist data set. - Z = linkage(xp.asarray(hierarchy_test_data.ytdist), 'single') + Z = xp.asarray(linkage(hierarchy_test_data.ytdist, 'single')) R = dendrogram(Z, no_plot=True) leaves = R["leaves"] assert_equal(leaves, [2, 5, 1, 0, 3, 4]) def test_valid_orientation(self, xp): - Z = linkage(xp.asarray(hierarchy_test_data.ytdist), 'single') + Z = xp.asarray(linkage(hierarchy_test_data.ytdist, 'single')) assert_raises(ValueError, dendrogram, Z, orientation="foo") def test_labels_as_array_or_list(self, xp): # test for gh-12418 - Z = linkage(xp.asarray(hierarchy_test_data.ytdist), 'single') + Z = xp.asarray(linkage(hierarchy_test_data.ytdist, 'single')) labels = [1, 3, 2, 6, 4, 5] result1 = dendrogram(Z, labels=xp.asarray(labels), no_plot=True) result2 = dendrogram(Z, labels=labels, no_plot=True) @@ -1019,13 +956,10 @@ def test_valid_label_size(self, xp): reason='dask.array has bad interaction with matplotlib' ) @pytest.mark.skipif(not have_matplotlib, reason="no matplotlib") - def test_dendrogram_plot(self, xp): - for orientation in ['top', 'bottom', 'left', 'right']: - self.check_dendrogram_plot(orientation, xp) - - def check_dendrogram_plot(self, orientation, xp): + @pytest.mark.parametrize("orientation", ['top', 'bottom', 'left', 'right']) + def test_dendrogram_plot(self, orientation, xp): # Tests dendrogram plotting. - Z = linkage(xp.asarray(hierarchy_test_data.ytdist), 'single') + Z = xp.asarray(linkage(hierarchy_test_data.ytdist, 'single')) expected = {'color_list': ['C1', 'C0', 'C0', 'C0', 'C0'], 'dcoord': [[0.0, 138.0, 138.0, 0.0], [0.0, 219.0, 219.0, 0.0], @@ -1094,7 +1028,7 @@ def check_dendrogram_plot(self, orientation, xp): ) @pytest.mark.skipif(not have_matplotlib, reason="no matplotlib") def test_dendrogram_truncate_mode(self, xp): - Z = linkage(xp.asarray(hierarchy_test_data.ytdist), 'single') + Z = xp.asarray(linkage(hierarchy_test_data.ytdist, 'single')) R = dendrogram(Z, 2, 'lastp', show_contracted=True) plt.close() @@ -1130,13 +1064,13 @@ def dendrogram_lock(self): def test_dendrogram_colors(self, xp, dendrogram_lock): # Tests dendrogram plots with alternate colors - Z = linkage(xp.asarray(hierarchy_test_data.ytdist), 'single') + Z = xp.asarray(linkage(hierarchy_test_data.ytdist, 'single')) with dendrogram_lock: # Global color palette might be changed concurrently set_link_color_palette(['c', 'm', 'y', 'k']) R = dendrogram(Z, no_plot=True, - above_threshold_color='g', color_threshold=250) + above_threshold_color='g', color_threshold=250) set_link_color_palette(['g', 'r', 'c', 'm', 'y', 'k']) color_list = R['color_list'] @@ -1148,14 +1082,14 @@ def test_dendrogram_colors(self, xp, dendrogram_lock): def test_dendrogram_leaf_colors_zero_dist(self, xp): # tests that the colors of leafs are correct for tree # with two identical points - x = xp.asarray([[1, 0, 0], + X = np.asarray([[1, 0, 0], [0, 0, 1], [0, 2, 0], [0, 0, 1], [0, 1, 0], [0, 1, 0]]) - z = linkage(x, "single") - d = dendrogram(z, no_plot=True) + Z = xp.asarray(linkage(X, "single")) + d = dendrogram(Z, no_plot=True) exp_colors = ['C0', 'C1', 'C1', 'C0', 'C2', 'C2'] colors = d["leaves_color_list"] assert_equal(colors, exp_colors) @@ -1163,14 +1097,14 @@ def test_dendrogram_leaf_colors_zero_dist(self, xp): def test_dendrogram_leaf_colors(self, xp): # tests that the colors are correct for a tree # with two near points ((0, 0, 1.1) and (0, 0, 1)) - x = xp.asarray([[1, 0, 0], + X = np.asarray([[1, 0, 0], [0, 0, 1.1], [0, 2, 0], [0, 0, 1], [0, 1, 0], [0, 1, 0]]) - z = linkage(x, "single") - d = dendrogram(z, no_plot=True) + Z = xp.asarray(linkage(X, "single")) + d = dendrogram(Z, no_plot=True) exp_colors = ['C0', 'C1', 'C1', 'C0', 'C2', 'C2'] colors = d["leaves_color_list"] assert_equal(colors, exp_colors) @@ -1215,33 +1149,12 @@ def calculate_maximum_inconsistencies(Z, R, k=3, xp=np): return B -@pytest.mark.thread_unsafe -@use_linkage -@skip_xp_backends(eager_only=True) -def test_unsupported_uncondensed_distance_matrix_linkage_warning(xp): - assert_warns(ClusterWarning, linkage, xp.asarray([[0, 1], [1, 0]])) - - -def test_euclidean_linkage_value_error(xp): - for method in _EUCLIDEAN_METHODS: - assert_raises(ValueError, linkage, xp.asarray([[1, 1], [1, 1]]), - method=method, metric='cityblock') - - -@use_linkage -def test_2x2_linkage(xp): - Z1 = linkage(xp.asarray([1]), method='single', metric='euclidean') - Z2 = linkage(xp.asarray([[0, 1], [0, 0]]), method='single', metric='euclidean') - xp_assert_close(Z1, Z2, rtol=1e-15) - - -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(to_tree) def test_node_compare(xp): np.random.seed(23) nobs = 50 X = np.random.randn(nobs, 4) - X = xp.asarray(X) - Z = ward(X) + Z = xp.asarray(ward(X)) tree = to_tree(Z) assert_(tree > tree.get_left()) assert_(tree.get_right() > tree.get_left()) @@ -1249,13 +1162,12 @@ def test_node_compare(xp): assert_(tree.get_right() != tree.get_left()) -@skip_xp_backends(np_only=True, reason='`cut_tree` uses non-standard indexing') +@make_xp_test_case(cut_tree) def test_cut_tree(xp): np.random.seed(23) nobs = 50 X = np.random.randn(nobs, 4) - X = xp.asarray(X) - Z = ward(X) + Z = xp.asarray(ward(X)) cutree = cut_tree(Z) # cutree.dtype varies between int32 and int64 over platforms @@ -1278,16 +1190,16 @@ def test_cut_tree(xp): cut_tree(Z, height=[10, 5]), rtol=1e-15) -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(optimal_leaf_ordering) def test_optimal_leaf_ordering(xp): # test with the distance vector y - Z = optimal_leaf_ordering(linkage(xp.asarray(hierarchy_test_data.ytdist)), + Z = optimal_leaf_ordering(xp.asarray(linkage(hierarchy_test_data.ytdist)), xp.asarray(hierarchy_test_data.ytdist)) expectedZ = hierarchy_test_data.linkage_ytdist_single_olo xp_assert_close(Z, xp.asarray(expectedZ), atol=1e-10) # test with the observation matrix X - Z = optimal_leaf_ordering(linkage(xp.asarray(hierarchy_test_data.X), 'ward'), + Z = optimal_leaf_ordering(xp.asarray(linkage(hierarchy_test_data.X, 'ward')), xp.asarray(hierarchy_test_data.X)) expectedZ = hierarchy_test_data.linkage_X_ward_olo xp_assert_close(Z, xp.asarray(expectedZ), atol=1e-06) @@ -1324,13 +1236,3 @@ def test_Heap(xp): pair = heap.get_min() assert_equal(pair['key'], 1) assert_equal(pair['value'], 10) - - -@use_linkage -@skip_xp_backends("jax.numpy", reason="Can't raise inside jax.pure_callback") -def test_centroid_neg_distance(xp): - # gh-21011 - values = xp.asarray([0, 0, -1]) - with pytest.raises(ValueError): - # This is just checking that this doesn't crash - linkage(values, method='centroid') diff --git a/scipy/cluster/tests/test_vq.py b/scipy/cluster/tests/test_vq.py index ee5bc567fef1..c368aa367569 100644 --- a/scipy/cluster/tests/test_vq.py +++ b/scipy/cluster/tests/test_vq.py @@ -1,12 +1,10 @@ -import warnings +import math import sys from copy import deepcopy from threading import Lock import numpy as np -from numpy.testing import ( - assert_array_equal, assert_equal, assert_, suppress_warnings -) +from numpy.testing import assert_array_equal, suppress_warnings import pytest from pytest import raises as assert_raises @@ -17,7 +15,8 @@ from scipy._lib import array_api_extra as xpx from scipy._lib._array_api import ( - SCIPY_ARRAY_API, xp_copy, xp_assert_close, xp_assert_equal + SCIPY_ARRAY_API, eager_warns, is_lazy_array, make_xp_test_case, + xp_copy, xp_assert_close, xp_assert_equal ) xfail_xp_backends = pytest.mark.xfail_xp_backends @@ -79,6 +78,7 @@ LABEL1 = np.array([0, 1, 2, 2, 2, 2, 1, 2, 1, 1, 1]) +@make_xp_test_case(whiten) class TestWhiten: def test_whiten(self, xp): @@ -95,11 +95,7 @@ def test_whiten(self, xp): [0.45067590, 0.45464607]]) xp_assert_close(whiten(obs), desired, rtol=1e-5) - @pytest.fixture - def whiten_lock(self): - return Lock() - - def test_whiten_zero_std(self, xp, whiten_lock): + def test_whiten_zero_std(self, xp): desired = xp.asarray([[0., 1.0, 2.86666544], [0., 1.0, 1.32460034], [0., 1.0, 3.74382172]]) @@ -108,27 +104,32 @@ def test_whiten_zero_std(self, xp, whiten_lock): [0., 1., 0.34243798], [0., 1., 0.96785929]]) - with whiten_lock: - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - - xp_assert_close(whiten(obs), desired, rtol=1e-5) + with eager_warns(obs, RuntimeWarning, match="standard deviation zero"): + actual = whiten(obs) + xp_assert_close(actual, desired, rtol=1e-5) - assert_equal(len(w), 1) - assert_(issubclass(w[-1].category, RuntimeWarning)) + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + @pytest.mark.parametrize("bad_value", [math.nan, math.inf, -math.inf]) + def test_whiten_not_finite(self, bad_value, xp): + obs = xp.asarray([[0.98744510, bad_value], + [0.62093317, 0.19406729], + [0.87545741, 0.00735733], + [0.85124403, 0.26499712], + [0.45067590, 0.45464607]]) - def test_whiten_not_finite(self, xp): - for bad_value in xp.nan, xp.inf, -xp.inf: - obs = xp.asarray([[0.98744510, bad_value], - [0.62093317, 0.19406729], - [0.87545741, 0.00735733], - [0.85124403, 0.26499712], - [0.45067590, 0.45464607]]) + if is_lazy_array(obs): + desired = xp.asarray([[5.08738849, math.nan], + [3.19909255, math.nan], + [4.51041982, math.nan], + [4.38567074, math.nan], + [2.32191480, math.nan]]) + xp_assert_close(whiten(obs), desired, rtol=1e-5) + else: assert_raises(ValueError, whiten, obs) @pytest.mark.skipif(SCIPY_ARRAY_API, reason='`np.matrix` unsupported in array API mode') - def test_whiten_not_finite_matrix(self, xp): + def test_whiten_not_finite_matrix(self): for bad_value in np.nan, np.inf, -np.inf: obs = matrix([[0.98744510, bad_value], [0.62093317, 0.19406729], @@ -138,9 +139,9 @@ def test_whiten_not_finite_matrix(self, xp): assert_raises(ValueError, whiten, obs) +@make_xp_test_case(vq) class TestVq: - @skip_xp_backends(cpu_only=True) def test_py_vq(self, xp): initc = np.concatenate([[X[0]], [X[1]], [X[2]]]) # label1.dtype varies between int32 and int64 over platforms @@ -150,28 +151,26 @@ def test_py_vq(self, xp): @pytest.mark.skipif(SCIPY_ARRAY_API, reason='`np.matrix` unsupported in array API mode') - def test_py_vq_matrix(self, xp): + def test_py_vq_matrix(self): initc = np.concatenate([[X[0]], [X[1]], [X[2]]]) # label1.dtype varies between int32 and int64 over platforms label1 = py_vq(matrix(X), matrix(initc))[0] assert_array_equal(label1, LABEL1) - @skip_xp_backends(np_only=True, reason='`_vq` only supports NumPy backend') def test_vq(self, xp): initc = np.concatenate([[X[0]], [X[1]], [X[2]]]) - label1, _ = _vq.vq(xp.asarray(X), xp.asarray(initc)) + label1, _ = _vq.vq(X, initc) assert_array_equal(label1, LABEL1) _, _ = vq(xp.asarray(X), xp.asarray(initc)) @pytest.mark.skipif(SCIPY_ARRAY_API, reason='`np.matrix` unsupported in array API mode') - def test_vq_matrix(self, xp): + def test_vq_matrix(self): initc = np.concatenate([[X[0]], [X[1]], [X[2]]]) label1, _ = _vq.vq(matrix(X), matrix(initc)) assert_array_equal(label1, LABEL1) _, _ = vq(matrix(X), matrix(initc)) - @skip_xp_backends(cpu_only=True) def test_vq_1d(self, xp): # Test special rank 1 vq algo, python implementation. data = X[:, 0] @@ -184,18 +183,15 @@ def test_vq_1d(self, xp): xp_assert_equal(ta, xp.asarray(a, dtype=xp.int64), check_dtype=False) xp_assert_equal(tb, xp.asarray(b)) - @skip_xp_backends(np_only=True, reason='`_vq` only supports NumPy backend') - def test__vq_sametype(self, xp): - a = xp.asarray([1.0, 2.0], dtype=xp.float64) - b = a.astype(xp.float32) + def test__vq_sametype(self): + a = np.asarray([1.0, 2.0]) + b = a.astype(np.float32) assert_raises(TypeError, _vq.vq, a, b) - @skip_xp_backends(np_only=True, reason='`_vq` only supports NumPy backend') - def test__vq_invalid_type(self, xp): - a = xp.asarray([1, 2], dtype=int) + def test__vq_invalid_type(self): + a = np.asarray([1, 2], dtype=int) assert_raises(TypeError, _vq.vq, a, a) - @skip_xp_backends(cpu_only=True) def test_vq_large_nfeat(self, xp): X = np.random.rand(20, 20) code_book = np.random.rand(3, 20) @@ -219,7 +215,6 @@ def test_vq_large_nfeat(self, xp): # codes1.dtype varies between int32 and int64 over platforms xp_assert_equal(codes1, xp.asarray(codes0, dtype=xp.int64), check_dtype=False) - @skip_xp_backends(cpu_only=True) def test_vq_large_features(self, xp): X = np.random.rand(10, 5) * 1000000 code_book = np.random.rand(2, 5) * 1000000 @@ -235,8 +230,8 @@ def test_vq_large_features(self, xp): # Whole class skipped on GPU for now; # once pdist/cdist are hooked up for CuPy, more tests will work -@skip_xp_backends(cpu_only=True) -class TestKMean: +@make_xp_test_case(kmeans, kmeans2) +class TestKMeans: def test_large_features(self, xp): # Generate a data set with large values, and run kmeans on it to @@ -264,7 +259,7 @@ def test_kmeans_simple(self, xp): @pytest.mark.skipif(SCIPY_ARRAY_API, reason='`np.matrix` unsupported in array API mode') - def test_kmeans_simple_matrix(self, xp): + def test_kmeans_simple_matrix(self): rng = np.random.default_rng(54321) initc = np.concatenate([[X[0]], [X[1]], [X[2]]]) code1 = kmeans(matrix(X), matrix(initc), iter=1, rng=rng)[0] @@ -299,9 +294,9 @@ def test_kmeans2_simple(self, xp): @pytest.mark.skipif(SCIPY_ARRAY_API, reason='`np.matrix` unsupported in array API mode') - def test_kmeans2_simple_matrix(self, xp): + def test_kmeans2_simple_matrix(self): rng = np.random.default_rng(12345678) - initc = xp.asarray(np.concatenate([[X[0]], [X[1]], [X[2]]])) + initc = np.concatenate([[X[0]], [X[1]], [X[2]]]) code1 = kmeans2(matrix(X), matrix(initc), iter=1, rng=rng)[0] code2 = kmeans2(matrix(X), matrix(initc), iter=2, rng=rng)[0] diff --git a/scipy/cluster/vq.py b/scipy/cluster/vq.py index b21d824da67f..65debbbde3ab 100644 --- a/scipy/cluster/vq.py +++ b/scipy/cluster/vq.py @@ -67,9 +67,8 @@ import warnings import numpy as np from collections import deque -from scipy._lib._array_api import ( - _asarray, array_namespace, xp_size, xp_copy -) +from scipy._lib._array_api import (_asarray, array_namespace, is_lazy_array, + xp_capabilities, xp_copy, xp_size) from scipy._lib._util import (check_random_state, rng_integers, _transition_to_rng) from scipy._lib import array_api_extra as xpx @@ -86,7 +85,8 @@ class ClusterError(Exception): pass -def whiten(obs, check_finite=True): +@xp_capabilities() +def whiten(obs, check_finite=None): """ Normalize a group of observations on a per feature basis. @@ -100,19 +100,19 @@ def whiten(obs, check_finite=True): ---------- obs : ndarray Each row of the array is an observation. The - columns are the features seen during each observation. + columns are the features seen during each observation:: - >>> # f0 f1 f2 - >>> obs = [[ 1., 1., 1.], #o0 - ... [ 2., 2., 2.], #o1 - ... [ 3., 3., 3.], #o2 - ... [ 4., 4., 4.]] #o3 + # f0 f1 f2 + obs = [[ 1., 1., 1.], #o0 + [ 2., 2., 2.], #o1 + [ 3., 3., 3.], #o2 + [ 4., 4., 4.]] #o3 check_finite : bool, optional Whether to check that the input matrices contain only finite numbers. Disabling may give a performance gain, but may result in problems (crashes, non-termination) if the inputs do contain infinities or NaNs. - Default: True + Default: True for eager backends and False for lazy ones. Returns ------- @@ -134,6 +134,8 @@ def whiten(obs, check_finite=True): """ xp = array_namespace(obs) + if check_finite is None: + check_finite = not is_lazy_array(obs) obs = _asarray(obs, check_finite=check_finite, xp=xp) std_dev = xp.std(obs, axis=0) zero_std_mask = std_dev == 0 @@ -145,6 +147,8 @@ def whiten(obs, check_finite=True): return obs / std_dev +@xp_capabilities(cpu_only=True, reason="uses spatial.distance.cdist", + jax_jit=False, allow_dask_compute=True) def vq(obs, code_book, check_finite=True): """ Assign codes from a code book to observations. @@ -168,13 +172,12 @@ def vq(obs, code_book, check_finite=True): code_book : ndarray The code book is usually generated using the k-means algorithm. Each row of the array holds a different code, and the columns are - the features of the code. + the features of the code:: - >>> # f0 f1 f2 f3 - >>> code_book = [ - ... [ 1., 2., 3., 4.], #c0 - ... [ 1., 2., 3., 4.], #c1 - ... [ 1., 2., 3., 4.]] #c2 + # f0 f1 f2 f3 + code_book = [[ 1., 2., 3., 4.], #c0 + [ 1., 2., 3., 4.], #c1 + [ 1., 2., 3., 4.]] #c2 check_finite : bool, optional Whether to check that the input matrices contain only finite numbers. @@ -208,10 +211,9 @@ def vq(obs, code_book, check_finite=True): code_book = _asarray(code_book, xp=xp, check_finite=check_finite) ct = xp.result_type(obs, code_book) - c_obs = xp.astype(obs, ct, copy=False) - c_code_book = xp.astype(code_book, ct, copy=False) - if xp.isdtype(ct, kind='real floating'): + c_obs = xp.astype(obs, ct, copy=False) + c_code_book = xp.astype(code_book, ct, copy=False) c_obs = np.asarray(c_obs) c_code_book = np.asarray(c_code_book) result = _vq.vq(c_obs, c_code_book) @@ -327,6 +329,7 @@ def _kmeans(obs, guess, thresh=1e-5, xp=None): return code_book, prev_avg_dists[1] +@xp_capabilities(cpu_only=True, jax_jit=False, allow_dask_compute=True) @_transition_to_rng("seed") def kmeans(obs, k_or_guess, iter=20, thresh=1e-5, check_finite=True, *, rng=None): @@ -614,7 +617,7 @@ def _kpp(data, k, rng, xp): cumprobs = probs.cumsum() r = rng.uniform() cumprobs = np.asarray(cumprobs) - data_idx = np.searchsorted(cumprobs, r) + data_idx = int(np.searchsorted(cumprobs, r)) init = xpx.at(init)[i, :].set(data[data_idx, :]) @@ -642,6 +645,7 @@ def _missing_raise(): _valid_miss_meth = {'warn': _missing_warn, 'raise': _missing_raise} +@xp_capabilities(cpu_only=True, jax_jit=False, allow_dask_compute=True) @_transition_to_rng("seed") def kmeans2(data, k, iter=10, thresh=1e-5, minit='random', missing='warn', check_finite=True, *, rng=None): diff --git a/scipy/conftest.py b/scipy/conftest.py index b3fc5a96d8a5..455867380a3c 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -216,7 +216,7 @@ def num_parallel_threads(): # by default, use all available backends if ( - isinstance(SCIPY_ARRAY_API, str) + isinstance(SCIPY_ARRAY_API, str) and SCIPY_ARRAY_API.lower() not in ("1", "true", "all") ): SCIPY_ARRAY_API_ = json.loads(SCIPY_ARRAY_API) @@ -285,7 +285,7 @@ def xp(request, monkeypatch): def _backends_kwargs_from_request(request, skip_or_xfail): """A helper for {skip,xfail}_xp_backends. - + Return dict of {backend to skip/xfail: top reason to skip/xfail it} """ markers = list(request.node.iter_markers(f'{skip_or_xfail}_xp_backends')) @@ -304,21 +304,20 @@ def _backends_kwargs_from_request(request, skip_or_xfail): f"must be a subset of {list(xp_known_backends)}") if marker.kwargs.get('np_only', False): - reason = marker.kwargs.get( - "reason", "do not run with non-NumPy backends") + reason = marker.kwargs.get("reason") or "do not run with non-NumPy backends" for backend in xp_skip_np_only_backends - exceptions: reasons[backend].append(reason) elif marker.kwargs.get('cpu_only', False): - reason = marker.kwargs.get( - "reason", "no array-agnostic implementation or delegation available " - "for this backend and device") + reason = marker.kwargs.get("reason") or ( + "no array-agnostic implementation or delegation available " + "for this backend and device") for backend in xp_skip_cpu_only_backends - exceptions: reasons[backend].append(reason) elif marker.kwargs.get('eager_only', False): - reason = marker.kwargs.get( - "reason", "eager checks not executed on lazy backends") + reason = marker.kwargs.get("reason") or ( + "eager checks not executed on lazy backends") for backend in xp_skip_eager_only_backends - exceptions: reasons[backend].append(reason) @@ -328,8 +327,8 @@ def _backends_kwargs_from_request(request, skip_or_xfail): if backend not in xp_known_backends: raise ValueError(f"Unknown backend: {backend}; " f"must be one of {list(xp_known_backends)}") - reason = marker.kwargs.get( - "reason", f"do not run with array API backend: {backend}") + reason = marker.kwargs.get("reason") or ( + f"do not run with array API backend: {backend}") # reason overrides the ones from cpu_only, np_only, and eager_only. # This is regardless of order of appearence of the markers. reasons[backend].insert(0, reason) @@ -343,7 +342,7 @@ def _backends_kwargs_from_request(request, skip_or_xfail): f"Please specify only one backend per marker: {marker.args}" ) - return {backend: backend_reasons[0] + return {backend: backend_reasons[0] for backend, backend_reasons in reasons.items() if backend_reasons} @@ -403,9 +402,10 @@ def skip_or_xfail_xp_backends(request: pytest.FixtureRequest, request, skip_or_xfail=skip_or_xfail ) xp = request.param - skip_or_xfail = getattr(pytest, skip_or_xfail) - reason = skip_xfail_reasons.get(xp.__name__) - if reason: + if xp.__name__ in skip_xfail_reasons: + reason = skip_xfail_reasons[xp.__name__] + assert reason # Default reason applied above + skip_or_xfail = getattr(pytest, skip_or_xfail) skip_or_xfail(reason=reason) @@ -465,7 +465,7 @@ def test_device(xp, devices): ) # Profile is currently set by environment variable `SCIPY_HYPOTHESIS_PROFILE` -# In the future, it would be good to work the choice into dev.py. +# In the future, it would be good to work the choice into `.spin/cmds.py`. SCIPY_HYPOTHESIS_PROFILE = os.environ.get("SCIPY_HYPOTHESIS_PROFILE", "deterministic") hypothesis.settings.load_profile(SCIPY_HYPOTHESIS_PROFILE) @@ -493,14 +493,8 @@ def warnings_errors_and_rng(test=None): known_warnings[name] = dict(category=RuntimeWarning, message='divide by zero') - # Deprecated stuff in scipy.signal and elsewhere - deprecated = [ - 'scipy.signal.cwt', 'scipy.signal.morlet', 'scipy.signal.morlet2', - 'scipy.signal.ricker', - 'scipy.integrate.simpson', - 'scipy.interpolate.interp2d', - 'scipy.linalg.kron', - ] + # Deprecated stuff + deprecated = [] for name in deprecated: known_warnings[name] = dict(category=DeprecationWarning) @@ -611,12 +605,25 @@ def warnings_errors_and_rng(test=None): # equivalent to "pytest --ignore=path/to/file" "scipy/special/_precompute", "scipy/interpolate/_interpnd_info.py", + "scipy/interpolate/_rbfinterp_pythran.py", + "scipy/_build_utils/tempita.py", "scipy/_lib/array_api_compat", "scipy/_lib/highs", "scipy/_lib/unuran", "scipy/_lib/_gcutils.py", "scipy/_lib/doccer.py", "scipy/_lib/_uarray", + "scipy/linalg/_cython_signature_generator.py", + "scipy/linalg/_generate_pyx.py", + "scipy/linalg/_linalg_pythran.py", + "scipy/linalg/_matfuncs_sqrtm_triu.py", + "scipy/ndimage/utils/generate_label_testvectors.py", + "scipy/optimize/_group_columns.py", + "scipy/optimize/_max_len_seq_inner.py", + "scipy/signal/_max_len_seq_inner.py", + "scipy/sparse/_generate_sparsetools.py", + "scipy/special/_generate_pyx.py", + "scipy/stats/_stats_pythran.py", ] dt_config.pytest_extra_xfail = { diff --git a/scipy/constants/tests/test_constants.py b/scipy/constants/tests/test_constants.py index e4f40fbed4cd..672fac18884d 100644 --- a/scipy/constants/tests/test_constants.py +++ b/scipy/constants/tests/test_constants.py @@ -2,12 +2,12 @@ import scipy.constants as sc from scipy._lib._array_api_no_0d import xp_assert_equal, xp_assert_close -from scipy._lib._array_api import make_skip_xp_backends +from scipy._lib._array_api import make_xp_test_case -skip_xp_backends = pytest.mark.skip_xp_backends +lazy_xp_modules = [sc] -@make_skip_xp_backends(sc.convert_temperature) +@make_xp_test_case(sc.convert_temperature) class TestConvertTemperature: def test_convert_temperature(self, xp): xp_assert_equal(sc.convert_temperature(xp.asarray(32.), 'f', 'Celsius'), @@ -62,7 +62,7 @@ def test_convert_temperature_errors(self): sc.convert_temperature(1, old_scale="kelvin", new_scale="brie") -@make_skip_xp_backends(sc.lambda2nu) +@make_xp_test_case(sc.lambda2nu) class TestLambdaToNu: def test_lambda_to_nu(self, xp): xp_assert_equal(sc.lambda2nu(xp.asarray([sc.speed_of_light, 1])), @@ -73,7 +73,7 @@ def test_lambda_to_nu_array_like(self): xp_assert_close(sc.lambda2nu([sc.speed_of_light, 1]), [1, sc.speed_of_light]) -@make_skip_xp_backends(sc.nu2lambda) +@make_xp_test_case(sc.nu2lambda) class TestNuToLambda: def test_nu_to_lambda(self, xp): xp_assert_equal(sc.nu2lambda(xp.asarray([sc.speed_of_light, 1])), diff --git a/scipy/fft/tests/test_fftlog.py b/scipy/fft/tests/test_fftlog.py index 76c5d6630934..474decb59759 100644 --- a/scipy/fft/tests/test_fftlog.py +++ b/scipy/fft/tests/test_fftlog.py @@ -23,7 +23,7 @@ def f(r, mu): r = np.logspace(-4, 4, 16) - dln = np.log(r[1]/r[0]) + dln = math.log(r[1]/r[0]) mu = 0.3 offset = 0.0 bias = 0.0 @@ -60,6 +60,10 @@ def f(r, mu): # test 3: positive bias bias = 0.8 offset = fhtoffset(dln, mu, bias=bias) + # offset is a np.float64, which array-api-strict disallows + # even if it's technically a subclass of float + offset = float(offset) + ours = fht(a, dln, mu, offset=offset, bias=bias) theirs = [-7.3436673558316850E+00, +0.1710271207817100E+00, +0.1065374386206564E+00, -0.5121739602708132E-01, @@ -75,6 +79,8 @@ def f(r, mu): # test 4: negative bias bias = -0.8 offset = fhtoffset(dln, mu, bias=bias) + offset = float(offset) + ours = fht(a, dln, mu, offset=offset, bias=bias) theirs = [+0.8985777068568745E-05, +0.4074898209936099E-04, +0.2123969254700955E-03, +0.1009558244834628E-02, @@ -101,6 +107,9 @@ def test_fht_identity(n, bias, offset, optimal, xp): if optimal: offset = fhtoffset(dln, mu, initial=offset, bias=bias) + # offset is a np.float64, which array-api-strict disallows + # even if it's technically a subclass of float + offset = float(offset) A = fht(a, dln, mu, offset=offset, bias=bias) a_ = ifht(A, dln, mu, offset=offset, bias=bias) @@ -161,9 +170,12 @@ def test_fht_exact(n, xp): r = np.logspace(-2, 2, n) a = xp.asarray(r**gamma) - dln = np.log(r[1]/r[0]) + dln = math.log(r[1]/r[0]) offset = fhtoffset(dln, mu, initial=0.0, bias=gamma) + # offset is a np.float64, which array-api-strict disallows + # even if it's technically a subclass of float + offset = float(offset) A = fht(a, dln, mu, offset=offset, bias=gamma) diff --git a/scipy/integrate/_bvp.py b/scipy/integrate/_bvp.py index 74406c89a689..fe2e0ae50b25 100644 --- a/scipy/integrate/_bvp.py +++ b/scipy/integrate/_bvp.py @@ -402,7 +402,9 @@ def solve_newton(n, m, h, col_fun, bc, jac, y, p, B, bvp_tol, bc_tol): References ---------- .. [1] U. Ascher, R. Mattheij and R. Russell "Numerical Solution of - Boundary Value Problems for Ordinary Differential Equations" + Boundary Value Problems for Ordinary Differential Equations", + Philidelphia, PA: Society for Industrial and Applied Mathematics, + 1995. """ # We know that the solution residuals at the middle points of the mesh # are connected with collocation residuals r_middle = 1.5 * col_res / h. @@ -872,9 +874,13 @@ def solve_bvp(fun, bc, x, y, p=None, S=None, fun_jac=None, bc_jac=None, Control and the Maltab PSE", ACM Trans. Math. Softw., Vol. 27, Number 3, pp. 299-316, 2001. .. [2] L.F. Shampine, P. H. Muir and H. Xu, "A User-Friendly Fortran BVP - Solver". + Solver", J. Numer. Anal., Ind. Appl. Math. (JNAIAM), Vol. 1, + Number 2, pp. 201-217, 2006. .. [3] U. Ascher, R. Mattheij and R. Russell "Numerical Solution of - Boundary Value Problems for Ordinary Differential Equations". + Boundary Value Problems for Ordinary Differential Equations", + Philidelphia, PA: Society for Industrial and Applied Mathematics, + 1995. + :doi:`10.1137/1.9781611971231` .. [4] `Cauchy-Riemann equations `_ on Wikipedia. diff --git a/scipy/integrate/_tanhsinh.py b/scipy/integrate/_tanhsinh.py index 0d5949eca447..a3cb94fc6543 100644 --- a/scipy/integrate/_tanhsinh.py +++ b/scipy/integrate/_tanhsinh.py @@ -1180,6 +1180,7 @@ def nsum(f, a, b, *, step=1, args=(), log=False, maxterms=int(2**20), tolerances # Branch for direct sum evaluation / integral approximation / invalid input i0 = ~valid_abstep # invalid + i0b = b < a # zero i1 = (nterms + 1 <= maxterms) & ~i0 # direct sum evaluation i2 = xp.isfinite(a) & ~i1 & ~i0 # infinite sum to the right i3 = xp.isfinite(b) & ~i2 & ~i1 & ~i0 # infinite sum to the left @@ -1189,6 +1190,9 @@ def nsum(f, a, b, *, step=1, args=(), log=False, maxterms=int(2**20), tolerances S[i0], E[i0] = xp.nan, xp.nan status[i0] = -1 + S[i0b], E[i0b] = zero, zero + status[i0b] = 0 + if xp.any(i1): args_direct = [arg[i1] for arg in args] tmp = _direct(f, a[i1], b[i1], step[i1], args_direct, constants, xp) @@ -1307,7 +1311,7 @@ def _integral_bound(f, a, b, step, args, constants, xp): tol = special.logsumexp(xp.stack((tol, rtol + lb.integral)), axis=0) else: tol = tol + rtol*lb.integral - i_skip = lb.status < 0 # avoid unnecessary f_evals if integral is divergent + i_skip = lb.status == -3 # avoid unnecessary f_evals if integral is divergent tol[i_skip] = xp.nan status = lb.status diff --git a/scipy/integrate/tests/test_cubature.py b/scipy/integrate/tests/test_cubature.py index 691eb213c591..1c3ee9790c5b 100644 --- a/scipy/integrate/tests/test_cubature.py +++ b/scipy/integrate/tests/test_cubature.py @@ -222,7 +222,9 @@ def _eval_indefinite_integral(F, a, b, xp): out = 0 for ind in itertools.product(range(2), repeat=ndim): - selected_points = xp.asarray([points[i, j] for i, j in zip(ind, range(ndim))]) + selected_points = xp.asarray( + [float(points[i, j]) for i, j in zip(ind, range(ndim))] + ) out += pow(-1, sum(ind) + ndim) * F(selected_points) return out diff --git a/scipy/integrate/tests/test_quadrature.py b/scipy/integrate/tests/test_quadrature.py index b7179903ee11..3cd2238b76ed 100644 --- a/scipy/integrate/tests/test_quadrature.py +++ b/scipy/integrate/tests/test_quadrature.py @@ -631,6 +631,7 @@ def _get_theoretical_diff_between_simps_and_cum_simps(self, y, x): # `simpson` uses the trapezoidal rule return theoretical_difference + @pytest.mark.fail_slow(10) @pytest.mark.thread_unsafe @pytest.mark.slow @given( @@ -662,6 +663,7 @@ def simpson_reference(y): res[..., 1:], ref[..., 1:] + theoretical_difference[..., 1:], atol=1e-16 ) + @pytest.mark.fail_slow(10) @pytest.mark.thread_unsafe @pytest.mark.slow @given( diff --git a/scipy/integrate/tests/test_tanhsinh.py b/scipy/integrate/tests/test_tanhsinh.py index b5fe548c738c..f3d56a782d15 100644 --- a/scipy/integrate/tests/test_tanhsinh.py +++ b/scipy/integrate/tests/test_tanhsinh.py @@ -825,13 +825,21 @@ def test_input_validation(self, xp): with pytest.raises(ValueError, match=message): nsum(f, a, b, tolerances=dict(rtol=pytest)) - with np.errstate(all='ignore'): + with (np.errstate(all='ignore')): res = nsum(f, xp.asarray([np.nan, np.inf]), xp.asarray(1.)) - assert xp.all((res.status == -1) & xp.isnan(res.sum) - & xp.isnan(res.error) & ~res.success & res.nfev == 1) + assert (res.status[0] == -1) and not res.success[0] + assert xp.isnan(res.sum[0]) and xp.isnan(res.error[0]) + assert (res.status[1] == 0) and res.success[1] + assert res.sum[1] == res.error[1] + assert xp.all(res.nfev[0] == 1) + res = nsum(f, xp.asarray(10.), xp.asarray([np.nan, 1])) - assert xp.all((res.status == -1) & xp.isnan(res.sum) - & xp.isnan(res.error) & ~res.success & res.nfev == 1) + assert (res.status[0] == -1) and not res.success[0] + assert xp.isnan(res.sum[0]) and xp.isnan(res.error[0]) + assert (res.status[1] == 0) and res.success[1] + assert res.sum[1] == res.error[1] + assert xp.all(res.nfev[0] == 1) + res = nsum(f, xp.asarray(1.), xp.asarray(10.), step=xp.asarray([xp.nan, -xp.inf, xp.inf, -1, 0])) assert xp.all((res.status == -1) & xp.isnan(res.sum) @@ -1084,8 +1092,8 @@ def f(x): return 1 / x res = nsum(f, xp.asarray(0), xp.asarray(10), maxterms=0) - assert xp.isnan(res.sum) - assert xp.isnan(res.error) + assert xp.isinf(res.sum) + assert xp.isinf(res.error) assert res.status == -2 res = nsum(f, xp.asarray(0), xp.asarray(10), maxterms=1) diff --git a/scipy/interpolate/_fitpack_repro.py b/scipy/interpolate/_fitpack_repro.py index c352761786c8..76ae803c63ac 100644 --- a/scipy/interpolate/_fitpack_repro.py +++ b/scipy/interpolate/_fitpack_repro.py @@ -57,7 +57,11 @@ def _get_residuals(x, y, t, k, w): _, _, c = _lsq_solve_qr(x, y, t, k, w) c = np.ascontiguousarray(c) spl = BSpline(t, c, k) - return _compute_residuals(w2, spl(x), y) + residuals = _compute_residuals(w2, spl(x), y) + fp = residuals.sum() + if np.isnan(fp): + raise ValueError(_iermesg[1]) + return residuals, fp def _compute_residuals(w2, splx, y): @@ -258,8 +262,7 @@ def _generate_knots_impl(x, y, *, w=None, xb=None, xe=None, k=3, s=0, nest=None) # construct the LSQ spline with this set of knots fpold = fp - residuals = _get_residuals(x, y, t, k, w=w) - fp = residuals.sum() + residuals, fp = _get_residuals(x, y, t, k, w=w) fpms = fp - s # c test whether the approximation sinf(x) is an acceptable solution. @@ -300,7 +303,7 @@ def _generate_knots_impl(x, y, *, w=None, xb=None, xe=None, k=3, s=0, nest=None) # recompute if needed if j < nplus - 1: - residuals = _get_residuals(x, y, t, k, w=w) + residuals, _ = _get_residuals(x, y, t, k, w=w) # this should never be reached return @@ -536,11 +539,15 @@ def __init__(self, **kwargs): self.__dict__.update(**kwargs) -_iermesg = { -2: """error. a theoretically impossible result was found during +_iermesg1 = """error. a theoretically impossible result was found during the iteration process for finding a smoothing spline with fp = s. probably causes : s too small. -there is an approximation returned but the corresponding +""" + +_iermesg = { +1: _iermesg1 + """the weighted sum of squared residuals is becoming NaN +""", +2: _iermesg1 + """there is an approximation returned but the corresponding weighted sum of squared residuals does not satisfy the condition abs(fp-s)/s < tol. """, @@ -659,7 +666,7 @@ def _make_splrep_impl(x, y, *, w=None, xb=None, xe=None, k=3, s=0, t=None, nest= nest = max(m + k + 1, 2*k + 3) else: if nest < 2*(k + 1): - raise ValueError(f"`nest` too small: {nest = } < 2*(k+1) = {2*(k+1)}.") + raise ValueError(f"`nest` too small: {nest = } < 2*(k+1) = {2*(k+1)}.") if t is not None: raise ValueError("Either supply `t` or `nest`.") @@ -685,13 +692,11 @@ def _make_splrep_impl(x, y, *, w=None, xb=None, xe=None, k=3, s=0, t=None, nest= # ### bespoke solver #### # initial conditions # f(p=inf) : LSQ spline with knots t (XXX: reuse R, c) - residuals = _get_residuals(x, y, t, k, w=w) - fp = residuals.sum() + _, fp = _get_residuals(x, y, t, k, w=w) fpinf = fp - s # f(p=0): LSQ spline without internal knots - residuals = _get_residuals(x, y, np.array([xb]*(k+1) + [xe]*(k+1)), k, w) - fp0 = residuals.sum() + _, fp0 = _get_residuals(x, y, np.array([xb]*(k+1) + [xe]*(k+1)), k, w) fp0 = fp0 - s # solve @@ -989,4 +994,3 @@ def make_splprep(x, *, w=None, u=None, ub=None, ue=None, k=3, s=0, t=None, nest= spl1 = BSpline(spl.t, cc, spl.k, axis=1) return spl1, u - diff --git a/scipy/interpolate/interpnd.py b/scipy/interpolate/interpnd.py index 2f4265377d20..36bfa6b9f566 100644 --- a/scipy/interpolate/interpnd.py +++ b/scipy/interpolate/interpnd.py @@ -7,10 +7,7 @@ __all__ = [ # noqa: F822 'CloughTocher2DInterpolator', - 'GradientEstimationWarning', 'LinearNDInterpolator', - 'NDInterpolatorBase', - 'estimate_gradients_2d_global', ] diff --git a/scipy/interpolate/src/_dierckxmodule.cc b/scipy/interpolate/src/_dierckxmodule.cc index 6d51f20dca81..ba8a21851441 100644 --- a/scipy/interpolate/src/_dierckxmodule.cc +++ b/scipy/interpolate/src/_dierckxmodule.cc @@ -23,8 +23,7 @@ check_array(PyObject *obj, npy_intp ndim, int typenum) { if(!cond) { std::string msg = ("Expected a " + std::to_string(ndim) + "-dim C contiguous array " + - " of dtype = " + std::to_string(typenum) + "( got " + - std::to_string(PyArray_TYPE((PyArrayObject*)obj)) +" )\n"); + " of dtype = " + std::to_string(typenum) + "\n"); // XXX: name the dtype from typenum? Also type of arg if not array PyErr_SetString(PyExc_ValueError, msg.c_str()); return 0; diff --git a/scipy/interpolate/tests/test_bsplines.py b/scipy/interpolate/tests/test_bsplines.py index b6898752be28..33cc70a390c4 100644 --- a/scipy/interpolate/tests/test_bsplines.py +++ b/scipy/interpolate/tests/test_bsplines.py @@ -2167,7 +2167,7 @@ def test_compare_with_GCVSPL(self): # using an iterative algorithm for minimizing the GCV criteria. These # algorithms may vary, so the tolerance should be rather low. # Not checking dtypes as gcvspl.npz stores little endian arrays, which - # result in conflicting dtypes on big endian systems. + # result in conflicting dtypes on big endian systems. xp_assert_close(y_compr, y_GCVSPL, atol=1e-4, rtol=1e-4, check_dtype=False) def test_non_regularized_case(self): @@ -3533,6 +3533,20 @@ def test_s_too_small(self): xp_assert_close(np.r_[spl.c, [0]*(spl.k+1)], tck[1], atol=5e-13) + def test_issue_22704(self): + # Reference - https://github.com/scipy/scipy/issues/22704 + x = np.asarray([20.00, 153.81, 175.57, 202.47, 237.11, + 253.61, 258.56, 273.40, 284.54, 293.61, + 298.56, 301.86, 305.57, 307.22, 308.45, + 310.10, 310.10, 310.50], dtype=np.float64) + y = np.asarray([53.00, 49.50, 48.60, 46.80, 43.20, + 40.32, 39.60, 36.00, 32.40, 28.80, + 25.20, 21.60, 18.00, 14.40, 10.80, + 7.20, 3.60, 0.0], dtype=np.float64) + w = np.asarray([1.38723] * y.shape[0], dtype=np.float64) + with assert_raises(ValueError): + make_splrep(x, y, w=w, k=2, s=12) + def test_shape(self): # make sure coefficients have the right shape (not extra dims) n, k = 10, 3 diff --git a/scipy/linalg/__init__.py b/scipy/linalg/__init__.py index 9381d7a02c15..8fa8f6de8cc2 100644 --- a/scipy/linalg/__init__.py +++ b/scipy/linalg/__init__.py @@ -46,7 +46,6 @@ lstsq - Solve a linear least-squares problem pinv - Pseudo-inverse (Moore-Penrose) using lstsq pinvh - Pseudo-inverse of hermitian matrix - kron - Kronecker product of two arrays khatri_rao - Khatri-Rao product of two arrays orthogonal_procrustes - Solve an orthogonal Procrustes problem matrix_balance - Balance matrix entries with a similarity transformation diff --git a/scipy/linalg/_decomp_lu_cython.pyi b/scipy/linalg/_decomp_lu_cython.pyi index 0a175b1de328..99ee34e6f27e 100644 --- a/scipy/linalg/_decomp_lu_cython.pyi +++ b/scipy/linalg/_decomp_lu_cython.pyi @@ -1,6 +1,8 @@ +import numpy as np from numpy.typing import NDArray -from typing import Any +from typing import TypeVar -def lu_decompose(a: NDArray[Any], lu: NDArray[Any], perm: NDArray[Any], permute_l: bool) -> None: ... # noqa: E501 +# this mimicks the `ctypedef fused lapack_t` +_LapackT = TypeVar("_LapackT", np.float32, np.float64, np.complex64, np.complex128) -def lu_dispatcher(a: NDArray[Any], lu: NDArray[Any], perm: NDArray[Any], permute_l: bool) -> None: ... # noqa: E501 +def lu_dispatcher(a: NDArray[_LapackT], u: NDArray[_LapackT], piv: NDArray[np.integer], permute_l: bool) -> None: ... diff --git a/scipy/linalg/_decomp_svd.py b/scipy/linalg/_decomp_svd.py index 6155afdb478c..4f8a9a2ccded 100644 --- a/scipy/linalg/_decomp_svd.py +++ b/scipy/linalg/_decomp_svd.py @@ -169,6 +169,9 @@ def svd(a, full_matrices=True, compute_uv=True, overwrite_a=False, if info > 0: raise LinAlgError("SVD did not converge") if info < 0: + if lapack_driver == "gesdd" and info == -4: + msg = "A has a NaN entry" + raise ValueError(msg) raise ValueError(f'illegal value in {-info}th argument of internal gesdd') if compute_uv: return u, s, v diff --git a/scipy/linalg/_matfuncs.py b/scipy/linalg/_matfuncs.py index 48328b4d9d45..3e020ca8a508 100644 --- a/scipy/linalg/_matfuncs.py +++ b/scipy/linalg/_matfuncs.py @@ -146,7 +146,7 @@ def fractional_matrix_power(A, t): @_apply_over_batch(('A', 2)) -def logm(A, disp=True): +def logm(A, disp=_NoValue): """ Compute matrix logarithm. @@ -161,6 +161,11 @@ def logm(A, disp=True): Emit warning if error in the result is estimated large instead of returning estimated error. (Default: True) + .. deprecated:: 1.16.0 + The `disp` argument is deprecated and will be + removed in SciPy 1.18.0. The previously returned error estimate + can be computed as ``norm(expm(logm(A)) - A, 1) / norm(A, 1)``. + Returns ------- logm : (N, N) ndarray @@ -201,6 +206,12 @@ def logm(A, disp=True): [ 1., 4.]]) """ + if disp is _NoValue: + disp = True + else: + warnings.warn("The `disp` argument is deprecated " + "and will be removed in SciPy 1.18.0.", + DeprecationWarning, stacklevel=2) A = np.asarray(A) # squareness checked in `_logm` # Avoid circular import ... this is OK, right? import scipy.linalg._matfuncs_inv_ssq @@ -413,14 +424,19 @@ def sqrtm(A, disp=_NoValue, blocksize=_NoValue): A : ndarray Input with last two dimensions are square ``(..., n, n)``. disp : bool, optional - Deprecated keyword. It will be removed in 1.20.0. + Print warning if error in the result is estimated large + instead of returning estimated error. (Default: True) .. deprecated:: 1.16.0 + The `disp` argument is deprecated and will be + removed in SciPy 1.18.0. The previously returned error estimate + can be computed as ``norm(X @ X - A, 'fro')**2 / norm(A, 'fro')`` blocksize : integer, optional - Deprecated keyword. It has no effect and will be removed in 1.18.0. .. deprecated:: 1.16.0 + The `blocksize` argument is deprecated as it is unused by the algorithm + and will be removed in SciPy 1.18.0. Returns ------- @@ -471,6 +487,17 @@ def sqrtm(A, disp=_NoValue, blocksize=_NoValue): [ 1., 4.]]) """ + if disp is _NoValue: + disp = True + else: + warnings.warn("The `disp` argument is deprecated and will be removed in SciPy " + "1.18.0.", + DeprecationWarning, stacklevel=2) + if blocksize is not _NoValue: + warnings.warn("The `blocksize` argument is deprecated and will be removed in " + "SciPy 1.18.0.", + DeprecationWarning, stacklevel=2) + a = np.asarray(A) if a.size == 1 and a.ndim < 2: return np.array([[np.exp(a.item())]]) @@ -864,7 +891,7 @@ def funm(A, func, disp=True): @_apply_over_batch(('A', 2)) -def signm(A, disp=True): +def signm(A, disp=_NoValue): """ Matrix sign function. @@ -878,6 +905,11 @@ def signm(A, disp=True): Print warning if error in the result is estimated large instead of returning estimated error. (Default: True) + .. deprecated:: 1.16.0 + The `disp` argument is deprecated and will be + removed in SciPy 1.18.0. The previously returned error estimate + can be computed as ``norm(signm @ signm - signm, 1)``. + Returns ------- signm : (N, N) ndarray @@ -897,6 +929,13 @@ def signm(A, disp=True): array([-1.+0.j, 1.+0.j, 1.+0.j]) """ + if disp is _NoValue: + disp = True + else: + warnings.warn("The `disp` argument is deprecated " + "and will be removed in SciPy 1.18.0.", + DeprecationWarning, stacklevel=2) + A = _asarray_square(A) def rounded_sign(x): diff --git a/scipy/linalg/_special_matrices.py b/scipy/linalg/_special_matrices.py index 5ba669ec66cf..2c7e36d756aa 100644 --- a/scipy/linalg/_special_matrices.py +++ b/scipy/linalg/_special_matrices.py @@ -7,7 +7,7 @@ __all__ = ['toeplitz', 'circulant', 'hankel', - 'hadamard', 'leslie', 'kron', 'block_diag', 'companion', + 'hadamard', 'leslie', 'block_diag', 'companion', 'helmert', 'hilbert', 'invhilbert', 'pascal', 'invpascal', 'dft', 'fiedler', 'fiedler_companion', 'convolution_matrix'] @@ -339,60 +339,6 @@ def leslie(f, s): return a -def kron(a, b): - """ - Kronecker product. - - .. deprecated:: 1.15.0 - `kron` has been deprecated in favour of `numpy.kron` and will be - removed in SciPy 1.17.0. - - The result is the block matrix:: - - a[0,0]*b a[0,1]*b ... a[0,-1]*b - a[1,0]*b a[1,1]*b ... a[1,-1]*b - ... - a[-1,0]*b a[-1,1]*b ... a[-1,-1]*b - - Parameters - ---------- - a : (M, N) ndarray - Input array - b : (P, Q) ndarray - Input array - - Returns - ------- - A : (M*P, N*Q) ndarray - Kronecker product of `a` and `b`. - - Examples - -------- - >>> from numpy import array - >>> from scipy.linalg import kron - >>> kron(array([[1,2],[3,4]]), array([[1,1,1]])) - array([[1, 1, 1, 2, 2, 2], - [3, 3, 3, 4, 4, 4]]) - - """ - msg = ("`kron` has been deprecated in favour of `numpy.kron` in SciPy" - " 1.15.0 and will be removed in SciPy 1.17.0.") - warnings.warn(msg, DeprecationWarning, stacklevel=2) - # accommodate empty arrays - if a.size == 0 or b.size == 0: - m = a.shape[0] * b.shape[0] - n = a.shape[1] * b.shape[1] - return np.empty_like(a, shape=(m, n)) - - if not a.flags['CONTIGUOUS']: - a = np.reshape(a, a.shape) - if not b.flags['CONTIGUOUS']: - b = np.reshape(b, b.shape) - o = np.outer(a, b) - o = o.reshape(a.shape + b.shape) - return np.concatenate(np.concatenate(o, axis=1), axis=1) - - def block_diag(*arrs): """ Create a block diagonal array from provided arrays. diff --git a/scipy/linalg/interpolative.py b/scipy/linalg/interpolative.py index 38070863aa51..37c2c0c7f10c 100644 --- a/scipy/linalg/interpolative.py +++ b/scipy/linalg/interpolative.py @@ -94,14 +94,6 @@ estimate_spectral_norm_diff estimate_rank -Following support functions are deprecated and will be removed in SciPy 1.17.0: - -.. autosummary:: - :toctree: generated/ - - seed - rand - References ========== @@ -367,7 +359,6 @@ import scipy.linalg._decomp_interpolative as _backend import numpy as np -import warnings __all__ = [ 'estimate_rank', @@ -375,11 +366,9 @@ 'estimate_spectral_norm_diff', 'id_to_svd', 'interp_decomp', - 'rand', 'reconstruct_interp_matrix', 'reconstruct_matrix_from_id', 'reconstruct_skel_matrix', - 'seed', 'svd', ] @@ -411,44 +400,6 @@ def _is_real(A): raise _TYPE_ERROR from e -def seed(seed=None): - """ - This function, historically, used to set the seed of the randomization algorithms - used in the `scipy.linalg.interpolative` functions written in Fortran77. - - The library has been ported to Python and now the functions use the native NumPy - generators and this function has no content and returns None. Thus this function - should not be used and will be removed in SciPy version 1.17.0. - """ - warnings.warn("`scipy.linalg.interpolative.seed` is deprecated and will be " - "removed in SciPy 1.17.0.", DeprecationWarning, stacklevel=3) - - -def rand(*shape): - """ - This function, historically, used to generate uniformly distributed random number - for the randomization algorithms used in the `scipy.linalg.interpolative` functions - written in Fortran77. - - The library has been ported to Python and now the functions use the native NumPy - generators. Thus this function should not be used and will be removed in the - SciPy version 1.17.0. - - If pseudo-random numbers are needed, NumPy pseudo-random generators should be used - instead. - - Parameters - ---------- - *shape - Shape of output array - - """ - warnings.warn("`scipy.linalg.interpolative.rand` is deprecated and will be " - "removed in SciPy 1.17.0.", DeprecationWarning, stacklevel=3) - rng = np.random.default_rng() - return rng.uniform(low=0., high=1.0, size=shape) - - def interp_decomp(A, eps_or_k, rand=True, rng=None): """ Compute ID of a matrix. diff --git a/scipy/linalg/special_matrices.py b/scipy/linalg/special_matrices.py index a881ce765dfa..4810e8a45067 100644 --- a/scipy/linalg/special_matrices.py +++ b/scipy/linalg/special_matrices.py @@ -6,7 +6,7 @@ __all__ = [ # noqa: F822 'toeplitz', 'circulant', 'hankel', - 'hadamard', 'leslie', 'kron', 'block_diag', 'companion', + 'hadamard', 'leslie', 'block_diag', 'companion', 'helmert', 'hilbert', 'invhilbert', 'pascal', 'invpascal', 'dft', 'fiedler', 'fiedler_companion', 'convolution_matrix' ] diff --git a/scipy/linalg/tests/test_batch.py b/scipy/linalg/tests/test_batch.py index 915cefadf6c3..4e62860952f1 100644 --- a/scipy/linalg/tests/test_batch.py +++ b/scipy/linalg/tests/test_batch.py @@ -142,17 +142,15 @@ def test_fractional_matrix_power(self, dtype): res2 = linalg.fractional_matrix_power(A, 1.5) np.testing.assert_equal(res1, res2) - @pytest.mark.parametrize('disp', [False, True]) @pytest.mark.parametrize('dtype', floating) - def test_logm(self, dtype, disp): + def test_logm(self, dtype): # One test failed absolute tolerance with default random seed rng = np.random.default_rng(89940026998903887141749720079406074936) A = get_random((5, 3, 4, 4), dtype=dtype, rng=rng) A = A + 3*np.eye(4) # avoid complex output for real input - n_out = 1 if disp else 2 - res1 = self.batch_test(linalg.logm, A, n_out=n_out, kwargs=dict(disp=disp)) + res1 = self.batch_test(linalg.logm, A) # test that `disp` can be passed by position - res2 = linalg.logm(A, disp) + res2 = linalg.logm(A) for res1i, res2i in zip(res1, res2): np.testing.assert_equal(res1i, res2i) diff --git a/scipy/linalg/tests/test_blas.py b/scipy/linalg/tests/test_blas.py index b6645d0ad5d9..3f1c526885a4 100644 --- a/scipy/linalg/tests/test_blas.py +++ b/scipy/linalg/tests/test_blas.py @@ -5,14 +5,12 @@ import math import pytest import numpy as np -import numpy.random -from numpy.testing import (assert_equal, assert_almost_equal, assert_, +from numpy.testing import (assert_equal, assert_almost_equal, assert_array_almost_equal, assert_allclose) from pytest import raises as assert_raises -from numpy import float32, float64, complex64, complex128, arange, triu, \ - tril, zeros, tril_indices, ones, mod, diag, append, eye, \ - nonzero +from numpy import (arange, triu, tril, zeros, tril_indices, ones, + diag, append, eye, nonzero) import scipy from scipy.linalg import _fblas as fblas, get_blas_funcs, toeplitz, solve @@ -22,8 +20,8 @@ except ImportError: cblas = None -REAL_DTYPES = [float32, float64] -COMPLEX_DTYPES = [complex64, complex128] +REAL_DTYPES = [np.float32, np.float64] +COMPLEX_DTYPES = [np.complex64, np.complex128] DTYPES = REAL_DTYPES + COMPLEX_DTYPES @@ -77,154 +75,111 @@ def test_get_blas_funcs_alias(): assert f is h -class TestCBLAS1Simple: +def parametrize_blas(mod, func_name, prefixes): + if mod is None: + return pytest.mark.skip(reason="cblas not available") + params = [] + for prefix in prefixes: + if 'z' in prefix: + dtype = np.complex128 + elif 'c' in prefix: + dtype = np.complex64 + elif 'd' in prefix: + dtype = np.float64 + else: + assert 's' in prefix + dtype = np.float32 + + f = getattr(mod, prefix + func_name) + params.append(pytest.param(f, dtype, id=prefix + func_name)) + + return pytest.mark.parametrize("f,dtype", params) + - def test_axpy(self): - for p in 'sd': - f = getattr(cblas, p+'axpy', None) - if f is None: - continue - assert_array_almost_equal(f([1, 2, 3], [2, -1, 3], a=5), - [7, 9, 18]) - for p in 'cz': - f = getattr(cblas, p+'axpy', None) - if f is None: - continue +class TestCBLAS1Simple: + @parametrize_blas(cblas, "axpy", "sdcz") + def test_axpy(self, f, dtype): + assert_array_almost_equal(f([1, 2, 3], [2, -1, 3], a=5), + [7, 9, 18]) + if dtype in COMPLEX_DTYPES: assert_array_almost_equal(f([1, 2j, 3], [2, -1, 3], a=5), [7, 10j-1, 18]) class TestFBLAS1Simple: - def test_axpy(self): - for p in 'sd': - f = getattr(fblas, p+'axpy', None) - if f is None: - continue - assert_array_almost_equal(f([1, 2, 3], [2, -1, 3], a=5), - [7, 9, 18]) - for p in 'cz': - f = getattr(fblas, p+'axpy', None) - if f is None: - continue + @parametrize_blas(fblas, "axpy", "sdcz") + def test_axpy(self, f, dtype): + assert_array_almost_equal(f([1, 2, 3], [2, -1, 3], a=5), + [7, 9, 18]) + if dtype in COMPLEX_DTYPES: assert_array_almost_equal(f([1, 2j, 3], [2, -1, 3], a=5), [7, 10j-1, 18]) - def test_copy(self): - for p in 'sd': - f = getattr(fblas, p+'copy', None) - if f is None: - continue - assert_array_almost_equal(f([3, 4, 5], [8]*3), [3, 4, 5]) - for p in 'cz': - f = getattr(fblas, p+'copy', None) - if f is None: - continue + @parametrize_blas(fblas, "copy", "sdcz") + def test_copy(self, f, dtype): + assert_array_almost_equal(f([3, 4, 5], [8]*3), [3, 4, 5]) + if dtype in COMPLEX_DTYPES: assert_array_almost_equal(f([3, 4j, 5+3j], [8]*3), [3, 4j, 5+3j]) - def test_asum(self): - for p in 'sd': - f = getattr(fblas, p+'asum', None) - if f is None: - continue - assert_almost_equal(f([3, -4, 5]), 12) - for p in ['sc', 'dz']: - f = getattr(fblas, p+'asum', None) - if f is None: - continue + @parametrize_blas(fblas, "asum", ["s", "d", "sc", "dz"]) + def test_asum(self, f, dtype): + assert_almost_equal(f([3, -4, 5]), 12) + if dtype in COMPLEX_DTYPES: assert_almost_equal(f([3j, -4, 3-4j]), 14) - def test_dot(self): - for p in 'sd': - f = getattr(fblas, p+'dot', None) - if f is None: - continue - assert_almost_equal(f([3, -4, 5], [2, 5, 1]), -9) - - def test_complex_dotu(self): - for p in 'cz': - f = getattr(fblas, p+'dotu', None) - if f is None: - continue - assert_almost_equal(f([3j, -4, 3-4j], [2, 3, 1]), -9+2j) - - def test_complex_dotc(self): - for p in 'cz': - f = getattr(fblas, p+'dotc', None) - if f is None: - continue - assert_almost_equal(f([3j, -4, 3-4j], [2, 3j, 1]), 3-14j) - - def test_nrm2(self): - for p in 'sd': - f = getattr(fblas, p+'nrm2', None) - if f is None: - continue - assert_almost_equal(f([3, -4, 5]), math.sqrt(50)) - for p in ['c', 'z', 'sc', 'dz']: - f = getattr(fblas, p+'nrm2', None) - if f is None: - continue + @parametrize_blas(fblas, "dot", "sd") + def test_dot(self, f, dtype): + assert_almost_equal(f([3, -4, 5], [2, 5, 1]), -9) + + @parametrize_blas(fblas, "dotu", "cz") + def test_dotu(self, f, dtype): + assert_almost_equal(f([3j, -4, 3-4j], [2, 3, 1]), -9+2j) + + @parametrize_blas(fblas, "dotc", "cz") + def test_dotc(self, f, dtype): + assert_almost_equal(f([3j, -4, 3-4j], [2, 3j, 1]), 3-14j) + + @parametrize_blas(fblas, "nrm2", ["s", "d", "sc", "dz"]) + def test_nrm2(self, f, dtype): + assert_almost_equal(f([3, -4, 5]), math.sqrt(50)) + if dtype in COMPLEX_DTYPES: assert_almost_equal(f([3j, -4, 3-4j]), math.sqrt(50)) - def test_scal(self): - for p in 'sd': - f = getattr(fblas, p+'scal', None) - if f is None: - continue - assert_array_almost_equal(f(2, [3, -4, 5]), [6, -8, 10]) - for p in 'cz': - f = getattr(fblas, p+'scal', None) - if f is None: - continue - assert_array_almost_equal(f(3j, [3j, -4, 3-4j]), [-9, -12j, 12+9j]) - for p in ['cs', 'zd']: - f = getattr(fblas, p+'scal', None) - if f is None: - continue + @parametrize_blas(fblas, "scal", ["s", "d", "cs", "zd"]) + def test_scal(self, f, dtype): + assert_array_almost_equal(f(2, [3, -4, 5]), [6, -8, 10]) + if dtype in COMPLEX_DTYPES: assert_array_almost_equal(f(3, [3j, -4, 3-4j]), [9j, -12, 9-12j]) - def test_swap(self): - for p in 'sd': - f = getattr(fblas, p+'swap', None) - if f is None: - continue - x, y = [2, 3, 1], [-2, 3, 7] - x1, y1 = f(x, y) - assert_array_almost_equal(x1, y) - assert_array_almost_equal(y1, x) - for p in 'cz': - f = getattr(fblas, p+'swap', None) - if f is None: - continue + @parametrize_blas(fblas, "swap", "sdcz") + def test_swap(self, f, dtype): + x, y = [2, 3, 1], [-2, 3, 7] + x1, y1 = f(x, y) + assert_array_almost_equal(x1, y) + assert_array_almost_equal(y1, x) + + if dtype in COMPLEX_DTYPES: x, y = [2, 3j, 1], [-2, 3, 7-3j] x1, y1 = f(x, y) assert_array_almost_equal(x1, y) assert_array_almost_equal(y1, x) - def test_amax(self): - for p in 'sd': - f = getattr(fblas, 'i'+p+'amax') - assert_equal(f([-2, 4, 3]), 1) - for p in 'cz': - f = getattr(fblas, 'i'+p+'amax') + @parametrize_blas(fblas, "amax", ["is", "id", "ic", "iz"]) + def test_amax(self, f, dtype): + assert_equal(f([-2, 4, 3]), 1) + if dtype in COMPLEX_DTYPES: assert_equal(f([-5, 4+3j, 6]), 1) + # XXX: need tests for rot,rotm,rotg,rotmg class TestFBLAS2Simple: - - def test_gemv(self): - for p in 'sd': - f = getattr(fblas, p+'gemv', None) - if f is None: - continue - assert_array_almost_equal(f(3, [[3]], [-4]), [-36]) - assert_array_almost_equal(f(3, [[3]], [-4], 3, [5]), [-21]) - for p in 'cz': - f = getattr(fblas, p+'gemv', None) - if f is None: - continue + @parametrize_blas(fblas, "gemv", "sdcz") + def test_gemv(self, f, dtype): + assert_array_almost_equal(f(3, [[3]], [-4]), [-36]) + assert_array_almost_equal(f(3, [[3]], [-4], 3, [5]), [-21]) + if dtype in COMPLEX_DTYPES: assert_array_almost_equal(f(3j, [[3-4j]], [-4]), [-48-36j]) assert_array_almost_equal(f(3j, [[3-4j]], [-4], 3, [5j]), [-48-21j]) @@ -232,84 +187,56 @@ def test_gemv(self): # All of these *ger* functions are segfaulting when called from multiple # threads under free-threaded CPython, see gh-21936. @pytest.mark.thread_unsafe - def test_ger(self): - - for p in 'sd': - f = getattr(fblas, p+'ger', None) - if f is None: - continue - assert_array_almost_equal(f(1, [1, 2], [3, 4]), [[3, 4], [6, 8]]) - assert_array_almost_equal(f(2, [1, 2, 3], [3, 4]), - [[6, 8], [12, 16], [18, 24]]) - - assert_array_almost_equal(f(1, [1, 2], [3, 4], - a=[[1, 2], [3, 4]]), [[4, 6], [9, 12]]) - - for p in 'cz': - f = getattr(fblas, p+'geru', None) - if f is None: - continue + @parametrize_blas(fblas, "ger", "sd") + def test_ger(self, f, dtype): + assert_array_almost_equal(f(1, [1, 2], [3, 4]), [[3, 4], [6, 8]]) + assert_array_almost_equal(f(2, [1, 2, 3], [3, 4]), + [[6, 8], [12, 16], [18, 24]]) + assert_array_almost_equal(f(1, [1, 2], [3, 4], + a=[[1, 2], [3, 4]]), [[4, 6], [9, 12]]) + if dtype in COMPLEX_DTYPES: assert_array_almost_equal(f(1, [1j, 2], [3, 4]), [[3j, 4j], [6, 8]]) - assert_array_almost_equal(f(-2, [1j, 2j, 3j], [3j, 4j]), + assert_array_almost_equal(f(2, [1j, 2j, 3j], [3j, 4j]), [[6, 8], [12, 16], [18, 24]]) - for p in 'cz': - for name in ('ger', 'gerc'): - f = getattr(fblas, p+name, None) - if f is None: - continue - assert_array_almost_equal(f(1, [1j, 2], [3, 4]), - [[3j, 4j], [6, 8]]) - assert_array_almost_equal(f(2, [1j, 2j, 3j], [3j, 4j]), - [[6, 8], [12, 16], [18, 24]]) - - def test_syr_her(self): + @pytest.mark.thread_unsafe + @parametrize_blas(fblas, "geru", "cz") + def test_geru(self, f, dtype): + assert_array_almost_equal(f(1, [1j, 2], [3, 4]), + [[3j, 4j], [6, 8]]) + assert_array_almost_equal(f(-2, [1j, 2j, 3j], [3j, 4j]), + [[6, 8], [12, 16], [18, 24]]) + + @pytest.mark.thread_unsafe + @parametrize_blas(fblas, "gerc", "cz") + def test_gerc(self, f, dtype): + assert_array_almost_equal(f(1, [1j, 2], [3, 4]), + [[3j, 4j], [6, 8]]) + assert_array_almost_equal(f(2, [1j, 2j, 3j], [3j, 4j]), + [[6, 8], [12, 16], [18, 24]]) + + @parametrize_blas(fblas, "syr", "sdcz") + def test_syr(self, f, dtype): x = np.arange(1, 5, dtype='d') resx = np.triu(x[:, np.newaxis] * x) resx_reverse = np.triu(x[::-1, np.newaxis] * x[::-1]) - y = np.linspace(0, 8.5, 17, endpoint=False) - z = np.arange(1, 9, dtype='d').view('D') resz = np.triu(z[:, np.newaxis] * z) resz_reverse = np.triu(z[::-1, np.newaxis] * z[::-1]) - rehz = np.triu(z[:, np.newaxis] * z.conj()) - rehz_reverse = np.triu(z[::-1, np.newaxis] * z[::-1].conj()) - w = np.c_[np.zeros(4), z, np.zeros(4)].ravel() - for p, rtol in zip('sd', [1e-7, 1e-14]): - f = getattr(fblas, p+'syr', None) - if f is None: - continue - assert_allclose(f(1.0, x), resx, rtol=rtol) - assert_allclose(f(1.0, x, lower=True), resx.T, rtol=rtol) - assert_allclose(f(1.0, y, incx=2, offx=2, n=4), resx, rtol=rtol) - # negative increments imply reversed vectors in blas - assert_allclose(f(1.0, y, incx=-2, offx=2, n=4), - resx_reverse, rtol=rtol) - - a = np.zeros((4, 4), 'f' if p == 's' else 'd', 'F') - b = f(1.0, x, a=a, overwrite_a=True) - assert_allclose(a, resx, rtol=rtol) + rtol = np.finfo(dtype).eps - b = f(2.0, x, a=a) - assert_(a is not b) - assert_allclose(b, 3*resx, rtol=rtol) + assert_allclose(f(1.0, x), resx, rtol=rtol) + assert_allclose(f(1.0, x, lower=True), resx.T, rtol=rtol) + assert_allclose(f(1.0, y, incx=2, offx=2, n=4), resx, rtol=rtol) + # negative increments imply reversed vectors in blas + assert_allclose(f(1.0, y, incx=-2, offx=2, n=4), + resx_reverse, rtol=rtol) - assert_raises(Exception, f, 1.0, x, incx=0) - assert_raises(Exception, f, 1.0, x, offx=5) - assert_raises(Exception, f, 1.0, x, offx=-2) - assert_raises(Exception, f, 1.0, x, n=-2) - assert_raises(Exception, f, 1.0, x, n=5) - assert_raises(Exception, f, 1.0, x, lower=2) - assert_raises(Exception, f, 1.0, x, a=np.zeros((2, 2), 'd', 'F')) - - for p, rtol in zip('cz', [1e-7, 1e-14]): - f = getattr(fblas, p+'syr', None) - if f is None: - continue + if dtype in COMPLEX_DTYPES: assert_allclose(f(1.0, z), resz, rtol=rtol) assert_allclose(f(1.0, z, lower=True), resz.T, rtol=rtol) assert_allclose(f(1.0, w, incx=3, offx=1, n=4), resz, rtol=rtol) @@ -317,50 +244,64 @@ def test_syr_her(self): assert_allclose(f(1.0, w, incx=-3, offx=1, n=4), resz_reverse, rtol=rtol) - a = np.zeros((4, 4), 'F' if p == 'c' else 'D', 'F') + a = np.zeros((4, 4), dtype, 'F') b = f(1.0, z, a=a, overwrite_a=True) assert_allclose(a, resz, rtol=rtol) - b = f(2.0, z, a=a) - assert_(a is not b) + assert a is not b assert_allclose(b, 3*resz, rtol=rtol) - assert_raises(Exception, f, 1.0, x, incx=0) - assert_raises(Exception, f, 1.0, x, offx=5) - assert_raises(Exception, f, 1.0, x, offx=-2) - assert_raises(Exception, f, 1.0, x, n=-2) - assert_raises(Exception, f, 1.0, x, n=5) - assert_raises(Exception, f, 1.0, x, lower=2) - assert_raises(Exception, f, 1.0, x, a=np.zeros((2, 2), 'd', 'F')) - - for p, rtol in zip('cz', [1e-7, 1e-14]): - f = getattr(fblas, p+'her', None) - if f is None: - continue - assert_allclose(f(1.0, z), rehz, rtol=rtol) - assert_allclose(f(1.0, z, lower=True), rehz.T.conj(), rtol=rtol) - assert_allclose(f(1.0, w, incx=3, offx=1, n=4), rehz, rtol=rtol) - # negative increments imply reversed vectors in blas - assert_allclose(f(1.0, w, incx=-3, offx=1, n=4), - rehz_reverse, rtol=rtol) + else: + a = np.zeros((4, 4), dtype, 'F') + b = f(1.0, x, a=a, overwrite_a=True) + assert_allclose(a, resx, rtol=rtol) + b = f(2.0, x, a=a) + assert a is not b + assert_allclose(b, 3*resx, rtol=rtol) - a = np.zeros((4, 4), 'F' if p == 'c' else 'D', 'F') - b = f(1.0, z, a=a, overwrite_a=True) - assert_allclose(a, rehz, rtol=rtol) + assert_raises(Exception, f, 1.0, x, incx=0) + assert_raises(Exception, f, 1.0, x, offx=5) + assert_raises(Exception, f, 1.0, x, offx=-2) + assert_raises(Exception, f, 1.0, x, n=-2) + assert_raises(Exception, f, 1.0, x, n=5) + assert_raises(Exception, f, 1.0, x, lower=2) + assert_raises(Exception, f, 1.0, x, a=np.zeros((2, 2), 'd', 'F')) - b = f(2.0, z, a=a) - assert_(a is not b) - assert_allclose(b, 3*rehz, rtol=rtol) - - assert_raises(Exception, f, 1.0, x, incx=0) - assert_raises(Exception, f, 1.0, x, offx=5) - assert_raises(Exception, f, 1.0, x, offx=-2) - assert_raises(Exception, f, 1.0, x, n=-2) - assert_raises(Exception, f, 1.0, x, n=5) - assert_raises(Exception, f, 1.0, x, lower=2) - assert_raises(Exception, f, 1.0, x, a=np.zeros((2, 2), 'd', 'F')) - - def test_syr2(self): + @parametrize_blas(fblas, "her", "cz") + def test_her(self, f, dtype): + x = np.arange(1, 5, dtype='d') + z = np.arange(1, 9, dtype='d').view('D') + rehz = np.triu(z[:, np.newaxis] * z.conj()) + rehz_reverse = np.triu(z[::-1, np.newaxis] * z[::-1].conj()) + w = np.c_[np.zeros(4), z, np.zeros(4)].ravel() + + rtol = np.finfo(dtype).eps + + assert_allclose(f(1.0, z), rehz, rtol=rtol) + assert_allclose(f(1.0, z, lower=True), rehz.T.conj(), rtol=rtol) + assert_allclose(f(1.0, w, incx=3, offx=1, n=4), rehz, rtol=rtol) + # negative increments imply reversed vectors in blas + assert_allclose(f(1.0, w, incx=-3, offx=1, n=4), + rehz_reverse, rtol=rtol) + + a = np.zeros((4, 4), dtype, 'F') + b = f(1.0, z, a=a, overwrite_a=True) + assert_allclose(a, rehz, rtol=rtol) + + b = f(2.0, z, a=a) + assert a is not b + assert_allclose(b, 3*rehz, rtol=rtol) + + assert_raises(Exception, f, 1.0, x, incx=0) + assert_raises(Exception, f, 1.0, x, offx=5) + assert_raises(Exception, f, 1.0, x, offx=-2) + assert_raises(Exception, f, 1.0, x, n=-2) + assert_raises(Exception, f, 1.0, x, n=5) + assert_raises(Exception, f, 1.0, x, lower=2) + assert_raises(Exception, f, 1.0, x, a=np.zeros((2, 2), 'd', 'F')) + + @parametrize_blas(fblas, "syr2", "sd") + def test_syr2(self, f, dtype): x = np.arange(1, 5, dtype='d') y = np.arange(5, 9, dtype='d') resxy = np.triu(x[:, np.newaxis] * y + y[:, np.newaxis] * x) @@ -368,44 +309,41 @@ def test_syr2(self): + y[::-1, np.newaxis] * x[::-1]) q = np.linspace(0, 8.5, 17, endpoint=False) - - for p, rtol in zip('sd', [1e-7, 1e-14]): - f = getattr(fblas, p+'syr2', None) - if f is None: - continue - assert_allclose(f(1.0, x, y), resxy, rtol=rtol) - assert_allclose(f(1.0, x, y, n=3), resxy[:3, :3], rtol=rtol) - assert_allclose(f(1.0, x, y, lower=True), resxy.T, rtol=rtol) - - assert_allclose(f(1.0, q, q, incx=2, offx=2, incy=2, offy=10), - resxy, rtol=rtol) - assert_allclose(f(1.0, q, q, incx=2, offx=2, incy=2, offy=10, n=3), - resxy[:3, :3], rtol=rtol) - # negative increments imply reversed vectors in blas - assert_allclose(f(1.0, q, q, incx=-2, offx=2, incy=-2, offy=10), - resxy_reverse, rtol=rtol) - - a = np.zeros((4, 4), 'f' if p == 's' else 'd', 'F') - b = f(1.0, x, y, a=a, overwrite_a=True) - assert_allclose(a, resxy, rtol=rtol) - - b = f(2.0, x, y, a=a) - assert_(a is not b) - assert_allclose(b, 3*resxy, rtol=rtol) - - assert_raises(Exception, f, 1.0, x, y, incx=0) - assert_raises(Exception, f, 1.0, x, y, offx=5) - assert_raises(Exception, f, 1.0, x, y, offx=-2) - assert_raises(Exception, f, 1.0, x, y, incy=0) - assert_raises(Exception, f, 1.0, x, y, offy=5) - assert_raises(Exception, f, 1.0, x, y, offy=-2) - assert_raises(Exception, f, 1.0, x, y, n=-2) - assert_raises(Exception, f, 1.0, x, y, n=5) - assert_raises(Exception, f, 1.0, x, y, lower=2) - assert_raises(Exception, f, 1.0, x, y, - a=np.zeros((2, 2), 'd', 'F')) - - def test_her2(self): + rtol = np.finfo(dtype).eps + + assert_allclose(f(1.0, x, y), resxy, rtol=rtol) + assert_allclose(f(1.0, x, y, n=3), resxy[:3, :3], rtol=rtol) + assert_allclose(f(1.0, x, y, lower=True), resxy.T, rtol=rtol) + + assert_allclose(f(1.0, q, q, incx=2, offx=2, incy=2, offy=10), + resxy, rtol=rtol) + assert_allclose(f(1.0, q, q, incx=2, offx=2, incy=2, offy=10, n=3), + resxy[:3, :3], rtol=rtol) + # negative increments imply reversed vectors in blas + assert_allclose(f(1.0, q, q, incx=-2, offx=2, incy=-2, offy=10), + resxy_reverse, rtol=rtol) + + a = np.zeros((4, 4), dtype, 'F') + b = f(1.0, x, y, a=a, overwrite_a=True) + assert_allclose(a, resxy, rtol=rtol) + + b = f(2.0, x, y, a=a) + assert a is not b + assert_allclose(b, 3*resxy, rtol=rtol) + + assert_raises(Exception, f, 1.0, x, y, incx=0) + assert_raises(Exception, f, 1.0, x, y, offx=5) + assert_raises(Exception, f, 1.0, x, y, offx=-2) + assert_raises(Exception, f, 1.0, x, y, incy=0) + assert_raises(Exception, f, 1.0, x, y, offy=5) + assert_raises(Exception, f, 1.0, x, y, offy=-2) + assert_raises(Exception, f, 1.0, x, y, n=-2) + assert_raises(Exception, f, 1.0, x, y, n=5) + assert_raises(Exception, f, 1.0, x, y, lower=2) + assert_raises(Exception, f, 1.0, x, y, a=np.zeros((2, 2), 'd', 'F')) + + @parametrize_blas(fblas, "her2", "cz") + def test_her2(self, f, dtype): x = np.arange(1, 9, dtype='d').view('D') y = np.arange(9, 17, dtype='d').view('D') resxy = x[:, np.newaxis] * y.conj() + y[:, np.newaxis] * x.conj() @@ -418,416 +356,400 @@ def test_her2(self): u = np.c_[np.zeros(4), x, np.zeros(4)].ravel() v = np.c_[np.zeros(4), y, np.zeros(4)].ravel() - for p, rtol in zip('cz', [1e-7, 1e-14]): - f = getattr(fblas, p+'her2', None) - if f is None: - continue - assert_allclose(f(1.0, x, y), resxy, rtol=rtol) - assert_allclose(f(1.0, x, y, n=3), resxy[:3, :3], rtol=rtol) - assert_allclose(f(1.0, x, y, lower=True), resxy.T.conj(), - rtol=rtol) - - assert_allclose(f(1.0, u, v, incx=3, offx=1, incy=3, offy=1), - resxy, rtol=rtol) - assert_allclose(f(1.0, u, v, incx=3, offx=1, incy=3, offy=1, n=3), - resxy[:3, :3], rtol=rtol) - # negative increments imply reversed vectors in blas - assert_allclose(f(1.0, u, v, incx=-3, offx=1, incy=-3, offy=1), - resxy_reverse, rtol=rtol) - - a = np.zeros((4, 4), 'F' if p == 'c' else 'D', 'F') - b = f(1.0, x, y, a=a, overwrite_a=True) - assert_allclose(a, resxy, rtol=rtol) - - b = f(2.0, x, y, a=a) - assert_(a is not b) - assert_allclose(b, 3*resxy, rtol=rtol) - - assert_raises(Exception, f, 1.0, x, y, incx=0) - assert_raises(Exception, f, 1.0, x, y, offx=5) - assert_raises(Exception, f, 1.0, x, y, offx=-2) - assert_raises(Exception, f, 1.0, x, y, incy=0) - assert_raises(Exception, f, 1.0, x, y, offy=5) - assert_raises(Exception, f, 1.0, x, y, offy=-2) - assert_raises(Exception, f, 1.0, x, y, n=-2) - assert_raises(Exception, f, 1.0, x, y, n=5) - assert_raises(Exception, f, 1.0, x, y, lower=2) - assert_raises(Exception, f, 1.0, x, y, - a=np.zeros((2, 2), 'd', 'F')) - - def test_gbmv(self): + rtol = np.finfo(dtype).eps + + assert_allclose(f(1.0, x, y), resxy, rtol=rtol) + assert_allclose(f(1.0, x, y, n=3), resxy[:3, :3], rtol=rtol) + assert_allclose(f(1.0, x, y, lower=True), resxy.T.conj(), + rtol=rtol) + + assert_allclose(f(1.0, u, v, incx=3, offx=1, incy=3, offy=1), + resxy, rtol=rtol) + assert_allclose(f(1.0, u, v, incx=3, offx=1, incy=3, offy=1, n=3), + resxy[:3, :3], rtol=rtol) + # negative increments imply reversed vectors in blas + assert_allclose(f(1.0, u, v, incx=-3, offx=1, incy=-3, offy=1), + resxy_reverse, rtol=rtol) + + a = np.zeros((4, 4), dtype, 'F') + b = f(1.0, x, y, a=a, overwrite_a=True) + assert_allclose(a, resxy, rtol=rtol) + + b = f(2.0, x, y, a=a) + assert a is not b + assert_allclose(b, 3*resxy, rtol=rtol) + + assert_raises(Exception, f, 1.0, x, y, incx=0) + assert_raises(Exception, f, 1.0, x, y, offx=5) + assert_raises(Exception, f, 1.0, x, y, offx=-2) + assert_raises(Exception, f, 1.0, x, y, incy=0) + assert_raises(Exception, f, 1.0, x, y, offy=5) + assert_raises(Exception, f, 1.0, x, y, offy=-2) + assert_raises(Exception, f, 1.0, x, y, n=-2) + assert_raises(Exception, f, 1.0, x, y, n=5) + assert_raises(Exception, f, 1.0, x, y, lower=2) + assert_raises(Exception, f, 1.0, x, y, + a=np.zeros((2, 2), 'd', 'F')) + + @pytest.mark.parametrize("dtype", DTYPES) + def test_gbmv(self, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - n = 7 - m = 5 - kl = 1 - ku = 2 - # fake a banded matrix via toeplitz - A = toeplitz(append(rng.random(kl+1), zeros(m-kl-1)), - append(rng.random(ku+1), zeros(n-ku-1))) - A = A.astype(dtype) - Ab = zeros((kl+ku+1, n), dtype=dtype) - - # Form the banded storage - Ab[2, :5] = A[0, 0] # diag - Ab[1, 1:6] = A[0, 1] # sup1 - Ab[0, 2:7] = A[0, 2] # sup2 - Ab[3, :4] = A[1, 0] # sub1 - - x = rng.random(n).astype(dtype) - y = rng.random(m).astype(dtype) - alpha, beta = dtype(3), dtype(-5) - - func, = get_blas_funcs(('gbmv',), dtype=dtype) - y1 = func(m=m, n=n, ku=ku, kl=kl, alpha=alpha, a=Ab, - x=x, y=y, beta=beta) - y2 = alpha * A.dot(x) + beta * y - assert_array_almost_equal(y1, y2) - - y1 = func(m=m, n=n, ku=ku, kl=kl, alpha=alpha, a=Ab, - x=y, y=x, beta=beta, trans=1) - y2 = alpha * A.T.dot(y) + beta * x - assert_array_almost_equal(y1, y2) - - def test_sbmv_hbmv(self): + n = 7 + m = 5 + kl = 1 + ku = 2 + # fake a banded matrix via toeplitz + A = toeplitz(append(rng.random(kl+1), zeros(m-kl-1)), + append(rng.random(ku+1), zeros(n-ku-1))) + A = A.astype(dtype) + Ab = zeros((kl+ku+1, n), dtype=dtype) + + # Form the banded storage + Ab[2, :5] = A[0, 0] # diag + Ab[1, 1:6] = A[0, 1] # sup1 + Ab[0, 2:7] = A[0, 2] # sup2 + Ab[3, :4] = A[1, 0] # sub1 + + x = rng.random(n).astype(dtype) + y = rng.random(m).astype(dtype) + alpha, beta = dtype(3), dtype(-5) + + func, = get_blas_funcs(('gbmv',), dtype=dtype) + y1 = func(m=m, n=n, ku=ku, kl=kl, alpha=alpha, a=Ab, + x=x, y=y, beta=beta) + y2 = alpha * A.dot(x) + beta * y + assert_array_almost_equal(y1, y2) + + y1 = func(m=m, n=n, ku=ku, kl=kl, alpha=alpha, a=Ab, + x=y, y=x, beta=beta, trans=1) + y2 = alpha * A.T.dot(y) + beta * x + assert_array_almost_equal(y1, y2) + + @pytest.mark.parametrize("dtype", DTYPES) + def test_sbmv_hbmv(self, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - n = 6 - k = 2 - A = zeros((n, n), dtype=dtype) - Ab = zeros((k+1, n), dtype=dtype) - - # Form the array and its packed banded storage - A[arange(n), arange(n)] = rng.random(n) - for ind2 in range(1, k+1): - temp = rng.random(n-ind2) - A[arange(n-ind2), arange(ind2, n)] = temp - Ab[-1-ind2, ind2:] = temp - A = A.astype(dtype) - A = A + A.T if ind < 2 else A + A.conj().T - Ab[-1, :] = diag(A) - x = rng.random(n).astype(dtype) - y = rng.random(n).astype(dtype) - alpha, beta = dtype(1.25), dtype(3) - - if ind > 1: - func, = get_blas_funcs(('hbmv',), dtype=dtype) - else: - func, = get_blas_funcs(('sbmv',), dtype=dtype) - y1 = func(k=k, alpha=alpha, a=Ab, x=x, y=y, beta=beta) - y2 = alpha * A.dot(x) + beta * y - assert_array_almost_equal(y1, y2) - - def test_spmv_hpmv(self): - rng = np.random.default_rng(12345698) - for ind, dtype in enumerate(DTYPES+COMPLEX_DTYPES): - n = 3 - A = rng.random((n, n)).astype(dtype) - if ind > 1: - A += rng.random((n, n))*1j - A = A.astype(dtype) - A = A + A.T if ind < 4 else A + A.conj().T - c, r = tril_indices(n) - Ap = A[r, c] - x = rng.random(n).astype(dtype) - y = rng.random(n).astype(dtype) - xlong = arange(2*n).astype(dtype) - ylong = ones(2*n).astype(dtype) - alpha, beta = dtype(1.25), dtype(2) - - if ind > 3: - func, = get_blas_funcs(('hpmv',), dtype=dtype) - else: - func, = get_blas_funcs(('spmv',), dtype=dtype) - y1 = func(n=n, alpha=alpha, ap=Ap, x=x, y=y, beta=beta) - y2 = alpha * A.dot(x) + beta * y - assert_array_almost_equal(y1, y2) - - # Test inc and offsets - y1 = func(n=n-1, alpha=alpha, beta=beta, x=xlong, y=ylong, ap=Ap, - incx=2, incy=2, offx=n, offy=n) - y2 = (alpha * A[:-1, :-1]).dot(xlong[3::2]) + beta * ylong[3::2] - assert_array_almost_equal(y1[3::2], y2) - assert_almost_equal(y1[4], ylong[4]) - - def test_spr_hpr(self): + n = 6 + k = 2 + A = zeros((n, n), dtype=dtype) + Ab = zeros((k+1, n), dtype=dtype) + + # Form the array and its packed banded storage + A[arange(n), arange(n)] = rng.random(n) + for ind2 in range(1, k+1): + temp = rng.random(n-ind2) + A[arange(n-ind2), arange(ind2, n)] = temp + Ab[-1-ind2, ind2:] = temp + A = A.astype(dtype) + if dtype in COMPLEX_DTYPES: + A += A.conj().T + func, = get_blas_funcs(('hbmv',), dtype=dtype) + else: + A += A.T + func, = get_blas_funcs(('sbmv',), dtype=dtype) + + Ab[-1, :] = diag(A) + x = rng.random(n).astype(dtype) + y = rng.random(n).astype(dtype) + alpha, beta = dtype(1.25), dtype(3) + + y1 = func(k=k, alpha=alpha, a=Ab, x=x, y=y, beta=beta) + y2 = alpha * A.dot(x) + beta * y + assert_array_almost_equal(y1, y2) + + @pytest.mark.parametrize("fname,dtype", [ + *[('spmv', dtype) for dtype in REAL_DTYPES + COMPLEX_DTYPES], + *[('hpmv', dtype) for dtype in COMPLEX_DTYPES], + ]) + def test_spmv_hpmv(self, fname, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES+COMPLEX_DTYPES): - n = 3 - A = rng.random((n, n)).astype(dtype) - if ind > 1: - A += rng.random((n, n))*1j - A = A.astype(dtype) - A = A + A.T if ind < 4 else A + A.conj().T - c, r = tril_indices(n) - Ap = A[r, c] - x = rng.random(n).astype(dtype) - alpha = (DTYPES+COMPLEX_DTYPES)[mod(ind, 4)](2.5) - - if ind > 3: - func, = get_blas_funcs(('hpr',), dtype=dtype) - y2 = alpha * x[:, None].dot(x[None, :].conj()) + A - else: - func, = get_blas_funcs(('spr',), dtype=dtype) - y2 = alpha * x[:, None].dot(x[None, :]) + A - - y1 = func(n=n, alpha=alpha, ap=Ap, x=x) - y1f = zeros((3, 3), dtype=dtype) - y1f[r, c] = y1 - y1f[c, r] = y1.conj() if ind > 3 else y1 - assert_array_almost_equal(y1f, y2) - - def test_spr2_hpr2(self): + n = 3 + A = rng.random((n, n)).astype(dtype) + if dtype in COMPLEX_DTYPES: + A += rng.random((n, n))*1j + A += A.T if fname == 'spmv' else A.conj().T + c, r = tril_indices(n) + Ap = A[r, c] + x = rng.random(n).astype(dtype) + y = rng.random(n).astype(dtype) + xlong = arange(2*n).astype(dtype) + ylong = ones(2*n).astype(dtype) + alpha, beta = dtype(1.25), dtype(2) + + func, = get_blas_funcs((fname,), dtype=dtype) + y1 = func(n=n, alpha=alpha, ap=Ap, x=x, y=y, beta=beta) + y2 = alpha * A.dot(x) + beta * y + assert_array_almost_equal(y1, y2) + + # Test inc and offsets + y1 = func(n=n-1, alpha=alpha, beta=beta, x=xlong, y=ylong, ap=Ap, + incx=2, incy=2, offx=n, offy=n) + y2 = (alpha * A[:-1, :-1]).dot(xlong[3::2]) + beta * ylong[3::2] + assert_array_almost_equal(y1[3::2], y2) + assert_almost_equal(y1[4], ylong[4]) + + @pytest.mark.parametrize("fname,dtype", [ + *[('spr', dtype) for dtype in REAL_DTYPES + COMPLEX_DTYPES], + *[('hpr', dtype) for dtype in COMPLEX_DTYPES], + ]) + def test_spr_hpr(self, fname, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - n = 3 - A = rng.random((n, n)).astype(dtype) - if ind > 1: - A += rng.random((n, n))*1j - A = A.astype(dtype) - A = A + A.T if ind < 2 else A + A.conj().T - c, r = tril_indices(n) - Ap = A[r, c] - x = rng.random(n).astype(dtype) - y = rng.random(n).astype(dtype) - alpha = dtype(2) - - if ind > 1: - func, = get_blas_funcs(('hpr2',), dtype=dtype) - else: - func, = get_blas_funcs(('spr2',), dtype=dtype) - - u = alpha.conj() * x[:, None].dot(y[None, :].conj()) - y2 = A + u + u.conj().T - y1 = func(n=n, alpha=alpha, x=x, y=y, ap=Ap) - y1f = zeros((3, 3), dtype=dtype) - y1f[r, c] = y1 - y1f[[1, 2, 2], [0, 0, 1]] = y1[[1, 3, 4]].conj() - assert_array_almost_equal(y1f, y2) - - def test_tbmv(self): + n = 3 + A = rng.random((n, n)).astype(dtype) + if dtype in COMPLEX_DTYPES: + A += rng.random((n, n))*1j + A += A.T if fname == 'spr' else A.conj().T + c, r = tril_indices(n) + Ap = A[r, c] + x = rng.random(n).astype(dtype) + + alpha = np.finfo(dtype).dtype.type(2.5) + if fname == 'hpr': + func, = get_blas_funcs(('hpr',), dtype=dtype) + y2 = alpha * x[:, None].dot(x[None, :].conj()) + A + else: + func, = get_blas_funcs(('spr',), dtype=dtype) + y2 = alpha * x[:, None].dot(x[None, :]) + A + + y1 = func(n=n, alpha=alpha, ap=Ap, x=x) + y1f = zeros((3, 3), dtype=dtype) + y1f[r, c] = y1 + y1f[c, r] = y1.conj() if fname == 'hpr' else y1 + assert_array_almost_equal(y1f, y2) + + @pytest.mark.parametrize("dtype", DTYPES) + def test_spr2_hpr2(self, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - n = 10 - k = 3 - x = rng.random(n).astype(dtype) - A = zeros((n, n), dtype=dtype) - # Banded upper triangular array - for sup in range(k+1): - A[arange(n-sup), arange(sup, n)] = rng.random(n-sup) - - # Add complex parts for c,z - if ind > 1: - A[nonzero(A)] += 1j * rng.random((k+1)*n-(k*(k+1)//2)).astype(dtype) - - # Form the banded storage - Ab = zeros((k+1, n), dtype=dtype) - for row in range(k+1): - Ab[-row-1, row:] = diag(A, k=row) - func, = get_blas_funcs(('tbmv',), dtype=dtype) - - y1 = func(k=k, a=Ab, x=x) - y2 = A.dot(x) - assert_array_almost_equal(y1, y2) - - y1 = func(k=k, a=Ab, x=x, diag=1) - A[arange(n), arange(n)] = dtype(1) - y2 = A.dot(x) - assert_array_almost_equal(y1, y2) - - y1 = func(k=k, a=Ab, x=x, diag=1, trans=1) - y2 = A.T.dot(x) - assert_array_almost_equal(y1, y2) - - y1 = func(k=k, a=Ab, x=x, diag=1, trans=2) - y2 = A.conj().T.dot(x) - assert_array_almost_equal(y1, y2) - - def test_tbsv(self): + n = 3 + A = rng.random((n, n)).astype(dtype) + if dtype in COMPLEX_DTYPES: + A += rng.random((n, n))*1j + A += A.conj().T + func, = get_blas_funcs(('hpr2',), dtype=dtype) + else: + A += A.T + func, = get_blas_funcs(('spr2',), dtype=dtype) + + c, r = tril_indices(n) + Ap = A[r, c] + x = rng.random(n).astype(dtype) + y = rng.random(n).astype(dtype) + alpha = dtype(2) + + u = alpha.conj() * x[:, None].dot(y[None, :].conj()) + y2 = A + u + u.conj().T + y1 = func(n=n, alpha=alpha, x=x, y=y, ap=Ap) + y1f = zeros((3, 3), dtype=dtype) + y1f[r, c] = y1 + y1f[[1, 2, 2], [0, 0, 1]] = y1[[1, 3, 4]].conj() + assert_array_almost_equal(y1f, y2) + + @pytest.mark.parametrize("dtype", DTYPES) + def test_tbmv(self, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - n = 6 - k = 3 - x = rng.random(n).astype(dtype) - A = zeros((n, n), dtype=dtype) - # Banded upper triangular array - for sup in range(k+1): - A[arange(n-sup), arange(sup, n)] = rng.random(n-sup) - - # Add complex parts for c,z - if ind > 1: - A[nonzero(A)] += 1j * rng.random((k+1)*n-(k*(k+1)//2)).astype(dtype) - - # Form the banded storage - Ab = zeros((k+1, n), dtype=dtype) - for row in range(k+1): - Ab[-row-1, row:] = diag(A, k=row) - func, = get_blas_funcs(('tbsv',), dtype=dtype) - - y1 = func(k=k, a=Ab, x=x) - y2 = solve(A, x) - assert_array_almost_equal(y1, y2) - - y1 = func(k=k, a=Ab, x=x, diag=1) - A[arange(n), arange(n)] = dtype(1) - y2 = solve(A, x) - assert_array_almost_equal(y1, y2) - - y1 = func(k=k, a=Ab, x=x, diag=1, trans=1) - y2 = solve(A.T, x) - assert_array_almost_equal(y1, y2) - - y1 = func(k=k, a=Ab, x=x, diag=1, trans=2) - y2 = solve(A.conj().T, x) - assert_array_almost_equal(y1, y2) - - def test_tpmv(self): + n = 10 + k = 3 + x = rng.random(n).astype(dtype) + A = zeros((n, n), dtype=dtype) + # Banded upper triangular array + for sup in range(k+1): + A[arange(n-sup), arange(sup, n)] = rng.random(n-sup) + + # Add complex parts for c,z + if dtype in COMPLEX_DTYPES: + A[nonzero(A)] += 1j * rng.random((k+1)*n-(k*(k+1)//2)).astype(dtype) + + # Form the banded storage + Ab = zeros((k+1, n), dtype=dtype) + for row in range(k+1): + Ab[-row-1, row:] = diag(A, k=row) + func, = get_blas_funcs(('tbmv',), dtype=dtype) + + y1 = func(k=k, a=Ab, x=x) + y2 = A.dot(x) + assert_array_almost_equal(y1, y2) + + y1 = func(k=k, a=Ab, x=x, diag=1) + A[arange(n), arange(n)] = dtype(1) + y2 = A.dot(x) + assert_array_almost_equal(y1, y2) + + y1 = func(k=k, a=Ab, x=x, diag=1, trans=1) + y2 = A.T.dot(x) + assert_array_almost_equal(y1, y2) + + y1 = func(k=k, a=Ab, x=x, diag=1, trans=2) + y2 = A.conj().T.dot(x) + assert_array_almost_equal(y1, y2) + + @pytest.mark.parametrize("dtype", DTYPES) + def test_tbsv(self, dtype): + rng = np.random.default_rng(12345) + n = 6 + k = 3 + x = rng.random(n).astype(dtype) + A = zeros((n, n), dtype=dtype) + # Banded upper triangular array + for sup in range(k+1): + A[arange(n-sup), arange(sup, n)] = rng.random(n-sup) + + # Add complex parts for c,z + if dtype in COMPLEX_DTYPES: + A[nonzero(A)] += 1j * rng.random((k+1)*n-(k*(k+1)//2)).astype(dtype) + + # Form the banded storage + Ab = zeros((k+1, n), dtype=dtype) + for row in range(k+1): + Ab[-row-1, row:] = diag(A, k=row) + func, = get_blas_funcs(('tbsv',), dtype=dtype) + + y1 = func(k=k, a=Ab, x=x) + y2 = solve(A, x) + assert_array_almost_equal(y1, y2) + + y1 = func(k=k, a=Ab, x=x, diag=1) + A[arange(n), arange(n)] = dtype(1) + y2 = solve(A, x) + assert_array_almost_equal(y1, y2) + + y1 = func(k=k, a=Ab, x=x, diag=1, trans=1) + y2 = solve(A.T, x) + assert_array_almost_equal(y1, y2) + + y1 = func(k=k, a=Ab, x=x, diag=1, trans=2) + y2 = solve(A.conj().T, x) + assert_array_almost_equal(y1, y2) + + @pytest.mark.parametrize("dtype", DTYPES) + def test_tpmv(self, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - n = 10 - x = rng.random(n).astype(dtype) - # Upper triangular array - if ind < 2: - A = triu(rng.random((n, n))) - else: - A = triu(rng.random((n, n)) + rng.random((n, n))*1j) - - # Form the packed storage - c, r = tril_indices(n) - Ap = A[r, c] - func, = get_blas_funcs(('tpmv',), dtype=dtype) - - y1 = func(n=n, ap=Ap, x=x) - y2 = A.dot(x) - assert_array_almost_equal(y1, y2) - - y1 = func(n=n, ap=Ap, x=x, diag=1) - A[arange(n), arange(n)] = dtype(1) - y2 = A.dot(x) - assert_array_almost_equal(y1, y2) - - y1 = func(n=n, ap=Ap, x=x, diag=1, trans=1) - y2 = A.T.dot(x) - assert_array_almost_equal(y1, y2) - - y1 = func(n=n, ap=Ap, x=x, diag=1, trans=2) - y2 = A.conj().T.dot(x) - assert_array_almost_equal(y1, y2) - - def test_tpsv(self): + n = 10 + x = rng.random(n).astype(dtype) + # Upper triangular array + if dtype in COMPLEX_DTYPES: + A = triu(rng.random((n, n)) + rng.random((n, n))*1j) + else: + A = triu(rng.random((n, n))) + + # Form the packed storage + c, r = tril_indices(n) + Ap = A[r, c] + func, = get_blas_funcs(('tpmv',), dtype=dtype) + + y1 = func(n=n, ap=Ap, x=x) + y2 = A.dot(x) + assert_array_almost_equal(y1, y2) + + y1 = func(n=n, ap=Ap, x=x, diag=1) + A[arange(n), arange(n)] = dtype(1) + y2 = A.dot(x) + assert_array_almost_equal(y1, y2) + + y1 = func(n=n, ap=Ap, x=x, diag=1, trans=1) + y2 = A.T.dot(x) + assert_array_almost_equal(y1, y2) + + y1 = func(n=n, ap=Ap, x=x, diag=1, trans=2) + y2 = A.conj().T.dot(x) + assert_array_almost_equal(y1, y2) + + @pytest.mark.parametrize("dtype", DTYPES) + def test_tpsv(self, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - n = 10 - x = rng.random(n).astype(dtype) - # Upper triangular array - if ind < 2: - A = triu(rng.random((n, n))) - else: - A = triu(rng.random((n, n)) + rng.random((n, n))*1j) - A += eye(n) - # Form the packed storage - c, r = tril_indices(n) - Ap = A[r, c] - func, = get_blas_funcs(('tpsv',), dtype=dtype) - - y1 = func(n=n, ap=Ap, x=x) - y2 = solve(A, x) - assert_array_almost_equal(y1, y2) - - y1 = func(n=n, ap=Ap, x=x, diag=1) - A[arange(n), arange(n)] = dtype(1) - y2 = solve(A, x) - assert_array_almost_equal(y1, y2) - - y1 = func(n=n, ap=Ap, x=x, diag=1, trans=1) - y2 = solve(A.T, x) - assert_array_almost_equal(y1, y2) - - y1 = func(n=n, ap=Ap, x=x, diag=1, trans=2) - y2 = solve(A.conj().T, x) - assert_array_almost_equal(y1, y2) - - def test_trmv(self): + n = 10 + x = rng.random(n).astype(dtype) + # Upper triangular array + if dtype in COMPLEX_DTYPES: + A = triu(rng.random((n, n)) + rng.random((n, n))*1j) + else: + A = triu(rng.random((n, n))) + A += eye(n) + # Form the packed storage + c, r = tril_indices(n) + Ap = A[r, c] + func, = get_blas_funcs(('tpsv',), dtype=dtype) + + y1 = func(n=n, ap=Ap, x=x) + y2 = solve(A, x) + assert_array_almost_equal(y1, y2) + + y1 = func(n=n, ap=Ap, x=x, diag=1) + A[arange(n), arange(n)] = dtype(1) + y2 = solve(A, x) + assert_array_almost_equal(y1, y2) + + y1 = func(n=n, ap=Ap, x=x, diag=1, trans=1) + y2 = solve(A.T, x) + assert_array_almost_equal(y1, y2) + + y1 = func(n=n, ap=Ap, x=x, diag=1, trans=2) + y2 = solve(A.conj().T, x) + assert_array_almost_equal(y1, y2) + + @pytest.mark.parametrize("dtype", DTYPES) + def test_trmv(self, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - n = 3 - A = (rng.random((n, n))+eye(n)).astype(dtype) - x = rng.random(3).astype(dtype) - func, = get_blas_funcs(('trmv',), dtype=dtype) - - y1 = func(a=A, x=x) - y2 = triu(A).dot(x) - assert_array_almost_equal(y1, y2) - - y1 = func(a=A, x=x, diag=1) - A[arange(n), arange(n)] = dtype(1) - y2 = triu(A).dot(x) - assert_array_almost_equal(y1, y2) - - y1 = func(a=A, x=x, diag=1, trans=1) - y2 = triu(A).T.dot(x) - assert_array_almost_equal(y1, y2) - - y1 = func(a=A, x=x, diag=1, trans=2) - y2 = triu(A).conj().T.dot(x) - assert_array_almost_equal(y1, y2) - - def test_trsv(self): + n = 3 + A = (rng.random((n, n))+eye(n)).astype(dtype) + x = rng.random(3).astype(dtype) + func, = get_blas_funcs(('trmv',), dtype=dtype) + + y1 = func(a=A, x=x) + y2 = triu(A).dot(x) + assert_array_almost_equal(y1, y2) + + y1 = func(a=A, x=x, diag=1) + A[arange(n), arange(n)] = dtype(1) + y2 = triu(A).dot(x) + assert_array_almost_equal(y1, y2) + + y1 = func(a=A, x=x, diag=1, trans=1) + y2 = triu(A).T.dot(x) + assert_array_almost_equal(y1, y2) + + y1 = func(a=A, x=x, diag=1, trans=2) + y2 = triu(A).conj().T.dot(x) + assert_array_almost_equal(y1, y2) + + @pytest.mark.parametrize("dtype", DTYPES) + def test_trsv(self, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - n = 15 - A = (rng.random((n, n))+eye(n)).astype(dtype) - x = rng.random(n).astype(dtype) - func, = get_blas_funcs(('trsv',), dtype=dtype) + n = 15 + A = (rng.random((n, n))+eye(n)).astype(dtype) + x = rng.random(n).astype(dtype) + func, = get_blas_funcs(('trsv',), dtype=dtype) - y1 = func(a=A, x=x) - y2 = solve(triu(A), x) - assert_array_almost_equal(y1, y2) + y1 = func(a=A, x=x) + y2 = solve(triu(A), x) + assert_array_almost_equal(y1, y2) - y1 = func(a=A, x=x, lower=1) - y2 = solve(tril(A), x) - assert_array_almost_equal(y1, y2) + y1 = func(a=A, x=x, lower=1) + y2 = solve(tril(A), x) + assert_array_almost_equal(y1, y2) - y1 = func(a=A, x=x, diag=1) - A[arange(n), arange(n)] = dtype(1) - y2 = solve(triu(A), x) - assert_array_almost_equal(y1, y2) + y1 = func(a=A, x=x, diag=1) + A[arange(n), arange(n)] = dtype(1) + y2 = solve(triu(A), x) + assert_array_almost_equal(y1, y2) - y1 = func(a=A, x=x, diag=1, trans=1) - y2 = solve(triu(A).T, x) - assert_array_almost_equal(y1, y2) + y1 = func(a=A, x=x, diag=1, trans=1) + y2 = solve(triu(A).T, x) + assert_array_almost_equal(y1, y2) - y1 = func(a=A, x=x, diag=1, trans=2) - y2 = solve(triu(A).conj().T, x) - assert_array_almost_equal(y1, y2) + y1 = func(a=A, x=x, diag=1, trans=2) + y2 = solve(triu(A).conj().T, x) + assert_array_almost_equal(y1, y2) class TestFBLAS3Simple: - - def test_gemm(self): - for p in 'sd': - f = getattr(fblas, p+'gemm', None) - if f is None: - continue - assert_array_almost_equal(f(3, [3], [-4]), [[-36]]) - assert_array_almost_equal(f(3, [3], [-4], 3, [5]), [-21]) - for p in 'cz': - f = getattr(fblas, p+'gemm', None) - if f is None: - continue + @parametrize_blas(fblas, "gemm", "sdcz") + def test_gemm(self, f, dtype): + assert_array_almost_equal(f(3, [3], [-4]), [[-36]]) + assert_array_almost_equal(f(3, [3], [-4], 3, [5]), [-21]) + if dtype in COMPLEX_DTYPES: assert_array_almost_equal(f(3j, [3-4j], [-4]), [[-48-36j]]) assert_array_almost_equal(f(3j, [3-4j], [-4], 3, [5j]), [-48-21j]) -def _get_func(func, ps='sdzc'): - """Just a helper: return a specified BLAS function w/typecode.""" - for p in ps: - f = getattr(fblas, p+func, None) - if f is None: - continue - yield f - - class TestBLAS3Symm: def setup_method(self): @@ -839,38 +761,37 @@ def setup_method(self): self.t = np.array([[2., -1., 8.], [3., 0., 9.]]) - def test_symm(self): - for f in _get_func('symm'): - res = f(a=self.a, b=self.b, c=self.c, alpha=1., beta=1.) - assert_array_almost_equal(res, self.t) + @parametrize_blas(fblas, "symm", "sdcz") + def test_symm(self, f, dtype): + res = f(a=self.a, b=self.b, c=self.c, alpha=1., beta=1.) + assert_array_almost_equal(res, self.t) - res = f(a=self.a.T, b=self.b, lower=1, c=self.c, alpha=1., beta=1.) - assert_array_almost_equal(res, self.t) + res = f(a=self.a.T, b=self.b, lower=1, c=self.c, alpha=1., beta=1.) + assert_array_almost_equal(res, self.t) - res = f(a=self.a, b=self.b.T, side=1, c=self.c.T, - alpha=1., beta=1.) - assert_array_almost_equal(res, self.t.T) + res = f(a=self.a, b=self.b.T, side=1, c=self.c.T, + alpha=1., beta=1.) + assert_array_almost_equal(res, self.t.T) - def test_summ_wrong_side(self): - f = getattr(fblas, 'dsymm', None) - if f is not None: - assert_raises(Exception, f, **{'a': self.a, 'b': self.b, - 'alpha': 1, 'side': 1}) - # `side=1` means C <- B*A, hence shapes of A and B are to be - # compatible. Otherwise, f2py exception is raised + @parametrize_blas(fblas, "symm", "sdcz") + def test_symm_wrong_side(self, f, dtype): + """`side=1` means C <- B*A, hence shapes of A and B are to be + compatible. Otherwise, f2py exception is raised. + """ + # FIXME narrow down to _fblas.error + with pytest.raises(Exception): + f(a=self.a, b=self.b, alpha=1, side=1) - def test_symm_wrong_uplo(self): + @parametrize_blas(fblas, "symm", "sdcz") + def test_symm_wrong_uplo(self, f, dtype): """SYMM only considers the upper/lower part of A. Hence setting wrong value for `lower` (default is lower=0, meaning upper triangle) gives a wrong result. """ - f = getattr(fblas, 'dsymm', None) - if f is not None: - res = f(a=self.a, b=self.b, c=self.c, alpha=1., beta=1.) - assert np.allclose(res, self.t) - - res = f(a=self.a, b=self.b, lower=1, c=self.c, alpha=1., beta=1.) - assert not np.allclose(res, self.t) + res = f(a=self.a, b=self.b, c=self.c, alpha=1., beta=1.) + assert np.allclose(res, self.t) + res = f(a=self.a, b=self.b, lower=1, c=self.c, alpha=1., beta=1.) + assert not np.allclose(res, self.t) class TestBLAS3Syrk: @@ -884,29 +805,28 @@ def setup_method(self): self.tt = np.array([[5., 6.], [6., 13.]]) - def test_syrk(self): - for f in _get_func('syrk'): - c = f(a=self.a, alpha=1.) - assert_array_almost_equal(np.triu(c), np.triu(self.t)) + @parametrize_blas(fblas, "syrk", "sdcz") + def test_syrk(self, f, dtype): + c = f(a=self.a, alpha=1.) + assert_array_almost_equal(np.triu(c), np.triu(self.t)) - c = f(a=self.a, alpha=1., lower=1) - assert_array_almost_equal(np.tril(c), np.tril(self.t)) + c = f(a=self.a, alpha=1., lower=1) + assert_array_almost_equal(np.tril(c), np.tril(self.t)) - c0 = np.ones(self.t.shape) - c = f(a=self.a, alpha=1., beta=1., c=c0) - assert_array_almost_equal(np.triu(c), np.triu(self.t+c0)) + c0 = np.ones(self.t.shape) + c = f(a=self.a, alpha=1., beta=1., c=c0) + assert_array_almost_equal(np.triu(c), np.triu(self.t+c0)) - c = f(a=self.a, alpha=1., trans=1) - assert_array_almost_equal(np.triu(c), np.triu(self.tt)) + c = f(a=self.a, alpha=1., trans=1) + assert_array_almost_equal(np.triu(c), np.triu(self.tt)) # prints '0-th dimension must be fixed to 3 but got 5', # FIXME: suppress? - # FIXME: how to catch the _fblas.error? - def test_syrk_wrong_c(self): - f = getattr(fblas, 'dsyrk', None) - if f is not None: - assert_raises(Exception, f, **{'a': self.a, 'alpha': 1., - 'c': np.ones((5, 8))}) + @parametrize_blas(fblas, "syrk", "sdcz") + def test_syrk_wrong_c(self, f, dtype): + # FIXME narrow down to _fblas.error + with pytest.raises(Exception): + f(a=self.a, alpha=1., c=np.ones((5, 8))) # if C is supplied, it must have compatible dimensions @@ -924,29 +844,26 @@ def setup_method(self): self.tt = np.array([[0., 1.], [1., 6]]) - def test_syr2k(self): - for f in _get_func('syr2k'): - c = f(a=self.a, b=self.b, alpha=1.) - assert_array_almost_equal(np.triu(c), np.triu(self.t)) + @parametrize_blas(fblas, "syr2k", "sdcz") + def test_syr2k(self, f, dtype): + c = f(a=self.a, b=self.b, alpha=1.) + assert_array_almost_equal(np.triu(c), np.triu(self.t)) - c = f(a=self.a, b=self.b, alpha=1., lower=1) - assert_array_almost_equal(np.tril(c), np.tril(self.t)) + c = f(a=self.a, b=self.b, alpha=1., lower=1) + assert_array_almost_equal(np.tril(c), np.tril(self.t)) - c0 = np.ones(self.t.shape) - c = f(a=self.a, b=self.b, alpha=1., beta=1., c=c0) - assert_array_almost_equal(np.triu(c), np.triu(self.t+c0)) + c0 = np.ones(self.t.shape) + c = f(a=self.a, b=self.b, alpha=1., beta=1., c=c0) + assert_array_almost_equal(np.triu(c), np.triu(self.t+c0)) - c = f(a=self.a, b=self.b, alpha=1., trans=1) - assert_array_almost_equal(np.triu(c), np.triu(self.tt)) + c = f(a=self.a, b=self.b, alpha=1., trans=1) + assert_array_almost_equal(np.triu(c), np.triu(self.tt)) # prints '0-th dimension must be fixed to 3 but got 5', FIXME: suppress? - def test_syr2k_wrong_c(self): - f = getattr(fblas, 'dsyr2k', None) - if f is not None: - assert_raises(Exception, f, **{'a': self.a, - 'b': self.b, - 'alpha': 1., - 'c': np.zeros((15, 8))}) + @parametrize_blas(fblas, "syr2k", "sdcz") + def test_syr2k_wrong_c(self, f, dtype): + with pytest.raises(Exception): + f(a=self.a, b=self.b, alpha=1., c=np.zeros((15, 8))) # if C is supplied, it must have compatible dimensions @@ -957,41 +874,41 @@ def setup_method(self): self.sigma_y = np.array([[0., -1.j], [1.j, 0.]]) - def test_symm_zc(self): - for f in _get_func('symm', 'zc'): - # NB: a is symmetric w/upper diag of ONLY - res = f(a=self.sigma_y, b=self.sigma_y, alpha=1.) - assert_array_almost_equal(np.triu(res), np.diag([1, -1])) + @parametrize_blas(fblas, "symm", "zc") + def test_symm(self, f, dtype): + # NB: a is symmetric w/upper diag of ONLY + res = f(a=self.sigma_y, b=self.sigma_y, alpha=1.) + assert_array_almost_equal(np.triu(res), np.diag([1, -1])) - def test_hemm_zc(self): - for f in _get_func('hemm', 'zc'): - # NB: a is hermitian w/upper diag of ONLY - res = f(a=self.sigma_y, b=self.sigma_y, alpha=1.) - assert_array_almost_equal(np.triu(res), np.diag([1, 1])) + @parametrize_blas(fblas, "hemm", "zc") + def test_hemm(self, f, dtype): + # NB: a is hermitian w/upper diag of ONLY + res = f(a=self.sigma_y, b=self.sigma_y, alpha=1.) + assert_array_almost_equal(np.triu(res), np.diag([1, 1])) - def test_syrk_zr(self): - for f in _get_func('syrk', 'zc'): - res = f(a=self.sigma_y, alpha=1.) - assert_array_almost_equal(np.triu(res), np.diag([-1, -1])) + @parametrize_blas(fblas, "syrk", "zc") + def test_syrk(self, f, dtype): + res = f(a=self.sigma_y, alpha=1.) + assert_array_almost_equal(np.triu(res), np.diag([-1, -1])) - def test_herk_zr(self): - for f in _get_func('herk', 'zc'): - res = f(a=self.sigma_y, alpha=1.) - assert_array_almost_equal(np.triu(res), np.diag([1, 1])) + @parametrize_blas(fblas, "herk", "zc") + def test_herk(self, f, dtype): + res = f(a=self.sigma_y, alpha=1.) + assert_array_almost_equal(np.triu(res), np.diag([1, 1])) - def test_syr2k_zr(self): - for f in _get_func('syr2k', 'zc'): - res = f(a=self.sigma_y, b=self.sigma_y, alpha=1.) - assert_array_almost_equal(np.triu(res), 2.*np.diag([-1, -1])) + @parametrize_blas(fblas, "syr2k", "zc") + def test_syr2k_zr(self, f, dtype): + res = f(a=self.sigma_y, b=self.sigma_y, alpha=1.) + assert_array_almost_equal(np.triu(res), 2.*np.diag([-1, -1])) - def test_her2k_zr(self): - for f in _get_func('her2k', 'zc'): - res = f(a=self.sigma_y, b=self.sigma_y, alpha=1.) - assert_array_almost_equal(np.triu(res), 2.*np.diag([1, 1])) + @parametrize_blas(fblas, "her2k", "zc") + def test_her2k_zr(self, f, dtype): + res = f(a=self.sigma_y, b=self.sigma_y, alpha=1.) + assert_array_almost_equal(np.triu(res), 2.*np.diag([1, 1])) class TestTRMM: - """Quick and simple tests for dtrmm.""" + """Quick and simple tests for *trmm.""" def setup_method(self): self.a = np.array([[1., 2., ], @@ -1006,106 +923,104 @@ def setup_method(self): self.b2 = np.array([[1, 4], [2, 5], [3, 6], [7, 8], [9, 10]], order="f") - @pytest.mark.parametrize("dtype_", DTYPES) - def test_side(self, dtype_): - trmm = get_blas_funcs("trmm", dtype=dtype_) + @pytest.mark.parametrize("dtype", DTYPES) + def test_side(self, dtype): + trmm = get_blas_funcs("trmm", dtype=dtype) # Provide large A array that works for side=1 but not 0 (see gh-10841) assert_raises(Exception, trmm, 1.0, self.a2, self.b2) - res = trmm(1.0, self.a2.astype(dtype_), self.b2.astype(dtype_), + res = trmm(1.0, self.a2.astype(dtype), self.b2.astype(dtype), side=1) k = self.b2.shape[1] assert_allclose(res, self.b2 @ self.a2[:k, :k], rtol=0., - atol=100*np.finfo(dtype_).eps) - - def test_ab(self): - f = getattr(fblas, 'dtrmm', None) - if f is not None: - result = f(1., self.a, self.b) - # default a is upper triangular - expected = np.array([[13., 16., -5.], - [5., 6., -2.]]) - assert_array_almost_equal(result, expected) - - def test_ab_lower(self): - f = getattr(fblas, 'dtrmm', None) - if f is not None: - result = f(1., self.a, self.b, lower=True) - expected = np.array([[3., 4., -1.], - [-1., -2., 0.]]) # now a is lower triangular - assert_array_almost_equal(result, expected) - - def test_b_overwrites(self): - # BLAS dtrmm modifies B argument in-place. + atol=100*np.finfo(dtype).eps) + + @parametrize_blas(fblas, "trmm", "sdcz") + def test_ab(self, f, dtype): + result = f(1., self.a, self.b) + # default a is upper triangular + expected = np.array([[13., 16., -5.], + [ 5., 6., -2.]]) + assert_array_almost_equal(result, expected) + + @parametrize_blas(fblas, "trmm", "sdcz") + def test_ab_lower(self, f, dtype): + result = f(1., self.a, self.b, lower=True) + expected = np.array([[ 3., 4., -1.], + [-1., -2., 0.]]) # now a is lower triangular + assert_array_almost_equal(result, expected) + + @parametrize_blas(fblas, "trmm", "sdcz") + def test_b_overwrites(self, f, dtype): + # BLAS *trmm modifies B argument in-place. # Here the default is to copy, but this can be overridden - f = getattr(fblas, 'dtrmm', None) - if f is not None: - for overwr in [True, False]: - bcopy = self.b.copy() - result = f(1., self.a, bcopy, overwrite_b=overwr) - # C-contiguous arrays are copied - assert_(bcopy.flags.f_contiguous is False and - np.may_share_memory(bcopy, result) is False) - assert_equal(bcopy, self.b) - - bcopy = np.asfortranarray(self.b.copy()) # or just transpose it - result = f(1., self.a, bcopy, overwrite_b=True) - assert_(bcopy.flags.f_contiguous is True and - np.may_share_memory(bcopy, result) is True) - assert_array_almost_equal(bcopy, result) - - -def test_trsm(): + b = self.b.astype(dtype) + for overwr in [True, False]: + bcopy = b.copy() + result = f(1., self.a, bcopy, overwrite_b=overwr) + # C-contiguous arrays are copied + assert not bcopy.flags.f_contiguous + assert not np.may_share_memory(bcopy, result) + assert_equal(bcopy, b) + + bcopy = np.asfortranarray(b.copy()) # or just transpose it + result = f(1., self.a, bcopy, overwrite_b=True) + assert bcopy.flags.f_contiguous + assert np.may_share_memory(bcopy, result) + assert_array_almost_equal(bcopy, result) + + +@pytest.mark.parametrize("dtype", DTYPES) +def test_trsm(dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - tol = np.finfo(dtype).eps*1000 - func, = get_blas_funcs(('trsm',), dtype=dtype) - - # Test protection against size mismatches - A = rng.random((4, 5)).astype(dtype) - B = rng.random((4, 4)).astype(dtype) - alpha = dtype(1) - assert_raises(Exception, func, alpha, A, B) - assert_raises(Exception, func, alpha, A.T, B) - - n = 8 - m = 7 - alpha = dtype(-2.5) - if ind < 2: - A = rng.random((m, m)) + eye(m) - else: - A = (rng.random((m, m)) + rng.random((m, m))*1j) + eye(m) - A = A.astype(dtype) - Au = triu(A) - Al = tril(A) - B1 = rng.random((m, n)).astype(dtype) - B2 = rng.random((n, m)).astype(dtype) - - x1 = func(alpha=alpha, a=A, b=B1) - assert_equal(B1.shape, x1.shape) - x2 = solve(Au, alpha*B1) - assert_allclose(x1, x2, atol=tol) - - x1 = func(alpha=alpha, a=A, b=B1, trans_a=1) - x2 = solve(Au.T, alpha*B1) - assert_allclose(x1, x2, atol=tol) - - x1 = func(alpha=alpha, a=A, b=B1, trans_a=2) - x2 = solve(Au.conj().T, alpha*B1) - assert_allclose(x1, x2, atol=tol) - - x1 = func(alpha=alpha, a=A, b=B1, diag=1) - Au[arange(m), arange(m)] = dtype(1) - x2 = solve(Au, alpha*B1) - assert_allclose(x1, x2, atol=tol) - - x1 = func(alpha=alpha, a=A, b=B2, diag=1, side=1) - x2 = solve(Au.conj().T, alpha*B2.conj().T) - assert_allclose(x1, x2.conj().T, atol=tol) - - x1 = func(alpha=alpha, a=A, b=B2, diag=1, side=1, lower=1) - Al[arange(m), arange(m)] = dtype(1) - x2 = solve(Al.conj().T, alpha*B2.conj().T) - assert_allclose(x1, x2.conj().T, atol=tol) + tol = np.finfo(dtype).eps*1000 + func, = get_blas_funcs(('trsm',), dtype=dtype) + + # Test protection against size mismatches + A = rng.random((4, 5)).astype(dtype) + B = rng.random((4, 4)).astype(dtype) + alpha = dtype(1) + assert_raises(Exception, func, alpha, A, B) + assert_raises(Exception, func, alpha, A.T, B) + + n = 8 + m = 7 + alpha = dtype(-2.5) + if dtype in COMPLEX_DTYPES: + A = (rng.random((m, m)) + rng.random((m, m))*1j) + eye(m) + else: + A = rng.random((m, m)) + eye(m) + A = A.astype(dtype) + Au = triu(A) + Al = tril(A) + B1 = rng.random((m, n)).astype(dtype) + B2 = rng.random((n, m)).astype(dtype) + + x1 = func(alpha=alpha, a=A, b=B1) + assert_equal(B1.shape, x1.shape) + x2 = solve(Au, alpha*B1) + assert_allclose(x1, x2, atol=tol) + + x1 = func(alpha=alpha, a=A, b=B1, trans_a=1) + x2 = solve(Au.T, alpha*B1) + assert_allclose(x1, x2, atol=tol) + + x1 = func(alpha=alpha, a=A, b=B1, trans_a=2) + x2 = solve(Au.conj().T, alpha*B1) + assert_allclose(x1, x2, atol=tol) + + x1 = func(alpha=alpha, a=A, b=B1, diag=1) + Au[arange(m), arange(m)] = dtype(1) + x2 = solve(Au, alpha*B1) + assert_allclose(x1, x2, atol=tol) + + x1 = func(alpha=alpha, a=A, b=B2, diag=1, side=1) + x2 = solve(Au.conj().T, alpha*B2.conj().T) + assert_allclose(x1, x2.conj().T, atol=tol) + + x1 = func(alpha=alpha, a=A, b=B2, diag=1, side=1, lower=1) + Al[arange(m), arange(m)] = dtype(1) + x2 = solve(Al.conj().T, alpha*B2.conj().T) + assert_allclose(x1, x2.conj().T, atol=tol) @pytest.mark.xfail(run=False, diff --git a/scipy/linalg/tests/test_decomp.py b/scipy/linalg/tests/test_decomp.py index d6c85bbc3223..37200f67847c 100644 --- a/scipy/linalg/tests/test_decomp.py +++ b/scipy/linalg/tests/test_decomp.py @@ -1224,6 +1224,13 @@ def test_svd_gesdd_nofegfault(): svd(df) +def test_gesdd_nan_error_message(): + A = np.eye(2) + A[0, 0] = np.nan + with pytest.raises(ValueError, match="NaN"): + svd(A, check_finite=False) + + class TestSVDVals: @pytest.mark.parametrize('dt', [int, float, np.float32, complex, np.complex64]) diff --git a/scipy/linalg/tests/test_decomp_cossin.py b/scipy/linalg/tests/test_decomp_cossin.py index df112f0e4cf7..c43d3e643d3f 100644 --- a/scipy/linalg/tests/test_decomp_cossin.py +++ b/scipy/linalg/tests/test_decomp_cossin.py @@ -112,6 +112,20 @@ def test_cossin_error_non_iterable(): with pytest.raises(ValueError, match="containing the subblocks of X"): cossin(12j) +def test_cossin_error_invalid_shape(): + # Invalid x12 dimensions + p, q = 3, 4 + invalid_x12 = np.ones((p, q + 2)) + valid_ones = np.ones((p, q)) + with pytest.raises(ValueError, + match=r"Invalid x12 dimensions: desired \(3, 4\), got \(3, 6\)"): + cossin((valid_ones, invalid_x12, valid_ones, valid_ones)) + + # Invalid x21 dimensions + invalid_x21 = np.ones(p + 2) + with pytest.raises(ValueError, + match=r"Invalid x21 dimensions: desired \(3, 4\), got \(1, 5\)"): + cossin((valid_ones, valid_ones, invalid_x21, valid_ones)) def test_cossin_error_non_square(): with pytest.raises(ValueError, match="only supports square"): diff --git a/scipy/linalg/tests/test_matfuncs.py b/scipy/linalg/tests/test_matfuncs.py index 3b3a69267a51..e5233a553dc0 100644 --- a/scipy/linalg/tests/test_matfuncs.py +++ b/scipy/linalg/tests/test_matfuncs.py @@ -65,7 +65,7 @@ def test_nils(self): def test_defective1(self): a = array([[0.0,1,0,0],[1,0,1,0],[0,0,0,1],[0,0,1,0]]) - signm(a, disp=False) + signm(a) #XXX: what would be the correct result? def test_defective2(self): @@ -75,7 +75,7 @@ def test_defective2(self): [-10.0,6.0,-20.0,-18.0,-2.0], [-9.6,9.6,-25.5,-15.4,-2.0], [9.8,-4.8,18.0,18.2,2.0])) - signm(a, disp=False) + signm(a) #XXX: what would be the correct result? def test_defective3(self): @@ -86,7 +86,7 @@ def test_defective3(self): [0., 0., 0., 0., 3., 10., 0.], [0., 0., 0., 0., 0., -2., 25.], [0., 0., 0., 0., 0., 0., -3.]]) - signm(a, disp=False) + signm(a) #XXX: what would be the correct result? @@ -94,6 +94,7 @@ class TestLogM: def setup_method(self): self.rng = np.random.default_rng(1738098768840254) + @pytest.mark.filterwarnings("ignore:.*inaccurate.*:RuntimeWarning") def test_nils(self): a = array([[-2., 25., 0., 0., 0., 0., 0.], [0., -3., 10., 3., 3., 3., 0.], @@ -103,14 +104,15 @@ def test_nils(self): [0., 0., 0., 0., 0., -2., 25.], [0., 0., 0., 0., 0., 0., -3.]]) m = (identity(7)*3.1+0j)-a - logm(m, disp=False) + logm(m) #XXX: what would be the correct result? + @pytest.mark.filterwarnings("ignore:.*inaccurate.*:RuntimeWarning") def test_al_mohy_higham_2012_experiment_1_logm(self): # The logm completes the round trip successfully. # Note that the expm leg of the round trip is badly conditioned. A = _get_al_mohy_higham_2012_experiment_1() - A_logm, info = logm(A, disp=False) + A_logm = logm(A) A_round_trip = expm(A_logm) assert_allclose(A_round_trip, A, rtol=5e-5, atol=1e-14) @@ -118,7 +120,7 @@ def test_al_mohy_higham_2012_experiment_1_funm_log(self): # The raw funm with np.log does not complete the round trip. # Note that the expm leg of the round trip is badly conditioned. A = _get_al_mohy_higham_2012_experiment_1() - A_funm_log, info = funm(A, np.log, disp=False) + A_funm_log = funm(A, np.log) A_round_trip = expm(A_funm_log) assert_(not np.allclose(A_round_trip, A, rtol=1e-5, atol=1e-14)) @@ -152,7 +154,7 @@ def test_round_trip_random_complex(self): 1j*self.rng.standard_normal((n, n))) for scale in np.logspace(-4, 4, 9): M = M_unscaled * scale - M_logm, info = logm(M, disp=False) + M_logm = logm(M) M_round_trip = expm(M_logm) assert_allclose(M_round_trip, M) @@ -173,17 +175,17 @@ def test_logm_type_preservation_and_conversion(self): # check float type preservation A = np.array(matrix_as_list, dtype=float) - A_logm, info = logm(A, disp=False) + A_logm = logm(A) assert_(A_logm.dtype.char not in complex_dtype_chars) # check complex type preservation A = np.array(matrix_as_list, dtype=complex) - A_logm, info = logm(A, disp=False) + A_logm = logm(A) assert_(A_logm.dtype.char in complex_dtype_chars) # check float->complex type conversion for the matrix negation A = -np.array(matrix_as_list, dtype=float) - A_logm, info = logm(A, disp=False) + A_logm = logm(A) assert_(A_logm.dtype.char in complex_dtype_chars) def test_complex_spectrum_real_logm(self): @@ -194,7 +196,7 @@ def test_complex_spectrum_real_logm(self): X = np.array(M, dtype=dt) w = scipy.linalg.eigvals(X) assert_(1e-2 < np.absolute(w.imag).sum()) - Y, info = logm(X, disp=False) + Y = logm(X) assert_(np.issubdtype(Y.dtype, np.inexact)) assert_allclose(expm(Y), X) @@ -206,7 +208,7 @@ def test_real_mixed_sign_spectrum(self): [[0, 1], [1, 0]]): for dt in float, complex: A = np.array(M, dtype=dt) - A_logm, info = logm(A, disp=False) + A_logm, info = logm(A) assert_(np.issubdtype(A_logm.dtype, np.complexfloating)) @pytest.mark.thread_unsafe @@ -215,7 +217,7 @@ def test_exactly_singular(self): B = np.asarray([[1, 1], [0, 0]]) for M in A, A.T, B, B.T: expected_warning = _matfuncs_inv_ssq.LogmExactlySingularWarning - L, info = assert_warns(expected_warning, logm, M, disp=False) + L = assert_warns(expected_warning, logm, M) E = expm(L) assert_allclose(E, M, atol=1e-14) @@ -223,7 +225,7 @@ def test_exactly_singular(self): def test_nearly_singular(self): M = np.array([[1e-100]]) expected_warning = _matfuncs_inv_ssq.LogmNearlySingularWarning - L, info = assert_warns(expected_warning, logm, M, disp=False) + L = assert_warns(expected_warning, logm, M) E = expm(L) assert_allclose(E, M, atol=1e-14) @@ -629,7 +631,7 @@ def test_random_matrices_and_powers(self): # to compute the fractional matrix power. # These can be compared because they both use the principal branch. A_power = fractional_matrix_power(A, p) - A_logm, info = logm(A, disp=False) + A_logm = logm(A) A_power_expm_logm = expm(A_logm * p) assert_allclose(A_power, A_power_expm_logm) @@ -638,7 +640,7 @@ def test_al_mohy_higham_2012_experiment_1(self): A = _get_al_mohy_higham_2012_experiment_1() # Test remainder matrix power. - A_funm_sqrt, info = funm(A, np.sqrt, disp=False) + A_funm_sqrt = funm(A, np.sqrt) A_sqrtm = sqrtm(A) A_rem_power = _matfuncs_inv_ssq._remainder_matrix_power(A, 0.5) A_power = fractional_matrix_power(A, 0.5) @@ -1104,3 +1106,13 @@ def test_empty(self): b = np.empty((5, 0)) res = khatri_rao(a, b) assert_allclose(res, np.empty((15, 0))) + +@pytest.mark.parametrize('func', + [logm, sqrtm, signm]) +def test_disp_dep(func): + with pytest.deprecated_call(): + func(np.eye(2), disp=False) + +def test_blocksize_dep(): + with pytest.deprecated_call(): + sqrtm(np.eye(2), blocksize=10) diff --git a/scipy/linalg/tests/test_special_matrices.py b/scipy/linalg/tests/test_special_matrices.py index 41a93d3bf62d..813f09915bb6 100644 --- a/scipy/linalg/tests/test_special_matrices.py +++ b/scipy/linalg/tests/test_special_matrices.py @@ -8,7 +8,7 @@ from scipy.fft import fft from scipy.special import comb from scipy.linalg import (toeplitz, hankel, circulant, hadamard, leslie, dft, - companion, kron, block_diag, + companion, block_diag, helmert, hilbert, invhilbert, pascal, invpascal, fiedler, fiedler_companion, eigvals, convolution_matrix) @@ -207,36 +207,6 @@ def test_zerosized_matrix_arg(self): [0, 0, 6, 7, 0, 0]]) -class TestKron: - @pytest.mark.thread_unsafe - def test_dep(self): - with pytest.deprecated_call(match="`kron`"): - kron(np.array([[1, 2],[3, 4]]),np.array([[1, 1, 1]])) - - @pytest.mark.filterwarnings('ignore::DeprecationWarning') - def test_basic(self): - - a = kron(array([[1, 2], [3, 4]]), array([[1, 1, 1]])) - assert_array_equal(a, array([[1, 1, 1, 2, 2, 2], - [3, 3, 3, 4, 4, 4]])) - - m1 = array([[1, 2], [3, 4]]) - m2 = array([[10], [11]]) - a = kron(m1, m2) - expected = array([[10, 20], - [11, 22], - [30, 40], - [33, 44]]) - assert_array_equal(a, expected) - - @pytest.mark.filterwarnings('ignore::DeprecationWarning') - def test_empty(self): - m1 = np.empty((0, 2)) - m2 = np.empty((1, 3)) - a = kron(m1, m2) - assert_allclose(a, np.empty((0, 6))) - - class TestHelmert: def test_orthogonality(self): diff --git a/scipy/meson.build b/scipy/meson.build index 1bc50f5026e8..7043a875dcb5 100644 --- a/scipy/meson.build +++ b/scipy/meson.build @@ -36,10 +36,12 @@ thread_dep = dependency('threads', required: false) # version easily for >=2.0. _numpy_dep = dependency('numpy', required: false) f2py_freethreading_arg = [] -if _numpy_dep.found() - if _numpy_dep.version().version_compare('>=2.1.0') - f2py_freethreading_arg = ['--free-threading'] - endif +if _numpy_dep.found() and _numpy_dep.version().version_compare('>=2.1.0') + f2py_freethreading_arg = ['--free-threading'] + message('f2py free-threading enabled') +else + message('f2py free-threading disabled; need numpy >=2.1.0.') + message('See https://github.com/mesonbuild/meson/issues/14651') endif # NumPy include directory - needed in all submodules diff --git a/scipy/ndimage/_delegators.py b/scipy/ndimage/_delegators.py index 180bb839dbad..bac1286a56c0 100644 --- a/scipy/ndimage/_delegators.py +++ b/scipy/ndimage/_delegators.py @@ -25,7 +25,7 @@ """ import numpy as np from scipy._lib._array_api import array_namespace -from scipy.ndimage._ni_support import _skip_if_dtype, _skip_if_int +from scipy.ndimage._ni_support import _skip_if_dtype def affine_transform_signature( @@ -202,7 +202,7 @@ def maximum_filter1d_signature(input, size, axis=-1, output=None, *args, **kwds) def maximum_signature(input, labels=None, index=None): - return array_namespace(input, labels, _skip_if_int(index)) + return array_namespace(input, labels, index) minimum_signature = maximum_signature median_signature = maximum_signature diff --git a/scipy/ndimage/_filters.py b/scipy/ndimage/_filters.py index e26b9329570b..36700ec8a000 100644 --- a/scipy/ndimage/_filters.py +++ b/scipy/ndimage/_filters.py @@ -1146,7 +1146,7 @@ def generic_gradient_magnitude(input, derivative, output=None, Returns ------- - generic_gradient_matnitude : ndarray + generic_gradient_magnitude : ndarray Filtered array. Has the same shape as `input`. """ diff --git a/scipy/ndimage/_ni_support.py b/scipy/ndimage/_ni_support.py index 9b9e8b2c8feb..52c526347653 100644 --- a/scipy/ndimage/_ni_support.py +++ b/scipy/ndimage/_ni_support.py @@ -137,7 +137,3 @@ def _skip_if_dtype(arg): return None if issubclass(arg, np.generic) else arg else: return None if isinstance(arg, np.dtype) else arg - - -def _skip_if_int(arg): - return None if (arg is None or isinstance(arg, int)) else arg diff --git a/scipy/ndimage/src/_rank_filter_1d.cpp b/scipy/ndimage/src/_rank_filter_1d.cpp index d1dc8a602008..ebd72441d769 100644 --- a/scipy/ndimage/src/_rank_filter_1d.cpp +++ b/scipy/ndimage/src/_rank_filter_1d.cpp @@ -155,13 +155,10 @@ void _rank_filter(T *in_arr, int rank, int arr_len, int win_len, T *out_arr, int mode, T cval, int origin) { int i, arr_len_thresh, lim = (win_len - 1) / 2 - origin; int lim2 = arr_len - lim; + if (lim2 < 0) return; int offset; Mediator *m = MediatorNew(win_len, rank); - T *data = new T[win_len]; - for (int i = 0; i < win_len; ++i) { - data[i] = 0; - } - + T *data = new T[win_len](); switch (mode) { case REFLECT: @@ -227,7 +224,7 @@ void _rank_filter(T *in_arr, int rank, int arr_len, int win_len, T *out_arr, break; case MIRROR: arr_len_thresh = arr_len - 2; - for (i = 0; i < lim + 1; i++) { + for (i = 0; i < lim; i++) { MediatorInsert(data, m, in_arr[arr_len_thresh - i]); out_arr[lim2 + i] = data[m->heap[0]]; } diff --git a/scipy/ndimage/src/ni_interpolation.c b/scipy/ndimage/src/ni_interpolation.c index fe73b4bf47fe..f0f86a7f32c9 100644 --- a/scipy/ndimage/src/ni_interpolation.c +++ b/scipy/ndimage/src/ni_interpolation.c @@ -265,7 +265,7 @@ NI_GeometricTransform(PyArrayObject *input, int (*map)(npy_intp*, double*, npy_intp ftmp[NPY_MAXDIMS], *fcoordinates = NULL, *foffsets = NULL; npy_intp cstride = 0, kk, hh, ll, jj; npy_intp size; - double **splvals = NULL, icoor[NPY_MAXDIMS]; + double **splvals = NULL, icoor[NPY_MAXDIMS], tmp; npy_intp idimensions[NPY_MAXDIMS], istrides[NPY_MAXDIMS]; NI_Iterator io, ic; npy_double *matrix = matrix_ar ? (npy_double*)PyArray_DATA(matrix_ar) : NULL; @@ -420,10 +420,16 @@ NI_GeometricTransform(PyArrayObject *input, int (*map)(npy_intp*, double*, /* do an affine transformation: */ npy_double *p = matrix; for(hh = 0; hh < irank; hh++) { - icoor[hh] = 0.0; - for(ll = 0; ll < orank; ll++) - icoor[hh] += io.coordinates[ll] * *p++; - icoor[hh] += shift[hh]; + tmp = shift[hh]; + ll = 0; + for (; ll + 1 < orank; ll += 2) { + tmp += io.coordinates[ll] * *p++; + tmp += io.coordinates[ll + 1] * *p++; + } + if (ll < orank) { + tmp += io.coordinates[ll] * *p++; + } + icoor[hh] = tmp; } } else if (coordinates) { /* mapping is from an coordinates array: */ @@ -508,7 +514,9 @@ NI_GeometricTransform(PyArrayObject *input, int (*map)(npy_intp*, double*, edge_offsets[hh] = NULL; } } - get_spline_interpolation_weights(cc, order, splvals[hh]); + if(order!=0){ + get_spline_interpolation_weights(cc, order, splvals[hh]); + } } else { /* we use the constant border condition: */ constant = 1; diff --git a/scipy/ndimage/tests/test_filters.py b/scipy/ndimage/tests/test_filters.py index f2542aebce9a..ad6448fcae4a 100644 --- a/scipy/ndimage/tests/test_filters.py +++ b/scipy/ndimage/tests/test_filters.py @@ -7,6 +7,9 @@ import numpy as np import pytest from numpy.testing import suppress_warnings, assert_allclose, assert_array_equal +from hypothesis import strategies as st +from hypothesis import given +import hypothesis.extra.numpy as npst from pytest import raises as assert_raises from scipy import ndimage from scipy._lib._array_api import ( @@ -2857,12 +2860,12 @@ def test_dtype_batch_memory(self, dtype, batch_memory, use_footprint, xp): if batch_memory == 1 else contextlib.nullcontext()) with context: res = ndimage.vectorized_filter(input, xp.sum, **kwargs) - xp_assert_close(res, xp.asarray(ref, dtype=sum_dtype)) + xp_assert_close(res, xp.astype(xp.stack(ref), sum_dtype)) assert res.dtype == sum_dtype output = xp.empty_like(input) res = ndimage.vectorized_filter(input, xp.sum, output=output, **kwargs) - xp_assert_close(res, xp.asarray(ref, dtype=dtype)) + xp_assert_close(res, xp.astype(xp.stack(ref), dtype)) assert res.dtype == dtype def test_mode_valid(self, xp): @@ -2982,3 +2985,14 @@ def test_edge_cases(self, xp): res = ndimage.vectorized_filter(input, function, size=21) ref = ndimage.vectorized_filter(input, function, size=21) xp_assert_close(res, ref) + + +@given(x=npst.arrays(dtype=np.float64, + shape=st.integers(min_value=1, max_value=1000)), + size=st.integers(min_value=1, max_value=50), + mode=st.sampled_from(["constant", "mirror", "wrap", "reflect", + "nearest"]), + ) +def test_gh_22586_crash_property(x, size, mode): + # property-based test for median_filter resilience to hard crashing + ndimage.median_filter(x, size=size, mode=mode) diff --git a/scipy/optimize/_differentialevolution.py b/scipy/optimize/_differentialevolution.py index f9ce15417856..f9cf1229e279 100644 --- a/scipy/optimize/_differentialevolution.py +++ b/scipy/optimize/_differentialevolution.py @@ -204,7 +204,7 @@ def differential_evolution(func, bounds, args=(), strategy='best1bin', convergence. atol : float, optional Absolute tolerance for convergence, the solving stops when - ``np.std(pop) <= atol + tol * np.abs(np.mean(population_energies))``, + ``np.std(population_energies) <= atol + tol * np.abs(np.mean(population_energies))``, where and `atol` and `tol` are the absolute and relative tolerance respectively. updating : {'immediate', 'deferred'}, optional diff --git a/scipy/optimize/_linprog_highs.py b/scipy/optimize/_linprog_highs.py index 468f840b2d02..98a70ef8b1d4 100644 --- a/scipy/optimize/_linprog_highs.py +++ b/scipy/optimize/_linprog_highs.py @@ -23,13 +23,13 @@ HighsDebugLevel, ObjSense, HighsModelStatus, -) -from ._highspy._core.simplex_constants import ( - SimplexStrategy, - SimplexEdgeWeightStrategy, + simplex_constants as s_c, # [1] ) from scipy.sparse import csc_array, vstack, issparse +# [1]: Directly importing from "._highspy._core.simplex_constants" +# causes problems when reloading. +# See https://github.com/scipy/scipy/pull/22869 for details. def _highs_to_scipy_status_message(highs_status, highs_message): """Converts HiGHS status number/message to SciPy status number/message""" @@ -293,13 +293,13 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True, simplex_dual_edge_weight_strategy, 'simplex_dual_edge_weight_strategy', choices={'dantzig': \ - SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDantzig, + s_c.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDantzig, 'devex': \ - SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDevex, + s_c.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDevex, 'steepest-devex': \ - SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyChoose, + s_c.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyChoose, 'steepest': \ - SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategySteepestEdge, + s_c.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategySteepestEdge, None: None}) c, A_ub, b_ub, A_eq, b_eq, bounds, x0, integrality = lp @@ -334,7 +334,7 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True, 'primal_feasibility_tolerance': primal_feasibility_tolerance, 'simplex_dual_edge_weight_strategy': simplex_dual_edge_weight_strategy_enum, - 'simplex_strategy': SimplexStrategy.kSimplexStrategyDual, + 'simplex_strategy': s_c.SimplexStrategy.kSimplexStrategyDual, 'ipm_iteration_limit': maxiter, 'simplex_iteration_limit': maxiter, 'mip_rel_gap': mip_rel_gap, diff --git a/scipy/optimize/_minimize.py b/scipy/optimize/_minimize.py index 6e4b2495643d..97859546c4b8 100644 --- a/scipy/optimize/_minimize.py +++ b/scipy/optimize/_minimize.py @@ -218,10 +218,10 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, where ``intermediate_result`` is a keyword parameter containing an `OptimizeResult` with attributes ``x`` and ``fun``, the present values - of the parameter vector and objective function. Note that the name - of the parameter must be ``intermediate_result`` for the callback - to be passed an `OptimizeResult`. These methods will also terminate if - the callback raises ``StopIteration``. + of the parameter vector and objective function. Not all attributes of + `OptimizeResult` may be present. The name of the parameter must be + ``intermediate_result`` for the callback to be passed an `OptimizeResult`. + These methods will also terminate if the callback raises ``StopIteration``. All methods except trust-constr (also) support a signature like:: @@ -490,6 +490,8 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, .. [19] Zhang, Z. "PRIMA: Reference Implementation for Powell's Methods with Modernization and Amelioration", https://www.libprima.net, :doi:`10.5281/zenodo.8052654` + .. [20] Karush-Kuhn-Tucker conditions, + https://en.wikipedia.org/wiki/Karush%E2%80%93Kuhn%E2%80%93Tucker_conditions Examples -------- @@ -529,7 +531,6 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, [ 0.09495377, 0.18996269, 0.38165151, 0.7664427, 1.53713523] ]) - Next, consider a minimization problem with several constraints (namely Example 16.4 from [5]_). The objective function is: @@ -547,10 +548,42 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, The optimization problem is solved using the SLSQP method as: - >>> res = minimize(fun, (2, 0), method='SLSQP', bounds=bnds, - ... constraints=cons) + >>> res = minimize(fun, (2, 0), method='SLSQP', bounds=bnds, constraints=cons) + + It should converge to the theoretical solution ``[1.4 ,1.7]``. *SLSQP* also + returns the multipliers that are used in the solution of the problem. These + multipliers, when the problem constraints are linear, can be thought of as the + Karush-Kuhn-Tucker (KKT) multipliers, which are a generalization + of Lagrange multipliers to inequality-constrained optimization problems ([20]_). + + Notice that at the solution, the first constraint is active. Let's evaluate the + function at solution: + + >>> cons[0]['fun'](res.x) + np.float64(1.4901224698604665e-09) + + Also, notice that at optimality there is a non-zero multiplier: + + >>> res.multipliers + array([0.8, 0. , 0. ]) + + This can be understood as the local sensitivity of the optimal value of the + objective function with respect to changes in the first constraint. If we + tighten the constraint by a small amount ``eps``: + + >>> eps = 0.01 + >>> cons[0]['fun'] = lambda x: x[0] - 2 * x[1] + 2 - eps + + we expect the optimal value of the objective function to increase by + approximately ``eps * res.multipliers[0]``: - It should converge to the theoretical solution (1.4 ,1.7). + >>> eps * res.multipliers[0] # Expected change in f0 + np.float64(0.008000000027153205) + >>> f0 = res.fun # Keep track of the previous optimal value + >>> res = minimize(fun, (2, 0), method='SLSQP', bounds=bnds, constraints=cons) + >>> f1 = res.fun # New optimal value + >>> f1 - f0 + np.float64(0.008019998807885509) """ x0 = np.atleast_1d(np.asarray(x0)) diff --git a/scipy/optimize/_nonlin.py b/scipy/optimize/_nonlin.py index 7d873765098e..0d7ea805fc70 100644 --- a/scipy/optimize/_nonlin.py +++ b/scipy/optimize/_nonlin.py @@ -15,6 +15,8 @@ from scipy._lib._util import copy_if_needed from scipy._lib._util import getfullargspec_no_self as _getfullargspec from ._linesearch import scalar_search_wolfe1, scalar_search_armijo +from inspect import signature +from difflib import get_close_matches __all__ = [ @@ -1491,9 +1493,39 @@ def __init__(self, rdiff=None, method='lgmres', inner_maxiter=20, self.method_kw.setdefault('store_outer_Av', False) self.method_kw.setdefault('atol', 0) + # Retrieve the signature of the method to find the valid parameters + valid_inner_params = [ + k for k in signature(self.method).parameters + if k not in ('self', 'args', 'kwargs') + ] + for key, value in kw.items(): - if not key.startswith('inner_'): + if not key.startswith("inner_"): raise ValueError(f"Unknown parameter {key}") + if key[6:] not in valid_inner_params: + # Use difflib to find close matches to the invalid key + inner_param_suggestions = get_close_matches(key[6:], + valid_inner_params, + n=1) + if inner_param_suggestions: + suggestion_msg = (f" Did you mean '" + f"{inner_param_suggestions[0]}'?") + else: + suggestion_msg = "" + + # warn user that the parameter is not valid for the inner method + warnings.warn( + f"Option '{key}' is invalid for the inner method: {method}." + " It will be ignored." + "Please check inner method documentation for valid options." + + suggestion_msg, + stacklevel=3, + category=UserWarning, + # using `skip_file_prefixes` would be a good idea + # and should be added once we drop support for Python 3.11 + ) + # ignore this parameter and continue + continue self.method_kw[key[6:]] = value def _update_diff_step(self): diff --git a/scipy/optimize/_optimize.py b/scipy/optimize/_optimize.py index 11fab66f1396..15c65f0ccfac 100644 --- a/scipy/optimize/_optimize.py +++ b/scipy/optimize/_optimize.py @@ -388,7 +388,7 @@ def rosen(x): return r -@xp_capabilities(skip_backends=[('jax', "JAX doesn't allow item assignment.")]) +@xp_capabilities(skip_backends=[('jax.numpy', "JAX doesn't allow item assignment.")]) def rosen_der(x): """ The derivative (i.e. gradient) of the Rosenbrock function. @@ -429,7 +429,7 @@ def rosen_der(x): return der -@xp_capabilities(skip_backends=[('jax', "JAX doesn't allow item assignment.")]) +@xp_capabilities(skip_backends=[('jax.numpy', "JAX doesn't allow item assignment.")]) def rosen_hess(x): """ The Hessian matrix of the Rosenbrock function. @@ -472,7 +472,7 @@ def rosen_hess(x): return H + xpx.create_diagonal(diagonal, xp=xp) -@xp_capabilities(skip_backends=[('jax', "JAX doesn't allow item assignment.")]) +@xp_capabilities(skip_backends=[('jax.numpy', "JAX doesn't allow item assignment.")]) def rosen_hess_prod(x, p): """ Product of the Hessian matrix of the Rosenbrock function with a vector. @@ -928,15 +928,14 @@ def _minimize_neldermead(func, x0, args=(), callback=None, iterations += 1 except _MaxFuncCallError: pass - finally: - ind = np.argsort(fsim) - sim = np.take(sim, ind, 0) - fsim = np.take(fsim, ind, 0) - if retall: - allvecs.append(sim[0]) - intermediate_result = OptimizeResult(x=sim[0], fun=fsim[0]) - if _call_callback_maybe_halt(callback, intermediate_result): - break + ind = np.argsort(fsim) + sim = np.take(sim, ind, 0) + fsim = np.take(fsim, ind, 0) + if retall: + allvecs.append(sim[0]) + intermediate_result = OptimizeResult(x=sim[0], fun=fsim[0]) + if _call_callback_maybe_halt(callback, intermediate_result): + break x = sim[0] fval = np.min(fsim) diff --git a/scipy/optimize/_slsqp_py.py b/scipy/optimize/_slsqp_py.py index 42e9fa3fee2e..d02ff56a4d93 100644 --- a/scipy/optimize/_slsqp_py.py +++ b/scipy/optimize/_slsqp_py.py @@ -221,14 +221,14 @@ def _minimize_slsqp(func, x0, args=(), jac=None, bounds=None, Minimize a scalar function of one or more variables using Sequential Least Squares Programming (SLSQP). - Options - ------- + Parameters + ---------- ftol : float Precision target for the value of f in the stopping criterion. This value controls the final accuracy for checking various optimality conditions; gradient of the lagrangian and absolute sum of the constraint violations - should be lower than ``ftol``. Similarly, if computed step size and the - objective function chage are checked against this value. Default is 1e-6. + should be lower than ``ftol``. Similarly, computed step size and the + objective function changes are checked against this value. Default is 1e-6. eps : float Step size used for numerical approximation of the Jacobian. disp : bool @@ -250,6 +250,46 @@ def _minimize_slsqp(func, x0, args=(), jac=None, bounds=None, .. versionadded:: 1.16.0 + Returns + ------- + res : OptimizeResult + The optimization result represented as an `OptimizeResult` object. + In this dict-like object the following fields are of particular importance: + ``x`` the solution array, ``success`` a Boolean flag indicating if the + optimizer exited successfully, ``message`` which describes the reason for + termination, and ``multipliers`` which contains the Karush-Kuhn-Tucker + (KKT) multipliers for the QP approximation used in solving the original + nonlinear problem. See ``Notes`` below. See also `OptimizeResult` for a + description of other attributes. + + Notes + ----- + The KKT multipliers are returned in the ``OptimizeResult.multipliers`` + attribute as a NumPy array. Denoting the dimension of the equality constraints + with ``meq``, and of inequality constraints with ``mineq``, then the returned + array slice ``m[:meq]`` contains the multipliers for the equality constraints, + and the remaining ``m[meq:meq + mineq]`` contains the multipliers for the + inequality constraints. The multipliers corresponding to bound inequalities + are not returned. See [1]_ pp. 321 or [2]_ for an explanation of how to interpret + these multipliers. The internal QP problem is solved using the methods given + in [3]_ Chapter 25. + + Note that if new-style `NonlinearConstraint` or `LinearConstraint` were + used, then ``minimize`` converts them first to old-style constraint dicts. + It is possible for a single new-style constraint to simultaneously contain + both inequality and equality constraints. This means that if there is mixing + within a single constraint, then the returned list of multipliers will have + a different length than the original new-style constraints. + + References + ---------- + .. [1] Nocedal, J., and S J Wright, 2006, "Numerical Optimization", Springer, + New York. + .. [2] Kraft, D., "A software package for sequential quadratic programming", + 1988, Tech. Rep. DFVLR-FB 88-28, DLR German Aerospace Center, Germany. + .. [3] Lawson, C. L., and R. J. Hanson, 1995, "Solving Least Squares Problems", + SIAM, Philadelphia, PA. + """ _check_unknown_options(unknown_options) acc = ftol diff --git a/scipy/optimize/tests/test_differentiable_functions.py b/scipy/optimize/tests/test_differentiable_functions.py index 1f8f53b794fd..9351497665a3 100644 --- a/scipy/optimize/tests/test_differentiable_functions.py +++ b/scipy/optimize/tests/test_differentiable_functions.py @@ -780,6 +780,15 @@ def test_finite_difference_hess_linear_operator(self): assert_array_equal(ex.nhev, nhev) assert_array_equal(analit.nhev+approx.nhev, nhev) + # Test VectorFunction.hess_wrapped with J0=None + x = np.array([1.5, 0.5]) + v = np.array([1.0, 2.0]) + njev_before = approx.hess_wrapped.njev + H = approx.hess_wrapped(x, v, J0=None) + assert isinstance(H, LinearOperator) + # The njev counter should be incremented by exactly 1 + assert approx.hess_wrapped.njev == njev_before + 1 + def test_fgh_overlap(self): # VectorFunction.fun/jac should return copies to internal attributes ex = ExVectorialFunction() diff --git a/scipy/optimize/tests/test_least_squares.py b/scipy/optimize/tests/test_least_squares.py index 234fbfad9e4a..c93b92d60efa 100644 --- a/scipy/optimize/tests/test_least_squares.py +++ b/scipy/optimize/tests/test_least_squares.py @@ -813,6 +813,17 @@ def test_loss(self): assert_raises(ValueError, least_squares, fun_trivial, 2.0, method='lm', loss='huber') + + def test_callback_with_lm_method(self): + def callback(x): + assert(False) # Dummy callback function + + with suppress_warnings() as sup: + sup.filter( + UserWarning, + "Callback function specified, but not supported with `lm` method." + ) + least_squares(fun_trivial, x0=[0], method='lm', callback=callback) def test_basic(): diff --git a/scipy/optimize/tests/test_nonlin.py b/scipy/optimize/tests/test_nonlin.py index e5eb094c1590..cffb9d104801 100644 --- a/scipy/optimize/tests/test_nonlin.py +++ b/scipy/optimize/tests/test_nonlin.py @@ -12,6 +12,7 @@ from numpy.linalg import inv import numpy as np import scipy +from scipy.sparse.linalg import minres from .test_minpack import pressure_network @@ -217,6 +218,41 @@ def wont_converge(x): with pytest.raises(scipy.optimize.NoConvergence): nonlin.newton_krylov(wont_converge, xin=[0], maxiter=1) + def test_warnings_invalid_inner_param(self): + """ + Test for ENH #21986, for behavior of `nonlin.newton_krylov` + Test the following scenarios: + 1. Raise warning for invalid inner param + 2. No warning for valid inner param + 3. No warning for user-provided callable method + """ + # This should raise exactly one warning + # (`inner_atol` is not valid for `minres`) + with pytest.warns(UserWarning, + match="Please check inner method documentation"): + nonlin.newton_krylov(F, F.xin, method="minres", inner_atol=1e-5) + + # This should not raise a warning (`minres` without `inner_atol`, + # but with `inner_maxiter` which is valid) + nonlin.newton_krylov(F, F.xin, method="minres", inner_maxiter=100, + inner_callback= lambda _ : ...) + + # Test newton_krylov with a user-provided callable method + def user_provided_callable_method_enh_21986(op, rhs, **kwargs): + """A dummy user-provided callable method for testing.""" + # Return a dummy result (mimicking minres) + return minres(op, rhs, **kwargs) + # This should not raise any warnings + nonlin.newton_krylov(F, F.xin, + method=user_provided_callable_method_enh_21986) + + def test_non_inner_prefix(self): + with pytest.raises(ValueError, + match="Unknown parameter" + ): + # Pass a parameter without 'inner_' prefix + nonlin.newton_krylov(F, F.xin, method="minres", invalid_param=1e-5) + class TestSecant: """Check that some Jacobian approximations satisfy the secant condition""" diff --git a/scipy/optimize/tests/test_optimize.py b/scipy/optimize/tests/test_optimize.py index 5886ad0b2f48..0a51dc290c41 100644 --- a/scipy/optimize/tests/test_optimize.py +++ b/scipy/optimize/tests/test_optimize.py @@ -35,10 +35,10 @@ from scipy.sparse import (coo_matrix, csc_matrix, csr_matrix, coo_array, csr_array, csc_array) from scipy._lib._array_api_no_0d import xp_assert_equal -from scipy._lib._array_api import make_skip_xp_backends +from scipy._lib._array_api import make_xp_test_case from scipy._lib._util import MapWrapper -skip_xp_backends = pytest.mark.skip_xp_backends +lazy_xp_modules = [optimize] def test_check_grad(): @@ -2475,20 +2475,20 @@ def test_powell_output(): class TestRosen: - @make_skip_xp_backends(optimize.rosen) + @make_xp_test_case(optimize.rosen) def test_rosen(self, xp): # integer input should be promoted to the default floating type x = xp.asarray([1, 1, 1]) xp_assert_equal(optimize.rosen(x), xp.asarray(0.)) - @make_skip_xp_backends(optimize.rosen_der) + @make_xp_test_case(optimize.rosen_der) def test_rosen_der(self, xp): x = xp.asarray([1, 1, 1, 1]) xp_assert_equal(optimize.rosen_der(x), xp.zeros_like(x, dtype=xp.asarray(1.).dtype)) - @make_skip_xp_backends(optimize.rosen_hess, optimize.rosen_hess_prod) + @make_xp_test_case(optimize.rosen_hess, optimize.rosen_hess_prod) def test_hess_prod(self, xp): one = xp.asarray(1.) diff --git a/scipy/optimize/tests/test_zeros.py b/scipy/optimize/tests/test_zeros.py index 35b34fa4b8ad..9902d6d63002 100644 --- a/scipy/optimize/tests/test_zeros.py +++ b/scipy/optimize/tests/test_zeros.py @@ -225,6 +225,10 @@ def test_lru_cached_individual(self, method): assert r.converged assert_allclose(root, 0) + def test_gh_22934(self): + with pytest.raises(ValueError, match="maxiter must be >= 0"): + zeros.brentq(lambda x: x**2 - 1, -2, 0, maxiter=-1) + class TestNewton(TestScalarRootFinders): def test_newton_collections(self): diff --git a/scipy/optimize/zeros.c b/scipy/optimize/zeros.c index d8393519c4a0..b23652736bd4 100644 --- a/scipy/optimize/zeros.c +++ b/scipy/optimize/zeros.c @@ -96,7 +96,7 @@ call_solver(solver_type solver, PyObject *self, PyObject *args) return NULL; } if (iter < 0) { - PyErr_SetString(PyExc_ValueError, "maxiter should be > 0"); + PyErr_SetString(PyExc_ValueError, "maxiter must be >= 0"); return NULL; } diff --git a/scipy/signal/_delegators.py b/scipy/signal/_delegators.py index ee1d6d02666a..1d0363b0040e 100644 --- a/scipy/signal/_delegators.py +++ b/scipy/signal/_delegators.py @@ -28,8 +28,7 @@ """ import numpy as np -from scipy._lib._array_api import array_namespace -from scipy.ndimage._ni_support import _skip_if_int +from scipy._lib._array_api import array_namespace, np_compat def _skip_if_lti(arg): @@ -57,9 +56,6 @@ def _skip_if_poly1d(arg): return None if isinstance(arg, np.poly1d) else arg -def _skip_if_float(arg): - return None if isinstance(arg, float) else arg - ################### def abcd_normalize_signature(A=None, B=None, C=None, D=None): @@ -172,8 +168,8 @@ def kaiser_beta_signature(a): def kaiserord_signature(ripple, width): return np -def get_window_signature(window, Nx, fftbins=True): - return np +def get_window_signature(window, Nx, fftbins=True, *, xp=None, device=None): + return np if xp is None else xp ################################# @@ -301,7 +297,7 @@ def deconvolve_signature(signal, divisor): def detrend_signature(data, axis=1, type='linear', bp=0, *args, **kwds): - return array_namespace(data, _skip_if_int(bp)) + return array_namespace(data, bp) def filtfilt_signature(b, a, x, *args, **kwds): @@ -312,6 +308,10 @@ def lfilter_signature(b, a, x, axis=-1, zi=None): return array_namespace(b, a, x, zi) +def envelope_signature(z, *args, **kwds): + return array_namespace(z) + + def find_peaks_signature( x, height=None, threshold=None, distance=None, prominence=None, width=None, wlen=None, rel_height=0.5, plateau_size=None @@ -334,7 +334,11 @@ def firls_signature(numtaps, bands, desired, *, weight=None, fs=None): def firwin_signature(numtaps, cutoff, *args, **kwds): - return array_namespace(cutoff) + if isinstance(cutoff, int | float): + xp = np_compat + else: + xp = array_namespace(cutoff) + return xp def firwin2_signature(numtaps, freq, gain, *args, **kwds): @@ -342,19 +346,19 @@ def firwin2_signature(numtaps, freq, gain, *args, **kwds): def freqs_zpk_signature(z, p, k, worN, *args, **kwds): - return array_namespace(z, p, _skip_if_int(worN)) + return array_namespace(z, p, worN) freqz_zpk_signature = freqs_zpk_signature def freqs_signature(b, a, worN=200, *args, **kwds): - return array_namespace(b, a, _skip_if_int(worN)) + return array_namespace(b, a, worN) freqz_signature = freqs_signature def freqz_sos_signature(sos, worN=512, *args, **kwds): - return array_namespace(sos, _skip_if_int(worN)) + return array_namespace(sos, worN) sosfreqz_signature = freqz_sos_signature @@ -365,7 +369,7 @@ def gausspulse_signature(t, *args, **kwds): def group_delay_signature(system, w=512, whole=False, fs=6.283185307179586): - return array_namespace(_skip_if_str_or_tuple(system), _skip_if_int(w)) + return array_namespace(_skip_if_str_or_tuple(system), w) def hilbert_signature(x, N=None, axis=-1): @@ -522,7 +526,7 @@ def symiirorder1_signature(signal, c0, z1, precision=-1.0): def symiirorder2_signature(input, r, omega, precision=-1.0): - return array_namespace(input, _skip_if_float(r), _skip_if_float(omega)) + return array_namespace(input, r, omega) def cspline1d_signature(signal, *args, **kwds): diff --git a/scipy/signal/_filter_design.py b/scipy/signal/_filter_design.py index d51f9eae183d..54b4fd5ed0df 100644 --- a/scipy/signal/_filter_design.py +++ b/scipy/signal/_filter_design.py @@ -4,20 +4,22 @@ import warnings import numpy as np -from numpy import (atleast_1d, poly, polyval, roots, asarray, +from numpy import (atleast_1d, asarray, pi, absolute, sqrt, tan, log10, arcsinh, sin, exp, cosh, arccosh, ceil, conjugate, sinh, concatenate, prod, array) from numpy.polynomial.polynomial import polyval as npp_polyval -from numpy.polynomial.polynomial import polyvalfromroots from scipy import special, optimize, fft as sp_fft from scipy.special import comb from scipy._lib._util import float_factorial from scipy.signal._arraytools import _validate_fs +from scipy.signal import _polyutils as _pu import scipy._lib.array_api_extra as xpx -from scipy._lib._array_api import array_namespace, xp_promote, xp_size +from scipy._lib._array_api import ( + array_namespace, xp_promote, xp_size, xp_default_dtype, is_jax, xp_float_to_complex, +) from scipy._lib.array_api_compat import numpy as np_compat @@ -58,6 +60,31 @@ def _is_int_type(x): return True +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/_core/function_base.py#L195-L302 +def _logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None, *, xp): + if not isinstance(base, float | int) and xp.asarray(base).ndim > 0: + # If base is non-scalar, broadcast it with the others, since it + # may influence how axis is interpreted. + start, stop, base = map(xp.asarray, (start, stop, base)) + ndmax = xp.broadcast_arrays(start, stop, base).ndim + start, stop, base = ( + xpx.atleast_nd(a, ndim=ndmax) + for a in (start, stop, base) + ) + base = xp.expand_dims(base) + try: + result_dt = xp.result_type(start, stop, base) + except ValueError: + # all of start, stop and base are python scalars + result_dt = xp_default_dtype(xp) + y = xp.linspace(start, stop, num=num, endpoint=endpoint, dtype=result_dt) + + yp = xp.pow(base, y) + if dtype is None: + return yp + return xp.astype(yp, dtype, copy=False) + + def findfreqs(num, den, N, kind='ba'): """ Find array of frequencies for computing the response of an analog filter. @@ -93,27 +120,42 @@ def findfreqs(num, den, N, kind='ba'): 3.16227766e-01, 1.00000000e+00, 3.16227766e+00, 1.00000000e+01, 3.16227766e+01, 1.00000000e+02]) """ + xp = array_namespace(num, den) + num, den = map(xp.asarray, (num, den)) + if kind == 'ba': - ep = atleast_1d(roots(den)) + 0j - tz = atleast_1d(roots(num)) + 0j + ep = xpx.atleast_nd(_pu.polyroots(den, xp=xp), ndim=1, xp=xp) + tz = xpx.atleast_nd(_pu.polyroots(num, xp=xp), ndim=1, xp=xp) elif kind == 'zp': - ep = atleast_1d(den) + 0j - tz = atleast_1d(num) + 0j + ep = xpx.atleast_nd(den, ndim=1, xp=xp) + tz = xpx.atleast_nd(num, ndim=1, xp=xp) else: raise ValueError("input must be one of {'ba', 'zp'}") - if len(ep) == 0: - ep = atleast_1d(-1000) + 0j + ep = xp_float_to_complex(ep, xp=xp) + tz = xp_float_to_complex(tz, xp=xp) - ez = np.r_[ep[ep.imag >= 0], tz[(np.abs(tz) < 1e5) & (tz.imag >= 0)]] + if ep.shape[0] == 0: + ep = xp.asarray([-1000], dtype=ep.dtype) - integ = np.abs(ez) < 1e-10 - hfreq = np.round(np.log10(np.max(3 * np.abs(ez.real + integ) + - 1.5 * ez.imag)) + 0.5) - lfreq = np.round(np.log10(0.1 * np.min(np.abs((ez + integ).real) + - 2 * ez.imag)) - 0.5) + ez = xp.concat(( + ep[xp.imag(ep) >= 0], + tz[(xp.abs(tz) < 1e5) & (xp.imag(tz) >= 0)] + )) - w = np.logspace(lfreq, hfreq, N) + integ = xp.astype(xp.abs(ez) < 1e-10, ez.dtype) # XXX True->1, False->0 + hfreq = xp.round( + xp.log10(xp.max(3*xp.abs(xp.real(ez) + integ) + 1.5*xp.imag(ez))) + 0.5 + ) + + # the fudge factor is for backwards compatibility: round(-1.5) can be -1 or -2 + # depending on the the floating-point jitter in -1.5 + fudge = 1e-14 if is_jax(xp) else 0 + lfreq = xp.round( + xp.log10(0.1*xp.min(xp.abs(xp.real(ez + integ)) + 2*xp.imag(ez))) - 0.5 - fudge + ) + + w = _logspace(lfreq, hfreq, N, xp=xp) return w @@ -178,16 +220,18 @@ def freqs(b, a, worN=200, plot=None): >>> plt.show() """ + xp = array_namespace(b, a, worN) + if worN is None: # For backwards compatibility w = findfreqs(b, a, 200) elif _is_int_type(worN): w = findfreqs(b, a, worN) else: - w = atleast_1d(worN) + w = xpx.atleast_nd(xp.asarray(worN), ndim=1, xp=xp) s = 1j * w - h = polyval(b, s) / polyval(a, s) + h = _pu.polyval(b, s, xp=xp) / _pu.polyval(a, s, xp=xp) if plot is not None: plot(w, h) @@ -254,8 +298,13 @@ def freqs_zpk(z, p, k, worN=200): >>> plt.show() """ - k = np.asarray(k) - if k.size > 1: + xp = array_namespace(z, p) + z, p = map(xp.asarray, (z, p)) + + # NB: k is documented to be a scalar; for backwards compat we keep allowing it + # to be a size-1 array, but it does not influence the namespace calculation. + k = xp.asarray(k, dtype=xp_default_dtype(xp)) + if xp_size(k) > 1: raise ValueError('k must be a single scalar gain') if worN is None: @@ -266,10 +315,10 @@ def freqs_zpk(z, p, k, worN=200): else: w = worN - w = atleast_1d(w) + w = xpx.atleast_nd(xp.asarray(w), ndim=1, xp=xp) s = 1j * w - num = polyvalfromroots(s, z) - den = polyvalfromroots(s, p) + num = _pu.npp_polyvalfromroots(s, z, xp=xp) + den = _pu.npp_polyvalfromroots(s, p, xp=xp) h = k * num/den return w, h @@ -433,8 +482,15 @@ def freqz(b, a=1, worN=512, whole=False, plot=None, fs=2*pi, (2, 1024) """ - b = atleast_1d(b) - a = atleast_1d(a) + xp = array_namespace(b, a) + + b, a = map(xp.asarray, (b, a)) + if xp.isdtype(a.dtype, 'integral'): + a = xp.astype(a, xp_default_dtype(xp)) + res_dtype = xp.result_type(b, a) + + b = xpx.atleast_nd(b, ndim=1, xp=xp) + a = xpx.atleast_nd(a, ndim=1, xp=xp) fs = _validate_fs(fs, allow_none=False) @@ -452,40 +508,51 @@ def freqz(b, a=1, worN=512, whole=False, plot=None, fs=2*pi, lastpoint = 2 * pi if whole else pi # if include_nyquist is true and whole is false, w should # include end point - w = np.linspace(0, lastpoint, N, - endpoint=include_nyquist and not whole) + w = xp.linspace(0, lastpoint, N, + endpoint=include_nyquist and not whole, dtype=res_dtype) n_fft = N if whole else 2 * (N - 1) if include_nyquist else 2 * N - if (a.size == 1 and (b.ndim == 1 or (b.shape[-1] == 1)) + if (xp_size(a) == 1 and (b.ndim == 1 or (b.shape[-1] == 1)) and n_fft >= b.shape[0] and n_fft > 0): # TODO: review threshold acc. to benchmark? - if np.isrealobj(b) and np.isrealobj(a): + + if (xp.isdtype(b.dtype, "real floating") and + xp.isdtype(a.dtype, "real floating") + ): fft_func = sp_fft.rfft else: fft_func = sp_fft.fft - h = fft_func(b, n=n_fft, axis=0)[:N] + + h = fft_func(b, n=n_fft, axis=0) + h = h[:min(N, h.shape[0]), ...] h /= a + if fft_func is sp_fft.rfft and whole: # exclude DC and maybe Nyquist (no need to use axis_reverse # here because we can build reversal with the truncation) - stop = -1 if n_fft % 2 == 1 else -2 - h_flip = slice(stop, 0, -1) - h = np.concatenate((h, h[h_flip].conj())) + stop = None if n_fft % 2 == 1 else -1 + h_flipped = xp.flip(h[1:stop, ...], axis=0) + h = xp.concat((h, xp.conj(h_flipped))) if b.ndim > 1: # Last axis of h has length 1, so drop it. h = h[..., 0] # Move the first axis of h to the end. - h = np.moveaxis(h, 0, -1) + h = xp.moveaxis(h, 0, -1) else: - w = atleast_1d(worN) + if isinstance(worN, complex): + # backwards compat + worN = worN.real + w = xpx.atleast_nd(xp.asarray(worN, dtype=res_dtype), ndim=1, xp=xp) + if xp.isdtype(w.dtype, 'integral'): + w = xp.astype(w, xp_default_dtype(xp)) del worN - w = 2*pi*w/fs + w = 2 * pi * w / fs if h is None: # still need to compute using freqs w - zm1 = exp(-1j * w) - h = (npp_polyval(zm1, b, tensor=False) / - npp_polyval(zm1, a, tensor=False)) + zm1 = xp.exp(-1j * w) + h = (_pu.npp_polyval(zm1, b, tensor=False, xp=xp) / + _pu.npp_polyval(zm1, a, tensor=False, xp=xp)) - w = w*(fs/(2*pi)) + w = w * (fs / (2 * pi)) if plot is not None: plot(w, h) @@ -576,7 +643,13 @@ def freqz_zpk(z, p, k, worN=512, whole=False, fs=2*pi): >>> plt.show() """ - z, p = map(atleast_1d, (z, p)) + xp = array_namespace(z, p) + z, p = map(xp.asarray, (z, p)) + + z = xpx.atleast_nd(z, ndim=1, xp=xp) + p = xpx.atleast_nd(p, ndim=1, xp=xp) + res_dtype = xp.result_type(z, p) + res_dtype = xp.float64 if res_dtype in (xp.float64, xp.complex128) else xp.float32 fs = _validate_fs(fs, allow_none=False) @@ -587,15 +660,19 @@ def freqz_zpk(z, p, k, worN=512, whole=False, fs=2*pi): if worN is None: # For backwards compatibility - w = np.linspace(0, lastpoint, 512, endpoint=False) + w = xp.linspace(0, lastpoint, 512, endpoint=False, dtype=res_dtype) elif _is_int_type(worN): - w = np.linspace(0, lastpoint, worN, endpoint=False) + w = xp.linspace(0, lastpoint, worN, endpoint=False, dtype=res_dtype) else: - w = atleast_1d(worN) - w = 2*pi*w/fs + w = xp.asarray(worN) + if xp.isdtype(w.dtype, 'integral'): + w = xp.astype(w, xp_default_dtype(xp)) + w = xpx.atleast_nd(w, ndim=1, xp=xp) + w = 2 * pi * w / fs - zm1 = exp(1j * w) - h = k * polyvalfromroots(zm1, z) / polyvalfromroots(zm1, p) + zm1 = xp.exp(1j * w) + func = _pu.npp_polyvalfromroots + h = xp.asarray(k, dtype=res_dtype) * func(zm1, z, xp=xp) / func(zm1, p, xp=xp) w = w*(fs/(2*pi)) @@ -728,16 +805,19 @@ def group_delay(system, w=512, whole=False, fs=2*pi): return w, gd -def _validate_sos(sos): +def _validate_sos(sos, xp=None): """Helper to validate a SOS input""" - sos = np.asarray(sos) - sos = np.atleast_2d(sos) + if xp is None: + xp = np # backcompat, cf sosfilt, sosfiltfilt + + sos = xp.asarray(sos) + sos = xpx.atleast_nd(sos, ndim=2, xp=xp) if sos.ndim != 2: raise ValueError('sos array must be 2D') n_sections, m = sos.shape if m != 6: raise ValueError('sos array must be shape (n_sections, 6)') - if not (sos[:, 3] == 1).all(): + if not xp.all(sos[:, 3] == 1): raise ValueError('sos[:, 3] should be all ones') return sos, n_sections @@ -856,13 +936,16 @@ def freqz_sos(sos, worN=512, whole=False, fs=2*pi): >>> plt.show() """ + xp = array_namespace(sos) + fs = _validate_fs(fs, allow_none=False) - sos, n_sections = _validate_sos(sos) + sos, n_sections = _validate_sos(sos, xp) if n_sections == 0: raise ValueError('Cannot compute frequencies with no sections') h = 1. - for row in sos: + for j in range(sos.shape[0]): + row = sos[j, :] w, rowh = freqz(row[:3], row[3:], worN=worN, whole=whole, fs=fs) h *= rowh return w, h @@ -1131,13 +1214,15 @@ def tf2zpk(b, a): array([ -2.5+2.59807621j , -2.5-2.59807621j]), 3.0) """ + xp = array_namespace(b, a) b, a = normalize(b, a) - b = (b + 0.0) / a[0] - a = (a + 0.0) / a[0] + + a, b = xp_promote(a, b, xp=xp, force_floating=True) + k = b[0] - b /= b[0] - z = roots(b) - p = roots(a) + b = b / b[0] + z = _pu.polyroots(b, xp=xp) + p = _pu.polyroots(a, xp=xp) return z, p, k @@ -1179,18 +1264,26 @@ def zpk2tf(z, p, k): >>> zpk2tf(z, p, k) ( array([ 5., -40., 60.]), array([ 1., -9., 8.])) """ - z = atleast_1d(z) - k = atleast_1d(k) - if len(z.shape) > 1: - temp = poly(z[0]) - b = np.empty((z.shape[0], z.shape[1] + 1), temp.dtype.char) - if len(k) == 1: + xp = array_namespace(z, p) + z, p, k = map(xp.asarray, (z, p, k)) + + z = xpx.atleast_nd(z, ndim=1, xp=xp) + k = xpx.atleast_nd(k, ndim=1, xp=xp) + if xp.isdtype(k.dtype, 'integral'): + k = xp.astype(k, xp_default_dtype(xp)) + + if z.ndim > 1: + temp = _pu.poly(z[0], xp=xp) + b = xp.empty((z.shape[0], z.shape[1] + 1), dtype=temp.dtype) + if k.shape[0] == 1: k = [k[0]] * z.shape[0] for i in range(z.shape[0]): - b[i] = k[i] * poly(z[i]) + b[i] = k[i] * _pu.poly(z[i], xp=xp) else: - b = k * poly(z) - a = atleast_1d(poly(p)) + b = k * _pu.poly(z, xp=xp) + + a = _pu.poly(p, xp=xp) + a = xpx.atleast_nd(xp.asarray(a), ndim=1, xp=xp) return b, a @@ -1286,17 +1379,20 @@ def sos2tf(sos): ( array([0.91256522, 0.91256522, 0. ]), array([1. , 0.82513043, 0. ])) """ - sos = np.asarray(sos) + xp = array_namespace(sos) + sos = xp.asarray(sos) + result_type = sos.dtype - if result_type.kind in 'bui': - result_type = np.float64 + if xp.isdtype(result_type, 'integral'): + result_type = xp_default_dtype(xp) + + b = xp.asarray([1], dtype=result_type) + a = xp.asarray([1], dtype=result_type) - b = np.array([1], dtype=result_type) - a = np.array([1], dtype=result_type) n_sections = sos.shape[0] for section in range(n_sections): - b = np.polymul(b, sos[section, :3]) - a = np.polymul(a, sos[section, 3:]) + b = _pu.polymul(b, sos[section, :3], xp=xp) + a = _pu.polymul(a, sos[section, 3:], xp=xp) return b, a @@ -1327,15 +1423,17 @@ def sos2zpk(sos): .. versionadded:: 0.16.0 """ - sos = np.asarray(sos) + xp = array_namespace(sos) + sos = xp.asarray(sos) + n_sections = sos.shape[0] - z = np.zeros(n_sections*2, np.complex128) - p = np.zeros(n_sections*2, np.complex128) + z = xp.zeros(n_sections*2, dtype=xp.complex128) + p = xp.zeros(n_sections*2, dtype=xp.complex128) k = 1. for section in range(n_sections): zpk = tf2zpk(sos[section, :3], sos[section, 3:]) - z[2*section:2*section+len(zpk[0])] = zpk[0] - p[2*section:2*section+len(zpk[1])] = zpk[1] + z = xpx.at(z, slice(2*section, 2*section + zpk[0].shape[0])).set(zpk[0]) + p = xpx.at(p, slice(2*section, 2*section + zpk[1].shape[0])).set(zpk[1]) k *= zpk[2] return z, p, k @@ -1550,6 +1648,12 @@ def zpk2sos(z, p, k, pairing=None, *, analog=False): # 4. Further optimizations of the section ordering / pole-zero pairing. # See the wiki for other potential issues. + xp = array_namespace(z, p) + + # convert to numpy, convert back on exit XXX + z, p = map(np.asarray, (z, p)) + k = np.asarray(k) + if pairing is None: pairing = 'minimal' if analog else 'nearest' @@ -1563,9 +1667,9 @@ def zpk2sos(z, p, k, pairing=None, *, analog=False): if len(z) == len(p) == 0: if not analog: - return np.array([[k, 0., 0., 1., 0., 0.]]) + return xp.asarray(np.asarray([[k, 0., 0., 1., 0., 0.]])) else: - return np.array([[0., 0., k, 0., 0., 1.]]) + return xp.asarray(np.asarray([[0., 0., k, 0., 0., 1.]])) if pairing != 'minimal': # ensure we have the same number of poles and zeros, and make copies @@ -1676,7 +1780,7 @@ def idx_worst(p): # put gain in first sos sos[0][:3] *= k - return sos + return xp.asarray(sos) def _align_nums(nums, xp): @@ -1725,26 +1829,6 @@ def _align_nums(nums, xp): return aligned_nums -def _trim_zeros(filt, trim='fb'): - # https://github.com/numpy/numpy/blob/v2.1.0/numpy/lib/_function_base_impl.py#L1874-L1925 - first = 0 - trim = trim.upper() - if 'F' in trim: - for i in filt: - if i != 0.: - break - else: - first = first + 1 - last = filt.shape[0] - if 'B' in trim: - for i in filt[::-1]: - if i != 0.: - break - else: - last = last - 1 - return filt[first:last] - - def normalize(b, a): """Normalize numerator/denominator of a continuous-time transfer function. @@ -1822,7 +1906,7 @@ def normalize(b, a): raise ValueError("Denominator must have at least on nonzero element.") # Trim leading zeros in denominator, leave at least one. - den = _trim_zeros(den, 'f') + den = _pu._trim_zeros(den, 'f') # Normalize transfer function num, den = num / den[0], den / den[0] diff --git a/scipy/signal/_fir_filter_design.py b/scipy/signal/_fir_filter_design.py index 981739e12418..eb3a2702f66f 100644 --- a/scipy/signal/_fir_filter_design.py +++ b/scipy/signal/_fir_filter_design.py @@ -1,19 +1,20 @@ """Functions for FIR filter design.""" -from math import ceil, log -import operator +from math import ceil, log, log2 import warnings from typing import Literal import numpy as np -from numpy.fft import irfft, fft, ifft -from scipy.special import sinc +from scipy.fft import irfft, fft, ifft from scipy.linalg import (toeplitz, hankel, solve, LinAlgError, LinAlgWarning, lstsq) from scipy.signal._arraytools import _validate_fs from . import _sigtools +from scipy._lib._array_api import array_namespace, xp_size, xp_default_dtype +import scipy._lib.array_api_extra as xpx + __all__ = ['kaiser_beta', 'kaiser_atten', 'kaiserord', 'firwin', 'firwin2', 'firwin_2d', 'remez', 'firls', 'minimum_phase'] @@ -372,6 +373,9 @@ def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True, array([ 0.04890915, 0.91284326, 0.04890915]) """ + # NB: scipy's version of array_namespace returns `np_compat` for int or floats + xp = array_namespace(cutoff) + # The major enhancements to this function added in November 2010 were # developed by Tom Krauss (see ticket #902). fs = _validate_fs(fs, allow_none=True) @@ -379,18 +383,19 @@ def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True, nyq = 0.5 * fs - cutoff = np.atleast_1d(cutoff) / float(nyq) + cutoff = xp.asarray(cutoff, dtype=xp_default_dtype(xp)) + cutoff = xpx.atleast_nd(cutoff, ndim=1, xp=xp) / float(nyq) # Check for invalid input. if cutoff.ndim > 1: raise ValueError("The cutoff argument must be at most " "one-dimensional.") - if cutoff.size == 0: + if xp_size(cutoff) == 0: raise ValueError("At least one cutoff frequency must be given.") - if cutoff.min() <= 0 or cutoff.max() >= 1: + if xp.min(cutoff) <= 0 or xp.max(cutoff) >= 1: raise ValueError("Invalid cutoff frequency: frequencies must be " "greater than 0 and less than fs/2.") - if np.any(np.diff(cutoff) <= 0): + if xp.any(cutoff[1:] - cutoff[:-1] <= 0): raise ValueError("Invalid cutoff frequencies: the frequencies " "must be strictly increasing.") @@ -401,69 +406,68 @@ def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True, beta = kaiser_beta(atten) window = ('kaiser', beta) - if isinstance(pass_zero, str): - if pass_zero in ('bandstop', 'lowpass'): - if pass_zero == 'lowpass': - if cutoff.size != 1: - raise ValueError('cutoff must have one element if ' - f'pass_zero=="lowpass", got {cutoff.shape}') - elif cutoff.size <= 1: - raise ValueError('cutoff must have at least two elements if ' - f'pass_zero=="bandstop", got {cutoff.shape}') - pass_zero = True - elif pass_zero in ('bandpass', 'highpass'): - if pass_zero == 'highpass': - if cutoff.size != 1: - raise ValueError('cutoff must have one element if ' - f'pass_zero=="highpass", got {cutoff.shape}') - elif cutoff.size <= 1: - raise ValueError('cutoff must have at least two elements if ' - f'pass_zero=="bandpass", got {cutoff.shape}') - pass_zero = False - else: - raise ValueError('pass_zero must be True, False, "bandpass", ' - '"lowpass", "highpass", or "bandstop", got ' - f'{pass_zero}') - pass_zero = bool(operator.index(pass_zero)) # ensure bool-like - - pass_nyquist = bool(cutoff.size & 1) ^ pass_zero + if pass_zero in ('bandstop', 'lowpass'): + if pass_zero == 'lowpass': + if xp_size(cutoff) != 1: + raise ValueError('cutoff must have one element if ' + f'pass_zero=="lowpass", got {cutoff.shape}') + elif xp_size(cutoff) <= 1: + raise ValueError('cutoff must have at least two elements if ' + f'pass_zero=="bandstop", got {cutoff.shape}') + pass_zero = True + elif pass_zero in ('bandpass', 'highpass'): + if pass_zero == 'highpass': + if xp_size(cutoff) != 1: + raise ValueError('cutoff must have one element if ' + f'pass_zero=="highpass", got {cutoff.shape}') + elif xp_size(cutoff) <= 1: + raise ValueError('cutoff must have at least two elements if ' + f'pass_zero=="bandpass", got {cutoff.shape}') + pass_zero = False + elif not (pass_zero is True or pass_zero is False): + raise ValueError(f"Parameter {pass_zero=} not in (True, False, 'bandpass', " + + "'lowpass', 'highpass', 'bandstop')") + + pass_nyquist = (xp_size(cutoff) % 2 == 0) == pass_zero if pass_nyquist and numtaps % 2 == 0: raise ValueError("A filter with an even number of coefficients must " "have zero response at the Nyquist frequency.") # Insert 0 and/or 1 at the ends of cutoff so that the length of cutoff # is even, and each pair in cutoff corresponds to passband. - cutoff = np.hstack(([0.0] * pass_zero, cutoff, [1.0] * pass_nyquist)) + cutoff = xp.concat((xp.zeros(int(pass_zero)), cutoff, xp.ones(int(pass_nyquist)))) + # `bands` is a 2-D array; each row gives the left and right edges of # a passband. - bands = cutoff.reshape(-1, 2) + bands = xp.reshape(cutoff, (-1, 2)) # Build up the coefficients. alpha = 0.5 * (numtaps - 1) - m = np.arange(0, numtaps) - alpha + m = xp.arange(0, numtaps, dtype=cutoff.dtype) - alpha h = 0 - for left, right in bands: - h += right * sinc(right * m) - h -= left * sinc(left * m) + for j in range(bands.shape[0]): + left, right = bands[j, 0], bands[j, 1] + h += right * xpx.sinc(right * m, xp=xp) + h -= left * xpx.sinc(left * m, xp=xp) # Get and apply the window function. from .windows import get_window - win = get_window(window, numtaps, fftbins=False) + win = get_window(window, numtaps, fftbins=False, xp=xp) h *= win # Now handle scaling if desired. if scale: # Get the first passband. - left, right = bands[0] + left, right = bands[0, ...] if left == 0: scale_frequency = 0.0 elif right == 1: scale_frequency = 1.0 else: scale_frequency = 0.5 * (left + right) - c = np.cos(np.pi * m * scale_frequency) - s = np.sum(h * c) + c = xp.cos(xp.pi * m * scale_frequency) + s = xp.sum(h * c) h /= s return h @@ -573,11 +577,14 @@ def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming', [-0.02286961 -0.06362756 0.57310236 0.57310236 -0.06362756 -0.02286961] """ + xp = array_namespace(freq, gain) + freq, gain = xp.asarray(freq), xp.asarray(gain) + fs = _validate_fs(fs, allow_none=True) fs = 2 if fs is None else fs nyq = 0.5 * fs - if len(freq) != len(gain): + if freq.shape[0] != gain.shape[0]: raise ValueError('freq and gain must be of same length.') if nfreqs is not None and numtaps >= nfreqs: @@ -588,11 +595,11 @@ def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming', if freq[0] != 0 or freq[-1] != nyq: raise ValueError('freq must start with 0 and end with fs/2.') - d = np.diff(freq) - if (d < 0).any(): + d = freq[1:] - freq[:-1] + if xp.any(d < 0): raise ValueError('The values in freq must be nondecreasing.') d2 = d[:-1] + d[1:] - if (d2 == 0).any(): + if xp.any(d2 == 0): raise ValueError('A value in freq must not occur more than twice.') if freq[1] == 0: raise ValueError('Value 0 must not be repeated in freq') @@ -623,28 +630,30 @@ def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming', if nfreqs is None: nfreqs = 1 + 2 ** int(ceil(log(numtaps, 2))) - if (d == 0).any(): + if xp.any(d == 0): # Tweak any repeated values in freq so that interp works. - freq = np.array(freq, copy=True) - eps = np.finfo(float).eps * nyq - for k in range(len(freq) - 1): + freq = xp.asarray(freq, copy=True) + eps = xp.finfo(xp_default_dtype(xp)).eps * nyq + for k in range(freq.shape[0] - 1): if freq[k] == freq[k + 1]: freq[k] = freq[k] - eps freq[k + 1] = freq[k + 1] + eps # Check if freq is strictly increasing after tweak - d = np.diff(freq) - if (d <= 0).any(): + d = freq[1:] - freq[:-1] + if xp.any(d <= 0): raise ValueError("freq cannot contain numbers that are too close " "(within eps * (fs/2): " f"{eps}) to a repeated value") # Linearly interpolate the desired response on a uniform mesh `x`. x = np.linspace(0.0, nyq, nfreqs) - fx = np.interp(x, freq, gain) + fx = np.interp(x, np.asarray(freq), np.asarray(gain)) # XXX array-api-extra#193 + x = xp.asarray(x) + fx = xp.asarray(fx) # Adjust the phases of the coefficients so that the first `ntaps` of the # inverse FFT are the desired filter coefficients. - shift = np.exp(-(numtaps - 1) / 2. * 1.j * np.pi * x / nyq) + shift = xp.exp(-(numtaps - 1) / 2. * 1j * xp.pi * x / nyq) if ftype > 2: shift *= 1j @@ -656,7 +665,7 @@ def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming', if window is not None: # Create the window to apply to the filter coefficients. from .windows import get_window - wind = get_window(window, numtaps, fftbins=False) + wind = get_window(window, numtaps, fftbins=False, xp=xp) else: wind = 1 @@ -665,7 +674,7 @@ def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming', out = out_full[:numtaps] * wind if ftype == 3: - out[out.size // 2] = 0.0 + out[xp_size(out) // 2] = 0.0 return out @@ -822,6 +831,12 @@ def remez(numtaps, bands, desired, *, weight=None, type='bandpass', >>> plt.show() """ + xp = array_namespace(bands, desired, weight) + bands = np.asarray(bands) + desired = np.asarray(desired) + if weight: + weight = np.asarray(weight) + fs = _validate_fs(fs, allow_none=True) fs = 1.0 if fs is None else fs @@ -837,8 +852,9 @@ def remez(numtaps, bands, desired, *, weight=None, type='bandpass', weight = [1] * len(desired) bands = np.asarray(bands).copy() - return _sigtools._remez(numtaps, bands, desired, weight, tnum, fs, - maxiter, grid_density) + result = _sigtools._remez(numtaps, bands, desired, weight, tnum, fs, + maxiter, grid_density) + return xp.asarray(result) def firls(numtaps, bands, desired, *, weight=None, fs=None): @@ -951,6 +967,10 @@ def firls(numtaps, bands, desired, *, weight=None, fs=None): >>> plt.show() """ + xp = array_namespace(bands, desired) + bands = np.asarray(bands) + desired = np.asarray(desired) + fs = _validate_fs(fs, allow_none=True) fs = 2 if fs is None else fs nyq = 0.5 * fs @@ -1054,10 +1074,10 @@ def firls(numtaps, bands, desired, *, weight=None, fs=None): # make coefficients symmetric (linear phase) coeffs = np.hstack((a[:0:-1], 2 * a[0], a[1:])) - return coeffs + return xp.asarray(coeffs) -def _dhtm(mag): +def _dhtm(mag, xp): """Compute the modified 1-D discrete Hilbert transform Parameters @@ -1068,20 +1088,20 @@ def _dhtm(mag): """ # Adapted based on code by Niranjan Damera-Venkata, # Brian L. Evans and Shawn R. McCaslin (see refs for `minimum_phase`) - sig = np.zeros(len(mag)) + sig = xp.zeros(mag.shape[0]) # Leave Nyquist and DC at 0, knowing np.abs(fftfreq(N)[midpt]) == 0.5 - midpt = len(mag) // 2 + midpt = mag.shape[0] // 2 sig[1:midpt] = 1 sig[midpt+1:] = -1 # eventually if we want to support complex filters, we will need a # np.abs() on the mag inside the log, and should remove the .real - recon = ifft(mag * np.exp(fft(sig * ifft(np.log(mag))))).real + recon = xp.real(ifft(mag * xp.exp(fft(sig * ifft(xp.log(mag)))))) return recon -def minimum_phase(h: np.ndarray, +def minimum_phase(h, method: Literal['homomorphic', 'hilbert'] = 'homomorphic', - n_fft: int | None = None, *, half: bool = True) -> np.ndarray: + n_fft: int | None = None, *, half: bool = True): """Convert a linear-phase FIR filter to minimum phase Parameters @@ -1232,13 +1252,16 @@ def minimum_phase(h: np.ndarray, linear filter `h` whereas the other minimum phase filters have only half the order and the square root of the magnitude response. """ - h = np.asarray(h) - if np.iscomplexobj(h): + xp = array_namespace(h) + + h = xp.asarray(h) + if xp.isdtype(h.dtype, "complex floating"): raise ValueError('Complex filters not supported') - if h.ndim != 1 or h.size <= 2: + if h.ndim != 1 or h.shape[0] <= 2: raise ValueError('h must be 1-D and at least 2 samples long') - n_half = len(h) // 2 - if not np.allclose(h[-n_half:][::-1], h[:n_half]): + n_half = h.shape[0] // 2 + + if not xp.any(xp.flip(h[-n_half:]) - h[:n_half] <= 1e-8 + 1e-6*abs(h[:n_half])): warnings.warn('h does not appear to by symmetric, conversion may fail', RuntimeWarning, stacklevel=2) if not isinstance(method, str) or method not in \ @@ -1247,45 +1270,46 @@ def minimum_phase(h: np.ndarray, if method == "hilbert" and not half: raise ValueError("`half=False` is only supported when `method='homomorphic'`") if n_fft is None: - n_fft = 2 ** int(np.ceil(np.log2(2 * (len(h) - 1) / 0.01))) + n_fft = 2 ** int(ceil(log2(2 * (h.shape[0] - 1) / 0.01))) n_fft = int(n_fft) - if n_fft < len(h): + if n_fft < h.shape[0]: raise ValueError(f'n_fft must be at least len(h)=={len(h)}') + if method == 'hilbert': - w = np.arange(n_fft) * (2 * np.pi / n_fft * n_half) - H = np.real(fft(h, n_fft) * np.exp(1j * w)) + w = xp.arange(n_fft, dtype=xp.float64) * (2 * xp.pi / n_fft * n_half) + H = xp.real(fft(h, n_fft) * xp.exp(1j * w)) dp = max(H) - 1 ds = 0 - min(H) - S = 4. / (np.sqrt(1+dp+ds) + np.sqrt(1-dp+ds)) ** 2 + S = 4. / (xp.sqrt(1+dp+ds) + xp.sqrt(1-dp+ds)) ** 2 H += ds H *= S - H = np.sqrt(H, out=H) + H = xp.sqrt(H) H += 1e-10 # ensure that the log does not explode - h_minimum = _dhtm(H) + h_minimum = _dhtm(H, xp) else: # method == 'homomorphic' # zero-pad; calculate the DFT - h_temp = np.abs(fft(h, n_fft)) + h_temp = xp.abs(fft(h, n_fft)) # take 0.25*log(|H|**2) = 0.5*log(|H|) - h_temp += 1e-7 * h_temp[h_temp > 0].min() # don't let log blow up - np.log(h_temp, out=h_temp) + h_temp += 1e-7 * xp.min(h_temp[h_temp > 0]) # don't let log blow up + h_temp = xp.log(h_temp) if half: # halving of magnitude spectrum optional h_temp *= 0.5 # IDFT - h_temp = ifft(h_temp).real + h_temp = xp.real(ifft(h_temp)) # multiply pointwise by the homomorphic filter # lmin[n] = 2u[n] - d[n] # i.e., double the positive frequencies and zero out the negative ones; # Oppenheim+Shafer 3rd ed p991 eq13.42b and p1004 fig13.7 - win = np.zeros(n_fft) + win = xp.zeros(n_fft) win[0] = 1 stop = n_fft // 2 win[1:stop] = 2 if n_fft % 2: win[stop] = 1 h_temp *= win - h_temp = ifft(np.exp(fft(h_temp))) + h_temp = ifft(xp.exp(fft(h_temp))) h_minimum = h_temp.real - n_out = (n_half + len(h) % 2) if half else len(h) + n_out = (n_half + h.shape[0] % 2) if half else h.shape[0] return h_minimum[:n_out] diff --git a/scipy/signal/_firfilter.cc b/scipy/signal/_firfilter.cc index 6dc5443df36c..9636961b737a 100644 --- a/scipy/signal/_firfilter.cc +++ b/scipy/signal/_firfilter.cc @@ -29,7 +29,18 @@ typedef void (OneMultAddFunction) (char *, char *, int64_t, char **, int64_t); #define MAKE_ONEMULTADD(fname, type) \ static void fname ## _onemultadd(char *sum, char *term1, int64_t str, char **pvals, int64_t n) { \ type dsum = *(type*)sum; \ - for (int64_t k=0; k < n; k++) { \ + int64_t k = 0; \ + for (; k <= n - 4; k += 4) { \ + type tmp0 = *(type*)(term1 + (k + 0) * str); \ + type tmp1 = *(type*)(term1 + (k + 1) * str); \ + type tmp2 = *(type*)(term1 + (k + 2) * str); \ + type tmp3 = *(type*)(term1 + (k + 3) * str); \ + dsum += tmp0 * *(type*)pvals[k + 0] + \ + tmp1 * *(type*)pvals[k + 1] + \ + tmp2 * *(type*)pvals[k + 2] + \ + tmp3 * *(type*)pvals[k + 3]; \ + } \ + for (; k < n; k++) { \ type tmp = *(type*)(term1 + k * str); \ dsum += tmp * *(type*)pvals[k]; \ } \ diff --git a/scipy/signal/_lti_conversion.py b/scipy/signal/_lti_conversion.py index 52c6efbbfa53..eb2c7fe309c6 100644 --- a/scipy/signal/_lti_conversion.py +++ b/scipy/signal/_lti_conversion.py @@ -431,8 +431,9 @@ def cont2discrete(system, dt, method="zoh", alpha=None): >>> plt.show() """ - if len(system) == 1: - return system.to_discrete() + if hasattr(system, 'to_discrete') and callable(system.to_discrete): + return system.to_discrete(dt=dt, method=method, alpha=alpha) + if len(system) == 2: sysd = cont2discrete(tf2ss(system[0], system[1]), dt, method=method, alpha=alpha) diff --git a/scipy/signal/_polyutils.py b/scipy/signal/_polyutils.py new file mode 100644 index 000000000000..04d6686924bd --- /dev/null +++ b/scipy/signal/_polyutils.py @@ -0,0 +1,172 @@ +"""Partial replacements for numpy polynomial routines, with Array API compatibility. + +This module contains both "old-style", np.poly1d, routines from the main numpy +namespace, and "new-style", np.polynomial.polynomial, routines. + +To distinguish the two sets, the "new-style" routine names start with `npp_` +""" +import scipy._lib.array_api_extra as xpx +from scipy._lib._array_api import xp_promote, xp_default_dtype + + +def _sort_cmplx(arr, xp): + # xp.sort is undefined for complex dtypes. Here we only need some + # consistent way to sort a complex array, including equal magnitude elements. + arr = xp.asarray(arr) + if xp.isdtype(arr.dtype, 'complex floating'): + sorter = abs(arr) + xp.real(arr) + xp.imag(arr)**3 + else: + sorter = arr + + idxs = xp.argsort(sorter) + return arr[idxs] + + +def polyroots(coef, *, xp): + """numpy.roots, best-effor replacement + """ + if coef.shape[0] < 2: + return xp.asarray([], dtype=coef.dtype) + + root_func = getattr(xp, 'roots', None) + if root_func: + # NB: cupy.roots is broken in CuPy 13.x, but CuPy is handled via delegation + # so we never hit this code path with xp being cupy + return root_func(coef) + + # companion matrix + n = coef.shape[0] + a = xp.eye(n - 1, n - 1, k=-1, dtype=coef.dtype) + a[:, -1] = -xp.flip(coef[1:]) / coef[0] + + # non-symmetric eigenvalue problem is not in the spec but is available on e.g. torch + if hasattr(xp.linalg, 'eigvals'): + return xp.linalg.eigvals(a) + else: + import numpy as np + return xp.asarray(np.linalg.eigvals(np.asarray(a))) + + +# https://github.com/numpy/numpy/blob/v2.1.0/numpy/lib/_function_base_impl.py#L1874-L1925 +def _trim_zeros(filt, trim='fb'): + first = 0 + trim = trim.upper() + if 'F' in trim: + for i in filt: + if i != 0.: + break + else: + first = first + 1 + last = filt.shape[0] + if 'B' in trim: + for i in filt[::-1]: + if i != 0.: + break + else: + last = last - 1 + return filt[first:last] + + +# ### Old-style routines ### + + +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/lib/_polynomial_impl.py#L1232 +def _poly1d(c_or_r, *, xp): + """ Constructor of np.poly1d object from an array of coefficients (r=False) + """ + c_or_r = xpx.atleast_nd(c_or_r, ndim=1, xp=xp) + if c_or_r.ndim > 1: + raise ValueError("Polynomial must be 1d only.") + c_or_r = _trim_zeros(c_or_r, trim='f') + if c_or_r.shape[0] == 0: + c_or_r = xp.asarray([0], dtype=c_or_r.dtype) + return c_or_r + + +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/lib/_polynomial_impl.py#L702-L779 +def polyval(p, x, *, xp): + """ Old-style polynomial, `np.polyval` + """ + y = xp.zeros_like(x) + + for pv in p: + y = y * x + pv + return y + + +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/lib/_polynomial_impl.py#L34-L157 +def poly(seq_of_zeros, *, xp): + # Only reproduce the 1D variant of np.poly + seq_of_zeros = xp.asarray(seq_of_zeros) + seq_of_zeros = xpx.atleast_nd(seq_of_zeros, ndim=1, xp=xp) + + if seq_of_zeros.shape[0] == 0: + return 1.0 + + # prefer np.convolve etc, if available + convolve_func = getattr(xp, 'convolve', None) + if convolve_func is None: + from scipy.signal import convolve as convolve_func + + dt = seq_of_zeros.dtype + a = xp.ones((1,), dtype=dt) + one = xp.ones_like(seq_of_zeros[0]) + for zero in seq_of_zeros: + a = convolve_func(a, xp.stack((one, -zero)), mode='full') + + if xp.isdtype(a.dtype, 'complex floating'): + # if complex roots are all complex conjugates, the roots are real. + roots = xp.asarray(seq_of_zeros, dtype=xp.complex128) + if xp.all(_sort_cmplx(roots, xp) == _sort_cmplx(xp.conj(roots), xp)): + a = xp.asarray(xp.real(a), copy=True) + + return a + + +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/lib/_polynomial_impl.py#L912 +def polymul(a1, a2, *, xp): + a1, a2 = _poly1d(a1, xp=xp), _poly1d(a2, xp=xp) + + # prefer np.convolve etc, if available + convolve_func = getattr(xp, 'convolve', None) + if convolve_func is None: + from scipy.signal import convolve as convolve_func + + val = convolve_func(a1, a2) + return val + + +# ### New-style routines ### + + +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/polynomial/polynomial.py#L663 +def npp_polyval(x, c, *, xp, tensor=True): + if xp.isdtype(c.dtype, 'integral'): + c = xp.astype(c, xp_default_dtype(xp)) + + c = xpx.atleast_nd(c, ndim=1, xp=xp) + if isinstance(x, tuple | list): + x = xp.asarray(x) + if tensor: + c = xp.reshape(c, (c.shape + (1,)*x.ndim)) + + c0, _ = xp_promote(c[-1, ...], x, broadcast=True, xp=xp) + for i in range(2, c.shape[0] + 1): + c0 = c[-i, ...] + c0*x + return c0 + + +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/polynomial/polynomial.py#L758-L842 +def npp_polyvalfromroots(x, r, *, xp, tensor=True): + r = xpx.atleast_nd(r, ndim=1, xp=xp) + # if r.dtype.char in '?bBhHiIlLqQpP': + # r = r.astype(np.double) + + if isinstance(x, tuple | list): + x = xp.asarray(x) + + if tensor: + r = xp.reshape(r, r.shape + (1,) * x.ndim) + elif x.ndim >= r.ndim: + raise ValueError("x.ndim must be < r.ndim when tensor == False") + return xp.prod(x - r, axis=0) diff --git a/scipy/signal/_short_time_fft.py b/scipy/signal/_short_time_fft.py index 5da4017fce75..748ac698e282 100644 --- a/scipy/signal/_short_time_fft.py +++ b/scipy/signal/_short_time_fft.py @@ -1699,7 +1699,7 @@ def _post_padding(self, n: int) -> tuple[int, int]: def k_max(self, n: int) -> int: """First sample index after signal end not touched by a time slice. - `k_max` - 1 is the largest sample index of the slice `p_max` for a + `k_max` - 1 is the largest sample index of the slice `p_max` - 1 for a given input signal of `n` samples. A detailed example is provided in the :ref:`tutorial_stft_sliding_win` section of the :ref:`user_guide`. diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index 2c138d24632a..a64b307836c3 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -27,9 +27,10 @@ from ._sosfilt import _sosfilt from scipy._lib._array_api import ( - array_namespace, is_torch, is_numpy, xp_copy, xp_size + array_namespace, is_torch, is_numpy, xp_copy, xp_size, xp_default_dtype ) +from scipy._lib.array_api_compat import is_array_api_obj import scipy._lib.array_api_compat.numpy as np_compat import scipy._lib.array_api_extra as xpx @@ -2653,10 +2654,10 @@ def hilbert2(x, N=None): return x -def envelope(z: np.ndarray, bp_in: tuple[int | None, int | None] = (1, None), *, +def envelope(z, bp_in: tuple[int | None, int | None] = (1, None), *, n_out: int | None = None, squared: bool = False, residual: Literal['lowpass', 'all', None] = 'lowpass', - axis: int = -1) -> np.ndarray: + axis: int = -1): r"""Compute the envelope of a real- or complex-valued signal. Parameters @@ -2871,6 +2872,7 @@ def envelope(z: np.ndarray, bp_in: tuple[int | None, int | None] = (1, None), *, >>> fg0.subplots_adjust(left=0.08, right=0.97, wspace=0.15) >>> plt.show() """ + xp = array_namespace(z) if not (-z.ndim <= axis < z.ndim): raise ValueError(f"Invalid parameter {axis=} for {z.shape=}!") if not (z.shape[axis] > 0): @@ -2893,12 +2895,13 @@ def envelope(z: np.ndarray, bp_in: tuple[int | None, int | None] = (1, None), *, f"for n={z.shape[axis]=} and {bp_in=}!") # moving active axis to end allows to use `...` for indexing: - z = np.moveaxis(z, axis, -1) + z = xp.moveaxis(z, axis, -1) - if np.iscomplexobj(z): + if xp.isdtype(z.dtype, 'complex floating'): Z = sp_fft.fft(z) else: # avoid calculating negative frequency bins for real signals: - Z = np.zeros_like(z, dtype=sp_fft.rfft(z.flat[:1]).dtype) + dt = sp_fft.rfft(z[..., :1]).dtype + Z = xp.zeros_like(z, dtype=dt) Z[..., :n//2 + 1] = sp_fft.rfft(z) if bp.start > 0: # make signal analytic within bp_in band: Z[..., bp] *= 2 @@ -2910,8 +2913,8 @@ def envelope(z: np.ndarray, bp_in: tuple[int | None, int | None] = (1, None), *, bp_shift = slice(bp.start + n//2, bp.stop + n//2) z_bb = sp_fft.ifft(sp_fft.fftshift(Z, axes=-1)[..., bp_shift], n=n_out) * fak - z_env = np.abs(z_bb) if not squared else z_bb.real ** 2 + z_bb.imag ** 2 - z_env = np.moveaxis(z_env, -1, axis) + z_env = xp.abs(z_bb) if not squared else xp.real(z_bb) ** 2 + xp.imag(z_bb) ** 2 + z_env = xp.moveaxis(z_env, -1, axis) # Calculate the residual from the input bandpass filter: if residual is None: @@ -2926,9 +2929,14 @@ def envelope(z: np.ndarray, bp_in: tuple[int | None, int | None] = (1, None), *, else: Z[..., bp.start:], Z[..., 0:(n + 1) // 2] = 0, 0 - z_res = fak * (sp_fft.ifft(Z, n=n_out) if np.iscomplexobj(z) else - sp_fft.irfft(Z, n=n_out)) - return np.stack((z_env, np.moveaxis(z_res, -1, axis)), axis=0) + if xp.isdtype(z.dtype, 'complex floating'): # resample accounts for unpaired bins: + z_res = resample(Z, n_out, axis=-1, domain='freq') # ifft() with corrections + else: # account for unpaired bin at m//2 before doing irfft(): + if n_out != n and (m := min(n, n_out)) % 2 == 0: + Z[..., m//2] *= 2 if n_out < n else 0.5 + z_res = fak * sp_fft.irfft(Z, n=n_out) + return xp.stack((z_env, xp.moveaxis(z_res, -1, axis)), axis=0) + def _cmplx_sort(p): """Sort roots based on magnitude. @@ -3500,210 +3508,301 @@ def invresz(r, p, k, tol=1e-3, rtype='avg'): def resample(x, num, t=None, axis=0, window=None, domain='time'): - """ - Resample `x` to `num` samples using Fourier method along the given axis. + r"""Resample `x` to `num` samples using the Fourier method along the given `axis`. - The resampled signal starts at the same value as `x` but is sampled - with a spacing of ``len(x) / num * (spacing of x)``. Because a - Fourier method is used, the signal is assumed to be periodic. + The resampling is performed by shortening or zero-padding the FFT of `x`. This has + the advantages of providing an ideal antialiasing filter and allowing arbitrary + up- or down-sampling ratios. The main drawback is the requirement of assuming `x` + to be a periodic signal. Parameters ---------- x : array_like - The data to be resampled. + The input signal made up of equidistant samples. If `x` is a multidimensional + array, the parameter `axis` specifies the time/frequency axis. It is assumed + here that ``n_x = x.shape[axis]`` specifies the number of samples and ``T`` the + sampling interval. num : int - The number of samples in the resampled signal. + The number of samples of the resampled output signal. It may be larger or + smaller than ``n_x``. t : array_like, optional - If `t` is given, it is assumed to be the equally spaced sample - positions associated with the signal data in `x`. + If `t` is not ``None``, then the timestamps of the resampled signal are also + returned. `t` must contain at least the first two timestamps of the input + signal `x` (all others are ignored). The timestamps of the output signal are + determined by ``t[0] + T * n_x / num * np.arange(num)`` with + ``T = t[1] - t[0]``. Default is ``None``. axis : int, optional - The axis of `x` that is resampled. Default is 0. + The time/frequency axis of `x` along which the resampling take place. + The Default is 0. window : array_like, callable, string, float, or tuple, optional - Specifies the window applied to the signal in the Fourier - domain. See below for details. - domain : string, optional - A string indicating the domain of the input `x`: - ``time`` Consider the input `x` as time-domain (Default), - ``freq`` Consider the input `x` as frequency-domain. + If not ``None``, it specifies a filter in the Fourier domain, which is applied + before resampling. I.e., the FFT ``X`` of `x` is calculated by + ``X = W * fft(x, axis=axis)``. ``W`` may be interpreted as a spectral windowing + function ``W(f_X)`` which consumes the frequencies ``f_X = fftfreq(n_x, T)``. + + If `window` is a 1d array of length ``n_x`` then ``W=window``. + If `window` is a callable then ``W = window(f_X)``. + Otherwise, `window` is passed to `~scipy.signal.get_window`, i.e., + ``W = fftshift(signal.get_window(window, n_x))``. Default is ``None``. + + domain : 'time' | 'freq', optional + If set to ``'time'`` (default) then an FFT is applied to `x`, otherwise + (``'freq'``) it is asssmued that an FFT was already applied, i.e., + ``x = fft(x_t, axis=axis)`` with ``x_t`` being the input signal in the time + domain. Returns ------- - resampled_x or (resampled_x, resampled_t) - Either the resampled array, or, if `t` was given, a tuple - containing the resampled array and the corresponding resampled - positions. + x_r : ndarray + The resampled signal made up of `num` samples and sampling interval + ``T * n_x / num``. + t_r : ndarray, optional + The `num` equidistant timestamps of `x_r`. + This is only returned if paramater `t` is not ``None``. See Also -------- - decimate : Downsample the signal after applying an FIR or IIR filter. - resample_poly : Resample using polyphase filtering and an FIR filter. + decimate : Downsample a (periodic/non-periodic) signal after applying an FIR + or IIR filter. + resample_poly : Resample a (periodic/non-periodic) signal using polyphase filtering + and an FIR filter. Notes ----- - The argument `window` controls a Fourier-domain window that tapers - the Fourier spectrum before zero-padding to alleviate ringing in - the resampled values for sampled signals you didn't intend to be - interpreted as band-limited. - - If `window` is a function, then it is called with a vector of inputs - indicating the frequency bins (i.e. fftfreq(x.shape[axis]) ). - - If `window` is an array of the same length as `x.shape[axis]` it is - assumed to be the window to be applied directly in the Fourier - domain (with dc and low-frequency first). - - For any other type of `window`, the function `scipy.signal.get_window` - is called to generate the window. - - The first sample of the returned vector is the same as the first - sample of the input vector. The spacing between samples is changed - from ``dx`` to ``dx * len(x) / num``. - - If `t` is not None, then it is used solely to calculate the resampled - positions `resampled_t` - - As noted, `resample` uses FFT transformations, which can be very - slow if the number of input or output samples is large and prime; - see :func:`~scipy.fft.fft`. In such cases, it can be faster to first downsample - a signal of length ``n`` with :func:`~scipy.signal.resample_poly` by a factor of - ``n//num`` before using `resample`. Note that this approach changes the - characteristics of the antialiasing filter. + This function uses the more efficient one-sided FFT, i.e. `~scipy.fft.rfft` / + `~scipy.fft.irfft`, if `x` is real-valued and in the time domain. + Else, the two-sided FFT, i.e., `~scipy.fft.fft` / `~scipy.fft.ifft`, is used + (all FFT functions are taken from the `scipy.fft` module). + + If a `window` is applied to a real-valued `x`, the one-sided spectral windowing + function is determined by taking the average of the negative and the positive + frequency component. This ensures that real-valued signals and complex signals with + zero imaginary part are treated identically. I.e., passing `x` or passing + ``x.astype(np.complex128)`` produce the same numeric result. + + If the number of input or output samples are prime or have few prime factors, this + function may be slow due to utilizing FFTs. Consult `~scipy.fft.prev_fast_len` and + `~scipy.fft.next_fast_len` for determining efficient signals lengths. + Alternatively, utilizing `resample_poly` to calculate an intermediate signal (as + illustrated in the example below) can result in significant speed increases. + + `resample` is intended to be used for periodic signals with equidistant sampling + intervals. For non-periodic signals, `resample_poly` may be a better choice. + Consult the `scipy.interpolate` module for methods of resampling signals with + non-constant sampling intervals. Examples -------- - Note that the end of the resampled data rises to meet the first - sample of the next cycle: + The following example depicts a signal being up-sampled from 20 samples to 100 + samples. The ringing at the beginning of the up-sampled signal is due to + interpreting the signal being periodic. The red square in the plot illustrates that + periodictiy by showing the first sample of the next cycle of the signal. >>> import numpy as np - >>> from scipy import signal - - >>> x = np.linspace(0, 10, 20, endpoint=False) - >>> y = np.cos(-x**2/6.0) - >>> f = signal.resample(y, 100) - >>> xnew = np.linspace(0, 10, 100, endpoint=False) - >>> import matplotlib.pyplot as plt - >>> plt.plot(x, y, 'go-', xnew, f, '.-', 10, y[0], 'ro') - >>> plt.legend(['data', 'resampled'], loc='best') + >>> from scipy.signal import resample + ... + >>> n0, n1 = 20, 100 # number of samples + >>> t0 = np.linspace(0, 10, n0, endpoint=False) # input time stamps + >>> x0 = np.cos(-t0**2/6) # input signal + ... + >>> x1 = resample(x0, n1) # resampled signal + >>> t1 = np.linspace(0, 10, n1, endpoint=False) # timestamps of x1 + ... + >>> fig0, ax0 = plt.subplots(1, 1, tight_layout=True) + >>> ax0.set_title(f"Resampling $x(t)$ from {n0} samples to {n1} samples") + >>> ax0.set(xlabel="Time $t$", ylabel="Amplitude $x(t)$") + >>> ax0.plot(t1, x1, '.-', alpha=.5, label=f"Resampled") + >>> ax0.plot(t0, x0, 'o-', alpha=.5, label="Original") + >>> ax0.plot(10, x0[0], 'rs', alpha=.5, label="Next Cycle") + >>> ax0.legend(loc='best') + >>> ax0.grid(True) >>> plt.show() - Consider the following signal ``y`` where ``len(y)`` is a large prime number: - - >>> N = 55949 - >>> freq = 100 - >>> x = np.linspace(0, 1, N) - >>> y = np.cos(2 * np.pi * freq * x) + The following example compares this function with a naive `~scipy.fft.rfft` / + `~scipy.fft.irfft` combination: An input signal with a sampling interval of one + second is upsampled by a factor of eight. The first figure depicts an odd number of + input samples whereas the second figure an even number. The upper subplots show the + signals over time: The input samples are marked by large green dots, the upsampled + signals by a continuous and a dashed line. The lower subplots show the magnitude + spectrum: The FFT values of the input are depicted by large green dots, which lie + in the frequency interval [-0.5, 0.5] Hz, whereas the frequency interval of the + upsampled signal is [-4, 4] Hz. The continuous green line depicts the upsampled + spectrum without antialiasing filter, which is a periodic continuation of the input + spectrum. The blue x's and orange dots depict the FFT values of the signal created + by the naive approach as well as this function's result. - Due to ``N`` being prime, - - >>> num = 5000 - >>> f = signal.resample(signal.resample_poly(y, 1, N // num), num) + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> from scipy.fft import fftshift, fftfreq, fft, rfft, irfft + >>> from scipy.signal import resample, resample_poly + ... + >>> fac, T0, T1 = 8, 1, 1/8 # upsampling factor and sampling intervals + >>> for n0 in (15, 16): # number of samples of input signal + ... n1 = fac * n0 # number of samples of upsampled signal + ... t0, t1 = T0 * np.arange(n0), T1 * np.arange(n1) # time stamps + ... x0 = np.zeros(n0) # input signal has two non-zero sample values + ... x0[n0//2], x0[n0//2+1] = n0 // 2, -(n0 // 2) + ... + ... x1n = irfft(rfft(x0), n=n1) * n1 / n0 # naive resampling + ... x1r = resample(x0, n1) # resample signal + ... + ... # Determine magnitude spectrum: + ... x0_up = np.zeros_like(x1r) # upsampling without antialiasing filter + ... x0_up[::n1 // n0] = x0 + ... X0, X0_up = (fftshift(fft(x_)) / n0 for x_ in (x0, x0_up)) + ... XX1 = (fftshift(fft(x_)) / n1 for x_ in (x1n, x1r)) + ... f0, f1 = fftshift(fftfreq(n0, T0)), fftshift(fftfreq(n1, T1)) # frequencies + ... df = f0[1] - f0[0] # frequency resolution + ... + ... fig, (ax0, ax1) = plt.subplots(2, 1, layout='constrained', figsize=(5, 4)) + ... ax0.set_title(rf"Upsampling ${fac}\times$ from {n0} to {n1} samples") + ... ax0.set(xlabel="Time $t$ in seconds", ylabel="Amplitude $x(t)$", + ... xlim=(0, n1*T1)) + ... ax0.step(t0, x0, 'C2o-', where='post', alpha=.3, linewidth=2, + ... label="$x_0(t)$ / $X_0(f)$") + ... for x_, l_ in zip((x1n, x1r), ('C0--', 'C1-')): + ... ax0.plot(t1, x_, l_, alpha=.5, label=None) + ... ax0.grid() + ... ax1.set(xlabel=rf"Frequency $f$ in hertz ($\Delta f = {df*1e3:.1f}\,$mHz)", + ... ylabel="Magnitude $|X(f)|$", xlim=(-0.7, 0.7)) + ... ax1.axvspan(0.5/T0, f1[-1], color='gray', alpha=.2) + ... ax1.axvspan(f1[0], -0.5/T0, color='gray', alpha=.2) + ... ax1.plot(f1, abs(X0_up), 'C2-', f0, abs(X0), 'C2o', alpha=.3, linewidth=2) + ... for X_, n_, l_ in zip(XX1, ("naive", "resample"), ('C0x--', 'C1.-')): + ... ax1.plot(f1, abs(X_), l_, alpha=.5, label=n_) + ... ax1.grid() + ... fig.legend(loc='outside lower center', ncols=4) + >>> plt.show() - runs significantly faster than + The first figure shows that upsampling an odd number of samples produces identical + results. The second figure illustrates that the signal produced with the naive + approach (dashed blue line) from an even number of samples does not touch all + original samples. This deviation is due to `resample` correctly treating unpaired + frequency bins. I.e., the input `x1` has a bin pair ±0.5 Hz, whereas the output has + only one unpaired bin at -0.5 Hz, which demands rescaling of that bin pair. + Generally, special treatment is required if ``n_x != num`` and ``min(n_x, num)`` is + even. If the bin values at `±m` are zero, obviously, no special treatment is + needed. Consult the source code of `resample` for details. + + The final example shows how to utilize `resample_poly` to speed up the + down-sampling: The input signal a non-zero value at :math:`t=0` and is downsampled + from 19937 to 128 samples. Since 19937 is prime, the FFT is expected to be slow. To + speed matters up, `resample_poly` is used to downsample first by a factor of ``n0 + // n1 = 155`` and then pass the result to `resample`. Two parameterization of + `resample_poly` are used: Passing ``padtype='wrap'`` treats the input as being + periodic wheras the default parametrization performs zero-padding. The upper + subplot shows the resulting signals over time whereas the lower subplot depicts the + resulting one-sided magnitude spectra. - >>> f = signal.resample(y, num) + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> from scipy.fft import rfftfreq, rfft + >>> from scipy.signal import resample, resample_poly + ... + >>> n0 = 19937 # number of input samples - prime + >>> n1 = 128 # number of output samples - fast FFT length + >>> T0, T1 = 1/n0, 1/n1 # sampling intervals + >>> t0, t1 = np.arange(n0)*T0, np.arange(n1)*T1 # time stamps + ... + >>> x0 = np.zeros(n0) # Input has one non-zero sample + >>> x0[0] = n0 + >>> + >>> x1r = resample(x0, n1) # slow due to n0 being prime + >>> # This is faster: + >>> x1p = resample(resample_poly(x0, 1, n0 // n1, padtype='wrap'), n1) # periodic + >>> x2p = resample(resample_poly(x0, 1, n0 // n1), n1) # with zero-padding + ... + >>> X0 = rfft(x0) / n0 + >>> X1r, X1p, X2p = rfft(x1r) / n1, rfft(x1p) / n1, rfft(x2p) / n1 + >>> f0, f1 = rfftfreq(n0, T0), rfftfreq(n1, T1) + ... + >>> fig, (ax0, ax1) = plt.subplots(2, 1, layout='constrained', figsize=(5, 4)) + >>> ax0.set_title(f"Dowsampled Impulse response (from {n0} to {n1} samples)") + >>> ax0.set(xlabel="Time $t$ in seconds", ylabel="Amplitude $x(t)$", xlim=(-T1, 1)) + >>> for x_ in (x1r, x1p, x2p): + ... ax0.plot(t1, x_, alpha=.5) + >>> ax0.grid() + >>> ax1.set(xlabel=rf"Frequency $f$ in hertz ($\Delta f = {f1[1]}\,$Hz)", + ... ylabel="Magnitude $|X(f)|$", xlim=(0, 0.55/T1)) + >>> ax1.axvspan(0.5/T1, f0[-1], color='gray', alpha=.2) + >>> ax1.plot(f1, abs(X1r), 'C0.-', alpha=.5, label="resample") + >>> ax1.plot(f1, abs(X1p), 'C1.-', alpha=.5, label="resample_poly(padtype='wrap')") + >>> ax1.plot(f1, abs(X2p), 'C2x-', alpha=.5, label="resample_poly") + >>> ax1.grid() + >>> fig.legend(loc='outside lower center', ncols=2) + >>> plt.show() + + The plots show that the results of the "pure" `resample` and the usage of the + default parameters of `resample_poly` agree well. The periodic padding of + `resample_poly` (``padtype='wrap'``) on the other hand produces significant + deviations. This is caused by the disconiuity at the beginning of the signal, for + which the default filter of `resample_poly` is not suited well. This example + illustrates that for some use cases, adpating the `resample_poly` parameters may + be beneficial. `resample` has a big advantage in this regard: It uses the ideal + antialiasing filter with the maximum bandwidth by default. + + Note that the doubled spectral magnitude at the Nyqist frequency of 64 Hz is due the + even number of ``n1=128`` output samples, which requires a special treatment as + discussed in the previous example. """ - if domain not in ('time', 'freq'): - raise ValueError("Acceptable domain flags are 'time' or" - f" 'freq', not domain={domain}") + raise ValueError(f"Parameter {domain=} not in ('time', 'freq')!") - x = np.asarray(x) - Nx = x.shape[axis] - - # Check if we can use faster real FFT - real_input = np.isrealobj(x) - - if domain == 'time': - # Forward transform - if real_input: - X = sp_fft.rfft(x, axis=axis) - else: # Full complex FFT - X = sp_fft.fft(x, axis=axis) - else: # domain == 'freq' - X = x - - # Apply window to spectrum - if window is not None: - if callable(window): - W = window(sp_fft.fftfreq(Nx)) - elif isinstance(window, np.ndarray): - if window.shape != (Nx,): - raise ValueError('window must have the same length as data') - W = window - else: - W = sp_fft.ifftshift(get_window(window, Nx)) - - newshape_W = [1] * x.ndim - newshape_W[axis] = X.shape[axis] - if real_input: - # Fold the window back on itself to mimic complex behavior - W_real = W.copy() - W_real[1:] += W_real[-1:0:-1] - W_real[1:] *= 0.5 - X *= W_real[:newshape_W[axis]].reshape(newshape_W) - else: - X *= W.reshape(newshape_W) - - # Copy each half of the original spectrum to the output spectrum, either - # truncating high frequencies (downsampling) or zero-padding them - # (upsampling) - - # Placeholder array for output spectrum - newshape = list(x.shape) - if real_input: - newshape[axis] = num // 2 + 1 - else: - newshape[axis] = num - Y = np.zeros(newshape, X.dtype) - - # Copy positive frequency components (and Nyquist, if present) - N = min(num, Nx) - nyq = N // 2 + 1 # Slice index that includes Nyquist if present - sl = [slice(None)] * x.ndim - sl[axis] = slice(0, nyq) - Y[tuple(sl)] = X[tuple(sl)] - if not real_input: - # Copy negative frequency components - if N > 2: # (slice expression doesn't collapse to empty array) - sl[axis] = slice(nyq - N, None) - Y[tuple(sl)] = X[tuple(sl)] - - # Split/join Nyquist component(s) if present - # So far we have set Y[+N/2]=X[+N/2] - if N % 2 == 0: - if num < Nx: # downsampling - if real_input: - sl[axis] = slice(N//2, N//2 + 1) - Y[tuple(sl)] *= 2. - else: - # select the component of Y at frequency +N/2, - # add the component of X at -N/2 - sl[axis] = slice(-N//2, -N//2 + 1) - Y[tuple(sl)] += X[tuple(sl)] - elif Nx < num: # upsampling - # select the component at frequency +N/2 and halve it - sl[axis] = slice(N//2, N//2 + 1) - Y[tuple(sl)] *= 0.5 - if not real_input: - temp = Y[tuple(sl)] - # set the component at -N/2 equal to the component at +N/2 - sl[axis] = slice(num-N//2, num-N//2 + 1) - Y[tuple(sl)] = temp - - # Inverse transform - if real_input: - y = sp_fft.irfft(Y, num, axis=axis) - else: - y = sp_fft.ifft(Y, axis=axis, overwrite_x=True) - - y *= (float(num) / float(Nx)) - - if t is None: - return y + xp = array_namespace(x, t) + x = xp.asarray(x) + if x.ndim > 1: # moving active axis to end allows to use `...` in indexing: + x = xp.moveaxis(x, axis, -1) + n_x = x.shape[-1] # number of samples along the time/frequency axis + s_fac = n_x / num # scaling factor represents sample interval dilatation + m = min(num, n_x) # number of relevant frequency bins + m2 = m // 2 + 1 # number of relevant frequency bins of a one-sided FFT + + if window is None: # Determine spectral windowing function: + W = None + elif callable(window): + W = window(sp_fft.fftfreq(n_x)) + elif hasattr(window, 'shape'): # must be an array object + if window.shape != (n_x,): + raise ValueError(f"{window.shape=} != ({n_x},), i.e., window length " + + "is not equal to number of frequency bins!") + W = xp.asarray(window, copy=True) # prevent modifying the function parameters else: - new_t = np.arange(0, num) * (t[1] - t[0]) * Nx / float(num) + t[0] - return y, new_t + W = sp_fft.fftshift(get_window(window, n_x, xp=xp)) + W = xp.astype(W, xp_default_dtype(xp)) # get_window always returns float64 + + if domain == 'time' and not xp.isdtype(x.dtype, 'complex floating'): # use rfft(): + X = sp_fft.rfft(x) + if W is not None: # fold window, i.e., W1[l] = (W[l] + W[-l]) / 2 for l > 0 + n_X = X.shape[-1] + W[1:n_X] += xp.flip(W[-n_X+1:]) #W[:-n_X:-1] + W[1:n_X] /= 2 + X *= W[:n_X] # apply window + X = X[..., :m2] # extract relevant data + if m % 2 == 0 and num != n_x: # Account for unpaired bin at m//2: + X[..., m//2] *= 2 if num < n_x else 0.5 + x_r = sp_fft.irfft(X / s_fac, n=num, overwrite_x=True) + else: # use standard two-sided FFT: + X = sp_fft.fft(x) if domain == 'time' else x + if W is not None: + X = X * W # writing X *= W could modify parameter x + Y = xp.zeros(X.shape[:-1] + (num,), dtype=X.dtype) + Y[..., :m2] = X[..., :m2] # copy part up to Nyquist frequency + if m2 < m: # == m > 2 + Y[..., m2-m:] = X[..., m2-m:] # copy negative frequency part + if m % 2 == 0: # Account for unpaired bin at m//2: + if num < n_x: # down-sampling: unite bin pair into one unpaired bin + Y[..., -m//2] += X[..., -m//2] + elif n_x < num: # up-sampling: split unpaired bin into bin pair + Y[..., m//2] /= 2 + Y[..., num-m//2] = Y[..., m//2] + x_r = sp_fft.ifft(Y / s_fac, n=num, overwrite_x=True) + + if x_r.ndim > 1: # moving active axis back to original position: + x_r = xp.moveaxis(x_r, -1, axis) + if t is not None: + return x_r, t[0] + (t[1] - t[0]) * s_fac * xp.arange(num) + return x_r def resample_poly(x, up, down, axis=0, window=('kaiser', 5.0), @@ -3830,7 +3929,9 @@ def resample_poly(x, up, down, axis=0, window=('kaiser', 5.0), >>> plt.show() """ - x = np.asarray(x) + xp = array_namespace(x) + + x = xp.asarray(x) if up != int(up): raise ValueError("up must be an integer") if down != int(down): @@ -3849,31 +3950,29 @@ def resample_poly(x, up, down, axis=0, window=('kaiser', 5.0), up //= g_ down //= g_ if up == down == 1: - return x.copy() + return xp.asarray(x, copy=True) n_in = x.shape[axis] n_out = n_in * up n_out = n_out // down + bool(n_out % down) - if isinstance(window, (list | np.ndarray)): - window = np.array(window) # use array to force a copy (we modify it) + if isinstance(window, list) or is_array_api_obj(window): + window = xp.asarray(window, copy=True) # force a copy (we modify `window`) if window.ndim > 1: raise ValueError('window must be 1-D') - half_len = (window.size - 1) // 2 + half_len = (xp_size(window) - 1) // 2 h = window else: # Design a linear-phase low-pass FIR filter max_rate = max(up, down) f_c = 1. / max_rate # cutoff of FIR filter (rel. to Nyquist) half_len = 10 * max_rate # reasonable cutoff for sinc-like function - if np.issubdtype(x.dtype, np.complexfloating): - h = firwin(2 * half_len + 1, f_c, - window=window).astype(x.dtype) # match dtype of x - elif np.issubdtype(x.dtype, np.floating): - h = firwin(2 * half_len + 1, f_c, - window=window).astype(x.dtype) # match dtype of x + if xp.isdtype(x.dtype, ("real floating", "complex floating")): + h = firwin(2 * half_len + 1, f_c, window=window) + h = xp.asarray(h, dtype=x.dtype) # match dtype of x else: - h = firwin(2 * half_len + 1, f_c, - window=window) + h = firwin(2 * half_len + 1, f_c, window=window) + h = xp.asarray(h) + h *= up # Zero-pad our filter to put the output samples at the center @@ -3881,16 +3980,20 @@ def resample_poly(x, up, down, axis=0, window=('kaiser', 5.0), n_post_pad = 0 n_pre_remove = (half_len + n_pre_pad) // down # We should rarely need to do this given our filter lengths... - while _output_len(len(h) + n_pre_pad + n_post_pad, n_in, + while _output_len(h.shape[0] + n_pre_pad + n_post_pad, n_in, up, down) < n_out + n_pre_remove: n_post_pad += 1 - h = np.concatenate((np.zeros(n_pre_pad, dtype=h.dtype), h, - np.zeros(n_post_pad, dtype=h.dtype))) + h = xp.concat((xp.zeros(n_pre_pad, dtype=h.dtype), h, + xp.zeros(n_post_pad, dtype=h.dtype))) n_pre_remove_end = n_pre_remove + n_out + # XXX consider using stats.quantile, which is natively Array API compatible + def _median(x, *args, **kwds): + return xp.asarray(np.median(np.asarray(x), *args, **kwds)) + # Remove background depending on the padtype option - funcs = {'mean': np.mean, 'median': np.median, - 'minimum': np.amin, 'maximum': np.amax} + funcs = {'mean': xp.mean, 'median': _median, + 'minimum': xp.min, 'maximum': xp.max} upfirdn_kwargs = {'mode': 'constant', 'cval': 0} if padtype in funcs: background_values = funcs[padtype](x, axis=axis, keepdims=True) @@ -4745,6 +4848,8 @@ def filtfilt(b, a, x, axis=-1, padtype='odd', padlen=None, method='pad', if edge > 0: # Slice the actual signal from the extended signal. y = axis_slice(y, start=edge, stop=-edge, axis=axis) + if is_torch(xp): + y = y.copy() # pytorch/pytorch#59786 : no negative strides in pytorch return xp.asarray(y) @@ -5048,11 +5153,12 @@ def decimate(x, q, n=None, ftype='iir', axis=-1, zero_phase=True): Parameters ---------- x : array_like - The signal to be downsampled, as an N-dimensional array. + The input signal made up of equidistant samples. If `x` is a multidimensional + array, the parameter `axis` specifies the time axis. q : int - The downsampling factor. When using IIR downsampling, it is recommended - to call `decimate` multiple times for downsampling factors higher than - 13. + The downsampling factor, which is a postive integer. When using IIR + downsampling, it is recommended to call `decimate` multiple times for + downsampling factors higher than 13. n : int, optional The order of the filter (1 less than the length for 'fir'). Defaults to 8 for 'iir' and 20 times the downsampling factor for 'fir'. @@ -5081,6 +5187,10 @@ def decimate(x, q, n=None, ftype='iir', axis=-1, zero_phase=True): Notes ----- + For non-integer downsampling factors, `~scipy.signal.resample` can be used. Consult + the `scipy.interpolate` module for methods of resampling signals with non-constant + sampling intervals. + The ``zero_phase`` keyword was added in 0.18.0. The possibility to use instances of ``dlti`` as ``ftype`` was added in 0.18.0. diff --git a/scipy/signal/_spectral_py.py b/scipy/signal/_spectral_py.py index 85002c3c70e0..aefb827aadc9 100644 --- a/scipy/signal/_spectral_py.py +++ b/scipy/signal/_spectral_py.py @@ -4,10 +4,11 @@ import numpy.typing as npt from scipy import fft as sp_fft from . import _signaltools +from ._short_time_fft import ShortTimeFFT, FFT_MODE_TYPE from .windows import get_window from ._arraytools import const_ext, even_ext, odd_ext, zero_ext import warnings -from typing import Literal +from typing import cast, Literal __all__ = ['periodogram', 'welch', 'lombscargle', 'csd', 'coherence', @@ -377,8 +378,8 @@ def periodogram(x, fs=1.0, window='boxcar', nfft=None, detrend='constant', complex data, a two-sided spectrum is always returned. scaling : { 'density', 'spectrum' }, optional Selects between computing the power spectral density ('density') - where `Pxx` has units of V**2/Hz and computing the squared magnitude - spectrum ('spectrum') where `Pxx` has units of V**2, if `x` + where `Pxx` has units of V²/Hz and computing the squared magnitude + spectrum ('spectrum') where `Pxx` has units of V², if `x` is measured in V and `fs` is measured in Hz. Defaults to 'density' axis : int, optional @@ -399,6 +400,12 @@ def periodogram(x, fs=1.0, window='boxcar', nfft=None, detrend='constant', Notes ----- + The ratio of the squared magnitude (``scaling='spectrum'``) divided by the spectral + power density (``scaling='density'``) is the constant factor of + ``sum(abs(window)**2)*fs / abs(sum(window))**2``. + If `return_onesided` is ``True``, the values of the negative frequencies are added + to values of the corresponding positive ones. + Consult the :ref:`tutorial_SpectralAnalysis` section of the :ref:`user_guide` for a discussion of the scalings of the power spectral density and the magnitude (squared) spectrum. @@ -554,6 +561,7 @@ def welch(x, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, See Also -------- + csd: Cross power spectral density using Welch's method periodogram: Simple, optionally modified periodogram lombscargle: Lomb-Scargle periodogram for unevenly sampled data @@ -561,12 +569,16 @@ def welch(x, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, ----- An appropriate amount of overlap will depend on the choice of window and on your requirements. For the default Hann window an overlap of - 50% is a reasonable trade off between accurately estimating the + 50% is a reasonable trade-off between accurately estimating the signal power, while not over counting any of the data. Narrower - windows may require a larger overlap. + windows may require a larger overlap. If `noverlap` is 0, this + method is equivalent to Bartlett's method [2]_. - If `noverlap` is 0, this method is equivalent to Bartlett's method - [2]_. + The ratio of the squared magnitude (``scaling='spectrum'``) divided by the spectral + power density (``scaling='density'``) is the constant factor of + ``sum(abs(window)**2)*fs / abs(sum(window))**2``. + If `return_onesided` is ``True``, the values of the negative frequencies are added + to values of the corresponding positive ones. Consult the :ref:`tutorial_SpectralAnalysis` section of the :ref:`user_guide` for a discussion of the scalings of the power spectral density and @@ -685,7 +697,8 @@ def csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, length of the window. noverlap: int, optional Number of points to overlap between segments. If `None`, - ``noverlap = nperseg // 2``. Defaults to `None`. + ``noverlap = nperseg // 2``. Defaults to `None` and may + not be greater than `nperseg`. nfft : int, optional Length of the FFT used, if a zero padded FFT is desired. If `None`, the FFT length is `nperseg`. Defaults to `None`. @@ -739,13 +752,39 @@ def csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, An appropriate amount of overlap will depend on the choice of window and on your requirements. For the default Hann window an overlap of - 50% is a reasonable trade off between accurately estimating the + 50% is a reasonable trade-off between accurately estimating the signal power, while not over counting any of the data. Narrower windows may require a larger overlap. + The ratio of the cross spectrum (``scaling='spectrum'``) divided by the cross + spectral density (``scaling='density'``) is the constant factor of + ``sum(abs(window)**2)*fs / abs(sum(window))**2``. + If `return_onesided` is ``True``, the values of the negative frequencies are added + to values of the corresponding positive ones. + Consult the :ref:`tutorial_SpectralAnalysis` section of the :ref:`user_guide` for a discussion of the scalings of a spectral density and an (amplitude) spectrum. + Welch's method may be interpreted as taking the average over the time slices of a + (cross-) spectrogram. Internally, this function utilizes the `ShortTimeFFT` to + determine the required (cross-) spectrogram. An example below illustrates that it + is straightforward to calculate `Pxy` directly with the `ShortTimeFFT`. However, + there are some notable differences in the behavior of the `ShortTimeFFT`: + + * There is no direct `ShortTimeFFT` equivalent for the `csd` parameter + combination ``return_onesided=True, scaling='density'``, since + ``fft_mode='onesided2X'`` requires ``'psd'`` scaling. The is due to `csd` + returning the doubled squared magnitude in this case, which does not have a + sensible interpretation. + * `ShortTimeFFT` uses `float64` / `complex128` internally, which is due to the + behavior of the utilized `~scipy.fft` module. Thus, those are the dtypes being + returned. The `csd` function casts the return values to `float32` / `complex64` + if the input is `float32` / `complex64` as well. + * The `csd` function calculates ``np.conj(Sx[q,p]) * Sy[q,p]``, whereas + `~ShortTimeFFT.spectrogram` calculates ``Sx[q,p] * np.conj(Sy[q,p])`` where + ``Sx[q,p]``, ``Sy[q,p]`` are the STFTs of `x` and `y`. Also, the window + positioning is different. + .. versionadded:: 0.16.0 References @@ -759,17 +798,17 @@ def csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, Examples -------- + The following example plots the cross power spectral density of two signals with + some common features: + >>> import numpy as np >>> from scipy import signal >>> import matplotlib.pyplot as plt >>> rng = np.random.default_rng() - - Generate two test signals with some common features. - - >>> fs = 10e3 - >>> N = 1e5 - >>> amp = 20 - >>> freq = 1234.0 + ... + ... # Generate two test signals with some common features: + >>> N, fs = 100_000, 10e3 # number of samples and sampling frequency + >>> amp, freq = 20, 1234.0 # amplitude and frequency of utilized sine signal >>> noise_power = 0.001 * fs / 2 >>> time = np.arange(N) / fs >>> b, a = signal.butter(2, 0.25, 'low') @@ -777,40 +816,133 @@ def csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, >>> y = signal.lfilter(b, a, x) >>> x += amp*np.sin(2*np.pi*freq*time) >>> y += rng.normal(scale=0.1*np.sqrt(noise_power), size=time.shape) + ... + ... # Compute and plot the magnitude of the cross spectral density: + >>> nperseg, noverlap, win = 1024, 512, 'hann' + >>> f, Pxy = signal.csd(x, y, fs, win, nperseg, noverlap) + >>> fig0, ax0 = plt.subplots(tight_layout=True) + >>> ax0.set_title(f"CSD ({win.title()}-window, {nperseg=}, {noverlap=})") + >>> ax0.set(xlabel="Frequency $f$ in kHz", ylabel="CSD Magnitude in V²/Hz") + >>> ax0.semilogy(f/1e3, np.abs(Pxy)) + >>> ax0.grid() + >>> plt.show() - Compute and plot the magnitude of the cross spectral density. + The cross spectral density is calculated by taking the average over the time slices + of a spectrogram: - >>> f, Pxy = signal.csd(x, y, fs, nperseg=1024) - >>> plt.semilogy(f, np.abs(Pxy)) - >>> plt.xlabel('frequency [Hz]') - >>> plt.ylabel('CSD [V**2/Hz]') - >>> plt.show() + >>> SFT = signal.ShortTimeFFT.from_window('hann', fs, nperseg, noverlap, + ... scale_to='psd', fft_mode='onesided2X', + ... phase_shift=None) + >>> Sxy1 = SFT.spectrogram(y, x, detr='constant', k_offset=nperseg//2, + ... p0=0, p1=(N-noverlap) // SFT.hop) + >>> Pxy1 = Sxy1.mean(axis=-1) + >>> np.allclose(Pxy, Pxy1) # same result as with csd() + True + + As discussed in the Notes section, the results of using an approach analogous to + the code snippet above and the `csd` function may deviate due to implementation + details. + Note that the code snippet above can be easily adapted to determine other + statistical properties than the mean value. """ - freqs, _, Pxy = _spectral_helper(x, y, fs, window, nperseg, noverlap, - nfft, detrend, return_onesided, scaling, - axis, mode='psd') + # The following lines are resembling the behavior of the originally utilized + # `_spectral_helper()` function: + same_data, axis = y is x, int(axis) + x = np.asarray(x) + + if not same_data: + y = np.asarray(y) + # Check if we can broadcast the outer axes together + x_outer, y_outer = list(x.shape), list(y.shape) + x_outer.pop(axis) + y_outer.pop(axis) + try: + outer_shape = np.broadcast_shapes(x_outer, y_outer) + except ValueError as e: + raise ValueError('x and y cannot be broadcast together.') from e + if x.size == 0 or y.size == 0: + out_shape = outer_shape + (min([x.shape[axis], y.shape[axis]]),) + empty_out = np.moveaxis(np.empty(out_shape), -1, axis) + return empty_out, empty_out + out_dtype = np.result_type(x, y, np.complex64) + else: # x is y: + if x.size == 0: + return np.empty(x.shape), np.empty(x.shape) + out_dtype = np.result_type(x, np.complex64) + + n = x.shape[axis] if same_data else max(x.shape[axis], y.shape[axis]) + if isinstance(window, str) or isinstance(window, tuple): + nperseg = int(nperseg) if nperseg is not None else 256 + if nperseg < 1: + raise ValueError(f"Parameter {nperseg=} is not a positive integer!") + elif n < nperseg: + warnings.warn(f"{nperseg=} is greater than signal length max(len(x), " + + f"len(y)) = {n}, using nperseg = {n}", stacklevel=3) + nperseg = n + win = get_window(window, nperseg) + else: + win = np.asarray(window) + if nperseg is None: + nperseg = len(win) + if nperseg != len(win): + raise ValueError(f"{nperseg=} does not equal {len(win)=}") + + nfft = int(nfft) if nfft is not None else nperseg + if nfft < nperseg: + raise ValueError(f"{nfft=} must be greater than or equal to {nperseg=}!") + noverlap = int(noverlap) if noverlap is not None else nperseg // 2 + if noverlap >= nperseg: + raise ValueError(f"{noverlap=} must be less than {nperseg=}!") + if np.iscomplexobj(x) and return_onesided: + return_onesided = False + + # using cast() to make mypy happy: + fft_mode = cast(FFT_MODE_TYPE, 'onesided' if return_onesided else 'twosided') + if scaling not in (scales := {'spectrum': 'magnitude', 'density': 'psd'}): + raise ValueError(f"Parameter {scaling=} not in {scales}!") + + SFT = ShortTimeFFT(win, nperseg - noverlap, fs, fft_mode=fft_mode, mfft=nfft, + scale_to=scales[scaling], phase_shift=None) + # csd() calculates X.conj()*Y instead of X*Y.conj(): + Pxy = SFT.spectrogram(y, x, detr=None if detrend is False else detrend, + p0=0, p1=(n - noverlap) // SFT.hop, k_offset=nperseg // 2, + axis=axis) + + # Note: + # 'onesided2X' scaling of ShortTimeFFT conflicts with the + # scaling='spectrum' parameter, since it doubles the squared magnitude, + # which in the view of the ShortTimeFFT implementation does not make sense. + # Hence, the doubling of the square is implemented here: + if return_onesided: + f_axis = Pxy.ndim - 1 + axis if axis < 0 else axis + Pxy = np.moveaxis(Pxy, f_axis, -1) + Pxy[..., 1:-1 if SFT.mfft % 2 == 0 else None] *= 2 + Pxy = np.moveaxis(Pxy, -1, f_axis) # Average over windows. - if len(Pxy.shape) >= 2 and Pxy.size > 0: - if Pxy.shape[-1] > 1: - if average == 'median': - # np.median must be passed real arrays for the desired result - bias = _median_bias(Pxy.shape[-1]) - if np.iscomplexobj(Pxy): - Pxy = (np.median(np.real(Pxy), axis=-1) - + 1j * np.median(np.imag(Pxy), axis=-1)) - else: - Pxy = np.median(Pxy, axis=-1) - Pxy /= bias - elif average == 'mean': - Pxy = Pxy.mean(axis=-1) + if Pxy.shape[-1] > 1: + if average == 'median': + # np.median must be passed real arrays for the desired result + bias = _median_bias(Pxy.shape[-1]) + if np.iscomplexobj(Pxy): + Pxy = (np.median(np.real(Pxy), axis=-1) + + np.median(np.imag(Pxy), axis=-1) * 1j) else: - raise ValueError(f'average must be "median" or "mean", got {average}') + Pxy = np.median(Pxy, axis=-1) + Pxy /= bias + elif average == 'mean': + Pxy = Pxy.mean(axis=-1) else: - Pxy = np.reshape(Pxy, Pxy.shape[:-1]) + raise ValueError(f"Parameter {average=} must be 'median' or 'mean'!") + else: + Pxy = np.reshape(Pxy, Pxy.shape[:-1]) - return freqs, Pxy + # cast output type; + Pxy = Pxy.astype(out_dtype) + if same_data: + Pxy = Pxy.real + return SFT.f, Pxy def spectrogram(x, fs=1.0, window=('tukey', .25), nperseg=None, noverlap=None, @@ -1826,7 +1958,7 @@ def coherence(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, ----- An appropriate amount of overlap will depend on the choice of window and on your requirements. For the default Hann window an overlap of - 50% is a reasonable trade off between accurately estimating the + 50% is a reasonable trade-off between accurately estimating the signal power, while not over counting any of the data. Narrower windows may require a larger overlap. @@ -1890,6 +2022,11 @@ def _spectral_helper(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, padded=False): """Calculate various forms of windowed FFTs for PSD, CSD, etc. + .. legacy:: function + + This function is soley used by the legacy functions `spectrogram` and `stft` + (which are also in this same source file `scipy/signal/_spectral_py.py`). + This is a helper function that implements the commonality between the stft, psd, csd, and spectrogram functions. It is not designed to be called externally. The windows are not averaged over; the result @@ -1934,8 +2071,8 @@ def _spectral_helper(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, complex data, a two-sided spectrum is always returned. scaling : { 'density', 'spectrum' }, optional Selects between computing the cross spectral density ('density') - where `Pxy` has units of V**2/Hz and computing the cross - spectrum ('spectrum') where `Pxy` has units of V**2, if `x` + where `Pxy` has units of V²/Hz and computing the cross + spectrum ('spectrum') where `Pxy` has units of V², if `x` and `y` are measured in V and `fs` is measured in Hz. Defaults to 'density' axis : int, optional @@ -2184,6 +2321,11 @@ def _fft_helper(x, win, detrend_func, nperseg, noverlap, nfft, sides): Calculate windowed FFT, for internal use by `scipy.signal._spectral_helper`. + .. legacy:: function + + This function is solely used by the legacy `_spectral_helper` function, + which is located also in this file. + This is a helper function that does the main FFT calculation for `_spectral helper`. All input validation is performed there, and the data axis is assumed to be the last axis of x. It is not designed to @@ -2233,6 +2375,11 @@ def _triage_segments(window, nperseg, input_length): Parses window and nperseg arguments for spectrogram and _spectral_helper. This is a helper function, not meant to be called externally. + .. legacy:: function + + This function is soley used by the legacy functions `spectrogram` and + `_spectral_helper` (which are also in this file). + Parameters ---------- window : string, tuple, or ndarray diff --git a/scipy/signal/_spline_filters.py b/scipy/signal/_spline_filters.py index e3d080cc9cae..edb69f0d5a20 100644 --- a/scipy/signal/_spline_filters.py +++ b/scipy/signal/_spline_filters.py @@ -579,6 +579,9 @@ def cspline1d_eval(cj, newx, dx=1.0, x0=0): newx = (np.asarray(newx) - x0) / float(dx) cj = np.asarray(cj) + if cj.size == 0: + raise ValueError("Spline coefficients 'cj' must not be empty.") + res = zeros_like(newx, dtype=cj.dtype) if res.size == 0: return xp.asarray(res) @@ -662,6 +665,9 @@ def qspline1d_eval(cj, newx, dx=1.0, x0=0): return xp.asarray(res) cj = np.asarray(cj) + if cj.size == 0: + raise ValueError("Spline coefficients 'cj' must not be empty.") + N = len(cj) cond1 = newx < 0 cond2 = newx > (N - 1) diff --git a/scipy/signal/_support_alternative_backends.py b/scipy/signal/_support_alternative_backends.py index 2cc8f6a8c950..173e21d757ff 100644 --- a/scipy/signal/_support_alternative_backends.py +++ b/scipy/signal/_support_alternative_backends.py @@ -20,7 +20,11 @@ ] # some cupyx.scipy.signal functions are incompatible with their scipy counterparts -CUPY_BLACKLIST = ['lfilter_zi', 'sosfilt_zi'] +CUPY_BLACKLIST = ['lfilter_zi', 'sosfilt_zi', 'get_window', 'envelope', 'remez'] + +# freqz_sos is a sosfreqz rename, and cupy does not have the new name yet (in v13.x) +CUPY_RENAMES = {'freqz_sos': 'sosfreqz'} + def delegate_xp(delegator, module_name): def inner(func): @@ -35,10 +39,12 @@ def wrapper(*args, **kwds): # try delegating to a cupyx/jax namesake if is_cupy(xp) and func.__name__ not in CUPY_BLACKLIST: + func_name = CUPY_RENAMES.get(func.__name__, func.__name__) + # https://github.com/cupy/cupy/issues/8336 import importlib cupyx_module = importlib.import_module(f"cupyx.scipy.{module_name}") - cupyx_func = getattr(cupyx_module, func.__name__) + cupyx_func = getattr(cupyx_module, func_name) return cupyx_func(*args, **kwds) elif is_jax(xp) and func.__name__ in JAX_SIGNAL_FUNCS: spx = scipy_namespace_for(xp) diff --git a/scipy/signal/meson.build b/scipy/signal/meson.build index 655abb9aa683..e5aec46b2294 100644 --- a/scipy/signal/meson.build +++ b/scipy/signal/meson.build @@ -74,6 +74,7 @@ py3.install_sources([ '_ltisys.py', '_max_len_seq.py', '_peak_finding.py', + '_polyutils.py', '_savitzky_golay.py', '_short_time_fft.py', '_signaltools.py', diff --git a/scipy/signal/tests/_scipy_spectral_test_shim.py b/scipy/signal/tests/_scipy_spectral_test_shim.py index 42d3d830d0e3..6003f68c0bce 100644 --- a/scipy/signal/tests/_scipy_spectral_test_shim.py +++ b/scipy/signal/tests/_scipy_spectral_test_shim.py @@ -24,11 +24,10 @@ from numpy.testing import assert_allclose from scipy.signal import ShortTimeFFT -from scipy.signal import csd, get_window, stft, istft +from scipy.signal import get_window, stft, istft from scipy.signal._arraytools import const_ext, even_ext, odd_ext, zero_ext from scipy.signal._short_time_fft import FFT_MODE_TYPE -from scipy.signal._spectral_py import _spectral_helper, _triage_segments, \ - _median_bias +from scipy.signal._spectral_py import _triage_segments def _stft_wrapper(x, fs=1.0, window='hann', nperseg=256, noverlap=None, @@ -226,156 +225,6 @@ def _istft_wrapper(Zxx, fs=1.0, window='hann', nperseg=None, noverlap=None, return t, x, (ST.lower_border_end[0], k_hi) -def _csd_wrapper(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, - nfft=None, detrend='constant', return_onesided=True, - scaling='density', axis=-1, average='mean'): - """Wrapper for the `csd()` function based on `ShortTimeFFT` for - unit testing. - """ - freqs, _, Pxy = _csd_test_shim(x, y, fs, window, nperseg, noverlap, nfft, - detrend, return_onesided, scaling, axis) - - # The following code is taken from csd(): - if len(Pxy.shape) >= 2 and Pxy.size > 0: - if Pxy.shape[-1] > 1: - if average == 'median': - # np.median must be passed real arrays for the desired result - bias = _median_bias(Pxy.shape[-1]) - if np.iscomplexobj(Pxy): - Pxy = (np.median(np.real(Pxy), axis=-1) - + 1j * np.median(np.imag(Pxy), axis=-1)) - else: - Pxy = np.median(Pxy, axis=-1) - Pxy /= bias - elif average == 'mean': - Pxy = Pxy.mean(axis=-1) - else: - raise ValueError(f'average must be "median" or "mean", got {average}') - else: - Pxy = np.reshape(Pxy, Pxy.shape[:-1]) - - return freqs, Pxy - - -def _csd_test_shim(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, - nfft=None, detrend='constant', return_onesided=True, - scaling='density', axis=-1): - """Compare output of _spectral_helper() and ShortTimeFFT, more - precisely _spect_helper_csd() for used in csd_wrapper(). - - The motivation of this function is to test if the ShortTimeFFT-based - wrapper `_spect_helper_csd()` returns the same values as `_spectral_helper`. - This function should only be usd by csd() in (unit) testing. - """ - freqs, t, Pxy = _spectral_helper(x, y, fs, window, nperseg, noverlap, nfft, - detrend, return_onesided, scaling, axis, - mode='psd') - freqs1, Pxy1 = _spect_helper_csd(x, y, fs, window, nperseg, noverlap, nfft, - detrend, return_onesided, scaling, axis) - - np.testing.assert_allclose(freqs1, freqs) - amax_Pxy = max(np.abs(Pxy).max(), 1) if Pxy.size else 1 - atol = np.finfo(Pxy.dtype).resolution * amax_Pxy # needed for large Pxy - # for c_ in range(Pxy.shape[-1]): - # np.testing.assert_allclose(Pxy1[:, c_], Pxy[:, c_], atol=atol) - np.testing.assert_allclose(Pxy1, Pxy, atol=atol) - return freqs, t, Pxy - - -def _spect_helper_csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, - nfft=None, detrend='constant', return_onesided=True, - scaling='density', axis=-1): - """Wrapper for replacing _spectral_helper() by using the ShortTimeFFT - for use by csd(). - - This function should be only used by _csd_test_shim() and is only useful - for testing the ShortTimeFFT implementation. - """ - - # The following lines are taken from the original _spectral_helper(): - same_data = y is x - axis = int(axis) - - # Ensure we have np.arrays, get outdtype - x = np.asarray(x) - if not same_data: - y = np.asarray(y) - # outdtype = np.result_type(x, y, np.complex64) - # else: - # outdtype = np.result_type(x, np.complex64) - - if not same_data: - # Check if we can broadcast the outer axes together - xouter = list(x.shape) - youter = list(y.shape) - xouter.pop(axis) - youter.pop(axis) - try: - outershape = np.broadcast(np.empty(xouter), np.empty(youter)).shape - except ValueError as e: - raise ValueError('x and y cannot be broadcast together.') from e - - if same_data: - if x.size == 0: - return np.empty(x.shape), np.empty(x.shape) - else: - if x.size == 0 or y.size == 0: - outshape = outershape + (min([x.shape[axis], y.shape[axis]]),) - emptyout = np.moveaxis(np.empty(outshape), -1, axis) - return emptyout, emptyout - - if nperseg is not None: # if specified by user - nperseg = int(nperseg) - if nperseg < 1: - raise ValueError('nperseg must be a positive integer') - - # parse window; if array like, then set nperseg = win.shape - n = x.shape[axis] if same_data else max(x.shape[axis], y.shape[axis]) - win, nperseg = _triage_segments(window, nperseg, input_length=n) - - if nfft is None: - nfft = nperseg - elif nfft < nperseg: - raise ValueError('nfft must be greater than or equal to nperseg.') - else: - nfft = int(nfft) - - if noverlap is None: - noverlap = nperseg // 2 - else: - noverlap = int(noverlap) - if noverlap >= nperseg: - raise ValueError('noverlap must be less than nperseg.') - nstep = nperseg - noverlap - - if np.iscomplexobj(x) and return_onesided: - return_onesided = False - - # using cast() to make mypy happy: - fft_mode = cast(FFT_MODE_TYPE, 'onesided' if return_onesided - else 'twosided') - scale = {'spectrum': 'magnitude', 'density': 'psd'}[scaling] - SFT = ShortTimeFFT(win, nstep, fs, fft_mode=fft_mode, mfft=nfft, - scale_to=scale, phase_shift=None) - - # _spectral_helper() calculates X.conj()*Y instead of X*Y.conj(): - Pxy = SFT.spectrogram(y, x, detr=None if detrend is False else detrend, - p0=0, p1=(n-noverlap)//SFT.hop, k_offset=nperseg//2, - axis=axis).conj() - # Note: - # 'onesided2X' scaling of ShortTimeFFT conflicts with the - # scaling='spectrum' parameter, since it doubles the squared magnitude, - # which in the view of the ShortTimeFFT implementation does not make sense. - # Hence, the doubling of the square is implemented here: - if return_onesided: - f_axis = Pxy.ndim - 1 + axis if axis < 0 else axis - Pxy = np.moveaxis(Pxy, f_axis, -1) - Pxy[..., 1:-1 if SFT.mfft % 2 == 0 else None] *= 2 - Pxy = np.moveaxis(Pxy, -1, f_axis) - - return SFT.f, Pxy - - def stft_compare(x, fs=1.0, window='hann', nperseg=256, noverlap=None, nfft=None, detrend=False, return_onesided=True, boundary='zeros', padded=True, axis=-1, scaling='spectrum'): @@ -460,21 +309,3 @@ def istft_compare(Zxx, fs=1.0, window='hann', nperseg=None, noverlap=None, assert_allclose(x_wrapper[k_lo:k_hi], x[k_lo:k_hi], atol=atol, rtol=rtol, err_msg=f"Signal values {e_msg_part}") return t, x - - -def csd_compare(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, - nfft=None, detrend='constant', return_onesided=True, - scaling='density', axis=-1, average='mean'): - """Assert that the results from the existing `csd()` and `_csd_wrapper()` - are close to each other. """ - kw = dict(x=x, y=y, fs=fs, window=window, nperseg=nperseg, - noverlap=noverlap, nfft=nfft, detrend=detrend, - return_onesided=return_onesided, scaling=scaling, axis=axis, - average=average) - freqs0, Pxy0 = csd(**kw) - freqs1, Pxy1 = _csd_wrapper(**kw) - - assert_allclose(freqs1, freqs0) - assert_allclose(Pxy1, Pxy0) - assert_allclose(freqs1, freqs0) - return freqs0, Pxy0 diff --git a/scipy/signal/tests/test_bsplines.py b/scipy/signal/tests/test_bsplines.py index 1c46eda24e2e..16d364bab373 100644 --- a/scipy/signal/tests/test_bsplines.py +++ b/scipy/signal/tests/test_bsplines.py @@ -182,6 +182,11 @@ def test_cspline1d_eval(self, xp): signal.cspline1d_eval(cj, xp.asarray(newx), dx=dx, x0=x[0]), newy ) + with pytest.raises(ValueError, + match="Spline coefficients 'cj' must not be empty."): + signal.cspline1d_eval(xp.asarray([], dtype=xp.float64), + xp.asarray([0.0], dtype=xp.float64)) + @skip_xp_backends(cpu_only=True) def test_qspline1d_eval(self, xp): xp_assert_close(signal.qspline1d_eval(xp.asarray([0., 0]), xp.asarray([0.])), @@ -211,6 +216,11 @@ def test_qspline1d_eval(self, xp): ) xp_assert_close(r, newy) + with pytest.raises(ValueError, + match="Spline coefficients 'cj' must not be empty."): + signal.qspline1d_eval(xp.asarray([], dtype=xp.float64), + xp.asarray([0.0], dtype=xp.float64)) + # i/o dtypes with scipy 1.9.1, likely fixed by backwards compat sepfir_dtype_map = {np.uint8: np.float32, int: np.float64, diff --git a/scipy/signal/tests/test_cont2discrete.py b/scipy/signal/tests/test_cont2discrete.py index 2f7c985fd69b..86da1d5c2e82 100644 --- a/scipy/signal/tests/test_cont2discrete.py +++ b/scipy/signal/tests/test_cont2discrete.py @@ -353,19 +353,27 @@ def test_c2d_ss(self): B = np.array([[0], [1]]) C = np.array([[1, 0]]) D = 0 + dt = 0.05 A_res = np.array([[0.985136404135682, 0.004876671474795], [0.009753342949590, 0.965629718236502]]) B_res = np.array([[0.000122937599964], [0.049135527547844]]) sys_ssc = lti(A, B, C, D) - sys_ssd = sys_ssc.to_discrete(0.05) + sys_ssd = sys_ssc.to_discrete(dt=dt) xp_assert_close(sys_ssd.A, A_res) xp_assert_close(sys_ssd.B, B_res) xp_assert_close(sys_ssd.C, C) xp_assert_close(sys_ssd.D, np.zeros_like(sys_ssd.D)) + sys_ssd2 = c2d(sys_ssc, dt=dt) + + xp_assert_close(sys_ssd2.A, A_res) + xp_assert_close(sys_ssd2.B, B_res) + xp_assert_close(sys_ssd2.C, C) + xp_assert_close(sys_ssd2.D, np.zeros_like(sys_ssd2.D)) + def test_c2d_tf(self): sys = lti([0.5, 0.3], [1.0, 0.4]) diff --git a/scipy/signal/tests/test_filter_design.py b/scipy/signal/tests/test_filter_design.py index 0a59780dee22..c9d7dd9f41cc 100644 --- a/scipy/signal/tests/test_filter_design.py +++ b/scipy/signal/tests/test_filter_design.py @@ -13,10 +13,10 @@ from pytest import raises as assert_raises from scipy._lib._array_api import ( xp_assert_close, xp_assert_equal, - assert_array_almost_equal, + assert_array_almost_equal, xp_size, xp_default_dtype, is_numpy ) -from numpy import array, spacing, sin, pi, sort +from numpy import array, spacing, sin, pi from scipy.signal import (argrelextrema, BadCoefficients, bessel, besselap, bilinear, buttap, butter, buttord, cheb1ap, cheb1ord, cheb2ap, cheb2ord, cheby1, cheby2, ellip, ellipap, ellipord, @@ -28,6 +28,9 @@ lp2bs_zpk) from scipy.signal._filter_design import (_cplxreal, _cplxpair, _norm_factor, _bessel_poly, _bessel_zeros) +from scipy.signal._filter_design import _logspace +from scipy.signal import _polyutils as _pu +from scipy.signal._polyutils import _sort_cmplx skip_xp_backends = pytest.mark.skip_xp_backends xfail_xp_backends = pytest.mark.xfail_xp_backends @@ -175,26 +178,33 @@ def test_real_integer_input(self): class TestTf2zpk: - @pytest.mark.parametrize('dt', (np.float64, np.complex128)) - def test_simple(self, dt): - z_r = np.array([0.5, -0.5]) - p_r = np.array([1.j / np.sqrt(2), -1.j / np.sqrt(2)]) + @skip_xp_backends( + cpu_only=True, reason="XXX zpk2sos is numpy-only", exceptions=['cupy'] + ) + @skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") + @pytest.mark.parametrize('dt', ('float64', 'complex128')) + def test_simple(self, dt, xp): + dtyp = getattr(xp, dt) + + z_r = xp.asarray([0.5, -0.5]) + p_r = xp.asarray([1.j / math.sqrt(2), -1.j / math.sqrt(2)]) # Sort the zeros/poles so that we don't fail the test if the order # changes - z_r.sort() - p_r.sort() - b = np.poly(z_r).astype(dt) - a = np.poly(p_r).astype(dt) + z_r = _sort_cmplx(z_r, xp=xp) + p_r = _sort_cmplx(p_r, xp=xp) + + b = xp.astype(_pu.poly(z_r, xp=xp), dtyp) + a = xp.astype(_pu.poly(p_r, xp=xp), dtyp) z, p, k = tf2zpk(b, a) - z.sort() + z = _sort_cmplx(z, xp=xp) # The real part of `p` is ~0.0, so sort by imaginary part - p = p[np.argsort(p.imag)] + p = p[xp.argsort(xp.imag(p))] assert_array_almost_equal(z, z_r) assert_array_almost_equal(p, p_r) - assert_array_almost_equal(k, 1.) - assert k.dtype == dt + assert math.isclose(xp.real(k), 1.) + assert k.dtype == dtyp def test_bad_filter(self): # Regression test for #651: better handling of badly conditioned @@ -206,76 +216,91 @@ def test_bad_filter(self): class TestZpk2Tf: - def test_identity(self): + def test_identity(self, xp): """Test the identity transfer function.""" - z = [] - p = [] + z = xp.asarray([]) + p = xp.asarray([]) k = 1. b, a = zpk2tf(z, p, k) - b_r = np.array([1.]) # desired result - a_r = np.array([1.]) # desired result + b_r = xp.asarray([1.]) # desired result + a_r = xp.asarray([1.]) # desired result # The test for the *type* of the return values is a regression # test for ticket #1095. In the case p=[], zpk2tf used to # return the scalar 1.0 instead of array([1.0]). xp_assert_equal(b, b_r) - assert isinstance(b, np.ndarray) xp_assert_equal(a, a_r) - assert isinstance(a, np.ndarray) + if is_numpy(xp): + assert isinstance(b, np.ndarray) + assert isinstance(a, np.ndarray) - def test_conj_pair(self): + @skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") + @skip_xp_backends(cpu_only=True, reason="XXX zpk2sos is numpy-only") + def test_conj_pair(self, xp): # conjugate pairs give real-coeff num & den - z = np.array([1j, -1j, 2j, -2j]) + z = xp.asarray([1j, -1j, 2j, -2j]) # shouldn't need elements of pairs to be adjacent - p = np.array([1+1j, 3-100j, 3+100j, 1-1j]) + p = xp.asarray([1+1j, 3-100j, 3+100j, 1-1j]) k = 23 # np.poly should do the right thing, but be explicit about # taking real part - b = k * np.poly(z).real - a = np.poly(p).real + z_np, p_np = map(np.asarray, (z, p)) + b_np = k * np.poly(z_np).real + a_np = np.poly(p_np).real + b, a = map(xp.asarray, (b_np, a_np)) bp, ap = zpk2tf(z, p, k) xp_assert_close(b, bp) xp_assert_close(a, ap) - assert np.isrealobj(bp) - assert np.isrealobj(ap) + assert xp.isdtype(bp.dtype, 'real floating') + assert xp.isdtype(ap.dtype, 'real floating') - def test_complexk(self): + @skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") + @skip_xp_backends( + cpu_only=True, reason="XXX zpk2sos is numpy-only", exceptions=['cupy'] + ) + def test_complexk(self, xp): # regression: z, p real, k complex k gave real b, a - b, a = np.array([1j, 1j]), np.array([1.0, 2]) + b, a = xp.asarray([1j, 1j]), xp.asarray([1.0, 2]) z, p, k = tf2zpk(b, a) - xp_assert_close(k, 1j) + xp_assert_close(k, xp.asarray(1j), check_0d=False) bp, ap = zpk2tf(z, p, k) xp_assert_close(b, bp) xp_assert_close(a, ap) +@skip_xp_backends("jax.numpy", reason='no eig in JAX on GPU.') class TestSos2Zpk: - def test_basic(self): + @skip_xp_backends("dask.array", reason="it https://github.com/dask/dask/issues/11883") + def test_basic(self, xp): sos = [[1, 0, 1, 1, 0, -0.81], [1, 0, 0, 1, 0, +0.49]] + sos = xp.asarray(sos) z, p, k = sos2zpk(sos) - z2 = [1j, -1j, 0, 0] - p2 = [0.9, -0.9, 0.7j, -0.7j] - k2 = 1 - assert_array_almost_equal(sort(z), sort(z2), decimal=4) - assert_array_almost_equal(sort(p), sort(p2), decimal=4) - assert_array_almost_equal(k, k2) + z2 = xp.asarray([1j, -1j, 0, 0]) + p2 = xp.asarray([0.9, -0.9, 0.7j, -0.7j]) + k2 = 1. + assert_array_almost_equal(_sort_cmplx(z, xp), _sort_cmplx(z2, xp), decimal=4) + assert_array_almost_equal(_sort_cmplx(p, xp), _sort_cmplx(p2, xp), decimal=4) + assert math.isclose(k, k2) sos = [[1.00000, +0.61803, 1.0000, 1.00000, +0.60515, 0.95873], [1.00000, -1.61803, 1.0000, 1.00000, -1.58430, 0.95873], [1.00000, +1.00000, 0.0000, 1.00000, +0.97915, 0.00000]] + sos = xp.asarray(sos) z, p, k = sos2zpk(sos) z2 = [-0.3090 + 0.9511j, -0.3090 - 0.9511j, 0.8090 + 0.5878j, 0.8090 - 0.5878j, -1.0000 + 0.0000j, 0] p2 = [-0.3026 + 0.9312j, -0.3026 - 0.9312j, 0.7922 + 0.5755j, 0.7922 - 0.5755j, -0.9791 + 0.0000j, 0] + z2 = xp.asarray(z2) + p2 = xp.asarray(p2) k2 = 1 - assert_array_almost_equal(sort(z), sort(z2), decimal=4) - assert_array_almost_equal(sort(p), sort(p2), decimal=4) + assert_array_almost_equal(_sort_cmplx(z, xp), _sort_cmplx(z2, xp), decimal=4) + assert_array_almost_equal(_sort_cmplx(p, xp), _sort_cmplx(p2, xp), decimal=4) sos = array([[1, 2, 3, 1, 0.2, 0.3], [4, 5, 6, 1, 0.4, 0.5]]) @@ -283,138 +308,164 @@ def test_basic(self): -0.625 - 1.05326872164704j, -0.625 + 1.05326872164704j]) p = array([-0.2 - 0.678232998312527j, -0.2 + 0.678232998312527j, -0.1 - 0.538516480713450j, -0.1 + 0.538516480713450j]) + sos, z, p = map(xp.asarray, (sos, z, p)) k = 4 z2, p2, k2 = sos2zpk(sos) - xp_assert_close(_cplxpair(z2), z) - xp_assert_close(_cplxpair(p2), p) + + xp_assert_close(_sort_cmplx(z2, xp=xp), _sort_cmplx(z, xp=xp)) + xp_assert_close(_sort_cmplx(p2, xp=xp), _sort_cmplx(p, xp=xp)) assert k2 == k @pytest.mark.thread_unsafe - def test_fewer_zeros(self): + def test_fewer_zeros(self, xp): """Test not the expected number of p/z (effectively at origin).""" sos = butter(3, 0.1, output='sos') + sos = xp.asarray(sos) # XXX convert butter z, p, k = sos2zpk(sos) - assert len(z) == 4 - assert len(p) == 4 + assert z.shape[0] == 4 + assert p.shape[0] == 4 sos = butter(12, [5., 30.], 'bandpass', fs=1200., analog=False, output='sos') + xp = xp.asarray(sos) with pytest.warns(BadCoefficients, match='Badly conditioned'): z, p, k = sos2zpk(sos) - assert len(z) == 24 - assert len(p) == 24 + assert z.shape[0] == 24 + assert p.shape[0] == 24 +@skip_xp_backends( + cpu_only=True, reason="XXX zpk2sos is numpy-only", exceptions=['cupy'] +) class TestSos2Tf: - def test_basic(self): - sos = [[1, 1, 1, 1, 0, -1], + def test_basic(self, xp): + sos = [[1.0, 1, 1, 1, 0, -1], [-2, 3, 1, 1, 10, 1]] + sos = xp.asarray(sos) b, a = sos2tf(sos) - assert_array_almost_equal(b, [-2, 1, 2, 4, 1]) - assert_array_almost_equal(a, [1, 10, 0, -10, -1]) + assert_array_almost_equal(b, xp.asarray([-2.0, 1, 2, 4, 1])) + assert_array_almost_equal(a, xp.asarray([1.0, 10, 0, -10, -1])) +@skip_xp_backends(cpu_only=True, reason="XXX zpk2sos is numpy-only") class TestTf2Sos: - def test_basic(self): - num = [2, 16, 44, 56, 32] - den = [3, 3, -15, 18, -12] + def test_basic(self, xp): + num = xp.asarray([2., 16, 44, 56, 32]) + den = xp.asarray([3., 3, -15, 18, -12]) sos = tf2sos(num, den) sos2 = [[0.6667, 4.0000, 5.3333, 1.0000, +2.0000, -4.0000], [1.0000, 2.0000, 2.0000, 1.0000, -1.0000, +1.0000]] + sos2 = xp.asarray(sos2) assert_array_almost_equal(sos, sos2, decimal=4) - b = [1, -3, 11, -27, 18] - a = [16, 12, 2, -4, -1] + b = xp.asarray([1.0, -3, 11, -27, 18]) + a = xp.asarray([16.0, 12, 2, -4, -1]) sos = tf2sos(b, a) sos2 = [[0.0625, -0.1875, 0.1250, 1.0000, -0.2500, -0.1250], [1.0000, +0.0000, 9.0000, 1.0000, +1.0000, +0.5000]] - # assert_array_almost_equal(sos, sos2, decimal=4) + sos2 = xp.asarray(sos2) + #assert_array_almost_equal(sos, sos2, decimal=4) @pytest.mark.parametrize('b, a, analog, sos', - [([1], [1], False, [[1., 0., 0., 1., 0., 0.]]), - ([1], [1], True, [[0., 0., 1., 0., 0., 1.]]), - ([1], [1., 0., -1.01, 0, 0.01], False, + [([1.0], [1.0], False, [[1., 0., 0., 1., 0., 0.]]), + ([1.0], [1.0], True, [[0., 0., 1., 0., 0., 1.]]), + ([1.0], [1., 0., -1.01, 0, 0.01], False, [[1., 0., 0., 1., 0., -0.01], [1., 0., 0., 1., 0., -1]]), - ([1], [1., 0., -1.01, 0, 0.01], True, + ([1.0], [1., 0., -1.01, 0, 0.01], True, [[0., 0., 1., 1., 0., -1], [0., 0., 1., 1., 0., -0.01]])]) - def test_analog(self, b, a, analog, sos): + def test_analog(self, b, a, analog, sos, xp): + b, a, sos = map(xp.asarray, (b, a, sos)) sos2 = tf2sos(b, a, analog=analog) assert_array_almost_equal(sos, sos2, decimal=4) +@skip_xp_backends( + cpu_only=True, reason="XXX zpk2sos is numpy-only", exceptions=['cupy'] +) class TestZpk2Sos: - @pytest.mark.parametrize('dt', 'fdgFDG') +# @pytest.mark.parametrize('dt', 'fdgFDG') + # XXX: quietly remove float128 and complex256 + @pytest.mark.parametrize('dt', ['float32', 'float64', 'complex64', 'complex128']) @pytest.mark.parametrize('pairing, analog', [('nearest', False), ('keep_odd', False), ('minimal', False), ('minimal', True)]) - def test_dtypes(self, dt, pairing, analog): - z = np.array([-1, -1]).astype(dt) - ct = dt.upper() # the poles have to be complex - p = np.array([0.57149 + 0.29360j, 0.57149 - 0.29360j]).astype(ct) - k = np.array(1).astype(dt) + def test_dtypes(self, dt, pairing, analog, xp): + dtype = getattr(xp, dt) + # the poles have to be complex + cdtype = (xp.empty(1, dtype=dtype) + 1j*xp.empty(1, dtype=dtype)).dtype + + z = xp.asarray([-1, -1], dtype=dtype) + p = xp.asarray([0.57149 + 0.29360j, 0.57149 - 0.29360j], dtype=cdtype) + k = xp.asarray(1, dtype=dtype) sos = zpk2sos(z, p, k, pairing=pairing, analog=analog) - sos2 = [[1, 2, 1, 1, -1.14298, 0.41280]] # octave & MATLAB + # octave & MATLAB + sos2 = xp.asarray([[1, 2, 1, 1, -1.14298, 0.41280]], dtype=dtype) assert_array_almost_equal(sos, sos2, decimal=4) - def test_basic(self): + def test_basic(self, xp): for pairing in ('nearest', 'keep_odd'): # # Cases that match octave # - z = [-1, -1] - p = [0.57149 + 0.29360j, 0.57149 - 0.29360j] + z = xp.asarray([-1.0, -1.0]) + p = xp.asarray([0.57149 + 0.29360j, 0.57149 - 0.29360j]) k = 1 sos = zpk2sos(z, p, k, pairing=pairing) - sos2 = [[1, 2, 1, 1, -1.14298, 0.41280]] # octave & MATLAB + sos2 = xp.asarray([[1, 2, 1, 1, -1.14298, 0.41280]]) # octave & MATLAB assert_array_almost_equal(sos, sos2, decimal=4) - z = [1j, -1j] - p = [0.9, -0.9, 0.7j, -0.7j] + z = xp.asarray([1j, -1j]) + p = xp.asarray([0.9, -0.9, 0.7j, -0.7j]) k = 1 sos = zpk2sos(z, p, k, pairing=pairing) sos2 = [[1, 0, 1, 1, 0, +0.49], [1, 0, 0, 1, 0, -0.81]] # octave + sos2 = xp.asarray(sos2) # sos2 = [[0, 0, 1, 1, -0.9, 0], # [1, 0, 1, 1, 0.9, 0]] # MATLAB assert_array_almost_equal(sos, sos2, decimal=4) - z = [] - p = [0.8, -0.5+0.25j, -0.5-0.25j] + z = xp.asarray([]) + p = xp.asarray([0.8, -0.5+0.25j, -0.5-0.25j]) k = 1. sos = zpk2sos(z, p, k, pairing=pairing) sos2 = [[1., 0., 0., 1., 1., 0.3125], [1., 0., 0., 1., -0.8, 0.]] # octave, MATLAB fails + sos2 = xp.asarray(sos2) assert_array_almost_equal(sos, sos2, decimal=4) - z = [1., 1., 0.9j, -0.9j] - p = [0.99+0.01j, 0.99-0.01j, 0.1+0.9j, 0.1-0.9j] + z = xp.asarray([1., 1., 0.9j, -0.9j]) + p = xp.asarray([0.99+0.01j, 0.99-0.01j, 0.1+0.9j, 0.1-0.9j]) k = 1 sos = zpk2sos(z, p, k, pairing=pairing) sos2 = [[1, 0, 0.81, 1, -0.2, 0.82], [1, -2, 1, 1, -1.98, 0.9802]] # octave + sos2 = xp.asarray(sos2) # sos2 = [[1, -2, 1, 1, -0.2, 0.82], # [1, 0, 0.81, 1, -1.98, 0.9802]] # MATLAB assert_array_almost_equal(sos, sos2, decimal=4) - z = [0.9+0.1j, 0.9-0.1j, -0.9] - p = [0.75+0.25j, 0.75-0.25j, 0.9] + z = xp.asarray([0.9+0.1j, 0.9-0.1j, -0.9]) + p = xp.asarray([0.75+0.25j, 0.75-0.25j, 0.9]) k = 1 sos = zpk2sos(z, p, k, pairing=pairing) if pairing == 'keep_odd': sos2 = [[1, -1.8, 0.82, 1, -1.5, 0.625], [1, 0.9, 0, 1, -0.9, 0]] # octave; MATLAB fails + sos2 = xp.asarray(sos2) assert_array_almost_equal(sos, sos2, decimal=4) else: # pairing == 'nearest' sos2 = [[1, 0.9, 0, 1, -1.5, 0.625], [1, -1.8, 0.82, 1, -0.9, 0]] # our algorithm + sos2 = xp.asarray(sos2) assert_array_almost_equal(sos, sos2, decimal=4) # @@ -425,6 +476,8 @@ def test_basic(self): +0.8090 - 0.5878j, -1.0000 + 0.0000j] p = [-0.3026 + 0.9312j, -0.3026 - 0.9312j, 0.7922 + 0.5755j, +0.7922 - 0.5755j, -0.9791 + 0.0000j] + z = xp.asarray(z) + p = xp.asarray(p) k = 1 sos = zpk2sos(z, p, k, pairing=pairing) # sos2 = [[1, 0.618, 1, 1, 0.6052, 0.95870], @@ -433,26 +486,31 @@ def test_basic(self): sos2 = [[1, 1, 0, 1, +0.97915, 0], [1, 0.61803, 1, 1, +0.60515, 0.95873], [1, -1.61803, 1, 1, -1.58430, 0.95873]] + sos2 = xp.asarray(sos2) assert_array_almost_equal(sos, sos2, decimal=4) z = [-1 - 1.4142j, -1 + 1.4142j, -0.625 - 1.0533j, -0.625 + 1.0533j] p = [-0.2 - 0.6782j, -0.2 + 0.6782j, -0.1 - 0.5385j, -0.1 + 0.5385j] + z = xp.asarray(z) + p = xp.asarray(p) k = 4 sos = zpk2sos(z, p, k, pairing=pairing) sos2 = [[4, 8, 12, 1, 0.2, 0.3], [1, 1.25, 1.5, 1, 0.4, 0.5]] # MATLAB + sos2 = xp.asarray(sos2, dtype=xp.float64) # sos2 = [[4, 8, 12, 1, 0.4, 0.5], # [1, 1.25, 1.5, 1, 0.2, 0.3]] # octave xp_assert_close(sos, sos2, rtol=1e-4, atol=1e-4) - z = [] - p = [0.2, -0.5+0.25j, -0.5-0.25j] + z = xp.asarray([]) + p = xp.asarray([0.2, -0.5+0.25j, -0.5-0.25j]) k = 1. sos = zpk2sos(z, p, k, pairing=pairing) sos2 = [[1., 0., 0., 1., -0.2, 0.], [1., 0., 0., 1., 1., 0.3125]] + sos2 = xp.asarray(sos2) # sos2 = [[1., 0., 0., 1., 1., 0.3125], # [1., 0., 0., 1., -0.2, 0]] # octave, MATLAB fails assert_array_almost_equal(sos, sos2, decimal=4) @@ -461,17 +519,16 @@ def test_basic(self): # "Digital Filters and Signal Processing (1995) p.400: # http://books.google.com/books?id=VZ8uabI1pNMC&lpg=PA400&ots=gRD9pi8Jua&dq=Pole%2Fzero%20pairing%20for%20minimum%20roundoff%20noise%20in%20BSF.&pg=PA400#v=onepage&q=Pole%2Fzero%20pairing%20for%20minimum%20roundoff%20noise%20in%20BSF.&f=false - deg2rad = np.pi / 180. + deg2rad = xp.pi / 180. k = 1. # first example - thetas = [22.5, 45, 77.5] - mags = [0.8, 0.6, 0.9] - z = np.array([np.exp(theta * deg2rad * 1j) for theta in thetas]) - z = np.concatenate((z, np.conj(z))) - p = np.array([mag * np.exp(theta * deg2rad * 1j) - for theta, mag in zip(thetas, mags)]) - p = np.concatenate((p, np.conj(p))) + thetas = xp.asarray([22.5, 45, 77.5]) + mags = xp.asarray([0.8, 0.6, 0.9]) + z = xp.exp(1j * deg2rad * thetas) + z = xp.concat((z, xp.conj(z))) + p = xp.exp(1j * deg2rad * thetas) * mags + p = xp.concat((p, xp.conj(p))) sos = zpk2sos(z, p, k) # sos2 = [[1, -0.43288, 1, 1, -0.38959, 0.81], # octave, # [1, -1.41421, 1, 1, -0.84853, 0.36], # MATLAB fails @@ -480,12 +537,13 @@ def test_basic(self): sos2 = [[1, -1.41421, 1, 1, -0.84853, 0.36], [1, -1.84776, 1, 1, -1.47821, 0.64], [1, -0.43288, 1, 1, -0.38959, 0.81]] + sos2 = xp.asarray(sos2) assert_array_almost_equal(sos, sos2, decimal=4) # second example - z = np.array([np.exp(theta * deg2rad * 1j) - for theta in (85., 10.)]) - z = np.concatenate((z, np.conj(z), [1, -1])) + thetas = xp.asarray([85., 10.]) + z = xp.exp(1j * deg2rad * thetas) + z = xp.concat((z, xp.conj(z), xp.asarray([1.0, -1.0]))) sos = zpk2sos(z, p, k) # sos2 = [[1, -0.17431, 1, 1, -0.38959, 0.81], # octave "wrong", @@ -495,6 +553,7 @@ def test_basic(self): sos2 = [[1, 0, -1, 1, -0.84853, 0.36], [1, -1.96962, 1, 1, -1.47821, 0.64], [1, -0.17431, 1, 1, -0.38959, 0.81]] + sos2 = xp.asarray(sos2) assert_array_almost_equal(sos, sos2, decimal=4) # these examples are taken from the doc string, and show the @@ -509,9 +568,10 @@ def test_basic(self): ('minimal', np.array([[0., 1., 1., 0., 1., -0.75], [1., 1., 0.5, 1., -1.6, 0.65]]))]) - def test_pairing(self, pairing, sos): - z1 = np.array([-1, -0.5-0.5j, -0.5+0.5j]) - p1 = np.array([0.75, 0.8+0.1j, 0.8-0.1j]) + def test_pairing(self, pairing, sos, xp): + sos = xp.asarray(sos) + z1 = xp.asarray([-1, -0.5-0.5j, -0.5+0.5j]) + p1 = xp.asarray([0.75, 0.8+0.1j, 0.8-0.1j]) sos2 = zpk2sos(z1, p1, 1, pairing=pairing) assert_array_almost_equal(sos, sos2, decimal=4) @@ -522,14 +582,16 @@ def test_pairing(self, pairing, sos): ([-0.7071+0.7071j, -0.7071-0.7071j, -0.1j, 0.1j], [[0., 0., 1., 1., 0., 0.01], [0., 0., 1., 1., 1.4142, 1.]])]) - def test_analog(self, p, sos_dt): + def test_analog(self, p, sos_dt, xp): # test `analog` argument # for discrete time, poles closest to unit circle should appear last # for cont. time, poles closest to imaginary axis should appear last - sos2_dt = zpk2sos([], p, 1, pairing='minimal', analog=False) - sos2_ct = zpk2sos([], p, 1, pairing='minimal', analog=True) + z, p = xp.asarray([]), xp.asarray(p) + sos_dt = xp.asarray(sos_dt) + sos2_dt = zpk2sos(z, p, 1, pairing='minimal', analog=False) + sos2_ct = zpk2sos(z, p, 1, pairing='minimal', analog=True) assert_array_almost_equal(sos_dt, sos2_dt, decimal=4) - assert_array_almost_equal(sos_dt[::-1], sos2_ct, decimal=4) + assert_array_almost_equal(xp.flip(sos_dt, axis=0), sos2_ct, decimal=4) def test_bad_args(self): with pytest.raises(ValueError, match=r'pairing must be one of'): @@ -548,45 +610,47 @@ def test_bad_args(self): class TestFreqs: - def test_basic(self): - _, h = freqs([1.0], [1.0], worN=8) - assert_array_almost_equal(h, np.ones(8)) + def test_basic(self, xp): + _, h = freqs(xp.asarray([1.0]), xp.asarray([1.0]), worN=8) + assert_array_almost_equal(h, xp.ones(8)) - def test_output(self): + def test_output(self, xp): # 1st order low-pass filter: H(s) = 1 / (s + 1) - w = [0.1, 1, 10, 100] - num = [1] - den = [1, 1] + w = xp.asarray([0.1, 1, 10, 100]) + num = xp.asarray([1.]) + den = xp.asarray([1, 1.]) w, H = freqs(num, den, worN=w) s = w * 1j expected = 1 / (s + 1) - assert_array_almost_equal(H.real, expected.real) - assert_array_almost_equal(H.imag, expected.imag) + assert_array_almost_equal(xp.real(H), xp.real(expected)) + assert_array_almost_equal(xp.imag(H), xp.imag(expected)) - def test_freq_range(self): + @skip_xp_backends("jax.numpy", reason="eigvals not available on CUDA") + def test_freq_range(self, xp): # Test that freqresp() finds a reasonable frequency range. # 1st order low-pass filter: H(s) = 1 / (s + 1) # Expected range is from 0.01 to 10. - num = [1] - den = [1, 1] + num = xp.asarray([1.]) + den = xp.asarray([1, 1.]) n = 10 - expected_w = np.logspace(-2, 1, n) + expected_w = _logspace(-2, 1, n, xp=xp) w, H = freqs(num, den, worN=n) assert_array_almost_equal(w, expected_w) - def test_plot(self): + def test_plot(self, xp): def plot(w, h): - assert_array_almost_equal(h, np.ones(8)) + assert_array_almost_equal(h, xp.ones(8)) - assert_raises(ZeroDivisionError, freqs, [1.0], [1.0], worN=8, - plot=lambda w, h: 1 / 0) - freqs([1.0], [1.0], worN=8, plot=plot) + with assert_raises(ZeroDivisionError): + freqs([1.0], [1.0], worN=8, plot=lambda w, h: 1 / 0) - def test_backward_compat(self): + freqs(xp.asarray([1.0]), xp.asarray([1.0]), worN=8, plot=plot) + + def test_backward_compat(self, xp): # For backward compatibility, test if None act as a wrapper for default - w1, h1 = freqs([1.0], [1.0]) - w2, h2 = freqs([1.0], [1.0], None) + w1, h1 = freqs(xp.asarray([1.0]), xp.asarray([1.0])) + w2, h2 = freqs(xp.asarray([1.0]), xp.asarray([1.0]), None) assert_array_almost_equal(w1, w2) assert_array_almost_equal(h1, h2) @@ -607,47 +671,55 @@ def test_w_or_N_types(self): class TestFreqs_zpk: - def test_basic(self): - _, h = freqs_zpk([1.0], [1.0], [1.0], worN=8) - assert_array_almost_equal(h, np.ones(8)) + def test_basic(self, xp): + _, h = freqs_zpk( + xp.asarray([1.0]), xp.asarray([1.0]), xp.asarray([1.0]), worN=8 + ) + assert_array_almost_equal(h, xp.ones(8)) - def test_output(self): + def test_output(self, xp): # 1st order low-pass filter: H(s) = 1 / (s + 1) - w = [0.1, 1, 10, 100] - z = [] - p = [-1] + w = xp.asarray([0.1, 1, 10, 100]) + z = xp.asarray([]) + p = xp.asarray([-1.0]) k = 1 w, H = freqs_zpk(z, p, k, worN=w) s = w * 1j expected = 1 / (s + 1) - assert_array_almost_equal(H.real, expected.real) - assert_array_almost_equal(H.imag, expected.imag) + assert_array_almost_equal(xp.real(H), xp.real(expected)) + assert_array_almost_equal(xp.imag(H), xp.imag(expected)) - def test_freq_range(self): + def test_freq_range(self, xp): # Test that freqresp() finds a reasonable frequency range. # 1st order low-pass filter: H(s) = 1 / (s + 1) # Expected range is from 0.01 to 10. - z = [] - p = [-1] + z = xp.asarray([]) + p = xp.asarray([-1.]) k = 1 n = 10 - expected_w = np.logspace(-2, 1, n) + expected_w = _logspace(-2, 1, n, xp=xp) w, H = freqs_zpk(z, p, k, worN=n) assert_array_almost_equal(w, expected_w) - def test_vs_freqs(self): + @skip_xp_backends("jax.numpy", reason="eigvals not available on CUDA") + def test_vs_freqs(self, xp): b, a = cheby1(4, 5, 100, analog=True, output='ba') z, p, k = cheby1(4, 5, 100, analog=True, output='zpk') + b, a = map(xp.asarray, (b, a)) # XXX convert cheby1 + z, p = map(xp.asarray, (z, p)) + w1, h1 = freqs(b, a) w2, h2 = freqs_zpk(z, p, k) xp_assert_close(w1, w2) xp_assert_close(h1, h2, rtol=1e-6) - def test_backward_compat(self): + def test_backward_compat(self, xp): # For backward compatibility, test if None act as a wrapper for default - w1, h1 = freqs_zpk([1.0], [1.0], [1.0]) - w2, h2 = freqs_zpk([1.0], [1.0], [1.0], None) + # Also, keep testing `k` a length-one list: it is documented as a scalar, + # but the implementation was allowing for a one-element array-likes + w1, h1 = freqs_zpk(xp.asarray([1.0]), xp.asarray([1.0]), [1.0]) + w2, h2 = freqs_zpk(xp.asarray([1.0]), xp.asarray([1.0]), [1.0], None) assert_array_almost_equal(w1, w2) assert_array_almost_equal(h1, h2) @@ -668,97 +740,129 @@ def test_w_or_N_types(self): class TestFreqz: - def test_ticket1441(self): + def test_ticket1441(self, xp): """Regression test for ticket 1441.""" # Because freqz previously used arange instead of linspace, # when N was large, it would return one more point than # requested. N = 100000 - w, h = freqz([1.0], worN=N) + w, h = freqz(xp.asarray([1.0]), worN=N) assert w.shape == (N,) - def test_basic(self): - w, h = freqz([1.0], worN=8) - assert_array_almost_equal(w, np.pi * np.arange(8) / 8.) - assert_array_almost_equal(h, np.ones(8)) - w, h = freqz([1.0], worN=9) - assert_array_almost_equal(w, np.pi * np.arange(9) / 9.) - assert_array_almost_equal(h, np.ones(9)) - - for a in [1, np.ones(2)]: - w, h = freqz(np.ones(2), a, worN=0) + def test_gh_22886(self, xp): + w, h = freqz(xp.asarray([1.]), worN=xp.asarray([0., 0.1])) + xp_assert_equal(w, xp.asarray([0. , 0.1])) + xp_assert_equal(h, xp.asarray([1.+0.j, 1.+0.j])) + + def test_basic(self, xp): + w, h = freqz(xp.asarray([1.0]), worN=8) + assert_array_almost_equal(w, xp.pi * xp.arange(8, dtype=w.dtype) / 8.) + assert_array_almost_equal(h, xp.ones(8)) + w, h = freqz(xp.asarray([1.0]), worN=9) + assert_array_almost_equal(w, xp.pi * xp.arange(9, dtype=w.dtype) / 9.) + assert_array_almost_equal(h, xp.ones(9)) + + for a in [1, xp.ones(2)]: + w, h = freqz(xp.ones(2), a, worN=0) assert w.shape == (0,) assert h.shape == (0,) - assert h.dtype == np.dtype('complex128') + hdt = xp.complex128 if xp_default_dtype(xp) == xp.float64 else xp.complex64 + assert h.dtype == hdt - t = np.linspace(0, 1, 4, endpoint=False) + def test_basic2(self, xp): + t = xp.linspace(0, 1, 4, endpoint=False) for b, a, h_whole in zip( - ([1., 0, 0, 0], np.sin(2 * np.pi * t)), - ([1., 0, 0, 0], [0.5, 0, 0, 0]), - ([1., 1., 1., 1.], [0, -4j, 0, 4j])): + (xp.asarray([1., 0, 0, 0]), xp.sin(2 * xp.pi * t)), + (xp.asarray([1., 0, 0, 0]), xp.asarray([0.5, 0, 0, 0])), + (xp.asarray([1., 1., 1., 1.]), xp.asarray([0, -4j, 0, 4j])) + ): + w, h = freqz(b, a, worN=4, whole=True) - expected_w = np.linspace(0, 2 * np.pi, 4, endpoint=False) + expected_w = xp.linspace(0, 2 * xp.pi, 4, endpoint=False) assert_array_almost_equal(w, expected_w) assert_array_almost_equal(h, h_whole) + # simultaneously check int-like support w, h = freqz(b, a, worN=np.int32(4), whole=True) assert_array_almost_equal(w, expected_w) assert_array_almost_equal(h, h_whole) + + w, h = freqz(b, a, worN=w, whole=True) + assert_array_almost_equal(w, expected_w) + assert_array_almost_equal(h, h_whole) + + def test_basic3(self): + t = np.linspace(0, 1, 4, endpoint=False) + expected_w = np.linspace(0, 2 * np.pi, 4, endpoint=False) + for b, a, h_whole in zip( + (np.asarray([1., 0, 0, 0]), np.sin(2 * np.pi * t)), + (np.asarray([1., 0, 0, 0]), np.asarray([0.5, 0, 0, 0])), + (np.asarray([1., 1., 1., 1.]), np.asarray([0, -4j, 0, 4j])) + ): + + w, h = freqz(b, a, worN=np.int32(4), whole=True) + assert_array_almost_equal(w, expected_w) + assert_array_almost_equal(h, h_whole) + w, h = freqz(b, a, worN=w, whole=True) assert_array_almost_equal(w, expected_w) assert_array_almost_equal(h, h_whole) - def test_basic_whole(self): - w, h = freqz([1.0], worN=8, whole=True) - assert_array_almost_equal(w, 2 * np.pi * np.arange(8.0) / 8) - assert_array_almost_equal(h, np.ones(8)) + def test_basic_whole(self, xp): + w, h = freqz(xp.asarray([1.0]), worN=8, whole=True) + assert_array_almost_equal(w, 2 * xp.pi * xp.arange(8.0) / 8) + assert_array_almost_equal(h, xp.ones(8)) - def test_plot(self): + def test_plot(self, xp): def plot(w, h): - assert_array_almost_equal(w, np.pi * np.arange(8.0) / 8) - assert_array_almost_equal(h, np.ones(8)) + assert_array_almost_equal(w, xp.pi * xp.arange(8.0) / 8) + assert_array_almost_equal(h, xp.ones(8)) + + with assert_raises(ZeroDivisionError): + freqz(xp.asarray([1.0]), worN=8, plot=lambda w, h: 1 / 0) - assert_raises(ZeroDivisionError, freqz, [1.0], worN=8, - plot=lambda w, h: 1 / 0) - freqz([1.0], worN=8, plot=plot) + freqz(xp.asarray([1.0]), worN=8, plot=plot) - def test_fft_wrapping(self): + def test_fft_wrapping(self, xp): # Some simple real FIR filters bs = list() # filters as_ = list() hs_whole = list() hs_half = list() # 3 taps - t = np.linspace(0, 1, 3, endpoint=False) - bs.append(np.sin(2 * np.pi * t)) + t = xp.linspace(0, 1, 3, endpoint=False) + bs.append(xp.sin(2 * xp.pi * t)) as_.append(3.) - hs_whole.append([0, -0.5j, 0.5j]) - hs_half.append([0, np.sqrt(1./12.), -0.5j]) + hs_whole.append(xp.asarray([0, -0.5j, 0.5j])) + hs_half.append(xp.asarray([0, math.sqrt(1./12.), -0.5j])) # 4 taps - t = np.linspace(0, 1, 4, endpoint=False) - bs.append(np.sin(2 * np.pi * t)) + t = xp.linspace(0, 1, 4, endpoint=False) + bs.append(xp.sin(2 * xp.pi * t)) as_.append(0.5) - hs_whole.append([0, -4j, 0, 4j]) - hs_half.append([0, np.sqrt(8), -4j, -np.sqrt(8)]) + hs_whole.append(xp.asarray([0, -4j, 0, 4j])) + hs_half.append(xp.asarray([0, math.sqrt(8), -4j, -math.sqrt(8)])) del t for ii, b in enumerate(bs): # whole a = as_[ii] - expected_w = np.linspace(0, 2 * np.pi, len(b), endpoint=False) + expected_w = xp.linspace(0, 2 * xp.pi, b.shape[0], endpoint=False) w, h = freqz(b, a, worN=expected_w, whole=True) # polyval err_msg = f'b = {b}, a={a}' assert_array_almost_equal(w, expected_w, err_msg=err_msg) assert_array_almost_equal(h, hs_whole[ii], err_msg=err_msg) - w, h = freqz(b, a, worN=len(b), whole=True) # FFT + + w, h = freqz(b, a, worN=b.shape[0], whole=True) # FFT assert_array_almost_equal(w, expected_w, err_msg=err_msg) assert_array_almost_equal(h, hs_whole[ii], err_msg=err_msg) + # non-whole - expected_w = np.linspace(0, np.pi, len(b), endpoint=False) + expected_w = xp.linspace(0, xp.pi, b.shape[0], endpoint=False) w, h = freqz(b, a, worN=expected_w, whole=False) # polyval assert_array_almost_equal(w, expected_w, err_msg=err_msg) assert_array_almost_equal(h, hs_half[ii], err_msg=err_msg) - w, h = freqz(b, a, worN=len(b), whole=False) # FFT + + w, h = freqz(b, a, worN=b.shape[0], whole=False) # FFT assert_array_almost_equal(w, expected_w, err_msg=err_msg) assert_array_almost_equal(h, hs_half[ii], err_msg=err_msg) @@ -766,91 +870,100 @@ def test_fft_wrapping(self): # assume polyval is accurate rng = np.random.RandomState(0) for ii in range(2, 10): # number of taps - b = rng.randn(ii) + b = xp.asarray(rng.randn(ii)) for kk in range(2): - a = rng.randn(1) if kk == 0 else rng.randn(3) + a = xp.asarray(rng.randn(1) if kk == 0 else rng.randn(3)) for jj in range(2): if jj == 1: - b = b + rng.randn(ii) * 1j + b = b + xp.asarray(rng.randn(ii)) * 1j + # whole - expected_w = np.linspace(0, 2 * np.pi, ii, endpoint=False) + expected_w = xp.linspace(0, 2 * xp.pi, ii, endpoint=False) w, expected_h = freqz(b, a, worN=expected_w, whole=True) assert_array_almost_equal(w, expected_w) w, h = freqz(b, a, worN=ii, whole=True) assert_array_almost_equal(w, expected_w) - assert_array_almost_equal(h, expected_h) + assert_array_almost_equal(h, expected_h, decimal=4) + # half - expected_w = np.linspace(0, np.pi, ii, endpoint=False) + expected_w = xp.linspace(0, xp.pi, ii, endpoint=False) w, expected_h = freqz(b, a, worN=expected_w, whole=False) assert_array_almost_equal(w, expected_w) w, h = freqz(b, a, worN=ii, whole=False) assert_array_almost_equal(w, expected_w) - assert_array_almost_equal(h, expected_h) + assert_array_almost_equal(h, expected_h, decimal=4) - def test_broadcasting1(self): + def test_broadcasting1(self, xp): # Test broadcasting with worN an integer or a 1-D array, # b and a are n-dimensional arrays. np.random.seed(123) b = np.random.rand(3, 5, 1) a = np.random.rand(2, 1) + b, a = map(xp.asarray, (b, a)) + for whole in [False, True]: # Test with worN being integers (one fast for FFT and one not), # a 1-D array, and an empty array. - for worN in [16, 17, np.linspace(0, 1, 10), np.array([])]: + for worN in [16, 17, xp.linspace(0, 1, 10), xp.asarray([])]: w, h = freqz(b, a, worN=worN, whole=whole) for k in range(b.shape[1]): bk = b[:, k, 0] ak = a[:, 0] ww, hh = freqz(bk, ak, worN=worN, whole=whole) xp_assert_close(ww, w) - xp_assert_close(hh, h[k]) + xp_assert_close(hh, h[k, ...]) - def test_broadcasting2(self): + def test_broadcasting2(self, xp): # Test broadcasting with worN an integer or a 1-D array, # b is an n-dimensional array, and a is left at the default value. np.random.seed(123) b = np.random.rand(3, 5, 1) + b = xp.asarray(b) for whole in [False, True]: - for worN in [16, 17, np.linspace(0, 1, 10)]: + for worN in [16, 17, xp.linspace(0, 1, 10)]: w, h = freqz(b, worN=worN, whole=whole) for k in range(b.shape[1]): bk = b[:, k, 0] ww, hh = freqz(bk, worN=worN, whole=whole) xp_assert_close(ww, w) - xp_assert_close(hh, h[k]) + xp_assert_close(hh, h[k, :]) - def test_broadcasting3(self): + def test_broadcasting3(self, xp): # Test broadcasting where b.shape[-1] is the same length # as worN, and a is left at the default value. np.random.seed(123) N = 16 b = np.random.rand(3, N) + b = xp.asarray(b) for whole in [False, True]: - for worN in [N, np.linspace(0, 1, N)]: + for worN in [N, xp.linspace(0, 1, N)]: w, h = freqz(b, worN=worN, whole=whole) - assert w.size == N + assert xp_size(w) == N for k in range(N): bk = b[:, k] ww, hh = freqz(bk, worN=w[k], whole=whole) - xp_assert_close(ww, np.asarray(w[k])[None]) - xp_assert_close(hh, np.asarray(h[k])[None]) + xp_assert_close(ww, xp.asarray(w[k])[None]) + xp_assert_close(hh, xp.asarray(h[k])[None]) - def test_broadcasting4(self): + def test_broadcasting4(self, xp): # Test broadcasting with worN a 2-D array. np.random.seed(123) b = np.random.rand(4, 2, 1, 1) a = np.random.rand(5, 2, 1, 1) + b, a = map(xp.asarray, (b, a)) + for whole in [False, True]: for worN in [np.random.rand(6, 7), np.empty((6, 0))]: + worN = xp.asarray(worN) w, h = freqz(b, a, worN=worN, whole=whole) xp_assert_close(w, worN, rtol=1e-14) assert h.shape == (2,) + worN.shape for k in range(2): ww, hh = freqz(b[:, k, 0, 0], a[:, k, 0, 0], - worN=worN.ravel(), + worN=xp.reshape(worN, (-1,)), whole=whole) - xp_assert_close(ww, worN.ravel(), rtol=1e-14) - xp_assert_close(hh, h[k, :, :].ravel()) + xp_assert_close(ww, xp.reshape(worN, (-1,)), rtol=1e-14) + xp_assert_close(hh, xp.reshape(h[k, :, :], (-1,))) def test_backward_compat(self): # For backward compatibility, test if None act as a wrapper for default @@ -859,44 +972,44 @@ def test_backward_compat(self): assert_array_almost_equal(w1, w2) assert_array_almost_equal(h1, h2) - def test_fs_param(self): + def test_fs_param(self, xp): fs = 900 - b = [0.039479155677484369, 0.11843746703245311, 0.11843746703245311, - 0.039479155677484369] - a = [1.0, -1.3199152021838287, 0.80341991081938424, - -0.16767146321568049] + b = xp.asarray([0.039479155677484369, 0.11843746703245311, 0.11843746703245311, + 0.039479155677484369]) + a = xp.asarray([1.0, -1.3199152021838287, 0.80341991081938424, + -0.16767146321568049]) # N = None, whole=False w1, h1 = freqz(b, a, fs=fs) w2, h2 = freqz(b, a) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs/2, 512, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs/2, 512, endpoint=False)) # N = None, whole=True w1, h1 = freqz(b, a, whole=True, fs=fs) w2, h2 = freqz(b, a, whole=True) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs, 512, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs, 512, endpoint=False)) # N = 5, whole=False w1, h1 = freqz(b, a, 5, fs=fs) w2, h2 = freqz(b, a, 5) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs/2, 5, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs/2, 5, endpoint=False)) # N = 5, whole=True w1, h1 = freqz(b, a, 5, whole=True, fs=fs) w2, h2 = freqz(b, a, 5, whole=True) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs, 5, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs, 5, endpoint=False)) # w is an array_like - for w in ([123], (123,), np.array([123]), (50, 123, 230), - np.array([50, 123, 230])): + for w in ([123], (123,), xp.asarray([123]), (50, 123, 230), + xp.asarray([50, 123, 230])): w1, h1 = freqz(b, a, w, fs=fs) - w2, h2 = freqz(b, a, 2*pi*np.array(w)/fs) + w2, h2 = freqz(b, a, 2*pi*xp.asarray(w, dtype=xp.float64)/ fs) xp_assert_close(h1, h2) - xp_assert_close(w, w1, check_dtype=False) + xp_assert_close(w1, xp.asarray(w), check_dtype=False) def test_w_or_N_types(self): # Measure at 7 (polyval) or 8 (fft) equally-spaced points @@ -916,26 +1029,27 @@ def test_w_or_N_types(self): # Measure at frequency 8 Hz for w in (8.0, 8.0+0j): # Only makes sense when fs is specified - w_out, h = freqz([1.0], worN=w, fs=100) - assert_array_almost_equal(w_out, [8]) - assert_array_almost_equal(h, [1]) - - def test_nyquist(self): - w, h = freqz([1.0], worN=8, include_nyquist=True) - assert_array_almost_equal(w, np.pi * np.arange(8) / 7.) - assert_array_almost_equal(h, np.ones(8)) - w, h = freqz([1.0], worN=9, include_nyquist=True) - assert_array_almost_equal(w, np.pi * np.arange(9) / 8.) - assert_array_almost_equal(h, np.ones(9)) - - for a in [1, np.ones(2)]: - w, h = freqz(np.ones(2), a, worN=0, include_nyquist=True) + w_out, h = freqz(np.asarray([1.0]), worN=w, fs=100) + assert_array_almost_equal(w_out, np.asarray([8])) + assert_array_almost_equal(h, np.asarray(1.), check_0d=False) + + def test_nyquist(self, xp): + w, h = freqz(xp.asarray([1.0]), worN=8, include_nyquist=True) + assert_array_almost_equal(w, xp.pi * xp.arange(8, dtype=w.dtype) / 7.) + assert_array_almost_equal(h, xp.ones(8)) + w, h = freqz(xp.asarray([1.0]), worN=9, include_nyquist=True) + assert_array_almost_equal(w, xp.pi * xp.arange(9, dtype=w.dtype) / 8.) + assert_array_almost_equal(h, xp.ones(9)) + + for a in [1, xp.ones(2)]: + w, h = freqz(xp.ones(2), a, worN=0, include_nyquist=True) assert w.shape == (0,) assert h.shape == (0,) - assert h.dtype == np.dtype('complex128') + hdt = xp.complex128 if xp_default_dtype(xp) == xp.float64 else xp.complex64 + assert h.dtype == hdt - w1, h1 = freqz([1.0], worN=8, whole = True, include_nyquist=True) - w2, h2 = freqz([1.0], worN=8, whole = True, include_nyquist=False) + w1, h1 = freqz(xp.asarray([1.0]), worN=8, whole = True, include_nyquist=True) + w2, h2 = freqz(xp.asarray([1.0]), worN=8, whole = True, include_nyquist=False) assert_array_almost_equal(w1, w2) assert_array_almost_equal(h1, h2) @@ -950,8 +1064,8 @@ def test_nyquist(self): (False, True, 257), (True, False, 257), (True, True, 257)]) - def test_17289(self, whole, nyquist, worN): - d = [0, 1] + def test_17289(self, whole, nyquist, worN, xp): + d = xp.asarray([0.0, 1.0]) w, Drfft = freqz(d, worN=32, whole=whole, include_nyquist=nyquist) _, Dpoly = freqz(d, worN=w) xp_assert_close(Drfft, Dpoly) @@ -964,9 +1078,9 @@ def test_fs_validation(self): freqz([1.0], fs=None) -class Testfreqz_sos: +class TestFreqz_sos: - def test_freqz_sos_basic(self): + def test_freqz_sos_basic(self, xp): # Compare the results of freqz and freqz_sos for a low order # Butterworth filter. @@ -974,6 +1088,8 @@ def test_freqz_sos_basic(self): b, a = butter(4, 0.2) sos = butter(4, 0.2, output='sos') + b, a, sos = map(xp.asarray, (b, a, sos)) # XXX until butter is converted + w, h = freqz(b, a, worN=N) w2, h2 = freqz_sos(sos, worN=N) xp_assert_equal(w2, w) @@ -981,132 +1097,158 @@ def test_freqz_sos_basic(self): b, a = ellip(3, 1, 30, (0.2, 0.3), btype='bandpass') sos = ellip(3, 1, 30, (0.2, 0.3), btype='bandpass', output='sos') + b, a, sos = map(xp.asarray, (b, a, sos)) # XXX until ellip is converted + w, h = freqz(b, a, worN=N) w2, h2 = freqz_sos(sos, worN=N) xp_assert_equal(w2, w) xp_assert_close(h2, h, rtol=1e-10, atol=1e-14) + # must have at least one section - assert_raises(ValueError, freqz_sos, sos[:0]) + with assert_raises(ValueError): + freqz_sos(sos[:0, ...]) - def test_backward_compat(self): + def test_backward_compat(self, xp): # For backward compatibility, test if None act as a wrapper for default N = 500 sos = butter(4, 0.2, output='sos') + sos = xp.asarray(sos) # XXX until butter is converted w1, h1 = freqz_sos(sos, worN=N) w2, h2 = sosfreqz(sos, worN=N) assert_array_almost_equal(w1, w2) assert_array_almost_equal(h1, h2) - def test_freqz_sos_design(self): + @skip_xp_backends("dask.array", reason="float cannot be interpreted as in integer") + def test_freqz_sos_design(self, xp): # Compare freqz_sos output against expected values for different # filter types # from cheb2ord N, Wn = cheb2ord([0.1, 0.6], [0.2, 0.5], 3, 60) sos = cheby2(N, 60, Wn, 'stop', output='sos') + sos = xp.asarray(sos) # XXX + zero = xp.asarray(0., dtype=xp.float64) + w, h = freqz_sos(sos) - h = np.abs(h) - w /= np.pi - xp_assert_close(20 * np.log10(h[w <= 0.1]), np.asarray(0.), atol=3.01, + h = xp.abs(h) + w = w / xp.pi + xp_assert_close(20 * xp.log10(h[w <= 0.1]), + zero, atol=3.01, check_shape=False) - xp_assert_close(20 * np.log10(h[w >= 0.6]), np.asarray(0.), atol=3.01, + xp_assert_close(20 * xp.log10(h[w >= 0.6]), + zero, atol=3.01, check_shape=False) xp_assert_close(h[(w >= 0.2) & (w <= 0.5)], - np.asarray(0.), atol=1e-3, + zero, atol=1e-3, check_shape=False) # <= -60 dB N, Wn = cheb2ord([0.1, 0.6], [0.2, 0.5], 3, 150) sos = cheby2(N, 150, Wn, 'stop', output='sos') + sos = xp.asarray(sos) + w, h = freqz_sos(sos) - dB = 20*np.log10(np.abs(h)) - w /= np.pi - xp_assert_close(dB[w <= 0.1], np.asarray(0.0), atol=3.01, check_shape=False) - xp_assert_close(dB[w >= 0.6], np.asarray(0.0), atol=3.01, check_shape=False) - assert np.all(dB[(w >= 0.2) & (w <= 0.5)] < -149.9) + dB = 20*xp.log10(xp.abs(h)) + w = w / xp.pi + xp_assert_close(dB[w <= 0.1], zero, atol=3.01, check_shape=False) + xp_assert_close(dB[w >= 0.6], zero, atol=3.01, check_shape=False) + assert xp.all(dB[(w >= 0.2) & (w <= 0.5)] < -149.9) # from cheb1ord N, Wn = cheb1ord(0.2, 0.3, 3, 40) sos = cheby1(N, 3, Wn, 'low', output='sos') + sos = xp.asarray(sos) + w, h = freqz_sos(sos) - h = np.abs(h) - w /= np.pi - xp_assert_close(20 * np.log10(h[w <= 0.2]), np.asarray(0.0), atol=3.01, + h = xp.abs(h) + w = w / xp.pi + xp_assert_close(20 * xp.log10(h[w <= 0.2]), zero, atol=3.01, check_shape=False) - xp_assert_close(h[w >= 0.3], np.asarray(0.0), atol=1e-2, + xp_assert_close(h[w >= 0.3], zero, atol=1e-2, check_shape=False) # <= -40 dB N, Wn = cheb1ord(0.2, 0.3, 1, 150) sos = cheby1(N, 1, Wn, 'low', output='sos') + sos = xp.asarray(sos) + w, h = freqz_sos(sos) - dB = 20*np.log10(np.abs(h)) + dB = 20*xp.log10(xp.abs(h)) w /= np.pi - xp_assert_close(dB[w <= 0.2], np.asarray(0.0), atol=1.01, - check_shape=False) - assert np.all(dB[w >= 0.3] < -149.9) + xp_assert_close(dB[w <= 0.2], zero, atol=1.01, check_shape=False) + assert xp.all(dB[w >= 0.3] < -149.9) # adapted from ellipord N, Wn = ellipord(0.3, 0.2, 3, 60) sos = ellip(N, 0.3, 60, Wn, 'high', output='sos') + sos = xp.asarray(sos) + w, h = freqz_sos(sos) - h = np.abs(h) - w /= np.pi - xp_assert_close(20 * np.log10(h[w >= 0.3]), np.asarray(0.0), atol=3.01, + h = xp.abs(h) + w = w / xp.pi + xp_assert_close(20 * xp.log10(h[w >= 0.3]), zero, atol=3.01, check_shape=False) - xp_assert_close(h[w <= 0.1], np.asarray(0.0), atol=1.5e-3, + xp_assert_close(h[w <= 0.1], zero, atol=1.5e-3, check_shape=False) # <= -60 dB (approx) # adapted from buttord N, Wn = buttord([0.2, 0.5], [0.14, 0.6], 3, 40) sos = butter(N, Wn, 'band', output='sos') + sos = xp.asarray(sos) + w, h = freqz_sos(sos) - h = np.abs(h) - w /= np.pi + h = xp.abs(h) + w = w / xp.pi h014 = h[w <= 0.14] - xp_assert_close(h014, np.zeros_like(h014), atol=1e-2) # <= -40 dB + xp_assert_close(h014, xp.zeros_like(h014), atol=1e-2) # <= -40 dB h06 = h[w >= 0.6] - xp_assert_close(h06, np.zeros_like(h06), atol=1e-2) # <= -40 dB - h0205 = 20 * np.log10(h[(w >= 0.2) & (w <= 0.5)]) - xp_assert_close(h0205, np.zeros_like(h0205), atol=3.01) + xp_assert_close(h06, xp.zeros_like(h06), atol=1e-2) # <= -40 dB + h0205 = 20 * xp.log10(h[(w >= 0.2) & (w <= 0.5)]) + xp_assert_close(h0205, xp.zeros_like(h0205), atol=3.01) N, Wn = buttord([0.2, 0.5], [0.14, 0.6], 3, 100) sos = butter(N, Wn, 'band', output='sos') + sos = xp.asarray(sos) + w, h = freqz_sos(sos) - dB = 20*np.log10(np.maximum(np.abs(h), 1e-10)) - w /= np.pi + dB = 20*xp.log10(xp.maximum(xp.abs(h), xp.asarray(1e-10))) + w = w / xp.pi - assert np.all(dB[(w > 0) & (w <= 0.14)] < -99.9) - assert np.all(dB[w >= 0.6] < -99.9) + assert xp.all(dB[(w > 0) & (w <= 0.14)] < -99.9) + assert xp.all(dB[w >= 0.6] < -99.9) db0205 = dB[(w >= 0.2) & (w <= 0.5)] - xp_assert_close(db0205, np.zeros_like(db0205), atol=3.01) + xp_assert_close(db0205, xp.zeros_like(db0205), atol=3.01) - def test_freqz_sos_design_ellip(self): + def test_freqz_sos_design_ellip(self, xp): N, Wn = ellipord(0.3, 0.1, 3, 60) sos = ellip(N, 0.3, 60, Wn, 'high', output='sos') + sos = xp.asarray(sos) + w, h = freqz_sos(sos) - h = np.abs(h) - w /= np.pi + h = xp.abs(h) + w = w / xp.pi - h03 = 20 * np.log10(h[w >= 0.3]) - xp_assert_close(h03, np.zeros_like(h03), atol=3.01) + h03 = 20 * xp.log10(h[w >= 0.3]) + xp_assert_close(h03, xp.zeros_like(h03), atol=3.01) h01 = h[w <= 0.1] - xp_assert_close(h01, np.zeros_like(h01), atol=1.5e-3) # <= -60 dB (approx) + xp_assert_close(h01, xp.zeros_like(h01), atol=1.5e-3) # <= -60 dB (approx) N, Wn = ellipord(0.3, 0.2, .5, 150) sos = ellip(N, .5, 150, Wn, 'high', output='sos') + sos = xp.asarray(sos) + w, h = freqz_sos(sos) - dB = 20*np.log10(np.maximum(np.abs(h), 1e-10)) - w /= np.pi + dB = 20*xp.log10(xp.maximum(xp.abs(h), xp.asarray(1e-10))) + w = w / xp.pi db03 = dB[w >= 0.3] - xp_assert_close(db03, np.zeros_like(db03), atol=.55) + xp_assert_close(db03, xp.zeros_like(db03), atol=.55) # Allow some numerical slop in the upper bound -150, so this is # a check that dB[w <= 0.2] is less than or almost equal to -150. - assert dB[w <= 0.2].max() < -150*(1 - 1e-12) + assert xp.max(dB[w <= 0.2]) < -150*(1 - 1e-12) @mpmath_check("0.10") - def test_freqz_sos_against_mp(self): + def test_freqz_sos_against_mp(self, xp): # Compare the result of freqz_sos applied to a high order Butterworth # filter against the result computed using mpmath. (signal.freqz fails # miserably with such high order filters.) @@ -1117,49 +1259,63 @@ def test_freqz_sos_against_mp(self): with mpmath.workdps(80): z_mp, p_mp, k_mp = mpsig.butter_lp(order, Wn) w_mp, h_mp = mpsig.zpkfreqz(z_mp, p_mp, k_mp, N) - w_mp = np.array([float(x) for x in w_mp]) - h_mp = np.array([complex(x) for x in h_mp]) + w_mp = xp.asarray([float(x) for x in w_mp], dtype=xp.float64) + h_mp = xp.asarray([complex(x) for x in h_mp], dtype=xp.complex128) sos = butter(order, Wn, output='sos') + sos = xp.asarray(sos, dtype=xp.float64) w, h = freqz_sos(sos, worN=N) xp_assert_close(w, w_mp, rtol=1e-12, atol=1e-14) xp_assert_close(h, h_mp, rtol=1e-12, atol=1e-14) - def test_fs_param(self): + def test_fs_param(self, xp): fs = 900 - sos = [[0.03934683014103762, 0.07869366028207524, 0.03934683014103762, + sos = xp.asarray( + [[0.03934683014103762, 0.07869366028207524, 0.03934683014103762, 1.0, -0.37256600288916636, 0.0], [1.0, 1.0, 0.0, 1.0, -0.9495739996946778, 0.45125966317124144]] + ) # N = None, whole=False w1, h1 = freqz_sos(sos, fs=fs) w2, h2 = freqz_sos(sos) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs/2, 512, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs/2, 512, endpoint=False)) # N = None, whole=True w1, h1 = freqz_sos(sos, whole=True, fs=fs) w2, h2 = freqz_sos(sos, whole=True) xp_assert_close(h1, h2, atol=1e-27) - xp_assert_close(w1, np.linspace(0, fs, 512, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs, 512, endpoint=False)) # N = 5, whole=False w1, h1 = freqz_sos(sos, 5, fs=fs) w2, h2 = freqz_sos(sos, 5) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs/2, 5, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs/2, 5, endpoint=False)) # N = 5, whole=True w1, h1 = freqz_sos(sos, 5, whole=True, fs=fs) w2, h2 = freqz_sos(sos, 5, whole=True) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs, 5, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs, 5, endpoint=False)) + + @skip_xp_backends(np_only=True, reason="array-likes") + def test_fs_param2(self, xp): + fs = 900 + sos = xp.asarray( + [[0.03934683014103762, 0.07869366028207524, 0.03934683014103762, + 1.0, -0.37256600288916636, 0.0], + [1.0, 1.0, 0.0, 1.0, -0.9495739996946778, 0.45125966317124144]] + ) # w is an array_like - for w in ([123], (123,), np.array([123]), (50, 123, 230), - np.array([50, 123, 230])): + for w in ([123], (123,), xp.asarray([123]), (50, 123, 230), + xp.asarray([50, 123, 230])): w1, h1 = freqz_sos(sos, w, fs=fs) - w2, h2 = freqz_sos(sos, 2*pi*np.array(w)/fs) + w1, h1 = map(xp.asarray, (w1, h1)) + + w2, h2 = freqz_sos(sos, 2*pi*xp.asarray(w, dtype=sos.dtype)/fs) xp_assert_close(h1, h2) xp_assert_close(w, w1, check_dtype=False) @@ -1193,77 +1349,94 @@ def test_fs_validation(self): class TestFreqz_zpk: - def test_ticket1441(self): + def test_ticket1441(self, xp): """Regression test for ticket 1441.""" # Because freqz previously used arange instead of linspace, # when N was large, it would return one more point than # requested. N = 100000 - w, h = freqz_zpk([0.5], [0.5], 1.0, worN=N) + w, h = freqz_zpk(xp.asarray([0.5]), xp.asarray([0.5]), 1.0, worN=N) assert w.shape == (N,) - def test_basic(self): - w, h = freqz_zpk([0.5], [0.5], 1.0, worN=8) - assert_array_almost_equal(w, np.pi * np.arange(8.0) / 8) - assert_array_almost_equal(h, np.ones(8)) + def test_basic(self, xp): + w, h = freqz_zpk(xp.asarray([0.5]), xp.asarray([0.5]), 1.0, worN=8) + assert_array_almost_equal(w, xp.pi * xp.arange(8.0) / 8) + assert_array_almost_equal(h, xp.ones(8)) - def test_basic_whole(self): - w, h = freqz_zpk([0.5], [0.5], 1.0, worN=8, whole=True) - assert_array_almost_equal(w, 2 * np.pi * np.arange(8.0) / 8) - assert_array_almost_equal(h, np.ones(8)) + def test_basic_whole(self, xp): + w, h = freqz_zpk(xp.asarray([0.5]), xp.asarray([0.5]), 1.0, worN=8, whole=True) + assert_array_almost_equal(w, 2 * xp.pi * xp.arange(8.0) / 8) + assert_array_almost_equal(h, xp.ones(8)) - def test_vs_freqz(self): + def test_vs_freqz(self, xp): b, a = cheby1(4, 5, 0.5, analog=False, output='ba') z, p, k = cheby1(4, 5, 0.5, analog=False, output='zpk') + b, a = map(xp.asarray, (b, a)) # XXX convert cheby1 + z, p = map(xp.asarray, (z, p)) + w1, h1 = freqz(b, a) w2, h2 = freqz_zpk(z, p, k) xp_assert_close(w1, w2) xp_assert_close(h1, h2, rtol=1e-6) - def test_backward_compat(self): + def test_backward_compat(self, xp): # For backward compatibility, test if None act as a wrapper for default - w1, h1 = freqz_zpk([0.5], [0.5], 1.0) - w2, h2 = freqz_zpk([0.5], [0.5], 1.0, None) + w1, h1 = freqz_zpk(xp.asarray([0.5]), xp.asarray([0.5]), 1.0) + w2, h2 = freqz_zpk(xp.asarray([0.5]), xp.asarray([0.5]), 1.0, None) assert_array_almost_equal(w1, w2) assert_array_almost_equal(h1, h2) - def test_fs_param(self): + def test_fs_param(self, xp): fs = 900 - z = [-1, -1, -1] - p = [0.4747869998473389+0.4752230717749344j, 0.37256600288916636, - 0.4747869998473389-0.4752230717749344j] + z = xp.asarray([-1, -1, -1.0]) + p = xp.asarray( + [0.4747869998473389 + 0.4752230717749344j, + 0.37256600288916636, + 0.4747869998473389 - 0.4752230717749344j] + ) k = 0.03934683014103762 # N = None, whole=False w1, h1 = freqz_zpk(z, p, k, whole=False, fs=fs) w2, h2 = freqz_zpk(z, p, k, whole=False) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs/2, 512, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs/2, 512, endpoint=False)) # N = None, whole=True w1, h1 = freqz_zpk(z, p, k, whole=True, fs=fs) w2, h2 = freqz_zpk(z, p, k, whole=True) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs, 512, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs, 512, endpoint=False)) # N = 5, whole=False w1, h1 = freqz_zpk(z, p, k, 5, fs=fs) w2, h2 = freqz_zpk(z, p, k, 5) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs/2, 5, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs/2, 5, endpoint=False)) # N = 5, whole=True w1, h1 = freqz_zpk(z, p, k, 5, whole=True, fs=fs) w2, h2 = freqz_zpk(z, p, k, 5, whole=True) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs, 5, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs, 5, endpoint=False)) + + @skip_xp_backends(np_only=True, reason="array_likes") + def test_fs_param2(self, xp): + fs = 900 + z = xp.asarray([-1, -1, -1.0]) + p = xp.asarray( + [0.4747869998473389 + 0.4752230717749344j, + 0.37256600288916636, + 0.4747869998473389 - 0.4752230717749344j] + ) + k = 0.03934683014103762 # w is an array_like - for w in ([123], (123,), np.array([123]), (50, 123, 230), - np.array([50, 123, 230])): + for w in ([123], (123,), xp.asarray([123]), (50, 123, 230), + xp.asarray([50, 123, 230])): w1, h1 = freqz_zpk(z, p, k, w, fs=fs) - w2, h2 = freqz_zpk(z, p, k, 2*pi*np.array(w)/fs) + w2, h2 = freqz_zpk(z, p, k, 2*pi*xp.asarray(w)/fs) xp_assert_close(h1, h2) xp_assert_close(w, w1, check_dtype=False) @@ -1289,7 +1462,7 @@ def test_w_or_N_types(self): def test_fs_validation(self): with pytest.raises(ValueError, match="Sampling.*single scalar"): - freqz_zpk([1.0], [1.0], [1.0], fs=np.array([10, 20])) + freqz_zpk([1.0], [1.0], [1.0], fs=np.array([10., 20])) with pytest.raises(ValueError, match="Sampling.*be none."): freqz_zpk([1.0], [1.0], [1.0], fs=None) @@ -1495,19 +1668,6 @@ def test_fs_validation(self): bilinear(b, a, fs=None) -def _sort_cmplx(arr, xp): - # xp.sort is undefined for complex dtypes. Here we only need some - # consistent way to sort a complex array, including equal magnitude elements. - arr = xp.asarray(arr) - if xp.isdtype(arr.dtype, 'complex floating'): - sorter = abs(arr) + xp.real(arr) + xp.imag(arr)**3 - else: - sorter = arr - - idxs = xp.argsort(sorter) - return arr[idxs] - - class TestLp2lp_zpk: @xfail_xp_backends( diff --git a/scipy/signal/tests/test_fir_filter_design.py b/scipy/signal/tests/test_fir_filter_design.py index 4701e20c9475..2c713f9d8c9a 100644 --- a/scipy/signal/tests/test_fir_filter_design.py +++ b/scipy/signal/tests/test_fir_filter_design.py @@ -1,18 +1,23 @@ +import math import numpy as np + from numpy.testing import assert_warns -from scipy._lib._array_api import ( - xp_assert_close, xp_assert_equal, - assert_almost_equal, assert_array_almost_equal, -) from pytest import raises as assert_raises import pytest +import scipy._lib.array_api_extra as xpx +from scipy._lib._array_api import ( + xp_assert_close, xp_assert_equal, assert_almost_equal, assert_array_almost_equal, + array_namespace, xp_default_dtype +) from scipy.fft import fft, fft2 -from scipy.special import sinc -from scipy.signal import kaiser_beta, kaiser_atten, kaiserord, \ - firwin, firwin2, freqz, remez, firls, minimum_phase, \ - convolve2d -from scipy.signal._fir_filter_design import firwin_2d +from scipy.signal import (kaiser_beta, kaiser_atten, kaiserord, + firwin, firwin2, freqz, remez, firls, minimum_phase, convolve2d, firwin_2d +) + +skip_xp_backends = pytest.mark.skip_xp_backends +xfail_xp_backends = pytest.mark.xfail_xp_backends + def test_kaiser_beta(): b = kaiser_beta(58.7) @@ -41,17 +46,19 @@ def test_kaiserord(): class TestFirwin: def check_response(self, h, expected_response, tol=.05): - N = len(h) + xp = array_namespace(h) + N = h.shape[0] alpha = 0.5 * (N-1) - m = np.arange(0,N) - alpha # time indices of taps + m = xp.arange(0, N) - alpha # time indices of taps for freq, expected in expected_response: - actual = abs(np.sum(h*np.exp(-1.j*np.pi*m*freq))) - mse = abs(actual-expected)**2 + actual = abs(xp.sum(h * xp.exp(-1j * xp.pi * m * freq))) + mse = abs(actual - expected)**2 assert mse < tol, f'response not as expected, mse={mse:g} > {tol:g}' - def test_response(self): + def test_response(self, xp): N = 51 f = .5 + # increase length just to try even/odd h = firwin(N, f) # low-pass from 0 to f self.check_response(h, [(.25,1), (.75,0)]) @@ -97,7 +104,7 @@ def mse(self, h, bands): mse = np.mean(abs(abs(H)-Hideal)**2) return mse - def test_scaling(self): + def test_scaling(self, xp): """ For one lowpass, bandpass, and highpass example filter, this test checks two things: @@ -133,110 +140,130 @@ def test_fs_validation(self): class TestFirWinMore: """Different author, different style, different tests...""" - def test_lowpass(self): + def test_lowpass(self, xp): width = 0.04 ntaps, beta = kaiserord(120, width) - kwargs = dict(cutoff=0.5, window=('kaiser', beta), scale=False) + cutoff = xp.asarray(0.5) + kwargs = dict(cutoff=cutoff, window=('kaiser', beta), scale=False) taps = firwin(ntaps, **kwargs) # Check the symmetry of taps. - assert_array_almost_equal(taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2-1:-1]) + assert_array_almost_equal(taps[:ntaps//2], xp.flip(taps)[:ntaps//2]) # Check the gain at a few samples where # we know it should be approximately 0 or 1. - freq_samples = np.array([0.0, 0.25, 0.5-width/2, 0.5+width/2, 0.75, 1.0]) - freqs, response = freqz(taps, worN=np.pi*freq_samples) - assert_array_almost_equal(np.abs(response), - [1.0, 1.0, 1.0, 0.0, 0.0, 0.0], decimal=5) + freq_samples = xp.asarray([0.0, 0.25, 0.5-width/2, 0.5+width/2, 0.75, 1.0]) + freqs, response = freqz(taps, worN=xp.pi*freq_samples) + + assert_array_almost_equal( + xp.abs(response), + xp.asarray([1.0, 1.0, 1.0, 0.0, 0.0, 0.0]), decimal=5 + ) taps_str = firwin(ntaps, pass_zero='lowpass', **kwargs) xp_assert_close(taps, taps_str) - def test_highpass(self): + def test_highpass(self, xp): width = 0.04 ntaps, beta = kaiserord(120, width) # Ensure that ntaps is odd. ntaps |= 1 - kwargs = dict(cutoff=0.5, window=('kaiser', beta), scale=False) + cutoff = xp.asarray(0.5) + kwargs = dict(cutoff=cutoff, window=('kaiser', beta), scale=False) taps = firwin(ntaps, pass_zero=False, **kwargs) # Check the symmetry of taps. - assert_array_almost_equal(taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2-1:-1]) + assert_array_almost_equal(taps[:ntaps//2], xp.flip(taps)[:ntaps//2]) # Check the gain at a few samples where # we know it should be approximately 0 or 1. - freq_samples = np.array([0.0, 0.25, 0.5-width/2, 0.5+width/2, 0.75, 1.0]) + freq_samples = xp.asarray([0.0, 0.25, 0.5 - width/2, 0.5 + width/2, 0.75, 1.0]) freqs, response = freqz(taps, worN=np.pi*freq_samples) - assert_array_almost_equal(np.abs(response), - [0.0, 0.0, 0.0, 1.0, 1.0, 1.0], decimal=5) + + assert_array_almost_equal(xp.abs(response), + xp.asarray([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), decimal=5) taps_str = firwin(ntaps, pass_zero='highpass', **kwargs) xp_assert_close(taps, taps_str) - def test_bandpass(self): + def test_bandpass(self, xp): width = 0.04 ntaps, beta = kaiserord(120, width) - kwargs = dict(cutoff=[0.3, 0.7], window=('kaiser', beta), scale=False) + kwargs = dict( + cutoff=xp.asarray([0.3, 0.7]), window=('kaiser', beta), scale=False + ) taps = firwin(ntaps, pass_zero=False, **kwargs) # Check the symmetry of taps. - assert_array_almost_equal(taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2-1:-1]) + assert_array_almost_equal(taps[:ntaps//2], xp.flip(taps)[:ntaps//2]) # Check the gain at a few samples where # we know it should be approximately 0 or 1. - freq_samples = np.array([0.0, 0.2, 0.3-width/2, 0.3+width/2, 0.5, - 0.7-width/2, 0.7+width/2, 0.8, 1.0]) + freq_samples = xp.asarray([0.0, 0.2, 0.3 - width/2, 0.3 + width/2, 0.5, + 0.7 - width/2, 0.7 + width/2, 0.8, 1.0]) freqs, response = freqz(taps, worN=np.pi*freq_samples) - assert_array_almost_equal(np.abs(response), - [0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0], decimal=5) + + assert_array_almost_equal(xp.abs(response), + xp.asarray([0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]), decimal=5) taps_str = firwin(ntaps, pass_zero='bandpass', **kwargs) xp_assert_close(taps, taps_str) - def test_bandstop_multi(self): + def test_bandstop_multi(self, xp): width = 0.04 ntaps, beta = kaiserord(120, width) - kwargs = dict(cutoff=[0.2, 0.5, 0.8], window=('kaiser', beta), + kwargs = dict(cutoff=xp.asarray([0.2, 0.5, 0.8]), window=('kaiser', beta), scale=False) taps = firwin(ntaps, **kwargs) # Check the symmetry of taps. - assert_array_almost_equal(taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2-1:-1]) + assert_array_almost_equal(taps[:ntaps//2], xp.flip(taps)[:ntaps//2]) # Check the gain at a few samples where # we know it should be approximately 0 or 1. - freq_samples = np.array([0.0, 0.1, 0.2-width/2, 0.2+width/2, 0.35, - 0.5-width/2, 0.5+width/2, 0.65, - 0.8-width/2, 0.8+width/2, 0.9, 1.0]) + freq_samples = xp.asarray([0.0, 0.1, 0.2 - width/2, 0.2 + width/2, 0.35, + 0.5 - width/2, 0.5 + width/2, 0.65, + 0.8 - width/2, 0.8 + width/2, 0.9, 1.0]) freqs, response = freqz(taps, worN=np.pi*freq_samples) - assert_array_almost_equal(np.abs(response), - [1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0], - decimal=5) + + assert_array_almost_equal( + xp.abs(response), + xp.asarray([1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]), + decimal=5 + ) taps_str = firwin(ntaps, pass_zero='bandstop', **kwargs) xp_assert_close(taps, taps_str) - def test_fs_nyq(self): + def test_fs_nyq(self, xp): """Test the fs and nyq keywords.""" nyquist = 1000 width = 40.0 relative_width = width/nyquist ntaps, beta = kaiserord(120, relative_width) - taps = firwin(ntaps, cutoff=[300, 700], window=('kaiser', beta), + taps = firwin(ntaps, cutoff=xp.asarray([300, 700]), window=('kaiser', beta), pass_zero=False, scale=False, fs=2*nyquist) # Check the symmetry of taps. - assert_array_almost_equal(taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2-1:-1]) + assert_array_almost_equal(taps[:ntaps//2], xp.flip(taps)[:ntaps//2]) # Check the gain at a few samples where # we know it should be approximately 0 or 1. - freq_samples = np.array([0.0, 200, 300-width/2, 300+width/2, 500, - 700-width/2, 700+width/2, 800, 1000]) + freq_samples = xp.asarray([0.0, 200, 300 - width/2, 300 + width/2, 500, + 700 - width/2, 700 + width/2, 800, 1000]) freqs, response = freqz(taps, worN=np.pi*freq_samples/nyquist) - assert_array_almost_equal(np.abs(response), - [0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0], decimal=5) + + assert_array_almost_equal(xp.abs(response), + xp.asarray([0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]), decimal=5) + + def test_array_cutoff(self, xp): + taps = firwin(3, xp.asarray([.1, .2])) + # smoke test against the value computed by scipy==1.5.2 + xp_assert_close( + taps, xp.asarray([-0.00801395, 1.0160279, -0.00801395]), atol=1e-8 + ) def test_bad_cutoff(self): """Test that invalid cutoff argument raises ValueError.""" @@ -265,9 +292,9 @@ def test_even_highpass_raises_value_error(self): def test_bad_pass_zero(self): """Test degenerate pass_zero cases.""" - with assert_raises(ValueError, match='pass_zero must be'): + with assert_raises(ValueError, match="^Parameter pass_zero='foo' not in "): firwin(41, 0.5, pass_zero='foo') - with assert_raises(TypeError, match='cannot be interpreted'): + with assert_raises(ValueError, match="^Parameter pass_zero=1.0 not in "): firwin(41, 0.5, pass_zero=1.) for pass_zero in ('lowpass', 'highpass'): with assert_raises(ValueError, match='cutoff must have one'): @@ -281,6 +308,7 @@ def test_fs_validation(self): firwin2(51, .5, 1, fs=np.array([10, 20])) +@skip_xp_backends(cpu_only=True, reason="firwin2 uses np.interp") class TestFirwin2: def test_invalid_args(self): @@ -330,90 +358,117 @@ def test_invalid_args(self): with assert_raises(ValueError, match='Type IV filter'): firwin2(16, [0.0, 0.5, 1.0], [1.0, 1.0, 0.0], antisymmetric=True) - def test01(self): + def test01(self, xp): width = 0.04 beta = 12.0 ntaps = 400 # Filter is 1 from w=0 to w=0.5, then decreases linearly from 1 to 0 as w # increases from w=0.5 to w=1 (w=1 is the Nyquist frequency). - freq = [0.0, 0.5, 1.0] - gain = [1.0, 1.0, 0.0] + freq = xp.asarray([0.0, 0.5, 1.0]) + gain = xp.asarray([1.0, 1.0, 0.0]) taps = firwin2(ntaps, freq, gain, window=('kaiser', beta)) - freq_samples = np.array([0.0, 0.25, 0.5-width/2, 0.5+width/2, - 0.75, 1.0-width/2]) + freq_samples = xp.asarray([0.0, 0.25, 0.5 - width/2, 0.5 + width/2, + 0.75, 1.0 - width/2]) freqs, response = freqz(taps, worN=np.pi*freq_samples) - assert_array_almost_equal(np.abs(response), - [1.0, 1.0, 1.0, 1.0-width, 0.5, width], decimal=5) + freqs, response = xp.asarray(freqs), xp.asarray(response) + assert_array_almost_equal( + xp.abs(response), + xp.asarray([1.0, 1.0, 1.0, 1.0 - width, 0.5, width]), decimal=5 + ) - def test02(self): + @skip_xp_backends("jax.numpy", reason="immutable arrays") + def test02(self, xp): width = 0.04 beta = 12.0 # ntaps must be odd for positive gain at Nyquist. ntaps = 401 # An ideal highpass filter. - freq = [0.0, 0.5, 0.5, 1.0] - gain = [0.0, 0.0, 1.0, 1.0] + freq = xp.asarray([0.0, 0.5, 0.5, 1.0]) + gain = xp.asarray([0.0, 0.0, 1.0, 1.0]) taps = firwin2(ntaps, freq, gain, window=('kaiser', beta)) - freq_samples = np.array([0.0, 0.25, 0.5-width, 0.5+width, 0.75, 1.0]) + freq_samples = np.array([0.0, 0.25, 0.5 - width, 0.5 + width, 0.75, 1.0]) freqs, response = freqz(taps, worN=np.pi*freq_samples) - assert_array_almost_equal(np.abs(response), - [0.0, 0.0, 0.0, 1.0, 1.0, 1.0], decimal=5) + freqs, response = xp.asarray(freqs), xp.asarray(response) + assert_array_almost_equal( + xp.abs(response), + xp.asarray([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), decimal=5 + ) - def test03(self): + @skip_xp_backends("jax.numpy", reason="immutable arrays") + def test03(self, xp): width = 0.02 ntaps, beta = kaiserord(120, width) # ntaps must be odd for positive gain at Nyquist. ntaps = int(ntaps) | 1 - freq = [0.0, 0.4, 0.4, 0.5, 0.5, 1.0] - gain = [1.0, 1.0, 0.0, 0.0, 1.0, 1.0] + freq = xp.asarray([0.0, 0.4, 0.4, 0.5, 0.5, 1.0]) + gain = xp.asarray([1.0, 1.0, 0.0, 0.0, 1.0, 1.0]) taps = firwin2(ntaps, freq, gain, window=('kaiser', beta)) - freq_samples = np.array([0.0, 0.4-width, 0.4+width, 0.45, - 0.5-width, 0.5+width, 0.75, 1.0]) + freq_samples = np.array([0.0, 0.4 - width, 0.4 + width, 0.45, + 0.5 - width, 0.5 + width, 0.75, 1.0]) freqs, response = freqz(taps, worN=np.pi*freq_samples) - assert_array_almost_equal(np.abs(response), - [1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0], decimal=5) + freqs, response = xp.asarray(freqs), xp.asarray(response) + assert_array_almost_equal( + xp.abs(response), + xp.asarray([1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), decimal=5 + ) - def test04(self): + @skip_xp_backends("jax.numpy", reason="immutable arrays") + def test04(self, xp): """Test firwin2 when window=None.""" ntaps = 5 # Ideal lowpass: gain is 1 on [0,0.5], and 0 on [0.5, 1.0] - freq = [0.0, 0.5, 0.5, 1.0] - gain = [1.0, 1.0, 0.0, 0.0] + freq = xp.asarray([0.0, 0.5, 0.5, 1.0]) + gain = xp.asarray([1.0, 1.0, 0.0, 0.0]) + taps = firwin2(ntaps, freq, gain, window=None, nfreqs=8193) alpha = 0.5 * (ntaps - 1) - m = np.arange(0, ntaps) - alpha - h = 0.5 * sinc(0.5 * m) + m = xp.arange(0, ntaps, dtype=freq.dtype) - alpha + h = 0.5 * xpx.sinc(0.5 * m) assert_array_almost_equal(h, taps) - def test05(self): + def test05(self, xp): """Test firwin2 for calculating Type IV filters""" ntaps = 1500 - freq = [0.0, 1.0] - gain = [0.0, 1.0] + freq = xp.asarray([0.0, 1.0]) + gain = xp.asarray([0.0, 1.0]) taps = firwin2(ntaps, freq, gain, window=None, antisymmetric=True) - assert_array_almost_equal(taps[: ntaps // 2], -taps[ntaps // 2:][::-1]) - freqs, response = freqz(taps, worN=2048) - assert_array_almost_equal(abs(response), freqs / np.pi, decimal=4) + flip = array_namespace(freq).flip + dec = {'decimal': 4.5} if xp_default_dtype(xp) == xp.float32 else {} + assert_array_almost_equal(taps[: ntaps // 2], flip(-taps[ntaps // 2:]), **dec) + + freqs, response = freqz(np.asarray(taps), worN=2048) # XXX convert freqz + assert_array_almost_equal(abs(xp.asarray(response)), + xp.asarray(freqs / np.pi), decimal=4) - def test06(self): + @skip_xp_backends("jax.numpy", reason="immutable arrays") + def test06(self, xp): """Test firwin2 for calculating Type III filters""" ntaps = 1501 - freq = [0.0, 0.5, 0.55, 1.0] - gain = [0.0, 0.5, 0.0, 0.0] + freq = xp.asarray([0.0, 0.5, 0.55, 1.0]) + gain = xp.asarray([0.0, 0.5, 0.0, 0.0]) taps = firwin2(ntaps, freq, gain, window=None, antisymmetric=True) assert taps[ntaps // 2] == 0.0 - assert_array_almost_equal(taps[: ntaps // 2], -taps[ntaps // 2 + 1:][::-1]) - freqs, response1 = freqz(taps, worN=2048) - response2 = np.interp(freqs / np.pi, freq, gain) + flip = array_namespace(freq).flip + dec = {'decimal': 4.5} if xp_default_dtype(xp) == xp.float32 else {} + assert_array_almost_equal(taps[: ntaps // 2], + flip(-taps[ntaps // 2 + 1:]), **dec + ) + + freqs, response1 = freqz(np.asarray(taps), worN=2048) # XXX convert freqz + response1 = xp.asarray(response1) + response2 = xp.asarray( + np.interp(np.asarray(freqs) / np.pi, np.asarray(freq), np.asarray(gain)) + ) assert_array_almost_equal(abs(response1), response2, decimal=3) - def test_fs_nyq(self): - taps1 = firwin2(80, [0.0, 0.5, 1.0], [1.0, 1.0, 0.0]) - taps2 = firwin2(80, [0.0, 30.0, 60.0], [1.0, 1.0, 0.0], fs=120.0) + def test_fs_nyq(self, xp): + taps1 = firwin2(80, xp.asarray([0.0, 0.5, 1.0]), xp.asarray([1.0, 1.0, 0.0])) + taps2 = firwin2(80, xp.asarray([0.0, 30.0, 60.0]), xp.asarray([1.0, 1.0, 0.0]), + fs=120.0) assert_array_almost_equal(taps1, taps2) def test_tuple(self): @@ -421,13 +476,15 @@ def test_tuple(self): taps2 = firwin2(150, [0.0, 0.5, 0.5, 1.0], [1.0, 1.0, 0.0, 0.0]) assert_array_almost_equal(taps1, taps2) - def test_input_modyfication(self): - freq1 = np.array([0.0, 0.5, 0.5, 1.0]) - freq2 = np.array(freq1) - firwin2(80, freq1, [1.0, 1.0, 0.0, 0.0]) + @skip_xp_backends("jax.numpy", reason="immutable arrays") + def test_input_modyfication(self, xp): + freq1 = xp.asarray([0.0, 0.5, 0.5, 1.0]) + freq2 = xp.asarray(freq1) + firwin2(80, freq1, xp.asarray([1.0, 1.0, 0.0, 0.0])) xp_assert_equal(freq1, freq2) +@skip_xp_backends(cpu_only=True) class TestRemez: def test_bad_args(self): @@ -462,14 +519,15 @@ def test_hilbert(self): idx = np.logical_and(f > a, f < 0.5-a) assert (abs(Hmag[idx] - 1) < 0.015).all(), "Pass Band Close To Unity" - def test_compare(self): + def test_compare(self, xp): # test comparison to MATLAB k = [0.024590270518440, -0.041314581814658, -0.075943803756711, -0.003530911231040, 0.193140296954975, 0.373400753484939, 0.373400753484939, 0.193140296954975, -0.003530911231040, -0.075943803756711, -0.041314581814658, 0.024590270518440] - h = remez(12, [0, 0.3, 0.5, 1], [1, 0], fs=2.) - xp_assert_close(h, k) + h = remez(12, xp.asarray([0, 0.3, 0.5, 1]), xp.asarray([1, 0]), fs=2.) + atol_arg = {'atol': 1e-8} if xp_default_dtype(xp) == xp.float32 else {} + xp_assert_close(h, xp.asarray(k, dtype=xp.float64), **atol_arg) h = [-0.038976016082299, 0.018704846485491, -0.014644062687875, 0.002879152556419, 0.016849978528150, -0.043276706138248, @@ -478,12 +536,19 @@ def test_compare(self): 0.129770906801075, -0.103908158578635, 0.073641298245579, -0.043276706138248, 0.016849978528150, 0.002879152556419, -0.014644062687875, 0.018704846485491, -0.038976016082299] - xp_assert_close(remez(21, [0, 0.8, 0.9, 1], [0, 1], fs=2.), h) + atol_arg = {'atol': 3e-8} if xp_default_dtype(xp) == xp.float32 else {} + xp_assert_close( + remez(21, xp.asarray([0, 0.8, 0.9, 1]), xp.asarray([0, 1]), fs=2.), + xp.asarray(h, dtype=xp.float64), **atol_arg + ) def test_fs_validation(self): with pytest.raises(ValueError, match="Sampling.*single scalar"): remez(11, .1, 1, fs=np.array([10, 20])) + + +@skip_xp_backends(cpu_only=True, reason="lstsq") class TestFirls: def test_bad_args(self): @@ -505,105 +570,121 @@ def test_bad_args(self): # negative weight assert_raises(ValueError, firls, 11, [0.1, 0.2], [0, 0], weight=[-1]) - def test_firls(self): + @skip_xp_backends("dask.array", reason="dask fancy indexing shape=(nan,)") + def test_firls(self, xp): N = 11 # number of taps in the filter a = 0.1 # width of the transition band # design a halfband symmetric low-pass filter - h = firls(11, [0, a, 0.5-a, 0.5], [1, 1, 0, 0], fs=1.0) + h = firls(11, xp.asarray([0, a, 0.5 - a, 0.5]), xp.asarray([1, 1, 0, 0]), + fs=1.0) # make sure the filter has correct # of taps assert h.shape[0] == N # make sure it is symmetric midx = (N-1) // 2 - assert_array_almost_equal(h[:midx], h[:-midx-1:-1]) + flip = array_namespace(h).flip + assert_array_almost_equal(h[:midx], flip(h[midx+1:])) # h[:-midx-1:-1]) # make sure the center tap is 0.5 - assert_almost_equal(h[midx], 0.5) + assert math.isclose(h[midx], 0.5, abs_tol=1e-8) # For halfband symmetric, odd coefficients (except the center) # should be zero (really small) - hodd = np.hstack((h[1:midx:2], h[-midx+1::2])) - assert_array_almost_equal(hodd, np.zeros_like(hodd)) + hodd = xp.stack((h[1:midx:2], h[-midx+1::2])) + assert_array_almost_equal(hodd, xp.zeros_like(hodd)) # now check the frequency response - w, H = freqz(h, 1) - f = w/2/np.pi - Hmag = np.abs(H) + w, H = freqz(np.asarray(h), 1) + w, H = xp.asarray(w), xp.asarray(H) + f = w/2/xp.pi + Hmag = xp.abs(H) # check that the pass band is close to unity - idx = np.logical_and(f > 0, f < a) - assert_array_almost_equal(Hmag[idx], np.ones_like(Hmag[idx]), decimal=3) + idx = xp.logical_and(f > 0, f < a) + assert_array_almost_equal(Hmag[idx], xp.ones_like(Hmag[idx]), decimal=3) # check that the stop band is close to zero - idx = np.logical_and(f > 0.5-a, f < 0.5) - assert_array_almost_equal(Hmag[idx], np.zeros_like(Hmag[idx]), decimal=3) + idx = xp.logical_and(f > 0.5 - a, f < 0.5) + assert_array_almost_equal(Hmag[idx], xp.zeros_like(Hmag[idx]), decimal=3) - def test_compare(self): + def test_compare(self, xp): # compare to OCTAVE output - taps = firls(9, [0, 0.5, 0.55, 1], [1, 1, 0, 0], weight=[1, 2]) + taps = firls(9, xp.asarray([0, 0.5, 0.55, 1]), + xp.asarray([1, 1, 0, 0]), weight=xp.asarray([1, 2])) # >> taps = firls(8, [0 0.5 0.55 1], [1 1 0 0], [1, 2]); known_taps = [-6.26930101730182e-04, -1.03354450635036e-01, -9.81576747564301e-03, 3.17271686090449e-01, 5.11409425599933e-01, 3.17271686090449e-01, -9.81576747564301e-03, -1.03354450635036e-01, -6.26930101730182e-04] - xp_assert_close(taps, known_taps) + atol_arg = {'atol': 5e-8} if xp_default_dtype(xp) == xp.float32 else {} + known_taps = xp.asarray(known_taps, dtype=xp.float64) + xp_assert_close(taps, known_taps, **atol_arg) # compare to MATLAB output - taps = firls(11, [0, 0.5, 0.5, 1], [1, 1, 0, 0], weight=[1, 2]) + taps = firls(11, xp.asarray([0, 0.5, 0.5, 1]), + xp.asarray([1, 1, 0, 0]), weight=xp.asarray([1, 2])) # >> taps = firls(10, [0 0.5 0.5 1], [1 1 0 0], [1, 2]); known_taps = [ 0.058545300496815, -0.014233383714318, -0.104688258464392, 0.012403323025279, 0.317930861136062, 0.488047220029700, 0.317930861136062, 0.012403323025279, -0.104688258464392, -0.014233383714318, 0.058545300496815] - xp_assert_close(taps, known_taps) + known_taps = xp.asarray(known_taps, dtype=xp.float64) + atol_arg = {'atol': 3e-8} if xp_default_dtype(xp) == xp.float32 else {} + xp_assert_close(taps, known_taps, **atol_arg) # With linear changes: - taps = firls(7, (0, 1, 2, 3, 4, 5), [1, 0, 0, 1, 1, 0], fs=20) + taps = firls(7, xp.asarray((0, 1, 2, 3, 4, 5)), + xp.asarray([1, 0, 0, 1, 1, 0]), fs=20) # >> taps = firls(6, [0, 0.1, 0.2, 0.3, 0.4, 0.5], [1, 0, 0, 1, 1, 0]) known_taps = [ 1.156090832768218, -4.1385894727395849, 7.5288619164321826, -8.5530572592947856, 7.5288619164321826, -4.1385894727395849, 1.156090832768218] + known_taps = xp.asarray(known_taps, dtype=xp.float64) xp_assert_close(taps, known_taps) - def test_rank_deficient(self): + def test_rank_deficient(self, xp): # solve() runs but warns (only sometimes, so here we don't use match) - x = firls(21, [0, 0.1, 0.9, 1], [1, 1, 0, 0]) - w, h = freqz(x, fs=2.) - absh2 = np.abs(h[:2]) - xp_assert_close(absh2, np.ones_like(absh2), atol=1e-5) - absh2 = np.abs(h[-2:]) - xp_assert_close(absh2, np.zeros_like(absh2), atol=1e-6, rtol=1e-7) + x = firls(21, xp.asarray([0, 0.1, 0.9, 1]), xp.asarray([1, 1, 0, 0])) + w, h = freqz(np.asarray(x), fs=2.) + w, h = map(xp.asarray, (w, h)) # XXX convert freqz + absh2 = xp.abs(h[:2]) + xp_assert_close(absh2, xp.ones_like(absh2), atol=1e-5) + absh2 = xp.abs(h[-2:]) + xp_assert_close(absh2, xp.zeros_like(absh2), atol=1e-6, rtol=1e-7) # switch to pinvh (tolerances could be higher with longer # filters, but using shorter ones is faster computationally and # the idea is the same) - x = firls(101, [0, 0.01, 0.99, 1], [1, 1, 0, 0]) - w, h = freqz(x, fs=2.) - mask = w < 0.01 - assert mask.sum() > 3 - habs = np.abs(h[mask]) - xp_assert_close(habs, np.ones_like(habs), atol=1e-4) - mask = w > 0.99 - assert mask.sum() > 3 - habs = np.abs(h[mask]) - xp_assert_close(habs, np.zeros_like(habs), atol=1e-4) + x = firls(101, xp.asarray([0, 0.01, 0.99, 1]), xp.asarray([1, 1, 0, 0])) + w, h = freqz(np.asarray(x), fs=2.) + w, h = map(xp.asarray, (w, h)) # XXX convert freqz + mask = xp.asarray(w < 0.01) + h = xp.asarray(h) + assert xp.sum(xp.astype(mask, xp.int64)) > 3 + habs = xp.abs(h[mask]) + xp_assert_close(habs, xp.ones_like(habs), atol=1e-4) + mask = xp.asarray(w > 0.99) + assert xp.sum(xp.astype(mask, xp.int64)) > 3 + habs = xp.abs(h[mask]) + xp_assert_close(habs, xp.zeros_like(habs), atol=1e-4) def test_fs_validation(self): with pytest.raises(ValueError, match="Sampling.*single scalar"): firls(11, .1, 1, fs=np.array([10, 20])) class TestMinimumPhase: + @pytest.mark.thread_unsafe def test_bad_args(self): # not enough taps assert_raises(ValueError, minimum_phase, [1.]) assert_raises(ValueError, minimum_phase, [1., 1.]) assert_raises(ValueError, minimum_phase, np.full(10, 1j)) - assert_raises(ValueError, minimum_phase, 'foo') + assert_raises((ValueError, TypeError), minimum_phase, 'foo') assert_raises(ValueError, minimum_phase, np.ones(10), n_fft=8) assert_raises(ValueError, minimum_phase, np.ones(10), method='foo') assert_warns(RuntimeWarning, minimum_phase, np.arange(3)) @@ -630,7 +711,9 @@ def test_homomorphic(self): assert len(h_linear) == len(h_new) xp_assert_close(np.abs(fft(h_new)), np.abs(fft(h_linear)), rtol=1e-4) - def test_hilbert(self): + @skip_xp_backends("dask.array", reason="too slow") + @skip_xp_backends("jax.numpy", reason="immutable arrays") + def test_hilbert(self, xp): # compare to MATLAB output of reference implementation # f=[0 0.3 0.5 1]; @@ -639,6 +722,8 @@ def test_hilbert(self): h = remez(12, [0, 0.3, 0.5, 1], [1, 0], fs=2.) k = [0.349585548646686, 0.373552164395447, 0.326082685363438, 0.077152207480935, -0.129943946349364, -0.059355880509749] + h = xp.asarray(h) + k = xp.asarray(k, dtype=xp.float64) m = minimum_phase(h, 'hilbert') xp_assert_close(m, k, rtol=5e-3) @@ -650,21 +735,23 @@ def test_hilbert(self): -0.157957283165866, 0.151739294892963, -0.129293146705090, 0.100787844523204, -0.065832656741252, 0.035361328741024, -0.014977068692269, -0.158416139047557] + h = xp.asarray(h) + k = xp.asarray(k, dtype=xp.float64) m = minimum_phase(h, 'hilbert', n_fft=2**19) xp_assert_close(m, k, rtol=2e-3) class Testfirwin_2d: def test_invalid_args(self): - with pytest.raises(ValueError, + with pytest.raises(ValueError, match="hsize must be a 2-element tuple or list"): firwin_2d((50,), window=(("kaiser", 5.0), "boxcar"), fc=0.4) - - with pytest.raises(ValueError, + + with pytest.raises(ValueError, match="window must be a 2-element tuple or list"): firwin_2d((51, 51), window=("hamming",), fc=0.5) - - with pytest.raises(ValueError, + + with pytest.raises(ValueError, match="window must be a 2-element tuple or list"): firwin_2d((51, 51), window="invalid_window", fc=0.5) diff --git a/scipy/signal/tests/test_signaltools.py b/scipy/signal/tests/test_signaltools.py index b63a6211fcf5..10c5c245fc94 100644 --- a/scipy/signal/tests/test_signaltools.py +++ b/scipy/signal/tests/test_signaltools.py @@ -34,7 +34,7 @@ from scipy._lib._array_api import ( xp_assert_close, xp_assert_equal, is_numpy, is_torch, is_jax, is_cupy, assert_array_almost_equal, assert_almost_equal, - xp_copy, xp_size, xp_default_dtype + xp_copy, xp_size, xp_default_dtype, array_namespace ) skip_xp_backends = pytest.mark.skip_xp_backends xfail_xp_backends = pytest.mark.xfail_xp_backends @@ -1381,51 +1381,77 @@ def test_basic(self, xp): padtype_options += _upfirdn_modes -@skip_xp_backends(np_only=True) +@skip_xp_backends("dask.array", reason="XXX something in dask") class TestResample: + + @skip_xp_backends("jax.numpy", reason="immutable arrays") + @skip_xp_backends( + cpu_only=True, reason="resample_poly/upfirdn is CPU only" + ) def test_basic(self, xp): # Some basic tests # Regression test for issue #3603. # window.shape must equal to sig.shape[0] - sig = np.arange(128) + sig = xp.arange(128, dtype=xp.float64) num = 256 win = signal.get_window(('kaiser', 8.0), 160) assert_raises(ValueError, signal.resample, sig, num, window=win) + assert_raises(ValueError, signal.resample, sig, num, domain='INVALID') # Other degenerate conditions assert_raises(ValueError, signal.resample_poly, sig, 'yo', 1) assert_raises(ValueError, signal.resample_poly, sig, 1, 0) + assert_raises(ValueError, signal.resample_poly, sig, 1.3, 2) + assert_raises(ValueError, signal.resample_poly, sig, 2, 1.3) assert_raises(ValueError, signal.resample_poly, sig, 2, 1, padtype='') assert_raises(ValueError, signal.resample_poly, sig, 2, 1, padtype='mean', cval=10) + assert_raises(ValueError, signal.resample_poly, sig, 2, 1, window=np.eye(2)) # test for issue #6505 - should not modify window.shape when axis ≠ 0 - sig2 = np.tile(np.arange(160), (2, 1)) + sig2 = xp.tile(xp.arange(160, dtype=xp.float64), (2, 1)) signal.resample(sig2, num, axis=-1, window=win) assert win.shape == (160,) + # Ensure coverage for parameter cval=None and cval != None: + x_ref = signal.resample_poly(sig, 2, 1) + x0 = signal.resample_poly(sig, 2, 1, padtype='constant') + x1 = signal.resample_poly(sig, 2, 1, padtype='constant', cval=0) + xp_assert_equal(x1, x_ref) + xp_assert_equal(x0, x_ref) + @pytest.mark.parametrize('window', (None, 'hamming')) @pytest.mark.parametrize('N', (20, 19)) @pytest.mark.parametrize('num', (100, 101, 10, 11)) + @skip_xp_backends('jax.numpy', reason='immutable arrays') def test_rfft(self, N, num, window, xp): # Make sure the speed up using rfft gives the same result as the normal # way using fft - x = np.linspace(0, 10, N, endpoint=False) - y = np.cos(-x**2/6.0) + dt_r = xp_default_dtype(xp) + dt_c = xp.complex64 if dt_r == xp.float32 else xp.complex128 + + x = xp.linspace(0, 10, N, endpoint=False) + y = xp.cos(-x**2/6.0) + desired = signal.resample(xp.astype(y, dt_c), num, window=window) xp_assert_close(signal.resample(y, num, window=window), - signal.resample(y + 0j, num, window=window).real) + xp.real(desired)) + + y = xp.stack([xp.cos(-x**2/6.0), xp.sin(-x**2/6.0)]) + y_complex = xp.astype(y, dt_c) + resampled = signal.resample(y_complex, num, axis=1, window=window) + + atol = 1e-9 if dt_r == xp.float64 else 3e-7 - y = np.array([np.cos(-x**2/6.0), np.sin(-x**2/6.0)]) - y_complex = y + 0j xp_assert_close( signal.resample(y, num, axis=1, window=window), - signal.resample(y_complex, num, axis=1, window=window).real, - atol=1e-9) + xp.real(resampled), + atol=atol) + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") def test_input_domain(self, xp): # Test if both input domain modes produce the same results. - tsig = np.arange(256) + 0j + tsig = xp.astype(xp.arange(256), xp.complex128) fsig = sp_fft.fft(tsig) num = 256 xp_assert_close( @@ -1433,39 +1459,54 @@ def test_input_domain(self, xp): signal.resample(tsig, num, domain='time'), atol=1e-9) + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") @pytest.mark.parametrize('nx', (1, 2, 3, 5, 8)) @pytest.mark.parametrize('ny', (1, 2, 3, 5, 8)) - @pytest.mark.parametrize('dtype', ('float', 'complex')) + @pytest.mark.parametrize('dtype', ('float64', 'complex128')) def test_dc(self, nx, ny, dtype, xp): - x = np.array([1] * nx, dtype) + dtype = getattr(xp, dtype) + x = xp.asarray([1] * nx, dtype=dtype) y = signal.resample(x, ny) - xp_assert_close(y, np.asarray([1] * ny, dtype=y.dtype)) + xp_assert_close(y, xp.asarray([1] * ny, dtype=y.dtype)) + @skip_xp_backends(cpu_only=True, reason="resample_poly/upfirdn is CPU only") @pytest.mark.parametrize('padtype', padtype_options) def test_mutable_window(self, padtype, xp): # Test that a mutable window is not modified - impulse = np.zeros(3) - window = np.random.RandomState(0).randn(2) - window_orig = window.copy() + impulse = xp.zeros(3) + window = xp.asarray(np.random.RandomState(0).randn(2)) + window_orig = xp.asarray(window, copy=True) signal.resample_poly(impulse, 5, 1, window=window, padtype=padtype) xp_assert_equal(window, window_orig) + @skip_xp_backends( + cpu_only=True, reason="resample_poly/upfirdn is CPU only" + ) @pytest.mark.parametrize('padtype', padtype_options) def test_output_float32(self, padtype, xp): # Test that float32 inputs yield a float32 output - x = np.arange(10, dtype=np.float32) - h = np.array([1, 1, 1], dtype=np.float32) + x = xp.arange(10, dtype=xp.float32) + h = xp.asarray([1, 1, 1], dtype=xp.float32) y = signal.resample_poly(x, 1, 2, window=h, padtype=padtype) - assert y.dtype == np.float32 + assert y.dtype == xp.float32 + + @skip_xp_backends( + cpu_only=True, reason="resample_poly/upfirdn is CPU only" + ) @pytest.mark.parametrize('padtype', padtype_options) - @pytest.mark.parametrize('dtype', [np.float32, np.float64]) + @pytest.mark.parametrize('dtype', ['float32', 'float64']) def test_output_match_dtype(self, padtype, dtype, xp): # Test that the dtype of x is preserved per issue #14733 - x = np.arange(10, dtype=dtype) + dtype = getattr(xp, dtype) + x = xp.arange(10, dtype=dtype) y = signal.resample_poly(x, 1, 2, padtype=padtype) assert y.dtype == x.dtype + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") + @skip_xp_backends( + cpu_only=True, reason="resample_poly/upfirdn is CPU only" + ) @pytest.mark.parametrize( "method, ext, padtype", [("fft", False, None)] @@ -1481,13 +1522,13 @@ def test_resample_methods(self, method, ext, padtype, xp): rates_to = [49, 50, 51, 99, 100, 101, 199, 200, 201] # Sinusoids, windowed to avoid edge artifacts - t = np.arange(rate) / float(rate) - freqs = np.array((1., 10., 40.))[:, np.newaxis] - x = np.sin(2 * np.pi * freqs * t) * hann(rate) + t = xp.arange(rate, dtype=xp.float64) / float(rate) + freqs = xp.asarray((1., 10., 40.))[:, xp.newaxis] + x = xp.sin(2 * xp.pi * freqs * t) * hann(rate, xp=xp) for rate_to in rates_to: - t_to = np.arange(rate_to) / float(rate_to) - y_tos = np.sin(2 * np.pi * freqs * t_to) * hann(rate_to) + t_to = xp.arange(rate_to, dtype=xp.float64) / float(rate_to) + y_tos = xp.sin(2 * xp.pi * freqs * t_to) * hann(rate_to, xp=xp) if method == 'fft': y_resamps = signal.resample(x, rate_to, axis=-1) else: @@ -1508,9 +1549,13 @@ def test_resample_methods(self, method, ext, padtype, xp): y_resamps = signal.resample_poly(x, rate_to, rate, axis=-1, **polyargs) - for y_to, y_resamp, freq in zip(y_tos, y_resamps, freqs): + for i in range(y_tos.shape[0]): + y_to = y_tos[i, :] + y_resamp = y_resamps[i, :] + freq = float(freqs[i, 0]) if freq >= 0.5 * rate_to: - y_to.fill(0.) # mostly low-passed away + #y_to.fill(0.) # mostly low-passed away + y_to = xp.zeros_like(y_to) # mostly low-passed away if padtype in ['minimum', 'maximum']: xp_assert_close(y_resamp, y_to, atol=3e-1) else: @@ -1523,9 +1568,10 @@ def test_resample_methods(self, method, ext, padtype, xp): # Random data rng = np.random.RandomState(0) x = hann(rate) * np.cumsum(rng.randn(rate)) # low-pass, wind + x = xp.asarray(x) for rate_to in rates_to: # random data - t_to = np.arange(rate_to) / float(rate_to) + t_to = xp.arange(rate_to, dtype=xp.float64) / float(rate_to) y_to = np.interp(t_to, t, x) if method == 'fft': y_resamp = signal.resample(x, rate_to) @@ -1533,63 +1579,123 @@ def test_resample_methods(self, method, ext, padtype, xp): y_resamp = signal.resample_poly(x, rate_to, rate, padtype=padtype) assert y_to.shape == y_resamp.shape - corr = np.corrcoef(y_to, y_resamp)[0, 1] + corr = xp.asarray(np.corrcoef(y_to, np.asarray(y_resamp))[0, 1]) assert corr > 0.99, corr # More tests of fft method (Master 0.18.1 fails these) if method == 'fft': - x1 = np.array([1.+0.j, 0.+0.j]) + x1 = xp.asarray([1.+0.j, 0.+0.j]) y1_test = signal.resample(x1, 4) # upsampling a complex array - y1_true = np.array([1.+0.j, 0.5+0.j, 0.+0.j, 0.5+0.j]) + y1_true = xp.asarray([1.+0.j, 0.5+0.j, 0.+0.j, 0.5+0.j]) xp_assert_close(y1_test, y1_true, atol=1e-12) - x2 = np.array([1., 0.5, 0., 0.5]) + x2 = xp.asarray([1., 0.5, 0., 0.5]) y2_test = signal.resample(x2, 2) # downsampling a real array - y2_true = np.array([1., 0.]) + y2_true = xp.asarray([1., 0.]) xp_assert_close(y2_test, y2_true, atol=1e-12) - def test_poly_vs_filtfilt(self, xp): + @pytest.mark.parametrize("n_in", (8, 9)) + @pytest.mark.parametrize("n_out", (3, 4)) + def test_resample_win_func(self, n_in, n_out): + """Test callable window function. """ + x_in = np.ones(n_in) + + def win(freqs): + """Scale input by 1/2""" + return 0.5 * np.ones_like(freqs) + + y0 = signal.resample(x_in, n_out) + y1 = signal.resample(x_in, n_out, window=win) + + xp_assert_close(2*y1, y0, atol=1e-12) + + @pytest.mark.parametrize("n_in", (6, 12)) + @pytest.mark.parametrize("n_out", (3, 4)) + def test__resample_param_t(self, n_in, n_out): + """Verify behavior for parameter `t`. + + Note that only `t[0]` and `t[1]` are utilized. + """ + t0, dt = 10, 2 + x_in = np.ones(n_in) + + y0 = signal.resample(x_in, n_out) + y1, t1 = signal.resample(x_in, n_out, t=[t0, t0+dt]) + t_ref = 10 + np.arange(len(y0)) * dt * n_in / n_out + + xp_assert_equal(y1, y0) # no influence of `t` + xp_assert_close(t1, t_ref, atol=1e-12) + + @pytest.mark.parametrize("n1", (2, 3, 7, 8)) + @pytest.mark.parametrize("n0", (2, 3, 7, 8)) + def test_resample_nyquist(self, n0, n1): + """Test behavior at Nyquist frequency to ensure issue #14569 is fixed. """ + f_ny = min(n0, n1) // 2 + tt = (np.arange(n_) / n_ for n_ in (n0, n1)) + x0, x1 = (np.cos(2 * np.pi * f_ny * t_) for t_ in tt) + + y1_r = signal.resample(x0, n1) + y1_c = signal.resample(x0 + 0j, n1) + + xp_assert_close(y1_r, x1, atol=1e-12) + xp_assert_close(y1_c.real, x1, atol=1e-12) + + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") + @skip_xp_backends( + cpu_only=True, exceptions=["cupy"], reason="filtfilt is CPU-only" + ) + @pytest.mark.parametrize('down_factor', [2, 11, 79]) + def test_poly_vs_filtfilt(self, down_factor, xp): # Check that up=1.0 gives same answer as filtfilt + slicing random_state = np.random.RandomState(17) try_types = (int, np.float32, np.complex64, float, complex) size = 10000 - down_factors = [2, 11, 79] for dtype in try_types: x = random_state.randn(size).astype(dtype) if dtype in (np.complex64, np.complex128): x += 1j * random_state.randn(size) + x = xp.asarray(x) # resample_poly assumes zeros outside of signl, whereas filtfilt # can only constant-pad. Make them equivalent: x[0] = 0 x[-1] = 0 - for down in down_factors: - h = signal.firwin(31, 1. / down, window='hamming') - yf = filtfilt(h, 1.0, x, padtype='constant')[::down] + h = signal.firwin(31, 1. / down_factor, window='hamming') + h = xp.asarray(h) # XXX: convert firwin + yf = filtfilt(h, 1.0, x, padtype='constant')[::down_factor] - # Need to pass convolved version of filter to resample_poly, - # since filtfilt does forward and backward, but resample_poly - # only goes forward - hc = convolve(h, h[::-1]) - y = signal.resample_poly(x, 1, down, window=hc) - xp_assert_close(yf, y, atol=1e-7, rtol=1e-7) + # Need to pass convolved version of filter to resample_poly, + # since filtfilt does forward and backward, but resample_poly + # only goes forward + hc = convolve(h, xp.flip(h)) + y = signal.resample_poly(x, 1, down_factor, window=hc) + xp_assert_close(yf, y, atol=1e-7, rtol=1e-7) + @skip_xp_backends( + cpu_only=True, exceptions=["cupy"], reason="correlate1d is CPU-only" + ) def test_correlate1d(self, xp): for down in [2, 4]: for nx in range(1, 40, down): for nweights in (32, 33): x = np.random.random((nx,)) weights = np.random.random((nweights,)) - y_g = correlate1d(x, weights[::-1], mode='constant') + x, weights = map(xp.asarray, (x, weights)) + flip = array_namespace(x).flip + y_g = correlate1d(x, flip(weights), mode='constant') y_s = signal.resample_poly( x, up=1, down=down, window=weights) xp_assert_close(y_g[::down], y_s) - @pytest.mark.parametrize('dtype', [np.int32, np.float32]) + @skip_xp_backends( + cpu_only=True, reason="resample_poly/upfirdn is CPU only" + ) + @pytest.mark.parametrize('dtype', ['int32', 'float32']) def test_gh_15620(self, dtype, xp): - data = np.array([0, 1, 2, 3, 2, 1, 0], dtype=dtype) + dtype = getattr(xp, dtype) + data = xp.asarray([0, 1, 2, 3, 2, 1, 0], dtype=dtype) actual = signal.resample_poly(data, up=2, down=1, @@ -3399,14 +3505,18 @@ def test_hilbert2_types(self, dtype, xp): assert xp.real(signal.hilbert2(in_typed)).dtype == dtype -@skip_xp_backends(np_only=True) class TestEnvelope: """Unit tests for function `._signaltools.envelope()`. """ @staticmethod - def assert_close(actual, desired, msg): + def assert_close(actual, desired, msg, xp): + a_r_tol = ({'atol': 1e-12, 'rtol': 1e-12} + if xp_default_dtype(xp) == xp.float64 + else {'atol': 1e-5, 'rtol': 1e-5} + ) + """Little helper to compare to arrays with proper tolerances""" - xp_assert_close(actual, desired, atol=1e-12, rtol=1e-12, err_msg=msg) + xp_assert_close(actual, desired, **a_r_tol, err_msg=msg) def test_envelope_invalid_parameters(self, xp): """For `envelope()` Raise all exceptions that are used to verify function @@ -3416,72 +3526,85 @@ def test_envelope_invalid_parameters(self, xp): envelope(np.ones(3), axis=2) with pytest.raises(ValueError, match=r"z.shape\[axis\] not > 0 for z.shape=.*"): - envelope(np.ones((3, 0)), axis=1) + envelope(xp.ones((3, 0)), axis=1) for bp_in in [(0, 1, 2), (0, 2.), (None, 2.)]: ts = ', '.join(map(str, bp_in)) with pytest.raises(ValueError, match=rf"bp_in=\({ts}\) isn't a 2-tuple of.*"): # noinspection PyTypeChecker - envelope(np.ones(4), bp_in=bp_in) + envelope(xp.ones(4), bp_in=bp_in) with pytest.raises(ValueError, match="n_out=10.0 is not a positive integer or.*"): # noinspection PyTypeChecker - envelope(np.ones(4), n_out=10.) + envelope(xp.ones(4), n_out=10.) for bp_in in [(-1, 3), (1, 1), (0, 10)]: with pytest.raises(ValueError, match=r"`-n//2 <= bp_in\[0\] < bp_in\[1\] <=.*"): - envelope(np.ones(4), bp_in=bp_in) + envelope(xp.ones(4), bp_in=bp_in) with pytest.raises(ValueError, match="residual='undefined' not in .*"): # noinspection PyTypeChecker - envelope(np.ones(4), residual='undefined') + envelope(xp.ones(4), residual='undefined') + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") def test_envelope_verify_parameters(self, xp): """Ensure that the various parametrizations produce compatible results. """ - Z, Zr_a = [4, 2, 2, 3, 0], [4, 0, 0, 6, 0, 0, 0, 0] + dt_r = xp_default_dtype(xp) + dt_c = xp.complex64 if dt_r == xp.float32 else xp.complex128 + + Z = xp.asarray([4.0, 2, 2, 3, 0], dtype=dt_r) + Zr_a = xp.asarray([4.0, 0, 0, 6, 0, 0, 0, 0], dtype=dt_r) z = sp_fft.irfft(Z) - n = len(z) + n = z.shape[0] # the reference envelope: - ze2_0, zr_0 = envelope(z, (1, 3), residual='all', squared=True) - self.assert_close(sp_fft.rfft(ze2_0), np.array([4, 2, 0, 0, 0]).astype(complex), - msg="Envelope calculation error") - self.assert_close(sp_fft.rfft(zr_0), np.array([4, 0, 0, 3, 0]).astype(complex), - msg="Residual calculation error") - - ze_1, zr_1 = envelope(z, (1, 3), residual='all', squared=False) + ze2_0, zr_0 = xp.unstack(envelope(z, (1, 3), residual='all', squared=True)) + self.assert_close(sp_fft.rfft(ze2_0), + xp.asarray([4, 2, 0, 0, 0], dtype=dt_c), + msg="Envelope calculation error", xp=xp) + self.assert_close(sp_fft.rfft(zr_0), + xp.asarray([4, 0, 0, 3, 0], dtype=dt_c), + msg="Residual calculation error", xp=xp) + + ze_1, zr_1 = xp.unstack(envelope(z, (1, 3), residual='all', squared=False)) self.assert_close(ze_1**2, ze2_0, - msg="Unsquared versus Squared envelope calculation error") + msg="Unsquared versus Squared envelope calculation error", + xp=xp) self.assert_close(zr_1, zr_0, - msg="Unsquared versus Squared residual calculation error") + msg="Unsquared versus Squared residual calculation error", + xp=xp) - ze2_2, zr_2 = envelope(z, (1, 3), residual='all', squared=True, n_out=3*n) + ze2_2, zr_2 = xp.unstack( + envelope(z, (1, 3), residual='all', squared=True, n_out=3*n) + ) self.assert_close(ze2_2[::3], ze2_0, - msg="3x up-sampled envelope calculation error") + msg="3x up-sampled envelope calculation error", xp=xp) self.assert_close(zr_2[::3], zr_0, - msg="3x up-sampled residual calculation error") + msg="3x up-sampled residual calculation error", xp=xp) - ze2_3, zr_3 = envelope(z, (1, 3), residual='lowpass', squared=True) + ze2_3, zr_3 = xp.unstack(envelope(z, (1, 3), residual='lowpass', squared=True)) self.assert_close(ze2_3, ze2_0, - msg="`residual='lowpass'` envelope calculation error") - self.assert_close(sp_fft.rfft(zr_3), np.array([4, 0, 0, 0, 0]).astype(complex), - msg="`residual='lowpass'` residual calculation error") + msg="`residual='lowpass'` envelope calculation error", xp=xp) + self.assert_close(sp_fft.rfft(zr_3), + xp.asarray([4, 0, 0, 0, 0], dtype=dt_c), + msg="`residual='lowpass'` residual calculation error", xp=xp) ze2_4 = envelope(z, (1, 3), residual=None, squared=True) self.assert_close(ze2_4, ze2_0, - msg="`residual=None` envelope calculation error") + msg="`residual=None` envelope calculation error", xp=xp) # compare complex analytic signal to real version - Z_a = np.copy(Z) + Z_a = xp.asarray(Z, copy=True) Z_a[1:] *= 2 z_a = sp_fft.ifft(Z_a, n=n) # analytic signal of Z - self.assert_close(z_a.real, z, - msg="Reference analytic signal error") - ze2_a, zr_a = envelope(z_a, (1, 3), residual='all', squared=True) - self.assert_close(ze2_a, ze2_0.astype(complex), # dtypes must match - msg="Complex envelope calculation error") - self.assert_close(sp_fft.fft(zr_a), np.array(Zr_a).astype(complex), - msg="Complex residual calculation error") - + self.assert_close(xp.real(z_a), z, + msg="Reference analytic signal error", xp=xp) + ze2_a, zr_a = xp.unstack(envelope(z_a, (1, 3), residual='all', squared=True)) + self.assert_close(ze2_a, xp.astype(ze2_0, dt_c), # dtypes must match + msg="Complex envelope calculation error", xp=xp) + self.assert_close(sp_fft.fft(zr_a), xp.asarray(Zr_a, dtype=dt_c), + msg="Complex residual calculation error", xp=xp) + + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") @pytest.mark.parametrize( " Z, bp_in, Ze2_desired, Zr_desired", [([1, 0, 2, 2, 0], (1, None), [4, 2, 0, 0, 0], [1, 0, 0, 0, 0]), @@ -3501,25 +3624,30 @@ def test_envelope_real_signals(self, Z, bp_in, Ze2_desired, Zr_desired, xp): determined by an FFT and that the absolute square of a Fourier series is again a Fourier series. """ + Z = xp.asarray(Z, dtype=xp.float64) + Ze2_desired = xp.asarray(Ze2_desired, dtype=xp.float64) + Zr_desired = xp.asarray(Zr_desired, dtype=xp.float64) + z = sp_fft.irfft(Z) - ze2, zr = envelope(z, bp_in, residual='all', squared=True) - ze2_lp, zr_lp = envelope(z, bp_in, residual='lowpass', squared=True) + ze2, zr = xp.unstack(envelope(z, bp_in, residual='all', squared=True)) + ze2_lp, zr_lp = xp.unstack(envelope(z, bp_in, residual='lowpass', squared=True)) Ze2, Zr, Ze2_lp, Zr_lp = (sp_fft.rfft(z_) for z_ in (ze2, zr, ze2_lp, zr_lp)) - Ze2_desired = np.array(Ze2_desired).astype(complex) - Zr_desired = np.array(Zr_desired).astype(complex) + Ze2_desired = xp.asarray(Ze2_desired, dtype=xp.complex128) + Zr_desired = xp.asarray(Zr_desired, dtype=xp.complex128) self.assert_close(Ze2, Ze2_desired, - msg="Envelope calculation error (residual='all')") + msg="Envelope calculation error (residual='all')", xp=xp) self.assert_close(Zr, Zr_desired, - msg="Residual calculation error (residual='all')") + msg="Residual calculation error (residual='all')", xp=xp) if bp_in[1] is not None: Zr_desired[bp_in[1]:] = 0 self.assert_close(Ze2_lp, Ze2_desired, - msg="Envelope calculation error (residual='lowpass')") + msg="Envelope calculation error (residual='lowpass')", xp=xp) self.assert_close(Zr_lp, Zr_desired, - msg="Residual calculation error (residual='lowpass')") + msg="Residual calculation error (residual='lowpass')", xp=xp) + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") @pytest.mark.parametrize( " Z, bp_in, Ze2_desired, Zr_desired", [([0, 5, 0, 5, 0], (None, None), [5, 0, 10, 0, 5], [0, 0, 0, 0, 0]), @@ -3532,57 +3660,95 @@ def test_envelope_complex_signals(self, Z, bp_in, Ze2_desired, Zr_desired, xp): We only need to test for the complex envelope here, since the ``Nones``s in the bandpass filter were already tested in the previous test. """ + Z = xp.asarray(Z, dtype=xp.float64) + Ze2_desired = xp.asarray(Ze2_desired, dtype=xp.complex128) + Zr_desired = xp.asarray(Zr_desired, dtype=xp.complex128) + z = sp_fft.ifft(sp_fft.ifftshift(Z)) - ze2, zr = envelope(z, bp_in, residual='all', squared=True) + ze2, zr = xp.unstack(envelope(z, bp_in, residual='all', squared=True)) Ze2, Zr = (sp_fft.fftshift(sp_fft.fft(z_)) for z_ in (ze2, zr)) - self.assert_close(Ze2, np.array(Ze2_desired).astype(complex), - msg="Envelope calculation error") - self.assert_close(Zr, np.array(Zr_desired).astype(complex), - msg="Residual calculation error") + self.assert_close(Ze2, Ze2_desired, + msg="Envelope calculation error", xp=xp) + self.assert_close(Zr, Zr_desired, + msg="Residual calculation error", xp=xp) + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") def test_envelope_verify_axis_parameter(self, xp): """Test for multi-channel envelope calculations. """ - z = sp_fft.irfft([[1, 0, 2, 2, 0], [7, 0, 4, 4, 0]]) - Ze2_desired = np.array([[4, 2, 0, 0, 0], [16, 8, 0, 0, 0]], - dtype=complex) - Zr_desired = np.array([[1, 0, 0, 0, 0], [7, 0, 0, 0, 0]], dtype=complex) + dt_r = xp_default_dtype(xp) + dt_c = xp.complex64 if dt_r == xp.float32 else xp.complex128 + + z = sp_fft.irfft(xp.asarray([[1.0, 0, 2, 2, 0], [7, 0, 4, 4, 0]], dtype=dt_r)) + Ze2_desired = xp.asarray([[4, 2, 0, 0, 0], [16, 8, 0, 0, 0]], + dtype=dt_c) + Zr_desired = xp.asarray([[1, 0, 0, 0, 0], [7, 0, 0, 0, 0]], dtype=dt_c) - ze2, zr = envelope(z, squared=True, axis=1) - ye2T, yrT = envelope(z.T, squared=True, axis=0) + ze2, zr = xp.unstack(envelope(z, squared=True, axis=1)) + ye2T, yrT = xp.unstack(envelope(z.T, squared=True, axis=0)) Ze2, Ye2, Zr, Yr = (sp_fft.rfft(z_) for z_ in (ze2, ye2T.T, zr, yrT.T)) - self.assert_close(Ze2, Ze2_desired, msg="2d envelope calculation error") - self.assert_close(Zr, Zr_desired, msg="2d residual calculation error") - self.assert_close(Ye2, Ze2_desired, msg="Transposed 2d envelope calc. error") - self.assert_close(Yr, Zr_desired, msg="Transposed 2d residual calc. error") + self.assert_close(Ze2, Ze2_desired, msg="2d envelope calculation error", xp=xp) + self.assert_close(Zr, Zr_desired, msg="2d residual calculation error", xp=xp) + self.assert_close( + Ye2, Ze2_desired, msg="Transposed 2d envelope calc. error", xp=xp + ) + self.assert_close( + Yr, Zr_desired, msg="Transposed 2d residual calc. error", xp=xp + ) + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") def test_envelope_verify_axis_parameter_complex(self, xp): """Test for multi-channel envelope calculations with complex values. """ - z = sp_fft.ifft(sp_fft.ifftshift([[1, 5, 0, 5, 2], [1, 10, 0, 10, 2]], axes=1)) - Ze2_des = np.array([[5, 0, 10, 0, 5], [20, 0, 40, 0, 20],], - dtype=complex) - Zr_des = np.array([[1, 0, 0, 0, 2], [1, 0, 0, 0, 2]], dtype=complex) + dt_r = xp_default_dtype(xp) + dt_c = xp.complex64 if dt_r == xp.float32 else xp.complex128 + inp = xp.asarray([[1.0, 5, 0, 5, 2], [1, 10, 0, 10, 2]], dtype=dt_r) + z = sp_fft.ifft(sp_fft.ifftshift(inp, axes=1)) + Ze2_des = xp.asarray([[5, 0, 10, 0, 5], [20, 0, 40, 0, 20],], dtype=dt_c) + Zr_des = xp.asarray([[1, 0, 0, 0, 2], [1, 0, 0, 0, 2]], dtype=dt_c) kw = dict(bp_in=(-1, 2), residual='all', squared=True) - ze2, zr = envelope(z, axis=1, **kw) - ye2T, yrT = envelope(z.T, axis=0, **kw) + ze2, zr = xp.unstack(envelope(z, axis=1, **kw)) + ye2T, yrT = xp.unstack(envelope(z.T, axis=0, **kw)) Ze2, Ye2, Zr, Yr = (sp_fft.fftshift(sp_fft.fft(z_), axes=1) for z_ in (ze2, ye2T.T, zr, yrT.T)) - self.assert_close(Ze2, Ze2_des, msg="2d envelope calculation error") - self.assert_close(Zr, Zr_des, msg="2d residual calculation error") - self.assert_close(Ye2, Ze2_des, msg="Transposed 2d envelope calc. error") - self.assert_close(Yr, Zr_des, msg="Transposed 2d residual calc. error") + self.assert_close(Ze2, Ze2_des, msg="2d envelope calculation error", xp=xp) + self.assert_close(Zr, Zr_des, msg="2d residual calculation error", xp=xp) + self.assert_close( + Ye2, Ze2_des, msg="Transposed 2d envelope calc. error", xp=xp + ) + self.assert_close(Yr, Zr_des, msg="Transposed 2d residual calc. error", xp=xp) + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") @pytest.mark.parametrize('X', [[4, 0, 0, 1, 2], [4, 0, 0, 2, 1, 2]]) def test_compare_envelope_hilbert(self, X, xp): """Compare output of `envelope()` and `hilbert()`. """ + X = xp.asarray(X, dtype=xp.float64) x = sp_fft.irfft(X) - e_hil = np.abs(hilbert(x)) + e_hil = xp.abs(hilbert(x)) e_env = envelope(x, (None, None), residual=None) - self.assert_close(e_hil, e_env, msg="Hilbert-Envelope comparison error") + self.assert_close(e_hil, e_env, msg="Hilbert-Envelope comparison error", xp=xp) + + def test_nyquist(self): + """Test behavior when input is a cosine at the Nyquist frequency. + + Resampling even length signals, requires accounting for unpaired bins at the + Nyquist frequency (consults the source code of `resample`). + + Since `envelope` excludes the Nyquist frequency from the envelope calculation, + only the residues need to be investigated. + """ + x4 = sp_fft.irfft([0, 0, 8]) # = [2, -2, 2, -2] + x6 = signal.resample(x4, num=6) # = [2, -1, -1, 2, -1, -1] + y6, y6_res = envelope(x4, n_out=6, residual='all') # real-valued case + z6, z6_res = envelope(x4 + 0j, n_out=6, residual='all') # complex-valued case + + xp_assert_close(y6, np.zeros(6), atol=1e-12) + xp_assert_close(y6_res, x6, atol=1e-12) + xp_assert_close(z6, np.zeros(6, dtype=z6.dtype), atol=1e-12) + xp_assert_close(z6_res, x6.astype(z6.dtype), atol=1e-12) @skip_xp_backends(np_only=True) class TestPartialFractionExpansion: diff --git a/scipy/signal/tests/test_spectral.py b/scipy/signal/tests/test_spectral.py index 12dac6300b9e..a4c62574676a 100644 --- a/scipy/signal/tests/test_spectral.py +++ b/scipy/signal/tests/test_spectral.py @@ -10,7 +10,7 @@ from scipy import signal from scipy.fft import fftfreq, rfftfreq, fft, irfft from scipy.integrate import trapezoid -from scipy.signal import (periodogram, welch, lombscargle, coherence, +from scipy.signal import (periodogram, welch, lombscargle, coherence, csd, spectrogram, check_COLA, check_NOLA) from scipy.signal.windows import hann from scipy.signal._spectral_py import _spectral_helper @@ -18,7 +18,6 @@ # Compare ShortTimeFFT.stft() / ShortTimeFFT.istft() with stft() / istft(): from scipy.signal.tests._scipy_spectral_test_shim import stft_compare as stft from scipy.signal.tests._scipy_spectral_test_shim import istft_compare as istft -from scipy.signal.tests._scipy_spectral_test_shim import csd_compare as csd class TestPeriodogram: @@ -416,8 +415,7 @@ def test_short_data(self): #for string-like window, input signal length < nperseg value gives #UserWarning, sets nperseg to x.shape[-1] with suppress_warnings() as sup: - msg = "nperseg = 256 is greater than input length = 8, using nperseg = 8" - sup.filter(UserWarning, msg) + sup.filter(UserWarning, "nperseg=256 is greater than signal.*") f, p = welch(x,window='hann') # default nperseg f1, p1 = welch(x,window='hann', nperseg=256) # user-specified nperseg f2, p2 = welch(x, nperseg=8) # valid nperseg, doesn't give warning @@ -560,6 +558,15 @@ def test_average(self): assert_raises(ValueError, welch, x, nperseg=8, average='unrecognised-average') + def test_ratio_scale_to(self): + """Verify the factor of ``sum(abs(window)**2)*fs / abs(sum(window))**2`` + used in the `welch` and `csd` docstrs. """ + x, win, fs = np.array([1., 0, 0, 0]), np.ones(4), 12 + params = dict(fs=fs, window=win, return_onesided=False, detrend=None) + p_dens = welch(x, scaling='density', **params)[1] + p_spec = welch(x, scaling='spectrum', **params)[1] + p_fac = sum(win**2)*fs / abs(sum(win))**2 + assert_allclose(p_spec / p_dens, p_fac) class TestCSD: def test_pad_shorter_x(self): @@ -735,6 +742,8 @@ def test_window_external(self): win_err = signal.get_window('hann', 32) assert_raises(ValueError, csd, x, x, 10, win_err, nperseg=None) # because win longer than signal + with pytest.raises(ValueError, match="Parameter nperseg=0.*"): + csd(x, x, 0, nperseg=0) def test_empty_input(self): f, p = csd([],np.zeros(10)) @@ -779,8 +788,7 @@ def test_short_data(self): #for string-like window, input signal length < nperseg value gives #UserWarning, sets nperseg to x.shape[-1] with suppress_warnings() as sup: - msg = "nperseg = 256 is greater than input length = 8, using nperseg = 8" - sup.filter(UserWarning, msg) + sup.filter(UserWarning, "nperseg=256 is greater than signal length.*") f, p = csd(x, x, window='hann') # default nperseg f1, p1 = csd(x, x, window='hann', nperseg=256) # user-specified nperseg f2, p2 = csd(x, x, nperseg=8) # valid nperseg, doesn't give warning @@ -811,6 +819,11 @@ def test_nfft_too_short(self): assert_raises(ValueError, csd, np.ones(12), np.zeros(12), nfft=3, nperseg=4) + def test_incompatible_inputs(self): + with pytest.raises(ValueError, match='x and y cannot be broadcast.*'): + csd(np.ones((1, 8, 1)), np.ones((2, 8)), nperseg=4) + + def test_real_onesided_even_32(self): x = np.zeros(16, 'f') x[0] = 1 diff --git a/scipy/signal/tests/test_windows.py b/scipy/signal/tests/test_windows.py index 1dad96494b5a..a5342b22d2a2 100644 --- a/scipy/signal/tests/test_windows.py +++ b/scipy/signal/tests/test_windows.py @@ -10,7 +10,7 @@ from scipy.signal import windows, get_window, resample from scipy._lib._array_api import ( xp_assert_close, xp_assert_equal, array_namespace, is_torch, is_jax, is_cupy, - assert_array_almost_equal, SCIPY_DEVICE, + assert_array_almost_equal, SCIPY_DEVICE, is_numpy ) skip_xp_backends = pytest.mark.skip_xp_backends @@ -824,7 +824,7 @@ def test_array_as_window(self, xp): sig = xp.arange(128) win = windows.get_window(('kaiser', 8.0), osfactor // 2, xp=xp) - with assert_raises(ValueError, match='must have the same length'): + with assert_raises(ValueError, match='^window.shape='): resample(sig, len(sig) * osfactor, window=win) def test_general_cosine(self, xp): @@ -854,6 +854,15 @@ def test_lanczos(self, xp): xp_assert_close(get_window('lanczos', 6, xp=xp), get_window('sinc', 6, xp=xp)) + def test_xp_default(self, xp): + # no explicit xp= argument, default to numpy + win = get_window('lanczos', 6) + assert isinstance(win, np.ndarray) + + win = get_window('lanczos', 6, xp=xp) + if not is_numpy(xp): + assert not isinstance(win, np.ndarray) + @skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/2620") def test_windowfunc_basics(xp): diff --git a/scipy/signal/windows/_windows.py b/scipy/signal/windows/_windows.py index d7ddde67a2fc..21aba9f789b6 100644 --- a/scipy/signal/windows/_windows.py +++ b/scipy/signal/windows/_windows.py @@ -1299,7 +1299,7 @@ def kaiser(M, beta, sym=True, *, xp=None, device=None): n = xp.arange(0, M, dtype=xp.float64, device=device) alpha = (M - 1) / 2.0 w = (special.i0(beta * xp.sqrt(1 - ((n - alpha) / alpha) ** 2.0)) / - special.i0(beta)) + special.i0(xp.asarray(beta, dtype=xp.float64))) return _truncate(w, needs_trunc) diff --git a/scipy/sparse/_base.py b/scipy/sparse/_base.py index 9e0374e1010b..08b193583346 100644 --- a/scipy/sparse/_base.py +++ b/scipy/sparse/_base.py @@ -1,11 +1,13 @@ """Base class for sparse matrices""" +from warnings import warn import math import numpy as np +import operator from ._sputils import (asmatrix, check_reshape_kwargs, check_shape, - get_sum_dtype, isdense, isscalarlike, - matrix, validateaxis, getdtype, isintlike) + get_sum_dtype, isdense, isscalarlike, _todata, + matrix, validateaxis, getdtype, is_pydata_spmatrix) from scipy._lib._sparse import SparseABC, issparse from ._matrix import spmatrix @@ -64,6 +66,18 @@ class SparseEfficiencyWarning(SparseWarning): MAXPRINT = 50 +# helper dicts to manipulate comparison operators +# We negate operators (with warning) when all implicit values would be True +op_neg = {operator.eq: operator.ne, operator.ne: operator.eq, + operator.lt: operator.ge, operator.ge: operator.lt, + operator.gt: operator.le, operator.le: operator.gt} + + +# We use symbolic version of operators in warning messages. +op_sym = {operator.eq: '==', operator.ge: '>=', operator.le: '<=', + operator.ne: '!=', operator.gt: '>', operator.lt: '<'} + + # `_spbase` is a subclass of `SparseABC`. # This allows other submodules to check for instances of sparse subclasses # via `scipy._lib._sparse.issparse`, without introducing @@ -143,7 +157,7 @@ def reshape(self, *args, **kwargs): Parameters ---------- - shape : length-2 tuple of ints + shape : tuple of ints The new shape should be compatible with the original shape. order : {'C', 'F'}, optional Read the elements using this index order. 'C' means to read and @@ -475,18 +489,94 @@ def asformat(self, format, copy=False): #################################################################### def multiply(self, other): - """Point-wise multiplication by another array/matrix.""" + """Element-wise multiplication by another array/matrix.""" if isscalarlike(other): return self._mul_scalar(other) - return self.tocsr().multiply(other) + + if self.ndim < 3: + return self.tocsr()._multiply_2d_with_broadcasting(other) + + if not (issparse(other) or isdense(other)): + # If it's a list or whatever, treat it like an array + other_a = np.asanyarray(other) + if other_a.ndim == 0 and other_a.dtype == np.object_: + # numpy creates a 0d object array if all else fails. + # Not interpretable as an array; return NotImplemented so + # other's __rmul__ can kick in if that's implemented. + return NotImplemented + # Allow custom sparse class indicated by attr sparse gh-6520 + try: + other.shape + except AttributeError: + other = other_a + + if self.shape != other.shape: + raise ValueError("inconsistent shapes: >2D multiply() does not yet " + "support broadcasting") + + # self is >2D so must be COO + if isdense(other): + data = np.multiply(self.data, other[self.coords]) + result = self.copy() + result.data = data.view(np.ndarray).ravel() + return result + + elif issparse(other): + csr_self = self.reshape(1, -1).tocsr() + csr_other = other.reshape(1, -1).tocsr() + return csr_self._binopt(csr_other, '_elmul_').reshape(self.shape) + + else: + # Not scalar, dense or sparse. Return NotImplemented so that + # other's __rmul__ can kick in if that's implemented. + return NotImplemented + + def _maximum_minimum(self, other, np_op): + if not (issparse(other) or isdense(other) or isscalarlike(other)): + # If it's a list or whatever, treat it like an array + other_a = np.asanyarray(other) + if other_a.ndim == 0 and other_a.dtype == np.object_: + # numpy creates a 0d object array if all else fails. + # We don't know how to handle it either. + raise NotImplementedError('maximum or minimum with an unrecognized ' + 'array type is not supported') + # Allow custom sparse class indicated by attr sparse gh-6520 + try: + other.shape + except AttributeError: + other = other_a + + if isscalarlike(other): + if np_op(0, other): + pos_neg = 'positive' if np_op == np.maximum else 'negative' + warn(f"Taking {np_op.__name__} with a {pos_neg} number results in a" + " dense matrix.", SparseEfficiencyWarning, stacklevel=3) + return self.__class__(np_op(self.toarray(), other)) + else: + csr_self = (self if self.ndim < 3 else self.reshape(1, -1)).tocsr() + result = csr_self._scalar_binopt(other, np_op) + return result if self.ndim < 3 else result.tocoo().reshape(self.shape) + elif isdense(other): + return np_op(self.todense(), other) + elif issparse(other): + if self.shape != other.shape: + raise ValueError(f"inconsistent shapes {self.shape=} {other.shape=}") + if self.ndim < 3: # shape is same so other.ndim < 3 + return self.tocsr()._binopt(other, f'_{np_op.__name__}_') + csr_self = self.reshape(1, -1).tocsr() + csr_other = other.reshape(1, -1).tocsr() + result = csr_self._binopt(csr_other, f'_{np_op.__name__}_') + return result.tocoo().reshape(self.shape) + else: + raise ValueError("Operands not compatible.") def maximum(self, other): """Element-wise maximum between this and another array/matrix.""" - return self.tocsr().maximum(other) + return self._maximum_minimum(other, np.maximum) def minimum(self, other): """Element-wise minimum between this and another array/matrix.""" - return self.tocsr().minimum(other) + return self._maximum_minimum(other, np.minimum) def dot(self, other): """Ordinary dot product @@ -516,23 +606,96 @@ def _broadcast_to(self, shape, copy=False): else: return self.tocsr()._broadcast_to(shape, copy) + def _comparison(self, other, op): + # We convert to CSR format and use methods _binopt or _scalar_binopt + # If ndim>2 we reshape to 2D, compare and then reshape back to nD + if not (issparse(other) or isdense(other) or isscalarlike(other)): + if is_pydata_spmatrix(other): + # cannot compare with pydata other, but it might compare with us. + return NotImplemented + # If it's a list or whatever, treat it like an array + other_a = np.asanyarray(other) + if other_a.ndim == 0 and other_a.dtype == np.object_: + # numpy creates a 0d object array if all else fails. + # Not interpretable as an array; return NotImplemented so + # other's dunder methods can kick in if implemented. + return NotImplemented + # Allow custom sparse class indicated by attr sparse gh-6520 + try: + other.shape + except AttributeError: + other = other_a + + if isscalarlike(other): + if not op(0, other): + if np.isnan(other): # op is not `ne`, so results are all False. + return self.__class__(self.shape, dtype=np.bool_) + else: + csr_self = (self if self.ndim < 3 else self.reshape(1, -1)).tocsr() + res = csr_self._scalar_binopt(other, op) + return res if self.ndim < 3 else res.tocoo().reshape(self.shape) + else: + warn(f"Comparing a sparse matrix with {other} using {op_sym[op]} " + f"is inefficient. Try using {op_sym[op_neg[op]]} instead.", + SparseEfficiencyWarning, stacklevel=3) + if np.isnan(other): + # op is `ne` cuz op(0, other) and isnan(other). Return all True. + return self.__class__(np.ones(self.shape, dtype=np.bool_)) + + # op is eq, le, or ge. Use negated op and then negate. + csr_self = (self if self.ndim < 3 else self.reshape(1, -1)).tocsr() + inv = csr_self._scalar_binopt(other, op_neg[op]) + all_true = csr_self.__class__(np.ones(csr_self.shape, dtype=np.bool_)) + result = all_true - inv + return result if self.ndim < 3 else result.tocoo().reshape(self.shape) + + elif isdense(other): + return op(self.todense(), other) + + elif issparse(other): + # TODO sparse broadcasting + if self.shape != other.shape: + # eq and ne return True or False instead of an array when the shapes + # don't match. Numpy doesn't do this. Is this what we want? + if op in (operator.eq, operator.ne): + return op == operator.eq + raise ValueError("inconsistent shape") + + csr_self = (self if self.ndim < 3 else self.reshape(1, -1)).tocsr() + csr_other = (other if other.ndim < 3 else other.reshape(1, -1)).tocsr() + if not op(0, 0): + result = csr_self._binopt(csr_other, f'_{op.__name__}_') + return result if self.ndim < 3 else result.tocoo().reshape(self.shape) + else: + # result will not be sparse. Use negated op and then negate. + warn(f"Comparing two sparse matrices using {op_sym[op]} " + f"is inefficient. Try using {op_sym[op_neg[op]]} instead.", + SparseEfficiencyWarning, stacklevel=3) + inv = csr_self._binopt(csr_other, f'_{op_neg[op].__name__}_') + all_true = csr_self.__class__(np.ones(csr_self.shape, dtype=np.bool_)) + result = all_true - inv + return result if self.ndim < 3 else result.tocoo().reshape(self.shape) + else: + # cannot compare with other, but it might compare with us. + return NotImplemented + def __eq__(self, other): - return self.tocsr().__eq__(other) + return self._comparison(other, operator.eq) def __ne__(self, other): - return self.tocsr().__ne__(other) + return self._comparison(other, operator.ne) def __lt__(self, other): - return self.tocsr().__lt__(other) + return self._comparison(other, operator.lt) def __gt__(self, other): - return self.tocsr().__gt__(other) + return self._comparison(other, operator.gt) def __le__(self, other): - return self.tocsr().__le__(other) + return self._comparison(other, operator.le) def __ge__(self, other): - return self.tocsr().__ge__(other) + return self._comparison(other, operator.ge) def __abs__(self): return abs(self.tocsr()) @@ -647,12 +810,12 @@ def _matmul_dispatch(self, other): # If it's a list or whatever, treat it like an array other_a = np.asanyarray(other) - if other_a.ndim == 0 and other_a.dtype == np.object_: + # numpy creates a 0d object array if all else fails. # Not interpretable as an array; return NotImplemented so that # other's __rmatmul__ can kick in if that's implemented. return NotImplemented - + # Allow custom sparse class indicated by attr sparse gh-6520 try: other.shape except AttributeError: @@ -752,6 +915,21 @@ def __rmatmul__(self, other): #################### def _divide(self, other, true_divide=False, rdivide=False): + # Do we need to continue to support true_divide and divide? + if not (issparse(other) or isdense(other) or isscalarlike(other)): + # If it's a list or whatever, treat it like an array + other_a = np.asanyarray(other) + if other_a.ndim == 0 and other_a.dtype == np.object_: + # numpy creates a 0d object array if all else fails. + # Not interpretable as an array; return NotImplemented so that + # other's __rdiv__ can kick in if that's implemented. + return NotImplemented + # Allow custom sparse class indicated by attr sparse gh-6520 + try: + other.shape + except AttributeError: + other = other_a + if isscalarlike(other): if rdivide: if true_divide: @@ -787,12 +965,16 @@ def _divide(self, other, true_divide=False, rdivide=False): if rdivide: return other._divide(self, true_divide, rdivide=False) - self_csr = self.tocsr() + csr_self = (self if self.ndim < 3 else self.reshape(1, -1)).tocsr() + csr_other = (other if self.ndim < 3 else other.reshape(1, -1)).tocsr() if true_divide and np.can_cast(self.dtype, np.float64): - return self_csr.astype(np.float64)._divide_sparse(other) + result = csr_self.astype(np.float64)._divide_sparse(csr_other) else: - return self_csr._divide_sparse(other) + result = csr_self._divide_sparse(csr_other) + return result if self.ndim < 3 else result.reshape(self.shape) else: + # not scalar, dense or sparse. Return NotImplemented so + # other's __rdiv__ can kick in if that's implemented. return NotImplemented def __truediv__(self, other): @@ -1148,25 +1330,39 @@ def sum(self, axis=None, dtype=None, out=None): # Mimic numpy's casting. res_dtype = get_sum_dtype(self.dtype) - # Note: all valid 1D axis values are canonically `None` so use this code. + # Note: all valid 1D axis values are canonically `None`. if axis is None: - ones = self._ascontainer(np.ones((self.shape[-1], 1), dtype=res_dtype)) - return (self @ ones).sum(dtype=dtype, out=out) + if self.nnz == 0: + return np.sum(self._ascontainer([0]), dtype=dtype or res_dtype, out=out) + return np.sum(self._ascontainer(_todata(self)), dtype=dtype, out=out) + elif isspmatrix(self): + # Ensure spmatrix sums stay 2D + new_shape = (1, self.shape[1]) if axis == (0,) else (self.shape[0], 1) + else: + new_shape = tuple(self.shape[i] for i in range(self.ndim) if i not in axis) + + if out is None: + # create out array with desired dtype + out = self._ascontainer(np.zeros(new_shape, dtype=dtype or res_dtype)) + else: + if out.shape != new_shape: + raise ValueError("out dimensions do not match shape") + + if self.ndim > 2: + return self._sum_nd(axis, res_dtype, out) - # We use multiplication by a matrix of ones to achieve this. + # We use multiplication by a matrix of ones to sum. # For some sparse array formats more efficient methods are # possible -- these should override this function. - if axis == 0: - # sum over columns + if axis == (0,): ones = self._ascontainer(np.ones((1, self.shape[0]), dtype=res_dtype)) - ret = ones @ self - else: # axis == 1: - # sum over rows + # sets dtype while loading into out + out[...] = (ones @ self).reshape(new_shape) + else: # axis == (1,) ones = self._ascontainer(np.ones((self.shape[1], 1), dtype=res_dtype)) - ret = self @ ones - - # This doesn't sum anything. It handles dtype and out. - return ret.sum(axis=axis, dtype=dtype, out=out) + # sets dtype while loading into out + out[...] = (self @ ones).reshape(new_shape) + return out def mean(self, axis=None, dtype=None, out=None): """ @@ -1207,32 +1403,21 @@ def mean(self, axis=None, dtype=None, out=None): """ axis = validateaxis(axis, ndim=self.ndim) - res_dtype = self.dtype.type integral = (np.issubdtype(self.dtype, np.integer) or np.issubdtype(self.dtype, np.bool_)) - # output dtype - if dtype is None: - if integral: - res_dtype = np.float64 - else: - res_dtype = np.dtype(dtype).type - # intermediate dtype for summation - inter_dtype = np.float64 if integral else res_dtype + inter_dtype = np.float64 if integral else self.dtype inter_self = self.astype(inter_dtype) if axis is None: denom = math.prod(self.shape) - elif isintlike(axis): - denom = self.shape[axis] else: denom = math.prod(self.shape[ax] for ax in axis) - res = (inter_self * (1.0 / denom)).sum(axis=axis, dtype=inter_dtype) - if out is None: - return res.sum(axis=(), dtype=dtype) - # out is handled differently by matrix and ndarray so use as_container - return self._ascontainer(res).sum(axis=(), dtype=dtype, out=out) + res = (inter_self * (1.0 / denom)).sum(axis=axis, dtype=inter_dtype, out=out) + if dtype is not None and out is None: + return res.astype(dtype, copy=False) + return res def diagonal(self, k=0): diff --git a/scipy/sparse/_compressed.py b/scipy/sparse/_compressed.py index d6910bd8fb8e..37ae345f829d 100644 --- a/scipy/sparse/_compressed.py +++ b/scipy/sparse/_compressed.py @@ -3,7 +3,6 @@ from warnings import warn import itertools -import operator import numpy as np from scipy._lib._util import _prune_array, copy_if_needed @@ -18,10 +17,9 @@ csr_sum_duplicates, csr_has_sorted_indices, csr_sort_indices, csr_matmat_maxnnz, csr_matmat) from ._index import IndexMixin -from ._sputils import (upcast, upcast_char, to_native, isdense, isshape, - getdtype, isscalarlike, isintlike, downcast_intp_index, - get_sum_dtype, check_shape, get_index_dtype, broadcast_shapes, - is_pydata_spmatrix) +from ._sputils import (upcast, upcast_char, to_native, isshape, + getdtype, isintlike, downcast_intp_index, + get_sum_dtype, check_shape, get_index_dtype, broadcast_shapes) class _cs_matrix(_data_matrix, _minmax_mixin, IndexMixin): @@ -241,135 +239,6 @@ def _scalar_binopt(self, other, op): res.eliminate_zeros() return res - def __eq__(self, other): - # Scalar other. - if isscalarlike(other): - if np.isnan(other): - return self.__class__(self.shape, dtype=np.bool_) - - if other == 0: - warn("Comparing a sparse matrix with 0 using == is inefficient" - ", try using != instead.", SparseEfficiencyWarning, - stacklevel=3) - all_true = self.__class__(np.ones(self.shape, dtype=np.bool_)) - inv = self._scalar_binopt(other, operator.ne) - return all_true - inv - else: - return self._scalar_binopt(other, operator.eq) - # Dense other. - elif isdense(other): - return self.todense() == other - # Pydata sparse other. - elif is_pydata_spmatrix(other): - return NotImplemented - # Sparse other. - elif issparse(other): - warn("Comparing sparse matrices using == is inefficient, try using" - " != instead.", SparseEfficiencyWarning, stacklevel=3) - # TODO sparse broadcasting - if self.shape != other.shape: - return False - elif self.format != other.format: - other = other.asformat(self.format) - res = self._binopt(other, '_ne_') - all_true = self.__class__(np.ones(self.shape, dtype=np.bool_)) - return all_true - res - else: - return NotImplemented - - def __ne__(self, other): - # Scalar other. - if isscalarlike(other): - if np.isnan(other): - warn("Comparing a sparse matrix with nan using != is" - " inefficient", SparseEfficiencyWarning, stacklevel=3) - all_true = self.__class__(np.ones(self.shape, dtype=np.bool_)) - return all_true - elif other != 0: - warn("Comparing a sparse matrix with a nonzero scalar using !=" - " is inefficient, try using == instead.", - SparseEfficiencyWarning, stacklevel=3) - all_true = self.__class__(np.ones(self.shape), dtype=np.bool_) - inv = self._scalar_binopt(other, operator.eq) - return all_true - inv - else: - return self._scalar_binopt(other, operator.ne) - # Dense other. - elif isdense(other): - return self.todense() != other - # Pydata sparse other. - elif is_pydata_spmatrix(other): - return NotImplemented - # Sparse other. - elif issparse(other): - # TODO sparse broadcasting - if self.shape != other.shape: - return True - elif self.format != other.format: - other = other.asformat(self.format) - return self._binopt(other, '_ne_') - else: - return NotImplemented - - def _inequality(self, other, op, op_name, bad_scalar_msg): - # Scalar other. - if isscalarlike(other): - if 0 == other and op_name in ('_le_', '_ge_'): - raise NotImplementedError(" >= and <= don't work with 0.") - elif op(0, other): - warn(bad_scalar_msg, SparseEfficiencyWarning, stacklevel=3) - other_arr = np.empty(self.shape, dtype=np.result_type(other)) - other_arr.fill(other) - other_arr = self.__class__(other_arr) - return self._binopt(other_arr, op_name) - else: - return self._scalar_binopt(other, op) - # Dense other. - elif isdense(other): - return op(self.todense(), other) - # Sparse other. - elif issparse(other): - # TODO sparse broadcasting - if self.shape != other.shape: - raise ValueError("inconsistent shapes") - elif self.format != other.format: - other = other.asformat(self.format) - if op_name not in ('_ge_', '_le_'): - return self._binopt(other, op_name) - - warn("Comparing sparse matrices using >= and <= is inefficient, " - "using <, >, or !=, instead.", - SparseEfficiencyWarning, stacklevel=3) - all_true = self.__class__(np.ones(self.shape, dtype=np.bool_)) - res = self._binopt(other, '_gt_' if op_name == '_le_' else '_lt_') - return all_true - res - else: - return NotImplemented - - def __lt__(self, other): - return self._inequality(other, operator.lt, '_lt_', - "Comparing a sparse matrix with a scalar " - "greater than zero using < is inefficient, " - "try using >= instead.") - - def __gt__(self, other): - return self._inequality(other, operator.gt, '_gt_', - "Comparing a sparse matrix with a scalar " - "less than zero using > is inefficient, " - "try using <= instead.") - - def __le__(self, other): - return self._inequality(other, operator.le, '_le_', - "Comparing a sparse matrix with a scalar " - "greater than zero using <= is inefficient, " - "try using > instead.") - - def __ge__(self, other): - return self._inequality(other, operator.ge, '_ge_', - "Comparing a sparse matrix with a scalar " - "less than zero using >= is inefficient, " - "try using < instead.") - ################################# # Arithmetic operator overrides # ################################# @@ -391,12 +260,9 @@ def _add_sparse(self, other): def _sub_sparse(self, other): return self._binopt(other, '_minus_') - def multiply(self, other): - """Point-wise multiplication by array/matrix, vector, or scalar.""" - # Scalar multiplication. - if isscalarlike(other): - return self._mul_scalar(other) - # Sparse matrix or vector. + def _multiply_2d_with_broadcasting(self, other): + """Element-wise multiplication by array/matrix, vector, or scalar.""" + # Called after checking that other is not scalarlike and self.ndim <=2 if issparse(other): if self.shape == other.shape: other = self.__class__(other) @@ -440,7 +306,7 @@ def multiply(self, other): if sN == 1 and sM == oM: new_self = _make_diagonal_csr(self.toarray().ravel(), is_array) return new_self._matmul_sparse(other) - raise ValueError("inconsistent shapes") + raise ValueError(f"inconsistent shapes {self.shape} and {other.shape}") # Assume other is a dense matrix/array, which produces a single-item # object array if other isn't convertible to ndarray. @@ -476,7 +342,7 @@ def multiply(self, other): elif other2d.shape[1] == self.shape[-1]: # Dense 2d matrix. data = np.multiply(ret.data, other2d[:, ret.col]) else: - raise ValueError("inconsistent shapes") + raise ValueError(f"inconsistent shapes {self.shape} and {other.shape}") idx_dtype = self._get_index_dtype(ret.col, maxval=ret.nnz * other2d.shape[0]) row = np.repeat(np.arange(other2d.shape[0], dtype=idx_dtype), ret.nnz) @@ -493,7 +359,7 @@ def multiply(self, other): elif other2d.shape[0] == self.shape[0]: # Dense 2d array. data = np.multiply(ret.data[:, None], other2d[ret.row]) else: - raise ValueError("inconsistent shapes") + raise ValueError(f"inconsistent shapes {self.shape} and {other.shape}") idx_dtype = self._get_index_dtype(ret.row, maxval=ret.nnz * other2d.shape[1]) row = np.repeat(ret.row.astype(idx_dtype, copy=False), other2d.shape[1]) @@ -510,7 +376,7 @@ def multiply(self, other): elif other2d.shape[1] == 1 and self.shape[0] == other2d.shape[0]: data = np.multiply(ret.data, other2d[ret.row].ravel()) else: - raise ValueError("inconsistent shapes") + raise ValueError(f"inconsistent shapes {self.shape} and {other.shape}") ret.data = data.view(np.ndarray).ravel() return ret @@ -619,45 +485,6 @@ def diagonal(self, k=0): diagonal.__doc__ = _spbase.diagonal.__doc__ - ##################### - # Other binary ops # - ##################### - - def _maximum_minimum(self, other, npop, op_name, dense_check): - if isscalarlike(other): - if dense_check(other): - warn("Taking maximum (minimum) with > 0 (< 0) number results" - " to a dense matrix.", SparseEfficiencyWarning, - stacklevel=3) - other_arr = np.empty(self.shape, dtype=np.asarray(other).dtype) - other_arr.fill(other) - other_arr = self.__class__(other_arr) - return self._binopt(other_arr, op_name) - else: - self.sum_duplicates() - new_data = npop(self.data, np.asarray(other)) - mat = self.__class__((new_data, self.indices, self.indptr), - dtype=new_data.dtype, shape=self.shape) - return mat - elif isdense(other): - return npop(self.todense(), other) - elif issparse(other): - return self._binopt(other, op_name) - else: - raise ValueError("Operands not compatible.") - - def maximum(self, other): - return self._maximum_minimum(other, np.maximum, - '_maximum_', lambda x: np.asarray(x) > 0) - - maximum.__doc__ = _spbase.maximum.__doc__ - - def minimum(self, other): - return self._maximum_minimum(other, np.minimum, - '_minimum_', lambda x: np.asarray(x) < 0) - - minimum.__doc__ = _spbase.minimum.__doc__ - ##################### # Reduce operations # ##################### @@ -1390,7 +1217,7 @@ def _divide_sparse(self, other): Divide this matrix by a second sparse matrix. """ if other.shape != self.shape: - raise ValueError('inconsistent shapes') + raise ValueError(f"inconsistent shapes {self.shape} and {other.shape}") r = self._binopt(other, '_eldiv_') diff --git a/scipy/sparse/_coo.py b/scipy/sparse/_coo.py index 834f29397b35..3b85916ef508 100644 --- a/scipy/sparse/_coo.py +++ b/scipy/sparse/_coo.py @@ -298,7 +298,7 @@ def toarray(self, order=None, out=None): M, N = self.shape coo_todense(M, N, self.nnz, self.row, self.col, self.data, B.ravel('A'), fortran) - else: + else: # dim>2 if fortran: strides = np.append(1, np.cumprod(self.shape[:-1])) else: @@ -603,7 +603,6 @@ def _add_sparse(self, other): A = self.__class__((new_data, new_coords), shape=self.shape) return A - def _sub_sparse(self, other): if self.ndim < 3: return self.tocsr()._sub_sparse(other) @@ -616,7 +615,6 @@ def _sub_sparse(self, other): A = coo_array((new_data, new_coords), shape=self.shape) return A - def _matmul_vector(self, other): if self.ndim > 2: result = np.zeros(math.prod(self.shape[:-1]), @@ -650,7 +648,6 @@ def _matmul_vector(self, other): return result[0] return result - def _rmatmul_dispatch(self, other): if isscalarlike(other): return self._mul_scalar(other) @@ -676,7 +673,6 @@ def _rmatmul_dispatch(self, other): perm = tuple(range(ret.ndim)[:-2]) + tuple(range(ret.ndim)[-2:][::-1]) return ret.transpose(perm) - def _matmul_dispatch(self, other): if isscalarlike(other): return self.multiply(other) @@ -689,7 +685,7 @@ def _matmul_dispatch(self, other): # Not interpretable as an array; return NotImplemented so that # other's __rmatmul__ can kick in if that's implemented. return NotImplemented - + # Allow custom sparse class indicated by attr sparse gh-6520 try: other.shape except AttributeError: @@ -726,7 +722,6 @@ def _matmul_dispatch(self, other): f"{err_prefix} (n,..,k={N}),(k={other.shape[-2]},..,m)->(n,..,m)" ) - if isscalarlike(other): # scalar value return self._mul_scalar(other) @@ -771,7 +766,6 @@ def _matmul_dispatch(self, other): result = result.reshape(result.shape[:-1]) return result - def _matmul_multivector(self, other): result_dtype = upcast_char(self.dtype.char, other.dtype.char) if self.ndim >= 3 or other.ndim >= 3: @@ -807,7 +801,6 @@ def _matmul_multivector(self, other): self.data, other.ravel('C'), result) return result.view(type=type(other)) - def dot(self, other): """Return the dot product of two arrays. @@ -861,6 +854,7 @@ def dot(self, other): >>> A.dot(B).shape (3, 4, 5, 5, 4, 3) """ + # handle non-array input: lists, ints, etc if not (issparse(other) or isdense(other) or isscalarlike(other)): # If it's a list or whatever, treat it like an array o_array = np.asanyarray(other) @@ -872,118 +866,68 @@ def dot(self, other): except AttributeError: other = o_array - if self.ndim < 3 and (np.isscalar(other) or other.ndim<3): - return _spbase.dot(self, other) # Handle scalar multiplication - if np.isscalar(other): + if isscalarlike(other): return self * other + + # other.shape[-2:][0] gets last index of 1d, next to last index of >1d + if self.shape[-1] != other.shape[-2:][0]: + raise ValueError(f"shapes {self.shape} and {other.shape}" + " are not aligned for n-D dot") + + if self.ndim < 3 and other.ndim < 3: + return self @ other if isdense(other): return self._dense_dot(other) - elif other.format != "coo": - raise TypeError("input must be a COO matrix/array") - elif self.ndim == 1 and other.ndim == 1: - # Handle inner product of vectors (1-D arrays) - if self.shape[0] != other.shape[0]: - raise ValueError(f"shapes {self.shape} and {other.shape}" - " are not aligned for inner product") - return self @ other - elif self.ndim == 2 and other.ndim == 2: - # Handle matrix multiplication (2-D arrays) - if self.shape[1] != other.shape[0]: - raise ValueError(f"shapes {self.shape} and {other.shape}" - " are not aligned for matmul") - return self @ other - else: - return self._sparse_dot(other) - + return self._sparse_dot(other.tocoo()) def _sparse_dot(self, other): - self_is_1d = self.ndim == 1 - other_is_1d = other.ndim == 1 - - # reshape to 2-D if self or other is 1-D - if self_is_1d: - self = self.reshape(self._shape_as_2d) # prepend 1 to shape - if other_is_1d: - other = other.reshape((other.shape[0], 1)) # append 1 to shape - - if self.shape[-1] != other.shape[-2]: - raise ValueError(f"shapes {self.shape} and {other.shape}" - " are not aligned for n-D dot") - - # Prepare the tensors for dot operation + # already checked: at least one is >2d, neither scalar, both are coo # Ravel non-reduced axes coordinates - self_raveled_coords = _ravel_non_reduced_axes(self.coords, - self.shape, [self.ndim-1]) - other_raveled_coords = _ravel_non_reduced_axes(other.coords, - other.shape, [other.ndim-2]) + self_2d, s_new_shape = _convert_to_2d(self, [self.ndim - 1]) + other_2d, o_new_shape = _convert_to_2d(other, [max(0, other.ndim - 2)]) - # Get the shape of the non-reduced axes - self_nonreduced_shape = self.shape[:-1] - other_nonreduced_shape = other.shape[:-2] + other.shape[-1:] + prod = self_2d @ other_2d.T # routes via 2-D CSR + prod = prod.tocoo() - # Create 2D coords arrays - ravel_coords_shape_self = (math.prod(self_nonreduced_shape), self.shape[-1]) - ravel_coords_shape_other = (other.shape[-2], math.prod(other_nonreduced_shape)) - - self_2d_coords = (self_raveled_coords, self.coords[-1]) - other_2d_coords = (other.coords[-2], other_raveled_coords) - - self_2d = coo_array((self.data, self_2d_coords), ravel_coords_shape_self) - other_2d = coo_array((other.data, other_2d_coords), ravel_coords_shape_other) - - prod = (self_2d @ other_2d).tocoo() # routes via 2-D CSR - - # Combine the shapes of the non-reduced axes - combined_shape = self_nonreduced_shape + other_nonreduced_shape + # Combine the shapes of the non-contracted axes + combined_shape = s_new_shape + o_new_shape # Unravel the 2D coordinates to get multi-dimensional coordinates - shapes = (self_nonreduced_shape, other_nonreduced_shape) - prod_coords = [] - for c, s in zip(prod.coords, shapes): - prod_coords.extend(np.unravel_index(c, s)) - - prod_arr = coo_array((prod.data, prod_coords), combined_shape) - - # reshape back if a or b were originally 1-D - # TODO: Move this logic before computation of prod_coords for efficiency - if self_is_1d: - prod_arr = prod_arr.reshape(combined_shape[1:]) - if other_is_1d: - prod_arr = prod_arr.reshape(combined_shape[:-1]) + coords = [] + new_shapes = (s_new_shape, o_new_shape) if s_new_shape else (o_new_shape,) + for c, s in zip(prod.coords, new_shapes): + coords.extend(np.unravel_index(c, s)) - return prod_arr + # Construct the resulting COO array with coords and shape + return coo_array((prod.data, coords), shape=combined_shape) def _dense_dot(self, other): - self_is_1d = self.ndim == 1 - other_is_1d = other.ndim == 1 - - # reshape to 2-D if self or other is 1-D - if self_is_1d: - self = self.reshape(self._shape_as_2d) # prepend 1 to shape - if other_is_1d: - other = other.reshape((other.shape[0], 1)) # append 1 to shape - - if self.shape[-1] != other.shape[-2]: - raise ValueError(f"shapes {self.shape} and {other.shape}" - " are not aligned for n-D dot") + # already checked: self is >0d, other is dense and >0d + # Ravel non-reduced axes coordinates + s_ndim = self.ndim + if s_ndim <= 2: + s_new_shape = () if s_ndim == 1 else (self.shape[0],) + self_2d = self + else: + self_2d, s_new_shape = _convert_to_2d(self, [self.ndim - 1]) - new_shape_self = ( - self.shape[:-1] + (1,) * (len(other.shape) - 1) + self.shape[-1:] - ) - new_shape_other = (1,) * (len(self.shape) - 1) + other.shape + o_ndim = other.ndim + if o_ndim <= 2: + o_new_shape = () if o_ndim == 1 else (other.shape[-1],) + other_2d = other + else: + o_new_shape = other.shape[:-2] + other.shape[-1:] + reorder_dims = (o_ndim - 2, *range(o_ndim - 2), o_ndim - 1) + o_reorg = np.transpose(other, reorder_dims) + other_2d = o_reorg.reshape((other.shape[-2], math.prod(o_new_shape))) - result_shape = self.shape[:-1] + other.shape[:-2] + other.shape[-1:] - result = self.reshape(new_shape_self) @ other.reshape(new_shape_other) - prod_arr = result.reshape(result_shape) + prod = self_2d @ other_2d # routes via 2-D CSR - # reshape back if a or b were originally 1-D - if self_is_1d: - prod_arr = prod_arr.reshape(result_shape[1:]) - if other_is_1d: - prod_arr = prod_arr.reshape(result_shape[:-1]) + # Combine the shapes of the non-contracted axes + combined_shape = s_new_shape + o_new_shape + return prod.reshape(combined_shape) - return prod_arr def tensordot(self, other, axes=2): """Return the tensordot product with another array along the given axes. @@ -1097,85 +1041,52 @@ def tensordot(self, other, axes=2): else: return self._sparse_tensordot(other, axes_self, axes_other) - - def _sparse_tensordot(self, other, axes_self, axes_other): - ndim_self = len(self.shape) - ndim_other = len(other.shape) - + def _sparse_tensordot(self, other, s_axes, o_axes): # Prepare the tensors for tensordot operation # Ravel non-reduced axes coordinates - self_non_red_coords = _ravel_non_reduced_axes(self.coords, self.shape, - axes_self) - self_reduced_coords = np.ravel_multi_index( - [self.coords[ax] for ax in axes_self], [self.shape[ax] for ax in axes_self]) - other_non_red_coords = _ravel_non_reduced_axes(other.coords, other.shape, - axes_other) - other_reduced_coords = np.ravel_multi_index( - [other.coords[a] for a in axes_other], [other.shape[a] for a in axes_other] - ) - # Get the shape of the non-reduced axes - self_nonreduced_shape = tuple(self.shape[ax] for ax in range(ndim_self) - if ax not in axes_self) - other_nonreduced_shape = tuple(other.shape[ax] for ax in range(ndim_other) - if ax not in axes_other) - - # Create 2D coords arrays - ravel_coords_shape_self = (math.prod(self_nonreduced_shape), - math.prod([self.shape[ax] for ax in axes_self])) - ravel_coords_shape_other = (math.prod([other.shape[ax] for ax in axes_other]), - math.prod(other_nonreduced_shape)) - - self_2d_coords = (self_non_red_coords, self_reduced_coords) - other_2d_coords = (other_reduced_coords, other_non_red_coords) - - self_2d = coo_array((self.data, self_2d_coords), ravel_coords_shape_self) - other_2d = coo_array((other.data, other_2d_coords), ravel_coords_shape_other) + self_2d, s_new_shape = _convert_to_2d(self, s_axes) + other_2d, o_new_shape = _convert_to_2d(other, o_axes) # Perform matrix multiplication (routed via 2-D CSR) - prod = (self_2d @ other_2d).tocoo() + prod = self_2d @ other_2d.T + # handle case of scalar result (axis includes all axes for both) + if not issparse(prod): + return prod + prod = prod.tocoo() # Combine the shapes of the non-contracted axes - combined_shape = self_nonreduced_shape + other_nonreduced_shape + combined_shape = s_new_shape + o_new_shape # Unravel the 2D coordinates to get multi-dimensional coordinates coords = [] - for c, s in zip(prod.coords, (self_nonreduced_shape, other_nonreduced_shape)): + new_shapes = (s_new_shape, o_new_shape) if s_new_shape else (o_new_shape,) + for c, s in zip(prod.coords, new_shapes): if s: coords.extend(np.unravel_index(c, s)) - if coords == []: # if result is scalar - return sum(prod.data) - - # Construct the resulting COO array with combined coordinates and shape + # Construct the resulting COO array with coords and shape return coo_array((prod.data, coords), shape=combined_shape) + def _dense_tensordot(self, other, s_axes, o_axes): + s_ndim = len(self.shape) + o_ndim = len(other.shape) - def _dense_tensordot(self, other, axes_self, axes_other): - ndim_self = len(self.shape) - ndim_other = len(other.shape) + s_non_axes = [i for i in range(s_ndim) if i not in s_axes] + s_axes_shape = [self.shape[i] for i in s_axes] + s_non_axes_shape = [self.shape[i] for i in s_non_axes] - non_reduced_axes_self = [ax for ax in range(ndim_self) if ax not in axes_self] - reduced_shape_self = [self.shape[s] for s in axes_self] - non_reduced_shape_self = [self.shape[s] for s in non_reduced_axes_self] + o_non_axes = [i for i in range(o_ndim) if i not in o_axes] + o_axes_shape = [other.shape[i] for i in o_axes] + o_non_axes_shape = [other.shape[i] for i in o_non_axes] - non_reduced_axes_other = [ax for ax in range(ndim_other) - if ax not in axes_other] - reduced_shape_other = [other.shape[s] for s in axes_other] - non_reduced_shape_other = [other.shape[s] for s in non_reduced_axes_other] - - permute_self = non_reduced_axes_self + axes_self - permute_other = ( - non_reduced_axes_other[:-1] + axes_other + non_reduced_axes_other[-1:] - ) - self = self.transpose(permute_self) - other = np.transpose(other, permute_other) + left = self.transpose(s_non_axes + s_axes) + right = np.transpose(other, o_non_axes[:-1] + o_axes + o_non_axes[-1:]) - reshape_self = (*non_reduced_shape_self, math.prod(reduced_shape_self)) - reshape_other = (*non_reduced_shape_other[:-1], math.prod(reduced_shape_other), - *non_reduced_shape_other[-1:]) - - return self.reshape(reshape_self).dot(other.reshape(reshape_other)) + reshape_left = (*s_non_axes_shape, math.prod(s_axes_shape)) + reshape_right = (*o_non_axes_shape[:-1], math.prod(o_axes_shape), + *o_non_axes_shape[-1:]) + return left.reshape(reshape_left).dot(right.reshape(reshape_right)) def _matmul_sparse(self, other): """ @@ -1219,7 +1130,6 @@ def _matmul_sparse(self, other): shape=(*broadcast_shape, self.shape[-2], other.shape[-1]), ) - def _broadcast_to(self, new_shape, copy=False): if self.shape == new_shape: return self.copy() if copy else self @@ -1275,6 +1185,26 @@ def _broadcast_to(self, new_shape, copy=False): return coo_array((new_data, new_coords), new_shape) + def _sum_nd(self, axis, res_dtype, out): + # axis and out are preprocessed. out.shape is new_shape + A2d, new_shape = _convert_to_2d(self, axis) + ones = np.ones((A2d.shape[1], 1), dtype=res_dtype) + # sets dtype while loading into out + out[...] = (A2d @ ones).reshape(new_shape) + return out + + def _min_or_max_axis_nd(self, axis, min_or_max, explicit): + A2d, new_shape = _convert_to_2d(self, axis) + res = A2d._min_or_max_axis(1, min_or_max, explicit) + unraveled_coords = np.unravel_index(res.coords[0], new_shape) + + return coo_array((res.data, unraveled_coords), new_shape) + + def _argminmax_axis_nd(self, axis, argminmax, compare, explicit): + A2d, new_shape = _convert_to_2d(self, axis) + res_flat = A2d._argminmax_axis(1, argminmax, compare, explicit) + return res_flat.reshape(new_shape) + def _block_diag(self): """ @@ -1349,22 +1279,26 @@ def _process_axes(ndim_a, ndim_b, axes): return axes_a, axes_b -def _ravel_non_reduced_axes(coords, shape, axes): - ndim = len(shape) - non_reduced_axes = [ax for ax in range(ndim) if ax not in axes] - - if not non_reduced_axes: - # Return an array with one row - return np.zeros_like(coords[0]) - - # Extract the shape of the non-reduced axes - non_reduced_shape = [shape[ax] for ax in non_reduced_axes] - - # Extract the coordinates of the non-reduced axes - non_reduced_coords = tuple(coords[idx] for idx in non_reduced_axes) - - # Ravel the coordinates into 1D - return np.ravel_multi_index(non_reduced_coords, non_reduced_shape) +def _convert_to_2d(coo, axis): + axis_coords = tuple(coo.coords[i] for i in axis) + axis_shape = tuple(coo.shape[i] for i in axis) + axis_ravel = _ravel_coords(axis_coords, axis_shape) + + ndim = len(coo.coords) + non_axis = tuple(i for i in range(ndim) if i not in axis) + if non_axis: + non_axis_coords = tuple(coo.coords[i] for i in non_axis) + non_axis_shape = tuple(coo.shape[i] for i in non_axis) + non_axis_ravel = _ravel_coords(non_axis_coords, non_axis_shape) + coords_2d = (non_axis_ravel, axis_ravel) + shape_2d = (math.prod(non_axis_shape), math.prod(axis_shape)) + else: # all axes included in axis so result will have 1 element + coords_2d = (axis_ravel,) + shape_2d = (math.prod(axis_shape),) + non_axis_shape = () + + new_coo = coo_array((coo.data, coords_2d), shape=shape_2d) + return new_coo, non_axis_shape def _ravel_coords(coords, shape, order='C'): diff --git a/scipy/sparse/_data.py b/scipy/sparse/_data.py index 420f44c44bcd..e6f876b69b7f 100644 --- a/scipy/sparse/_data.py +++ b/scipy/sparse/_data.py @@ -124,7 +124,7 @@ def power(self, n, dtype=None): data = self._deduped_data() if dtype is not None: - data = data.astype(dtype) + data = data.astype(dtype, copy=False) return self._with_data(data ** n) ########################### @@ -172,9 +172,8 @@ class _minmax_mixin: """ def _min_or_max_axis(self, axis, min_or_max, explicit): + # already checked that self.shape[axis] is not zero N = self.shape[axis] - if N == 0: - raise ValueError("zero-size array to reduction operation") M = self.shape[1 - axis] idx_dtype = self._get_index_dtype(maxval=M) @@ -208,7 +207,7 @@ def _min_or_max_axis(self, axis, min_or_max, explicit): def _min_or_max(self, axis, out, min_or_max, explicit): if out is not None: - raise ValueError("Sparse arrays do not support an 'out' parameter.") + raise ValueError("Sparse min/max does not support an 'out' parameter.") axis = validateaxis(axis, ndim=self.ndim) @@ -224,12 +223,15 @@ def _min_or_max(self, axis, out, min_or_max, explicit): m = min_or_max(zero, m) return m - return self._min_or_max_axis(axis, min_or_max, explicit) + if any(self.shape[d] == 0 for d in axis): + raise ValueError("zero-size array to reduction operation") - def _arg_min_or_max_axis(self, axis, argmin_or_argmax, compare, explicit): - if self.shape[axis] == 0: - raise ValueError("Cannot apply the operation along a zero-sized dimension.") + if self.ndim == 2: + # note: 2D ensures that len(axis)==1 so we pass in the int axis[0] + return self._min_or_max_axis(axis[0], min_or_max, explicit) + return self._min_or_max_axis_nd(axis, min_or_max, explicit) + def _argminmax_axis(self, axis, argminmax, compare, explicit): zero = self.dtype.type(0) mat = self.tocsc() if axis == 0 else self.tocsr() @@ -243,7 +245,7 @@ def _arg_min_or_max_axis(self, axis, argmin_or_argmax, compare, explicit): p, q = mat.indptr[i:i + 2] data = mat.data[p:q] indices = mat.indices[p:q] - extreme_index = argmin_or_argmax(data) + extreme_index = argminmax(data) extreme_value = data[extreme_index] if explicit: if q - p > 0: @@ -266,21 +268,31 @@ def _arg_min_or_max_axis(self, axis, argmin_or_argmax, compare, explicit): return self._ascontainer(ret) - def _arg_min_or_max(self, axis, out, argmin_or_argmax, compare, explicit): + def _argminmax(self, axis, out, argminmax, compare, explicit): if out is not None: - raise ValueError("Sparse types do not support an 'out' parameter.") + minmax = "argmin" if argminmax == np.argmin else "argmax" + raise ValueError(f"Sparse {minmax} does not support an 'out' parameter.") axis = validateaxis(axis, ndim=self.ndim) if axis is not None: - return self._arg_min_or_max_axis(axis, argmin_or_argmax, compare, explicit) + if any(self.shape[i] == 0 for i in axis): + minmax = "argmin" if argminmax == np.argmin else "argmax" + raise ValueError(f"Cannot apply {minmax} along a zero-sized dimension.") + + if self.ndim == 2: + # note: 2D ensures that len(axis)==1 so we pass in the int axis[0] + return self._argminmax_axis(axis[0], argminmax, compare, explicit) + return self._argminmax_axis_nd(axis, argminmax, compare, explicit) if 0 in self.shape: - raise ValueError("Cannot apply the operation to an empty matrix.") + minmax = "argmin" if argminmax == np.argmin else "argmax" + raise ValueError(f"Cannot apply {minmax} to an empty matrix.") if self.nnz == 0: if explicit: - raise ValueError("Cannot apply the operation to zero matrix " + minmax = "argmin" if argminmax == np.argmin else "argmax" + raise ValueError(f"Cannot apply {minmax} to zero matrix " "when explicit=True.") return 0 @@ -288,28 +300,34 @@ def _arg_min_or_max(self, axis, out, argmin_or_argmax, compare, explicit): mat = self.tocoo() # Convert to canonical form: no duplicates, sorted indices. mat.sum_duplicates() - extreme_index = argmin_or_argmax(mat.data) + extreme_index = argminmax(mat.data) if explicit: return extreme_index extreme_value = mat.data[extreme_index] - num_col = mat.shape[-1] + if mat.ndim > 2: + mat = mat.reshape(-1) # If the min value is less than zero, or max is greater than zero, # then we do not need to worry about implicit zeros. - if compare(extreme_value, zero): + # And we use a "cheap test" for the rare case of no implicit zeros. + maxnnz = math.prod(self.shape) + if compare(extreme_value, zero) or mat.nnz == maxnnz: # cast to Python int to avoid overflow and RuntimeError - return int(mat.row[extreme_index]) * num_col + int(mat.col[extreme_index]) - - # Cheap test for the rare case where we have no implicit zeros. - size = math.prod(self.shape) - if size == mat.nnz: + if mat.ndim == 1: # includes nD case that was reshaped above + return int(mat.col[extreme_index]) + # ndim == 2 + num_col = mat.shape[-1] return int(mat.row[extreme_index]) * num_col + int(mat.col[extreme_index]) # At this stage, any implicit zero could be the min or max value. # After sum_duplicates(), the `row` and `col` arrays are guaranteed to # be sorted in C-order, which means the linearized indices are sorted. - linear_indices = mat.row * num_col + mat.col - first_implicit_zero_index = _find_missing_index(linear_indices, size) + if mat.ndim == 1: # includes nD case that was reshaped above + linear_indices = mat.coords[-1] + else: # ndim == 2 + num_col = mat.shape[-1] + linear_indices = mat.row * num_col + mat.col + first_implicit_zero_index = _find_missing_index(linear_indices, maxnnz) if extreme_value == zero: return min(first_implicit_zero_index, extreme_index) return first_implicit_zero_index @@ -506,7 +524,7 @@ def argmax(self, axis=None, out=None, *, explicit=False): explicit : {False, True} optional (default: False) When set to True, only explicitly stored elements will be considered. - If axis is not None and a row/column has no stored elements, argmax + If axis is not None and an axis has no stored elements, argmax is undefined, so the index ``0`` is returned for that row/column. .. versionadded:: 1.15.0 @@ -516,7 +534,7 @@ def argmax(self, axis=None, out=None, *, explicit=False): ind : numpy.matrix or int Indices of maximum elements. If matrix, its size along `axis` is 1. """ - return self._arg_min_or_max(axis, out, np.argmax, np.greater, explicit) + return self._argminmax(axis, out, np.argmax, np.greater, explicit) def argmin(self, axis=None, out=None, *, explicit=False): """Return indices of minimum elements along an axis. @@ -538,7 +556,7 @@ def argmin(self, axis=None, out=None, *, explicit=False): explicit : {False, True} optional (default: False) When set to True, only explicitly stored elements will be considered. - If axis is not None and a row/column has no stored elements, argmin + If axis is not None and an axis has no stored elements, argmin is undefined, so the index ``0`` is returned for that row/column. .. versionadded:: 1.15.0 @@ -548,4 +566,4 @@ def argmin(self, axis=None, out=None, *, explicit=False): ind : numpy.matrix or int Indices of minimum elements. If matrix, its size along `axis` is 1. """ - return self._arg_min_or_max(axis, out, np.argmin, np.less, explicit) + return self._argminmax(axis, out, np.argmin, np.less, explicit) diff --git a/scipy/sparse/_dia.py b/scipy/sparse/_dia.py index e5508f9c5c8e..86474618f591 100644 --- a/scipy/sparse/_dia.py +++ b/scipy/sparse/_dia.py @@ -11,9 +11,10 @@ from ._base import issparse, _formats, _spbase, sparray from ._data import _data_matrix from ._sputils import ( - isshape, upcast_char, getdtype, get_sum_dtype, validateaxis, check_shape + isdense, isscalarlike, isshape, upcast_char, getdtype, get_sum_dtype, + validateaxis, check_shape ) -from ._sparsetools import dia_matvec +from ._sparsetools import dia_matmat, dia_matvec, dia_matvecs class _dia_base(_data_matrix): @@ -148,7 +149,7 @@ def sum(self, axis=None, dtype=None, out=None): num_rows, num_cols = self.shape ret = None - if axis == 0: + if axis == (0,): mask = self._data_mask() x = (self.data * mask).sum(axis=0) if x.shape[0] == num_cols: @@ -158,7 +159,7 @@ def sum(self, axis=None, dtype=None, out=None): res[:x.shape[0]] = x ret = self._ascontainer(res, dtype=res_dtype) - else: + else: # axis is None or (1,) row_sums = np.zeros((num_rows, 1), dtype=res_dtype) one = np.ones(num_cols, dtype=res_dtype) dia_matvec(num_rows, num_cols, len(self.offsets), @@ -175,14 +176,15 @@ def sum(self, axis=None, dtype=None, out=None): sum.__doc__ = _spbase.sum.__doc__ - def _add_sparse(self, other): + def _add_sparse(self, other, sub=False): # If other is not DIA format, let them handle us instead. if not isinstance(other, _dia_base): return other._add_sparse(self) # Fast path for exact equality of the sparsity structure. if np.array_equal(self.offsets, other.offsets): - return self._with_data(self.data + other.data) + return self._with_data(self.data - other.data if sub else + self.data + other.data) # Find the union of the offsets (which will be sorted and unique). new_offsets = np.union1d(self.offsets, other.offsets) @@ -195,26 +197,91 @@ def _add_sparse(self, other): # permutation of the existing offsets and the diagonal lengths match. if self_d == other_d and len(new_offsets) == len(self.offsets): new_data = self.data[_invert_index(self_idx)] - new_data[other_idx, :] += other.data + if sub: + new_data[other_idx, :] -= other.data + else: + new_data[other_idx, :] += other.data elif self_d == other_d and len(new_offsets) == len(other.offsets): - new_data = other.data[_invert_index(other_idx)] + if sub: + new_data = -other.data[_invert_index(other_idx)] + else: + new_data = other.data[_invert_index(other_idx)] new_data[self_idx, :] += self.data else: # Maximum diagonal length of the result. d = min(self.shape[0] + new_offsets[-1], self.shape[1]) - # Add all diagonals to a freshly-allocated data array. + # Add all diagonals to a freshly allocated data array. new_data = np.zeros( (len(new_offsets), d), dtype=np.result_type(self.data, other.data), ) new_data[self_idx, :self_d] += self.data[:, :d] - new_data[other_idx, :other_d] += other.data[:, :d] + if sub: + new_data[other_idx, :other_d] -= other.data[:, :d] + else: + new_data[other_idx, :other_d] += other.data[:, :d] return self._dia_container((new_data, new_offsets), shape=self.shape) + def _sub_sparse(self, other): + # If other is not DIA format, use default handler. + if not isinstance(other, _dia_base): + return super()._sub_sparse(other) + + return self._add_sparse(other, sub=True) + def _mul_scalar(self, other): return self._with_data(self.data * other) + def multiply(self, other): + if isscalarlike(other): + return self._mul_scalar(other) + + if isdense(other): + if other.ndim > 2: + return self.toarray() * other + + # Use default handler for pathological cases. + if 0 in self.shape or 1 in self.shape or 0 in other.shape: + return super().multiply(other) + + other = np.atleast_2d(other) + other_rows, other_cols = other.shape + rows, cols = self.shape + L = min(self.data.shape[1], cols) + data = self.data[:, :L].astype(np.result_type(self.data, other)) + if other_rows == 1: + data *= other[0, :L] + elif other_rows != rows: + raise ValueError('inconsistent shapes') + else: + j = np.arange(L) + if L > rows: + i = (j - self.offsets[:, None]) % rows + else: # can use faster method + i = j - self.offsets[:, None] % rows + if other_cols == 1: + j = 0 + elif other_cols != cols: + raise ValueError('inconsistent shapes') + data *= other[i, j] + return self._with_data(data) + + # If other is not DIA format or needs broadcasting (unreasonable + # use case for DIA anyway), use default handler. + if not isinstance(other, _dia_base) or other.shape != self.shape: + return super().multiply(other) + + # Find common offsets (unique diagonals don't contribute) + # and indices corresponding to them in multiplicand and multiplier. + offsets, self_idx, other_idx = \ + np.intersect1d(self.offsets, other.offsets, + assume_unique=True, return_indices=True) + # Only overlapping length of diagonals can have non-zero products. + L = min(self.data.shape[1], other.data.shape[1]) + data = self.data[self_idx, :L] * other.data[other_idx, :L] + return self._dia_container((data, offsets), shape=self.shape) + def _matmul_vector(self, other): x = other @@ -230,6 +297,29 @@ def _matmul_vector(self, other): return y + def _matmul_multivector(self, other): + res = np.zeros((self.shape[0], other.shape[1]), + dtype=np.result_type(self.data, other)) + dia_matvecs(*self.shape, *self.data.shape, self.offsets, self.data, + other.shape[1], other, res) + return res + + def _matmul_sparse(self, other): + # If other is not DIA format, use default handler. + if not isinstance(other, _dia_base): + return super()._matmul_sparse(other) + + # If any dimension is zero, return empty array immediately. + if 0 in self.shape or 0 in other.shape: + return self._dia_container((self.shape[0], other.shape[1])) + + offsets, data = dia_matmat(*self.shape, *self.data.shape, + self.offsets, self.data, + other.shape[1], *other.data.shape, + other.offsets, other.data) + return self._dia_container((data.reshape(len(offsets), -1), offsets), + (self.shape[0], other.shape[1])) + def _setdiag(self, values, k=0): M, N = self.shape diff --git a/scipy/sparse/_generate_sparsetools.py b/scipy/sparse/_generate_sparsetools.py index 4300d77bf552..423a1c2dc5e5 100644 --- a/scipy/sparse/_generate_sparsetools.py +++ b/scipy/sparse/_generate_sparsetools.py @@ -107,10 +107,12 @@ coo_todense v iilIIT*Ti coo_todense_nd v IllIT*Ti coo_matvec v lIITT*T +dia_matmat v iiiiITiiiIT*V*W coo_matvec_nd v llIITT*T coo_matmat_dense v llIITT*T coo_matmat_dense_nd v lllIIITT*T dia_matvec v iiiiITT*T +dia_matvecs v iiiiITiT*T cs_graph_components i iII*I """ diff --git a/scipy/sparse/_sputils.py b/scipy/sparse/_sputils.py index a200724e951e..326c68bab785 100644 --- a/scipy/sparse/_sputils.py +++ b/scipy/sparse/_sputils.py @@ -403,7 +403,7 @@ def isdense(x) -> bool: return isinstance(x, np.ndarray) -def validateaxis(axis, *, ndim=2) -> int | tuple[int, ...]: +def validateaxis(axis, *, ndim=2) -> tuple[int, ...] | None: if axis is None: return None @@ -437,8 +437,6 @@ def validateaxis(axis, *, ndim=2) -> int | tuple[int, ...]: raise ValueError("axis tuple has too many elements") elif len_axis == ndim: return None - elif len_axis == 1: - return canon_axis[0] else: return tuple(canon_axis) diff --git a/scipy/sparse/linalg/__init__.py b/scipy/sparse/linalg/__init__.py index ae19314d48b3..e32dd362f8d4 100644 --- a/scipy/sparse/linalg/__init__.py +++ b/scipy/sparse/linalg/__init__.py @@ -22,6 +22,7 @@ inv -- compute the sparse matrix inverse expm -- compute the sparse matrix exponential expm_multiply -- compute the product of a matrix exponential and a matrix + funm_multiply_krylov -- use a Krylov method to compute f(A)b for a general f matrix_power -- compute the matrix power by raising a matrix to an exponent Matrix norms @@ -136,6 +137,7 @@ from ._onenormest import * from ._norm import * from ._expm_multiply import * +from ._funm_multiply_krylov import * from ._special_sparse_arrays import * # Deprecated namespaces, to be removed in v2.0.0 diff --git a/scipy/sparse/linalg/_dsolve/_superlu_utils.c b/scipy/sparse/linalg/_dsolve/_superlu_utils.c index 63951b7f33f1..ea7e1bc70182 100644 --- a/scipy/sparse/linalg/_dsolve/_superlu_utils.c +++ b/scipy/sparse/linalg/_dsolve/_superlu_utils.c @@ -42,6 +42,11 @@ static SuperLUGlobalObject *get_tls_global(void) PyDict_SetItemString(thread_dict, key, (PyObject *)obj); + /* + Py_DECREF is added because get_tls_global returns + borrowed reference. This avoids memory leak of obj. + */ + Py_DECREF(obj); return obj; } diff --git a/scipy/sparse/linalg/_dsolve/_superluobject.c b/scipy/sparse/linalg/_dsolve/_superluobject.c index 6d07473e7f19..9656ec18d204 100644 --- a/scipy/sparse/linalg/_dsolve/_superluobject.c +++ b/scipy/sparse/linalg/_dsolve/_superluobject.c @@ -915,20 +915,11 @@ static int trans_cvt(PyObject * input, trans_t * value) { ENUM_CHECK_INIT; ENUM_CHECK(NOTRANS); + ENUM_CHECK_NAME(NOTRANS, "N"); ENUM_CHECK(TRANS); + ENUM_CHECK_NAME(TRANS, "T"); ENUM_CHECK(CONJ); - if (my_strxcmp(s, "N") == 0) { - *value = NOTRANS; - return 1; - } - if (my_strxcmp(s, "T") == 0) { - *value = TRANS; - return 1; - } - if (my_strxcmp(s, "H") == 0) { - *value = CONJ; - return 1; - } + ENUM_CHECK_NAME(CONJ, "H"); ENUM_CHECK_FINISH("invalid value for 'Trans' parameter"); } @@ -967,34 +958,13 @@ static int milu_cvt(PyObject * input, milu_t * value) static int droprule_one_cvt(PyObject * input, int *value) { ENUM_CHECK_INIT; - if (my_strxcmp(s, "BASIC") == 0) { - *value = DROP_BASIC; - return 1; - } - if (my_strxcmp(s, "PROWS") == 0) { - *value = DROP_PROWS; - return 1; - } - if (my_strxcmp(s, "COLUMN") == 0) { - *value = DROP_COLUMN; - return 1; - } - if (my_strxcmp(s, "AREA") == 0) { - *value = DROP_AREA; - return 1; - } - if (my_strxcmp(s, "SECONDARY") == 0) { - *value = DROP_SECONDARY; - return 1; - } - if (my_strxcmp(s, "DYNAMIC") == 0) { - *value = DROP_DYNAMIC; - return 1; - } - if (my_strxcmp(s, "INTERP") == 0) { - *value = DROP_INTERP; - return 1; - } + ENUM_CHECK_NAME(DROP_BASIC, "BASIC"); + ENUM_CHECK_NAME(DROP_PROWS, "PROWS"); + ENUM_CHECK_NAME(DROP_COLUMN, "COLUMN"); + ENUM_CHECK_NAME(DROP_AREA, "AREA"); + ENUM_CHECK_NAME(DROP_SECONDARY, "SECONDARY"); + ENUM_CHECK_NAME(DROP_DYNAMIC, "DYNAMIC"); + ENUM_CHECK_NAME(DROP_INTERP, "INTERP"); ENUM_CHECK_FINISH("invalid value for 'ILU_DropRule' parameter"); } diff --git a/scipy/sparse/linalg/_funm_multiply_krylov.py b/scipy/sparse/linalg/_funm_multiply_krylov.py new file mode 100644 index 000000000000..c0fb2e1aa5ae --- /dev/null +++ b/scipy/sparse/linalg/_funm_multiply_krylov.py @@ -0,0 +1,348 @@ +""" + Restared Krylov method for evaluating f(A)b + + The original code was written in MATLAB by Stefan Guttel. + It was later adapted to C++ and Python by Nicolas Guidotti. + Both authors agrees to relicense the code under the BSD license. + + Copyright (C) 2025 Nicolas Guidotti and Stefan Guttel. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. 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. + + 3. 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 COPYRIGHT HOLDER 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. + +""" + +import numpy as np +from scipy.linalg import norm +from ._isolve.iterative import _get_atol_rtol + +__all__ = ['funm_multiply_krylov'] + +def _funm_multiply_krylov_arnoldi(A, b, bnorm, V, H, m): + """ + The Arnoldi iteration for constructing the basis V and the projection H = V * A V + for the Krylov subspace Km(A, b) of order m. + + Parameters + ---------- + A : transposable linear operator + The operator whose matrix function is of interest. + b : ndarray + The vector b to multiply the f(A) with. + V : ndarray + The n x (m + 1) matrix whose columns determines the basis for + Krylov subspace Km(A, b). + H : ndarray + A (m + 1) x m upper Hessenberg matrix representing the projection of A + onto Km(A, b). + m : int + The order of the Krylov subspace. + + Returns + ------- + breakdown : bool + Indicate if the Arnoldi broke down or not + + iter : int + Returns the last valid iteration. + + """ + + dotprod = np.vdot if np.iscomplexobj(b) else np.dot + norm_tol = np.finfo(b.dtype.char).eps ** 2 + V[:, 0] = b / bnorm + + for k in range(0, m): + V[:, k + 1] = A.dot(V[:, k]) + + # Uses the modified Gram-Schmift process to orthogonalize V[:, k + 1] + # against the previous basis vectors + for i in range(0, k + 1): + H[i, k] = dotprod(V[:, i], V[:, k + 1]) + V[:, k + 1] = V[:, k + 1] - H[i, k] * V[:, i] + + H[k + 1, k] = norm(V[:, k + 1]) + if H[k + 1, k] < norm_tol: + return True, k + + V[:, k + 1] = V[:, k + 1] / H[k + 1, k] + + return False, m + +def _funm_multiply_krylov_lanczos(A, b, bnorm, V, H, m): + """ + The Lanczos iteration for constructing the basis V and the projection H = V * A V + for the Krylov subspace Km(A, b) of order m. A must be Hermitian. + + Parameters + ---------- + A : transposable linear operator + The operator whose matrix function is of interest. + b : ndarray + The vector b to multiply the f(A) with. + V : ndarray + The n x (m + 1) matrix whose columns determines the basis for + Krylov subspace Km(A, b). + H : ndarray + A (m + 1) x m upper Hessenberg matrix representing the projection of A + onto Km(A, b). + m : int + The order of the Krylov subspace. + + Returns + ------- + breakdown : bool + Indicate if the Arnoldi broke down or not + + iter : int + Returns the last valid iteration. + + """ + dotprod = np.vdot if np.iscomplexobj(b) else np.dot + norm_tol = np.finfo(b.dtype.char).eps ** 2 + V[:, 0] = b / bnorm + + for k in range(0, m): + if k > 0: + V[:, k + 1] = A.dot(V[:, k]) - H[k, k - 1] * V[:, k - 1] + else: + V[:, k + 1] = A.dot(V[:, k]) + + H[k, k] = dotprod(V[:, k + 1], V[:, k]) + V[:, k + 1] = V[:, k + 1] - H[k, k] * V[:, k] + + H[k + 1, k] = norm(V[:, k + 1]) + + if H[k + 1, k] < norm_tol: + return True, k + + V[:, k + 1] = V[:, k + 1] / H[k + 1, k] + if k < m - 1: + H[k, k + 1] = H[k + 1, k] + + return False, m + + +def funm_multiply_krylov(f, A, b, *, assume_a = "general", t = 1.0, atol = 0.0, + rtol = 1e-6, restart_every_m = None, max_restarts = 20): + """ + A restarted Krylov method for evaluating ``y = f(tA) b`` from [1]_ [2]_. + + Parameters + ---------- + f : callable + Callable object that computes the matrix function ``F = f(X)``. + + A : {sparse array, ndarray, LinearOperator} + A real or complex N-by-N matrix. + Alternatively, `A` can be a linear operator which can + produce ``Ax`` using, e.g., ``scipy.sparse.linalg.LinearOperator``. + + b : ndarray + A vector to multiply the ``f(tA)`` with. + + assume_a : string, optional + Indicate the structure of ``A``. The algorithm will use this information + to select the appropriated code path. The available options are + 'hermitian'/'her' and 'general'/'gen'. If ommited, then it is assumed + that ``A`` has a 'general' structure. + + t : float, optional + The value to scale the matrix ``A`` with. The default is ``t = 1.0`` + + atol, rtol : float, optional + Parameters for the convergence test. For convergence, + ``norm(||y_k - y_k-1||) <= max(rtol*norm(b), atol)`` should be satisfied. + The default is ``atol=0.`` and ``rtol=1e-6``. + + restart_every_m : integer + If the iteration number reaches this value a restart is triggered. + Larger values increase iteration cost but may be necessary for convergence. + If omitted, ``min(20, n)`` is used. + + max_restarts : int, optional + Maximum number of restart cycles. The algorithm will stop + after max_restarts cycles even if the specified tolerance has not been + achieved. The default is ``max_restarts=20`` + + Returns + ------- + y : ndarray + The result of ``f(tA) b``. + + Notes + ----- + The convergence of the Krylov method heavily depends on the spectrum + of ``A`` and the function ``f``. With restarting, there are only formal + proofs for functions of order 1 (e.g., ``exp``, ``sin``, ``cos``) and + Stieltjes functions [2]_ [3]_, while the general case remains an open problem. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csr_array + >>> from scipy.sparse.linalg import funm_multiply_krylov + >>> from scipy.linalg import expm, solve + >>> A = csr_array([[3, 2, 0], [1, -1, 0], [0, 5, 1]], dtype=float) + >>> b = np.array([2, 4, -1], dtype=float) + >>> t = 0.1 + + Compute ``y = exp(tA) b``. + + >>> y = funm_multiply_krylov(expm, A, b, t = t) + >>> y + array([3.6164913 , 3.88421511 , 0.96073457]) + + >>> ref = expm(t * A.todense()) @ b + >>> err = y - ref + >>> err + array([4.44089210e-16 , 0.00000000e+00 , 2.22044605e-16]) + + Compute :math:`y = (A^3 - A) b`. + + >>> poly = lambda X : X @ X @ X - X + >>> y = funm_multiply_krylov(poly, A, b) + >>> y + array([132. , 24. , 70.]) + + >>> ref = poly(A.todense()) @ b + >>> err = y - ref + >>> err + array([ 0.00000000e+00 , 7.10542736e-15 , -2.84217094e-14]) + + Compute :math:`y = f(tA) b`, where :math:`f(X) = X^{-1}(e^{X} - I)`. This is + known as the "phi function" from the exponential integrator literature. + + >>> phim_1 = lambda X : solve(X, expm(X) - np.eye(X.shape[0])) + >>> y = funm_multiply_krylov(phim_1, A, b, t = t) + >>> y + array([ 2.76984306 , 3.92769192 , -0.03111392]) + + >>> ref = phim_1(t * A.todense()) @ b + >>> err = y - ref + >>> err + array([ 0.00000000e+00 , 8.88178420e-16 , -4.60742555e-15]) + + References + ---------- + .. [1] M. Afanasjew, M. Eiermann, O. G. Ernst, and S. Güttel, + "Implementation of a restarted Krylov subspace method for the + evaluation of matrix functions," Linear Algebra and its Applications, + vol. 429, no. 10, pp. 2293-2314, Nov. 2008, :doi:`10.1016/j.laa.2008.06.029`. + + .. [2] M. Eiermann and O. G. Ernst, "A Restarted Krylov Subspace Method + for the Evaluation of Matrix Functions," SIAM J. Numer. Anal., vol. 44, + no. 6, pp. 2481-2504, Jan. 2006, :doi:`10.1137/050633846`. + + .. [3] A. Frommer, S. Güttel, and M. Schweitzer, "Convergence of Restarted + Krylov Subspace Methods for Stieltjes Functions of Matrices," SIAM J. + Matrix Anal. Appl., vol. 35, no. 4, pp. 1602-1624, + Jan. 2014, :doi:`10.1137/140973463`. + + """ + + if assume_a not in {'hermitian', 'general', 'her', 'gen'}: + raise ValueError(f'scipy.sparse.linalg.funm_multiply_krylov: {assume_a} ' + 'is not a recognized matrix structure') + is_hermitian = (assume_a == 'her') or (assume_a == 'hermitian') + + if len(b.shape) != 1: + raise ValueError("scipy.sparse.linalg.funm_multiply_krylov: " + "argument 'b' must be a 1D array.") + n = b.shape[0] + + if restart_every_m is None: + restart_every_m = min(20, n) + + restart_every_m = int(restart_every_m) + max_restarts = int(max_restarts) + + if restart_every_m <= 0: + raise ValueError("scipy.sparse.linalg.funm_multiply_krylov: " + "argument 'restart_every_m' must be positive.") + + if max_restarts <= 0: + raise ValueError("scipy.sparse.linalg.funm_multiply_krylov: " + "argument 'max_restarts' must be positive.") + + m = restart_every_m + max_restarts = min(max_restarts, int(n / m) + 1) + mmax = m * max_restarts + + bnorm = norm(b) + atol, _ = _get_atol_rtol("funm_multiply_krylov", bnorm, atol, rtol) + + if bnorm == 0: + y = np.array(b) + return y + + # Preallocate the maximum memory space. + # Using the column major order here since we work with + # each individual column separately. + internal_type = np.common_type(A, b) + V = np.zeros((n, m + 1), dtype = internal_type, order = 'F') + H = np.zeros((mmax + 1, mmax), dtype = internal_type, order = 'F') + + restart = 1 + + if is_hermitian: + breakdown, j = _funm_multiply_krylov_lanczos(A, b, bnorm, V, + H[:m + 1, :m], m) + else: + breakdown, j = _funm_multiply_krylov_arnoldi(A, b, bnorm, V, + H[:m + 1, :m], m) + + fH = f(t * H[:j, :j]) + y = bnorm * V[:, :j].dot(fH[:, 0]) + + if breakdown: + return y + + update_norm = norm(bnorm * fH[:, 0]) + + while restart < max_restarts and update_norm > atol: + begin = restart * m + end = (restart + 1) * m + + if is_hermitian: + breakdown, j = _funm_multiply_krylov_lanczos(A, V[:, m], 1, V, + H[begin:end + 1, begin:end], m) + else: + breakdown, j = _funm_multiply_krylov_arnoldi(A, V[:, m], 1, V, + H[begin:end + 1, begin:end], m) + + if breakdown: + end = begin + j + fH = f(t * H[:end, :end]) + y[:end] = y[:end] + bnorm * V[:, :m].dot(fH[begin:end, 0]) + return y + + fH = f(t * H[:end, :end]) + y = y + bnorm * V[:, :m].dot(fH[begin:end, 0]) + update_norm = norm(bnorm * fH[begin:end, 0]) + restart += 1 + + return y diff --git a/scipy/sparse/linalg/meson.build b/scipy/sparse/linalg/meson.build index e201ae10943f..43f68a15af9a 100644 --- a/scipy/sparse/linalg/meson.build +++ b/scipy/sparse/linalg/meson.build @@ -1,6 +1,7 @@ py3.install_sources([ '__init__.py', '_expm_multiply.py', + '_funm_multiply_krylov.py', '_interface.py', '_matfuncs.py', '_norm.py', diff --git a/scipy/sparse/linalg/tests/meson.build b/scipy/sparse/linalg/tests/meson.build index ca7458878fb0..e80cf09ed5a5 100644 --- a/scipy/sparse/linalg/tests/meson.build +++ b/scipy/sparse/linalg/tests/meson.build @@ -1,6 +1,7 @@ py3.install_sources([ '__init__.py', 'propack_test_data.npz', + 'test_funm_multiply_krylov.py', 'test_expm_multiply.py', 'test_interface.py', 'test_matfuncs.py', diff --git a/scipy/sparse/linalg/tests/test_funm_multiply_krylov.py b/scipy/sparse/linalg/tests/test_funm_multiply_krylov.py new file mode 100644 index 000000000000..93b545e96462 --- /dev/null +++ b/scipy/sparse/linalg/tests/test_funm_multiply_krylov.py @@ -0,0 +1,160 @@ +"""Test functions for the sparse.linalg._krylov_funm module.""" +from functools import partial + +import numpy as np +import pytest +from numpy.testing import (assert_allclose) +import scipy.sparse +import scipy.linalg +from scipy.sparse.linalg import aslinearoperator +from scipy.linalg import (expm, cosm, coshm, sinm, sinhm) + +from scipy.sparse.linalg._funm_multiply_krylov import funm_multiply_krylov + +REAL_DTYPES = (np.float32, np.float64) +COMPLEX_DTYPES = (np.complex64, np.complex128) +DTYPES = REAL_DTYPES + COMPLEX_DTYPES + +# This the phi_1 function from exponential integrators: phi_1 (z) = (e^z - 1) / z +def custom(X): + return scipy.linalg.solve(X, expm(X) - np.eye(X.shape[0])) + +FUNCS = (expm, cosm, coshm, sinm, sinhm, custom) + +class TestKrylovFunmv: + + def test_krylov_funm_zero_vector(self): + n = 20 + A = np.zeros((n, n)) + b = np.zeros(n) + observed = funm_multiply_krylov(expm, A, b) + expected = np.zeros(n) + assert_allclose(observed, expected) + + @pytest.mark.parametrize("f", FUNCS) + def test_funm_multiply_krylov_nonhermitian_dense(self, f): + rng = np.random.default_rng(1738151906092735) + n = 60 + nsamples = 10 + + for i in range(nsamples): + A = rng.standard_normal((n, n)) + b = rng.standard_normal(n) + + fA = f(A) + expected = fA @ b + observed = funm_multiply_krylov(f, A, b) + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + observed = funm_multiply_krylov(f, aslinearoperator(A), b) + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + + @pytest.mark.parametrize("f", FUNCS) + def test_funm_multiply_krylov_nonhermitian_sparse(self, f): + + rng = np.random.default_rng(1738151906092735) + n = 100 + nsamples = 10 + + for i in range(nsamples): + D = scipy.sparse.diags(rng.standard_normal(n)) + A = scipy.sparse.random_array((n, n), density = 0.01, rng = rng) + D + denseA = A.todense() + b = rng.standard_normal(n) + + fA = f(denseA) + expected = fA @ b + observed = funm_multiply_krylov(f, A, b) + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + observed = funm_multiply_krylov(f, aslinearoperator(A), b) + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + + @pytest.mark.parametrize("f", FUNCS) + def test_funm_multiply_krylov_hermitian_dense(self, f): + + rng = np.random.default_rng(1738151906092735) + n = 60 + nsamples = 10 + + for i in range(nsamples): + R = np.triu(rng.standard_normal((n, n))) + A = R.T + R + b = rng.standard_normal(n) + + fA = f(A) + expected = fA @ b + observed = funm_multiply_krylov(f, A, b, assume_a = 'her') + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + observed = funm_multiply_krylov(f, aslinearoperator(A), b, assume_a = 'her') + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + + @pytest.mark.parametrize("f", FUNCS) + def test_funm_multiply_krylov_hermitian_sparse(self, f): + + rng = np.random.default_rng(1738151906092735) + n = 100 + nsamples = 10 + + for i in range(nsamples): + D = scipy.sparse.diags(rng.standard_normal(n)) + A = scipy.sparse.random_array((n, n), density = 0.01, rng = rng) + R = scipy.sparse.triu(A) + A = R + R.T + D + denseA = A.todense() + b = rng.standard_normal(n) + + fA = f(denseA) + expected = fA @ b + observed = funm_multiply_krylov(f, A, b, assume_a = 'her') + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + + observed = funm_multiply_krylov(f, aslinearoperator(A), b, assume_a = 'her') + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + + def test_funm_multiply_krylov_breakdown(self): + rng = np.random.default_rng(1738151906092735) + + # From test_iterative + A = np.array([[0, 0, 0, 0, 0, 1, -1, -0, -0, -0, -0], + [0, 0, 0, 0, 0, 2, -0, -1, -0, -0, -0], + [0, 0, 0, 0, 0, 2, -0, -0, -1, -0, -0], + [0, 0, 0, 0, 0, 2, -0, -0, -0, -1, -0], + [0, 0, 0, 0, 0, 1, -0, -0, -0, -0, -1], + [1, 2, 2, 2, 1, 0, -0, -0, -0, -0, -0], + [-1, 0, 0, 0, 0, 0, -1, -0, -0, -0, -0], + [0, -1, 0, 0, 0, 0, -0, -1, -0, -0, -0], + [0, 0, -1, 0, 0, 0, -0, -0, -1, -0, -0], + [0, 0, 0, -1, 0, 0, -0, -0, -0, -1, -0], + [0, 0, 0, 0, -1, 0, -0, -0, -0, -0, -1]], dtype = float) + b = rng.standard_normal(A.shape[0]) + + fA = expm(A) + expected = fA @ b + observed = funm_multiply_krylov(expm, A, b, restart_every_m = 40) + assert_allclose(observed, expected) + + +@pytest.mark.parametrize("dtype_a", DTYPES) +@pytest.mark.parametrize("dtype_b", DTYPES) +def test_funm_multiply_krylov_types(dtype_a, dtype_b): + assert_allclose_ = (partial(assert_allclose, rtol = 1.8e-3, atol = 1e-5) + if {dtype_a, dtype_b} else assert_allclose) + + rng = np.random.default_rng(1738151906092735) + n = 50 + + if dtype_a in REAL_DTYPES: + A = rng.random([n, n]).astype(dtype_a) + else: + A = (rng.random([n, n]) + 1j * rng.random([n, n])).astype(dtype_a) + + if dtype_b in REAL_DTYPES: + b = (2 * rng.random(n)).astype(dtype_b) + else: + b = (rng.random(n) + 1j * rng.random(n)).astype(dtype_b) + + expA = expm(A) + expected = expA @ b + observed = funm_multiply_krylov(expm, A, b) + assert_allclose_(observed, expected) + observed = funm_multiply_krylov(expm, aslinearoperator(A), b) + assert_allclose_(observed, expected) diff --git a/scipy/sparse/linalg/tests/test_pydata_sparse.py b/scipy/sparse/linalg/tests/test_pydata_sparse.py index f6b855271063..0b6a304f4b7f 100644 --- a/scipy/sparse/linalg/tests/test_pydata_sparse.py +++ b/scipy/sparse/linalg/tests/test_pydata_sparse.py @@ -256,3 +256,17 @@ def test_ne(same_matrix): # temporary splint until pydata sparse support sparray equality sp_sparse = sp.coo_matrix(sp_sparse).asformat(sp_sparse.format) assert not (sp_sparse != pd_sparse).any() + + +def test_ge(same_matrix): + sp_sparse, pd_sparse = same_matrix + # temporary splint until pydata sparse support sparray equality + sp_sparse = sp.coo_matrix(sp_sparse).asformat(sp_sparse.format) + assert (sp_sparse >= pd_sparse).all() + + +def test_gt(same_matrix): + sp_sparse, pd_sparse = same_matrix + # temporary splint until pydata sparse support sparray equality + sp_sparse = sp.coo_matrix(sp_sparse).asformat(sp_sparse.format) + assert not (sp_sparse > pd_sparse).any() diff --git a/scipy/sparse/sparsetools/dia.h b/scipy/sparse/sparsetools/dia.h index 9700ec3c1849..75b951dbb47c 100644 --- a/scipy/sparse/sparsetools/dia.h +++ b/scipy/sparse/sparsetools/dia.h @@ -2,10 +2,124 @@ #define __DIA_H__ #include +#include + +using std::min, std::max; + +template +T min_el(const T vec[], I len) { + return *std::min_element(vec, vec + len); +} + +template +T max_el(const T vec[], I len) { + return *std::max_element(vec, vec + len); +} + + +/* + * Compute DIA matrix output = A * B for DIA matrices A, B + * + * + * Input Arguments: + * I A_rows - number of rows in A + * I A_cols - number of columns in A + * I A_diags - number of diagonals in A + * I A_L - length of each diagonal in A + * I A_offsets[A_diags] - diagonal offsets in A + * T A_data[A_diags,A_L] - diagonals data of A + * I B_cols - number of columns in B + * I B_diags - number of diagonals in B + * I B_L - length of each diagonal in B + * I B_offsets[B_diags] - diagonal offsets in B + * T B_data[B_diags,B_L] - diagonals data of B + * + * Output Arguments: + * V offsets - diagonal offsets + * W data - diagonals data + * + * Note: + * Number of diagonals in output (diags) is the length of output offsets; + * length of each diagonal (L) equals B_L; data dimensions are [diags,L] + * Negative offsets correspond to lower diagonals + * Positive offsets correspond to upper diagonals + * + */ +template +void dia_matmat(const I A_rows, + const I A_cols, + const I A_diags, + const I A_L, + const I A_offsets[], + const T A_data[], + const I B_cols, + const I B_diags, + const I B_L, + const I B_offsets[], + const T B_data[], + std::vector* offsets, + std::vector* data) +{ + const I B_rows = A_cols, + rows = A_rows, cols = B_cols, + L = min(cols, B_L); + + // range of combinations of input offsets + const I min_map_ofs = min_el(A_offsets, A_diags) + min_el(B_offsets, B_diags), + max_map_ofs = max_el(A_offsets, A_diags) + max_el(B_offsets, B_diags); + // limits for output offsets + const I min_ofs = max(min_map_ofs, 1 - rows), + max_ofs = min(max_map_ofs, L - 1); + // mark output offsets + std::vector buf(max_map_ofs - min_map_ofs + 1); + // (alias to buf indexable from [min_map_ofs] to [max_map_ofs]; + // only [min_ofs] to [max_ofs] will be used later) + I* ofs_map = buf.data() - min_map_ofs; + for (I i = 0; i < A_diags; ++i) + for (I j = 0; j < B_diags; ++j) + ofs_map[A_offsets[i] + B_offsets[j]] = I(true); + // enumerate marks + offsets->resize(max_ofs - min_ofs + 1); + I N_ofs = 0; + for (I ofs = min_ofs; ofs <= max_ofs; ++ofs) + if (ofs_map[ofs]) { + (*offsets)[N_ofs] = ofs; + ofs_map[ofs] = N_ofs; + ++N_ofs; + } + offsets->resize(N_ofs); + + // allocate output diagonals, filled with zeros + data->resize(N_ofs * L); + // loop over diagonals in B + for (I B_i = 0; B_i < B_diags; ++B_i) { + const I B_ofs = B_offsets[B_i]; + const T* B_diag_r = B_data + npy_intp(B_L) * B_i + B_ofs; // row-indexed + // span of data within current (row-indexed) B diagonal + const I B_j_beg = max(-B_ofs, I(0)), + B_j_end = min(min(B_cols, B_L) - B_ofs, B_rows); + // loop over diagonals in A + for (I A_i = 0; A_i < A_diags; ++A_i) { + const I A_ofs = A_offsets[A_i]; + const T* A_diag_c = A_data + npy_intp(A_L) * A_i; // column-indexed + // output offset and corresponding diagonal + const I ofs = A_ofs + B_ofs; + if (ofs < min_ofs or ofs > max_ofs) + continue; + T* diag_r = data->data() + npy_intp(L) * ofs_map[ofs] + B_ofs; // row-indexed + // overlapping span of data within current B and A diagonals + const I j_beg = max(B_j_beg, A_ofs), + j_end = min({B_j_end, min(A_cols, A_L), A_rows + A_ofs}); + // add partial product to output + for (I j = j_beg; j < j_end; ++j) + diag_r[j] += A_diag_c[j] * B_diag_r[j]; + } + } +} /* - * Compute Y += A*X for DIA matrix A and dense vectors X,Y + * Compute Y += A * X for DIA matrix A and dense vectors X, Y * * * Input Arguments: @@ -31,29 +145,86 @@ void dia_matvec(const I n_row, const I n_col, const I n_diags, const I L, - const I offsets[], - const T diags[], - const T Xx[], - T Yx[]) + const I offsets[], + const T diags[], + const T Xx[], + T Yx[]) { - for(I i = 0; i < n_diags; i++){ + for (I i = 0; i < n_diags; i++){ const I k = offsets[i]; //diagonal offset - const I i_start = std::max(0,-k); - const I j_start = std::max(0, k); - const I j_end = std::min(std::min(n_row + k, n_col),L); + const I i_start = max(0, -k); + const I j_start = max(0, k); + const I j_end = min(min(n_row + k, n_col), L); const I N = j_end - j_start; //number of elements to process - const T * diag = diags + (npy_intp)i*L + j_start; + const T * diag = diags + (npy_intp)i * L + j_start; const T * x = Xx + j_start; T * y = Yx + i_start; - for(I n = 0; n < N; n++){ + for (I n = 0; n < N; n++) { y[n] += diag[n] * x[n]; } } } +/* + * Compute output += A * B for DIA matrix A and dense matrices B, output + * + * + * Input Arguments: + * I A_rows - number of rows in A + * I A_cols - number of columns in A + * I A_diags - number of diagonals in A + * I A_L - length of each diagonal in A + * I offsets[A_diags] - diagonal offsets in A + * T A_data[A_diags,A_L] - diagonals data of A + * I B_cols - number of columns in B + * T B_data[A_rows,B_cols] - data of B (in C order) + * + * Output Arguments: + * T data[A_rows,B_cols] - output data (in C order) + * + * Note: + * Output array data must be preallocated + * Number of rows in B must be equal A_cols + * Negative offsets correspond to lower diagonals + * Positive offsets correspond to upper diagonals + * + */ +template +void dia_matvecs(const I A_rows, + const I A_cols, + const I A_diags, + const I A_L, + const I A_offsets[], + const T A_data[], + const I B_cols, + const T B_data[], + T data[]) +{ + const I rows = A_rows, cols = B_cols, + k_end = min(A_cols, A_L); // for index along A columns and B rows + // loop over output rows + for (I i = 0; i < rows; ++i) { + T* row = data + npy_intp(cols) * i; + // loop over diagonals in A + for (I n = 0; n < A_diags; ++n) { + const I k = i + A_offsets[n]; + if (k < 0 or k >= k_end) + continue; + // element at i-th row, k-th column in A + const T a = (A_data + npy_intp(A_L) * n)[k]; + // k-th row in B + const T* B_row = B_data + npy_intp(B_cols) * k; + // loop over columns in current output row + for (I j = 0; j < cols; ++j) + row[j] += a * B_row[j]; + } + } +} + + #endif diff --git a/scipy/sparse/tests/test_arithmetic1d.py b/scipy/sparse/tests/test_arithmetic1d.py index 3d5d2ee2f1bc..62b56f5fe2f6 100644 --- a/scipy/sparse/tests/test_arithmetic1d.py +++ b/scipy/sparse/tests/test_arithmetic1d.py @@ -194,7 +194,10 @@ def test_elementwise_multiply_broadcast(self, spcreator): with pytest.raises(ValueError, match=matchme): i.multiply(j) continue - sp_mult = i.multiply(j) + try: + sp_mult = i.multiply(j) + except ValueError: + continue assert_allclose(toarray(sp_mult), dense_mult) def test_elementwise_divide(self, spcreator, dat1d): @@ -320,7 +323,7 @@ def test_size_zero_matrix_arithmetic(self, spcreator): assert_equal(asp.dot(asp), np.dot(a, a)) # bad matrix products - with pytest.raises(ValueError, match='dimension mismatch'): + with pytest.raises(ValueError, match='dimension mismatch|shapes.*not aligned'): asp.dot(f) # elemente-wise multiplication diff --git a/scipy/sparse/tests/test_base.py b/scipy/sparse/tests/test_base.py index d89bdf586b8e..f79980390a59 100644 --- a/scipy/sparse/tests/test_base.py +++ b/scipy/sparse/tests/test_base.py @@ -39,7 +39,7 @@ class for generic tests" section. coo_matrix, lil_matrix, dia_matrix, bsr_matrix, csc_array, csr_array, dok_array, coo_array, lil_array, dia_array, bsr_array, - eye, issparse, SparseEfficiencyWarning, sparray) + eye, issparse, SparseEfficiencyWarning, sparray, spmatrix) from scipy.sparse._base import _formats from scipy.sparse._sputils import (supported_dtypes, isscalarlike, get_index_dtype, asmatrix, matrix) @@ -1053,7 +1053,9 @@ def test_sum_invalid_params(self): datsp.sum(axis=(0, 3)) with assert_raises(TypeError, match="axis must be an integer"): datsp.sum(axis=1.5) - assert_raises(ValueError, datsp.sum, axis=1, out=out) + # error msg varies by sparray (1d result) or spmatrix (2d result) + with assert_raises(ValueError, match="do.*n.t match.*shape|wrong.*dimensions"): + datsp.mean(axis=1, out=out) def test_sum_dtype(self): dat = array([[0, 1, 2], @@ -1111,6 +1113,21 @@ def test_numpy_sum(self): assert_array_almost_equal(dat_sum, datsp_sum) assert_equal(dat_sum.dtype, datsp_sum.dtype) + def test_sum_mean_container_type(self): + dat = array([[0, 1, 2], + [3, -4, 5], + [-6, 7, 9]]) + datsp = self.spcreator(dat) + + assert isscalarlike(datsp.sum()) + matrix_or_array = ndarray if self.is_array_test else np.matrix + assert isinstance(datsp.sum(axis=0), matrix_or_array) + assert isinstance(datsp.sum(axis=1), matrix_or_array) + + assert isscalarlike(datsp.mean()) + assert isinstance(datsp.mean(axis=0), matrix_or_array) + assert isinstance(datsp.mean(axis=1), matrix_or_array) + def test_mean(self): keep = not self.is_array_test def check(dtype): @@ -1151,7 +1168,7 @@ def check(dtype): for dtype in self.checked_dtypes: check(dtype) - def test_mean_invalid_params(self): + def test_mean_invalid_param(self): out = self.asdense(np.zeros((1, 3))) dat = array([[0, 1, 2], [3, -4, 5], @@ -1164,7 +1181,8 @@ def test_mean_invalid_params(self): datsp.mean(axis=(0, 3)) with assert_raises(TypeError, match="axis must be an integer"): datsp.mean(axis=1.5) - with assert_raises(ValueError, match="doesn't match.*shape|wrong.*dimensions"): + # error msg varies by sparray (1d result) or spmatrix (2d result) + with assert_raises(ValueError, match="do.*n.t match.*shape|wrong.*dimensions"): datsp.mean(axis=1, out=out) def test_mean_dtype(self): @@ -1663,7 +1681,10 @@ def test_elementwise_multiply_broadcast(self): except ValueError: assert_raises(ValueError, i.multiply, j) continue - sp_mult = i.multiply(j) + try: + sp_mult = i.multiply(j) + except ValueError: + continue if issparse(sp_mult): assert_almost_equal(sp_mult.toarray(), dense_mult) else: @@ -2080,8 +2101,8 @@ def check(dtype, dtype2, btype): with suppress_warnings() as sup: sup.filter(SparseEfficiencyWarning, - "Taking maximum .minimum. with > 0 .< 0. number " - "results to a dense matrix") + "Taking (maximum|minimum) with a (positive|negative) number " + "results in a dense matrix") max_s = A.maximum(B) min_s = A.minimum(B) @@ -3722,9 +3743,11 @@ def test_minmax(self): X = self.spcreator(np.arange(1, 10).reshape(3, 3)) assert_equal(X.min(), 1) assert_equal(X.min().dtype, X.dtype) + assert_equal(X.min(explicit=True), 1) X = -X assert_equal(X.max(), -1) + assert_equal(X.max(explicit=True), -1) # and a fully sparse matrix Z = self.spcreator(np.zeros((1, 1))) @@ -3847,6 +3870,38 @@ def test_minmax_axis(self): assert_equal(X.max(axis=axis, explicit=ex).toarray(), D.max(axis=axis)) assert_equal(X.min(axis=axis, explicit=ex).toarray(), D.min(axis=axis)) + def test_minmax_container_type(self): + dat = array([[0, 1, 2], + [3, -4, 5], + [-6, 7, 9]]) + datsp = self.spcreator(dat) + matrix_or_array = ndarray if self.is_array_test else np.matrix + spmatrix_or_sparray = sparray if self.is_array_test else spmatrix + + assert isscalarlike(datsp.min()) + assert isinstance(datsp.min(axis=0), spmatrix_or_sparray) + assert isinstance(datsp.min(axis=1), spmatrix_or_sparray) + + assert isscalarlike(datsp.max()) + assert isinstance(datsp.max(axis=0), spmatrix_or_sparray) + assert isinstance(datsp.max(axis=1), spmatrix_or_sparray) + + assert isscalarlike(datsp.nanmin()) + assert isinstance(datsp.nanmin(axis=0), spmatrix_or_sparray) + assert isinstance(datsp.nanmin(axis=1), spmatrix_or_sparray) + + assert isscalarlike(datsp.nanmax()) + assert isinstance(datsp.nanmax(axis=0), spmatrix_or_sparray) + assert isinstance(datsp.nanmax(axis=1), spmatrix_or_sparray) + + assert isscalarlike(datsp.argmin()) + assert isinstance(datsp.argmin(axis=0), matrix_or_array) + assert isinstance(datsp.argmin(axis=1), matrix_or_array) + + assert isscalarlike(datsp.argmax()) + assert isinstance(datsp.argmax(axis=0), matrix_or_array) + assert isinstance(datsp.argmax(axis=1), matrix_or_array) + def test_nanminmax(self): D = self.asdense(np.arange(50).reshape(5,10), dtype=float) D[1, :] = 0 @@ -3886,10 +3941,12 @@ def test_minmax_invalid_params(self): datsp = self.spcreator(dat) for fname in ('min', 'max'): + datfunc = getattr(dat, fname) func = getattr(datsp, fname) assert_raises(ValueError, func, axis=3) assert_raises(TypeError, func, axis=1.5) assert_raises(ValueError, func, axis=1, out=1) + assert_equal(func(axis=(0, 1)), datfunc(axis=(0, 1))) def test_numpy_minmax(self): # See gh-5987 @@ -3939,6 +3996,13 @@ def test_argmax(self): assert_equal(mat.argmax(axis=1), np.argmax(D, axis=1)) assert_equal(mat.argmin(axis=1), np.argmin(D, axis=1)) + # full matrix with explicit=True + mat = self.spcreator(self.asdense(D5)) + assert_equal(mat.argmax(explicit=True), 5) + assert_equal((-mat).argmax(explicit=True), 2) + assert_equal(mat.argmin(explicit=True), 2) + assert_equal((-mat).argmin(explicit=True), 5) + # zero-size matrices D6 = self.spcreator(np.empty((0, 5))) D7 = self.spcreator(np.empty((5, 0))) @@ -4985,6 +5049,40 @@ def test_tocoo_tocsr_tocsc_gh19245(self): csc = dia.tocsc() assert csc.indices.dtype == np.int32 + def test_add_sparse(self): + # test format and cases not covered by common add tests + A = diag([1, 2]) + B = A + diag([3], 1) + Asp = dia_matrix(A) + Bsp = dia_matrix(B) + + Csp = Asp + Bsp + assert isinstance(Csp, dia_matrix) + assert_array_equal(Csp.toarray(), A + B) + + Csp = Bsp + Asp + assert isinstance(Csp, dia_matrix) + assert_array_equal(Csp.toarray(), B + A) + + def test_sub_sparse(self): + # test format and cases not covered by common sub tests + A = diag([1, 2]) + B = A + diag([3], 1) + Asp = dia_matrix(A) + Bsp = dia_matrix(B) + + Csp = Asp - Bsp + assert isinstance(Csp, dia_matrix) + assert_array_equal(Csp.toarray(), A - B) + + Csp = Bsp - Asp + assert isinstance(Csp, dia_matrix) + assert_array_equal(Csp.toarray(), B - A) + + Bsp = Bsp.asformat('csr') + assert_array_equal((Asp - Bsp).toarray(), A - B) + assert_array_equal((Bsp - Asp).toarray(), B - A) + def test_mul_scalar(self): # repro for gh-20434 m = self.dia_container([[1, 2], [0, 4]]) @@ -4996,6 +5094,50 @@ def test_mul_scalar(self): assert isinstance(res2, m.__class__) assert_array_equal(res2.toarray(), [[3, 6], [0, 12]]) + def test_matmul_dia(self): + # test DIA structure of DIA @ DIA: + + # that all and only needed elements are used and produced + A = array([[1, 2, 3], + [4, 5, 6]]) + B = array([[11, 12], + [13, 14], + [15, 16]]) + Asp = dia_matrix(A) + Bsp = dia_matrix(B) + Asp.data[Asp.data == 0] = -1 # poison outside elements + Bsp.data[Bsp.data == 0] = -1 + assert_array_equal(Asp.toarray(), A) + assert_array_equal(Bsp.toarray(), B) + + C = A @ B + Csp = Asp @ Bsp + assert isinstance(Csp, dia_matrix) + assert_array_equal(Csp.toarray(), C) + assert_array_equal(Csp.offsets, [-1, 0, 1]) + assert_array_equal(Csp.data, dia_matrix(C).data) + + C = B @ A + Csp = Bsp @ Asp + assert isinstance(Csp, dia_matrix) + assert_array_equal(Csp.toarray(), C) + assert_array_equal(Csp.offsets, [-2, -1, 0, 1, 2]) + assert_array_equal(Csp.data, dia_matrix(C).data) + + # short data and that order of input offsets doesn't matter + Asp = dia_matrix(([[0., 1., 2.], [3., 4., 5.]], [1, -2]), (5, 5)) + Bsp = dia_matrix(([[6., 7., 8.], [0., 0., 9.]], [-1, 2]), (5, 5)) + + Csp = Asp @ Bsp + assert_array_equal(Csp.offsets, array([-3, 0])) + assert_array_equal(Csp.data, [[24., 35., 0.], + [6., 14., 27.]]) + + Csp = Bsp @ Asp + assert_array_equal(Csp.offsets, array([-3, 0])) + assert_array_equal(Csp.data, [[24., 0., 0.], + [27., 6., 14.]]) + class TestDIAMatrix(_MatrixMixin, TestDIA): spcreator = dia_matrix diff --git a/scipy/sparse/tests/test_common1d.py b/scipy/sparse/tests/test_common1d.py index 6f36008caa6c..377088500149 100644 --- a/scipy/sparse/tests/test_common1d.py +++ b/scipy/sparse/tests/test_common1d.py @@ -180,7 +180,7 @@ def test_mean_invalid_params(self, spcreator): datsp.mean(axis=(0, 3)) with pytest.raises(TypeError, match='axis must be an integer'): datsp.mean(axis=1.5) - with pytest.raises(ValueError, match='output parameter.*wrong.*dimension'): + with pytest.raises(ValueError, match='out.*not match shape'): datsp.mean(axis=1, out=out) def test_sum_dtype(self, spcreator): diff --git a/scipy/sparse/tests/test_coo.py b/scipy/sparse/tests/test_coo.py index e9281748e6fc..42b538b31a48 100644 --- a/scipy/sparse/tests/test_coo.py +++ b/scipy/sparse/tests/test_coo.py @@ -1,8 +1,9 @@ +import math import numpy as np -from numpy.testing import assert_equal +from numpy.testing import assert_equal, assert_allclose, suppress_warnings import pytest from scipy.linalg import block_diag -from scipy.sparse import coo_array, random_array +from scipy.sparse import coo_array, random_array, SparseEfficiencyWarning from .._coo import _block_diag, _extract_block_diag @@ -563,8 +564,7 @@ def test_nd_add_sparse_with_inconsistent_shapes(a_shape, b_shape): arr_a = random_array((a_shape), density=0.6, rng=rng, dtype=int) arr_b = random_array((b_shape), density=0.6, rng=rng, dtype=int) - with pytest.raises(ValueError, - match="(Incompatible|inconsistent) shapes|cannot be broadcast"): + with pytest.raises(ValueError, match="inconsistent shapes"): arr_a + arr_b @@ -849,3 +849,271 @@ def test_extract_block_diag(shape): res = _extract_block_diag(_block_diag(sp_x), shape) assert_equal(res.toarray(), sp_x.toarray()) + + +add_sub_shapes = [ + ((3,4), (3,4)), ((3,4,6), (3,4,6)), ((3,7,5), (3,7,5)) +] +@pytest.mark.parametrize(('a_shape', 'b_shape'), add_sub_shapes) +def test_add_no_broadcasting(a_shape, b_shape): + rng = np.random.default_rng(23409823) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + + res = a + b + exp = np.add(a.toarray(), b.toarray()) + assert_equal(res.toarray(), exp) + res = a + b.toarray() + assert_equal(res, exp) + +@pytest.mark.parametrize(('a_shape', 'b_shape'), add_sub_shapes) +def test_sub_no_broadcasting(a_shape, b_shape): + rng = np.random.default_rng(23409823) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + + res = a - b + exp = np.subtract(a.toarray(), b.toarray()) + assert_equal(res.toarray(), exp) + + res = a - b.toarray() + assert_equal(res, exp) + +argmax_argmin_shapes_axis = [ + ((3,), None), ((3,), 0), + ((4,6), 1), ((7,3), 0), ((3,5), None), + ((2,8,7), 2), ((2,8,7), 0), + ((2,0), 0), ((3,0,0,2), 0), + ((3,2,4,7), None), ((3,2,4,7), 1), ((3,2,4,7), 0), ((3,2,4,7), 2), + ((3,2,4,7), -2), ((4,5,7,8,2), 4), ((4,5,7,8,2), -3), +] +@pytest.mark.parametrize(('shape', 'axis'), argmax_argmin_shapes_axis) +def test_argmax_argmin(shape, axis): + rng = np.random.default_rng(23409823) + a = random_array(shape, density=0.6, random_state=rng, dtype=int) + + res = a.argmax(axis=axis) + exp = np.argmax(a.toarray(), axis=axis) + assert_equal(res, exp) + + res = a.argmin(axis=axis) + exp = np.argmin(a.toarray(), axis=axis) + assert_equal(res, exp) + + +max_min_shapes_axis = [ + ((3,), None), ((3,), 0), + ((4,6), 1), ((7,3), 0), ((3,5), None), + ((2,8,7), 2), ((2,8,7), 0), + ((3,2,4,7), None), ((3,2,4,7), 1), ((3,2,4,7), 0), ((3,2,4,7), 2), + ((4,5,7,8,2), 4), ((4,5,8,1), 3), ((4,6), (0,)), ((4,6), (0,1)), + ((3,0,2), 2), ((3,0,2), (0,2)), ((3,0), 0), + ((3,7,8,5), (0,1)), ((3,7,8,5), (2,1)), ((3,7,8,5), (2,0)), + ((3,7,8,5), (0,-2)), ((3,7,8,5), (-1,2)), ((3,7,8,5), (3)), + ((3,7,8,5), (0,1,2)), ((3,7,8,5), (0,1,2,3)), +] +@pytest.mark.parametrize(('shape', 'axis'), max_min_shapes_axis) +def test_min_max(shape, axis): + rng = np.random.default_rng(23409823) + a = random_array(shape, density=0.6, random_state=rng, dtype=int) + + res_min = a.min(axis=axis) + exp_min = np.min(a.toarray(), axis=axis) + res_max = a.max(axis=axis) + exp_max = np.max(a.toarray(), axis=axis) + res_nanmin = a.nanmin(axis=axis) + exp_nanmin = np.nanmin(a.toarray(), axis=axis) + res_nanmax = a.nanmax(axis=axis) + exp_nanmax = np.nanmax(a.toarray(), axis=axis) + + for res, exp in [(res_min, exp_min), (res_max, exp_max), + (res_nanmin, exp_nanmin), (res_nanmax, exp_nanmax)]: + if np.issubdtype(type(res), np.number): + assert_equal(res, exp) + else: + assert_equal(res.toarray(), exp) + + +def test_min_max_full(): + for a in (coo_array([[[1, 2, 3, 4]]]), coo_array([[1, 2, 3, 4]])): + assert a.min() == 1 + assert (-a).max() == -1 + + +sum_mean_params = [ + ((3,), None, None), ((3,), 0, None), + ((4,6), 1, None), ((7,3), 0, None), ((3,5), None, None), + ((2,8,7), 2, None), ((2,8,7), 0, np.zeros((8,7))), + ((3,2,4,7), None, None), ((3,2,4,7), 1, np.zeros((3,4,7))), + ((3,2,4,7), 0, None), ((4,5,7,8,2), 4, None), + ((4,5,8,1), 3, None), ((4,6), (0,), None), ((4,6), (0,1), None), + ((3,0,2), 2, None), ((3,0,2), (0,2), None), ((3,0), 0, None), + ((3,7,8,5), (0,1), np.zeros((8,5))), ((3,7,8,5), (2,1), None), + ((3,7,8,5), (0,-2), None), ((3,7,8,5), (-1,2), np.zeros((3,7))), + ((3,7,8,5), (3), None), ((3,7,8,5), (0,1,2), np.zeros((5,))), + ((3,7,8,5), (0,1,2,3), None), +] +@pytest.mark.parametrize(('shape', 'axis', 'out'), sum_mean_params) +def test_sum(shape, axis, out): + rng = np.random.default_rng(23409823) + a = random_array(shape, density=0.6, random_state=rng, dtype=int) + + res = a.sum(axis=axis, out=out) + exp = np.sum(a.toarray(), axis=axis) + assert_equal(res, exp) + if out is not None: + assert_equal(out, exp) + assert id(res) == id(out) + + +@pytest.mark.parametrize(('shape', 'axis', 'out'), sum_mean_params) +def test_mean(shape, axis, out): + rng = np.random.default_rng(23409823) + a = random_array(shape, density=0.6, random_state=rng, dtype=int) + + res = a.mean(axis=axis, out=out) + exp = np.mean(a.toarray(), axis=axis) + assert_allclose(res, exp) + if out is not None: + assert id(res) == id(out) + assert_allclose(out, exp) + + +def test_pow_abs_round(): + rng = np.random.default_rng(23409823) + a = random_array((3,6,5,2,4), density=0.6, random_state=rng, dtype=int) + assert_allclose((a**3).toarray(), np.power(a.toarray(), 3)) + assert_allclose((a**7).toarray(), np.power(a.toarray(), 7)) + assert_allclose(round(a).toarray(), np.round(a.toarray())) + assert_allclose(abs(a).toarray(), np.abs(a.toarray())) + + +#bitwise_op_and_compare_broadcast_shapes = [ +# ((3,4), (3,4)), ((1,4), (2,1)), ((3,5), (1,)), ((1,), (7,8)), +# ((3,4,6), (3,4,6)), ((4,3), (2,1,3)), ((2,1,3), (4,3)), +# ((3,5,4), (1,)), ((1,), (7,8,4)), ((16,1,6), (2,6)), ((3,7,5), (3,7,5)), +# ((16,2,6), (1,2,6)), ((7,8), (5,7,8)), ((4,5,1), (5,1)), +# ((6,8,3), (4,1,1,3)), ((1,1,1), (3,4,2)), ((3,4,2), (1,1,1,1,1)), +bitwise_op_and_compare_shapes = [ + ((3,4), (3,4)), ((3,4,6), (3,4,6)), ((3,7,5), (3,7,5)), +] +@pytest.mark.parametrize(('a_shape', 'b_shape'), bitwise_op_and_compare_shapes) +def test_boolean_comparisons(a_shape, b_shape): + rng = np.random.default_rng(23409823) + sup = suppress_warnings() + sup.filter(SparseEfficiencyWarning) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + with sup: + assert_equal((a==b).toarray(), a.toarray()==b.toarray()) + assert_equal((a!=b).toarray(), a.toarray()!=b.toarray()) + assert_equal((a>=b).toarray(), a.toarray()>=b.toarray()) + assert_equal((a<=b).toarray(), a.toarray()<=b.toarray()) + assert_equal((a>b).toarray(), a.toarray()>b.toarray()) + assert_equal((a=b).toarray(), np.bitwise_not((ab).toarray())) + + +def test_boolean_comparisons_with_scalar(): + rng = np.random.default_rng(23409823) + sup = suppress_warnings() + sup.filter(SparseEfficiencyWarning) + a = random_array((5,4,8,7), density=0.6, random_state=rng, dtype=int) + with sup: + assert_equal((a==0).toarray(), a.toarray()==0) + assert_equal((a!=0).toarray(), a.toarray()!=0) + assert_equal((a>=1).toarray(), a.toarray()>=1) + assert_equal((a<=1).toarray(), a.toarray()<=1) + assert_equal((a>0).toarray(), a.toarray()>0) + assert_equal((a<0).toarray(), a.toarray()<0) + + +@pytest.mark.parametrize(('a_shape', 'b_shape'), bitwise_op_and_compare_shapes) +def test_multiply(a_shape, b_shape): + rng = np.random.default_rng(23409823) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + res = a * b + exp = np.multiply(a.toarray(), b.toarray()) + assert_equal(res.toarray(), exp) + + +def test_multiply_with_scalar(): + rng = np.random.default_rng(23409823) + a = random_array((3,5,4), density=0.6, random_state=rng, dtype=int) + res = a * 7 + exp = np.multiply(a.toarray(), 7) + assert_equal(res.toarray(), exp) + + +@pytest.mark.parametrize(('a_shape', 'b_shape'), bitwise_op_and_compare_shapes) +def test_divide(a_shape, b_shape): + rng = np.random.default_rng(23409823) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = np.arange(1, 1 + math.prod(b_shape)).reshape(b_shape) + res = a / b + exp = a.toarray() / b + assert_allclose(res.toarray(), exp) + + res = a / b + assert_allclose(res.toarray(), exp) + + +def test_divide_with_scalar(): + rng = np.random.default_rng(23409823) + a = random_array((3,5,4), density=0.6, random_state=rng, dtype=int) + res = a / 7 + exp = a.toarray() / 7 + assert_allclose(res.toarray(), exp) + + +@pytest.mark.parametrize(('a_shape', 'b_shape'), bitwise_op_and_compare_shapes) +def test_maximum(a_shape, b_shape): + rng = np.random.default_rng(23409823) + sup = suppress_warnings() + sup.filter(SparseEfficiencyWarning) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + with sup: + res = a.maximum(b) + exp = np.maximum(a.toarray(), b.toarray()) + assert_equal(res.toarray(), exp) + +@pytest.mark.parametrize(('a_shape', 'b_shape'), bitwise_op_and_compare_shapes) +def test_minimum(a_shape, b_shape): + rng = np.random.default_rng(23409823) + sup = suppress_warnings() + sup.filter(SparseEfficiencyWarning) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + with sup: + res = a.minimum(b) + exp = np.minimum(a.toarray(), b.toarray()) + assert_equal(res.toarray(), exp) + + +def test_maximum_with_scalar(): + sup = suppress_warnings() + sup.filter(SparseEfficiencyWarning) + a = coo_array([0,1,6]) + b = coo_array([[15, 0], [14, 5], [0, -12]]) + c = coo_array([[[[3,0], [2,4]], [[8,9], [-3,12]]], + [[[5,2], [3,0]], [[0,7], [0,-6]]]]) + with sup: + assert_equal(a.maximum(5).toarray(), np.maximum(a.toarray(), 5)) + assert_equal(b.maximum(9).toarray(), np.maximum(b.toarray(), 9)) + assert_equal(c.maximum(5).toarray(), np.maximum(c.toarray(), 5)) + +def test_minimum_with_scalar(): + sup = suppress_warnings() + sup.filter(SparseEfficiencyWarning) + a = coo_array([0,1,6]) + b = coo_array([[15, 0], [14, 5], [0, -12]]) + c = coo_array([[[[3,0], [2,4]], [[8,9], [-3,12]]], + [[[5,2], [3,0]], [[0,7], [0,-6]]]]) + with sup: + assert_equal(a.minimum(5).toarray(), np.minimum(a.toarray(), 5)) + assert_equal(b.minimum(9).toarray(), np.minimum(b.toarray(), 9)) + assert_equal(c.minimum(5).toarray(), np.minimum(c.toarray(), 5)) diff --git a/scipy/sparse/tests/test_sparsetools.py b/scipy/sparse/tests/test_sparsetools.py index 6c9b275a46e6..3a254d449eca 100644 --- a/scipy/sparse/tests/test_sparsetools.py +++ b/scipy/sparse/tests/test_sparsetools.py @@ -120,6 +120,7 @@ def setup_method(self): def teardown_method(self): gc.collect() + @pytest.mark.fail_slow(2) # keep in fast set, only non-slow test def test_coo_todense(self): # Check *_todense routines (cf. gh-2179) # diff --git a/scipy/sparse/tests/test_sputils.py b/scipy/sparse/tests/test_sputils.py index 7a34a56ad335..345f89f8585c 100644 --- a/scipy/sparse/tests/test_sputils.py +++ b/scipy/sparse/tests/test_sputils.py @@ -131,20 +131,19 @@ def test_validateaxis(self): for ax in [5, -5, (0, 5), (-5, 0)]: with assert_raises(ValueError, match="out of range"): sputils.validateaxis(ax, ndim=2) - for axis in (0, 1, None): + for axis in ((0,), (1,), None): assert sputils.validateaxis(axis, ndim=2) == axis - convert_axis_2d = {-2: 0, -1: 1, (0, 1): None, (0, -1): None} - for axis, canonical_axis in convert_axis_2d.items(): + axis_2d = {-2: (0,), -1: (1,), 0: (0,), 1: (1,), (0, 1): None, (0, -1): None} + for axis, canonical_axis in axis_2d.items(): assert sputils.validateaxis(axis, ndim=2) == canonical_axis # ndim 4 - for axis in (2, 3, (2, 3), (2, 1), (0, 3)): + for axis in ((2,), (3,), (2, 3), (2, 1), (0, 3)): assert sputils.validateaxis(axis, ndim=4) == axis - convert_axis_4d = {-4: 0, -3: 1, (3, -4): (3, 0)} - for axis, canonical_axis in convert_axis_4d.items(): + axis_4d = {-4: (0,), -3: (1,), 2: (2,), 3: (3,), (3, -4): (3, 0)} + for axis, canonical_axis in axis_4d.items(): sputils.validateaxis(axis, ndim=4) == canonical_axis - @pytest.mark.parametrize("container", [csr_array, bsr_array]) def test_safely_cast_index_compressed(self, container): # This is slow to test completely as nnz > imax is big diff --git a/scipy/spatial/_ckdtree.pyx b/scipy/spatial/_ckdtree.pyx index e0e9244593e6..279c61c883ae 100644 --- a/scipy/spatial/_ckdtree.pyx +++ b/scipy/spatial/_ckdtree.pyx @@ -31,10 +31,6 @@ cdef extern from "": __all__ = ['cKDTree'] -cdef extern from *: - int NPY_LIKELY(int) - int NPY_UNLIKELY(int) - # C++ implementations # =================== @@ -188,7 +184,7 @@ cdef class coo_entries: _dtype = [('i',np.intp),('j',np.intp),('v',np.float64)] res_dtype = np.dtype(_dtype, align = True) n = self.buf.size() - if NPY_LIKELY(n > 0): + if (n > 0): pr = self.buf.data() uintptr = ( pr) dtype = np.dtype(np.uint8) @@ -211,7 +207,7 @@ cdef class coo_entries: coo_entry *pr dict res_dict n = self.buf.size() - if NPY_LIKELY(n > 0): + if (n > 0): pr = self.buf.data() res_dict = dict() for k in range(n): @@ -261,7 +257,7 @@ cdef class ordered_pairs: np.uintp_t uintptr np.intp_t n n = self.buf.size() - if NPY_LIKELY(n > 0): + if (n > 0): pr = self.buf.data() uintptr = ( pr) dtype = np.dtype(np.intp) @@ -1076,7 +1072,7 @@ cdef class cKDTree: results = n * [None] for i in range(n): m = (vvres[i].size()) - if NPY_LIKELY(m > 0): + if (m > 0): tmp = m * [None] cur = vvres[i].data() for j in range(m): diff --git a/scipy/spatial/_qhull.pyx b/scipy/spatial/_qhull.pyx index 8765b8db5755..0622121529c4 100644 --- a/scipy/spatial/_qhull.pyx +++ b/scipy/spatial/_qhull.pyx @@ -54,11 +54,11 @@ cdef extern from "setjmp.h" nogil: void longjmp(jmp_buf STATE, int VALUE) nogil # Define the clockwise constant -cdef extern from "qhull_src/src/user_r.h": +cdef extern from "": cdef enum: qh_ORIENTclock -cdef extern from "qhull_src/src/qset_r.h": +cdef extern from "": ctypedef union setelemT: void *p int i @@ -70,7 +70,7 @@ cdef extern from "qhull_src/src/qset_r.h": int qh_setsize(qhT *, setT *set) nogil void qh_setappend(qhT *, setT **setp, void *elem) nogil -cdef extern from "qhull_src/src/libqhull_r.h": +cdef extern from "": ctypedef double realT ctypedef double coordT ctypedef double pointT @@ -182,7 +182,7 @@ cdef extern from "qhull_misc.h": boolT ismalloc, char* qhull_cmd, void *outfile, void *errfile, coordT* feaspoint) nogil -cdef extern from "qhull_src/src/io_r.h": +cdef extern from "": ctypedef enum qh_RIDGE: qh_RIDGEall qh_RIDGEinner @@ -197,14 +197,14 @@ cdef extern from "qhull_src/src/io_r.h": void qh_order_vertexneighbors(qhT *, vertexT *vertex) nogil int qh_compare_facetvisit(const void *p1, const void *p2) nogil -cdef extern from "qhull_src/src/geom_r.h": +cdef extern from "": pointT *qh_facetcenter(qhT *, setT *vertices) nogil double qh_getarea(qhT *, facetT *facetlist) nogil -cdef extern from "qhull_src/src/poly_r.h": +cdef extern from "": void qh_check_maxout(qhT *) nogil -cdef extern from "qhull_src/src/mem_r.h": +cdef extern from "": void qh_memfree(qhT *, void *object, int insize) from libc.stdlib cimport qsort @@ -2935,6 +2935,22 @@ class HalfspaceIntersection(_QhullUser): of halfspaces is also not possible after `close` has been called. """ + if halfspaces.ndim > 2: + raise ValueError("`halfspaces` should be provided as a 2D array") + # We check for non-feasibility of incremental additions + # in a manner similar to `qh_sethalfspace` + halfspaces = np.atleast_2d(halfspaces) + dists = np.dot(halfspaces[:, :self.ndim], self.interior_point) + halfspaces[:, -1] + # HalfspaceIntersection uses closed half spaces so + # the feasible point also cannot be directly on the boundary + viols = dists >= 0 + if viols.any(): + # error out with an indication of the first violating + # half space discovered + first_viol = np.nonzero(viols)[0].min() + bad_hs = halfspaces[first_viol, :] + msg = f"feasible point is not clearly inside halfspace: {bad_hs}" + raise QhullError(msg) self._add_points(halfspaces, restart, self.interior_point) @property diff --git a/scipy/spatial/ckdtree/src/build.cxx b/scipy/spatial/ckdtree/src/build.cxx index 1083fc16f7b2..97c07b396604 100644 --- a/scipy/spatial/ckdtree/src/build.cxx +++ b/scipy/spatial/ckdtree/src/build.cxx @@ -52,7 +52,7 @@ build(ckdtree *self, ckdtree_intp_t start_idx, intptr_t end_idx, } else { - if (CKDTREE_LIKELY(_compact)) { + if (_compact) { /* Recompute hyperrectangle bounds. This should lead to a more * compact kd-tree but comes at the expense of larger construction * time. However, construction time is usually dwarfed by the @@ -107,7 +107,7 @@ build(ckdtree *self, ckdtree_intp_t start_idx, intptr_t end_idx, return partition_ptr - indices; }; - if (CKDTREE_LIKELY(_median)) { + if (_median) { /* split on median to create a balanced tree * adopted from scikit-learn */ @@ -143,7 +143,7 @@ build(ckdtree *self, ckdtree_intp_t start_idx, intptr_t end_idx, p = partition_pivot(indices + start_idx, indices + end_idx, split); } - if (CKDTREE_UNLIKELY(p == start_idx || p == end_idx)) { + if (p == start_idx || p == end_idx) { // All children are equal in this dimension, try again with new bounds assert(!_compact); self->tree_buffer->pop_back(); @@ -160,7 +160,7 @@ build(ckdtree *self, ckdtree_intp_t start_idx, intptr_t end_idx, return build(self, start_idx, end_idx, tmp_maxes, tmp_mins, _median, _compact); } - if (CKDTREE_LIKELY(_compact)) { + if (_compact) { _less = build(self, start_idx, p, maxes, mins, _median, _compact); _greater = build(self, p, end_idx, maxes, mins, _median, _compact); } diff --git a/scipy/spatial/ckdtree/src/ckdtree_decl.h b/scipy/spatial/ckdtree/src/ckdtree_decl.h index a89fe5f4de02..d1d577255db0 100644 --- a/scipy/spatial/ckdtree/src/ckdtree_decl.h +++ b/scipy/spatial/ckdtree/src/ckdtree_decl.h @@ -7,9 +7,6 @@ * */ #include #include -#define CKDTREE_LIKELY(x) NPY_LIKELY(x) -#define CKDTREE_UNLIKELY(x) NPY_UNLIKELY(x) -#define CKDTREE_PREFETCH(x, rw, loc) NPY_PREFETCH(x, rw, loc) #define ckdtree_intp_t npy_intp #define ckdtree_fmin(x, y) fmin(x, y) diff --git a/scipy/spatial/ckdtree/src/count_neighbors.cxx b/scipy/spatial/ckdtree/src/count_neighbors.cxx index 0f0bc78d6fd6..9e36fefc32f5 100644 --- a/scipy/spatial/ckdtree/src/count_neighbors.cxx +++ b/scipy/spatial/ckdtree/src/count_neighbors.cxx @@ -100,27 +100,12 @@ traverse( const ckdtree_intp_t end1 = node1->end_idx; const ckdtree_intp_t end2 = node2->end_idx; - CKDTREE_PREFETCH(sdata + sindices[start1] * m, 0, m); - - if (start1 < end1 - 1) - CKDTREE_PREFETCH(sdata + sindices[start1+1] * m, 0, m); /* brute-force */ for (i = start1; i < end1; ++i) { - if (i < end1 - 2) - CKDTREE_PREFETCH(sdata + sindices[i+2] * m, 0, m); - - CKDTREE_PREFETCH(odata + oindices[start2] * m, 0, m); - - if (start2 < end2 - 1) - CKDTREE_PREFETCH(odata + oindices[start2+1] * m, 0, m); - for (j = start2; j < end2; ++j) { - if (j < end2 - 2) - CKDTREE_PREFETCH(odata + oindices[j+2] * m, 0, m); - double d = MinMaxDist::point_point_p(params->self.tree, sdata + sindices[i] * m, odata + oindices[j] * m, @@ -210,14 +195,14 @@ count_neighbors(struct CNBParams *params, Rectangle r1(self->m, self->raw_mins, self->raw_maxes); Rectangle r2(other->m, other->raw_mins, other->raw_maxes); - if (CKDTREE_LIKELY(self->raw_boxsize_data == NULL)) { - HANDLE(CKDTREE_LIKELY(p == 2), MinkowskiDistP2) + if (self->raw_boxsize_data == NULL) { + HANDLE(p == 2, MinkowskiDistP2) HANDLE(p == 1, MinkowskiDistP1) HANDLE(std::isinf(p), MinkowskiDistPinf) HANDLE(1, MinkowskiDistPp) {} } else { - HANDLE(CKDTREE_LIKELY(p == 2), BoxMinkowskiDistP2) + HANDLE(p == 2, BoxMinkowskiDistP2) HANDLE(p == 1, BoxMinkowskiDistP1) HANDLE(std::isinf(p), BoxMinkowskiDistPinf) HANDLE(1, BoxMinkowskiDistPp) diff --git a/scipy/spatial/ckdtree/src/distance.h b/scipy/spatial/ckdtree/src/distance.h index f07c75a34b43..036c99a97b62 100644 --- a/scipy/spatial/ckdtree/src/distance.h +++ b/scipy/spatial/ckdtree/src/distance.h @@ -117,10 +117,10 @@ struct BoxDist1D { * * We will fix the convention later. * */ - if (CKDTREE_UNLIKELY(full <= 0)) { + if (full <= 0) { /* A non-periodic dimension */ /* \/ */ - if(max <= 0 || min >= 0) { + if (max <= 0 || min >= 0) { /* do not pass though 0 */ min = ckdtree_fabs(min); max = ckdtree_fabs(max); @@ -230,7 +230,7 @@ struct BoxDist1D { tmax = x - max; tmin = x - min; /* is the test point in this range */ - if(CKDTREE_LIKELY(tmax < 0 && tmin > 0)) { + if(tmax < 0 && tmin > 0) { /* yes. min distance is 0 */ return 0; } @@ -262,8 +262,8 @@ struct BoxDist1D { wrap_distance(const double x, const double hb, const double fb) { double x1; - if (CKDTREE_UNLIKELY(x < -hb)) x1 = fb + x; - else if (CKDTREE_UNLIKELY(x > hb)) x1 = x - fb; + if (x < -hb) x1 = fb + x; + else if (x > hb) x1 = x - fb; else x1 = x; #if 0 printf("ckdtree_fabs_b x : %g x1 %g\n", x, x1); diff --git a/scipy/spatial/ckdtree/src/query.cxx b/scipy/spatial/ckdtree/src/query.cxx index e8bad724a1e9..add06991851f 100644 --- a/scipy/spatial/ckdtree/src/query.cxx +++ b/scipy/spatial/ckdtree/src/query.cxx @@ -133,7 +133,7 @@ struct nodeinfo { } inline void update_side_distance(const int d, const double new_side_distance, const double p) { - if (CKDTREE_UNLIKELY(std::isinf(p))) { + if (std::isinf(p)) { min_distance = ckdtree_fmax(min_distance, new_side_distance); } else { min_distance += new_side_distance - side_distances()[d]; @@ -261,7 +261,7 @@ query_single_point(const ckdtree *self, } /* fiddle approximation factor */ - if (CKDTREE_LIKELY(p == 2.0)) { + if (p == 2.0) { double tmp = 1. + eps; epsfac = 1. / (tmp*tmp); } @@ -273,7 +273,7 @@ query_single_point(const ckdtree *self, epsfac = 1. / std::pow((1. + eps), p); /* internally we represent all distances as distance**p */ - if (CKDTREE_LIKELY(p == 2.0)) { + if (p == 2.0) { double tmp = distance_upper_bound; distance_upper_bound = tmp*tmp; } @@ -292,15 +292,8 @@ query_single_point(const ckdtree *self, const double *data = self->raw_data; const ckdtree_intp_t *indices = self->raw_indices; - CKDTREE_PREFETCH(data+indices[start_idx]*m, 0, m); - if (start_idx < end_idx - 1) - CKDTREE_PREFETCH(data+indices[start_idx+1]*m, 0, m); - for (i=start_idx; iraw_boxsize_data == NULL)) { + if (self->raw_boxsize_data == NULL) { /* * non periodic : the 'near' node is know from the * relative distance to the split, and @@ -449,13 +442,13 @@ query_single_point(const ckdtree *self, /* fill output arrays with sorted neighbors */ for (i = 0; i < nk; ++i) { - if(CKDTREE_UNLIKELY(k[i] - 1 >= nnb)) { + if (k[i] - 1 >= nnb) { result_indices[i] = self->n; result_distances[i] = inf; } else { neighbor = sorted_neighbors[k[i] - 1]; result_indices[i] = neighbor.contents.intdata; - if (CKDTREE_LIKELY(p == 2.0)) + if (p == 2.0) result_distances[i] = std::sqrt(-neighbor.priority); else if ((p == 1.) || (std::isinf(p))) result_distances[i] = -neighbor.priority; @@ -488,12 +481,12 @@ query_knn(const ckdtree *self, ckdtree_intp_t m = self->m; ckdtree_intp_t i; - if(CKDTREE_LIKELY(!self->raw_boxsize_data)) { + if (!self->raw_boxsize_data) { for (i=0; iraw_boxsize_data[j]); } - HANDLE(CKDTREE_LIKELY(p == 2), BoxMinkowskiDistP2) + HANDLE(p == 2, BoxMinkowskiDistP2) HANDLE(p == 1, BoxMinkowskiDistP1) HANDLE(std::isinf(p), BoxMinkowskiDistPinf) HANDLE(1, BoxMinkowskiDistPp) {} diff --git a/scipy/spatial/ckdtree/src/query_ball_point.cxx b/scipy/spatial/ckdtree/src/query_ball_point.cxx index 917d2be20d53..a3ebe0748a41 100644 --- a/scipy/spatial/ckdtree/src/query_ball_point.cxx +++ b/scipy/spatial/ckdtree/src/query_ball_point.cxx @@ -75,15 +75,8 @@ traverse_checking(const ckdtree *self, const ckdtree_intp_t start = lnode->start_idx; const ckdtree_intp_t end = lnode->end_idx; - CKDTREE_PREFETCH(data + indices[start] * m, 0, m); - if (start < end - 1) - CKDTREE_PREFETCH(data + indices[start+1] * m, 0, m); - for (i = start; i < end; ++i) { - if (i < end -2 ) - CKDTREE_PREFETCH(data + indices[i+2] * m, 0, m); - d = MinMaxDist::point_point_p(self, data + indices[i] * m, tpt, p, m, tub); if (d <= tub) { @@ -124,9 +117,9 @@ query_ball_point(const ckdtree *self, const double *x, for (ckdtree_intp_t i=0; i < n_queries; ++i) { const ckdtree_intp_t m = self->m; Rectangle rect(m, self->raw_mins, self->raw_maxes); - if (CKDTREE_LIKELY(self->raw_boxsize_data == NULL)) { + if (self->raw_boxsize_data == NULL) { Rectangle point(m, x + i * m, x + i * m); - HANDLE(CKDTREE_LIKELY(p == 2), MinkowskiDistP2) + HANDLE(p == 2, MinkowskiDistP2) HANDLE(p == 1, MinkowskiDistP1) HANDLE(std::isinf(p), MinkowskiDistPinf) HANDLE(1, MinkowskiDistPp) @@ -137,7 +130,7 @@ query_ball_point(const ckdtree *self, const double *x, for(j=0; jraw_boxsize_data[j]); } - HANDLE(CKDTREE_LIKELY(p == 2), BoxMinkowskiDistP2) + HANDLE(p == 2, BoxMinkowskiDistP2) HANDLE(p == 1, BoxMinkowskiDistP1) HANDLE(std::isinf(p), BoxMinkowskiDistPinf) HANDLE(1, BoxMinkowskiDistPp) diff --git a/scipy/spatial/ckdtree/src/query_ball_tree.cxx b/scipy/spatial/ckdtree/src/query_ball_tree.cxx index b0af311f289c..9e87e0ff5c43 100644 --- a/scipy/spatial/ckdtree/src/query_ball_tree.cxx +++ b/scipy/spatial/ckdtree/src/query_ball_tree.cxx @@ -90,28 +90,12 @@ traverse_checking(const ckdtree *self, const ckdtree *other, const ckdtree_intp_t end1 = lnode1->end_idx; const ckdtree_intp_t end2 = lnode2->end_idx; - CKDTREE_PREFETCH(sdata + sindices[start1] * m, 0, m); - - if (start1 < end1 - 1) - CKDTREE_PREFETCH(sdata + sindices[start1+1] * m, 0, m); - for (i = start1; i < end1; ++i) { - if (i < end1 - 2) - CKDTREE_PREFETCH(sdata + sindices[i+2] * m, 0, m); - - CKDTREE_PREFETCH(odata + oindices[start2] * m, 0, m); - - if (start2 < end2 - 1) - CKDTREE_PREFETCH(odata + oindices[start2+1] * m, 0, m); - auto &results_i = results[sindices[i]]; for (j = start2; j < end2; ++j) { - if (j < end2 - 2) - CKDTREE_PREFETCH(odata + oindices[j+2] * m, 0, m); - d = MinMaxDist::point_point_p( self, sdata + sindices[i] * m, @@ -195,14 +179,14 @@ query_ball_tree(const ckdtree *self, const ckdtree *other, Rectangle r1(self->m, self->raw_mins, self->raw_maxes); Rectangle r2(other->m, other->raw_mins, other->raw_maxes); - if(CKDTREE_LIKELY(self->raw_boxsize_data == NULL)) { - HANDLE(CKDTREE_LIKELY(p == 2), MinkowskiDistP2) + if (self->raw_boxsize_data == NULL) { + HANDLE(p == 2, MinkowskiDistP2) HANDLE(p == 1, MinkowskiDistP1) HANDLE(std::isinf(p), MinkowskiDistPinf) HANDLE(1, MinkowskiDistPp) {} } else { - HANDLE(CKDTREE_LIKELY(p == 2), BoxMinkowskiDistP2) + HANDLE(p == 2, BoxMinkowskiDistP2) HANDLE(p == 1, BoxMinkowskiDistP1) HANDLE(std::isinf(p), BoxMinkowskiDistPinf) HANDLE(1, BoxMinkowskiDistPp) diff --git a/scipy/spatial/ckdtree/src/query_pairs.cxx b/scipy/spatial/ckdtree/src/query_pairs.cxx index 90d8f495dd2c..d3835fb12491 100644 --- a/scipy/spatial/ckdtree/src/query_pairs.cxx +++ b/scipy/spatial/ckdtree/src/query_pairs.cxx @@ -106,31 +106,17 @@ traverse_checking(const ckdtree *self, const ckdtree_intp_t end1 = lnode1->end_idx; const ckdtree_intp_t end2 = lnode2->end_idx; - CKDTREE_PREFETCH(data+indices[start1]*m, 0, m); - if (start1 < end1 - 1) - CKDTREE_PREFETCH(data+indices[start1+1]*m, 0, m); for(i = start1; i < end1; ++i) { - if (i < end1 - 2) - CKDTREE_PREFETCH(data+indices[i+2]*m, 0, m); - /* Special care here to avoid duplicate pairs */ if (node1 == node2) min_j = i + 1; else min_j = start2; - if (min_j < end2) - CKDTREE_PREFETCH(data+indices[min_j]*m, 0, m); - if (min_j < end2 - 1) - CKDTREE_PREFETCH(data+indices[min_j+1]*m, 0, m); - for (j = min_j; j < end2; ++j) { - if (j < end2 - 2) - CKDTREE_PREFETCH(data+indices[j+2]*m, 0, m); - d = MinMaxDist::point_point_p( self, data + indices[i] * m, @@ -215,14 +201,14 @@ query_pairs(const ckdtree *self, Rectangle r1(self->m, self->raw_mins, self->raw_maxes); Rectangle r2(self->m, self->raw_mins, self->raw_maxes); - if(CKDTREE_LIKELY(self->raw_boxsize_data == NULL)) { - HANDLE(CKDTREE_LIKELY(p == 2), MinkowskiDistP2) + if (self->raw_boxsize_data == NULL) { + HANDLE(p == 2, MinkowskiDistP2) HANDLE(p == 1, MinkowskiDistP1) HANDLE(std::isinf(p), MinkowskiDistPinf) HANDLE(1, MinkowskiDistPp) {} } else { - HANDLE(CKDTREE_LIKELY(p == 2), BoxMinkowskiDistP2) + HANDLE(p == 2, BoxMinkowskiDistP2) HANDLE(p == 1, BoxMinkowskiDistP1) HANDLE(std::isinf(p), BoxMinkowskiDistPinf) HANDLE(1, BoxMinkowskiDistPp) diff --git a/scipy/spatial/ckdtree/src/rectangle.h b/scipy/spatial/ckdtree/src/rectangle.h index 39e9e0591c55..352def0bbb7e 100644 --- a/scipy/spatial/ckdtree/src/rectangle.h +++ b/scipy/spatial/ckdtree/src/rectangle.h @@ -128,7 +128,7 @@ template p = _p; /* internally we represent all distances as distance ** p */ - if (CKDTREE_LIKELY(p == 2.0)) + if (p == 2.0) upper_bound = _upper_bound * _upper_bound; else if ((!std::isinf(p)) && (!std::isinf(_upper_bound))) upper_bound = std::pow(_upper_bound,p); @@ -136,7 +136,7 @@ template upper_bound = _upper_bound; /* fiddle approximation factor */ - if (CKDTREE_LIKELY(p == 2.0)) { + if (p == 2.0) { double tmp = 1. + eps; epsfac = 1. / (tmp*tmp); } @@ -212,7 +212,7 @@ template subnomial = subnomial || ((min2 != 0 && min2 < inaccurate_distance_limit) || max2 < inaccurate_distance_limit); subnomial = subnomial || (min_distance < inaccurate_distance_limit || max_distance < inaccurate_distance_limit); - if (CKDTREE_UNLIKELY(subnomial)) { + if (subnomial) { MinMaxDist::rect_rect_p(tree, rect1, rect2, p, &min_distance, &max_distance); } else { min_distance += (min2 - min1); @@ -235,7 +235,7 @@ template --stack_size; /* assert stack_size >= 0 */ - if (CKDTREE_UNLIKELY(stack_size < 0)) { + if (stack_size < 0) { const char *msg = "Bad stack size. This error should never occur."; throw std::logic_error(msg); } diff --git a/scipy/spatial/ckdtree/src/sparse_distances.cxx b/scipy/spatial/ckdtree/src/sparse_distances.cxx index 1229d1f7bf56..fa7537e0b630 100644 --- a/scipy/spatial/ckdtree/src/sparse_distances.cxx +++ b/scipy/spatial/ckdtree/src/sparse_distances.cxx @@ -23,6 +23,7 @@ traverse(const ckdtree *self, const ckdtree *other, if (tracker->min_distance > tracker->upper_bound) return; + else if (node1->split_dim == -1) { /* 1 is leaf node */ if (node2->split_dim == -1) { /* 1 & 2 are leaves */ @@ -39,24 +40,11 @@ traverse(const ckdtree *self, const ckdtree *other, const ckdtree_intp_t end1 = node1->end_idx; const ckdtree_intp_t end2 = node2->end_idx; - CKDTREE_PREFETCH(sdata + sindices[start1] * m, 0, m); - if (start1 < end1 - 1) - CKDTREE_PREFETCH(sdata + sindices[start1+1] * m, 0, m); for (ckdtree_intp_t i = start1; i < end1; ++i) { - if (i < end1 - 2) - CKDTREE_PREFETCH(sdata + sindices[i+2] * m, 0, m); - - CKDTREE_PREFETCH(odata + oindices[start2] * m, 0, m); - if (start2 < end2 - 1) - CKDTREE_PREFETCH(sdata + oindices[start2+1] * m, 0, m); - for (ckdtree_intp_t j = start2; j < end2; ++j) { - if (j < end2 - 2) - CKDTREE_PREFETCH(odata + oindices[j+2] * m, 0, m); - double d = MinMaxDist::point_point_p( self, sdata + sindices[i] * m, @@ -64,7 +52,7 @@ traverse(const ckdtree *self, const ckdtree *other, p, m, tub); if (d <= tub) { - if (CKDTREE_LIKELY(p == 2.0)) + if (p == 2.0) d = std::sqrt(d); else if ((p != 1) && (!std::isinf(p))) d = std::pow(d, 1. / p); @@ -136,14 +124,14 @@ sparse_distance_matrix(const ckdtree *self, const ckdtree *other, Rectangle r1(self->m, self->raw_mins, self->raw_maxes); Rectangle r2(other->m, other->raw_mins, other->raw_maxes); - if(CKDTREE_LIKELY(self->raw_boxsize_data == NULL)) { - HANDLE(CKDTREE_LIKELY(p == 2), MinkowskiDistP2) + if(self->raw_boxsize_data == NULL) { + HANDLE(p == 2, MinkowskiDistP2) HANDLE(p == 1, MinkowskiDistP1) HANDLE(std::isinf(p), MinkowskiDistPinf) HANDLE(1, MinkowskiDistPp) {} } else { - HANDLE(CKDTREE_LIKELY(p == 2), BoxMinkowskiDistP2) + HANDLE(p == 2, BoxMinkowskiDistP2) HANDLE(p == 1, BoxMinkowskiDistP1) HANDLE(std::isinf(p), BoxMinkowskiDistPinf) HANDLE(1, BoxMinkowskiDistPp) diff --git a/scipy/spatial/distance.py b/scipy/spatial/distance.py index 8ed75b56d0d6..cd1b3045c570 100644 --- a/scipy/spatial/distance.py +++ b/scipy/spatial/distance.py @@ -2294,7 +2294,8 @@ def pdist(X, metric='euclidean', *, out=None, **kwargs): X = _asarray(X) if X.ndim != 2: - raise ValueError('A 2-dimensional array must be passed.') + raise ValueError('A 2-dimensional array must be passed. ' + f'(Shape was {X.shape}).') n = X.shape[0] return xpx.lazy_apply(_np_pdist, X, out, @@ -2303,7 +2304,7 @@ def pdist(X, metric='euclidean', *, out=None, **kwargs): kwargs.pop('V', None), kwargs.pop('VI', None), # See src/distance_pybind.cpp::pdist - shape=((n * (n - 1)) // 2, ), dtype=X.dtype, + shape=((n * (n - 1)) // 2, ), dtype=X.dtype, as_numpy=True, metric=metric, **kwargs) @@ -2706,7 +2707,7 @@ def num_obs_dm(d): -------- Find the number of original observations corresponding to a square redundant distance matrix d. - + >>> from scipy.spatial.distance import num_obs_dm >>> d = [[0, 100, 200], [100, 0, 150], [200, 150, 0]] >>> num_obs_dm(d) @@ -2736,7 +2737,7 @@ def num_obs_y(Y): -------- Find the number of original observations corresponding to a condensed distance matrix Y. - + >>> from scipy.spatial.distance import num_obs_y >>> Y = [1, 2, 3.5, 7, 10, 4] >>> num_obs_y(Y) diff --git a/scipy/spatial/meson.build b/scipy/spatial/meson.build index 1f46d1c7c53d..2718ddc59c4f 100644 --- a/scipy/spatial/meson.build +++ b/scipy/spatial/meson.build @@ -10,37 +10,16 @@ spt_cython_gen = generator(cython, output : '@BASENAME@.c', depends : [_cython_tree, _spatial_pxd, _lib_pxd, cython_lapack_pxd]) -qhull_src = [ - 'qhull_src/src/geom2_r.c', - 'qhull_src/src/geom_r.c', - 'qhull_src/src/global_r.c', - 'qhull_src/src/io_r.c', - 'qhull_src/src/libqhull_r.c', - 'qhull_src/src/mem_r.c', - 'qhull_src/src/merge_r.c', - 'qhull_src/src/poly2_r.c', - 'qhull_src/src/poly_r.c', - 'qhull_src/src/qset_r.c', - 'qhull_src/src/random_r.c', - 'qhull_src/src/rboxlib_r.c', - 'qhull_src/src/stat_r.c', - 'qhull_src/src/user_r.c', - 'qhull_src/src/usermem_r.c', - 'qhull_src/src/userprintf_r.c', - 'qhull_src/src/userprintf_rbox_r.c' -] - py3.extension_module('_qhull', [spt_cython_gen.process('_qhull.pyx'), - 'qhull_misc.h', 'qhull_misc.c'] + qhull_src, + 'qhull_misc.h', 'qhull_misc.c'], c_args: cython_c_args, include_directories: [ '../_lib', '../_build_utils/src', - 'qhull_src/src' ], link_args: version_link_args, - dependencies: [np_dep], + dependencies: [np_dep, qhull_r_dep], install: true, subdir: 'scipy/spatial' ) @@ -105,12 +84,6 @@ py3.extension_module('_hausdorff', subdir: 'scipy/spatial' ) -py3.install_sources([ - 'qhull_src/COPYING.txt' - ], - subdir: 'scipy/spatial/qhull_src' -) - py3.install_sources([ '_qhull.pyi', '_voronoi.pyi', diff --git a/scipy/spatial/qhull_misc.c b/scipy/spatial/qhull_misc.c index 9b8eafc7cfee..94696eb41eae 100644 --- a/scipy/spatial/qhull_misc.c +++ b/scipy/spatial/qhull_misc.c @@ -1,10 +1,9 @@ -#include "qhull_src/src/qhull_ra.h" +#include - -/* This is a patched version of qhull_src/src/user_r.c:qh_new_qhull, +/* This is a patched version of subprojects/qhull_r/libqhull_r/user_r.c:qh_new_qhull, with an additional "feaspoint" argument. - See qhull_src/README.scipy + See scipy/spatial/qhull_misc_README.txt */ int qh_new_qhull_scipy(qhT *qh, int dim, int numpoints, coordT *points, boolT ismalloc, char *qhull_cmd, FILE *outfile, FILE *errfile, coordT* feaspoint) { diff --git a/scipy/spatial/qhull_misc.h b/scipy/spatial/qhull_misc.h index 041b2a22c1d7..4a0a4a76d91e 100644 --- a/scipy/spatial/qhull_misc.h +++ b/scipy/spatial/qhull_misc.h @@ -9,7 +9,7 @@ #define qhull_misc_lib_check() QHULL_LIB_CHECK -#include "qhull_src/src/libqhull_r.h" +#include int qh_new_qhull_scipy(qhT *qh, int dim, int numpoints, coordT *points, boolT ismalloc, char *qhull_cmd, FILE *outfile, FILE *errfile, coordT* feaspoint); diff --git a/scipy/spatial/qhull_src/README.scipy b/scipy/spatial/qhull_misc_README.txt similarity index 81% rename from scipy/spatial/qhull_src/README.scipy rename to scipy/spatial/qhull_misc_README.txt index c2d270a3c2b4..aeb56ff39b18 100644 --- a/scipy/spatial/qhull_src/README.scipy +++ b/scipy/spatial/qhull_misc_README.txt @@ -1,12 +1,9 @@ -The directory ./qhull_src/src ships unmodified Qhull 2019.1 "_r" -version source code. - -The file scipy/spatial/qhull_misc.c additionally contains a function +The file scipy/spatial/qhull_misc.c contains a function "qh_new_qhull_scipy" derived from Qhull sources, via the following patch: ---- a/scipy/spatial/qhull_src/src/user_r.c -+++ b/scipy/spatial/qhull_src/src/user_r.c +--- a/subprojects/qhull_r/libqhull_r/user_r.c ++++ b/subprojects/qhull_r/libqhull_r/user_r.c @@ -122,7 +122,7 @@ An example of using qh_new_qhull is user_eg_r.c */ @@ -16,8 +13,7 @@ patch: /* gcc may issue a "might be clobbered" warning for dim, points, and ismalloc [-Wclobbered]. These parameters are not referenced after a longjmp() and hence not clobbered. See http://stackoverflow.com/questions/7721854/what-sense-do-these-clobbered-variable-warnings-make */ -@@ -158,7 +158,26 @@ int qh_new_qhull(qhT *qh, int dim, int numpoints, coordT *points, boolT ismalloc - /* points is an array of halfspaces, +@@ -159,6 +159,26 @@ int qh_new_qhull(qhT *qh, int dim, int numpoints, coordT *points, boolT ismalloc the last coordinate of each halfspace is its offset */ hulldim= dim-1; - qh_setfeasible(qh, hulldim); diff --git a/scipy/spatial/qhull_src/README.txt b/scipy/spatial/qhull_src/README.txt deleted file mode 100644 index eb18af9e02e5..000000000000 --- a/scipy/spatial/qhull_src/README.txt +++ /dev/null @@ -1,659 +0,0 @@ -Name - - qhull, rbox 2019.1 2019/06/21 - -Convex hull, Delaunay triangulation, Voronoi diagrams, Halfspace intersection - - Documentation: - html/index.htm - - - Available from: - - - (git@github.com:qhull/qhull.git) - - News and a paper: - - - - Version 1 (simplicial only): - - -Purpose - - Qhull is a general dimension convex hull program that reads a set - of points from stdin, and outputs the smallest convex set that contains - the points to stdout. It also generates Delaunay triangulations, Voronoi - diagrams, furthest-site Voronoi diagrams, and halfspace intersections - about a point. - - Rbox is a useful tool in generating input for Qhull; it generates - hypercubes, diamonds, cones, circles, simplices, spirals, - lattices, and random points. - - Qhull produces graphical output for Geomview. This helps with - understanding the output. - -Environment requirements - - Qhull and rbox should run on all 32-bit and 64-bit computers. Use - an ANSI C or C++ compiler to compile the program. The software is - self-contained. It comes with examples and test scripts. - - Qhull's C++ interface uses the STL. The C++ test program uses QTestLib - from the Qt Framework. - - Qhull is copyrighted software. Please read COPYING.txt and REGISTER.txt - before using or distributing Qhull. - -To cite Qhull, please use - - Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., "The Quickhull - algorithm for convex hulls," ACM Trans. on Mathematical Software, - 22(4):469-483, Dec 1996, http://www.qhull.org. - -To modify Qhull, particularly the C++ interface - - Qhull is on GitHub - (http://github.com/qhull/qhull/wiki, git@github.com:qhull/qhull.git) - - For internal documentation, see html/qh-code.htm - -To install Qhull - - Qhull is precompiled for Windows 32-bit, otherwise it needs compilation. - - Qhull includes Makefiles for gcc and other targets, CMakeLists.txt for CMake, - .sln/.vcproj/.vcxproj files for Microsoft Visual Studio, and .pro files - for Qt Creator. It compiles under Windows with mingw. - - Install and build instructions follow. - - See the end of this document for a list of distributed files. - ------------------ -Index - -Installing Qhull on Windows 10, 8, 7 (32- or 64-bit), Windows XP, and Windows NT -Installing Qhull on Unix with gcc -Installing Qhull with CMake 2.6 or later -Installing Qhull with Qt -Working with Qhull's C++ interface -Calling Qhull from C programs -Compiling Qhull with Microsoft Visual C++ -Compiling Qhull with Qt Creator -Compiling Qhull with mingw on Windows -Compiling Qhull with cygwin on Windows -Compiling from Makfile without gcc -Compiling on other machines and compilers -Distributed files -Authors - ------------------ -Installing Qhull on Windows 10, 8, 7 (32- or 64-bit), Windows XP, and Windows NT - - The zip file contains rbox.exe, qhull.exe, qconvex.exe, qdelaunay.exe, - qhalf.exe, qvoronoi.exe, testqset.exe, user_eg*.exe, documentation files, - and source files. Qhull.exe and user-eg3.exe are compiled with the reentrant - library while the other executables use the non-reentrant library. - - To install Qhull: - - Unzip the files into a directory (e.g., named 'qhull') - - Click on QHULL-GO or open a command window into Qhull's bin directory. - - Test with 'rbox D4 | qhull' - - To uninstall Qhull - - Delete the qhull directory - - To learn about Qhull: - - Execute 'qconvex' for a synopsis and examples. - Or 'qconvex --help' or 'qconvex -?' - - Execute 'rbox 10 | qconvex' to compute the convex hull of 10 random points. - - Execute 'rbox 10 | qconvex i TO file' to write results to 'file'. - - Browse the documentation: qhull\html\index.htm - - If an error occurs, Windows sends the error to stdout instead of stderr. - Use 'TO xxx' to send normal output to xxx - - To improve the command window - - Double-click the window bar to increase the size of the window - - Right-click the window bar - - Select Properties - - Check QuickEdit Mode - Select text with right-click or Enter - Paste text with right-click - - Change Font to Lucinda Console - - Change Layout to Screen Buffer Height 999, Window Size Height 55 - - Change Colors to Screen Background White, Screen Text Black - - Click OK - - Select 'Modify shortcut that started this window', then OK - - If you regularly use qhull on a Windows host, install a bash shell such as - https://gitforwindows.org/ # based on MSYS2 - https://github.com/git-for-windows/git/wiki - http://www.msys2.org/ - https://github.com/msys2/msys2/wiki - [mar'19] Git for Windows v2.21 requires 'qhull --help' - www.cygwin.com - www.mingw.org/wiki/msys # for Windows XP - Road Bash (www.qhull.org/bash) # based on MSYS - ------------------ -Installing Qhull on Unix with gcc - - To build Qhull, static libraries, shared library, and C++ interface - - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - make - - export LD_LIBRARY_PATH=$PWD/lib:$LD_LIBRARY_PATH - - The Makefiles may be edited for other compilers. - If 'testqset' exits with an error, qhull is broken - - A simple Makefile for Qhull is in src/libqhull and src/libqhull_r. - To build the Qhull executables and libqhullstatic - - Extract Qhull from qhull...tgz or qhull...zip - - cd src/libqhull_r # cd src/libqhull - - make - - ------------------ -Installing Qhull with CMake 2.6 or later - - See CMakeLists.txt for examples and further build instructions - - To build Qhull, static libraries, shared library, and C++ interface - - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - cd build - - cmake --help # List build generators - - make -G "" .. && cmake .. - - cmake .. - - make - - make install - - The ".." is important. It refers to the parent directory (i.e., qhull/) - - On Windows, CMake installs to C:/Program Files/qhull. 64-bit generators - have a "Win64" tag. Qhull's data structures are substantial larger as - 64-bit code than as 32-bit code. This may slow down Qhull. - - If creating a qhull package, please include a pkg-config file based on build/qhull*.pc.in - - If cmake fails with "No CMAKE_C_COMPILER could be found" - - cmake was not able to find the build environment specified by -G "..." - ------------------ -Installing Qhull with Qt - - To build Qhull, including its C++ test (qhulltest) - - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - Load src/qhull-all.pro into QtCreator - - Build - - qhulltest depends on shared libraries QtCore.a and QtTest.a. They may need to be copied - into the bin directory. On Windows, copy Qt5Core.dll and Qt5Test.dll, e.g., qt/5.11.2/msvc2017_64/bin - - If qhulltest fails without an error message, check for missing Q54Core.dll and Qt5Test.dll - -------------------- -Working with Qhull's C++ interface - - See html/qh-code.htm#cpp for calling Qhull from C++ programs - - See html/qh-code.htm#reentrant for converting from Qhull-2012 - - Examples of using the C++ interface - user_eg3_r.cpp - qhulltest/*_test.cpp - - Qhull's C++ interface is likely to change. Stay current with GitHub. - - To clone Qhull's next branch from http://github.com/qhull/qhull/wiki - git init - git clone git@github.com:qhull/qhull.git - cd qhull - git checkout next - ... - git pull origin next - - Compile qhullcpp and libqhullstatic_r with the same compiler. Both libraries - use the C routines setjmp() and longjmp() for error handling. They must - be compiled with the same compiler. - -------------------- -Calling Qhull from C programs - - See html/qh-code.htm#library for calling Qhull from C programs - - See html/qh-code.htm#reentrant for converting from Qhull-2012 - - Warning: You will need to understand Qhull's data structures and read the - code. Most users will find it easier to call Qhull as an external command. - - The reentrant 'C' code (src/libqhull_r), passes a pointer to qhT - to most Qhull routines. This allows multiple instances of Qhull to run - at the same time. It simplifies the C++ interface. - - The non-reentrant 'C' code (src/libqhull) looks unusual. It refers to - Qhull's global data structure, qhT, through a 'qh' macro (e.g., 'qh ferr'). - This allows the same code to use static memory or heap memory. - If qh_QHpointer is defined, qh_qh is a pointer to an allocated qhT; - otherwise qh_qh is a global static data structure of type qhT. - ------------------- -Compiling Qhull with Microsoft Visual C++ - - To compile 32-bit Qhull with Microsoft Visual C++ 2010 and later - - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - Load solution build/qhull-32.sln - - Right-click 'Retarget solution' from toolset v110 to your Platform Toolset - - Build target 'Win32' - - Project qhulltest requires Qt for DevStudio (http://www.qt.io) - Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/5.2.0/5.2.0/msvc2012) - If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified' - - Copy Qt shared libraries, QtCore.dll and QtTest.dll, into the bin directory - - To compile 64-bit Qhull with Microsoft Visual C++ 2010 and later - - 64-bit Qhull has larger data structures due to 64-bit pointers. This may slow down Qhull. - - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - Load solution build/qhull-64.sln - - Right-click 'Retarget solution' from toolset v110 to your Platform Toolset - - Build target 'x64' - - Project qhulltest requires Qt for DevStudio (http://www.qt.io) - Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/5.2.0/5.2.0/msvc2012_64) - If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified' - - If error -- MSB8020: The build tools for Visual Studio 2012 (Platform Toolset = 'v110') cannot be found. - - 'Project > Retarget solution' for both qhull-32.sln and qhull-64.sln - - 'Save All' both projects - - To compile Qhull with Microsoft Visual C++ 2005 (vcproj files) - - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - Load solution build/qhull.sln - - Build target 'win32' (not 'x64') - - Project qhulltest requires Qt for DevStudio (http://www.qt.io) - Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/4.7.4) - If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified' - ------------------ -Compiling Qhull with Qt Creator - - Qt (http://www.qt.io) is a C++ framework for Windows, Linux, and Macintosh - - Qhull uses QTestLib to test qhull's C++ interface (see src/qhulltest/) - - To compile Qhull with Qt Creator - - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - Download the Qt SDK - - Start Qt Creator - - Load src/qhull-all.pro - - Build - ------------------ -Compiling Qhull with mingw/gcc on Windows - - To compile Qhull with MINGW - - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - Install GitForWindows (https://gitforwindows.org/) - or MSYS2 (http://www.msys2.org/) - - Install MINGW-w64 - with gcc (https://mingw-w64.org/) - Download installer (https://sourceforge.net/projects/mingw-w64/files/) - Select i686/posix/dwarf - Install in C:\mingw-w64\... # Not 'Program Files\...' - Rename mingw32/bin/mingw32-make.exe to make.exe - Add the 'bin' directory to your $PATH environment variable - - Compile Qhull from the home directory - make help - make - - Notes - - Mingw is included with Qt SDK in qt/Tools/mingw53_32 - - For Windows XP - Install Road Bash (http://www.qhull.org/bash) or MSYS (http://www.mingw.org/wiki/msys) - Install MINGW (http://mingw.org/) - ------------------ -Compiling Qhull with cygwin on Windows - - To compile Qhull with cygwin - - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - Install cygwin (http://www.cygwin.com) - - Include packages for gcc, make, ar, and ln - - make - ------------------ -Compiling from Makfile without gcc - - The file, qhull-src.tgz, contains documentation and source files for - qhull and rbox. - - To unpack the tgz file - - tar zxf qhull-src.tgz - - cd qhull - - Use qhull/Makefile - Simpler Makefiles are qhull/src/libqhull/Makefile and qhull/src/libqhull_r/Makefile - - Compiling qhull and rbox with Makefile - - in Makefile, check the CC, CCOPTS1, PRINTMAN, and PRINTC defines - - the defaults are gcc and enscript - - CCOPTS1 should include the ANSI flag. It defines __STDC__ - - in user.h, check the definitions of qh_SECticks and qh_CPUclock. - - use '#define qh_CLOCKtype 2' for timing runs longer than 1 hour - - type: make - - this builds: qhull qconvex qdelaunay qhalf qvoronoi rbox libqhull.a libqhull_r.a - - type: make doc - - this prints the man page - - See also qhull/html/index.htm - - if your compiler reports many errors, it is probably not a ANSI C compiler - - you will need to set the -ansi switch or find another compiler - - if your compiler warns about missing prototypes for fprintf() etc. - - this is ok, your compiler should have these in stdio.h - - if your compiler warns about missing prototypes for memset() etc. - - include memory.h in qhull_a.h - - if your compiler reports "global.c: storage size of 'qh_qh' isn't known" - - delete the initializer "={0}" in global.c, stat.c and mem.c - - if your compiler warns about "stat.c: improper initializer" - - this is ok, the initializer is not used - - if you have trouble building libqhull.a with 'ar' - - try 'make -f Makefile.txt qhullx' - - if the code compiles, the qhull test case will automatically execute - - if an error occurs, there's an incompatibility between machines - - If you can, try a different compiler - - You can turn off the Qhull memory manager with qh_NOmem in mem.h - - You can turn off compiler optimization (-O2 in Makefile) - - If you find the source of the problem, please let us know - - to install the programs and their man pages: - - define MANDIR and BINDIR - - type 'make install' - - - if you have Geomview (www.geomview.org) - - try 'rbox 100 | qconvex G >a' and load 'a' into Geomview - - run 'q_eg' for Geomview examples of Qhull output (see qh-eg.htm) - ------------------- -Compiling on other machines and compilers - - Qhull may compile with Borland C++ 5.0 bcc32. A Makefile is included. - Execute 'cd src/libqhull; make -f Mborland'. If you use the Borland IDE, set - the ANSI option in Options:Project:Compiler:Source:Language-compliance. - - Qhull may compile with Borland C++ 4.02 for Win32 and DOS Power Pack. - Use 'cd src/libqhull; make -f Mborland -D_DPMI'. Qhull 1.0 compiles with - Borland C++ 4.02. For rbox 1.0, use "bcc32 -WX -w- -O2-e -erbox -lc rbox.c". - Use the same options for Qhull 1.0. [D. Zwick] - - If you have troubles with the memory manager, you can turn it off by - defining qh_NOmem in mem.h. - ------------------ -Distributed files - - README.txt // Instructions for installing Qhull - REGISTER.txt // Qhull registration - COPYING.txt // Copyright notice - QHULL-GO.lnk // Windows icon for eg/qhull-go.bat - Announce.txt // Announcement - CMakeLists.txt // CMake build file (2.6 or later) - CMakeModules/CheckLFS.cmake // enables Large File Support in cmake - File_id.diz // Package descriptor - index.htm // Home page - Makefile // Makefile for gcc and other compilers - qhull*.md5sum // md5sum for all files - - bin/* // Qhull executables and dll (.zip only) - build/config.cmake.in // extract target variables - build/qhull*.pc.in // pkg-config templates for qhull_r, qhull, and qhull_p - build/qhull-32.sln // 32-bit DevStudio solution and project files (2010 and later) - build/*-32.vcxproj - build/qhull-64.sln // 64-bit DevStudio solution and project files (2010 and later) - build/*-64.vcxproj - build/qhull.sln // DevStudio solution and project files (2005 and 2009) - build/*.vcproj - eg/* // Test scripts and geomview files from q_eg - html/index.htm // Manual - html/qh-faq.htm // Frequently asked questions - html/qh-get.htm // Download page - html/qhull-cpp.xml // C++ style notes as a Road FAQ (www.qhull.org/road) - src/Changes.txt // Change history for Qhull and rbox - src/qhull-all.pro // Qt project - -eg/ - q_benchmark // shell script for precision and performance benchmark - q_benchmark-ok.txt // reviewed output from q_benchmark - q_eg // shell script for Geomview examples (eg.01.cube) - q_egtest // shell script for Geomview test examples - q_test // shell script to test qhull - q_test.bat // Windows batch test for QHULL-GO.bat - // cd bin; ..\eg\q_test.bat >q_test.x 2>&1 - q_test-ok.txt // reviewed output from q_test - qhulltest-ok.txt // reviewed output from qhulltest (Qt only) - make-qhull_qh.sh // shell script to create non-reentrant qhull_qh from reentrant Qhull - make-vcproj.sh // shell script to create vcproj and vcxprog files - qhull-zip.sh // shell script to create distribution files - qtest.sh // shell script for testing and logging qhull - -rbox consists of (bin, html): - rbox.exe // Win32 executable (.zip only) - rbox.htm // html manual - rbox.man // Unix man page - rbox.txt - -qhull consists of (bin, html): - qconvex.exe // Win32 executables and dlls (.zip download only) - qhull.exe // Built with the reentrant library (about 2% slower) - qdelaunay.exe - qhalf.exe - qvoronoi.exe - qhull_r.dll - qhull-go.bat // command window - qconvex.htm // html manual - qdelaun.htm - qdelau_f.htm - qhalf.htm - qvoronoi.htm - qvoron_f.htm - qh-eg.htm - qh-code.htm - qh-impre.htm - index.htm - qh-opt*.htm - qh-quick.htm - qh--*.gif // images for manual - normal_voronoi_knauss_oesterle.jpg - qh_findbestfacet-drielsma.pdf - qhull.man // Unix man page - qhull.txt - -bin/ - msvcr80.dll // Visual C++ redistributable file (.zip download only) - -src/ - qhull/unix.c // Qhull and rbox applications using non-reentrant libqhullstatic.a - rbox/rbox.c - qconvex/qconvex.c - qhalf/qhalf.c - qdelaunay/qdelaunay.c - qvoronoi/qvoronoi.c - - qhull/unix_r.c // Qhull and rbox applications using reentrant libqhullstatic_r.a - rbox/rbox_r.c - qconvex/qconvex_r.c // Qhull applications built with reentrant libqhull_r/Makefile - qhalf/qhalf_r.c - qdelaunay/qdelaun_r.c - qvoronoi/qvoronoi_r.c - - user_eg/user_eg_r.c // example of using qhull_r.dll from a user program - user_eg2/user_eg2_r.c // example of using libqhullstatic_r.a from a user program - user_eg3/user_eg3_r.cpp // example of Qhull's C++ interface libqhullcpp with libqhullstatic_r.a - qhulltest/qhulltest.cpp // Test of Qhull's C++ interface using Qt's QTestLib - qhull-*.pri // Include files for Qt projects - testqset_r/testqset_r.c // Test of reentrant qset_r.c and mem_r.c - testqset/testqset.c // Test of non-rentrant qset.c and mem.c - -src/libqhull - libqhull.pro // Qt project for non-rentrant, shared library (qhull.dll) - index.htm // design documentation for libqhull - qh-*.htm - qhull-exports.def // Export Definition files for Visual C++ - qhull-nomerge-exports.def - qhull_p-exports.def - qhull_p-nomerge-exports.def - Makefile // Simple gcc Makefile for qhull and libqhullstatic.a - Mborland // Makefile for Borland C++ 5.0 - - libqhull.h // header file for qhull - user.h // header file of user definable constants - libqhull.c // Quickhull algorithm with partitioning - user.c // user re-definable functions - usermem.c - userprintf.c - userprintf_rbox.c - - qhull_a.h // include files for libqhull/*.c - geom.c // geometric routines - geom2.c - geom.h - global.c // global variables - io.c // input-output routines - io.h - mem.c // memory routines, this is stand-alone code - mem.h - merge.c // merging of non-convex facets - merge.h - poly.c // polyhedron routines - poly2.c - poly.h - qset.c // set routines, this only depends on mem.c - qset.h - random.c // utilities w/ Park & Miller's random number generator - random.h - rboxlib.c // point set generator for rbox - stat.c // statistics - stat.h - -src/libqhull_r - libqhull_r.pro // Qt project for rentrant, shared library (qhull_r.dll) - index.htm // design documentation for libqhull_r - qh-*_r.htm - qhull_r-exports.def // Export Definition files for Visual C++ - qhull_r-nomerge-exports.def - Makefile // Simple gcc Makefile for qhull and libqhullstatic.a - - libqhull_r.h // header file for qhull - user_r.h // header file of user definable constants - libqhull_r.c // Quickhull algorithm wi_r.hpartitioning - user_r.c // user re-definable functions - usermem.c - userprintf.c - userprintf_rbox.c - qhull_ra.h // include files for libqhull/*_r.c - geom_r.c // geometric routines - geom2.c - geom_r.h - global_r.c // global variables - io_r.c // input-output routines - io_r.h - mem_r.c // memory routines, this is stand-alone code - mem.h - merge_r.c // merging of non-convex facets - merge.h - poly_r.c // polyhedron routines - poly2.c - poly_r.h - qset_r.c // set routines, this only depends on mem_r.c - qset.h - random_r.c // utilities w/ Park & Miller's random number generator - random.h - rboxlib_r.c // point set generator for rbox - stat_r.c // statistics - stat.h - -src/libqhullcpp/ - libqhullcpp.pro // Qt project for renentrant, static C++ library - Qhull.cpp // Calls libqhull_r.c from C++ - Qhull.h - qt-qhull.cpp // Supporting methods for Qt - - Coordinates.cpp // input classes - Coordinates.h - - PointCoordinates.cpp - PointCoordinates.h - RboxPoints.cpp // call rboxlib.c from C++ - RboxPoints.h - - QhullFacet.cpp // data structure classes - QhullFacet.h - QhullHyperplane.cpp - QhullHyperplane.h - QhullPoint.cpp - QhullPoint.h - QhullQh.cpp - QhullRidge.cpp - QhullRidge.h - QhullVertex.cpp - QhullVertex.h - - QhullFacetList.cpp // collection classes - QhullFacetList.h - QhullFacetSet.cpp - QhullFacetSet.h - QhullIterator.h - QhullLinkedList.h - QhullPoints.cpp - QhullPoints.h - QhullPointSet.cpp - QhullPointSet.h - QhullSet.cpp - QhullSet.h - QhullSets.h - QhullVertexSet.cpp - QhullVertexSet.h - - functionObjects.h // supporting classes - QhullError.cpp - QhullError.h - QhullQh.cpp - QhullQh.h - QhullStat.cpp - QhullStat.h - RoadError.cpp // Supporting base classes - RoadError.h - RoadLogEvent.cpp - RoadLogEvent.h - usermem_r-cpp.cpp // Optional override for qh_exit() to throw an error - -src/libqhullstatic/ - libqhullstatic.pro // Qt project for non-reentrant, static library - -src/libqhullstatic_r/ - libqhullstatic_r.pro // Qt project for reentrant, static library - -src/qhulltest/ - qhulltest.pro // Qt project for test of C++ interface - Coordinates_test.cpp // Test of each class - PointCoordinates_test.cpp - Qhull_test.cpp - QhullFacet_test.cpp - QhullFacetList_test.cpp - QhullFacetSet_test.cpp - QhullHyperplane_test.cpp - QhullLinkedList_test.cpp - QhullPoint_test.cpp - QhullPoints_test.cpp - QhullPointSet_test.cpp - QhullRidge_test.cpp - QhullSet_test.cpp - QhullVertex_test.cpp - QhullVertexSet_test.cpp - RboxPoints_test.cpp - RoadTest.cpp // Run multiple test files with QTestLib - RoadTest.h - ------------------ -Authors - - C. Bradford Barber Hannu Huhdanpaa (Version 1.0) - bradb@shore.net hannu@qhull.org - - Qhull 1.0 and 2.0 were developed under NSF grants NSF/DMS-8920161 - and NSF-CCR-91-15793 750-7504 at the Geometry Center and Harvard - University. If you find Qhull useful, please let us know. diff --git a/scipy/spatial/tests/test_kdtree.py b/scipy/spatial/tests/test_kdtree.py index 8b912f249a22..c1c41db04ebe 100644 --- a/scipy/spatial/tests/test_kdtree.py +++ b/scipy/spatial/tests/test_kdtree.py @@ -426,6 +426,7 @@ def test_random_ball_vectorized(kdtree_type): assert_(isinstance(r[0, 0], list)) +@pytest.mark.fail_slow(5) def test_query_ball_point_multithreading(kdtree_type): np.random.seed(0) n = 5000 diff --git a/scipy/spatial/tests/test_qhull.py b/scipy/spatial/tests/test_qhull.py index 3ff733a7a6af..c22b91e7f65f 100644 --- a/scipy/spatial/tests/test_qhull.py +++ b/scipy/spatial/tests/test_qhull.py @@ -1220,6 +1220,72 @@ def test_halfspace_batch(self, k): ind2 = np.lexsort((expected_intersections[:, 1], expected_intersections[:, 0])) assert_allclose(actual_intersections[ind1], expected_intersections[ind2]) + + @pytest.mark.parametrize("halfspaces", [ + (np.array([-0.70613882, -0.45589431, 0.04178256])), + (np.array([[-0.70613882, -0.45589431, 0.04178256], + [0.70807342, -0.45464871, -0.45969769], + [0., 0.76515026, -0.35614825]])), + ]) + def test_gh_19865(self, halfspaces): + # starting off with a feasible interior point and + # adding halfspaces for which it is no longer feasible + # should result in an error rather than a problematic + # intersection polytope + initial_square = np.array( + [[1, 0, -1], [0, 1, -1], [-1, 0, -1], [0, -1, -1]] + ) + incremental_intersector = qhull.HalfspaceIntersection(initial_square, + np.zeros(2), + incremental=True) + with pytest.raises(qhull.QhullError, match="feasible.*-0.706.*"): + incremental_intersector.add_halfspaces(halfspaces) + + + def test_gh_19865_3d(self): + # 3d case where closed half space is enforced for + # feasibility + halfspaces = np.array([[1, 1, 1, -1], # doesn't exclude origin + [-1, -1, -1, -1], # doesn't exclude origin + [1, 0, 0, 0]]) # the origin is on the line + initial_cube = np.array([[1, 0, 0, -1], + [-1, 0, 0, -1], + [0, 1, 0, -1], + [0, -1, 0, -1], + [0, 0, 1, -1], + [0, 0, -1, -1]]) + incremental_intersector = qhull.HalfspaceIntersection(initial_cube, + np.zeros(3), + incremental=True) + with pytest.raises(qhull.QhullError, match="feasible.*[1 0 0 0]"): + incremental_intersector.add_halfspaces(halfspaces) + + + def test_2d_add_halfspace_input(self): + # incrementally added halfspaces should respect the 2D + # array shape requirement + initial_square = np.array( + [[1, 0, -1], [0, 1, -1], [-1, 0, -1], [0, -1, -1]] + ) + incremental_intersector = qhull.HalfspaceIntersection(initial_square, + np.zeros(2), + incremental=True) + with pytest.raises(ValueError, match="2D array"): + incremental_intersector.add_halfspaces(np.ones((4, 4, 4))) + + def test_1d_add_halfspace_input(self): + # we do allow 1D `halfspaces` input to add_halfspaces() + initial_square = np.array( + [[1, 0, -1], [0, 1, -1], [-1, 0, -1], [0, -1, -1]] + ) + incremental_intersector = qhull.HalfspaceIntersection(initial_square, + np.zeros(2), + incremental=True) + assert_allclose(incremental_intersector.dual_vertices, np.arange(4)) + incremental_intersector.add_halfspaces(np.array([2, 2, -1])) + assert_allclose(incremental_intersector.dual_vertices, np.arange(5)) + + @pytest.mark.parametrize("diagram_type", [Voronoi, qhull.Delaunay]) def test_gh_20623(diagram_type): rng = np.random.default_rng(123) diff --git a/scipy/spatial/transform/tests/test_rigid_transform.py b/scipy/spatial/transform/tests/test_rigid_transform.py index 9bcf5bbd22ee..183db247f665 100644 --- a/scipy/spatial/transform/tests/test_rigid_transform.py +++ b/scipy/spatial/transform/tests/test_rigid_transform.py @@ -1,13 +1,30 @@ import pytest import numpy as np -from numpy.testing import assert_allclose from scipy.spatial.transform import Rotation, RigidTransform from scipy.spatial.transform._rigid_transform import normalize_dual_quaternion +from scipy._lib._array_api import ( + is_lazy_array, + xp_vector_norm, + is_numpy, + xp_assert_close, +) +import scipy._lib.array_api_extra as xpx -def test_repr(): - actual = repr(RigidTransform.identity()) +pytestmark = pytest.mark.skip_xp_backends(np_only=True) + + +def rotation_to_xp(r: Rotation, xp): + return Rotation.from_quat(xp.asarray(r.as_quat())) + + +def rigid_transform_to_xp(r: RigidTransform, xp): + return RigidTransform.from_matrix(xp.asarray(r.as_matrix())) + + +def test_repr(xp): + actual = repr(RigidTransform.from_matrix(xp.eye(4))) expected = """\ RigidTransform.from_matrix(array([[1., 0., 0., 0.], [0., 1., 0., 0.], @@ -15,7 +32,8 @@ def test_repr(): [0., 0., 0., 1.]]))""" assert actual == expected - actual = repr(RigidTransform.identity(2)) + tf = RigidTransform.from_matrix(xp.asarray(RigidTransform.identity(2).as_matrix())) + actual = repr(tf) expected = """\ RigidTransform.from_matrix(array([[[1., 0., 0., 0.], [0., 1., 0., 0.], @@ -29,176 +47,240 @@ def test_repr(): assert actual == expected -def test_from_rotation(): +def test_from_rotation(xp): atol = 1e-12 # Test single rotation - r = Rotation.identity() + r = Rotation.from_matrix(xp.eye(3)) tf = RigidTransform.from_rotation(r) - assert_allclose(tf.as_matrix(), np.eye(4), atol=atol) + xp_assert_close(tf.as_matrix(), xp.eye(4), atol=atol) assert tf.single - r = Rotation.from_euler('z', 90, degrees=True) + r = Rotation.from_euler('z', xp.asarray(90), degrees=True) tf = RigidTransform.from_rotation(r) - assert_allclose(tf.as_matrix()[:3, :3], r.as_matrix(), atol=atol) - assert_allclose(tf.as_matrix()[:3, 3], [0, 0, 0], atol=atol) - assert_allclose(tf.as_matrix()[3], [0, 0, 0, 1], atol=atol) + xp_assert_close(tf.as_matrix()[:3, :3], r.as_matrix(), atol=atol) + xp_assert_close(tf.as_matrix()[:3, 3], xp.asarray([0.0, 0, 0]), atol=atol) + xp_assert_close(tf.as_matrix()[3, :], xp.asarray([0.0, 0, 0, 1]), atol=atol) assert tf.single # Test multiple rotations - r = Rotation.from_euler('zyx', [[90, 0, 0], [0, 90, 0]], degrees=True) + r = Rotation.from_euler('zyx', xp.asarray([[90, 0, 0], [0, 90, 0]]), degrees=True) tf = RigidTransform.from_rotation(r) - assert_allclose(tf.as_matrix()[:, :3, :3], r.as_matrix(), atol=atol) - assert_allclose(tf.as_matrix()[:, :3, 3], [[0, 0, 0], [0, 0, 0]], atol=atol) - assert_allclose(tf.as_matrix()[:, 3], [[0, 0, 0, 1], [0, 0, 0, 1]], atol=atol) + xp_assert_close(tf.as_matrix()[:, :3, :3], r.as_matrix(), atol=atol) + xp_assert_close(tf.as_matrix()[:, :3, 3], xp.asarray([[0.0, 0, 0], [0, 0, 0]]), + atol=atol) + xp_assert_close(tf.as_matrix()[:, 3, :], xp.asarray([[0.0, 0, 0, 1], [0, 0, 0, 1]]), + atol=atol) assert not tf.single -def test_from_translation(): +def test_from_translation(xp): # Test single translation - t = np.array([1, 2, 3]) + t = xp.asarray([1, 2, 3]) tf = RigidTransform.from_translation(t) - expected = np.eye(4) - expected[:3, 3] = t - assert_allclose(tf.as_matrix(), expected) + expected = xp.eye(4) + expected = xpx.at(expected)[..., :3, 3].set(t) + xp_assert_close(tf.as_matrix(), expected) assert tf.single # Test multiple translations - t = np.array([[1, 2, 3], [4, 5, 6]]) + t = xp.asarray([[1, 2, 3], [4, 5, 6]]) + tf = RigidTransform.from_translation(t) + for i in range(t.shape[0]): + expected = xp.eye(4) + expected = xpx.at(expected)[..., :3, 3].set(t[i, ...]) + xp_assert_close(tf.as_matrix()[i, ...], expected) + assert not tf.single + + +def test_from_translation_array_like(): + # Test single translation + t = [1, 2, 3] + tf = RigidTransform.from_translation(t) + tf_expected = RigidTransform.from_translation(np.array(t)) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix()) + assert tf.single + + # Test multiple translations + t = [[1, 2, 3], [4, 5, 6]] tf = RigidTransform.from_translation(t) - for i in range(len(t)): - expected = np.eye(4) - expected[:3, 3] = t[i] - assert_allclose(tf.as_matrix()[i], expected) + tf_expected = RigidTransform.from_translation(np.array(t)) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix()) assert not tf.single -def test_from_matrix(): +def test_from_matrix(xp): atol = 1e-12 # Test single transform matrix - matrix = np.eye(4) - matrix[:3, 3] = [1, 2, 3] + matrix = xp.eye(4) + matrix = xpx.at(matrix)[..., :3, 3].set(xp.asarray([1, 2, 3])) tf = RigidTransform.from_matrix(matrix) - assert_allclose(tf.as_matrix(), matrix, atol=atol) + xp_assert_close(tf.as_matrix(), matrix, atol=atol) assert tf.single # Test multiple transform matrices - matrices = np.array([np.eye(4)]*2) - matrices[0, :3, 3] = [1, 2, 3] - matrices[1, :3, 3] = [4, 5, 6] + matrices = xp.repeat(xp.eye(4)[None, ...], 2, axis=0) + matrices = xpx.at(matrices)[0, :3, 3].set(xp.asarray([1, 2, 3])) + matrices = xpx.at(matrices)[1, :3, 3].set(xp.asarray([4, 5, 6])) tf = RigidTransform.from_matrix(matrices) - assert_allclose(tf.as_matrix(), matrices, atol=atol) + xp_assert_close(tf.as_matrix(), matrices, atol=atol) assert not tf.single # Test non-1 determinant - matrix = np.diag([2, 2, 2, 1]) + matrix = xp.eye(4) + matrix = xpx.at(matrix)[..., :3, :3].set(xp.eye(3) * 2.0) tf = RigidTransform.from_matrix(matrix) - assert_allclose(tf.as_matrix(), np.eye(4), atol=atol) + xp_assert_close(tf.as_matrix(), xp.eye(4), atol=atol) # Test non-orthogonal rotation matrix - matrix = np.array([[1, 1, 0, 0], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1]]) - tf = RigidTransform.from_matrix(matrix) - expected = np.array([[0.894427, 0.447214, 0, 0], - [-0.447214, 0.894427, 0, 0], + matrix = xp.asarray([[1, 1, 0, 0], + [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) - assert_allclose(tf.as_matrix(), expected, atol=1e-6) + tf = RigidTransform.from_matrix(matrix) + expected = xp.asarray([[0.894427, 0.447214, 0, 0], + [-0.447214, 0.894427, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1]]) + xp_assert_close(tf.as_matrix(), expected, atol=1e-6) # Test invalid matrix - with pytest.raises(ValueError): - invalid = np.eye(4) - invalid[3, 3] = 2 # Invalid last row - RigidTransform.from_matrix(invalid) + invalid = xp.eye(4) + invalid = xpx.at(invalid)[..., 3, 3].set(2) # Invalid last row + if is_lazy_array(invalid): + tf = RigidTransform.from_matrix(invalid) + assert xp.all(xp.isnan(tf.as_matrix())) + else: + with pytest.raises(ValueError): + RigidTransform.from_matrix(invalid) -def test_from_components(): +def test_from_matrix_array_like(): + # Test single transform matrix + matrix = [[1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1]] + expected = np.eye(4) + tf = RigidTransform.from_matrix(matrix) + xp_assert_close(tf.as_matrix(), expected) + assert tf.single + + # Test multiple transform matrices + matrices = [matrix, matrix] + tf = RigidTransform.from_matrix(matrices) + for i in range(len(matrices)): + xp_assert_close(tf.as_matrix()[i, ...], expected) + assert not tf.single + + +def test_from_components(xp): atol = 1e-12 # Test single rotation and translation - t = np.array([1, 2, 3]) - r = Rotation.from_euler('zyx', [90, 0, 0], degrees=True) + t = xp.asarray([1, 2, 3]) + r = Rotation.from_euler("zyx", xp.asarray([90, 0, 0]), degrees=True) tf = RigidTransform.from_components(t, r) - expected = np.zeros((4, 4)) - expected[:3, :3] = r.as_matrix() - expected[:3, 3] = t - expected[3, 3] = 1 - assert_allclose(tf.as_matrix(), expected, atol=atol) + expected = xp.zeros((4, 4)) + expected = xpx.at(expected)[..., :3, :3].set(r.as_matrix()) + expected = xpx.at(expected)[..., :3, 3].set(t) + expected = xpx.at(expected)[..., 3, 3].set(1) + xp_assert_close(tf.as_matrix(), expected, atol=atol) assert tf.single # Test single rotation and multiple translations - t = np.array([[1, 2, 3], [4, 5, 6]]) - r = Rotation.from_euler('z', 90, degrees=True) + t = xp.asarray([[1, 2, 3], [4, 5, 6]]) + r = Rotation.from_euler('z', xp.asarray(90), degrees=True) tf = RigidTransform.from_components(t, r) assert not tf.single - for i in range(len(t)): - expected = np.zeros((4, 4)) - expected[:3, :3] = r.as_matrix() - expected[:3, 3] = t[i] - expected[3, 3] = 1 - assert_allclose(tf.as_matrix()[i], expected, atol=atol) + for i in range(t.shape[0]): + expected = xp.zeros((4, 4)) + expected = xpx.at(expected)[..., :3, :3].set(r.as_matrix()) + expected = xpx.at(expected)[..., :3, 3].set(t[i, ...]) + expected = xpx.at(expected)[..., 3, 3].set(1) + xp_assert_close(tf.as_matrix()[i, ...], expected, atol=atol) # Test multiple rotations and translations - t = np.array([[1, 2, 3], [4, 5, 6]]) - r = Rotation.from_euler('zyx', [[90, 0, 0], [0, 90, 0]], degrees=True) + t = xp.asarray([[1, 2, 3], [4, 5, 6]]) + r = Rotation.from_euler('zyx', xp.asarray([[90, 0, 0], [0, 90, 0]]), degrees=True) tf = RigidTransform.from_components(t, r) assert not tf.single - for i in range(len(t)): - expected = np.zeros((4, 4)) - expected[:3, :3] = r[i].as_matrix() - expected[:3, 3] = t[i] - expected[3, 3] = 1 - assert_allclose(tf.as_matrix()[i], expected, atol=atol) + for i in range(t.shape[0]): + expected = xp.zeros((4, 4)) + expected = xpx.at(expected)[..., :3, :3].set(r.as_matrix()[i, ...]) + expected = xpx.at(expected)[..., :3, 3].set(t[i, ...]) + expected = xpx.at(expected)[..., 3, 3].set(1) + xp_assert_close(tf.as_matrix()[i, ...], expected, atol=atol) -def test_as_components(): +def test_from_components_array_like(): + rng = np.random.default_rng(123) + # Test single rotation and translation + t = [1, 2, 3] + r = Rotation.random(rng=rng) + tf = RigidTransform.from_components(t, r) + tf_expected = RigidTransform.from_components(np.array(t), r) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12) + assert tf.single + + # Test multiple rotations and translations + t = [[1, 2, 3], [4, 5, 6]] + r = Rotation.random(len(t), rng=rng) + tf = RigidTransform.from_components(t, r) + tf_expected = RigidTransform.from_components(np.array(t), r) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12) + assert not tf.single + + + +def test_as_components(xp): atol = 1e-12 n = 10 rng = np.random.default_rng(123) - t = rng.normal(size=(n, 3)) - r = Rotation.random(n, rng=rng) + t = xp.asarray(rng.normal(size=(n, 3))) + r = rotation_to_xp(Rotation.random(n, rng=rng), xp=xp) tf = RigidTransform.from_components(t, r) new_t, new_r = tf.as_components() assert all(new_r.approx_equal(r, atol=atol)) - assert_allclose(new_t, t, atol=atol) + xp_assert_close(new_t, t, atol=atol) -def test_from_exp_coords(): +def test_from_exp_coords(xp): # example from 3.3 of # https://hades.mech.northwestern.edu/images/2/25/MR-v2.pdf angle1 = np.deg2rad(30.0) - tf1 = RigidTransform.from_matrix([ + mat = xp.asarray([ [np.cos(angle1), -np.sin(angle1), 0.0, 1.0], [np.sin(angle1), np.cos(angle1), 0.0, 2.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0] ]) + tf1 = RigidTransform.from_matrix(mat) angle2 = np.deg2rad(60.0) - tf2 = RigidTransform.from_matrix([ + mat = xp.asarray([ [np.cos(angle2), -np.sin(angle2), 0.0, 2.0], [np.sin(angle2), np.cos(angle2), 0.0, 1.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0] ]) + tf2 = RigidTransform.from_matrix(mat) expected = tf2 * tf1.inv() actual = RigidTransform.from_exp_coords( - np.deg2rad(30.0) * np.array([0.0, 0.0, 1.0, 3.37, -3.37, 0.0])) - assert_allclose(actual.as_matrix(), expected.as_matrix(), atol=1e-2) + np.deg2rad(30.0) * xp.asarray([0.0, 0.0, 1.0, 3.37, -3.37, 0.0])) + xp_assert_close(actual.as_matrix(), expected.as_matrix(), atol=1e-2) # test cases generated by comparison to pytransform3d - exp_coords = [ + exp_coords = xp.asarray([ [-2.01041204, -0.52983629, 0.65773501, 0.10386614, 0.05855009, 0.54959179], [-0.22537438, -0.24132627, -2.4747121, -0.09158594, 1.88075832, -0.03197204] - ] - expected_matrix = [ + ]) + expected_matrix = xp.asarray([ [[0.76406621, 0.10504613, -0.63652819, -0.10209961], [0.59956454, -0.47987325, 0.64050295, 0.40158789], [-0.2381705, -0.87102639, -0.42963687, 0.19637636], @@ -207,18 +289,18 @@ def test_from_exp_coords(): [-0.58017785, -0.78232107, 0.22664378, 0.52660831], [0.21909052, 0.11810973, 0.96852952, -0.02968529], [0., 0., 0., 1.]] - ] - assert_allclose( + ]) + xp_assert_close( RigidTransform.from_exp_coords(exp_coords).as_matrix(), expected_matrix, atol=1e-8) # identity - assert_allclose( - RigidTransform.from_exp_coords(np.zeros(6)).as_matrix(), - np.eye(4), atol=1e-12) + xp_assert_close( + RigidTransform.from_exp_coords(xp.zeros(6)).as_matrix(), + xp.eye(4), atol=1e-12) # only translation - expected_matrix = np.array([ + expected_matrix = xp.asarray([ [[1.0, 0.0, 0.0, 3.0], [0.0, 1.0, 0.0, -5.4], [0.0, 0.0, 1.0, 100.2], @@ -228,103 +310,130 @@ def test_from_exp_coords(): [0.0, 0.0, 1.0, 1.3], [0.0, 0.0, 0.0, 1.0]] ]) - actual = RigidTransform.from_exp_coords([ + actual = RigidTransform.from_exp_coords(xp.asarray([ [0.0, 0.0, 0.0, 3.0, -5.4, 100.2], [0.0, 0.0, 0.0, -3.0, 13.3, 1.3], - ]) - assert_allclose(actual.as_matrix(), expected_matrix, atol=1e-12) + ])) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12) # only rotation rot = Rotation.from_euler( 'zyx', - [[34, -12, 0.5], - [-102, -55, 30]], + xp.asarray([[34, -12, 0.5], + [-102, -55, 30]]), degrees=True) rotvec = rot.as_rotvec() - expected_matrix = np.array([np.eye(4), np.eye(4)]) - expected_matrix[:, :3, :3] = rot.as_matrix() + expected_matrix = xp.repeat(xp.eye(4)[None, ...], 2, axis=0) + expected_matrix = xpx.at(expected_matrix)[..., :3, :3].set(rot.as_matrix()) actual = RigidTransform.from_exp_coords( - np.hstack((rotvec, np.zeros((2, 3))))) - assert_allclose(actual.as_matrix(), expected_matrix, atol=1e-12) + xp.concat((rotvec, xp.zeros((2, 3))), axis=-1)) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12) + + +def test_from_exp_coords_array_like(): + rng = np.random.default_rng(123) + # Test single transform + t = np.array([1, 2, 3]) + r = Rotation.random(rng=rng) + tf_expected = RigidTransform.from_components(t, r) + exp_coords = tf_expected.as_exp_coords().tolist() + assert isinstance(exp_coords, list) + tf = RigidTransform.from_exp_coords(exp_coords) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12) + + # Test multiple transforms + t = [[1, 2, 3], [4, 5, 6]] + r = Rotation.random(len(t), rng=rng) + tf_expected = RigidTransform.from_components(t, r) + exp_coords = tf_expected.as_exp_coords().tolist() + assert isinstance(exp_coords, list) + tf = RigidTransform.from_exp_coords(exp_coords) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12) -def test_as_exp_coords(): +def test_as_exp_coords(xp): # identity - expected = np.zeros(6) + expected = xp.zeros(6) actual = RigidTransform.from_exp_coords(expected).as_exp_coords() - assert_allclose(actual, expected, atol=1e-12) + xp_assert_close(actual, expected, atol=1e-12) rng = np.random.default_rng(10) # pure rotation - rot_vec = rng.normal(scale=0.1, size=(1000, 3)) + rot_vec = xp.asarray(rng.normal(scale=0.1, size=(1000, 3))) tf = RigidTransform.from_rotation(Rotation.from_rotvec(rot_vec)) exp_coords = tf.as_exp_coords() - assert_allclose(exp_coords[:, :3], rot_vec, rtol=1e-13) - assert_allclose(exp_coords[:, 3:], 0.0, atol=1e-16) + xp_assert_close(exp_coords[:, :3], rot_vec, rtol=1e-13) + expected = xp.zeros_like(rot_vec) + xp_assert_close(exp_coords[:, 3:], expected, atol=1e-16) # pure translation - translation = rng.normal(scale=100.0, size=(1000, 3)) + translation = xp.asarray(rng.normal(scale=100.0, size=(1000, 3))) tf = RigidTransform.from_translation(translation) exp_coords = tf.as_exp_coords() - assert_allclose(exp_coords[:, :3], 0.0, atol=1e-16) - assert_allclose(exp_coords[:, 3:], translation, rtol=1e-15) + xp_assert_close(exp_coords[:, :3], expected, atol=1e-16) + xp_assert_close(exp_coords[:, 3:], translation, rtol=1e-15) -def test_from_dual_quat(): +def test_from_dual_quat(xp): # identity - assert_allclose( + xp_assert_close( RigidTransform.from_dual_quat( - np.array([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0])).as_matrix(), - np.eye(4), atol=1e-12) - assert_allclose( + xp.asarray([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0])).as_matrix(), + xp.eye(4), atol=1e-12) + xp_assert_close( RigidTransform.from_dual_quat( - np.array([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + xp.asarray([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), scalar_first=True).as_matrix(), - np.eye(4), atol=1e-12) + xp.eye(4), atol=1e-12) # only translation actual = RigidTransform.from_dual_quat( - [0, 0, 0, 1, 0.25, 0.15, -0.7, 0]) - expected_matrix = np.array([ + xp.asarray([0, 0, 0, 1, 0.25, 0.15, -0.7, 0])) + expected_matrix = xp.asarray([ [1, 0, 0, 0.5], [0, 1, 0, 0.3], [0, 0, 1, -1.4], [0, 0, 0, 1] ]) - assert_allclose(actual.as_matrix(), expected_matrix, atol=1e-12) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12) actual = RigidTransform.from_dual_quat( - [1, 0, 0, 0, 0, 0.25, 0.15, -0.7], scalar_first=True) - expected_matrix = np.array([ + xp.asarray([1, 0, 0, 0, 0, 0.25, 0.15, -0.7]), scalar_first=True) + expected_matrix = xp.asarray([ [1, 0, 0, 0.5], [0, 1, 0, 0.3], [0, 0, 1, -1.4], [0, 0, 0, 1] ]) - assert_allclose(actual.as_matrix(), expected_matrix, atol=1e-12) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12) # only rotation - actual_rot = Rotation.from_euler("xyz", [65, -13, 90], degrees=True) + actual_rot = Rotation.from_euler("xyz", xp.asarray([65, -13, 90]), degrees=True) actual = RigidTransform.from_dual_quat( - np.hstack((actual_rot.as_quat(), np.zeros(4)))) - expected_matrix = np.eye(4) - expected_matrix[:3, :3] = actual_rot.as_matrix() - assert_allclose(actual.as_matrix(), expected_matrix, atol=1e-12) + xp.concat((actual_rot.as_quat(), xp.zeros(4)), axis=-1)) + expected_matrix = xp.eye(4) + expected_matrix = xpx.at(expected_matrix)[..., :3, :3].set(actual_rot.as_matrix()) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12) actual = RigidTransform.from_dual_quat( - np.hstack((actual_rot.as_quat(scalar_first=True), np.zeros(4))), + xp.concat((actual_rot.as_quat(scalar_first=True), xp.zeros(4)), axis=-1), scalar_first=True) - expected_matrix = np.eye(4) - expected_matrix[:3, :3] = actual_rot.as_matrix() - assert_allclose(actual.as_matrix(), expected_matrix, atol=1e-12) + expected_matrix = xp.eye(4) + expected_matrix = xpx.at(expected_matrix)[..., :3, :3].set(actual_rot.as_matrix()) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12) # rotation and translation + # rtol is set to 1e-7 because xp_assert_close deviates from + # np.testing.assert_allclose in that it does not automatically default to 1e-7 for + # floating point inputs. + # See https://numpy.org/doc/2.2/reference/generated/numpy.testing.assert_allclose.html actual = RigidTransform.from_dual_quat( + xp.asarray( [[0.0617101, -0.06483886, 0.31432811, 0.94508498, 0.04985168, -0.26119618, 0.1691491, -0.07743254], [0.19507259, 0.49404931, -0.06091285, 0.8450749, - 0.65049656, -0.30782513, 0.16566752, 0.04174544]]) - expected_matrix = np.array( + 0.65049656, -0.30782513, 0.16566752, 0.04174544]])) + expected_matrix = xp.asarray( [[[0.79398752, -0.60213598, -0.08376202, 0.24605262], [0.58613113, 0.79477941, -0.15740392, -0.4932833], [0.16135089, 0.07588122, 0.98397557, 0.34262676], @@ -333,276 +442,319 @@ def test_from_dual_quat(): [0.08979911, 0.91647262, -0.3898898, -0.70540077], [-0.8587822, 0.26951399, 0.43572393, -0.47776265], [0., 0., 0., 1.]]]) - assert_allclose(actual.as_matrix(), expected_matrix, atol=1e-12) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12, rtol=1e-7) actual = RigidTransform.from_dual_quat( + xp.asarray( [[0.94508498, 0.0617101, -0.06483886, 0.31432811, -0.07743254, 0.04985168, -0.26119618, 0.1691491], [0.8450749, 0.19507259, 0.49404931, -0.06091285, - 0.04174544, 0.65049656, -0.30782513, 0.16566752]], + 0.04174544, 0.65049656, -0.30782513, 0.16566752]]), scalar_first=True) - assert_allclose(actual.as_matrix(), expected_matrix, atol=1e-12) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12, rtol=1e-7) # unnormalized dual quaternions # invalid real quaternion with norm 0 - actual = RigidTransform.from_dual_quat(np.zeros(8)) - assert_allclose(actual.as_matrix(), np.eye(4), atol=1e-12) + actual = RigidTransform.from_dual_quat(xp.zeros(8)) + xp_assert_close(actual.as_matrix(), xp.eye(4), atol=1e-12) # real quaternion with norm != 1 - unnormalized_dual_quat = np.array( + unnormalized_dual_quat = xp.asarray( [-0.2547655, 1.23506123, 0.20230088, 0.24247194, # norm 1.3 0.38559628, 0.08184063, 0.1755943, -0.1582222] # orthogonal ) - assert pytest.approx(np.linalg.norm(unnormalized_dual_quat[:4])) == 1.3 - assert pytest.approx(np.dot(unnormalized_dual_quat[:4], - unnormalized_dual_quat[4:]), abs=8) == 0.0 + xp_assert_close(xp_vector_norm(unnormalized_dual_quat[:4]), xp.asarray(1.3)[()], + atol=1e-12) + xp_assert_close(xp.vecdot(unnormalized_dual_quat[:4], + unnormalized_dual_quat[4:])[()], + xp.asarray(0.0)[()], atol=1e-8) + dual_quat = RigidTransform.from_dual_quat( unnormalized_dual_quat).as_dual_quat() - assert pytest.approx(np.linalg.norm(dual_quat[:4])) == 1.0 - assert pytest.approx(np.dot(dual_quat[:4], dual_quat[4:])) == 0.0 + xp_assert_close(xp_vector_norm(dual_quat[:4]), xp.asarray(1.0)[()], atol=1e-12) + xp_assert_close(xp.vecdot(dual_quat[:4], dual_quat[4:])[()], xp.asarray(0.0)[()], + atol=1e-12) # real and dual quaternion are not orthogonal - unnormalized_dual_quat = np.array( + unnormalized_dual_quat = xp.asarray( [0.20824458, 0.75098079, 0.54542913, -0.30849493, # unit norm -0.16051025, 0.10742978, 0.21277201, 0.20596935] # not orthogonal ) - assert pytest.approx(np.linalg.norm(unnormalized_dual_quat[:4])) == 1.0 - assert np.dot(unnormalized_dual_quat[:4], - unnormalized_dual_quat[4:]) != 0.0 + xp_assert_close(xp_vector_norm(unnormalized_dual_quat[:4]), xp.asarray(1.0)[()], + atol=1e-12) + assert xp.vecdot(unnormalized_dual_quat[:4], unnormalized_dual_quat[4:]) != 0.0 dual_quat = RigidTransform.from_dual_quat( unnormalized_dual_quat).as_dual_quat() - assert pytest.approx(np.linalg.norm(dual_quat[:4])) == 1.0 - assert pytest.approx(np.dot(dual_quat[:4], dual_quat[4:])) == 0.0 + xp_assert_close(xp_vector_norm(dual_quat[:4]), xp.asarray(1.0)[()], atol=1e-12) + xp_assert_close(xp.vecdot(dual_quat[:4], dual_quat[4:])[()], xp.asarray(0.0)[()], + atol=1e-12) # invalid real quaternion with norm 0, non-orthogonal dual quaternion - unnormalized_dual_quat = np.array( + unnormalized_dual_quat = xp.asarray( [0.0, 0.0, 0.0, 0.0, -0.16051025, 0.10742978, 0.21277201, 0.20596935]) - assert np.dot(np.array([0.0, 0.0, 0.0, 1.0]), - unnormalized_dual_quat[4:]) != 0.0 + assert xp.vecdot(xp.asarray([0.0, 0, 0, 1]), unnormalized_dual_quat[4:]) != 0.0 dual_quat = RigidTransform.from_dual_quat( unnormalized_dual_quat).as_dual_quat() - assert_allclose(dual_quat[:4], np.array([0, 0, 0, 1]), atol=1e-12) - assert pytest.approx(np.dot(dual_quat[:4], dual_quat[4:])) == 0.0 + xp_assert_close(dual_quat[:4], xp.asarray([0.0, 0, 0, 1]), atol=1e-12) + xp_assert_close(xp.vecdot(dual_quat[:4], dual_quat[4:])[()], xp.asarray(0.0)[()], + atol=1e-12) # compensation for precision loss in real quaternion rng = np.random.default_rng(1000) - t = rng.normal(size=(3,)) - r = Rotation.random(10, rng=rng) + t = xp.asarray(rng.normal(size=(3,))) + r = rotation_to_xp(Rotation.random(10, rng=rng), xp=xp) random_dual_quats = RigidTransform.from_components(t, r).as_dual_quat() # ensure that random quaternions are not normalized - random_dual_quats[:, :4] = random_dual_quats[:, :4].round(2) - assert not np.any(np.isclose( - np.linalg.norm(random_dual_quats[:, :4], axis=1), 1.0, atol=0.0001)) + random_dual_quats = xpx.at(random_dual_quats)[:, :4].add(0.01) + assert not xp.any(xpx.isclose(xp_vector_norm(random_dual_quats[:, :4], axis=1), + 1.0, atol=0.0001)) dual_quat_norm = RigidTransform.from_dual_quat( random_dual_quats).as_dual_quat() - assert_allclose( - np.linalg.norm(dual_quat_norm[:, :4], axis=1), 1.0, atol=1e-12) + expected = xp.ones(dual_quat_norm.shape[0]) + xp_assert_close(xp_vector_norm(dual_quat_norm[:, :4], axis=1), expected, atol=1e-12) # compensation for precision loss in dual quaternion, results in violation # of orthogonality constraint - t = rng.normal(size=(10, 3)) - r = Rotation.random(10, rng=rng) + t = xp.asarray(rng.normal(size=(10, 3))) + r = rotation_to_xp(Rotation.random(10, rng=rng), xp=xp) random_dual_quats = RigidTransform.from_components(t, r).as_dual_quat() # ensure that random quaternions are not normalized - random_dual_quats[:, 4:] = random_dual_quats[:, 4:].round(2) - assert not np.any(np.isclose( - np.einsum("ij,ij->i", - random_dual_quats[:, :4], - random_dual_quats[:, 4:]), - 0.0, atol=0.0001)) + random_dual_quats = xpx.at(random_dual_quats)[:, 4:].add(0.01) + q_norm = xp.vecdot(random_dual_quats[:, :4], random_dual_quats[:, 4:]) + assert not xp.any(xpx.isclose(q_norm, 0.0, atol=0.0001)) dual_quat_norm = RigidTransform.from_dual_quat( random_dual_quats).as_dual_quat() - assert_allclose( - np.einsum("ij,ij->i", dual_quat_norm[:, :4], dual_quat_norm[:, 4:]), - 0.0, atol=1e-12) - assert_allclose( - random_dual_quats[:, :4], dual_quat_norm[:, :4], atol=1e-12) + expected = xp.zeros(dual_quat_norm.shape[0]) + xp_assert_close(xp.vecdot(dual_quat_norm[:, :4], dual_quat_norm[:, 4:]), expected, + atol=1e-12) + xp_assert_close(random_dual_quats[:, :4], dual_quat_norm[:, :4], atol=1e-12) + + +def test_from_dual_quat_array_like(): + rng = np.random.default_rng(123) + # Test single transform + t = np.array([1, 2, 3]) + r = Rotation.random(rng=rng) + tf_expected = RigidTransform.from_components(t, r) + dual_quat = tf_expected.as_dual_quat().tolist() + assert isinstance(dual_quat, list) + tf = RigidTransform.from_dual_quat(dual_quat) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12) + + # Test multiple transforms + t = [[1, 2, 3], [4, 5, 6]] + r = Rotation.random(len(t), rng=rng) + tf_expected = RigidTransform.from_components(t, r) + dual_quat = tf_expected.as_dual_quat().tolist() + assert isinstance(dual_quat, list) + tf = RigidTransform.from_dual_quat(dual_quat) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12) -def test_as_dual_quat(): +def test_as_dual_quat(xp): # identity - expected = np.array([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]) - actual = RigidTransform.identity().as_dual_quat() - assert_allclose(actual, expected, atol=1e-12) + expected = xp.asarray([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]) + actual = xp.asarray(RigidTransform.identity().as_dual_quat()) + xp_assert_close(actual, expected, atol=1e-12) - expected = np.array([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - actual = RigidTransform.identity().as_dual_quat(scalar_first=True) - assert_allclose(actual, expected, atol=1e-12) + expected = xp.asarray([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + actual = xp.asarray(RigidTransform.identity().as_dual_quat(scalar_first=True)) + xp_assert_close(actual, expected, atol=1e-12) rng = np.random.default_rng(10) # only rotation for _ in range(10): - real_part = Rotation.random(rng=rng).as_quat() - dual_part = np.zeros(4) - expected = np.hstack((real_part, dual_part)) + real_part = xp.asarray(Rotation.random(rng=rng).as_quat()) + dual_part = xp.zeros(4) + expected = xp.concat((real_part, dual_part), axis=-1) actual = RigidTransform.from_dual_quat(expected).as_dual_quat() # because of double cover: - if np.sign(expected[0]) != np.sign(actual[0]): - actual *= -1.0 - assert_allclose(actual, expected, atol=1e-12) + if xp.sign(expected[0]) != xp.sign(actual[0]): + actual = -actual + xp_assert_close(actual, expected, atol=1e-12) # only translation for _ in range(10): - tf = rng.normal(size=3) - expected = np.hstack(([0, 0, 0, 1], 0.5 * tf, [0])) + tf = 0.5 * rng.normal(size=3) + expected = xp.asarray([0.0, 0, 0, 1, *tf.tolist(), 0]) actual = RigidTransform.from_dual_quat(expected).as_dual_quat() # because of double cover: - if np.sign(expected[0]) != np.sign(actual[0]): - actual *= -1.0 - assert_allclose(actual, expected, atol=1e-12) + if xp.sign(expected[0]) != xp.sign(actual[0]): + actual = -actual + xp_assert_close(actual, expected, atol=1e-12) # rotation and translation for _ in range(10): - t = rng.normal(size=3) - r = Rotation.random(rng=rng) + t = xp.asarray(rng.normal(size=3)) + r = rotation_to_xp(Rotation.random(rng=rng), xp=xp) expected = RigidTransform.from_components(t, r).as_dual_quat() actual = RigidTransform.from_dual_quat(expected).as_dual_quat() # because of double cover: - if np.sign(expected[0]) != np.sign(actual[0]): - actual *= -1.0 - assert_allclose(actual, expected, atol=1e-12) + if xp.sign(expected[0]) != xp.sign(actual[0]): + actual = -actual + xp_assert_close(actual, expected, atol=1e-12) -def test_from_as_internal_consistency(): +def test_from_as_internal_consistency(xp): atol = 1e-12 n = 1000 rng = np.random.default_rng(10) - t = rng.normal(size=(n, 3)) - r = Rotation.random(n, rng=rng) + t = xp.asarray(rng.normal(size=(n, 3))) + r = rotation_to_xp(Rotation.random(n, rng=rng), xp=xp) tf0 = RigidTransform.from_components(t, r) tf1 = RigidTransform.from_components(*tf0.as_components()) - assert_allclose(tf0.as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol) tf1 = RigidTransform.from_components(tf0.translation, tf0.rotation) - assert_allclose(tf0.as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol) tf1 = RigidTransform.from_exp_coords(tf0.as_exp_coords()) - assert_allclose(tf0.as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol) tf1 = RigidTransform.from_matrix(tf0.as_matrix()) - assert_allclose(tf0.as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol) tf1 = RigidTransform.from_dual_quat(tf0.as_dual_quat()) - assert_allclose(tf0.as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol) # exp_coords small rotation tf0 = RigidTransform.from_components( - rng.normal(scale=1000.0, size=(1000, 3)), - Rotation.from_rotvec(rng.normal(scale=1e-10, size=(1000, 3)))) + xp.asarray(rng.normal(scale=1000.0, size=(1000, 3))), + Rotation.from_rotvec(xp.asarray(rng.normal(scale=1e-10, size=(1000, 3))))) tf1 = RigidTransform.from_exp_coords(tf0.as_exp_coords()) - assert_allclose(tf0.as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol) def test_identity(): + # We do not use xp here because identity always returns numpy arrays atol = 1e-12 # Test single identity tf = RigidTransform.identity() - assert_allclose(tf.as_matrix(), np.eye(4), atol=atol) + xp_assert_close(tf.as_matrix(), np.eye(4), atol=atol) # Test multiple identities tf = RigidTransform.identity(5) - assert_allclose(tf.as_matrix(), np.array([np.eye(4)] * 5), atol=atol) + xp_assert_close(tf.as_matrix(), np.array([np.eye(4)] * 5), atol=atol) -def test_apply(): +def test_apply(xp): atol = 1e-12 ## Single transform - r = Rotation.from_euler('z', 90, degrees=True) - t = np.array([2, 3, 4]) + r = Rotation.from_euler('z', xp.asarray(90), degrees=True) + t = xp.asarray([2, 3, 4]) tf = RigidTransform.from_components(t, r) # Single vector, single transform - vec = np.array([1, 0, 0]) + vec = xp.asarray([1, 0, 0]) expected = t + r.apply(vec) res = tf.apply(vec) - assert_allclose(res, expected, atol=atol) + xp_assert_close(res, expected, atol=atol) # Multiple vectors, single transform - vecs = np.array([[1, 0, 0], [0, 1, 0]]) + vecs = xp.asarray([[1, 0, 0], [0, 1, 0]]) expected = t + r.apply(vecs) - assert_allclose(tf.apply(vecs), expected, atol=atol) + xp_assert_close(tf.apply(vecs), expected, atol=atol) ## Multiple transforms - r = Rotation.from_euler('z', [90, 0], degrees=True) - t = np.array([[2, 3, 4], [5, 6, 7]]) + r = Rotation.from_euler('z', xp.asarray([90, 0]), degrees=True) + t = xp.asarray([[2, 3, 4], [5, 6, 7]]) tf = RigidTransform.from_components(t, r) # Single vector, multiple transforms - vec = np.array([1, 0, 0]) + vec = xp.asarray([1, 0, 0]) expected = t + r.apply(vec) - assert_allclose(tf.apply(vec), expected, atol=atol) + xp_assert_close(tf.apply(vec), expected, atol=atol) # Multiple vectors, multiple transforms - vecs = np.array([[1, 0, 0], [0, 1, 0]]) + vecs = xp.asarray([[1, 0, 0], [0, 1, 0]]) expected = t + r.apply(vecs) - assert_allclose(tf.apply(vecs), expected, atol=atol) + xp_assert_close(tf.apply(vecs), expected, atol=atol) + + +def test_apply_array_like(): + rng = np.random.default_rng(123) + # Single vector + t = np.array([1, 2, 3]) + r = Rotation.random(rng=rng) + tf = RigidTransform.from_components(t, r) + vec = [1, 0, 0] + expected = t + r.apply(vec) + xp_assert_close(tf.apply(vec), expected, atol=1e-12) + # Multiple vectors + t = np.array([[1, 2, 3], [4, 5, 6]]) + r = Rotation.random(len(t), rng=rng) + tf = RigidTransform.from_components(t, r) + vec = [[1, 0, 0], [0, 1, 0]] + expected = t + r.apply(vec) + xp_assert_close(tf.apply(vec), expected, atol=1e-12) -def test_inverse_apply(): + +def test_inverse_apply(xp): atol = 1e-12 # Test applying inverse transform - t = np.array([1, 2, 3]) - r = Rotation.from_euler('z', 90, degrees=True) + t = xp.asarray([1, 2, 3]) + r = Rotation.from_euler('z', xp.asarray(90), degrees=True) tf = RigidTransform.from_components(t, r) # Test single vector - vec = np.array([1, 0, 0]) + vec = xp.asarray([1, 0, 0]) expected = tf.inv().apply(vec) - assert_allclose(tf.apply(vec, inverse=True), expected, atol=atol) + xp_assert_close(tf.apply(vec, inverse=True), expected, atol=atol) # Test multiple vectors - vecs = np.array([[1, 0, 0], [0, 1, 0]]) + vecs = xp.asarray([[1, 0, 0], [0, 1, 0]]) expected = tf.inv().apply(vecs) - assert_allclose(tf.apply(vecs, inverse=True), expected, atol=atol) + xp_assert_close(tf.apply(vecs, inverse=True), expected, atol=atol) -def test_rotation_alone(): +def test_rotation_alone(xp): atol = 1e-12 - r = Rotation.from_euler('z', 90, degrees=True) + r = Rotation.from_euler('z', xp.asarray(90), degrees=True) tf = RigidTransform.from_rotation(r) - vec = np.array([1, 0, 0]) + vec = xp.asarray([1, 0, 0]) expected = r.apply(vec) - assert_allclose(tf.apply(vec), expected, atol=atol) + xp_assert_close(tf.apply(vec), expected, atol=atol) -def test_translation_alone(): +def test_translation_alone(xp): atol = 1e-12 - t = np.array([1, 2, 3]) + t = xp.asarray([1.0, 2, 3]) tf = RigidTransform.from_translation(t) - vec = np.array([5, 6, 7]) + vec = xp.asarray([5.0, 6, 7]) expected = t + vec - assert_allclose(tf.apply(vec), expected, atol=atol) + xp_assert_close(tf.apply(vec), expected, atol=atol) -def test_composition(): +def test_composition(xp): atol = 1e-12 # Test composing single transforms - t1 = np.array([1, 0, 0]) - r1 = Rotation.from_euler('z', 90, degrees=True) + t1 = xp.asarray([1.0, 0, 0]) + r1 = Rotation.from_euler('z', xp.asarray(90), degrees=True) tf1 = RigidTransform.from_components(t1, r1) - t2 = np.array([0, 1, 0]) - r2 = Rotation.from_euler('x', 90, degrees=True) + t2 = xp.asarray([0, 1, 0]) + r2 = Rotation.from_euler('x', xp.asarray(90), degrees=True) tf2 = RigidTransform.from_components(t2, r2) composed = tf2 * tf1 - vec = np.array([1, 0, 0]) + vec = xp.asarray([1, 0, 0]) expected = tf2.apply(tf1.apply(vec)) - assert_allclose(composed.apply(vec), expected, atol=atol) + xp_assert_close(composed.apply(vec), expected, atol=atol) assert composed.single expected = t2 + r2.apply(t1 + r1.apply(vec)) - assert_allclose(composed.apply(vec), expected, atol=atol) + xp_assert_close(composed.apply(vec), expected, atol=atol) # Multiple transforms with single transform t2 = np.array([[1, 2, 3], [4, 5, 6]]) @@ -610,243 +762,275 @@ def test_composition(): composed = tf2 * tf1 expected = tf2.apply(tf1.apply(vec)) - assert_allclose(composed.apply(vec), expected, atol=atol) + xp_assert_close(composed.apply(vec), expected, atol=atol) assert not composed.single expected = t2 + r2.apply(t1 + r1.apply(vec)) - assert_allclose(composed.apply(vec), expected, atol=atol) + xp_assert_close(composed.apply(vec), expected, atol=atol) # Multiple transforms with multiple transforms - t1 = np.array([[1, 0, 0], [0, -1, 1]]) + t1 = xp.asarray([[1, 0, 0], [0, -1, 1]]) tf1 = RigidTransform.from_components(t1, r1) composed = tf2 * tf1 expected = tf2.apply(tf1.apply(vec)) - assert_allclose(composed.apply(vec), expected, atol=atol) + xp_assert_close(composed.apply(vec), expected, atol=atol) assert not composed.single expected = t2 + r2.apply(t1 + r1.apply(vec)) - assert_allclose(composed.apply(vec), expected, atol=atol) + xp_assert_close(composed.apply(vec), expected, atol=atol) -def test_pow(): +def test_pow(xp): atol = 1e-12 num = 10 rng = np.random.default_rng(100) - t = rng.normal(size=(num, 3)) - r = Rotation.random(num, rng=rng) + t = xp.asarray(rng.normal(size=(num, 3))) + r = rotation_to_xp(Rotation.random(num, rng=rng), xp=xp) p = RigidTransform.from_components(t, r) p_inv = p.inv() # Test the short-cuts and other integers for n in [-5, -2, -1, 0, 1, 2, 5]: q = p**n - r = RigidTransform.identity(num) + r = rigid_transform_to_xp(RigidTransform.identity(num), xp=xp) for _ in range(abs(n)): if n > 0: r = r * p else: r = r * p_inv - assert_allclose(q.as_matrix(), r.as_matrix(), atol=atol) + xp_assert_close(q.as_matrix(), r.as_matrix(), atol=atol) # Test shape preservation - r = RigidTransform.from_rotation(Rotation.from_quat([0, 0, 0, 1])) + r = RigidTransform.from_rotation(Rotation.from_quat(xp.asarray([0, 0, 0, 1]))) assert (r**n).as_matrix().shape == (4, 4) - r = RigidTransform.from_rotation(Rotation.from_quat([[0, 0, 0, 1]])) + r = RigidTransform.from_rotation(Rotation.from_quat(xp.asarray([[0, 0, 0, 1]]))) assert (r**n).as_matrix().shape == (1, 4, 4) # Test fractional powers q = p**0.5 - assert_allclose((q * q).as_matrix(), p.as_matrix(), atol=atol) + xp_assert_close((q * q).as_matrix(), p.as_matrix(), atol=atol) q = p**-0.5 - assert_allclose((q * q).as_matrix(), p.inv().as_matrix(), atol=atol) + xp_assert_close((q * q).as_matrix(), p.inv().as_matrix(), atol=atol) q = p** 1.5 - assert_allclose((q * q).as_matrix(), (p**3).as_matrix(), atol=atol) + xp_assert_close((q * q).as_matrix(), (p**3).as_matrix(), atol=atol) q = p** -1.5 - assert_allclose((q * q).as_matrix(), (p**-3).as_matrix(), atol=atol) + xp_assert_close((q * q).as_matrix(), (p**-3).as_matrix(), atol=atol) # pow function - tf = pow(RigidTransform.identity(), 2) - assert_allclose(tf.as_matrix(), np.eye(4), atol=atol) + tf = pow(RigidTransform.from_matrix(xp.eye(4)), 2) + xp_assert_close(tf.as_matrix(), xp.eye(4), atol=atol) -def test_pow_equivalence_with_rotation(): +def test_pow_equivalence_with_rotation(xp): atol = 1e-12 num = 10 rng = np.random.default_rng(100) - r = Rotation.random(num, rng=rng) + r = rotation_to_xp(Rotation.random(num, rng=rng), xp=xp) p = RigidTransform.from_rotation(r) for n in [-5, -2, -1.5, -1, -0.5, 0.0, 0.5, 1, 1.5, 2, 5]: - assert_allclose((p**n).rotation.as_matrix(), (r**n).as_matrix(), atol=atol) + xp_assert_close((p**n).rotation.as_matrix(), (r**n).as_matrix(), atol=atol) -def test_inverse(): +def test_inverse(xp): atol = 1e-12 # Test inverse transform - r = Rotation.from_euler('z', 90, degrees=True) - t = np.array([1, 2, 3]) + r = Rotation.from_euler('z', xp.asarray(90), degrees=True) + t = xp.asarray([1, 2, 3]) tf = RigidTransform.from_components(t, r) # Test that tf * tf.inv() equals identity tf_inv = tf.inv() composed = tf * tf_inv - assert_allclose(composed.as_matrix(), np.eye(4), atol=atol) + xp_assert_close(composed.as_matrix(), xp.eye(4), atol=atol) n = 10 rng = np.random.default_rng(1000) - t = rng.normal(size=(n, 3)) - r = Rotation.random(n, rng=rng) + t = xp.asarray(rng.normal(size=(n, 3))) + r = rotation_to_xp(Rotation.random(n, rng=rng), xp=xp) tf = RigidTransform.from_components(t, r) tf_inv = tf.inv() composed = tf * tf_inv - assert_allclose(composed.as_matrix(), np.array([np.eye(4)] * n), atol=atol) + expected = xp.repeat(xp.eye(4)[None, ...], n, axis=0) + xp_assert_close(composed.as_matrix(), expected, atol=atol) # Test multiple transforms - r = Rotation.from_euler('zyx', [[90, 0, 0], [0, 90, 0]], degrees=True) - t = np.array([[1, 2, 3], [4, 5, 6]]) + r = Rotation.from_euler('zyx', xp.asarray([[90, 0, 0], [0, 90, 0]]), degrees=True) + t = xp.asarray([[1, 2, 3], [4, 5, 6]]) tf = RigidTransform.from_components(t, r) tf_inv = tf.inv() composed = tf * tf_inv - assert_allclose(composed.as_matrix(), np.array([np.eye(4)] * 2), atol=atol) + expected = xp.repeat(xp.eye(4)[None, ...], 2, axis=0) + xp_assert_close(composed.as_matrix(), expected, atol=atol) -def test_properties(): +def test_properties(xp): atol = 1e-12 # Test rotation and translation properties for single transform - r = Rotation.from_euler('z', 90, degrees=True) - t = np.array([1, 2, 3]) + r = Rotation.from_euler('z', xp.asarray(90), degrees=True) + t = xp.asarray([1.0, 2, 3]) tf = RigidTransform.from_components(t, r) - assert_allclose(tf.rotation.as_matrix(), r.as_matrix(), atol=atol) + xp_assert_close(tf.rotation.as_matrix(), r.as_matrix(), atol=atol) assert tf.rotation.approx_equal(r) - assert_allclose(tf.translation, t, atol=atol) + xp_assert_close(tf.translation, t, atol=atol) # Test rotation and translation properties for multiple transforms - r = Rotation.from_euler('zyx', [[90, 0, 0], [0, 90, 0]], degrees=True) - t = np.array([[1, 2, 3], [4, 5, 6]]) + r = Rotation.from_euler('zyx', xp.asarray([[90, 0, 0], [0, 90, 0]]), degrees=True) + t = xp.asarray([[1.0, 2, 3], [4, 5, 6]]) tf = RigidTransform.from_components(t, r) - assert_allclose(tf.rotation.as_matrix(), r.as_matrix(), atol=atol) + xp_assert_close(tf.rotation.as_matrix(), r.as_matrix(), atol=atol) assert all(tf.rotation.approx_equal(r)) - assert_allclose(tf.translation, t, atol=atol) + xp_assert_close(tf.translation, t, atol=atol) -def test_indexing(): +def test_indexing(xp): atol = 1e-12 # Test indexing for multiple transforms - r = Rotation.from_euler('zyx', [[90, 0, 0], [0, 90, 0]], degrees=True) - t = np.array([[1, 2, 3], [4, 5, 6]]) + r = Rotation.from_euler('zyx', xp.asarray([[90, 0, 0], [0, 90, 0]]), degrees=True) + t = xp.asarray([[1.0, 2, 3], [4, 5, 6]]) tf = RigidTransform.from_components(t, r) # Test single index - assert_allclose(tf[0].as_matrix()[:3, :3], r[0].as_matrix(), atol=atol) - assert_allclose(tf[0].as_matrix()[:3, 3], t[0], atol=atol) + xp_assert_close(tf[0].as_matrix()[:3, :3], r[0].as_matrix(), atol=atol) + xp_assert_close(tf[0].as_matrix()[:3, 3], t[0, ...], atol=atol) # Test slice tf_slice = tf[0:2] - assert_allclose(tf_slice.as_matrix()[:, :3, :3], r[0:2].as_matrix(), atol=atol) - assert_allclose(tf_slice.as_matrix()[:, :3, 3], t[0:2], atol=atol) + xp_assert_close(tf_slice.as_matrix()[:, :3, :3], r[0:2].as_matrix(), atol=atol) + xp_assert_close(tf_slice.as_matrix()[:, :3, 3], t[0:2, ...], atol=atol) # Test boolean indexing - tf_masked = tf[[True, True]] - assert_allclose(tf_masked.as_matrix()[:, :3, :3], r.as_matrix(), atol=atol) - assert_allclose(tf_masked.as_matrix()[:, :3, 3], t, atol=atol) + tf_masked = tf[xp.asarray([True, True])] + xp_assert_close(tf_masked.as_matrix()[:, :3, :3], r.as_matrix(), atol=atol) + xp_assert_close(tf_masked.as_matrix()[:, :3, 3], t, atol=atol) + + tf_masked = tf[xp.asarray([False, True])] + xp_assert_close(tf_masked.as_matrix()[:, :3, :3], + r[xp.asarray([False, True])].as_matrix(), atol=atol) + xp_assert_close(tf_masked.as_matrix()[:, :3, 3], t[xp.asarray([False, True])], + atol=atol) + + tf_masked = tf[xp.asarray([False, False])] + assert len(tf_masked) == 0 + + +def test_indexing_array_like(): + atol = 1e-12 + + r = Rotation.from_euler('zyx', np.array([[90, 0, 0], [0, 90, 0]]), degrees=True) + t = np.array([[1.0, 2, 3], [4, 5, 6]]) + tf = RigidTransform.from_components(t, r) tf_masked = tf[[False, True]] - assert_allclose(tf_masked.as_matrix()[:, :3, :3], r[[False, True]].as_matrix(), + xp_assert_close(tf_masked.as_matrix()[:, :3, :3], r[[False, True]].as_matrix(), atol=atol) - assert_allclose(tf_masked.as_matrix()[:, :3, 3], t[[False, True]], atol=atol) - + xp_assert_close(tf_masked.as_matrix()[:, :3, 3], t[[False, True]], atol=atol) tf_masked = tf[[False, False]] assert len(tf_masked) == 0 -def test_concatenate(): +def test_concatenate(xp): atol = 1e-12 # Test concatenation of transforms - t1 = np.array([1, 0, 0]) - r1 = Rotation.from_euler('z', 90, degrees=True) + t1 = xp.asarray([1, 0, 0]) + r1 = Rotation.from_euler('z', xp.asarray(90), degrees=True) tf1 = RigidTransform.from_components(t1, r1) - t2 = np.array([0, 1, 0]) - r2 = Rotation.from_euler('x', 90, degrees=True) + t2 = xp.asarray([0, 1, 0]) + r2 = Rotation.from_euler('x', xp.asarray(90), degrees=True) tf2 = RigidTransform.from_components(t2, r2) # Concatenate single transforms concatenated1 = RigidTransform.concatenate([tf1, tf2]) - assert_allclose(concatenated1[0].as_matrix(), tf1.as_matrix(), atol=atol) - assert_allclose(concatenated1[1].as_matrix(), tf2.as_matrix(), atol=atol) + xp_assert_close(concatenated1[0].as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(concatenated1[1].as_matrix(), tf2.as_matrix(), atol=atol) # Concatenate multiple transforms concatenated2 = RigidTransform.concatenate([tf1, concatenated1]) - assert_allclose(concatenated2[0].as_matrix(), tf1.as_matrix(), atol=atol) - assert_allclose(concatenated2[1].as_matrix(), tf1.as_matrix(), atol=atol) - assert_allclose(concatenated2[2].as_matrix(), tf2.as_matrix(), atol=atol) + xp_assert_close(concatenated2[0].as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(concatenated2[1].as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(concatenated2[2].as_matrix(), tf2.as_matrix(), atol=atol) -def test_input_validation(): +def test_input_validation(xp): # Test invalid matrix shapes - inputs = [np.eye(3), np.zeros((4, 3)), [], np.zeros((1, 1, 4, 4))] + inputs = [xp.eye(3), xp.zeros((4, 3)), [], xp.zeros((1, 1, 4, 4))] for input in inputs: with pytest.raises(ValueError, match="Expected `matrix` to have shape"): RigidTransform.from_matrix(input) # Test invalid last row - with pytest.raises(ValueError, match="last row of transformation matrix 0"): - matrix = np.eye(4) - matrix[3, :] = [1, 0, 0, 1] - RigidTransform.from_matrix(matrix) + matrix = xp.eye(4) + matrix = xpx.at(matrix)[3, :].set(xp.asarray([1, 0, 0, 1])) + if is_lazy_array(matrix): + matrix = RigidTransform.from_matrix(matrix).as_matrix() + assert xp.all(xp.isnan(matrix)) + else: + with pytest.raises(ValueError, match="last row of transformation matrix 0"): + RigidTransform.from_matrix(matrix) # Test invalid last row for multiple transforms - with pytest.raises(ValueError, match="last row of transformation matrix 1"): - matrix = np.array([np.eye(4)] * 2) - matrix[1, 3, :] = [1, 0, 0, 1] - RigidTransform.from_matrix(matrix) + matrix = xp.zeros((2, 4, 4)) + matrix = xpx.at(matrix)[...].set(xp.eye(4)) + matrix = xpx.at(matrix)[1, 3, :].set(xp.asarray([1, 0, 0, 1])) + if is_lazy_array(matrix): + matrix = RigidTransform.from_matrix(matrix).as_matrix() + assert not xp.any(xp.isnan(matrix[0, ...])) + assert xp.all(xp.isnan(matrix[1, ...])) + else: + with pytest.raises(ValueError, match="last row of transformation matrix 1"): + RigidTransform.from_matrix(matrix) # Test left handed rotation matrix - with pytest.raises(ValueError, match="Non-positive determinant"): - matrix = np.eye(4) - matrix[0, 0] = -1 - RigidTransform(matrix, normalize=True) + matrix = xp.eye(4) + matrix = xpx.at(matrix)[0, 0].set(-1) + if is_lazy_array(matrix): + matrix = RigidTransform.from_matrix(matrix).as_matrix() + assert xp.all(xp.isnan(matrix[..., :3, :3])) + else: + with pytest.raises(ValueError, match="Non-positive determinant"): + RigidTransform(matrix, normalize=True) # Test non-Rotation input with pytest.raises(ValueError, match="Expected `rotation` to be a `Rotation` instance"): - RigidTransform.from_rotation(np.eye(3)) + RigidTransform.from_rotation(xp.eye(3)) -def test_translation_validation(): +def test_translation_validation(xp): # Test invalid translation shapes with pytest.raises(ValueError, match="Expected `translation` to have shape"): - RigidTransform.from_translation([1, 2]) + RigidTransform.from_translation(xp.asarray([1, 2])) with pytest.raises(ValueError, match="Expected `translation` to have shape"): - RigidTransform.from_translation(np.zeros((2, 2))) + RigidTransform.from_translation(xp.zeros((2, 2))) with pytest.raises(ValueError, match="Expected `translation` to have shape"): - RigidTransform.from_translation(np.zeros((1, 1, 3))) + RigidTransform.from_translation(xp.zeros((1, 1, 3))) -def test_vector_validation(): - tf = RigidTransform.identity(2) +def test_vector_validation(xp): + tf = rigid_transform_to_xp(RigidTransform.identity(2), xp=xp) # Test invalid vector shapes with pytest.raises(ValueError, match="Expected vector to have shape"): - tf.apply([1, 2]) + tf.apply(xp.asarray([1, 2])) with pytest.raises(ValueError, match="Expected vector to have shape"): - tf.apply(np.zeros((2, 2))) + tf.apply(xp.zeros((2, 2))) with pytest.raises(ValueError, match="Expected vector to have shape"): - tf.apply(np.zeros((1, 1, 3))) + tf.apply(xp.zeros((1, 1, 3))) -def test_indexing_validation(): - tf = RigidTransform.identity() +def test_indexing_validation(xp): + tf = RigidTransform.from_matrix(xp.eye(4)) # Test indexing on single transform with pytest.raises(TypeError, match="Single transform is not subscriptable"): @@ -860,27 +1044,27 @@ def test_indexing_validation(): len(tf) -def test_composition_validation(): - tf2 = RigidTransform.from_translation([[1, 2, 3], [4, 5, 6]]) - tf3 = RigidTransform.from_translation([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) +def test_composition_validation(xp): + tf2 = RigidTransform.from_translation(xp.asarray([[1, 2, 3], [4, 5, 6]])) + tf3 = RigidTransform.from_translation(xp.asarray([[1, 2, 3], [4, 5, 6], [7, 8, 9]])) # Test incompatible shapes with pytest.raises(ValueError, match="Expected equal number of transforms"): tf2 * tf3 -def test_concatenate_validation(): - tf = RigidTransform.identity() +def test_concatenate_validation(xp): + tf = RigidTransform.from_matrix(xp.eye(4)) # Test invalid inputs with pytest.raises(TypeError, match="input must contain RigidTransform objects"): - RigidTransform.concatenate([tf, np.eye(4)]) + RigidTransform.concatenate([tf, xp.eye(4)]) -def test_setitem_validation(): - tf = RigidTransform.from_translation([[1, 2, 3], [4, 5, 6]]) - single = RigidTransform.identity() +def test_setitem_validation(xp): + tf = RigidTransform.from_translation(xp.asarray([[1, 2, 3], [4, 5, 6]])) + single = RigidTransform.from_matrix(xp.eye(4)) # Test setting item on single transform with pytest.raises(TypeError, match="Single transform is not subscriptable"): @@ -888,58 +1072,64 @@ def test_setitem_validation(): # Test invalid value type with pytest.raises(TypeError, match="value must be a RigidTransform"): - tf[0] = np.eye(4) + tf[0] = xp.eye(4) -def test_copy_flag(): +@pytest.mark.skip_xp_backends("jax.numpy", + reason="JAX does not support memory sharing") +def test_copy_flag(xp): # Test that copy=True creates new memory - matrix = np.eye(4) + matrix = xp.eye(4) tf = RigidTransform(matrix, normalize=False, copy=True) matrix[0, 0] = 2 assert tf.as_matrix()[0, 0] == 1 # Test that copy=False shares memory - matrix = np.eye(4) + matrix = xp.eye(4) tf = RigidTransform(matrix, normalize=False, copy=False) matrix[0, 0] = 2 assert tf.as_matrix()[0, 0] == 2 -def test_normalize_dual_quaternion(): - dual_quat = normalize_dual_quaternion(np.zeros((1, 8))) - assert_allclose(np.linalg.norm(dual_quat[0, :4]), 1.0, atol=1e-12) - assert_allclose(dual_quat[0, :4] @ dual_quat[0, 4:], 0.0, atol=1e-12) +def test_normalize_dual_quaternion(xp): + dual_quat = normalize_dual_quaternion(xp.zeros((1, 8))) + xp_assert_close(xp_vector_norm(dual_quat[0, :4], axis=-1), xp.asarray(1.0)[()], + atol=1e-12) + xp_assert_close(xp.vecdot(dual_quat[0, :4], dual_quat[0, 4:])[()], + xp.asarray(0.0)[()], atol=1e-12) rng = np.random.default_rng(103213650) - dual_quat = rng.normal(size=(1000, 8)) + dual_quat = xp.asarray(rng.normal(size=(1000, 8))) dual_quat = normalize_dual_quaternion(dual_quat) - assert_allclose(np.linalg.norm(dual_quat[:, :4], axis=1), 1.0, atol=1e-12) - assert_allclose(np.einsum("ij,ij->i", dual_quat[:, :4], dual_quat[:, 4:]), - 0.0, atol=1e-12) + expected = xp.ones(dual_quat.shape[0]) + xp_assert_close(xp_vector_norm(dual_quat[:, :4], axis=-1), expected, atol=1e-12) + expected = xp.zeros(dual_quat.shape[0]) + xp_assert_close(xp.vecdot(dual_quat[:, :4], dual_quat[:, 4:]), expected, atol=1e-12) -def test_empty_transform_construction(): - tf = RigidTransform.from_matrix(np.empty((0, 4, 4))) +def test_empty_transform_construction(xp): + tf = RigidTransform.from_matrix(xp.empty((0, 4, 4))) assert len(tf) == 0 assert not tf.single - - tf = RigidTransform.from_rotation(Rotation.random(0)) + + tf = RigidTransform.from_rotation(Rotation.from_quat(xp.zeros((0, 4)))) assert len(tf) == 0 assert not tf.single - tf = RigidTransform.from_translation(np.empty((0, 3))) + tf = RigidTransform.from_translation(xp.empty((0, 3))) assert len(tf) == 0 assert not tf.single - tf = RigidTransform.from_components(np.empty((0, 3)), Rotation.random(0)) + empty_rot = Rotation.from_quat(xp.zeros((0, 4))) + tf = RigidTransform.from_components(xp.empty((0, 3)), empty_rot) assert len(tf) == 0 assert not tf.single - tf = RigidTransform.from_exp_coords(np.empty((0, 6))) + tf = RigidTransform.from_exp_coords(xp.empty((0, 6))) assert len(tf) == 0 assert not tf.single - tf = RigidTransform.from_dual_quat(np.empty((0, 8))) + tf = RigidTransform.from_dual_quat(xp.empty((0, 8))) assert len(tf) == 0 assert not tf.single @@ -948,8 +1138,8 @@ def test_empty_transform_construction(): assert not tf.single -def test_empty_transform_representation(): - tf = RigidTransform.identity(0) +def test_empty_transform_representation(xp): + tf = RigidTransform.from_matrix(xp.empty((0, 4, 4))) assert len(tf.rotation) == 0 assert tf.translation.shape == (0, 3) @@ -963,20 +1153,20 @@ def test_empty_transform_representation(): assert tf.as_dual_quat().shape == (0, 8) -def test_empty_transform_application(): - tf = RigidTransform.identity(0) +def test_empty_transform_application(xp): + tf = RigidTransform.from_matrix(xp.empty((0, 4, 4))) - assert tf.apply(np.zeros((3,))).shape == (0, 3) - assert tf.apply(np.empty((0, 3))).shape == (0, 3) + assert tf.apply(xp.zeros((3,))).shape == (0, 3) + assert tf.apply(xp.empty((0, 3))).shape == (0, 3) with pytest.raises(ValueError, match="operands could not be broadcast together"): - tf.apply(np.zeros((2, 3))) + tf.apply(xp.zeros((2, 3))) -def test_empty_transform_composition(): - tf_empty = RigidTransform.identity(0) - tf_single = RigidTransform.identity() - tf_many = RigidTransform.identity(3) +def test_empty_transform_composition(xp): + tf_empty = RigidTransform.from_matrix(xp.empty((0, 4, 4))) + tf_single = RigidTransform.from_matrix(xp.eye(4)) + tf_many = rigid_transform_to_xp(RigidTransform.identity(3), xp=xp) assert len(tf_empty * tf_empty) == 0 assert len(tf_empty * tf_single) == 0 @@ -989,10 +1179,10 @@ def test_empty_transform_composition(): tf_empty * tf_many -def test_empty_transform_concatenation(): - tf_empty = RigidTransform.identity(0) - tf_single = RigidTransform.identity() - tf_many = RigidTransform.identity(2) +def test_empty_transform_concatenation(xp): + tf_empty = RigidTransform.from_matrix(xp.empty((0, 4, 4))) + tf_single = RigidTransform.from_matrix(xp.eye(4)) + tf_many = rigid_transform_to_xp(RigidTransform.identity(2), xp=xp) assert len(RigidTransform.concatenate([tf_empty, tf_empty])) == 0 assert len(RigidTransform.concatenate([tf_empty, tf_single])) == 1 @@ -1002,8 +1192,8 @@ def test_empty_transform_concatenation(): assert len(RigidTransform.concatenate([tf_many, tf_empty, tf_single])) == 3 -def test_empty_transform_inv_and_pow(): - tf = RigidTransform.identity(0) +def test_empty_transform_inv_and_pow(xp): + tf = RigidTransform.from_matrix(xp.empty((0, 4, 4))) assert len(tf.inv()) == 0 assert len(tf ** 0) == 0 assert len(tf ** 1) == 0 @@ -1011,19 +1201,21 @@ def test_empty_transform_inv_and_pow(): assert len(tf ** 0.5) == 0 -def test_empty_transform_indexing(): - tf_many = RigidTransform.identity(3) - tf_zero = tf_many[[]] +def test_empty_transform_indexing(xp): + tf_many = rigid_transform_to_xp(RigidTransform.identity(3), xp=xp) + tf_zero = tf_many[xp.asarray([], dtype=xp.int64)] assert len(tf_zero) == 0 - assert len(tf_zero[[]]) == 0 - assert len(tf_zero[:5]) == 0 # Slices can go out of bounds. + assert len(tf_zero[xp.asarray([], dtype=xp.int64)]) == 0 + # Array API does not specify out-of-bounds indexing. Only check for numpy. + if is_numpy(xp): + assert len(tf_zero[:5]) == 0 # Slices can go out of bounds. with pytest.raises(IndexError): tf_zero[0] with pytest.raises(IndexError): - tf_zero[[0, 2]] + tf_zero[xp.asarray([0, 2])] with pytest.raises(IndexError): - tf_zero[[False, True]] + tf_zero[xp.asarray([False, True])] diff --git a/scipy/spatial/transform/tests/test_rotation.py b/scipy/spatial/transform/tests/test_rotation.py index 2c91018f7a2b..5dd8356266f5 100644 --- a/scipy/spatial/transform/tests/test_rotation.py +++ b/scipy/spatial/transform/tests/test_rotation.py @@ -1,15 +1,30 @@ +import math + import pytest import numpy as np -from numpy.testing import assert_equal, assert_array_almost_equal -from numpy.testing import assert_allclose +from numpy.testing import assert_equal from scipy.spatial.transform import Rotation, Slerp from scipy.stats import special_ortho_group from itertools import permutations, product +from scipy._lib._array_api import ( + xp_assert_equal, + is_numpy, + is_lazy_array, + xp_vector_norm, + xp_assert_close, + eager_warns, + xp_default_dtype +) +import scipy._lib.array_api_extra as xpx import pickle import copy + +pytestmark = pytest.mark.skip_xp_backends(np_only=True) + + def basis_vec(axis): if axis == 'x': return [1, 0, 0] @@ -18,81 +33,114 @@ def basis_vec(axis): elif axis == 'z': return [0, 0, 1] -def test_generic_quat_matrix(): - x = np.array([[3, 4, 0, 0], [5, 12, 0, 0]]) + +def rotation_to_xp(r: Rotation, xp): + return Rotation.from_quat(xp.asarray(r.as_quat())) + + +def test_init_non_array(): + Rotation((0, 0, 0, 1)) + Rotation([0, 0, 0, 1]) + + +def test_generic_quat_matrix(xp): + x = xp.asarray([[3.0, 4, 0, 0], [5, 12, 0, 0]]) r = Rotation.from_quat(x) - expected_quat = x / np.array([[5], [13]]) - assert_array_almost_equal(r.as_quat(), expected_quat) + expected_quat = x / xp.asarray([[5.0], [13.0]]) + xp_assert_close(r.as_quat(), expected_quat) -def test_from_single_1d_quaternion(): - x = np.array([3, 4, 0, 0]) +def test_from_single_1d_quaternion(xp): + x = xp.asarray([3.0, 4, 0, 0]) r = Rotation.from_quat(x) expected_quat = x / 5 - assert_array_almost_equal(r.as_quat(), expected_quat) + xp_assert_close(r.as_quat(), expected_quat) -def test_from_single_2d_quaternion(): - x = np.array([[3, 4, 0, 0]]) +def test_from_single_2d_quaternion(xp): + x = xp.asarray([[3.0, 4, 0, 0]]) r = Rotation.from_quat(x) expected_quat = x / 5 - assert_array_almost_equal(r.as_quat(), expected_quat) + xp_assert_close(r.as_quat(), expected_quat) -def test_from_quat_scalar_first(): +def test_from_quat_scalar_first(xp): rng = np.random.RandomState(0) - r = Rotation.from_quat([1, 0, 0, 0], scalar_first=True) - assert_allclose(r.as_matrix(), np.eye(3), rtol=1e-15, atol=1e-16) + r = Rotation.from_quat(xp.asarray([1, 0, 0, 0]), scalar_first=True) + xp_assert_close(r.as_matrix(), xp.eye(3), rtol=1e-15, atol=1e-16) - r = Rotation.from_quat(np.tile([1, 0, 0, 0], (10, 1)), scalar_first=True) - assert_allclose(r.as_matrix(), np.tile(np.eye(3), (10, 1, 1)), - rtol=1e-15, atol=1e-16) + q = xp.tile(xp.asarray([1, 0, 0, 0]), (10, 1)) + r = Rotation.from_quat(q, scalar_first=True) + xp_assert_close( + r.as_matrix(), xp.tile(xp.eye(3), (10, 1, 1)), rtol=1e-15, atol=1e-16 + ) - q = rng.randn(100, 4) - q /= np.linalg.norm(q, axis=1)[:, None] - for qi in q: + q = xp.asarray(rng.randn(100, 4)) + q /= xp_vector_norm(q, axis=1)[:, None] + for i in range(q.shape[0]): # Array API conforming loop + qi = q[i, ...] r = Rotation.from_quat(qi, scalar_first=True) - assert_allclose(np.roll(r.as_quat(), 1), qi, rtol=1e-15) + xp_assert_close(xp.roll(r.as_quat(), 1), qi, rtol=1e-15) r = Rotation.from_quat(q, scalar_first=True) - assert_allclose(np.roll(r.as_quat(), 1, axis=1), q, rtol=1e-15) + xp_assert_close(xp.roll(r.as_quat(), 1, axis=1), q, rtol=1e-15) + + +def test_from_quat_array_like(): + rng = np.random.default_rng(123) + # Single rotation + r_expected = Rotation.random(rng=rng) + r = Rotation.from_quat(r_expected.as_quat().tolist()) + assert r_expected.approx_equal(r, atol=1e-12) + + # Multiple rotations + r_expected = Rotation.random(3, rng=rng) + r = Rotation.from_quat(r_expected.as_quat().tolist()) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) + + +def test_from_quat_int_dtype(xp): + r = Rotation.from_quat(xp.asarray([1, 0, 0, 0])) + assert r.as_quat().dtype == xp_default_dtype(xp) -def test_as_quat_scalar_first(): +def test_as_quat_scalar_first(xp): rng = np.random.RandomState(0) - r = Rotation.from_euler('xyz', np.zeros(3)) - assert_allclose(r.as_quat(scalar_first=True), [1, 0, 0, 0], + r = Rotation.from_euler('xyz', xp.zeros(3)) + xp_assert_close(r.as_quat(scalar_first=True), xp.asarray([1.0, 0, 0, 0]), rtol=1e-15, atol=1e-16) - r = Rotation.from_euler('xyz', np.zeros((10, 3))) - assert_allclose(r.as_quat(scalar_first=True), - np.tile([1, 0, 0, 0], (10, 1)), rtol=1e-15, atol=1e-16) + r = Rotation.from_euler('xyz', xp.zeros((10, 3))) + xp_assert_close(r.as_quat(scalar_first=True), + xp.tile(xp.asarray([1.0, 0, 0, 0]), (10, 1)), + rtol=1e-15, atol=1e-16) - q = rng.randn(100, 4) - q /= np.linalg.norm(q, axis=1)[:, None] - for qi in q: + q = xp.asarray(rng.randn(100, 4)) + q /= xp_vector_norm(q, axis=1)[:, None] + for i in range(q.shape[0]): # Array API conforming loop + qi = q[i, ...] r = Rotation.from_quat(qi) - assert_allclose(r.as_quat(scalar_first=True), np.roll(qi, 1), + xp_assert_close(r.as_quat(scalar_first=True), xp.roll(qi, 1), rtol=1e-15) - assert_allclose(r.as_quat(canonical=True, scalar_first=True), - np.roll(r.as_quat(canonical=True), 1), + xp_assert_close(r.as_quat(canonical=True, scalar_first=True), + xp.roll(r.as_quat(canonical=True), 1), rtol=1e-15) r = Rotation.from_quat(q) - assert_allclose(r.as_quat(scalar_first=True), np.roll(q, 1, axis=1), + xp_assert_close(r.as_quat(scalar_first=True), xp.roll(q, 1, axis=1), rtol=1e-15) - assert_allclose(r.as_quat(canonical=True, scalar_first=True), - np.roll(r.as_quat(canonical=True), 1, axis=1), rtol=1e-15) + xp_assert_close(r.as_quat(canonical=True, scalar_first=True), + xp.roll(r.as_quat(canonical=True), 1, axis=1), rtol=1e-15) -def test_from_square_quat_matrix(): +def test_from_square_quat_matrix(xp): # Ensure proper norm array broadcasting - x = np.array([ - [3, 0, 0, 4], + x = xp.asarray([ + [3.0, 0, 0, 4], [5, 0, 12, 0], [0, 0, 0, 1], [-1, -1, -1, 1], @@ -100,643 +148,724 @@ def test_from_square_quat_matrix(): [-1, -1, -1, -1] # Check double cover ]) r = Rotation.from_quat(x) - expected_quat = x / np.array([[5], [13], [1], [2], [1], [2]]) - assert_array_almost_equal(r.as_quat(), expected_quat) + expected_quat = x / xp.asarray([[5.0], [13], [1], [2], [1], [2]]) + xp_assert_close(r.as_quat(), expected_quat) -def test_quat_double_to_canonical_single_cover(): - x = np.array([ - [-1, 0, 0, 0], +def test_quat_double_to_canonical_single_cover(xp): + x = xp.asarray([ + [-1.0, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, -1], [-1, -1, -1, -1] ]) r = Rotation.from_quat(x) - expected_quat = np.abs(x) / np.linalg.norm(x, axis=1)[:, None] - assert_allclose(r.as_quat(canonical=True), expected_quat) + expected_quat = xp.abs(x) / xp_vector_norm(x, axis=1)[:, None] + xp_assert_close(r.as_quat(canonical=True), expected_quat) -def test_quat_double_cover(): +def test_quat_double_cover(xp): # See the Rotation.from_quat() docstring for scope of the quaternion # double cover property. # Check from_quat and as_quat(canonical=False) - q = np.array([0, 0, 0, -1]) + q = xp.asarray([0.0, 0, 0, -1]) r = Rotation.from_quat(q) - assert_equal(q, r.as_quat(canonical=False)) - + xp_assert_equal(q, r.as_quat(canonical=False)) # Check composition and inverse - q = np.array([1, 0, 0, 1])/np.sqrt(2) # 90 deg rotation about x + q = xp.asarray([1.0, 0, 0, 1])/math.sqrt(2) # 90 deg rotation about x r = Rotation.from_quat(q) r3 = r*r*r - assert_allclose(r.as_quat(canonical=False)*np.sqrt(2), - [1, 0, 0, 1]) - assert_allclose(r.inv().as_quat(canonical=False)*np.sqrt(2), - [-1, 0, 0, 1]) - assert_allclose(r3.as_quat(canonical=False)*np.sqrt(2), - [1, 0, 0, -1]) - assert_allclose(r3.inv().as_quat(canonical=False)*np.sqrt(2), - [-1, 0, 0, -1]) + xp_assert_close(r.as_quat(canonical=False)*math.sqrt(2), + xp.asarray([1.0, 0, 0, 1])) + xp_assert_close(r.inv().as_quat(canonical=False)*math.sqrt(2), + xp.asarray([-1.0, 0, 0, 1])) + xp_assert_close(r3.as_quat(canonical=False)*math.sqrt(2), + xp.asarray([1.0, 0, 0, -1])) + xp_assert_close(r3.inv().as_quat(canonical=False)*math.sqrt(2), + xp.asarray([-1.0, 0, 0, -1])) # More sanity checks - assert_allclose((r*r.inv()).as_quat(canonical=False), - [0, 0, 0, 1], atol=2e-16) - assert_allclose((r3*r3.inv()).as_quat(canonical=False), - [0, 0, 0, 1], atol=2e-16) - assert_allclose((r*r3).as_quat(canonical=False), - [0, 0, 0, -1], atol=2e-16) - assert_allclose((r.inv()*r3.inv()).as_quat(canonical=False), - [0, 0, 0, -1], atol=2e-16) + xp_assert_close((r*r.inv()).as_quat(canonical=False), + xp.asarray([0.0, 0, 0, 1]), atol=2e-16) + xp_assert_close((r3*r3.inv()).as_quat(canonical=False), + xp.asarray([0.0, 0, 0, 1]), atol=2e-16) + xp_assert_close((r*r3).as_quat(canonical=False), + xp.asarray([0.0, 0, 0, -1]), atol=2e-16) + xp_assert_close((r.inv() * r3.inv()).as_quat(canonical=False), + xp.asarray([0.0, 0, 0, -1]), atol=2e-16) -def test_from_quat_wrong_shape(): +def test_from_quat_wrong_shape(xp): # Wrong shape 1d array with pytest.raises(ValueError, match='Expected `quat` to have shape'): - Rotation.from_quat(np.array([1, 2, 3])) + Rotation.from_quat(xp.asarray([1, 2, 3])) # Wrong shape 2d array with pytest.raises(ValueError, match='Expected `quat` to have shape'): - Rotation.from_quat(np.array([ + Rotation.from_quat(xp.asarray([ [1, 2, 3, 4, 5], [4, 5, 6, 7, 8] ])) # 3d array with pytest.raises(ValueError, match='Expected `quat` to have shape'): - Rotation.from_quat(np.array([ + Rotation.from_quat(xp.asarray([ [[1, 2, 3, 4]], [[4, 5, 6, 7]] ])) -def test_zero_norms_from_quat(): - x = np.array([ +def test_zero_norms_from_quat(xp): + x = xp.asarray([ [3, 4, 0, 0], [0, 0, 0, 0], [5, 0, 12, 0] ]) - with pytest.raises(ValueError): - Rotation.from_quat(x) + if is_lazy_array(x): + assert xp.all(xp.isnan(Rotation.from_quat(x).as_quat()[1, ...])) + else: + with pytest.raises(ValueError): + Rotation.from_quat(x) -def test_as_matrix_single_1d_quaternion(): - quat = [0, 0, 0, 1] +def test_as_matrix_single_1d_quaternion(xp): + quat = xp.asarray([0, 0, 0, 1]) mat = Rotation.from_quat(quat).as_matrix() # mat.shape == (3,3) due to 1d input - assert_array_almost_equal(mat, np.eye(3)) + xp_assert_close(mat, xp.eye(3)) -def test_as_matrix_single_2d_quaternion(): - quat = [[0, 0, 1, 1]] +def test_as_matrix_single_2d_quaternion(xp): + quat = xp.asarray([[0, 0, 1, 1]]) mat = Rotation.from_quat(quat).as_matrix() assert_equal(mat.shape, (1, 3, 3)) - expected_mat = np.array([ - [0, -1, 0], + expected_mat = xp.asarray([ + [0.0, -1, 0], [1, 0, 0], [0, 0, 1] ]) - assert_array_almost_equal(mat[0], expected_mat) + xp_assert_close(mat[0, ...], expected_mat) -def test_as_matrix_from_square_input(): - quats = [ +def test_as_matrix_from_square_input(xp): + quats = xp.asarray([ [0, 0, 1, 1], [0, 1, 0, 1], [0, 0, 0, 1], [0, 0, 0, -1] - ] + ]) mat = Rotation.from_quat(quats).as_matrix() assert_equal(mat.shape, (4, 3, 3)) - expected0 = np.array([ - [0, -1, 0], + expected0 = xp.asarray([ + [0.0, -1, 0], [1, 0, 0], [0, 0, 1] ]) - assert_array_almost_equal(mat[0], expected0) + xp_assert_close(mat[0, ...], expected0) - expected1 = np.array([ - [0, 0, 1], + expected1 = xp.asarray([ + [0.0, 0, 1], [0, 1, 0], [-1, 0, 0] ]) - assert_array_almost_equal(mat[1], expected1) + xp_assert_close(mat[1, ...], expected1) - assert_array_almost_equal(mat[2], np.eye(3)) - assert_array_almost_equal(mat[3], np.eye(3)) + xp_assert_close(mat[2, ...], xp.eye(3)) + xp_assert_close(mat[3, ...], xp.eye(3)) -def test_as_matrix_from_generic_input(): - quats = [ +def test_as_matrix_from_generic_input(xp): + quats = xp.asarray([ [0, 0, 1, 1], [0, 1, 0, 1], [1, 2, 3, 4] - ] + ]) mat = Rotation.from_quat(quats).as_matrix() assert_equal(mat.shape, (3, 3, 3)) - expected0 = np.array([ - [0, -1, 0], + expected0 = xp.asarray([ + [0.0, -1, 0], [1, 0, 0], [0, 0, 1] ]) - assert_array_almost_equal(mat[0], expected0) + xp_assert_close(mat[0, ...], expected0) - expected1 = np.array([ - [0, 0, 1], + expected1 = xp.asarray([ + [0.0, 0, 1], [0, 1, 0], [-1, 0, 0] ]) - assert_array_almost_equal(mat[1], expected1) + xp_assert_close(mat[1, ...], expected1) - expected2 = np.array([ + expected2 = xp.asarray([ [0.4, -2, 2.2], [2.8, 1, 0.4], [-1, 2, 2] ]) / 3 - assert_array_almost_equal(mat[2], expected2) + xp_assert_close(mat[2, ...], expected2) -def test_from_single_2d_matrix(): - mat = [ +def test_from_single_2d_matrix(xp): + mat = xp.asarray([ [0, 0, 1], [1, 0, 0], [0, 1, 0] - ] - expected_quat = [0.5, 0.5, 0.5, 0.5] - assert_array_almost_equal( - Rotation.from_matrix(mat).as_quat(), - expected_quat) + ]) + expected_quat = xp.asarray([0.5, 0.5, 0.5, 0.5]) + xp_assert_close(Rotation.from_matrix(mat).as_quat(), expected_quat) -def test_from_single_3d_matrix(): - mat = np.array([ +def test_from_single_3d_matrix(xp): + mat = xp.asarray([[ [0, 0, 1], [1, 0, 0], - [0, 1, 0] - ]).reshape((1, 3, 3)) - expected_quat = np.array([0.5, 0.5, 0.5, 0.5]).reshape((1, 4)) - assert_array_almost_equal( - Rotation.from_matrix(mat).as_quat(), - expected_quat) + [0, 1, 0], + ]]) + expected_quat = xp.asarray([[0.5, 0.5, 0.5, 0.5]]) + xp_assert_close(Rotation.from_matrix(mat).as_quat(), expected_quat) -def test_from_matrix_calculation(): - expected_quat = np.array([1, 1, 6, 1]) / np.sqrt(39) - mat = np.array([ +def test_from_matrix_calculation(xp): + atol = 1e-8 + expected_quat = xp.asarray([1.0, 1, 6, 1]) / math.sqrt(39) + mat = xp.asarray([ [-0.8974359, -0.2564103, 0.3589744], [0.3589744, -0.8974359, 0.2564103], [0.2564103, 0.3589744, 0.8974359] ]) - assert_array_almost_equal( - Rotation.from_matrix(mat).as_quat(), - expected_quat) - assert_array_almost_equal( - Rotation.from_matrix(mat.reshape((1, 3, 3))).as_quat(), - expected_quat.reshape((1, 4))) + xp_assert_close(Rotation.from_matrix(mat).as_quat(), expected_quat, atol=atol) + xp_assert_close(Rotation.from_matrix(xp.reshape(mat, (1, 3, 3))).as_quat(), + xp.reshape(expected_quat, (1, 4)), + atol=atol) -def test_matrix_calculation_pipeline(): - mat = special_ortho_group.rvs(3, size=10, random_state=0) - assert_array_almost_equal(Rotation.from_matrix(mat).as_matrix(), mat) +def test_matrix_calculation_pipeline(xp): + mat = xp.asarray(special_ortho_group.rvs(3, size=10, random_state=0)) + xp_assert_close(Rotation.from_matrix(mat).as_matrix(), mat) -def test_from_matrix_ortho_output(): +def test_from_matrix_ortho_output(xp): + atol = 1e-12 rnd = np.random.RandomState(0) - mat = rnd.random_sample((100, 3, 3)) - dets = np.linalg.det(mat) - for i in range(len(dets)): + mat = xp.asarray(rnd.random_sample((100, 3, 3))) + dets = xp.linalg.det(mat) + for i in range(dets.shape[0]): # Make sure we have a right-handed rotation matrix if dets[i] < 0: - mat[i] = -mat[i] + mat = xpx.at(mat)[i, ...].set(-mat[i, ...]) ortho_mat = Rotation.from_matrix(mat).as_matrix() - mult_result = np.einsum('...ij,...jk->...ik', ortho_mat, - ortho_mat.transpose((0, 2, 1))) + mult_result = xp.matmul(ortho_mat, xp.matrix_transpose(ortho_mat)) - eye3d = np.zeros((100, 3, 3)) - for i in range(3): - eye3d[:, i, i] = 1.0 + eye3d = xp.zeros((100, 3, 3)) + xp.eye(3) + xp_assert_close(mult_result, eye3d, atol=atol) - assert_array_almost_equal(mult_result, eye3d) - -def test_from_matrix_normalize(): - mat = np.array([ +def test_from_matrix_normalize(xp): + mat = xp.asarray([ [1, 1, 0], [0, 1, 0], [0, 0, 1]]) - expected = np.array([[ 0.894427, 0.447214, 0.0], - [-0.447214, 0.894427, 0.0], - [ 0.0, 0.0, 1.0]]) - assert_allclose(Rotation.from_matrix(mat).as_matrix(), expected, atol=1e-6) + expected = xp.asarray([[ 0.894427, 0.447214, 0.0], + [-0.447214, 0.894427, 0.0], + [ 0.0, 0.0, 1.0]]) + xp_assert_close(Rotation.from_matrix(mat).as_matrix(), expected, atol=1e-6) - mat = np.array([ + mat = xp.asarray([ [0, -0.5, 0 ], [0.5, 0 , 0 ], [0, 0 , 0.5]]) - expected = np.array([[ 0, -1, 0], - [ 1, 0, 0], - [ 0, 0, 1]]) - assert_allclose(Rotation.from_matrix(mat).as_matrix(), expected, atol=1e-6) - - -def test_from_matrix_non_positive_determinant(): - mat = np.eye(3) - mat[0, 0] = 0 - with pytest.raises(ValueError, match='Non-positive determinant'): - Rotation.from_matrix(mat) - - mat[0, 0] = -1 - with pytest.raises(ValueError, match='Non-positive determinant'): - Rotation.from_matrix(mat) + expected = xp.asarray([[0.0, -1, 0], + [ 1, 0, 0], + [ 0, 0, 1]]) + xp_assert_close(Rotation.from_matrix(mat).as_matrix(), expected, atol=1e-6) + + +def test_from_matrix_non_positive_determinant(xp): + mat = xp.eye(3) + mat = xpx.at(mat)[0, 0].set(0) + if is_lazy_array(mat): + assert xp.all(xp.isnan(Rotation.from_matrix(mat).as_matrix())) + else: + with pytest.raises(ValueError, match="Non-positive determinant"): + Rotation.from_matrix(mat) + + mat = xpx.at(mat)[0, 0].set(-1) + if is_lazy_array(mat): + assert xp.all(xp.isnan(Rotation.from_matrix(mat).as_matrix())) + else: + with pytest.raises(ValueError, match="Non-positive determinant"): + Rotation.from_matrix(mat) + + +def test_from_matrix_array_like(): + rng = np.random.default_rng(123) + # Single rotation + r_expected = Rotation.random(rng=rng) + r = Rotation.from_matrix(r_expected.as_matrix().tolist()) + assert r_expected.approx_equal(r, atol=1e-12) + + # Multiple rotations + r_expected = Rotation.random(3, rng=rng) + r = Rotation.from_matrix(r_expected.as_matrix().tolist()) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) + + +def test_from_matrix_int_dtype(xp): + mat = xp.asarray([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + r = Rotation.from_matrix(mat) + assert r.as_quat().dtype == xp_default_dtype(xp) -def test_from_1d_single_rotvec(): - rotvec = [1, 0, 0] - expected_quat = np.array([0.4794255, 0, 0, 0.8775826]) +def test_from_1d_single_rotvec(xp): + atol = 1e-7 + rotvec = xp.asarray([1, 0, 0]) + expected_quat = xp.asarray([0.4794255, 0, 0, 0.8775826]) result = Rotation.from_rotvec(rotvec) - assert_array_almost_equal(result.as_quat(), expected_quat) + xp_assert_close(result.as_quat(), expected_quat, atol=atol) -def test_from_2d_single_rotvec(): - rotvec = [[1, 0, 0]] - expected_quat = np.array([[0.4794255, 0, 0, 0.8775826]]) +def test_from_2d_single_rotvec(xp): + atol = 1e-7 + rotvec = xp.asarray([[1, 0, 0]]) + expected_quat = xp.asarray([[0.4794255, 0, 0, 0.8775826]]) result = Rotation.from_rotvec(rotvec) - assert_array_almost_equal(result.as_quat(), expected_quat) + xp_assert_close(result.as_quat(), expected_quat, atol=atol) -def test_from_generic_rotvec(): - rotvec = [ +def test_from_generic_rotvec(xp): + atol = 1e-7 + rotvec = xp.asarray([ [1, 2, 2], [1, -1, 0.5], - [0, 0, 0] - ] - expected_quat = np.array([ + [0, 0, 0]]) + expected_quat = xp.asarray([ [0.3324983, 0.6649967, 0.6649967, 0.0707372], [0.4544258, -0.4544258, 0.2272129, 0.7316889], [0, 0, 0, 1] ]) - assert_array_almost_equal( - Rotation.from_rotvec(rotvec).as_quat(), - expected_quat) + xp_assert_close(Rotation.from_rotvec(rotvec).as_quat(), expected_quat, atol=atol) -def test_from_rotvec_small_angle(): - rotvec = np.array([ - [5e-4 / np.sqrt(3), -5e-4 / np.sqrt(3), 5e-4 / np.sqrt(3)], +def test_from_rotvec_small_angle(xp): + rotvec = xp.asarray([ + [5e-4 / math.sqrt(3), -5e-4 / math.sqrt(3), 5e-4 / math.sqrt(3)], [0.2, 0.3, 0.4], [0, 0, 0] ]) quat = Rotation.from_rotvec(rotvec).as_quat() # cos(theta/2) ~~ 1 for small theta - assert_allclose(quat[0, 3], 1) + xp_assert_close(quat[0, 3], xp.asarray(1.0)[()]) # sin(theta/2) / theta ~~ 0.5 for small theta - assert_allclose(quat[0, :3], rotvec[0] * 0.5) + xp_assert_close(quat[0, :3], rotvec[0, ...] * 0.5) - assert_allclose(quat[1, 3], 0.9639685) - assert_allclose( - quat[1, :3], - np.array([ + xp_assert_close(quat[1, 3], xp.asarray(0.9639685)[()]) + xp_assert_close(quat[1, :3], + xp.asarray([ 0.09879603932153465, 0.14819405898230198, - 0.19759207864306931 - ])) + 0.19759207864306931])) + + xp_assert_equal(quat[2, ...], xp.asarray([0.0, 0, 0, 1])) + + +def test_from_rotvec_array_like(): + rng = np.random.default_rng(123) + # Single rotation + r_expected = Rotation.random(rng=rng) + r = Rotation.from_rotvec(r_expected.as_rotvec().tolist()) + assert r_expected.approx_equal(r, atol=1e-12) + + # Multiple rotations + r_expected = Rotation.random(3, rng=rng) + r = Rotation.from_rotvec(r_expected.as_rotvec().tolist()) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) - assert_equal(quat[2], np.array([0, 0, 0, 1])) +def test_from_rotvec_int_dtype(xp): + rotvec = xp.asarray([1, 0, 0]) + r = Rotation.from_rotvec(rotvec) + assert r.as_quat().dtype == xp_default_dtype(xp) -def test_degrees_from_rotvec(): - rotvec1 = [1.0 / np.cbrt(3), 1.0 / np.cbrt(3), 1.0 / np.cbrt(3)] + +def test_degrees_from_rotvec(xp): + rotvec1 = xp.asarray([1 / 3 ** (1/3)] * 3) rot1 = Rotation.from_rotvec(rotvec1, degrees=True) quat1 = rot1.as_quat() - rotvec2 = np.deg2rad(rotvec1) + # deg2rad is not implemented in Array API -> / 180 * xp.pi + rotvec2 = xp.asarray(rotvec1 / 180 * xp.pi) rot2 = Rotation.from_rotvec(rotvec2) quat2 = rot2.as_quat() - assert_allclose(quat1, quat2) + xp_assert_close(quat1, quat2) -def test_malformed_1d_from_rotvec(): +def test_malformed_1d_from_rotvec(xp): with pytest.raises(ValueError, match='Expected `rot_vec` to have shape'): - Rotation.from_rotvec([1, 2]) + Rotation.from_rotvec(xp.asarray([1, 2])) -def test_malformed_2d_from_rotvec(): +def test_malformed_2d_from_rotvec(xp): with pytest.raises(ValueError, match='Expected `rot_vec` to have shape'): - Rotation.from_rotvec([ + Rotation.from_rotvec(xp.asarray([ [1, 2, 3, 4], [5, 6, 7, 8] - ]) + ])) -def test_as_generic_rotvec(): - quat = np.array([ +def test_as_generic_rotvec(xp): + quat = xp.asarray([ [1, 2, -1, 0.5], [1, -1, 1, 0.0003], [0, 0, 0, 1] ]) - quat /= np.linalg.norm(quat, axis=1)[:, None] + quat /= xp_vector_norm(quat, axis=-1, keepdims=True) rotvec = Rotation.from_quat(quat).as_rotvec() - angle = np.linalg.norm(rotvec, axis=1) + angle = xp_vector_norm(rotvec, axis=-1) - assert_allclose(quat[:, 3], np.cos(angle/2)) - assert_allclose(np.cross(rotvec, quat[:, :3]), np.zeros((3, 3))) + xp_assert_close(quat[:, 3], xp.cos(angle / 2)) + xp_assert_close(xp.linalg.cross(rotvec, quat[:, :3]), xp.zeros((3, 3)), atol=1e-15) -def test_as_rotvec_single_1d_input(): - quat = np.array([1, 2, -3, 2]) - expected_rotvec = np.array([0.5772381, 1.1544763, -1.7317144]) +def test_as_rotvec_single_1d_input(xp): + quat = xp.asarray([1, 2, -3, 2]) + expected_rotvec = xp.asarray([0.5772381, 1.1544763, -1.7317144]) actual_rotvec = Rotation.from_quat(quat).as_rotvec() assert_equal(actual_rotvec.shape, (3,)) - assert_allclose(actual_rotvec, expected_rotvec) + xp_assert_close(actual_rotvec, expected_rotvec) -def test_as_rotvec_single_2d_input(): - quat = np.array([[1, 2, -3, 2]]) - expected_rotvec = np.array([[0.5772381, 1.1544763, -1.7317144]]) +def test_as_rotvec_single_2d_input(xp): + quat = xp.asarray([[1, 2, -3, 2]]) + expected_rotvec = xp.asarray([[0.5772381, 1.1544763, -1.7317144]]) actual_rotvec = Rotation.from_quat(quat).as_rotvec() assert_equal(actual_rotvec.shape, (1, 3)) - assert_allclose(actual_rotvec, expected_rotvec) + xp_assert_close(actual_rotvec, expected_rotvec) -def test_as_rotvec_degrees(): +def test_as_rotvec_degrees(xp): # x->y, y->z, z->x - mat = [[0, 0, 1], [1, 0, 0], [0, 1, 0]] + mat = xp.asarray([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) rot = Rotation.from_matrix(mat) rotvec = rot.as_rotvec(degrees=True) - angle = np.linalg.norm(rotvec) - assert_allclose(angle, 120.0) - assert_allclose(rotvec[0], rotvec[1]) - assert_allclose(rotvec[1], rotvec[2]) + angle = xp_vector_norm(rotvec, axis=-1) + xp_assert_close(angle, xp.asarray(120.0)[()]) + xp_assert_close(rotvec[0], rotvec[1]) + xp_assert_close(rotvec[1], rotvec[2]) -def test_rotvec_calc_pipeline(): +def test_rotvec_calc_pipeline(xp): # Include small angles - rotvec = np.array([ + rotvec = xp.asarray([ [0, 0, 0], [1, -1, 2], [-3e-4, 3.5e-4, 7.5e-5] ]) - assert_allclose(Rotation.from_rotvec(rotvec).as_rotvec(), rotvec) - assert_allclose(Rotation.from_rotvec(rotvec, degrees=True).as_rotvec(degrees=True), + xp_assert_close(Rotation.from_rotvec(rotvec).as_rotvec(), rotvec) + xp_assert_close(Rotation.from_rotvec(rotvec, degrees=True).as_rotvec(degrees=True), rotvec) -def test_from_1d_single_mrp(): - mrp = [0, 0, 1.0] - expected_quat = np.array([0, 0, 1, 0]) +def test_from_1d_single_mrp(xp): + mrp = xp.asarray([0, 0, 1.0]) + expected_quat = xp.asarray([0.0, 0, 1, 0]) result = Rotation.from_mrp(mrp) - assert_array_almost_equal(result.as_quat(), expected_quat) + xp_assert_close(result.as_quat(), expected_quat, atol=1e-12) -def test_from_2d_single_mrp(): - mrp = [[0, 0, 1.0]] - expected_quat = np.array([[0, 0, 1, 0]]) +def test_from_2d_single_mrp(xp): + mrp = xp.asarray([[0, 0, 1.0]]) + expected_quat = xp.asarray([[0.0, 0, 1, 0]]) result = Rotation.from_mrp(mrp) - assert_array_almost_equal(result.as_quat(), expected_quat) + xp_assert_close(result.as_quat(), expected_quat) + +def test_from_mrp_array_like(): + rng = np.random.default_rng(123) + # Single rotation + r_expected = Rotation.random(rng=rng) + r = Rotation.from_mrp(r_expected.as_mrp().tolist()) + assert r_expected.approx_equal(r, atol=1e-12) -def test_from_generic_mrp(): - mrp = np.array([ + # Multiple rotations + r_expected = Rotation.random(3, rng=rng) + r = Rotation.from_mrp(r_expected.as_mrp().tolist()) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) + + +def test_from_mrp_int_dtype(xp): + mrp = xp.asarray([0, 0, 1]) + r = Rotation.from_mrp(mrp) + assert r.as_quat().dtype == xp_default_dtype(xp) + + +def test_from_generic_mrp(xp): + mrp = xp.asarray([ [1, 2, 2], [1, -1, 0.5], [0, 0, 0]]) - expected_quat = np.array([ + expected_quat = xp.asarray([ [0.2, 0.4, 0.4, -0.8], [0.61538462, -0.61538462, 0.30769231, -0.38461538], [0, 0, 0, 1]]) - assert_array_almost_equal(Rotation.from_mrp(mrp).as_quat(), expected_quat) + xp_assert_close(Rotation.from_mrp(mrp).as_quat(), expected_quat) -def test_malformed_1d_from_mrp(): +def test_malformed_1d_from_mrp(xp): with pytest.raises(ValueError, match='Expected `mrp` to have shape'): - Rotation.from_mrp([1, 2]) + Rotation.from_mrp(xp.asarray([1, 2])) -def test_malformed_2d_from_mrp(): +def test_malformed_2d_from_mrp(xp): with pytest.raises(ValueError, match='Expected `mrp` to have shape'): - Rotation.from_mrp([ + Rotation.from_mrp(xp.asarray([ [1, 2, 3, 4], [5, 6, 7, 8] - ]) + ])) -def test_as_generic_mrp(): - quat = np.array([ +def test_as_generic_mrp(xp): + quat = xp.asarray([ [1, 2, -1, 0.5], [1, -1, 1, 0.0003], [0, 0, 0, 1]]) - quat /= np.linalg.norm(quat, axis=1)[:, None] + quat /= xp_vector_norm(quat, axis=1)[:, None] - expected_mrp = np.array([ + expected_mrp = xp.asarray([ [0.33333333, 0.66666667, -0.33333333], [0.57725028, -0.57725028, 0.57725028], [0, 0, 0]]) - assert_array_almost_equal(Rotation.from_quat(quat).as_mrp(), expected_mrp) + xp_assert_close(Rotation.from_quat(quat).as_mrp(), expected_mrp) -def test_past_180_degree_rotation(): + +def test_past_180_degree_rotation(xp): # ensure that a > 180 degree rotation is returned as a <180 rotation in MRPs # in this case 270 should be returned as -90 - expected_mrp = np.array([-np.tan(np.pi/2/4), 0.0, 0]) - assert_array_almost_equal( - Rotation.from_euler('xyz', [270, 0, 0], degrees=True).as_mrp(), - expected_mrp + expected_mrp = xp.asarray([-math.tan(xp.pi / 2 / 4), 0.0, 0]) + xp_assert_close( + Rotation.from_euler('xyz', xp.asarray([270, 0, 0]), degrees=True).as_mrp(), + expected_mrp, ) -def test_as_mrp_single_1d_input(): - quat = np.array([1, 2, -3, 2]) - expected_mrp = np.array([0.16018862, 0.32037724, -0.48056586]) +def test_as_mrp_single_1d_input(xp): + quat = xp.asarray([1, 2, -3, 2]) + expected_mrp = xp.asarray([0.16018862, 0.32037724, -0.48056586]) actual_mrp = Rotation.from_quat(quat).as_mrp() assert_equal(actual_mrp.shape, (3,)) - assert_allclose(actual_mrp, expected_mrp) + xp_assert_close(actual_mrp, expected_mrp) -def test_as_mrp_single_2d_input(): - quat = np.array([[1, 2, -3, 2]]) - expected_mrp = np.array([[0.16018862, 0.32037724, -0.48056586]]) +def test_as_mrp_single_2d_input(xp): + quat = xp.asarray([[1, 2, -3, 2]]) + expected_mrp = xp.asarray([[0.16018862, 0.32037724, -0.48056586]]) actual_mrp = Rotation.from_quat(quat).as_mrp() assert_equal(actual_mrp.shape, (1, 3)) - assert_allclose(actual_mrp, expected_mrp) + xp_assert_close(actual_mrp, expected_mrp) -def test_mrp_calc_pipeline(): - actual_mrp = np.array([ +def test_mrp_calc_pipeline(xp): + actual_mrp = xp.asarray([ [0, 0, 0], [1, -1, 2], [0.41421356, 0, 0], [0.1, 0.2, 0.1]]) - expected_mrp = np.array([ + expected_mrp = xp.asarray([ [0, 0, 0], [-0.16666667, 0.16666667, -0.33333333], [0.41421356, 0, 0], [0.1, 0.2, 0.1]]) - assert_allclose(Rotation.from_mrp(actual_mrp).as_mrp(), expected_mrp) + xp_assert_close(Rotation.from_mrp(actual_mrp).as_mrp(), expected_mrp) -def test_from_euler_single_rotation(): - quat = Rotation.from_euler('z', 90, degrees=True).as_quat() - expected_quat = np.array([0, 0, 1, 1]) / np.sqrt(2) - assert_allclose(quat, expected_quat) +def test_from_euler_single_rotation(xp): + quat = Rotation.from_euler("z", xp.asarray(90), degrees=True).as_quat() + expected_quat = xp.asarray([0.0, 0, 1, 1]) / math.sqrt(2) + xp_assert_close(quat, expected_quat) -def test_single_intrinsic_extrinsic_rotation(): - extrinsic = Rotation.from_euler('z', 90, degrees=True).as_matrix() - intrinsic = Rotation.from_euler('Z', 90, degrees=True).as_matrix() - assert_allclose(extrinsic, intrinsic) +def test_single_intrinsic_extrinsic_rotation(xp): + extrinsic = Rotation.from_euler('z', xp.asarray(90), degrees=True).as_matrix() + intrinsic = Rotation.from_euler('Z', xp.asarray(90), degrees=True).as_matrix() + xp_assert_close(extrinsic, intrinsic) -def test_from_euler_rotation_order(): +def test_from_euler_rotation_order(xp): # Intrinsic rotation is same as extrinsic with order reversed rnd = np.random.RandomState(0) - a = rnd.randint(low=0, high=180, size=(6, 3)) - b = a[:, ::-1] + a = xp.asarray(rnd.randint(low=0, high=180, size=(6, 3))) + b = xp.flip(a, axis=-1) x = Rotation.from_euler('xyz', a, degrees=True).as_quat() y = Rotation.from_euler('ZYX', b, degrees=True).as_quat() - assert_allclose(x, y) + xp_assert_close(x, y) -def test_from_euler_elementary_extrinsic_rotation(): +def test_from_euler_elementary_extrinsic_rotation(xp): + atol = 1e-12 # Simple test to check if extrinsic rotations are implemented correctly - mat = Rotation.from_euler('zx', [90, 90], degrees=True).as_matrix() - expected_mat = np.array([ - [0, -1, 0], + mat = Rotation.from_euler('zx', xp.asarray([90, 90]), degrees=True).as_matrix() + expected_mat = xp.asarray([ + [0.0, -1, 0], [0, 0, -1], [1, 0, 0] ]) - assert_array_almost_equal(mat, expected_mat) + xp_assert_close(mat, expected_mat, atol=atol) -def test_from_euler_intrinsic_rotation_312(): - angles = [ +def test_from_euler_intrinsic_rotation_312(xp): + atol = 1e-7 + angles = xp.asarray([ [30, 60, 45], [30, 60, 30], [45, 30, 60] - ] + ]) mat = Rotation.from_euler('ZXY', angles, degrees=True).as_matrix() - assert_array_almost_equal(mat[0], np.array([ + xp_assert_close(mat[0, ...], xp.asarray([ [0.3061862, -0.2500000, 0.9185587], [0.8838835, 0.4330127, -0.1767767], [-0.3535534, 0.8660254, 0.3535534] - ])) + ]), atol=atol) - assert_array_almost_equal(mat[1], np.array([ + xp_assert_close(mat[1, ...], xp.asarray([ [0.5334936, -0.2500000, 0.8080127], [0.8080127, 0.4330127, -0.3995191], [-0.2500000, 0.8660254, 0.4330127] - ])) + ]), atol=atol) - assert_array_almost_equal(mat[2], np.array([ + xp_assert_close(mat[2, ...], xp.asarray([ [0.0473672, -0.6123725, 0.7891491], [0.6597396, 0.6123725, 0.4355958], [-0.7500000, 0.5000000, 0.4330127] - ])) + ]), atol=atol) -def test_from_euler_intrinsic_rotation_313(): - angles = [ +def test_from_euler_intrinsic_rotation_313(xp): + angles = xp.asarray([ [30, 60, 45], [30, 60, 30], [45, 30, 60] - ] + ]) mat = Rotation.from_euler('ZXZ', angles, degrees=True).as_matrix() - assert_array_almost_equal(mat[0], np.array([ + xp_assert_close(mat[0, ...], xp.asarray([ [0.43559574, -0.78914913, 0.4330127], [0.65973961, -0.04736717, -0.750000], [0.61237244, 0.61237244, 0.500000] ])) - assert_array_almost_equal(mat[1], np.array([ + xp_assert_close(mat[1, ...], xp.asarray([ [0.6250000, -0.64951905, 0.4330127], [0.64951905, 0.1250000, -0.750000], [0.4330127, 0.750000, 0.500000] ])) - assert_array_almost_equal(mat[2], np.array([ + xp_assert_close(mat[2, ...], xp.asarray([ [-0.1767767, -0.91855865, 0.35355339], [0.88388348, -0.30618622, -0.35355339], [0.4330127, 0.25000000, 0.8660254] ])) -def test_from_euler_extrinsic_rotation_312(): - angles = [ +def test_from_euler_extrinsic_rotation_312(xp): + angles = xp.asarray([ [30, 60, 45], [30, 60, 30], [45, 30, 60] - ] + ]) mat = Rotation.from_euler('zxy', angles, degrees=True).as_matrix() - assert_array_almost_equal(mat[0], np.array([ + xp_assert_close(mat[0, ...], xp.asarray([ [0.91855865, 0.1767767, 0.35355339], [0.25000000, 0.4330127, -0.8660254], [-0.30618622, 0.88388348, 0.35355339] ])) - assert_array_almost_equal(mat[1], np.array([ + xp_assert_close(mat[1, ...], xp.asarray([ [0.96650635, -0.0580127, 0.2500000], [0.25000000, 0.4330127, -0.8660254], [-0.0580127, 0.89951905, 0.4330127] ])) - assert_array_almost_equal(mat[2], np.array([ + xp_assert_close(mat[2, ...], xp.asarray([ [0.65973961, -0.04736717, 0.7500000], [0.61237244, 0.61237244, -0.5000000], [-0.43559574, 0.78914913, 0.4330127] ])) -def test_from_euler_extrinsic_rotation_313(): - angles = [ +def test_from_euler_extrinsic_rotation_313(xp): + angles = xp.asarray([ [30, 60, 45], [30, 60, 30], [45, 30, 60] - ] + ]) mat = Rotation.from_euler('zxz', angles, degrees=True).as_matrix() - assert_array_almost_equal(mat[0], np.array([ + xp_assert_close(mat[0, ...], xp.asarray([ [0.43559574, -0.65973961, 0.61237244], [0.78914913, -0.04736717, -0.61237244], [0.4330127, 0.75000000, 0.500000] ])) - assert_array_almost_equal(mat[1], np.array([ + xp_assert_close(mat[1, ...], xp.asarray([ [0.62500000, -0.64951905, 0.4330127], [0.64951905, 0.12500000, -0.750000], [0.4330127, 0.75000000, 0.500000] ])) - assert_array_almost_equal(mat[2], np.array([ + xp_assert_close(mat[2, ...], xp.asarray([ [-0.1767767, -0.88388348, 0.4330127], [0.91855865, -0.30618622, -0.250000], [0.35355339, 0.35355339, 0.8660254] ])) +def test_from_euler_array_like(): + rng = np.random.default_rng(123) + order = "xyz" + # Single rotation + r_expected = Rotation.random(rng=rng) + r = Rotation.from_euler(order, r_expected.as_euler(order).tolist()) + assert r_expected.approx_equal(r, atol=1e-12) + + # Multiple rotations + r_expected = Rotation.random(3, rng=rng) + r = Rotation.from_euler(order, r_expected.as_euler(order).tolist()) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) + + +def test_from_euler_scalar(): + rng = np.random.default_rng(123) + deg = rng.uniform(low=-180, high=180) + r_expected = Rotation.from_euler("x", deg, degrees=True) + r = Rotation.from_euler("x", float(deg), degrees=True) + assert r_expected.approx_equal(r, atol=1e-12) + + @pytest.mark.parametrize("seq_tuple", permutations("xyz")) @pytest.mark.parametrize("intrinsic", (False, True)) -def test_as_euler_asymmetric_axes(seq_tuple, intrinsic): +def test_as_euler_asymmetric_axes(xp, seq_tuple, intrinsic): # helper function for mean error tests def test_stats(error, mean_max, rms_max): - mean = np.mean(error, axis=0) - std = np.std(error, axis=0) - rms = np.hypot(mean, std) - assert np.all(np.abs(mean) < mean_max) - assert np.all(rms < rms_max) + mean = xp.mean(error, axis=0) + std = xp.std(error, axis=0) + rms = xp.hypot(mean, std) + assert xp.all(xp.abs(mean) < mean_max) + assert xp.all(rms < rms_max) rnd = np.random.RandomState(0) n = 1000 @@ -744,6 +873,7 @@ def test_stats(error, mean_max, rms_max): angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) angles[:, 1] = rnd.uniform(low=-np.pi / 2, high=np.pi / 2, size=(n,)) angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) + angles = xp.asarray(angles) seq = "".join(seq_tuple) if intrinsic: @@ -752,9 +882,11 @@ def test_stats(error, mean_max, rms_max): seq = seq.upper() rotation = Rotation.from_euler(seq, angles) angles_quat = rotation.as_euler(seq) + # TODO: Why are we using _as_euler_from_matrix here? As a sanity check? It is not + # part of the public API and should not be used anywhere else angles_mat = rotation._as_euler_from_matrix(seq) - assert_allclose(angles, angles_quat, atol=0, rtol=1e-12) - assert_allclose(angles, angles_mat, atol=0, rtol=1e-12) + xp_assert_close(angles, angles_quat, atol=0, rtol=1e-12) + xp_assert_close(angles, angles_mat, atol=0, rtol=1e-12) test_stats(angles_quat - angles, 1e-15, 1e-14) test_stats(angles_mat - angles, 1e-15, 1e-14) @@ -762,14 +894,14 @@ def test_stats(error, mean_max, rms_max): @pytest.mark.parametrize("seq_tuple", permutations("xyz")) @pytest.mark.parametrize("intrinsic", (False, True)) -def test_as_euler_symmetric_axes(seq_tuple, intrinsic): +def test_as_euler_symmetric_axes(xp, seq_tuple, intrinsic): # helper function for mean error tests def test_stats(error, mean_max, rms_max): - mean = np.mean(error, axis=0) - std = np.std(error, axis=0) - rms = np.hypot(mean, std) - assert np.all(np.abs(mean) < mean_max) - assert np.all(rms < rms_max) + mean = xp.mean(error, axis=0) + std = xp.std(error, axis=0) + rms = xp.hypot(mean, std) + assert xp.all(xp.abs(mean) < mean_max) + assert xp.all(rms < rms_max) rnd = np.random.RandomState(0) n = 1000 @@ -777,6 +909,7 @@ def test_stats(error, mean_max, rms_max): angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) angles[:, 1] = rnd.uniform(low=0, high=np.pi, size=(n,)) angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) + angles = xp.asarray(angles) # Rotation of the form A/B/A are rotation around symmetric axes seq = "".join([seq_tuple[0], seq_tuple[1], seq_tuple[0]]) @@ -784,9 +917,10 @@ def test_stats(error, mean_max, rms_max): seq = seq.upper() rotation = Rotation.from_euler(seq, angles) angles_quat = rotation.as_euler(seq) + # TODO: Same as before: Remove _as_euler_from_matrix? angles_mat = rotation._as_euler_from_matrix(seq) - assert_allclose(angles, angles_quat, atol=0, rtol=1e-13) - assert_allclose(angles, angles_mat, atol=0, rtol=1e-9) + xp_assert_close(angles, angles_quat, atol=0, rtol=1e-13) + xp_assert_close(angles, angles_mat, atol=0, rtol=1e-9) test_stats(angles_quat - angles, 1e-16, 1e-14) test_stats(angles_mat - angles, 1e-15, 1e-13) @@ -794,10 +928,11 @@ def test_stats(error, mean_max, rms_max): @pytest.mark.thread_unsafe @pytest.mark.parametrize("seq_tuple", permutations("xyz")) @pytest.mark.parametrize("intrinsic", (False, True)) -def test_as_euler_degenerate_asymmetric_axes(seq_tuple, intrinsic): +def test_as_euler_degenerate_asymmetric_axes(xp, seq_tuple, intrinsic): + atol = 1e-12 # Since we cannot check for angle equality, we check for rotation matrix # equality - angles = np.array([ + angles = xp.asarray([ [45, 90, 35], [35, -90, 20], [35, 90, 25], @@ -811,20 +946,23 @@ def test_as_euler_degenerate_asymmetric_axes(seq_tuple, intrinsic): rotation = Rotation.from_euler(seq, angles, degrees=True) mat_expected = rotation.as_matrix() - with pytest.warns(UserWarning, match="Gimbal lock"): + # We can only warn on non-lazy backends because we'd need to condition on traced + # booleans + with eager_warns(mat_expected, UserWarning, match="Gimbal lock"): angle_estimates = rotation.as_euler(seq, degrees=True) mat_estimated = Rotation.from_euler(seq, angle_estimates, degrees=True).as_matrix() - assert_array_almost_equal(mat_expected, mat_estimated) + xp_assert_close(mat_expected, mat_estimated, atol=atol) @pytest.mark.thread_unsafe @pytest.mark.parametrize("seq_tuple", permutations("xyz")) @pytest.mark.parametrize("intrinsic", (False, True)) -def test_as_euler_degenerate_symmetric_axes(seq_tuple, intrinsic): +def test_as_euler_degenerate_symmetric_axes(xp, seq_tuple, intrinsic): + atol = 1e-12 # Since we cannot check for angle equality, we check for rotation matrix # equality - angles = np.array([ + angles = xp.asarray([ [15, 0, 60], [35, 0, 75], [60, 180, 35], @@ -839,22 +977,23 @@ def test_as_euler_degenerate_symmetric_axes(seq_tuple, intrinsic): rotation = Rotation.from_euler(seq, angles, degrees=True) mat_expected = rotation.as_matrix() - with pytest.warns(UserWarning, match="Gimbal lock"): + # We can only warn on non-lazy backends + with eager_warns(mat_expected, UserWarning, match="Gimbal lock"): angle_estimates = rotation.as_euler(seq, degrees=True) mat_estimated = Rotation.from_euler(seq, angle_estimates, degrees=True).as_matrix() - assert_array_almost_equal(mat_expected, mat_estimated) + xp_assert_close(mat_expected, mat_estimated, atol=atol) @pytest.mark.thread_unsafe @pytest.mark.parametrize("seq_tuple", permutations("xyz")) @pytest.mark.parametrize("intrinsic", (False, True)) -def test_as_euler_degenerate_compare_algorithms(seq_tuple, intrinsic): +def test_as_euler_degenerate_compare_algorithms(xp, seq_tuple, intrinsic): # this test makes sure that both algorithms are doing the same choices # in degenerate cases # asymmetric axes - angles = np.array([ + angles = xp.asarray([ [45, 90, 35], [35, -90, 20], [35, 90, 25], @@ -867,21 +1006,20 @@ def test_as_euler_degenerate_compare_algorithms(seq_tuple, intrinsic): seq = seq.upper() rot = Rotation.from_euler(seq, angles, degrees=True) - with pytest.warns(UserWarning, match="Gimbal lock"): + with eager_warns(rot, UserWarning, match="Gimbal lock"): estimates_matrix = rot._as_euler_from_matrix(seq, degrees=True) - with pytest.warns(UserWarning, match="Gimbal lock"): estimates_quat = rot.as_euler(seq, degrees=True) - assert_allclose( + xp_assert_close( estimates_matrix[:, [0, 2]], estimates_quat[:, [0, 2]], atol=0, rtol=1e-12 ) - assert_allclose(estimates_matrix[:, 1], estimates_quat[:, 1], atol=0, rtol=1e-7) + xp_assert_close(estimates_matrix[:, 1], estimates_quat[:, 1], atol=0, rtol=1e-7) # symmetric axes # Absolute error tolerance must be looser to directly compare the results # from both algorithms, because of numerical loss of precision for the # method _as_euler_from_matrix near a zero angle value - angles = np.array([ + angles = xp.asarray([ [15, 0, 60], [35, 0, 75], [60, 180, 35], @@ -897,45 +1035,49 @@ def test_as_euler_degenerate_compare_algorithms(seq_tuple, intrinsic): seq = seq.upper() rot = Rotation.from_euler(seq, angles, degrees=True) - with pytest.warns(UserWarning, match="Gimbal lock"): + with eager_warns(rot, UserWarning, match="Gimbal lock"): estimates_matrix = rot._as_euler_from_matrix(seq, degrees=True) - with pytest.warns(UserWarning, match="Gimbal lock"): + with eager_warns(rot, UserWarning, match="Gimbal lock"): estimates_quat = rot.as_euler(seq, degrees=True) - assert_allclose( + xp_assert_close( estimates_matrix[:, [0, 2]], estimates_quat[:, [0, 2]], atol=0, rtol=1e-12 ) - assert_allclose( + xp_assert_close( estimates_matrix[~idx, 1], estimates_quat[~idx, 1], atol=0, rtol=1e-7 ) - assert_allclose( + xp_assert_close( estimates_matrix[idx, 1], estimates_quat[idx, 1], atol=1e-6 ) # problematic, angles[1] = 0 -def test_inv(): +def test_inv(xp): + atol = 1e-12 rnd = np.random.RandomState(0) n = 10 # preserve use of old random_state during SPEC 7 transition p = Rotation.random(num=n, random_state=rnd) + p = Rotation.from_quat(xp.asarray(p.as_quat())) q = p.inv() p_mat = p.as_matrix() q_mat = q.as_matrix() - result1 = np.einsum('...ij,...jk->...ik', p_mat, q_mat) - result2 = np.einsum('...ij,...jk->...ik', q_mat, p_mat) + result1 = xp.asarray(np.einsum("...ij,...jk->...ik", p_mat, q_mat)) + result2 = xp.asarray(np.einsum("...ij,...jk->...ik", q_mat, p_mat)) - eye3d = np.empty((n, 3, 3)) - eye3d[:] = np.eye(3) + eye3d = xp.empty((n, 3, 3)) + eye3d = xpx.at(eye3d)[..., :3, :3].set(xp.eye(3)) - assert_array_almost_equal(result1, eye3d) - assert_array_almost_equal(result2, eye3d) + xp_assert_close(result1, eye3d, atol=atol) + xp_assert_close(result2, eye3d, atol=atol) -def test_inv_single_rotation(): +def test_inv_single_rotation(xp): + atol = 1e-12 rng = np.random.default_rng(146972845698875399755764481408308808739) p = Rotation.random(rng=rng) + p = Rotation.from_quat(xp.asarray(p.as_quat())) q = p.inv() p_mat = p.as_matrix() @@ -943,93 +1085,105 @@ def test_inv_single_rotation(): res1 = np.dot(p_mat, q_mat) res2 = np.dot(q_mat, p_mat) - eye = np.eye(3) + eye = xp.eye(3) - assert_array_almost_equal(res1, eye) - assert_array_almost_equal(res2, eye) + xp_assert_close(res1, eye, atol=atol) + xp_assert_close(res2, eye, atol=atol) x = Rotation.random(num=1, rng=rng) + x = Rotation.from_quat(xp.asarray(x.as_quat())) y = x.inv() x_matrix = x.as_matrix() y_matrix = y.as_matrix() - result1 = np.einsum('...ij,...jk->...ik', x_matrix, y_matrix) - result2 = np.einsum('...ij,...jk->...ik', y_matrix, x_matrix) + result1 = xp.linalg.matmul(x_matrix, y_matrix) + result2 = xp.linalg.matmul(y_matrix, x_matrix) - eye3d = np.empty((1, 3, 3)) - eye3d[:] = np.eye(3) + eye3d = xp.empty((1, 3, 3)) + eye3d = xpx.at(eye3d)[..., :3, :3].set(xp.eye(3)) - assert_array_almost_equal(result1, eye3d) - assert_array_almost_equal(result2, eye3d) + xp_assert_close(result1, eye3d, atol=atol) + xp_assert_close(result2, eye3d, atol=atol) -def test_identity_magnitude(): +def test_identity_magnitude(xp): n = 10 - assert_allclose(Rotation.identity(n).magnitude(), 0) - assert_allclose(Rotation.identity(n).inv().magnitude(), 0) + r = Rotation.identity(n) + r = Rotation.from_quat(xp.asarray(r.as_quat())) + expected = xp.zeros(n) + xp_assert_close(r.magnitude(), expected) + xp_assert_close(r.inv().magnitude(), expected) -def test_single_identity_magnitude(): - assert Rotation.identity().magnitude() == 0 - assert Rotation.identity().inv().magnitude() == 0 +def test_single_identity_magnitude(xp): + r = Rotation.from_quat(xp.asarray(Rotation.identity().as_quat())) + assert r.magnitude() == 0 + assert r.inv().magnitude() == 0 -def test_identity_invariance(): +def test_identity_invariance(xp): + atol = 1e-12 n = 10 p = Rotation.random(n, rng=0) - - result = p * Rotation.identity(n) - assert_array_almost_equal(p.as_quat(), result.as_quat()) + p = Rotation.from_quat(xp.asarray(p.as_quat())) + q = Rotation.from_quat(xp.asarray(Rotation.identity(n).as_quat())) + result = p * q + xp_assert_close(p.as_quat(), result.as_quat()) result = result * p.inv() - assert_array_almost_equal(result.magnitude(), np.zeros(n)) + xp_assert_close(result.magnitude(), xp.zeros(n), atol=atol) -def test_single_identity_invariance(): +def test_single_identity_invariance(xp): + atol = 1e-12 n = 10 p = Rotation.random(n, rng=0) + p = Rotation.from_quat(xp.asarray(p.as_quat())) - result = p * Rotation.identity() - assert_array_almost_equal(p.as_quat(), result.as_quat()) + q = Rotation.from_quat(xp.asarray(Rotation.identity().as_quat())) + result = p * q + xp_assert_close(p.as_quat(), result.as_quat()) result = result * p.inv() - assert_array_almost_equal(result.magnitude(), np.zeros(n)) + xp_assert_close(result.magnitude(), xp.zeros(n), atol=atol) -def test_magnitude(): - r = Rotation.from_quat(np.eye(4)) +def test_magnitude(xp): + r = Rotation.from_quat(xp.eye(4)) result = r.magnitude() - assert_array_almost_equal(result, [np.pi, np.pi, np.pi, 0]) + xp_assert_close(result, xp.asarray([xp.pi, xp.pi, xp.pi, 0])) - r = Rotation.from_quat(-np.eye(4)) + r = Rotation.from_quat(-xp.eye(4)) result = r.magnitude() - assert_array_almost_equal(result, [np.pi, np.pi, np.pi, 0]) + xp_assert_close(result, xp.asarray([xp.pi, xp.pi, xp.pi, 0])) -def test_magnitude_single_rotation(): - r = Rotation.from_quat(np.eye(4)) +def test_magnitude_single_rotation(xp): + r = Rotation.from_quat(xp.eye(4)) result1 = r[0].magnitude() - assert_allclose(result1, np.pi) + xp_assert_close(result1, xp.pi) result2 = r[3].magnitude() - assert_allclose(result2, 0) + xp_assert_close(result2, 0.0) -def test_approx_equal(): +def test_approx_equal(xp): rng = np.random.default_rng(146972845698875399755764481408308808739) p = Rotation.random(10, rng=rng) q = Rotation.random(10, rng=rng) + p = Rotation.from_quat(xp.asarray(p.as_quat())) + q = Rotation.from_quat(xp.asarray(q.as_quat())) r = p * q.inv() r_mag = r.magnitude() - atol = np.median(r_mag) # ensure we get mix of Trues and Falses - assert_equal(p.approx_equal(q, atol), (r_mag < atol)) + atol = xp.asarray(np.median(r_mag)) # ensure we get mix of Trues and Falses + xp_assert_equal(p.approx_equal(q, atol), (r_mag < atol)) @pytest.mark.thread_unsafe -def test_approx_equal_single_rotation(): +def test_approx_equal_single_rotation(xp): # also tests passing single argument to approx_equal - p = Rotation.from_rotvec([0, 0, 1e-9]) # less than default atol of 1e-8 - q = Rotation.from_quat(np.eye(4)) + p = Rotation.from_rotvec(xp.asarray([0, 0, 1e-9])) # less than default atol of 1e-8 + q = Rotation.from_quat(xp.eye(4)) assert p.approx_equal(q[3]) assert not p.approx_equal(q[0]) @@ -1040,40 +1194,47 @@ def test_approx_equal_single_rotation(): assert p.approx_equal(q[3], degrees=True) -def test_mean(): +def test_mean(xp): + axes = xp.concat((-xp.eye(3), xp.eye(3))) axes = np.concatenate((-np.eye(3), np.eye(3))) - thetas = np.linspace(0, np.pi / 2, 100) + thetas = xp.linspace(0, xp.pi / 2, 100) for t in thetas: r = Rotation.from_rotvec(t * axes) - assert_allclose(r.mean().magnitude(), 0, atol=1E-10) + xp_assert_close(r.mean().magnitude(), 0.0, atol=1e-10) -def test_weighted_mean(): +def test_weighted_mean(xp): # test that doubling a weight is equivalent to including a rotation twice. - axes = np.array([[0, 0, 0], [1, 0, 0], [1, 0, 0]]) - thetas = np.linspace(0, np.pi / 2, 100) + axes = xp.asarray([[0.0, 0, 0], [1, 0, 0], [1, 0, 0]]) + thetas = xp.linspace(0, xp.pi / 2, 100) for t in thetas: - rw = Rotation.from_rotvec(t * axes[:2]) + rw = Rotation.from_rotvec(t * axes[:2, ...]) mw = rw.mean(weights=[1, 2]) r = Rotation.from_rotvec(t * axes) m = r.mean() - assert_allclose((m * mw.inv()).magnitude(), 0, atol=1E-10) + xp_assert_close((m * mw.inv()).magnitude(), 0.0, atol=1e-10) -def test_mean_invalid_weights(): - with pytest.raises(ValueError, match="non-negative"): - r = Rotation.from_quat(np.eye(4)) - r.mean(weights=-np.ones(4)) +def test_mean_invalid_weights(xp): + r = Rotation.from_quat(xp.eye(4)) + if is_lazy_array(r.as_quat()): + m = r.mean(weights=-xp.ones(4)) + assert all(xp.isnan(m._quat)) + else: + with pytest.raises(ValueError, match="non-negative"): + r.mean(weights=-xp.ones(4)) -def test_reduction_no_indices(): - result = Rotation.identity().reduce(return_indices=False) +def test_reduction_no_indices(xp): + r = Rotation.from_quat(xp.asarray([0.0, 0.0, 0.0, 1.0])) + result = r.reduce(return_indices=False) assert isinstance(result, Rotation) -def test_reduction_none_indices(): - result = Rotation.identity().reduce(return_indices=True) +def test_reduction_none_indices(xp): + r = Rotation.from_quat(xp.asarray([0.0, 0.0, 0.0, 1.0])) + result = r.reduce(return_indices=True) assert type(result) is tuple assert len(result) == 3 @@ -1082,11 +1243,12 @@ def test_reduction_none_indices(): assert right_best is None -def test_reduction_scalar_calculation(): +def test_reduction_scalar_calculation(xp): + atol = 1e-12 rng = np.random.default_rng(146972845698875399755764481408308808739) - l = Rotation.random(5, rng=rng) - r = Rotation.random(10, rng=rng) - p = Rotation.random(7, rng=rng) + l = Rotation.from_quat(xp.asarray(Rotation.random(5, rng=rng).as_quat())) + r = Rotation.from_quat(xp.asarray(Rotation.random(10, rng=rng).as_quat())) + p = Rotation.from_quat(xp.asarray(Rotation.random(7, rng=rng).as_quat())) reduced, left_best, right_best = p.reduce(l, r, return_indices=True) # Loop implementation of the vectorized calculation in Rotation.reduce @@ -1098,66 +1260,66 @@ def test_reduction_scalar_calculation(): scalars = np.reshape(np.moveaxis(scalars, 1, 0), (scalars.shape[1], -1)) max_ind = np.argmax(np.reshape(scalars, (len(p), -1)), axis=1) - left_best_check = max_ind // len(r) - right_best_check = max_ind % len(r) - assert (left_best == left_best_check).all() - assert (right_best == right_best_check).all() + left_best_check = xp.asarray(max_ind // len(r)) + right_best_check = xp.asarray(max_ind % len(r)) + assert xp.all(left_best == left_best_check) + assert xp.all(right_best == right_best_check) reduced_check = l[left_best_check] * p * r[right_best_check] mag = (reduced.inv() * reduced_check).magnitude() - assert_array_almost_equal(mag, np.zeros(len(p))) + xp_assert_close(mag, xp.zeros(len(p)), atol=atol) -def test_apply_single_rotation_single_point(): - mat = np.array([ +def test_apply_single_rotation_single_point(xp): + mat = xp.asarray([ [0, -1, 0], [1, 0, 0], [0, 0, 1] ]) r_1d = Rotation.from_matrix(mat) - r_2d = Rotation.from_matrix(np.expand_dims(mat, axis=0)) + r_2d = Rotation.from_matrix(xp.expand_dims(mat, axis=0)) - v_1d = np.array([1, 2, 3]) - v_2d = np.expand_dims(v_1d, axis=0) - v1d_rotated = np.array([-2, 1, 3]) - v2d_rotated = np.expand_dims(v1d_rotated, axis=0) + v_1d = xp.asarray([1.0, 2, 3]) + v_2d = xp.expand_dims(v_1d, axis=0) + v1d_rotated = xp.asarray([-2.0, 1, 3]) + v2d_rotated = xp.expand_dims(v1d_rotated, axis=0) - assert_allclose(r_1d.apply(v_1d), v1d_rotated) - assert_allclose(r_1d.apply(v_2d), v2d_rotated) - assert_allclose(r_2d.apply(v_1d), v2d_rotated) - assert_allclose(r_2d.apply(v_2d), v2d_rotated) + xp_assert_close(r_1d.apply(v_1d), v1d_rotated) + xp_assert_close(r_1d.apply(v_2d), v2d_rotated) + xp_assert_close(r_2d.apply(v_1d), v2d_rotated) + xp_assert_close(r_2d.apply(v_2d), v2d_rotated) - v1d_inverse = np.array([2, -1, 3]) - v2d_inverse = np.expand_dims(v1d_inverse, axis=0) + v1d_inverse = xp.asarray([2.0, -1, 3]) + v2d_inverse = xp.expand_dims(v1d_inverse, axis=0) - assert_allclose(r_1d.apply(v_1d, inverse=True), v1d_inverse) - assert_allclose(r_1d.apply(v_2d, inverse=True), v2d_inverse) - assert_allclose(r_2d.apply(v_1d, inverse=True), v2d_inverse) - assert_allclose(r_2d.apply(v_2d, inverse=True), v2d_inverse) + xp_assert_close(r_1d.apply(v_1d, inverse=True), v1d_inverse) + xp_assert_close(r_1d.apply(v_2d, inverse=True), v2d_inverse) + xp_assert_close(r_2d.apply(v_1d, inverse=True), v2d_inverse) + xp_assert_close(r_2d.apply(v_2d, inverse=True), v2d_inverse) -def test_apply_single_rotation_multiple_points(): - mat = np.array([ +def test_apply_single_rotation_multiple_points(xp): + mat = xp.asarray([ [0, -1, 0], [1, 0, 0], [0, 0, 1] ]) r1 = Rotation.from_matrix(mat) - r2 = Rotation.from_matrix(np.expand_dims(mat, axis=0)) + r2 = Rotation.from_matrix(xp.expand_dims(mat, axis=0)) - v = np.array([[1, 2, 3], [4, 5, 6]]) - v_rotated = np.array([[-2, 1, 3], [-5, 4, 6]]) + v = xp.asarray([[1, 2, 3], [4, 5, 6]]) + v_rotated = xp.asarray([[-2.0, 1, 3], [-5, 4, 6]]) - assert_allclose(r1.apply(v), v_rotated) - assert_allclose(r2.apply(v), v_rotated) + xp_assert_close(r1.apply(v), v_rotated) + xp_assert_close(r2.apply(v), v_rotated) - v_inverse = np.array([[2, -1, 3], [5, -4, 6]]) + v_inverse = xp.asarray([[2.0, -1, 3], [5, -4, 6]]) - assert_allclose(r1.apply(v, inverse=True), v_inverse) - assert_allclose(r2.apply(v, inverse=True), v_inverse) + xp_assert_close(r1.apply(v, inverse=True), v_inverse) + xp_assert_close(r2.apply(v, inverse=True), v_inverse) -def test_apply_multiple_rotations_single_point(): +def test_apply_multiple_rotations_single_point(xp): mat = np.empty((2, 3, 3)) mat[0] = np.array([ [0, -1, 0], @@ -1169,23 +1331,24 @@ def test_apply_multiple_rotations_single_point(): [0, 0, -1], [0, 1, 0] ]) + mat = xp.asarray(mat) r = Rotation.from_matrix(mat) - v1 = np.array([1, 2, 3]) - v2 = np.expand_dims(v1, axis=0) + v1 = xp.asarray([1, 2, 3]) + v2 = xp.expand_dims(v1, axis=0) - v_rotated = np.array([[-2, 1, 3], [1, -3, 2]]) + v_rotated = xp.asarray([[-2.0, 1, 3], [1, -3, 2]]) - assert_allclose(r.apply(v1), v_rotated) - assert_allclose(r.apply(v2), v_rotated) + xp_assert_close(r.apply(v1), v_rotated) + xp_assert_close(r.apply(v2), v_rotated) - v_inverse = np.array([[2, -1, 3], [1, 3, -2]]) + v_inverse = xp.asarray([[2.0, -1, 3], [1, 3, -2]]) - assert_allclose(r.apply(v1, inverse=True), v_inverse) - assert_allclose(r.apply(v2, inverse=True), v_inverse) + xp_assert_close(r.apply(v1, inverse=True), v_inverse) + xp_assert_close(r.apply(v2, inverse=True), v_inverse) -def test_apply_multiple_rotations_multiple_points(): +def test_apply_multiple_rotations_multiple_points(xp): mat = np.empty((2, 3, 3)) mat[0] = np.array([ [0, -1, 0], @@ -1197,22 +1360,24 @@ def test_apply_multiple_rotations_multiple_points(): [0, 0, -1], [0, 1, 0] ]) + mat = xp.asarray(mat) r = Rotation.from_matrix(mat) - v = np.array([[1, 2, 3], [4, 5, 6]]) - v_rotated = np.array([[-2, 1, 3], [4, -6, 5]]) - assert_allclose(r.apply(v), v_rotated) + v = xp.asarray([[1, 2, 3], [4, 5, 6]]) + v_rotated = xp.asarray([[-2.0, 1, 3], [4, -6, 5]]) + xp_assert_close(r.apply(v), v_rotated) - v_inverse = np.array([[2, -1, 3], [4, 6, -5]]) - assert_allclose(r.apply(v, inverse=True), v_inverse) + v_inverse = xp.asarray([[2.0, -1, 3], [4, 6, -5]]) + xp_assert_close(r.apply(v, inverse=True), v_inverse) -def test_apply_shapes(): - vector0 = np.array([1.0, 2.0, 3.0]) - vector1 = np.array([vector0]) - vector2 = np.array([vector0, vector0]) - matrix0 = np.identity(3) - matrix1 = np.array([matrix0]) - matrix2 = np.array([matrix0, matrix0]) + +def test_apply_shapes(xp): + vector0 = xp.asarray([1.0, 2.0, 3.0]) + vector1 = xp.asarray([vector0]) + vector2 = xp.asarray([vector0, vector0]) + matrix0 = xp.eye(3) + matrix1 = xp.asarray([matrix0]) + matrix2 = xp.asarray([matrix0, matrix0]) for m, v in product([matrix0, matrix1, matrix2], [vector0, vector1, vector2]): r = Rotation.from_matrix(m) @@ -1224,7 +1389,23 @@ def test_apply_shapes(): x = r.apply(v, inverse=True) assert x.shape == shape -def test_getitem(): + +def test_apply_array_like(): + rng = np.random.default_rng(123) + # Single vector + r = Rotation.random(rng=rng) + t = rng.uniform(-100, 100, size=(3,)) + v = r.apply(t.tolist()) + v_expected = r.apply(t) + xp_assert_close(v, v_expected, atol=1e-12) + # Multiple vectors + t = rng.uniform(-100, 100, size=(2, 3)) + v = r.apply(t.tolist()) + v_expected = r.apply(t) + xp_assert_close(v, v_expected, atol=1e-12) + + +def test_getitem(xp): mat = np.empty((2, 3, 3)) mat[0] = np.array([ [0, -1, 0], @@ -1236,47 +1417,60 @@ def test_getitem(): [0, 0, -1], [0, 1, 0] ]) + mat = xp.asarray(mat) r = Rotation.from_matrix(mat) - assert_allclose(r[0].as_matrix(), mat[0], atol=1e-15) - assert_allclose(r[1].as_matrix(), mat[1], atol=1e-15) - assert_allclose(r[:-1].as_matrix(), np.expand_dims(mat[0], axis=0), atol=1e-15) + xp_assert_close(r[0].as_matrix(), mat[0], atol=1e-15) + xp_assert_close(r[1].as_matrix(), mat[1, ...], atol=1e-15) + xp_assert_close(r[:-1].as_matrix(), xp.expand_dims(mat[0, ...], axis=0), atol=1e-15) -def test_getitem_single(): +def test_getitem_single(xp): with pytest.raises(TypeError, match='not subscriptable'): - Rotation.identity()[0] + Rotation.from_quat(xp.asarray([0, 0, 0, 1]))[0] -def test_setitem_single(): - r = Rotation.identity() +def test_getitem_array_like(): + mat = np.array([[[0.0, -1, 0], + [1, 0, 0], + [0, 0, 1]], + [[1, 0, 0], + [0, 0, -1], + [0, 1, 0]]]) + r = Rotation.from_matrix(mat) + xp_assert_close(r[[0]].as_matrix(), mat[[0]], atol=1e-15) + xp_assert_close(r[[0, 1]].as_matrix(), mat[[0, 1]], atol=1e-15) + + +def test_setitem_single(xp): + r = Rotation.from_quat(xp.asarray([0, 0, 0, 1])) with pytest.raises(TypeError, match='not subscriptable'): - r[0] = Rotation.identity() + r[0] = Rotation.from_quat(xp.asarray([0, 0, 0, 1])) -def test_setitem_slice(): +def test_setitem_slice(xp): rng = np.random.default_rng(146972845698875399755764481408308808739) - r1 = Rotation.random(10, rng=rng) - r2 = Rotation.random(5, rng=rng) + r1 = Rotation.from_quat(xp.asarray(Rotation.random(10, rng=rng).as_quat())) + r2 = Rotation.from_quat(xp.asarray(Rotation.random(5, rng=rng).as_quat())) r1[1:6] = r2 - assert_equal(r1[1:6].as_quat(), r2.as_quat()) + xp_assert_equal(r1[1:6].as_quat(), r2.as_quat()) -def test_setitem_integer(): +def test_setitem_integer(xp): rng = np.random.default_rng(146972845698875399755764481408308808739) - r1 = Rotation.random(10, rng=rng) - r2 = Rotation.random(rng=rng) + r1 = Rotation.from_quat(xp.asarray(Rotation.random(10, rng=rng).as_quat())) + r2 = Rotation.from_quat(xp.asarray(Rotation.random(rng=rng).as_quat())) r1[1] = r2 - assert_equal(r1[1].as_quat(), r2.as_quat()) + xp_assert_equal(r1[1].as_quat(), r2.as_quat()) -def test_setitem_wrong_type(): - r = Rotation.random(10, rng=0) +def test_setitem_wrong_type(xp): + r = Rotation.from_quat(xp.asarray(Rotation.random(10, rng=0).as_quat())) with pytest.raises(TypeError, match='Rotation object'): r[0] = 1 -def test_n_rotations(): +def test_n_rotations(xp): mat = np.empty((2, 3, 3)) mat[0] = np.array([ [0, -1, 0], @@ -1288,6 +1482,7 @@ def test_n_rotations(): [0, 0, -1], [0, 1, 0] ]) + mat = xp.asarray(mat) r = Rotation.from_matrix(mat) assert_equal(len(r), 2) @@ -1295,6 +1490,7 @@ def test_n_rotations(): def test_random_rotation_shape(): + # No xp testing since random rotations are always using NumPy rng = np.random.default_rng(146972845698875399755764481408308808739) assert_equal(Rotation.random(rng=rng).as_quat().shape, (4,)) assert_equal(Rotation.random(None, rng=rng).as_quat().shape, (4,)) @@ -1303,80 +1499,80 @@ def test_random_rotation_shape(): assert_equal(Rotation.random(5, rng=rng).as_quat().shape, (5, 4)) -def test_align_vectors_no_rotation(): - x = np.array([[1, 2, 3], [4, 5, 6]]) - y = x.copy() +def test_align_vectors_no_rotation(xp): + x = xp.asarray([[1, 2, 3], [4, 5, 6]]) + y = xp.asarray(x, copy=True) r, rssd = Rotation.align_vectors(x, y) - assert_array_almost_equal(r.as_matrix(), np.eye(3)) - assert_allclose(rssd, 0, atol=1e-6) + xp_assert_close(r.as_matrix(), xp.eye(3), atol=1e-12) + xp_assert_close(rssd, xp.asarray(0.0)[()], check_shape=False, atol=1e-6) -def test_align_vectors_no_noise(): +def test_align_vectors_no_noise(xp): rng = np.random.default_rng(14697284569885399755764481408308808739) - c = Rotation.random(rng=rng) - b = rng.normal(size=(5, 3)) + c = Rotation.from_quat(xp.asarray(Rotation.random(rng=rng).as_quat())) + b = xp.asarray(rng.normal(size=(5, 3))) a = c.apply(b) est, rssd = Rotation.align_vectors(a, b) - assert_allclose(c.as_quat(), est.as_quat()) - assert_allclose(rssd, 0, atol=1e-7) + xp_assert_close(c.as_quat(), est.as_quat()) + xp_assert_close(rssd, xp.asarray(0.0)[()], check_shape=False, atol=1e-7) -def test_align_vectors_improper_rotation(): +def test_align_vectors_improper_rotation(xp): # Tests correct logic for issue #10444 - x = np.array([[0.89299824, -0.44372674, 0.0752378], - [0.60221789, -0.47564102, -0.6411702]]) - y = np.array([[0.02386536, -0.82176463, 0.5693271], - [-0.27654929, -0.95191427, -0.1318321]]) + x = xp.asarray([[0.89299824, -0.44372674, 0.0752378], + [0.60221789, -0.47564102, -0.6411702]]) + y = xp.asarray([[0.02386536, -0.82176463, 0.5693271], + [-0.27654929, -0.95191427, -0.1318321]]) est, rssd = Rotation.align_vectors(x, y) - assert_allclose(x, est.apply(y), atol=1e-6) - assert_allclose(rssd, 0, atol=1e-7) + xp_assert_close(x, est.apply(y), atol=1e-6) + xp_assert_close(rssd, xp.asarray(0.0)[()], check_shape=False, atol=1e-7) -def test_align_vectors_rssd_sensitivity(): - rssd_expected = 0.141421356237308 - sens_expected = np.array([[0.2, 0. , 0.], - [0. , 1.5, 1.], - [0. , 1. , 1.]]) +def test_align_vectors_rssd_sensitivity(xp): + rssd_expected = xp.asarray(0.141421356237308)[()] + sens_expected = xp.asarray([[0.2, 0. , 0.], + [0. , 1.5, 1.], + [0. , 1. , 1.]]) atol = 1e-6 - a = [[0, 1, 0], [0, 1, 1], [0, 1, 1]] - b = [[1, 0, 0], [1, 1.1, 0], [1, 0.9, 0]] + a = xp.asarray([[0, 1, 0], [0, 1, 1], [0, 1, 1]]) + b = xp.asarray([[1, 0, 0], [1, 1.1, 0], [1, 0.9, 0]]) rot, rssd, sens = Rotation.align_vectors(a, b, return_sensitivity=True) - assert np.isclose(rssd, rssd_expected, atol=atol) - assert np.allclose(sens, sens_expected, atol=atol) + xp_assert_close(rssd, rssd_expected, atol=atol) + xp_assert_close(sens, sens_expected, atol=atol) -def test_align_vectors_scaled_weights(): +def test_align_vectors_scaled_weights(xp): n = 10 - a = Rotation.random(n, rng=0).apply([1, 0, 0]) - b = Rotation.random(n, rng=1).apply([1, 0, 0]) + a = xp.asarray(Rotation.random(n, rng=0).apply([1, 0, 0])) + b = xp.asarray(Rotation.random(n, rng=1).apply([1, 0, 0])) scale = 2 - est1, rssd1, cov1 = Rotation.align_vectors(a, b, np.ones(n), True) - est2, rssd2, cov2 = Rotation.align_vectors(a, b, scale * np.ones(n), True) + est1, rssd1, cov1 = Rotation.align_vectors(a, b, xp.ones(n), True) + est2, rssd2, cov2 = Rotation.align_vectors(a, b, scale * xp.ones(n), True) - assert_allclose(est1.as_matrix(), est2.as_matrix()) - assert_allclose(np.sqrt(scale) * rssd1, rssd2, atol=1e-6) - assert_allclose(cov1, cov2) + xp_assert_close(est1.as_matrix(), est2.as_matrix()) + xp_assert_close(math.sqrt(scale) * rssd1, rssd2, atol=1e-6) + xp_assert_close(cov1, cov2) -def test_align_vectors_noise(): +def test_align_vectors_noise(xp): rng = np.random.default_rng(146972845698875399755764481408308808739) n_vectors = 100 - rot = Rotation.random(rng=rng) - vectors = rng.normal(size=(n_vectors, 3)) + rot = rotation_to_xp(Rotation.random(rng=rng), xp) + vectors = xp.asarray(rng.normal(size=(n_vectors, 3))) result = rot.apply(vectors) # The paper adds noise as independently distributed angular errors sigma = np.deg2rad(1) tolerance = 1.5 * sigma noise = Rotation.from_rotvec( - rng.normal( + xp.asarray(rng.normal( size=(n_vectors, 3), scale=sigma - ) + )) ) # Attitude errors must preserve norm. Hence apply individual random @@ -1388,99 +1584,134 @@ def test_align_vectors_noise(): # Use rotation compositions to find out closeness error_vector = (rot * est.inv()).as_rotvec() - assert_allclose(error_vector[0], 0, atol=tolerance) - assert_allclose(error_vector[1], 0, atol=tolerance) - assert_allclose(error_vector[2], 0, atol=tolerance) + xp_assert_close(error_vector[0], xp.asarray(0.0)[()], atol=tolerance) + xp_assert_close(error_vector[1], xp.asarray(0.0)[()], atol=tolerance) + xp_assert_close(error_vector[2], xp.asarray(0.0)[()], atol=tolerance) # Check error bounds using covariance matrix cov *= sigma - assert_allclose(cov[0, 0], 0, atol=tolerance) - assert_allclose(cov[1, 1], 0, atol=tolerance) - assert_allclose(cov[2, 2], 0, atol=tolerance) + xp_assert_close(cov[0, 0], xp.asarray(0.0)[()], atol=tolerance) + xp_assert_close(cov[1, 1], xp.asarray(0.0)[()], atol=tolerance) + xp_assert_close(cov[2, 2], xp.asarray(0.0)[()], atol=tolerance) - assert_allclose(rssd, np.sum((noisy_result - est.apply(vectors))**2)**0.5) + rssd_check = xp.sum((noisy_result - est.apply(vectors)) ** 2) ** 0.5 + xp_assert_close(rssd, rssd_check, check_shape=False) -def test_align_vectors_invalid_input(): +def test_align_vectors_invalid_input(xp): with pytest.raises(ValueError, match="Expected input `a` to have shape"): - Rotation.align_vectors([1, 2, 3, 4], [1, 2, 3]) + a, b = xp.asarray([1, 2, 3, 4]), xp.asarray([1, 2, 3]) + Rotation.align_vectors(a, b) with pytest.raises(ValueError, match="Expected input `b` to have shape"): - Rotation.align_vectors([1, 2, 3], [1, 2, 3, 4]) + a, b = xp.asarray([1, 2, 3]), xp.asarray([1, 2, 3, 4]) + Rotation.align_vectors(a, b) with pytest.raises(ValueError, match="Expected inputs `a` and `b` " "to have same shapes"): - Rotation.align_vectors([[1, 2, 3],[4, 5, 6]], [[1, 2, 3]]) + a, b = xp.asarray([[1, 2, 3], [4, 5, 6]]), xp.asarray([[1, 2, 3]]) + Rotation.align_vectors(a, b) with pytest.raises(ValueError, match="Expected `weights` to be 1 dimensional"): - Rotation.align_vectors([[1, 2, 3]], [[1, 2, 3]], weights=[[1]]) + a, b = xp.asarray([[1, 2, 3]]), xp.asarray([[1, 2, 3]]) + weights = xp.asarray([[1]]) + Rotation.align_vectors(a, b, weights) with pytest.raises(ValueError, match="Expected `weights` to have number of values"): - Rotation.align_vectors([[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]], - weights=[1, 2, 3]) - - with pytest.raises(ValueError, - match="`weights` may not contain negative values"): - Rotation.align_vectors([[1, 2, 3]], [[1, 2, 3]], weights=[-1]) - - with pytest.raises(ValueError, - match="Only one infinite weight is allowed"): - Rotation.align_vectors([[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]], - weights=[np.inf, np.inf]) - - with pytest.raises(ValueError, - match="Cannot align zero length primary vectors"): - Rotation.align_vectors([[0, 0, 0]], [[1, 2, 3]]) - - with pytest.raises(ValueError, - match="Cannot return sensitivity matrix"): - Rotation.align_vectors([[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]], - return_sensitivity=True, weights=[np.inf, 1]) - - with pytest.raises(ValueError, - match="Cannot return sensitivity matrix"): - Rotation.align_vectors([[1, 2, 3]], [[1, 2, 3]], - return_sensitivity=True) - - -def test_align_vectors_align_constrain(): + a, b = xp.asarray([[1, 2, 3], [4, 5, 6]]), xp.asarray([[1, 2, 3], [4, 5, 6]]) + weights = xp.asarray([1, 2, 3]) + Rotation.align_vectors(a, b, weights) + + a, b = xp.asarray([[1, 2, 3]]), xp.asarray([[1, 2, 3]]) + weights = xp.asarray([-1]) + if is_lazy_array(weights): + r, rssd = Rotation.align_vectors(a, b, weights) + assert xp.all(xp.isnan(r.as_quat())), "Quaternion should be nan" + assert xp.isnan(rssd), "RSSD should be nan" + else: + with pytest.raises(ValueError, + match="`weights` may not contain negative values"): + Rotation.align_vectors(a, b, weights) + + a, b = xp.asarray([[1, 2, 3], [4, 5, 6]]), xp.asarray([[1, 2, 3], [4, 5, 6]]) + weights = xp.asarray([xp.inf, xp.inf]) + if is_lazy_array(weights): + r, rssd = Rotation.align_vectors(a, b, weights) + assert xp.all(xp.isnan(r.as_quat())), "Quaternion should be nan" + assert xp.isnan(rssd), "RSSD should be nan" + else: + with pytest.raises(ValueError, + match="Only one infinite weight is allowed"): + Rotation.align_vectors(a, b, weights) + + a, b = xp.asarray([[0, 0, 0]]), xp.asarray([[1, 2, 3]]) + if is_lazy_array(a): + r, rssd = Rotation.align_vectors(a, b) + assert xp.all(xp.isnan(r.as_quat())), "Quaternion should be nan" + assert xp.isnan(rssd), "RSSD should be nan" + else: + with pytest.raises(ValueError, + match="Cannot align zero length primary vectors"): + Rotation.align_vectors(a, b) + + a, b = xp.asarray([[1, 2, 3], [4, 5, 6]]), xp.asarray([[1, 2, 3], [4, 5, 6]]) + weights = xp.asarray([xp.inf, 1]) + if is_lazy_array(a): + r, rssd, sens = Rotation.align_vectors(a, b, weights, return_sensitivity=True) + assert xp.all(xp.isnan(sens)), "Sensitivity matrix should be nan" + else: + with pytest.raises(ValueError, + match="Cannot return sensitivity matrix"): + Rotation.align_vectors(a, b, weights, return_sensitivity=True) + + a, b = xp.asarray([[1, 2, 3]]), xp.asarray([[1, 2, 3]]) + if is_lazy_array(a): + r, rssd, sens = Rotation.align_vectors(a, b, return_sensitivity=True) + assert xp.all(xp.isnan(sens)), "Sensitivity matrix should be nan" + else: + with pytest.raises(ValueError, + match="Cannot return sensitivity matrix"): + Rotation.align_vectors(a, b, return_sensitivity=True) + + +def test_align_vectors_align_constrain(xp): # Align the primary +X B axis with the primary +Y A axis, and rotate about # it such that the +Y B axis (residual of the [1, 1, 0] secondary b vector) # is aligned with the +Z A axis (residual of the [0, 1, 1] secondary a # vector) atol = 1e-12 - b = [[1, 0, 0], [1, 1, 0]] - a = [[0, 1, 0], [0, 1, 1]] - m_expected = np.array([[0, 0, 1], - [1, 0, 0], - [0, 1, 0]]) - R, rssd = Rotation.align_vectors(a, b, weights=[np.inf, 1]) - assert_allclose(R.as_matrix(), m_expected, atol=atol) - assert_allclose(R.apply(b), a, atol=atol) # Pri and sec align exactly - assert np.isclose(rssd, 0, atol=atol) + b = xp.asarray([[1, 0, 0], [1, 1, 0]]) + a = xp.asarray([[0.0, 1, 0], [0, 1, 1]]) + m_expected = xp.asarray([[0.0, 0, 1], + [1, 0, 0], + [0, 1, 0]]) + R, rssd = Rotation.align_vectors(a, b, weights=xp.asarray([xp.inf, 1])) + xp_assert_close(R.as_matrix(), m_expected, atol=atol) + xp_assert_close(R.apply(b), a, atol=atol) # Pri and sec align exactly + assert xpx.isclose(rssd, 0.0, atol=atol, xp=xp) # Do the same but with an inexact secondary rotation - b = [[1, 0, 0], [1, 2, 0]] + b = xp.asarray([[1, 0, 0], [1, 2, 0]]) rssd_expected = 1.0 - R, rssd = Rotation.align_vectors(a, b, weights=[np.inf, 1]) - assert_allclose(R.as_matrix(), m_expected, atol=atol) - assert_allclose(R.apply(b)[0], a[0], atol=atol) # Only pri aligns exactly - assert np.isclose(rssd, rssd_expected, atol=atol) - a_expected = [[0, 1, 0], [0, 1, 2]] - assert_allclose(R.apply(b), a_expected, atol=atol) + R, rssd = Rotation.align_vectors(a, b, weights=xp.asarray([xp.inf, 1])) + xp_assert_close(R.as_matrix(), m_expected, atol=atol) + xp_assert_close(R.apply(b)[0, ...], a[0, ...], atol=atol) # Only pri aligns exactly + assert xpx.isclose(rssd, rssd_expected, atol=atol, xp=xp) + a_expected = xp.asarray([[0.0, 1, 0], [0, 1, 2]]) + xp_assert_close(R.apply(b), a_expected, atol=atol) # Check random vectors - b = [[1, 2, 3], [-2, 3, -1]] - a = [[-1, 3, 2], [1, -1, 2]] + b = xp.asarray([[1, 2, 3], [-2, 3, -1]]) + a = xp.asarray([[-1.0, 3, 2], [1, -1, 2]]) rssd_expected = 1.3101595297515016 - R, rssd = Rotation.align_vectors(a, b, weights=[np.inf, 1]) - assert_allclose(R.apply(b)[0], a[0], atol=atol) # Only pri aligns exactly - assert np.isclose(rssd, rssd_expected, atol=atol) + R, rssd = Rotation.align_vectors(a, b, weights=xp.asarray([xp.inf, 1])) + xp_assert_close(R.apply(b)[0, ...], a[0, ...], atol=atol) # Only pri aligns exactly + assert xpx.isclose(rssd, rssd_expected, atol=atol, xp=xp) -def test_align_vectors_near_inf(): +def test_align_vectors_near_inf(xp): # align_vectors should return near the same result for high weights as for # infinite weights. rssd will be different with floating point error on the # exactly aligned vector being multiplied by a large non-infinite weight @@ -1491,58 +1722,60 @@ def test_align_vectors_near_inf(): for i in range(n): # Get random pairs of 3-element vectors - a = [1*mats[0][i][0], 2*mats[1][i][0]] - b = [3*mats[2][i][0], 4*mats[3][i][0]] + a = xp.asarray([1 * mats[0][i][0], 2 * mats[1][i][0]]) + b = xp.asarray([3 * mats[2][i][0], 4 * mats[3][i][0]]) R, _ = Rotation.align_vectors(a, b, weights=[1e10, 1]) - R2, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1]) - assert_allclose(R.as_matrix(), R2.as_matrix(), atol=1e-4) + R2, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1]) + xp_assert_close(R.as_matrix(), R2.as_matrix(), atol=1e-4) for i in range(n): # Get random triplets of 3-element vectors - a = [1*mats[0][i][0], 2*mats[1][i][0], 3*mats[2][i][0]] - b = [4*mats[3][i][0], 5*mats[4][i][0], 6*mats[5][i][0]] + a = xp.asarray([1*mats[0][i][0], 2*mats[1][i][0], 3*mats[2][i][0]]) + b = xp.asarray([4*mats[3][i][0], 5*mats[4][i][0], 6*mats[5][i][0]]) R, _ = Rotation.align_vectors(a, b, weights=[1e10, 2, 1]) - R2, _ = Rotation.align_vectors(a, b, weights=[np.inf, 2, 1]) - assert_allclose(R.as_matrix(), R2.as_matrix(), atol=1e-4) + R2, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 2, 1]) + xp_assert_close(R.as_matrix(), R2.as_matrix(), atol=1e-4) -def test_align_vectors_parallel(): +def test_align_vectors_parallel(xp): atol = 1e-12 - a = [[1, 0, 0], [0, 1, 0]] - b = [[0, 1, 0], [0, 1, 0]] - m_expected = np.array([[0, 1, 0], - [-1, 0, 0], - [0, 0, 1]]) - R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1]) - assert_allclose(R.as_matrix(), m_expected, atol=atol) - R, _ = Rotation.align_vectors(a[0], b[0]) - assert_allclose(R.as_matrix(), m_expected, atol=atol) - assert_allclose(R.apply(b[0]), a[0], atol=atol) - - b = [[1, 0, 0], [1, 0, 0]] - m_expected = np.array([[1, 0, 0], - [0, 1, 0], - [0, 0, 1]]) - R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1]) - assert_allclose(R.as_matrix(), m_expected, atol=atol) - R, _ = Rotation.align_vectors(a[0], b[0]) - assert_allclose(R.as_matrix(), m_expected, atol=atol) - assert_allclose(R.apply(b[0]), a[0], atol=atol) - - -def test_align_vectors_antiparallel(): + a = xp.asarray([[1.0, 0, 0], [0, 1, 0]]) + b = xp.asarray([[0.0, 1, 0], [0, 1, 0]]) + m_expected = xp.asarray([[0.0, 1, 0], + [-1, 0, 0], + [0, 0, 1]]) + R, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1]) + xp_assert_close(R.as_matrix(), m_expected, atol=atol) + R, _ = Rotation.align_vectors(a[0, ...], b[0, ...]) + xp_assert_close(R.as_matrix(), m_expected, atol=atol) + xp_assert_close(R.apply(b[0, ...]), a[0, ...], atol=atol) + + b = xp.asarray([[1, 0, 0], [1, 0, 0]]) + m_expected = xp.asarray([[1.0, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + R, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1]) + xp_assert_close(R.as_matrix(), m_expected, atol=atol) + R, _ = Rotation.align_vectors(a[0, ...], b[0, ...]) + xp_assert_close(R.as_matrix(), m_expected, atol=atol) + xp_assert_close(R.apply(b[0, ...]), a[0, ...], atol=atol) + + +def test_align_vectors_antiparallel(xp): # Test exact 180 deg rotation atol = 1e-12 - as_to_test = np.array([[[1, 0, 0], [0, 1, 0]], + as_to_test = np.array([[[1.0, 0, 0], [0, 1, 0]], [[0, 1, 0], [1, 0, 0]], [[0, 0, 1], [0, 1, 0]]]) + bs_to_test = [[-a[0], a[1]] for a in as_to_test] for a, b in zip(as_to_test, bs_to_test): - R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1]) - assert_allclose(R.magnitude(), np.pi, atol=atol) - assert_allclose(R.apply(b[0]), a[0], atol=atol) + a, b = xp.asarray(a), xp.asarray(b) + R, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1]) + xp_assert_close(R.magnitude(), xp.pi, atol=atol) + xp_assert_close(R.apply(b[0, ...]), a[0, ...], atol=atol) # Test exact rotations near 180 deg Rs = Rotation.random(100, rng=0) @@ -1551,42 +1784,60 @@ def test_align_vectors_antiparallel(): b = [[-1, 0, 0], [0, 1, 0]] as_to_test = [] for dR in dRs: - as_to_test.append([dR.apply(a[0]), a[1]]) + as_to_test.append(np.array([dR.apply(a[0]), a[1]])) for a in as_to_test: - R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1]) + a, b = xp.asarray(a), xp.asarray(b) + R, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1]) R2, _ = Rotation.align_vectors(a, b, weights=[1e10, 1]) - assert_allclose(R.as_matrix(), R2.as_matrix(), atol=atol) + xp_assert_close(R.as_matrix(), R2.as_matrix(), atol=atol) -def test_align_vectors_primary_only(): +def test_align_vectors_primary_only(xp): atol = 1e-12 mats_a = Rotation.random(100, rng=0).as_matrix() mats_b = Rotation.random(100, rng=1).as_matrix() + for mat_a, mat_b in zip(mats_a, mats_b): # Get random 3-element unit vectors - a = mat_a[0] - b = mat_b[0] + a = xp.asarray(mat_a[0]) + b = xp.asarray(mat_b[0]) # Compare to align_vectors with primary only R, rssd = Rotation.align_vectors(a, b) - assert_allclose(R.apply(b), a, atol=atol) + xp_assert_close(R.apply(b), a, atol=atol) assert np.isclose(rssd, 0, atol=atol) -def test_repr_single_rotation(): - q = np.array([0, 0, 0, 1]) +def test_align_vectors_array_like(): + rng = np.random.default_rng(123) + c = Rotation.random(rng=rng) + b = rng.normal(size=(5, 3)) + a = c.apply(b) + + est_expected, rssd_expected = Rotation.align_vectors(a, b) + est, rssd = Rotation.align_vectors(a.tolist(), b.tolist()) + xp_assert_close(est_expected.as_quat(), est.as_quat()) + xp_assert_close(rssd, rssd_expected) + + +def test_repr_single_rotation(xp): + q = xp.asarray([0, 0, 0, 1]) actual = repr(Rotation.from_quat(q)) - expected = """\ + if is_numpy(xp): + expected = """\ Rotation.from_matrix(array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]))""" - assert actual == expected + assert actual == expected + else: + assert actual.startswith("Rotation.from_matrix(") -def test_repr_rotation_sequence(): - q = np.array([[0, 1, 0, 1], [0, 0, 1, 1]]) / np.sqrt(2) +def test_repr_rotation_sequence(xp): + q = xp.asarray([[0.0, 1, 0, 1], [0, 0, 1, 1]]) / math.sqrt(2) actual = f"{Rotation.from_quat(q)!r}" - expected = """\ + if is_numpy(xp): + expected = """\ Rotation.from_matrix(array([[[ 0., 0., 1.], [ 0., 1., 0.], [-1., 0., 0.]], @@ -1594,204 +1845,242 @@ def test_repr_rotation_sequence(): [[ 0., -1., 0.], [ 1., 0., 0.], [ 0., 0., 1.]]]))""" - assert actual == expected + assert actual == expected + else: + assert actual.startswith("Rotation.from_matrix(") -def test_slerp(): +def test_slerp(xp): rnd = np.random.RandomState(0) - key_rots = Rotation.from_quat(rnd.uniform(size=(5, 4))) + key_rots = Rotation.from_quat(xp.asarray(rnd.uniform(size=(5, 4)))) key_quats = key_rots.as_quat() key_times = [0, 1, 2, 3, 4] interpolator = Slerp(key_times, key_rots) + assert isinstance(interpolator.times, type(xp.asarray(0))) times = [0, 0.5, 0.25, 1, 1.5, 2, 2.75, 3, 3.25, 3.60, 4] interp_rots = interpolator(times) interp_quats = interp_rots.as_quat() # Dot products are affected by sign of quaternions - interp_quats[interp_quats[:, -1] < 0] *= -1 + mask = (interp_quats[:, -1] < 0)[:, None] + interp_quats = xp.where(mask, -interp_quats, interp_quats) # Checking for quaternion equality, perform same operation - key_quats[key_quats[:, -1] < 0] *= -1 + mask = (key_quats[:, -1] < 0)[:, None] + key_quats = xp.where(mask, -key_quats, key_quats) # Equality at keyframes, including both endpoints - assert_allclose(interp_quats[0], key_quats[0]) - assert_allclose(interp_quats[3], key_quats[1]) - assert_allclose(interp_quats[5], key_quats[2]) - assert_allclose(interp_quats[7], key_quats[3]) - assert_allclose(interp_quats[10], key_quats[4]) + xp_assert_close(interp_quats[0, ...], key_quats[0, ...]) + xp_assert_close(interp_quats[3, ...], key_quats[1, ...]) + xp_assert_close(interp_quats[5, ...], key_quats[2, ...]) + xp_assert_close(interp_quats[7, ...], key_quats[3, ...]) + xp_assert_close(interp_quats[10, ...], key_quats[4, ...]) # Constant angular velocity between keyframes. Check by equating # cos(theta) between quaternion pairs with equal time difference. - cos_theta1 = np.sum(interp_quats[0] * interp_quats[2]) - cos_theta2 = np.sum(interp_quats[2] * interp_quats[1]) - assert_allclose(cos_theta1, cos_theta2) + cos_theta1 = xp.sum(interp_quats[0, ...] * interp_quats[2, ...]) + cos_theta2 = xp.sum(interp_quats[2, ...] * interp_quats[1, ...]) + xp_assert_close(cos_theta1, cos_theta2) - cos_theta4 = np.sum(interp_quats[3] * interp_quats[4]) - cos_theta5 = np.sum(interp_quats[4] * interp_quats[5]) - assert_allclose(cos_theta4, cos_theta5) + cos_theta4 = xp.sum(interp_quats[3, ...] * interp_quats[4, ...]) + cos_theta5 = xp.sum(interp_quats[4, ...] * interp_quats[5, ...]) + xp_assert_close(cos_theta4, cos_theta5) # theta1: 0 -> 0.25, theta3 : 0.5 -> 1 # Use double angle formula for double the time difference - cos_theta3 = np.sum(interp_quats[1] * interp_quats[3]) - assert_allclose(cos_theta3, 2 * (cos_theta1**2) - 1) + cos_theta3 = xp.sum(interp_quats[1, ...] * interp_quats[3, ...]) + xp_assert_close(cos_theta3, 2 * (cos_theta1**2) - 1) # Miscellaneous checks assert_equal(len(interp_rots), len(times)) -def test_slerp_rot_is_rotation(): +def test_slerp_rot_is_rotation(xp): with pytest.raises(TypeError, match="must be a `Rotation` instance"): - r = np.array([[1,2,3,4], - [0,0,0,1]]) - t = np.array([0, 1]) + r = xp.asarray([[1,2,3,4], + [0,0,0,1]]) + t = xp.asarray([0, 1]) Slerp(t, r) + SLERP_EXCEPTION_MESSAGE = "must be a sequence of at least 2 rotations" -def test_slerp_single_rot(): - r = Rotation.from_quat([1, 2, 3, 4]) + +def test_slerp_single_rot(xp): + r = Rotation.from_quat(xp.asarray([[1.0, 2, 3, 4]])) with pytest.raises(ValueError, match=SLERP_EXCEPTION_MESSAGE): Slerp([1], r) -def test_slerp_rot_len0(): +def test_slerp_rot_len0(xp): r = Rotation.random() + r = Rotation.from_quat(xp.asarray(r.as_quat())) with pytest.raises(ValueError, match=SLERP_EXCEPTION_MESSAGE): Slerp([], r) -def test_slerp_rot_len1(): +def test_slerp_rot_len1(xp): r = Rotation.random(1) + r = Rotation.from_quat(xp.asarray(r.as_quat())) with pytest.raises(ValueError, match=SLERP_EXCEPTION_MESSAGE): Slerp([1], r) -def test_slerp_time_dim_mismatch(): +def test_slerp_time_dim_mismatch(xp): with pytest.raises(ValueError, match="times to be specified in a 1 dimensional array"): rnd = np.random.RandomState(0) - r = Rotation.from_quat(rnd.uniform(size=(2, 4))) - t = np.array([[1], - [2]]) + r = Rotation.from_quat(xp.asarray(rnd.uniform(size=(2, 4)))) + t = xp.asarray([[1], + [2]]) Slerp(t, r) -def test_slerp_num_rotations_mismatch(): +def test_slerp_num_rotations_mismatch(xp): with pytest.raises(ValueError, match="number of rotations to be equal to " "number of timestamps"): rnd = np.random.RandomState(0) - r = Rotation.from_quat(rnd.uniform(size=(5, 4))) - t = np.arange(7) + r = Rotation.from_quat(xp.asarray(rnd.uniform(size=(5, 4)))) + t = xp.arange(7) Slerp(t, r) -def test_slerp_equal_times(): - with pytest.raises(ValueError, match="strictly increasing order"): - rnd = np.random.RandomState(0) - r = Rotation.from_quat(rnd.uniform(size=(5, 4))) - t = [0, 1, 2, 2, 4] - Slerp(t, r) +def test_slerp_equal_times(xp): + rnd = np.random.RandomState(0) + q = xp.asarray(rnd.uniform(size=(5, 4))) + r = Rotation.from_quat(q) + t = [0, 1, 2, 2, 4] + if is_lazy_array(q): + s = Slerp(t, r) + assert xp.all(xp.isnan(s.times)) + else: + with pytest.raises(ValueError, match="strictly increasing order"): + Slerp(t, r) -def test_slerp_decreasing_times(): - with pytest.raises(ValueError, match="strictly increasing order"): - rnd = np.random.RandomState(0) - r = Rotation.from_quat(rnd.uniform(size=(5, 4))) - t = [0, 1, 3, 2, 4] - Slerp(t, r) +def test_slerp_decreasing_times(xp): + rnd = np.random.RandomState(0) + q = xp.asarray(rnd.uniform(size=(5, 4))) + r = Rotation.from_quat(q) + t = [0, 1, 3, 2, 4] + if is_lazy_array(q): + s = Slerp(t, r) + assert xp.all(xp.isnan(s.times)) + else: + with pytest.raises(ValueError, match="strictly increasing order"): + Slerp(t, r) -def test_slerp_call_time_dim_mismatch(): +def test_slerp_call_time_dim_mismatch(xp): rnd = np.random.RandomState(0) - r = Rotation.from_quat(rnd.uniform(size=(5, 4))) - t = np.arange(5) + r = Rotation.from_quat(xp.asarray(rnd.uniform(size=(5, 4)))) + t = xp.arange(5) s = Slerp(t, r) with pytest.raises(ValueError, match="`times` must be at most 1-dimensional."): - interp_times = np.array([[3.5], - [4.2]]) + interp_times = xp.asarray([[3.5], + [4.2]]) s(interp_times) -def test_slerp_call_time_out_of_range(): +def test_slerp_call_time_out_of_range(xp): rnd = np.random.RandomState(0) - r = Rotation.from_quat(rnd.uniform(size=(5, 4))) - t = np.arange(5) + 1 + r = Rotation.from_quat(xp.asarray(rnd.uniform(size=(5, 4)))) + t = xp.arange(5) + 1 s = Slerp(t, r) - with pytest.raises(ValueError, match="times must be within the range"): - s([0, 1, 2]) - with pytest.raises(ValueError, match="times must be within the range"): - s([1, 2, 6]) - - -def test_slerp_call_scalar_time(): - r = Rotation.from_euler('X', [0, 80], degrees=True) + times_low = xp.asarray([0, 1, 2]) + times_high = xp.asarray([1, 2, 6]) + if is_lazy_array(times_low): + q = s(times_low).as_quat() + in_range = xp.logical_and(times_low >= xp.min(t), times_low <= xp.max(t)) + assert xp.all(xp.isnan(q[~in_range, ...])) + assert xp.all(~xp.isnan(q[in_range, ...])) + q = s(times_high).as_quat() + in_range = xp.logical_and(times_high >= xp.min(t), times_high <= xp.max(t)) + assert xp.all(xp.isnan(q[~in_range, ...])) + assert xp.all(~xp.isnan(q[in_range, ...])) + else: + with pytest.raises(ValueError, match="times must be within the range"): + s(times_low) + with pytest.raises(ValueError, match="times must be within the range"): + s(times_high) + + +def test_slerp_call_scalar_time(xp): + r = Rotation.from_euler('X', xp.asarray([0, 80]), degrees=True) s = Slerp([0, 1], r) r_interpolated = s(0.25) - r_interpolated_expected = Rotation.from_euler('X', 20, degrees=True) + r_interpolated_expected = Rotation.from_euler('X', xp.asarray(20), degrees=True) delta = r_interpolated * r_interpolated_expected.inv() - assert_allclose(delta.magnitude(), 0, atol=1e-16) + assert xp.allclose(delta.magnitude(), 0, atol=1e-16) -def test_multiplication_stability(): +def test_multiplication_stability(xp): qs = Rotation.random(50, rng=0) + qs = Rotation.from_quat(xp.asarray(qs.as_quat())) rs = Rotation.random(1000, rng=1) + rs = Rotation.from_quat(xp.asarray(rs.as_quat())) + expected = xp.ones(len(rs)) for q in qs: rs *= q * rs - assert_allclose(np.linalg.norm(rs.as_quat(), axis=1), 1) + xp_assert_close(xp_vector_norm(rs.as_quat(), axis=1), expected) -def test_pow(): +def test_pow(xp): atol = 1e-14 p = Rotation.random(10, rng=0) + p = Rotation.from_quat(xp.asarray(p.as_quat())) p_inv = p.inv() # Test the short-cuts and other integers for n in [-5, -2, -1, 0, 1, 2, 5]: # Test accuracy q = p ** n r = Rotation.identity(10) + r = Rotation.from_quat(xp.asarray(r.as_quat())) for _ in range(abs(n)): if n > 0: r = r * p else: r = r * p_inv ang = (q * r.inv()).magnitude() - assert np.all(ang < atol) + assert xp.all(ang < atol) # Test shape preservation - r = Rotation.from_quat([0, 0, 0, 1]) + r = Rotation.from_quat(xp.asarray([0, 0, 0, 1])) assert (r**n).as_quat().shape == (4,) - r = Rotation.from_quat([[0, 0, 0, 1]]) + r = Rotation.from_quat(xp.asarray([[0, 0, 0, 1]])) assert (r**n).as_quat().shape == (1, 4) # Large angle fractional for n in [-1.5, -0.5, -0.0, 0.0, 0.5, 1.5]: q = p ** n r = Rotation.from_rotvec(n * p.as_rotvec()) - assert_allclose(q.as_quat(), r.as_quat(), atol=atol) + xp_assert_close(q.as_quat(), r.as_quat(), atol=atol) # Small angle - p = Rotation.from_rotvec([1e-12, 0, 0]) + p = Rotation.from_rotvec(xp.asarray([1e-12, 0, 0])) n = 3 q = p ** n r = Rotation.from_rotvec(n * p.as_rotvec()) - assert_allclose(q.as_quat(), r.as_quat(), atol=atol) + xp_assert_close(q.as_quat(), r.as_quat(), atol=atol) -def test_pow_errors(): +def test_pow_errors(xp): p = Rotation.random(rng=0) + p = Rotation.from_quat(xp.asarray(p.as_quat())) with pytest.raises(NotImplementedError, match='modulus not supported'): pow(p, 1, 1) def test_rotation_within_numpy_array(): + # TODO: Do we want to support this for all Array API frameworks? single = Rotation.random(rng=0) multiple = Rotation.random(2, rng=1) @@ -1800,8 +2089,8 @@ def test_rotation_within_numpy_array(): array = np.array(multiple) assert_equal(array.shape, (2,)) - assert_allclose(array[0].as_matrix(), multiple[0].as_matrix()) - assert_allclose(array[1].as_matrix(), multiple[1].as_matrix()) + xp_assert_close(array[0].as_matrix(), multiple[0].as_matrix()) + xp_assert_close(array[1].as_matrix(), multiple[1].as_matrix()) array = np.array([single]) assert_equal(array.shape, (1,)) @@ -1809,8 +2098,8 @@ def test_rotation_within_numpy_array(): array = np.array([multiple]) assert_equal(array.shape, (1, 2)) - assert_allclose(array[0, 0].as_matrix(), multiple[0].as_matrix()) - assert_allclose(array[0, 1].as_matrix(), multiple[1].as_matrix()) + xp_assert_close(array[0, 0].as_matrix(), multiple[0].as_matrix()) + xp_assert_close(array[0, 1].as_matrix(), multiple[1].as_matrix()) array = np.array([single, multiple], dtype=object) assert_equal(array.shape, (2,)) @@ -1821,20 +2110,25 @@ def test_rotation_within_numpy_array(): assert_equal(array.shape, (3, 2)) -def test_pickling(): - r = Rotation.from_quat([0, 0, np.sin(np.pi/4), np.cos(np.pi/4)]) +def test_pickling(xp): + # Note: Array API makes no provision for arrays to be pickleable, so + # it's OK to skip this test for the backends that don't support it + r = Rotation.from_quat(xp.asarray([0, 0, math.sin(np.pi/4), math.cos(np.pi/4)])) pkl = pickle.dumps(r) unpickled = pickle.loads(pkl) - assert_allclose(r.as_matrix(), unpickled.as_matrix(), atol=1e-15) + xp_assert_close(r.as_matrix(), unpickled.as_matrix(), atol=1e-15) -def test_deepcopy(): - r = Rotation.from_quat([0, 0, np.sin(np.pi/4), np.cos(np.pi/4)]) +def test_deepcopy(xp): + # Note: Array API makes no provision for arrays to support the `__copy__` + # protocol, so it's OK to skip this test for the backends that don't + r = Rotation.from_quat(xp.asarray([0, 0, math.sin(np.pi/4), math.cos(np.pi/4)])) r1 = copy.deepcopy(r) - assert_allclose(r.as_matrix(), r1.as_matrix(), atol=1e-15) + xp_assert_close(r.as_matrix(), r1.as_matrix(), atol=1e-15) def test_as_euler_contiguous(): + # The Array API does not specify contiguous arrays, so we can only check for NumPy r = Rotation.from_quat([0, 0, 0, 1]) e1 = r.as_euler('xyz') # extrinsic euler rotation e2 = r.as_euler('XYZ') # intrinsic @@ -1844,36 +2138,39 @@ def test_as_euler_contiguous(): assert all(i >= 0 for i in e2.strides) -def test_concatenate(): +def test_concatenate(xp): rotation = Rotation.random(10, rng=0) + rotation = Rotation.from_quat(xp.asarray(rotation.as_quat())) sizes = [1, 2, 3, 1, 3] starts = [0] + list(np.cumsum(sizes)) split = [rotation[i:i + n] for i, n in zip(starts, sizes)] result = Rotation.concatenate(split) - assert_equal(rotation.as_quat(), result.as_quat()) + xp_assert_equal(rotation.as_quat(), result.as_quat()) # Test Rotation input for multiple rotations result = Rotation.concatenate(rotation) - assert_equal(rotation.as_quat(), result.as_quat()) + xp_assert_equal(rotation.as_quat(), result.as_quat()) # Test that a copy is returned assert rotation is not result # Test Rotation input for single rotations - result = Rotation.concatenate(Rotation.identity()) - assert_equal(Rotation.identity().as_quat(), result.as_quat()) + rot = Rotation.from_quat(xp.asarray(Rotation.identity().as_quat())) + result = Rotation.concatenate(rot) + xp_assert_equal(rot.as_quat(), result.as_quat()) -def test_concatenate_wrong_type(): +def test_concatenate_wrong_type(xp): with pytest.raises(TypeError, match='Rotation objects only'): - Rotation.concatenate([Rotation.identity(), 1, None]) + rot = Rotation(xp.asarray(Rotation.identity().as_quat())) + Rotation.concatenate([rot, 1, None]) # Regression test for gh-16663 -def test_len_and_bool(): - rotation_multi_one = Rotation([[0, 0, 0, 1]]) - rotation_multi = Rotation([[0, 0, 0, 1], [0, 0, 0, 1]]) - rotation_single = Rotation([0, 0, 0, 1]) +def test_len_and_bool(xp): + rotation_multi_one = Rotation(xp.asarray([[0, 0, 0, 1]])) + rotation_multi = Rotation(xp.asarray([[0, 0, 0, 1], [0, 0, 0, 1]])) + rotation_single = Rotation(xp.asarray([0, 0, 0, 1])) assert len(rotation_multi_one) == 1 assert len(rotation_multi) == 2 @@ -1886,61 +2183,93 @@ def test_len_and_bool(): assert rotation_single -def test_from_davenport_single_rotation(): - axis = [0, 0, 1] +def test_from_davenport_single_rotation(xp): + axis = xp.asarray([0, 0, 1]) quat = Rotation.from_davenport(axis, 'extrinsic', 90, degrees=True).as_quat() - expected_quat = np.array([0, 0, 1, 1]) / np.sqrt(2) - assert_allclose(quat, expected_quat) + expected_quat = xp.asarray([0.0, 0, 1, 1]) / math.sqrt(2) + xp_assert_close(quat, expected_quat) -def test_from_davenport_one_or_two_axes(): - ez = [0, 0, 1] - ey = [0, 1, 0] +def test_from_davenport_one_or_two_axes(xp): + ez = xp.asarray([0.0, 0, 1]) + ey = xp.asarray([0.0, 1, 0]) # Single rotation, single axis, axes.shape == (3, ) - rot = Rotation.from_rotvec(np.array(ez) * np.pi/4) - rot_dav = Rotation.from_davenport(ez, 'e', np.pi/4) - assert_allclose(rot.as_quat(canonical=True), + rot = Rotation.from_rotvec(ez * xp.pi/4) + rot_dav = Rotation.from_davenport(ez, 'e', xp.pi/4) + xp_assert_close(rot.as_quat(canonical=True), rot_dav.as_quat(canonical=True)) # Single rotation, single axis, axes.shape == (1, 3) - rot = Rotation.from_rotvec([np.array(ez) * np.pi/4]) - rot_dav = Rotation.from_davenport([ez], 'e', [np.pi/4]) - assert_allclose(rot.as_quat(canonical=True), + axes = xp.reshape(ez, (1, 3)) # Torch can't create tensors from xp.asarray([ez]) + rot = Rotation.from_rotvec(axes * xp.pi/4) + rot_dav = Rotation.from_davenport(axes, 'e', [xp.pi/4]) + xp_assert_close(rot.as_quat(canonical=True), rot_dav.as_quat(canonical=True)) # Single rotation, two axes, axes.shape == (2, 3) - rot = Rotation.from_rotvec([np.array(ez) * np.pi/4, - np.array(ey) * np.pi/6]) + axes = xp.stack([ez, ey], axis=0) + rot = Rotation.from_rotvec(axes * xp.asarray([[xp.pi/4], [xp.pi/6]])) rot = rot[0] * rot[1] - rot_dav = Rotation.from_davenport([ey, ez], 'e', [np.pi/6, np.pi/4]) - assert_allclose(rot.as_quat(canonical=True), + axes_dav = xp.stack([ey, ez], axis=0) + rot_dav = Rotation.from_davenport(axes_dav, 'e', [xp.pi/6, xp.pi/4]) + xp_assert_close(rot.as_quat(canonical=True), rot_dav.as_quat(canonical=True)) # Two rotations, single axis, axes.shape == (3, ) - rot = Rotation.from_rotvec([np.array(ez) * np.pi/6, - np.array(ez) * np.pi/4]) - rot_dav = Rotation.from_davenport([ez], 'e', [np.pi/6, np.pi/4]) - assert_allclose(rot.as_quat(canonical=True), + axes = xp.stack([ez, ez], axis=0) + rot = Rotation.from_rotvec(axes * xp.asarray([[xp.pi/6], [xp.pi/4]])) + axes_dav = xp.reshape(ez, (1, 3)) + rot_dav = Rotation.from_davenport(axes_dav, 'e', [xp.pi/6, xp.pi/4]) + xp_assert_close(rot.as_quat(canonical=True), rot_dav.as_quat(canonical=True)) -def test_from_davenport_invalid_input(): +def test_from_davenport_invalid_input(xp): ez = [0, 0, 1] ey = [0, 1, 0] ezy = [0, 1, 1] - with pytest.raises(ValueError, match="must be orthogonal"): - Rotation.from_davenport([ez, ezy], 'e', [0, 0]) - with pytest.raises(ValueError, match="must be orthogonal"): - Rotation.from_davenport([ez, ey, ezy], 'e', [0, 0, 0]) + # We can only raise in non-lazy frameworks. + axes = xp.asarray([ez, ezy]) + if is_lazy_array(axes): + q = Rotation.from_davenport(axes, 'e', [0, 0]).as_quat() + assert xp.all(xp.isnan(q)) + else: + with pytest.raises(ValueError, match="must be orthogonal"): + Rotation.from_davenport(axes, 'e', [0, 0]) + axes = xp.asarray([ez, ey, ezy]) + if is_lazy_array(axes): + q = Rotation.from_davenport(axes, 'e', [0, 0, 0]).as_quat() + assert xp.all(xp.isnan(q)) + else: + with pytest.raises(ValueError, match="must be orthogonal"): + Rotation.from_davenport(axes, 'e', [0, 0, 0]) with pytest.raises(ValueError, match="order should be"): - Rotation.from_davenport([ez], 'xyz', [0]) + Rotation.from_davenport(xp.asarray([ez]), 'xyz', [0]) with pytest.raises(ValueError, match="Expected `angles`"): - Rotation.from_davenport([ez, ey, ez], 'e', [0, 1, 2, 3]) + Rotation.from_davenport(xp.asarray([ez, ey, ez]), 'e', [0, 1, 2, 3]) -def test_as_davenport(): +def test_from_davenport_array_like(): + rng = np.random.default_rng(123) + # Single rotation + e1 = np.array([1, 0, 0]) + e2 = np.array([0, 1, 0]) + e3 = np.array([0, 0, 1]) + r_expected = Rotation.random(rng=rng) + angles = r_expected.as_davenport([e1, e2, e3], 'e') + r = Rotation.from_davenport([e1, e2, e3], 'e', angles.tolist()) + assert r_expected.approx_equal(r, atol=1e-12) + + # Multiple rotations + r_expected = Rotation.random(2, rng=rng) + angles = r_expected.as_davenport([e1, e2, e3], 'e') + r = Rotation.from_davenport([e1, e2, e3], 'e', angles.tolist()) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) + + +def test_as_davenport(xp): rnd = np.random.RandomState(0) n = 100 angles = np.empty((n, 3)) @@ -1949,21 +2278,22 @@ def test_as_davenport(): angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) lambdas = rnd.uniform(low=0, high=np.pi, size=(20,)) - e1 = np.array([1, 0, 0]) - e2 = np.array([0, 1, 0]) + e1 = xp.asarray([1.0, 0, 0]) + e2 = xp.asarray([0.0, 1, 0]) for lamb in lambdas: - ax_lamb = [e1, e2, Rotation.from_rotvec(lamb*e2).apply(e1)] + e3 = xp.asarray(Rotation.from_rotvec(lamb*e2).apply(e1)) + ax_lamb = xp.stack([e1, e2, e3], axis=0) angles[:, 1] = angles_middle - lamb for order in ['extrinsic', 'intrinsic']: - ax = ax_lamb if order == 'intrinsic' else ax_lamb[::-1] - rot = Rotation.from_davenport(ax, order, angles) - angles_dav = rot.as_davenport(ax, order) - assert_allclose(angles_dav, angles) + ax = ax_lamb if order == "intrinsic" else xp.flip(ax_lamb, axis=0) + rot = Rotation.from_davenport(xp.asarray(ax), order, angles) + angles_dav = rot.as_davenport(xp.asarray(ax), order) + xp_assert_close(angles_dav, xp.asarray(angles)) @pytest.mark.thread_unsafe -def test_as_davenport_degenerate(): +def test_as_davenport_degenerate(xp): # Since we cannot check for angle equality, we check for rotation matrix # equality rnd = np.random.RandomState(0) @@ -1976,23 +2306,25 @@ def test_as_davenport_degenerate(): angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) lambdas = rnd.uniform(low=0, high=np.pi, size=(5,)) - e1 = np.array([1, 0, 0]) - e2 = np.array([0, 1, 0]) + e1 = xp.asarray([1.0, 0, 0]) + e2 = xp.asarray([0.0, 1, 0]) for lamb in lambdas: - ax_lamb = [e1, e2, Rotation.from_rotvec(lamb*e2).apply(e1)] + e3 = xp.asarray(Rotation.from_rotvec(lamb*e2).apply(e1)) + ax_lamb = xp.stack([e1, e2, e3], axis=0) angles[:, 1] = angles_middle - lamb for order in ['extrinsic', 'intrinsic']: ax = ax_lamb if order == 'intrinsic' else ax_lamb[::-1] - rot = Rotation.from_davenport(ax, order, angles) - with pytest.warns(UserWarning, match="Gimbal lock"): - angles_dav = rot.as_davenport(ax, order) + rot = Rotation.from_davenport(xp.asarray(ax), order, angles) + with eager_warns(rot, UserWarning, match="Gimbal lock"): + angles_dav = rot.as_davenport(xp.asarray(ax), order) mat_expected = rot.as_matrix() - mat_estimated = Rotation.from_davenport(ax, order, angles_dav).as_matrix() - assert_array_almost_equal(mat_expected, mat_estimated) + rot_estimated = Rotation.from_davenport(xp.asarray(ax), order, angles_dav) + mat_estimated = rot_estimated.as_matrix() + xp_assert_close(mat_expected, mat_estimated, atol=1e-12) -def test_compare_from_davenport_from_euler(): +def test_compare_from_davenport_from_euler(xp): rnd = np.random.RandomState(0) n = 100 angles = np.empty((n, 3)) @@ -2007,9 +2339,9 @@ def test_compare_from_davenport_from_euler(): ax = [basis_vec(i) for i in seq] if order == 'intrinsic': seq = seq.upper() - eul = Rotation.from_euler(seq, angles) - dav = Rotation.from_davenport(ax, order, angles) - assert_allclose(eul.as_quat(canonical=True), dav.as_quat(canonical=True), + eul = Rotation.from_euler(seq, xp.asarray(angles)) + dav = Rotation.from_davenport(xp.asarray(ax), order, xp.asarray(angles)) + xp_assert_close(eul.as_quat(canonical=True), dav.as_quat(canonical=True), rtol=1e-12) # asymmetric sequences @@ -2020,12 +2352,12 @@ def test_compare_from_davenport_from_euler(): ax = [basis_vec(i) for i in seq] if order == 'intrinsic': seq = seq.upper() - eul = Rotation.from_euler(seq, angles) - dav = Rotation.from_davenport(ax, order, angles) - assert_allclose(eul.as_quat(), dav.as_quat(), rtol=1e-12) + eul = Rotation.from_euler(seq, xp.asarray(angles)) + dav = Rotation.from_davenport(xp.asarray(ax), order, xp.asarray(angles)) + xp_assert_close(eul.as_quat(), dav.as_quat(), rtol=1e-12) -def test_compare_as_davenport_as_euler(): +def test_compare_as_davenport_as_euler(xp): rnd = np.random.RandomState(0) n = 100 angles = np.empty((n, 3)) @@ -2040,10 +2372,10 @@ def test_compare_as_davenport_as_euler(): ax = [basis_vec(i) for i in seq] if order == 'intrinsic': seq = seq.upper() - rot = Rotation.from_euler(seq, angles) + rot = Rotation.from_euler(seq, xp.asarray(angles)) eul = rot.as_euler(seq) - dav = rot.as_davenport(ax, order) - assert_allclose(eul, dav, rtol=1e-12) + dav = rot.as_davenport(xp.asarray(ax), order) + xp_assert_close(eul, dav, rtol=1e-12) # asymmetric sequences angles[:, 1] -= np.pi / 2 @@ -2053,13 +2385,13 @@ def test_compare_as_davenport_as_euler(): ax = [basis_vec(i) for i in seq] if order == 'intrinsic': seq = seq.upper() - rot = Rotation.from_euler(seq, angles) + rot = Rotation.from_euler(seq, xp.asarray(angles)) eul = rot.as_euler(seq) - dav = rot.as_davenport(ax, order) - assert_allclose(eul, dav, rtol=1e-12) + dav = rot.as_davenport(xp.asarray(ax), order) + xp_assert_close(eul, dav, rtol=1e-12) -def test_zero_rotation_construction(): +def test_zero_rotation_construction(xp): r = Rotation.random(num=0) assert len(r) == 0 @@ -2069,68 +2401,69 @@ def test_zero_rotation_construction(): r_get = Rotation.random(num=3)[[]] assert len(r_get) == 0 - r_quat = Rotation.from_quat(np.zeros((0, 4))) + r_quat = Rotation.from_quat(xp.zeros((0, 4))) assert len(r_quat) == 0 - r_matrix = Rotation.from_matrix(np.zeros((0, 3, 3))) + r_matrix = Rotation.from_matrix(xp.zeros((0, 3, 3))) assert len(r_matrix) == 0 - r_euler = Rotation.from_euler("xyz", np.zeros((0, 3))) + r_euler = Rotation.from_euler("xyz", xp.zeros((0, 3))) assert len(r_euler) == 0 - r_vec = Rotation.from_rotvec(np.zeros((0, 3))) + r_vec = Rotation.from_rotvec(xp.zeros((0, 3))) assert len(r_vec) == 0 - r_dav = Rotation.from_davenport(np.eye(3), "extrinsic", np.zeros((0, 3))) + r_dav = Rotation.from_davenport(xp.eye(3), "extrinsic", xp.zeros((0, 3))) assert len(r_dav) == 0 - r_mrp = Rotation.from_mrp(np.zeros((0, 3))) + r_mrp = Rotation.from_mrp(xp.zeros((0, 3))) assert len(r_mrp) == 0 -def test_zero_rotation_representation(): - r = Rotation.random(num=0) +def test_zero_rotation_representation(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) assert r.as_quat().shape == (0, 4) assert r.as_matrix().shape == (0, 3, 3) assert r.as_euler("xyz").shape == (0, 3) assert r.as_rotvec().shape == (0, 3) assert r.as_mrp().shape == (0, 3) - assert r.as_davenport(np.eye(3), "extrinsic").shape == (0, 3) + assert r.as_davenport(xp.eye(3), "extrinsic").shape == (0, 3) -def test_zero_rotation_array_rotation(): - r = Rotation.random(num=0) +def test_zero_rotation_array_rotation(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) - v = np.array([1, 2, 3]) + v = xp.asarray([1, 2, 3]) v_rotated = r.apply(v) assert v_rotated.shape == (0, 3) - v0 = np.zeros((0, 3)) + v0 = xp.zeros((0, 3)) v0_rot = r.apply(v0) assert v0_rot.shape == (0, 3) - v2 = np.ones((2, 3)) + v2 = xp.ones((2, 3)) with pytest.raises( ValueError, match="Expected equal numbers of rotations and vectors"): r.apply(v2) -def test_zero_rotation_multiplication(): - r = Rotation.random(num=0) +def test_zero_rotation_multiplication(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) - r_single = Rotation.random() + r_single = Rotation.from_quat(xp.asarray([0.0, 0, 0, 1])) r_mult_left = r * r_single assert len(r_mult_left) == 0 r_mult_right = r_single * r assert len(r_mult_right) == 0 - r0 = Rotation.random(0) + r0 = Rotation.from_quat(xp.zeros((0, 4))) r_mult = r * r0 assert len(r_mult) == 0 msg_rotation_error = "Expected equal number of rotations" r2 = Rotation.random(2) + r2 = Rotation.from_quat(xp.asarray(r2.as_quat())) with pytest.raises(ValueError, match=msg_rotation_error): r0 * r2 @@ -2138,55 +2471,62 @@ def test_zero_rotation_multiplication(): r2 * r0 -def test_zero_rotation_concatentation(): - r = Rotation.random(num=0) +def test_zero_rotation_concatentation(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) r0 = Rotation.concatenate([r, r]) assert len(r0) == 0 - r1 = r.concatenate([Rotation.random(), r]) + r1 = Rotation.from_quat(xp.asarray([0.0, 0, 0, 1])) + r1 = r.concatenate([r1, r]) assert len(r1) == 1 - r3 = r.concatenate([Rotation.random(3), r]) + r3 = Rotation.from_quat(xp.asarray(Rotation.random(3).as_quat())) + r3 = r.concatenate([r3, r]) assert len(r3) == 3 - r4 = r.concatenate([r, Rotation.random(4)]) + r4 = Rotation.from_quat(xp.asarray(Rotation.random(4).as_quat())) + r4 = r.concatenate([r, r4]) + r4 = r.concatenate([r, r4]) assert len(r4) == 4 -def test_zero_rotation_power(): - r = Rotation.random(num=0) +def test_zero_rotation_power(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) for pp in [-1.5, -1, 0, 1, 1.5]: pow0 = r**pp assert len(pow0) == 0 -def test_zero_rotation_inverse(): - r = Rotation.random(num=0) +def test_zero_rotation_inverse(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) r_inv = r.inv() assert len(r_inv) == 0 -def test_zero_rotation_magnitude(): - r = Rotation.random(num=0) +def test_zero_rotation_magnitude(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) magnitude = r.magnitude() assert magnitude.shape == (0,) -def test_zero_rotation_mean(): - r = Rotation.random(num=0) +def test_zero_rotation_mean(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) with pytest.raises(ValueError, match="Mean of an empty rotation set is undefined."): r.mean() -def test_zero_rotation_approx_equal(): - r = Rotation.random(0) - assert r.approx_equal(Rotation.random(0)).shape == (0,) - assert r.approx_equal(Rotation.random()).shape == (0,) - assert Rotation.random().approx_equal(r).shape == (0,) +def test_zero_rotation_approx_equal(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) + r0 = Rotation.from_quat(xp.zeros((0, 4))) + assert r.approx_equal(r0).shape == (0,) + r1 = Rotation.from_quat(xp.asarray([0.0, 0, 0, 1])) + assert r.approx_equal(r1).shape == (0,) + r2 = Rotation.from_quat(xp.asarray(Rotation.random().as_quat())) + assert r2.approx_equal(r).shape == (0,) approx_msg = "Expected equal number of rotations" - r3 = Rotation.random(2) + r3 = Rotation.from_quat(xp.asarray(Rotation.random(2).as_quat())) with pytest.raises(ValueError, match=approx_msg): r.approx_equal(r3) @@ -2194,36 +2534,36 @@ def test_zero_rotation_approx_equal(): r3.approx_equal(r) -def test_zero_rotation_get_set(): - r = Rotation.random(0) +def test_zero_rotation_get_set(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) - r_get = r[[]] + r_get = r[xp.asarray([], dtype=xp.bool)] assert len(r_get) == 0 r_slice = r[:0] assert len(r_slice) == 0 with pytest.raises(IndexError): - r[[0]] + r[xp.asarray([0])] with pytest.raises(IndexError): - r[[True]] + r[xp.asarray([True])] with pytest.raises(IndexError): - r[0] = Rotation.random() + r[0] = Rotation.from_quat(xp.asarray([0, 0, 0, 1])) -def test_boolean_indexes(): - r = Rotation.random(3) +def test_boolean_indexes(xp): + r = Rotation.from_quat(xp.asarray(Rotation.random(3).as_quat())) - r0 = r[[False, False, False]] + r0 = r[xp.asarray([False, False, False])] assert len(r0) == 0 - r1 = r[[False, True, False]] + r1 = r[xp.asarray([False, True, False])] assert len(r1) == 1 - r3 = r[[True, True, True]] + r3 = r[xp.asarray([True, True, True])] assert len(r3) == 3 with pytest.raises(IndexError): - r[[True, True]] + r[xp.asarray([True, True])] diff --git a/scipy/spatial/transform/tests/test_rotation_spline.py b/scipy/spatial/transform/tests/test_rotation_spline.py index 6441431f2fb5..316121b8f06b 100644 --- a/scipy/spatial/transform/tests/test_rotation_spline.py +++ b/scipy/spatial/transform/tests/test_rotation_spline.py @@ -1,7 +1,7 @@ from itertools import product import numpy as np from numpy.testing import assert_allclose -from pytest import raises +import pytest from scipy.spatial.transform import Rotation, RotationSpline from scipy.spatial.transform._rotation_spline import ( _angular_rate_to_rotvec_dot_matrix, @@ -11,6 +11,9 @@ _create_block_3_diagonal_matrix) +pytestmark = pytest.mark.skip_xp_backends(np_only=True) + + def test_angular_rate_to_rotvec_conversions(): np.random.seed(0) rv = np.random.randn(4, 3) @@ -141,22 +144,40 @@ def test_spline_properties(): def test_error_handling(): - raises(ValueError, RotationSpline, [1.0], Rotation.random()) + with pytest.raises(ValueError): + RotationSpline([1.0], Rotation.random()) r = Rotation.random(10) t = np.arange(10).reshape(5, 2) - raises(ValueError, RotationSpline, t, r) + with pytest.raises(ValueError): + RotationSpline(t, r) t = np.arange(9) - raises(ValueError, RotationSpline, t, r) + with pytest.raises(ValueError): + RotationSpline(t, r) t = np.arange(10) t[5] = 0 - raises(ValueError, RotationSpline, t, r) + with pytest.raises(ValueError): + RotationSpline(t, r) t = np.arange(10) s = RotationSpline(t, r) - raises(ValueError, s, 10, -1) - - raises(ValueError, s, np.arange(10).reshape(5, 2)) + with pytest.raises(ValueError): + s(10, -1) + + with pytest.raises(ValueError): + s(np.arange(10).reshape(5, 2)) + + +@pytest.mark.skip_xp_backends("numpy") +def test_xp_errors(xp): + times = xp.asarray([0, 10]) + r = Rotation.random(2) + r = Rotation.from_quat(xp.asarray(r.as_quat())) + s = RotationSpline(times, r) + t = xp.asarray([0.5, 1.5]) + # RotationSpline does not have native Array API support, so we check that it + # converts any array to NumPy and outputs NumPy arrays. + assert isinstance(s(t).as_quat(), np.ndarray) diff --git a/scipy/special/__init__.py b/scipy/special/__init__.py index c3922e797d06..2572e27b48d8 100644 --- a/scipy/special/__init__.py +++ b/scipy/special/__init__.py @@ -785,10 +785,7 @@ from ._ufuncs import * # Replace some function definitions from _ufuncs to add Array API support -from ._support_alternative_backends import ( - log_ndtr, ndtr, ndtri, erf, erfc, i0, i0e, i1, i1e, gammaln, - gammainc, gammaincc, logit, expit, entr, rel_entr, xlogy, - chdtr, chdtrc, betainc, betaincc, stdtr, stdtrit) +from ._support_alternative_backends import * from . import _basic from ._basic import * @@ -842,13 +839,3 @@ from scipy._lib._testutils import PytestTester test = PytestTester(__name__) del PytestTester - - -def _get_include(): - """This function is for development purposes only. - - This function could disappear or its behavior could change at any time. - """ - import os - return os.path.dirname(__file__) - diff --git a/scipy/special/_basic.py b/scipy/special/_basic.py index f9d6295b5645..713fd8374b22 100644 --- a/scipy/special/_basic.py +++ b/scipy/special/_basic.py @@ -2859,15 +2859,9 @@ def _factorialx_array_exact(n, k=1): k > 1 corresponds to the multifactorial. """ un = np.unique(n) - # numpy changed nan-sorting behaviour with 1.21, see numpy/numpy#18070; - # to unify the behaviour, we remove the nan's here; the respective - # values will be set separately at the end - un = un[~np.isnan(un)] # Convert to object array if np.int64 can't handle size - if np.isnan(n).any(): - dt = float - elif k in _FACTORIALK_LIMITS_64BITS.keys(): + if k in _FACTORIALK_LIMITS_64BITS.keys(): if un[-1] > _FACTORIALK_LIMITS_64BITS[k]: # e.g. k=1: 21! > np.iinfo(np.int64).max dt = object @@ -2908,9 +2902,6 @@ def _factorialx_array_exact(n, k=1): val *= _range_prod(int(prev + 1), int(current), k=k) out[n == current] = val - if np.isnan(n).any(): - out = out.astype(np.float64) - out[np.isnan(n)] = np.nan return out @@ -3231,7 +3222,7 @@ def factorial2(n, exact=False, extend="zero"): -------- >>> from scipy.special import factorial2 >>> factorial2(7, exact=False) - array(105.00000000000001) + np.float64(105.00000000000001) >>> factorial2(7, exact=True) 105 diff --git a/scipy/special/_gufuncs.cpp b/scipy/special/_gufuncs.cpp index 11778ff5bb99..7eca3717b5bd 100644 --- a/scipy/special/_gufuncs.cpp +++ b/scipy/special/_gufuncs.cpp @@ -1,4 +1,4 @@ -#include "xsf/numpy.h" +#include #include "sf_error.h" #include "xsf_special.h" diff --git a/scipy/special/_logsumexp.py b/scipy/special/_logsumexp.py index 01f102d9d531..b7338c3f881d 100644 --- a/scipy/special/_logsumexp.py +++ b/scipy/special/_logsumexp.py @@ -227,24 +227,29 @@ def _logsumexp(a, b, *, axis, return_sign, xp): s = xp.where(s == 0, s, s/m) # Separate sign/magnitude information - sgn = None - if return_sign: - # Use the numpy>=2.0 convention for sign. - # When all array libraries agree, this can become sng = xp.sign(s). - sgn = _sign(s + 1, xp=xp) * _sign(m, xp=xp) - - if xp.isdtype(s.dtype, "real floating"): - # The log functions need positive arguments - s = xp.where(s < -1, -s - 2, s) - m = xp.abs(m) - else: - # `a_max` can have a sign component for complex input - sgn = sgn * xp.exp(xp.imag(a_max) * 1.0j) + # Originally, this was only performed if `return_sign=True`. + # However, this is also needed if any elements of `m < 0` or `s < -1`. + # An improvement would be to perform the calculations only on these entries. + + # Use the numpy>=2.0 convention for sign. + # When all array libraries agree, this can become sng = xp.sign(s). + sgn = _sign(s + 1, xp=xp) * _sign(m, xp=xp) + + if xp.isdtype(s.dtype, "real floating"): + # The log functions need positive arguments + s = xp.where(s < -1, -s - 2, s) + m = xp.abs(m) + else: + # `a_max` can have a sign component for complex input + sgn = sgn * xp.exp(xp.imag(a_max) * 1.0j) # Take log and undo shift out = xp.log1p(s) + xp.log(m) + a_max - out = xp.real(out) if return_sign else out + if return_sign: + out = xp.real(out) + elif xp.isdtype(out.dtype, 'real floating'): + out = xpx.at(out)[sgn < 0].set(xp.nan) return out, sgn diff --git a/scipy/special/_special_ufuncs.cpp b/scipy/special/_special_ufuncs.cpp index 61abd14a0517..f13f9e5c45e8 100644 --- a/scipy/special/_special_ufuncs.cpp +++ b/scipy/special/_special_ufuncs.cpp @@ -1,40 +1,40 @@ -#include "xsf/numpy.h" +#include #include #include #include "sf_error.h" -#include "xsf/airy.h" -#include "xsf/alg.h" -#include "xsf/amos.h" -#include "xsf/bessel.h" -#include "xsf/beta.h" -#include "xsf/binom.h" -#include "xsf/digamma.h" -#include "xsf/ellip.h" -#include "xsf/erf.h" -#include "xsf/exp.h" -#include "xsf/expint.h" -#include "xsf/fresnel.h" -#include "xsf/gamma.h" -#include "xsf/hyp2f1.h" -#include "xsf/iv_ratio.h" -#include "xsf/kelvin.h" -#include "xsf/lambertw.h" -#include "xsf/legendre.h" -#include "xsf/log.h" -#include "xsf/log_exp.h" -#include "xsf/mathieu.h" -#include "xsf/par_cyl.h" -#include "xsf/specfun.h" -#include "xsf/sph_bessel.h" -#include "xsf/sph_harm.h" -#include "xsf/sphd_wave.h" -#include "xsf/stats.h" -#include "xsf/struve.h" -#include "xsf/trig.h" -#include "xsf/wright_bessel.h" -#include "xsf/zeta.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "xsf_special.h" // This is the extension module for the NumPy ufuncs in SciPy's special module. To create such a ufunc, call diff --git a/scipy/special/_support_alternative_backends.py b/scipy/special/_support_alternative_backends.py index 65d98ac92059..e9811fcfe495 100644 --- a/scipy/special/_support_alternative_backends.py +++ b/scipy/special/_support_alternative_backends.py @@ -1,65 +1,142 @@ import functools import operator +from collections.abc import Callable +from dataclasses import dataclass +from types import ModuleType import numpy as np from scipy._lib._array_api import ( array_namespace, scipy_namespace_for, is_numpy, is_dask, is_marray, - xp_promote, SCIPY_ARRAY_API + xp_promote, xp_capabilities, SCIPY_ARRAY_API ) import scipy._lib.array_api_extra as xpx from . import _ufuncs -# These don't really need to be imported, but otherwise IDEs might not realize -# that these are defined in this file / report an error in __init__.py -from ._ufuncs import ( - log_ndtr, ndtr, ndtri, erf, erfc, i0, i0e, i1, i1e, gammaln, # noqa: F401 - gammainc, gammaincc, logit, expit, entr, rel_entr, xlogy, # noqa: F401 - chdtr, chdtrc, betainc, betaincc, stdtr, stdtrit # noqa: F401 -) - -array_api_compat_prefix = "scipy._lib.array_api_compat" -def get_array_special_func(f_name, xp): - if is_numpy(xp): - return getattr(_ufuncs, f_name) - - spx = scipy_namespace_for(xp) - if spx is not None: - f = getattr(spx.special, f_name, None) +@dataclass +class _FuncInfo: + # NumPy-only function. IT MUST BE ELEMENTWISE. + func: Callable + # Number of arguments, not counting out= + # This is for testing purposes only, due to the fact that + # inspect.signature() just returns *args for ufuncs. + n_args: int + # @xp_capabilities decorator, for the purpose of + # documentation and unit testing. Omit to indicate + # full support for all backends. + xp_capabilities: Callable[[Callable], Callable] | None = None + # Generic implementation to fall back on if there is no native dispatch + # available. This is a function that accepts (main namespace, scipy namespace) + # and returns the final callable, or None if not available. + generic_impl: Callable[ + [ModuleType, ModuleType | None], Callable | None + ] | None = None + + @property + def name(self): + return self.func.__name__ + + # These are needed by @lru_cache below + def __hash__(self): + return hash(self.func) + + def __eq__(self, other): + return isinstance(other, _FuncInfo) and self.func == other.func + + @property + def wrapper(self): + if self.name in globals(): + # Already initialised. We are likely in a unit test. + # Return function potentially overridden by xpx.testing.lazy_xp_function. + import scipy.special + return getattr(scipy.special, self.name) + + if SCIPY_ARRAY_API: + @functools.wraps(self.func) + def wrapped(*args, **kwargs): + xp = array_namespace(*args) + return self._wrapper_for(xp)(*args, **kwargs) + + # Allow pickling the function. Normally this is done by @wraps, + # but in this case it doesn't work because self.func is a ufunc. + wrapped.__module__ = "scipy.special" + wrapped.__qualname__ = self.name + func = wrapped + else: + func = self.func + + capabilities = self.xp_capabilities or xp_capabilities() + # In order to retain a naked ufunc when SCIPY_ARRAY_API is + # disabled, xp_capabilities must apply its changes in place. + cap_func = capabilities(func) + assert cap_func is func + return func + + @functools.lru_cache(1000) + def _wrapper_for(self, xp): + if is_numpy(xp): + return self.func + + # If a native implementation is available, use that + spx = scipy_namespace_for(xp) + f = _get_native_func(xp, spx, self.name) if f is not None: return f - # if generic array-API implementation is available, use that; - # otherwise, fall back to NumPy/SciPy - if f_name in _generic_implementations: - f = _generic_implementations[f_name](xp=xp, spx=spx) - if f is not None: - return f + # If generic Array API implementation is available, use that + if self.generic_impl is not None: + f = self.generic_impl(xp, spx) + if f is not None: + return f - def f(*args, **kwargs): if is_marray(xp): - _f = globals()[f_name] # Allow nested wrapping - data_args = [arg.data for arg in args] - out = _f(*data_args, **kwargs) - mask = functools.reduce(operator.or_, (arg.mask for arg in args)) - return xp.asarray(out, mask=mask) - - elif is_dask(xp): - # IMPORTANT: map_blocks works only because all ufuncs in this module + # Unwrap the array, apply the function on the wrapped namespace, + # and then re-wrap it. + # IMPORTANT: this only works because all functions in this module + # are elementwise. Otherwise, we would not be able to define a + # general rule for mask propagation. + + _f = globals()[self.name] # Allow nested wrapping + def f(*args, _f=_f, xp=xp, **kwargs): + data_args = [arg.data for arg in args] + out = _f(*data_args, **kwargs) + mask = functools.reduce(operator.or_, (arg.mask for arg in args)) + return xp.asarray(out, mask=mask) + + return f + + if is_dask(xp): + # Apply the function to each block of the Dask array. + # IMPORTANT: map_blocks works only because all functions in this module # are elementwise. It would be a grave mistake to apply this to gufuncs # or any other function with reductions, as they would change their # output depending on chunking! - _f = globals()[f_name] # Allow nested wrapping - # Hide dtype kwarg from map_blocks - return xp.map_blocks(functools.partial(_f, **kwargs), *args) + _f = globals()[self.name] # Allow nested wrapping + def f(*args, _f=_f, xp=xp, **kwargs): + # Hide dtype kwarg from map_blocks + return xp.map_blocks(functools.partial(_f, **kwargs), *args) - else: - _f = getattr(_ufuncs, f_name) + return f + + # As a final resort, use the NumPy/SciPy implementation + _f = self.func + def f(*args, _f=_f, xp=xp, **kwargs): + # TODO use xpx.lazy_apply to add jax.jit support + # (but dtype propagation can be non-trivial) args = [np.asarray(arg) for arg in args] out = _f(*args, **kwargs) return xp.asarray(out) + return f + + +def _get_native_func(xp, spx, f_name): + f = getattr(spx.special, f_name, None) if spx else None + if f is None and hasattr(xp, 'special'): + # Currently dead branch, in anticipation of 'special' Array API extension + # https://github.com/data-apis/array-api/issues/725 + f = getattr(xp.special, f_name, None) return f @@ -86,25 +163,20 @@ def __rel_entr(x, y, *, xp=xp): def _xlogy(xp, spx): def __xlogy(x, y, *, xp=xp): + x, y = xp_promote(x, y, force_floating=True, xp=xp) with np.errstate(divide='ignore', invalid='ignore'): temp = x * xp.log(y) return xp.where(x == 0., 0., temp) return __xlogy -def _get_native_func(xp, spx, f_name): - f = getattr(spx.special, f_name, None) if spx else None - if f is None and hasattr(xp, 'special'): - f = getattr(xp.special, f_name, None) - return f - def _chdtr(xp, spx): # The difference between this and just using `gammainc` # defined by `get_array_special_func` is that if `gammainc` # isn't found, we don't want to use the SciPy version; we'll # return None here and use the SciPy version of `chdtr`. - gammainc = _get_native_func(xp, spx, 'gammainc') # noqa: F811 + gammainc = _get_native_func(xp, spx, 'gammainc') if gammainc is None: return None @@ -123,7 +195,7 @@ def _chdtrc(xp, spx): # defined by `get_array_special_func` is that if `gammaincc` # isn't found, we don't want to use the SciPy version; we'll # return None here and use the SciPy version of `chdtrc`. - gammaincc = _get_native_func(xp, spx, 'gammaincc') # noqa: F811 + gammaincc = _get_native_func(xp, spx, 'gammaincc') if gammaincc is None: return None @@ -136,7 +208,7 @@ def __chdtrc(v, x): def _betaincc(xp, spx): - betainc = _get_native_func(xp, spx, 'betainc') # noqa: F811 + betainc = _get_native_func(xp, spx, 'betainc') if betainc is None: return None @@ -147,7 +219,7 @@ def __betaincc(a, b, x): def _stdtr(xp, spx): - betainc = _get_native_func(xp, spx, 'betainc') # noqa: F811 + betainc = _get_native_func(xp, spx, 'betainc') if betainc is None: return None @@ -160,11 +232,12 @@ def __stdtr(df, t): def _stdtrit(xp, spx): - betainc = _get_native_func(xp, spx, 'betainc') # noqa: F811 + # Need either native stdtr or native betainc + stdtr = _get_native_func(xp, spx, 'stdtr') or _stdtr(xp, spx) # If betainc is not defined, the root-finding would be done with `xp` # despite `stdtr` being evaluated with SciPy/NumPy `stdtr`. Save the # conversions: in this case, just evaluate `stdtrit` with SciPy/NumPy. - if betainc is None: + if stdtr is None: return None from scipy.optimize.elementwise import bracket_root, find_root @@ -178,62 +251,45 @@ def fun(t, df, p): return stdtr(df, t) - p return __stdtrit -_generic_implementations = {'rel_entr': _rel_entr, - 'xlogy': _xlogy, - 'chdtr': _chdtr, - 'chdtrc': _chdtrc, - 'betaincc': _betaincc, - 'stdtr': _stdtr, - 'stdtrit': _stdtrit, - } - - -# functools.wraps doesn't work because: -# 'numpy.ufunc' object has no attribute '__module__' -def support_alternative_backends(f_name): - func = getattr(_ufuncs, f_name) - - @functools.wraps(func) - def wrapped(*args, **kwargs): - xp = array_namespace(*args) - f = get_array_special_func(f_name, xp) - return f(*args, **kwargs) - - return wrapped - - -# function name: number of args (for testing purposes) -array_special_func_map = { - 'log_ndtr': 1, - 'ndtr': 1, - 'ndtri': 1, - 'erf': 1, - 'erfc': 1, - 'i0': 1, - 'i0e': 1, - 'i1': 1, - 'i1e': 1, - 'gammaln': 1, - 'gammainc': 2, - 'gammaincc': 2, - 'logit': 1, - 'expit': 1, - 'entr': 1, - 'rel_entr': 2, - 'xlogy': 2, - 'chdtr': 2, - 'chdtrc': 2, - 'betainc': 3, - 'betaincc': 3, - 'stdtr': 2, - 'stdtrit': 2, -} - -globals().update( - {f_name: support_alternative_backends(f_name) - if SCIPY_ARRAY_API - else getattr(_ufuncs, f_name) - for f_name in array_special_func_map} +# Inventory of automatically dispatched functions +# IMPORTANT: these must all be **elementwise** functions! + +# PyTorch doesn't implement `betainc`. +# On torch CPU we can fall back to NumPy, but on GPU it won't work. +_needs_betainc = xp_capabilities(cpu_only=True, exceptions=['jax.numpy', 'cupy']) + +_special_funcs = ( + _FuncInfo(_ufuncs.betainc, 3, _needs_betainc), + _FuncInfo(_ufuncs.betaincc, 3, _needs_betainc, generic_impl=_betaincc), + _FuncInfo(_ufuncs.chdtr, 2, generic_impl=_chdtr), + _FuncInfo(_ufuncs.chdtrc, 2, generic_impl=_chdtrc), + _FuncInfo(_ufuncs.erf, 1), + _FuncInfo(_ufuncs.erfc, 1), + _FuncInfo(_ufuncs.entr, 1), + _FuncInfo(_ufuncs.expit, 1), + _FuncInfo(_ufuncs.i0, 1), + _FuncInfo(_ufuncs.i0e, 1), + _FuncInfo(_ufuncs.i1, 1), + _FuncInfo(_ufuncs.i1e, 1), + _FuncInfo(_ufuncs.log_ndtr, 1), + _FuncInfo(_ufuncs.logit, 1), + _FuncInfo(_ufuncs.gammaln, 1), + _FuncInfo(_ufuncs.gammainc, 2), + _FuncInfo(_ufuncs.gammaincc, 2), + _FuncInfo(_ufuncs.ndtr, 1), + _FuncInfo(_ufuncs.ndtri, 1), + _FuncInfo(_ufuncs.rel_entr, 2, generic_impl=_rel_entr), + _FuncInfo(_ufuncs.stdtr, 2, _needs_betainc, generic_impl=_stdtr), + _FuncInfo(_ufuncs.stdtrit, 2, + xp_capabilities( + cpu_only=True, exceptions=['cupy'], # needs betainc + skip_backends=[("jax.numpy", "no scipy.optimize support")]), + generic_impl=_stdtrit), + _FuncInfo(_ufuncs.xlogy, 2, generic_impl=_xlogy), ) -__all__ = list(array_special_func_map) +# Override ufuncs. +# When SCIPY_ARRAY_API is disabled, this exclusively updates the docstrings in place +# and populates the xp_capabilities table, while retaining the original ufuncs. +globals().update({nfo.func.__name__: nfo.wrapper for nfo in _special_funcs}) +__all__ = [nfo.func.__name__ for nfo in _special_funcs] diff --git a/scipy/special/boost_special_functions.h b/scipy/special/boost_special_functions.h index 7eaeba33d404..9f281a183ea4 100644 --- a/scipy/special/boost_special_functions.h +++ b/scipy/special/boost_special_functions.h @@ -699,8 +699,22 @@ Real ncx2_pdf_wrap(const Real x, const Real k, const Real l) { if (std::isfinite(x)) { + try { return boost::math::pdf( - boost::math::non_central_chi_squared_distribution(k, l), x); + boost::math::non_central_chi_squared_distribution(k, l), x); + } + catch (const std::overflow_error& e) { + sf_error("_ncx2_pdf", SF_ERROR_OVERFLOW, NULL); + return INFINITY; + } + catch (const std::underflow_error& e) { + sf_error("_ncx2_pdf", SF_ERROR_UNDERFLOW, NULL); + return 0; + } + catch (...) { + sf_error("_ncx2_pdf", SF_ERROR_OTHER, NULL); + return NAN; + } } return NAN; // inf or -inf returns NAN } @@ -1318,6 +1332,9 @@ binom_cdf_wrap(const Real x, const Real n, const Real p) return boost::math::cdf( boost::math::binomial_distribution(n, p), x); } + if (std::isnan(x)) { + return std::numeric_limits::quiet_NaN(); + } // -inf => 0, inf => 1 return 1 - std::signbit(x); } diff --git a/scipy/special/dd_real_wrappers.cpp b/scipy/special/dd_real_wrappers.cpp index ea07431af9ff..8d281303d6da 100644 --- a/scipy/special/dd_real_wrappers.cpp +++ b/scipy/special/dd_real_wrappers.cpp @@ -5,7 +5,7 @@ */ #include "dd_real_wrappers.h" -#include "xsf/cephes/dd_real.h" +#include using xsf::cephes::detail::double_double; diff --git a/scipy/special/meson.build b/scipy/special/meson.build index e18876588aa6..eb840af3669f 100644 --- a/scipy/special/meson.build +++ b/scipy/special/meson.build @@ -54,7 +54,7 @@ endif py3.extension_module('_special_ufuncs', ['_special_ufuncs.cpp', '_special_ufuncs_docs.cpp', 'sf_error.cc'], include_directories: ['..', '../_lib', '../_build_utils/src'], - dependencies: [np_dep], + dependencies: [xsf_dep, np_dep], link_args: version_link_args, cpp_args: ufuncs_cpp_args, install: true, @@ -64,7 +64,7 @@ py3.extension_module('_special_ufuncs', py3.extension_module('_gufuncs', ['_gufuncs.cpp', '_gufuncs_docs.cpp', 'sf_error.cc'], include_directories: ['..', '../_lib', '../_build_utils/src'], - dependencies: [np_dep], + dependencies: [xsf_dep, np_dep], link_args: version_link_args, cpp_args: ufuncs_cpp_args, install: true, @@ -73,7 +73,7 @@ py3.extension_module('_gufuncs', py3.extension_module('_specfun', [cython_gen_cpp.process('_specfun.pyx')], - dependencies: [np_dep], + dependencies: [xsf_dep, np_dep], link_args: version_link_args, install: true, subdir: 'scipy/special' @@ -123,6 +123,7 @@ py3.extension_module('_ufuncs', cpp_args: ['-DSP_SPECFUN_ERROR'], include_directories: ['..', '../_lib', '../_build_utils/src'], dependencies: [ + xsf_dep, lapack_dep, npymath_lib, np_dep, @@ -162,10 +163,9 @@ py3.extension_module('_ufuncs_cxx', uf_cython_gen_cpp.process(cython_special[2]), # _ufuncs_cxx.pyx ], cpp_args: ufuncs_cxx_cpp_args, - include_directories: ['..', '../_lib/boost_math/include', '../_lib', - '../_build_utils/src'], + include_directories: ['..', '../_lib', '../_build_utils/src'], link_args: version_link_args, - dependencies: [np_dep, ellint_dep], + dependencies: [boost_math_dep, xsf_dep, np_dep, ellint_dep], install: true, subdir: 'scipy/special', ) @@ -176,7 +176,7 @@ py3.extension_module('_ellip_harm_2', cpp_args: ['-DSP_SPECFUN_ERROR'], include_directories: ['..', '../_lib', '../_build_utils/src'], link_args: version_link_args, - dependencies: [lapack_dep, np_dep], + dependencies: [xsf_dep, lapack_dep, np_dep], install: true, subdir: 'scipy/special', ) @@ -193,7 +193,7 @@ py3.extension_module('cython_special', cpp_args: ['-DSP_SPECFUN_ERROR'], include_directories: ['..', '../_lib', '../_build_utils/src'], link_args: version_link_args, - dependencies: [np_dep, npymath_lib, lapack_dep], + dependencies: [xsf_dep, np_dep, npymath_lib, lapack_dep], link_with: cdflib_lib, install: true, subdir: 'scipy/special', @@ -209,7 +209,7 @@ py3.extension_module('_comb', py3.extension_module('_test_internal', [cython_gen.process('_test_internal.pyx'), 'dd_real_wrappers.cpp'], include_directories: ['../_lib', '../_build_utils/src'], - dependencies: [np_dep], + dependencies: [xsf_dep, np_dep], link_args: version_link_args, install: true, subdir: 'scipy/special', @@ -259,73 +259,6 @@ foreach npz_file: npz_files ) endforeach -# Headers for special functions in `xsf` library are included in -# both build and install dirs for development purposes. Not public! - -xsf_sources = [ - 'xsf/config.h', - 'xsf/error.h', - 'xsf/evalpoly.h', - 'xsf/lambertw.h', - 'xsf/binom.h', - 'xsf/trig.h', - 'xsf/zlog1.h', - 'xsf/loggamma.h', - 'xsf/digamma.h', - 'xsf/wright_bessel.h', - 'xsf/hyp2f1.h', - 'xsf/tools.h', - 'xsf/iv_ratio.h', - 'xsf/cdflib.h', - 'xsf/sici.h', - 'xsf/expint.h' -] - -xsf_cephes_sources = [ - 'xsf/cephes/const.h', - 'xsf/cephes/polevl.h', - 'xsf/cephes/beta.h', - 'xsf/cephes/gamma.h', - 'xsf/cephes/trig.h', - 'xsf/cephes/zeta.h', - 'xsf/cephes/psi.h', - 'xsf/cephes/lanczos.h', - 'xsf/cephes/rgamma.h', - 'xsf/cephes/chbevl.h', - 'xsf/cephes/poch.h', - 'xsf/cephes/hyp2f1.h', - 'xsf/cephes/besselpoly.h', - 'xsf/cephes/i0.h', - 'xsf/cephes/i1.h', - 'xsf/cephes/scipy_iv.h', - 'xsf/cephes/j0.h', - 'xsf/cephes/j1.h', - 'xsf/cephes/jv.h', - 'xsf/cephes/cbrt.h', - 'xsf/cephes/airy.h', - 'xsf/cephes/k0.h', - 'xsf/cephes/k1.h', - 'xsf/cephes/kn.h', - 'xsf/cephes/ndtr.h', - 'xsf/cephes/igam.h', - 'xsf/cephes/unity.h', - 'xsf/cephes/chdtr.h', - 'xsf/cephes/igami.h', - 'xsf/cephes/hyperg.h', - 'xsf/cephes/expn.h', - 'xsf/cephes/ellie.h', - 'xsf/cephes/ellik.h', - 'xsf/cephes/ellpe.h', - 'xsf/cephes/ellpk.h', - 'xsf/cephes/igam_asymp_coeff.h', - 'xsf/cephes/sici.h', - 'xsf/cephes/shichi.h', - 'xsf/cephes/sindg.h', - 'xsf/cephes/tandg.h' -] - -py3.install_sources(xsf_sources, subdir: 'scipy/special/xsf') -py3.install_sources(xsf_cephes_sources, subdir: 'scipy/special/xsf/cephes') python_sources = [ '__init__.py', diff --git a/scipy/special/sf_error.h b/scipy/special/sf_error.h index 0bc26b6db3d0..333e08983853 100644 --- a/scipy/special/sf_error.h +++ b/scipy/special/sf_error.h @@ -1,6 +1,6 @@ #pragma once -#include "xsf/error.h" +#include #ifdef __cplusplus extern "C" { diff --git a/scipy/special/stirling2.h b/scipy/special/stirling2.h index 20307a94d8ec..1f8d6e213a64 100644 --- a/scipy/special/stirling2.h +++ b/scipy/special/stirling2.h @@ -6,8 +6,8 @@ #include #include -#include "xsf/binom.h" -#include "xsf/lambertw.h" +#include +#include #include "sf_error.h" diff --git a/scipy/special/tests/meson.build b/scipy/special/tests/meson.build index 512db3022a81..65e0ca673779 100644 --- a/scipy/special/tests/meson.build +++ b/scipy/special/tests/meson.build @@ -56,7 +56,6 @@ python_sources = [ 'test_wrightomega.py', 'test_zeta.py', 'test_boost_ufuncs.py', - 'test_xsf_cuda.py', ] diff --git a/scipy/special/tests/test_boost_ufuncs.py b/scipy/special/tests/test_boost_ufuncs.py index 132fb9ab11ec..43da7337d0d2 100644 --- a/scipy/special/tests/test_boost_ufuncs.py +++ b/scipy/special/tests/test_boost_ufuncs.py @@ -59,3 +59,6 @@ def test_landau(): assert_allclose(ppf, x) isf = scu._landau_isf(sf, *args) assert_allclose(isf, x, rtol=1e-6) + +def test_gh22956(): + _ = scu._ncx2_pdf(30, 1e307, 16) diff --git a/scipy/special/tests/test_cdflib.py b/scipy/special/tests/test_cdflib.py index 4fb1a29ab451..7e6a2e3023da 100644 --- a/scipy/special/tests/test_cdflib.py +++ b/scipy/special/tests/test_cdflib.py @@ -3,7 +3,6 @@ The following functions still need tests: -- ncfdtri - ncfdtridfn - ncfdtridfd - ncfdtrinc @@ -491,7 +490,7 @@ def test_bdtrik_nbdtrik_inf(): @pytest.mark.parametrize( - "dfn,dfd,nc,f,expected", + "dfn,dfd,nc,f,expected_cdf", [[100.0, 0.1, 0.1, 100.0, 0.29787396410092676], [100.0, 100.0, 0.01, 0.1, 4.4344737598690424e-26], [100.0, 0.01, 0.1, 0.01, 0.002848616633080384], @@ -505,7 +504,7 @@ def test_bdtrik_nbdtrik_inf(): [100.0, 100.0, 0.1, 10.0, 1.0], [1.0, 0.1, 100.0, 10.0, 0.02926064279680897]] ) -def test_ncfdtr(dfn, dfd, nc, f, expected): +def test_ncfdtr_ncfdtri(dfn, dfd, nc, f, expected_cdf): # Reference values computed with mpmath with the following script # # import numpy as np @@ -548,8 +547,11 @@ def test_ncfdtr(dfn, dfd, nc, f, expected): # rng = np.random.default_rng(1234) # sample_idx = rng.choice(len(re), replace=False, size=12) # cases = np.array(cases)[sample_idx].tolist() - assert_allclose(sp.ncfdtr(dfn, dfd, nc, f), expected, rtol=1e-13, atol=0) - + assert_allclose(sp.ncfdtr(dfn, dfd, nc, f), expected_cdf, rtol=1e-13, atol=0) + # testing tails where the CDF reaches 0 or 1 does not make sense for inverses + # of a CDF as they are not bijective in these regions + if 0 < expected_cdf < 1: + assert_allclose(sp.ncfdtri(dfn, dfd, nc, expected_cdf), f, rtol=5e-11) @pytest.mark.parametrize( "args", diff --git a/scipy/special/tests/test_logsumexp.py b/scipy/special/tests/test_logsumexp.py index 225d6bb800fb..5c897aa7ec7e 100644 --- a/scipy/special/tests/test_logsumexp.py +++ b/scipy/special/tests/test_logsumexp.py @@ -4,7 +4,7 @@ import numpy as np -from scipy._lib._array_api import (is_array_api_strict, make_skip_xp_backends, +from scipy._lib._array_api import (is_array_api_strict, make_xp_test_case, xp_default_dtype, xp_device) from scipy._lib._array_api_no_0d import (xp_assert_equal, xp_assert_close, xp_assert_less) @@ -12,17 +12,10 @@ from scipy.special import log_softmax, logsumexp, softmax from scipy.special._logsumexp import _wrap_radians -from scipy._lib.array_api_extra.testing import lazy_xp_function - dtypes = ['float32', 'float64', 'int32', 'int64', 'complex64', 'complex128'] integral_dtypes = ['int32', 'int64'] -lazy_xp_function(_wrap_radians, static_argnames="xp") -lazy_xp_function(logsumexp, static_argnames=("axis", "keepdims", "return_sign")) -lazy_xp_function(softmax, static_argnames="axis") -lazy_xp_function(log_softmax, static_argnames="axis") - def test_wrap_radians(xp): x = xp.asarray([-math.pi-1, -math.pi, -1, -1e-300, @@ -39,7 +32,7 @@ def test_wrap_radians(xp): @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning") @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning") @pytest.mark.filterwarnings("ignore:overflow encountered:RuntimeWarning") -@make_skip_xp_backends(logsumexp) +@make_xp_test_case(logsumexp) class TestLogSumExp: def test_logsumexp(self, xp): # Test with zero-size array @@ -222,9 +215,6 @@ def test_gh18295(self, xp): ref = xp.logaddexp(a[0], a[1]) xp_assert_close(res, ref) - @pytest.mark.filterwarnings( - "ignore:The `numpy.copyto` function is not implemented:FutureWarning:dask" - ) @pytest.mark.parametrize('dtype', ['complex64', 'complex128']) def test_gh21610(self, xp, dtype): # gh-21610 noted that `logsumexp` could return imaginary components @@ -240,7 +230,7 @@ def test_gh21610(self, xp, dtype): res = logsumexp(x, axis=1) ref = xp.log(xp.sum(xp.exp(x), axis=1)) - max = xp.full_like(xp.imag(res), xp.asarray(xp.pi)) + max = xp.full_like(xp.imag(res), xp.pi) xp_assert_less(xp.abs(xp.imag(res)), max) xp_assert_close(res, ref) @@ -311,8 +301,22 @@ def test_device(self, x_raw, xp, devices): assert xp_device(logsumexp(x)) == xp_device(x) assert xp_device(logsumexp(x, b=x)) == xp_device(x) + def test_gh22903(self, xp): + # gh-22903 reported that `logsumexp` produced NaN where the weight associated + # with the max magnitude element was negative and `return_sign=False`, even if + # the net result should be the log of a positive number. + + # result is log of positive number + a = xp.asarray([3.06409428, 0.37251854, 3.87471931]) + b = xp.asarray([1.88190708, 2.84174795, -0.85016884]) + xp_assert_close(logsumexp(a, b=b), logsumexp(a, b=b, return_sign=True)[0]) + + # result is log of negative number + b = xp.asarray([1.88190708, 2.84174795, -3.85016884]) + xp_assert_close(logsumexp(a, b=b), xp.asarray(xp.nan)) + -@make_skip_xp_backends(softmax) +@make_xp_test_case(softmax) class TestSoftmax: def test_softmax_fixtures(self, xp): xp_assert_close(softmax(xp.asarray([1000., 0., 0., 0.])), @@ -381,7 +385,7 @@ def test_softmax_array_like(self): np.asarray([1., 0., 0., 0.]), rtol=1e-13) -@make_skip_xp_backends(log_softmax) +@make_xp_test_case(log_softmax) class TestLogSoftmax: def test_log_softmax_basic(self, xp): xp_assert_close(log_softmax(xp.asarray([1000., 1.])), diff --git a/scipy/special/tests/test_mpmath.py b/scipy/special/tests/test_mpmath.py index 43e84e444f2e..0cfa9126050f 100644 --- a/scipy/special/tests/test_mpmath.py +++ b/scipy/special/tests/test_mpmath.py @@ -321,6 +321,7 @@ def evf(mu, nu, x): # beta # ------------------------------------------------------------------------------ +@pytest.mark.slow @check_version(mpmath, '0.15') def test_beta(): np.random.seed(1234) diff --git a/scipy/special/tests/test_support_alternative_backends.py b/scipy/special/tests/test_support_alternative_backends.py index 8f24a881344f..e98939fef4b1 100644 --- a/scipy/special/tests/test_support_alternative_backends.py +++ b/scipy/special/tests/test_support_alternative_backends.py @@ -1,106 +1,103 @@ -from types import ModuleType +from functools import partial +import pickle import pytest +from hypothesis import given, strategies +import hypothesis.extra.numpy as npst +from packaging import version from scipy import special -from scipy.special._support_alternative_backends import (get_array_special_func, - array_special_func_map) +from scipy.special._support_alternative_backends import _special_funcs from scipy._lib._array_api_no_0d import xp_assert_close from scipy._lib._array_api import (is_cupy, is_dask, is_jax, is_torch, - SCIPY_ARRAY_API, SCIPY_DEVICE) + make_xp_pytest_param, make_xp_test_case, + xp_default_dtype) from scipy._lib.array_api_compat import numpy as np -from scipy._lib.array_api_extra.testing import lazy_xp_function +# Run all tests in this module in the Array API CI, including those without +# the xp fixture +pytestmark = pytest.mark.array_api_backends -special_wrapped = ModuleType("special_wrapped") -lazy_xp_modules = [special_wrapped] -for f_name in array_special_func_map: - f = getattr(special, f_name) - setattr(special_wrapped, f_name, f) - lazy_xp_function(f) +lazy_xp_modules = [special] -@pytest.mark.skipif(not SCIPY_ARRAY_API, reason="Alternative backends must be enabled.") -def test_dispatch_to_unrecognized_library(): - xp = pytest.importorskip("array_api_strict") - f = get_array_special_func('ndtr', xp=xp) - x = [1, 2, 3] - res = f(xp.asarray(x)) - ref = xp.asarray(special.ndtr(np.asarray(x))) - xp_assert_close(res, ref) +def _skip_or_tweak_alternative_backends(xp, f_name, dtypes): + """Skip tests for specific intersections of scipy.special functions + vs. backends vs. dtypes vs. devices. + Also suggest bespoke tweaks. + Returns + ------- + positive_only : bool + Whether you should exclusively test positive inputs. + dtypes_np_ref : list[str] + The dtypes to use for the reference NumPy arrays. + """ + if ((is_jax(xp) and f_name == 'gammaincc') # google/jax#20699 + # gh-20972 + or ((is_cupy(xp) or is_jax(xp) or is_torch(xp)) and f_name == 'chdtrc')): + positive_only = True + else: + positive_only = False -@pytest.mark.skipif(not SCIPY_ARRAY_API, - reason="xp_promote won't accept non-numpy objects") -@pytest.mark.parametrize('dtype', ['float32', 'float64', 'int64']) -def test_rel_entr_generic(dtype): - xp = pytest.importorskip("array_api_strict") - f = get_array_special_func('rel_entr', xp=xp) - dtype_np = getattr(np, dtype) - dtype_xp = getattr(xp, dtype) - x = [-1, 0, 0, 1] - y = [1, 0, 2, 3] + if not any('int' in dtype for dtype in dtypes): + return positive_only, dtypes - x_xp = xp.asarray(x, dtype=dtype_xp) - y_xp = xp.asarray(y, dtype=dtype_xp) - res = f(x_xp, y_xp) + # Integer-specific issues from this point onwards - x_np = np.asarray(x, dtype=dtype_np) - y_np = np.asarray(y, dtype=dtype_np) - ref = special.rel_entr(x_np[-1], y_np[-1]) - ref = np.asarray([np.inf, 0, 0, ref], dtype=ref.dtype) - ref = xp.asarray(ref) + if ((is_torch(xp) and f_name in {'gammainc', 'gammaincc'}) + or (is_cupy(xp) and f_name in {'stdtr', 'i0e', 'i1e'}) + or (is_jax(xp) and f_name in {'stdtr', 'ndtr', 'ndtri', 'log_ndtr'}) + ): + pytest.skip(f"`{f_name}` does not support integer types") + + # int/float mismatched args support is sketchy + if (any('float' in dtype for dtype in dtypes) + and ((is_torch(xp) and f_name in ('rel_entr', 'xlogy')) + or (is_jax(xp) and f_name in ('gammainc', 'gammaincc', + 'rel_entr', 'xlogy'))) + ): + pytest.xfail("dtypes do not match") - xp_assert_close(res, ref) + dtypes_np_ref = dtypes + if (is_torch(xp) and xp_default_dtype(xp) == xp.float32 + and f_name not in {'betainc', 'betaincc', 'stdtr', 'stdtrit'} + ): + # On PyTorch with float32 default dtype, sometimes ints are promoted + # to float32, and sometimes to float64. + # When they are promoted to float32, explicitly convert the reference + # numpy arrays to float32 to prevent them from being automatically promoted + # to float64 instead. + dtypes_np_ref = ['float32' if 'int' in dtype else dtype for dtype in dtypes] + + return positive_only, dtypes_np_ref -@pytest.mark.fail_slow(5) -# `reversed` is for developer convenience: test new function first = less waiting -@pytest.mark.parametrize('f_name,n_args', reversed(array_special_func_map.items())) @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") -@pytest.mark.parametrize('dtype', ['float32', 'float64']) @pytest.mark.parametrize('shapes', [[(0,)]*4, [tuple()]*4, [(10,)]*4, [(10,), (11, 1), (12, 1, 1), (13, 1, 1, 1)]]) -def test_support_alternative_backends(xp, f_name, n_args, dtype, shapes): - if (SCIPY_DEVICE != 'cpu' - and is_torch(xp) - and f_name in {'stdtr', 'stdtrit', 'betaincc', 'betainc'} - ): - pytest.skip(f"`{f_name}` does not have an array-agnostic implementation " - "and cannot delegate to PyTorch.") - if is_jax(xp) and f_name == "stdtrit": - pytest.skip(f"`{f_name}` requires scipy.optimize support for immutable arrays") - - shapes = shapes[:n_args] - f = getattr(special, f_name) # Unwrapped - fw = getattr(special_wrapped, f_name) # Wrapped by lazy_xp_function - +@pytest.mark.parametrize('dtype', ['float32', 'float64', 'int64']) +@pytest.mark.parametrize( + 'func,nfo', [make_xp_pytest_param(i.wrapper, i) for i in _special_funcs]) +def test_support_alternative_backends(xp, func, nfo, dtype, shapes): + positive_only, [dtype_np_ref] = _skip_or_tweak_alternative_backends( + xp, nfo.name, [dtype]) dtype_np = getattr(np, dtype) dtype_xp = getattr(xp, dtype) - # # To test the robustness of the alternative backend's implementation, - # # use Hypothesis to generate arguments - # from hypothesis import given, strategies, reproduce_failure, assume - # import hypothesis.extra.numpy as npst - # @given(data=strategies.data()) - # mbs = npst.mutually_broadcastable_shapes(num_shapes=n_args) - # shapes, final_shape = data.draw(mbs) - # elements = dict(allow_subnormal=False) # consider min_value, max_value - # args_np = [np.asarray(data.draw(npst.arrays(dtype_np, shape, elements=elements)), - # dtype=dtype_np) - # for shape in shapes] - - # For CI, be a little more forgiving; just generate normally distributed arguments + shapes = shapes[:nfo.n_args] rng = np.random.default_rng(984254252920492019) - args_np = [rng.standard_normal(size=shape, dtype=dtype_np) for shape in shapes] - - if ((is_jax(xp) and f_name == 'gammaincc') # google/jax#20699 - # gh-20972 - or ((is_cupy(xp) or is_jax(xp) or is_torch(xp)) and f_name == 'chdtrc')): - args_np[0] = np.abs(args_np[0]) - args_np[1] = np.abs(args_np[1]) + if 'int' in dtype: + iinfo = np.iinfo(dtype_np) + rand = partial(rng.integers, iinfo.min, iinfo.max + 1) + else: + rand = rng.standard_normal + args_np = [rand(size=shape, dtype=dtype_np) for shape in shapes] + if positive_only: + args_np = [np.abs(arg) for arg in args_np] args_xp = [xp.asarray(arg, dtype=dtype_xp) for arg in args_np] + args_np = [np.asarray(arg, dtype=dtype_np_ref) for arg in args_np] if is_dask(xp): # We're using map_blocks to dispatch the function to Dask. @@ -109,13 +106,137 @@ def test_support_alternative_backends(xp, f_name, n_args, dtype, shapes): # Try to trigger bugs related to having multiple chunks. args_xp = [arg.rechunk(5) for arg in args_xp] - res = fw(*args_xp) - ref = xp.asarray(f(*args_np), dtype=dtype_xp) + res = nfo.wrapper(*args_xp) # Also wrapped by lazy_xp_function + ref = nfo.func(*args_np) # Unwrapped ufunc + + # When dtype_np is integer, the output dtype can be float + atol = 0 if ref.dtype.kind in 'iu' else 10 * np.finfo(ref.dtype).eps + xp_assert_close(res, xp.asarray(ref), atol=atol) + + +@pytest.mark.parametrize( + 'func, nfo', + [make_xp_pytest_param(i.wrapper, i) for i in _special_funcs if i.n_args >= 2]) +@pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") +def test_support_alternative_backends_mismatched_dtypes(xp, func, nfo): + """Test mix-n-match of int and float arguments""" + dtypes = ['int64', 'float32', 'float64'][:nfo.n_args] + dtypes_xp = [xp.int64, xp.float32, xp.float64][:nfo.n_args] + positive_only, dtypes_np_ref = _skip_or_tweak_alternative_backends( + xp, nfo.name, dtypes) + + rng = np.random.default_rng(984254252920492019) + iinfo = np.iinfo(np.int64) + randint = partial(rng.integers, iinfo.min, iinfo.max + 1) + args_np = [ + randint(size=1, dtype=np.int64), + rng.standard_normal(size=1, dtype=np.float32), + rng.standard_normal(size=1, dtype=np.float64), + ][:nfo.n_args] + if positive_only: + args_np = [np.abs(arg) for arg in args_np] + + args_xp = [xp.asarray(arg, dtype=dtype_xp) + for arg, dtype_xp in zip(args_np, dtypes_xp)] + args_np = [np.asarray(arg, dtype=dtype_np_ref) + for arg, dtype_np_ref in zip(args_np, dtypes_np_ref)] + + res = nfo.wrapper(*args_xp) # Also wrapped by lazy_xp_function + ref = nfo.func(*args_np) # Unwrapped ufunc + + atol = 10 * np.finfo(ref.dtype).eps + xp_assert_close(res, xp.asarray(ref), atol=atol) + + +@pytest.mark.xslow +@given(data=strategies.data()) +@pytest.mark.fail_slow(5) +@pytest.mark.parametrize( + 'func,nfo', [make_xp_pytest_param(nfo.wrapper, nfo) for nfo in _special_funcs]) +@pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") +@pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") +@pytest.mark.filterwarnings("ignore:overflow encountered:RuntimeWarning:dask") +@pytest.mark.filterwarnings( + "ignore:overflow encountered:RuntimeWarning:array_api_strict" +) +def test_support_alternative_backends_hypothesis(xp, func, nfo, data): + dtype = data.draw(strategies.sampled_from(['float32', 'float64', 'int64'])) + positive_only, [dtype_np_ref] = _skip_or_tweak_alternative_backends( + xp, nfo.name, [dtype]) + dtype_np = getattr(np, dtype) + dtype_xp = getattr(xp, dtype) + + elements = {'allow_subnormal': False} + # Most failures are due to NaN or infinity; uncomment to suppress them + # elements['allow_infinity'] = False + # elements['allow_nan'] = False + if positive_only: + elements['min_value'] = 0 + + shapes, _ = data.draw( + npst.mutually_broadcastable_shapes(num_shapes=nfo.n_args)) + args_np = [data.draw(npst.arrays(dtype_np, shape, elements=elements)) + for shape in shapes] + + args_xp = [xp.asarray(arg, dtype=dtype_xp) for arg in args_np] + args_np = [np.asarray(arg, dtype=dtype_np_ref) for arg in args_np] + + res = nfo.wrapper(*args_xp) # Also wrapped by lazy_xp_function + ref = nfo.func(*args_np) # Unwrapped ufunc + + # When dtype_np is integer, the output dtype can be float + atol = 0 if ref.dtype.kind in 'iu' else 10 * np.finfo(ref.dtype).eps + xp_assert_close(res, xp.asarray(ref), atol=atol) + + +@pytest.mark.parametrize("func", [nfo.wrapper for nfo in _special_funcs]) +def test_pickle(func): + roundtrip = pickle.loads(pickle.dumps(func)) + assert roundtrip is func + + +@pytest.mark.parametrize("func", [nfo.wrapper for nfo in _special_funcs]) +def test_repr(func): + assert func.__name__ in repr(func) + assert "locals" not in repr(func) + + +@pytest.mark.skipif( + version.parse(np.__version__) < version.parse("2.2"), + reason="Can't update ufunc __doc__ when SciPy is compiled vs. NumPy < 2.2") +@pytest.mark.parametrize('func', [nfo.wrapper for nfo in _special_funcs]) +def test_doc(func): + """xp_capabilities updates the docstring in place. + Make sure it does so exactly once, including when SCIPY_ARRAY_API is not set. + """ + match = "has experimental support for Python Array API" + assert func.__doc__.count(match) == 1 + + +@pytest.mark.parametrize('func,n_args', + [(nfo.wrapper, nfo.n_args) for nfo in _special_funcs]) +def test_ufunc_kwargs(func, n_args): + """Test that numpy-specific out= and dtype= keyword arguments + of ufuncs still work when SCIPY_ARRAY_API is set. + """ + # out= + args = [np.asarray([.1, .2])] * n_args + out = np.empty(2) + y = func(*args, out=out) + xp_assert_close(y, out) + + # out= with out.dtype != args.dtype + out = np.empty(2, dtype=np.float32) + y = func(*args, out=out) + xp_assert_close(y, out) + + # dtype= + y = func(*args, dtype=np.float32) + assert y.dtype == np.float32 - eps = np.finfo(dtype_np).eps - xp_assert_close(res, ref, atol=10*eps) +@make_xp_test_case(special.chdtr) def test_chdtr_gh21311(xp): # the edge case behavior of generic chdtr was not right; see gh-21311 # be sure to test at least these cases diff --git a/scipy/special/tests/test_xsf_cuda.py b/scipy/special/tests/test_xsf_cuda.py deleted file mode 100644 index dc39c2eeaf43..000000000000 --- a/scipy/special/tests/test_xsf_cuda.py +++ /dev/null @@ -1,114 +0,0 @@ -import os -import pytest -import scipy.special as sc -import shutil -import tempfile - -from uuid import uuid4 - -from scipy.special._testutils import check_version -from scipy.special._testutils import MissingModule - -try: - import cupy # type: ignore -except (ImportError, AttributeError): - cupy = MissingModule('cupy') - - -def get_test_cases(): - cases_source = [ - (sc.beta, "cephes/beta.h", "out0 = xsf::cephes::beta(in0, in1)"), - (sc.binom, "binom.h", "out0 = xsf::binom(in0, in1)"), - (sc.digamma, "digamma.h", "xsf::digamma(in0)"), - (sc.expn, "cephes/expn.h", "out0 = xsf::cephes::expn(in0, in1)"), - (sc.hyp2f1, "hyp2f1.h", "out0 = xsf::hyp2f1(in0, in1, in2, in3)"), - (sc._ufuncs._lambertw, "lambertw.h", "out0 = xsf::lambertw(in0, in1, in2)"), - (sc.ellipkinc, "cephes/ellik.h", "out0 = xsf::cephes::ellik(in0, in1)"), - (sc.ellipeinc, "cephes/ellie.h", "out0 = xsf::cephes::ellie(in0, in1)"), - (sc.gdtrib, "cdflib.h", "out0 = xsf::gdtrib(in0, in1, in2)"), - (sc.sici, "sici.h", "xsf::sici(in0, &out0, &out1)"), - (sc.shichi, "sici.h", "xsf::shichi(in0, &out0, &out1)"), - ] - - cases = [] - for ufunc, header, routine in cases_source: - preamble = f"#include " - for signature in ufunc.types: - cases.append((signature, preamble, routine)) - return cases - - -dtype_map = { - "f": "float32", - "d": "float64", - "F": "complex64", - "D": "complex128", - "i": "int32", - "l": "int64", -} - - -def get_params(signature): - in_, out = signature.split("->") - in_params = [] - out_params = [] - for i, typecode in enumerate(in_): - in_params.append(f"{dtype_map[typecode]} in{i}") - for i, typecode in enumerate(out): - out_params.append(f"{dtype_map[typecode]} out{i}") - in_params = ", ".join(in_params) - out_params = ", ".join(out_params) - return in_params, out_params - - -def get_sample_input(signature, xp): - dtype_map = { - "f": xp.float32, - "d": xp.float64, - "F": xp.complex64, - "D": xp.complex128, - "i": xp.int32, - "l": xp.int64, - } - - in_, _ = signature.split("->") - args = [] - for typecode in in_: - args.append(xp.zeros(2, dtype=dtype_map[typecode])) - return args - - -@pytest.fixture(scope="module", autouse=True) -def manage_cupy_cache(): - # Temporarily change cupy kernel cache location so kernel cache will not be polluted - # by these tests. Remove temporary cache in teardown. - temp_cache_dir = tempfile.mkdtemp() - original_cache_dir = os.environ.get('CUPY_CACHE_DIR', None) - os.environ['CUPY_CACHE_DIR'] = temp_cache_dir - - yield - - if original_cache_dir is not None: - os.environ['CUPY_CACHE_DIR'] = original_cache_dir - else: - del os.environ['CUPY_CACHE_DIR'] - shutil.rmtree(temp_cache_dir) - - -@check_version(cupy, "13.0.0") -@pytest.mark.parametrize("signature,preamble,routine", get_test_cases()) -@pytest.mark.xslow -def test_compiles_in_cupy(signature, preamble, routine, manage_cupy_cache): - name = f"x{uuid4().hex}" - in_params, out_params = get_params(signature) - - func = cupy.ElementwiseKernel( - in_params, - out_params, - routine, - name, - preamble=preamble, - options=(f"--include-path={sc._get_include()}", "-std=c++17") - ) - - _ = func(*get_sample_input(signature, cupy)) diff --git a/scipy/special/xsf/.clang-format b/scipy/special/xsf/.clang-format deleted file mode 100644 index 96eefe452059..000000000000 --- a/scipy/special/xsf/.clang-format +++ /dev/null @@ -1,12 +0,0 @@ -BasedOnStyle: LLVM -Standard: Cpp11 -UseTab: Never -IndentWidth: 4 -BreakBeforeBraces: Attach -Cpp11BracedListStyle: true -NamespaceIndentation: Inner -AlwaysBreakTemplateDeclarations: true -SpaceAfterCStyleCast: true -ColumnLimit: 120 -InsertNewlineAtEOF: true -AlignAfterOpenBracket: BlockIndent diff --git a/scipy/special/xsf/airy.h b/scipy/special/xsf/airy.h deleted file mode 100644 index 7bdec5340212..000000000000 --- a/scipy/special/xsf/airy.h +++ /dev/null @@ -1,567 +0,0 @@ -#pragma once - -#include "amos.h" -#include "cephes/airy.h" -#include "config.h" -#include "error.h" - -inline int cephes_airy(float xf, float *aif, float *aipf, float *bif, float *bipf) { - double ai; - double aip; - double bi; - double bip; - int res = xsf::cephes::airy(xf, &ai, &aip, &bi, &bip); - - *aif = ai; - *aipf = aip; - *bif = bi; - *bipf = bip; - return res; -} - -namespace xsf { -namespace detail { - - template - void itairy(T x, T &apt, T &bpt, T &ant, T &bnt) { - - // ====================================================== - // Purpose: Compute the integrals of Airy fnctions with - // respect to t from 0 and x ( x ≥ 0 ) - // Input : x --- Upper limit of the integral - // Output : APT --- Integration of Ai(t) from 0 and x - // BPT --- Integration of Bi(t) from 0 and x - // ANT --- Integration of Ai(-t) from 0 and x - // BNT --- Integration of Bi(-t) from 0 and x - // ====================================================== - - int k, l; - T fx, gx, r, su1, su2, su3, su4, su5, su6, xp6, xe, xr1, xr2; - - const T pi = 3.141592653589793; - const T c1 = 0.355028053887817; - const T c2 = 0.258819403792807; - const T sr3 = 1.732050807568877; - const T q0 = 1.0 / 3.0; - const T q1 = 2.0 / 3.0; - const T q2 = 1.4142135623730951; - const T eps = 1e-5; - static const T a[16] = {0.569444444444444, 0.891300154320988, 0.226624344493027e+01, - 0.798950124766861e+01, 0.360688546785343e+02, 0.198670292131169e+03, - 0.129223456582211e+04, 0.969483869669600e+04, 0.824184704952483e+05, - 0.783031092490225e+06, 0.822210493622814e+07, 0.945557399360556e+08, - 0.118195595640730e+10, 0.159564653040121e+11, 0.231369166433050e+12, - 0.358622522796969e+13}; - - if (x == 0.0) { - apt = 0.0; - bpt = 0.0; - ant = 0.0; - bnt = 0.0; - } else { - if (std::abs(x) <= 9.25) { - for (l = 0; l < 2; l++) { - x = x * std::pow(-1, l); - fx = x; - r = x; - for (k = 1; k < 41; k++) { - r = r * (3.0 * k - 2.0) / (3.0 * k + 1.0) * x / (3.0 * k) * x / (3.0 * k - 1.0) * x; - fx += r; - if (std::abs(r) < std::abs(fx) * eps) { - break; - } - } - gx = 0.5 * x * x; - r = gx; - for (k = 1; k < 41; k++) { - r = r * (3.0 * k - 1.0) / (3.0 * k + 2.0) * x / (3.0 * k) * x / (3.0 * k + 1.0) * x; - gx += r; - if (std::abs(r) < std::abs(gx) * eps) { - break; - } - } - ant = c1 * fx - c2 * gx; - bnt = sr3 * (c1 * fx + c2 * gx); - if (l == 0) { - apt = ant; - bpt = bnt; - } else { - ant = -ant; - bnt = -bnt; - x = -x; - } - } - } else { - xe = x * std::sqrt(x) / 1.5; - xp6 = 1.0 / std::sqrt(6.0 * pi * xe); - su1 = 1.0; - r = 1.0; - xr1 = 1.0 / xe; - for (k = 1; k < 17; k++) { - r = -r * xr1; - su1 += a[k - 1] * r; - } - su2 = 1.0; - r = 1.0; - for (k = 1; k < 17; k++) { - r = r * xr1; - su2 += a[k - 1] * r; - } - apt = q0 - std::exp(-xe) * xp6 * su1; - bpt = 2.0 * std::exp(xe) * xp6 * su2; - su3 = 1.0; - r = 1.0; - xr2 = 1.0 / (xe * xe); - for (k = 1; k < 9; k++) { - r = -r * xr2; - su3 += a[2 * k - 1] * r; - } - su4 = a[0] * xr1; - r = xr1; - for (k = 1; k < 8; k++) { - r = -r * xr2; - su4 += a[2 * k] * r; - } - su5 = su3 + su4; - su6 = su3 - su4; - ant = q1 - q2 * xp6 * (su5 * std::cos(xe) - su6 * std::sin(xe)); - bnt = q2 * xp6 * (su5 * std::sin(xe) + su6 * std::cos(xe)); - } - } - } - -} // namespace detail - -inline void airyb(double x, double *ai, double *bi, double *ad, double *bd) { - - // ======================================================= - // Purpose: Compute Airy functions and their derivatives - // Input: x --- Argument of Airy function - // Output: AI --- Ai(x) - // BI --- Bi(x) - // AD --- Ai'(x) - // BD --- Bi'(x) - // ======================================================= - - int k, km, km2, kmax; - double ck[52], dk[52]; - double xa, xq, xm, fx, r, gx, df, dg, sai, sad, sbi, sbd, xp1, xr2; - double xf, rp, xar, xe, xr1, xcs, xss, ssa, sda, ssb, sdb; - - const double eps = 1.0e-15; - const double pi = 3.141592653589793; - const double c1 = 0.355028053887817; - const double c2 = 0.258819403792807; - const double sr3 = 1.732050807568877; - - km2 = 0; - xa = fabs(x); - xq = sqrt(xa); - xm = 8.0; - if (x > 0.0) - xm = 5.0; - - if (x == 0.0) { - *ai = c1; - *bi = sr3 * c1; - *ad = -c2; - *bd = sr3 * c2; - return; - } - - if (xa <= xm) { - fx = 1.0; - r = 1.0; - for (k = 1; k <= 40; k++) { - r = r * x / (3.0 * k) * x / (3.0 * k - 1.0) * x; - fx += r; - if (fabs(r) < fabs(fx) * eps) - break; - } - - gx = x; - r = x; - for (k = 1; k <= 40; k++) { - r = r * x / (3.0 * k) * x / (3.0 * k + 1.0) * x; - gx += r; - if (fabs(r) < fabs(gx) * eps) - break; - } - - *ai = c1 * fx - c2 * gx; - *bi = sr3 * (c1 * fx + c2 * gx); - - df = 0.5 * x * x; - r = df; - for (k = 1; k <= 40; k++) { - r = r * x / (3.0 * k) * x / (3.0 * k + 2.0) * x; - df += r; - if (fabs(r) < fabs(df) * eps) - break; - } - - dg = 1.0; - r = 1.0; - for (k = 1; k <= 40; k++) { - r = r * x / (3.0 * k) * x / (3.0 * k - 2.0) * x; - dg += r; - if (fabs(r) < fabs(dg) * eps) - break; - } - - *ad = c1 * df - c2 * dg; - *bd = sr3 * (c1 * df + c2 * dg); - } else { - km = (int) (24.5 - xa); - if (xa < 6.0) - km = 14; - if (xa > 15.0) - km = 10; - - if (x > 0.0) { - kmax = km; - } else { - // Choose cutoffs so that the remainder term in asymptotic - // expansion is epsilon size. The X<0 branch needs to be fast - // in order to make AIRYZO efficient - if (xa > 70.0) - km = 3; - if (xa > 500.0) - km = 2; - if (xa > 1000.0) - km = 1; - - km2 = km; - if (xa > 150.0) - km2 = 1; - if (xa > 3000.0) - km2 = 0; - kmax = 2 * km + 1; - } - xe = xa * xq / 1.5; - xr1 = 1.0 / xe; - xar = 1.0 / xq; - xf = sqrt(xar); - rp = 0.5641895835477563; - r = 1.0; - for (k = 1; k <= kmax; k++) { - r = r * (6.0 * k - 1.0) / 216.0 * (6.0 * k - 3.0) / k * (6.0 * k - 5.0) / (2.0 * k - 1.0); - ck[k - 1] = r; - dk[k - 1] = -(6.0 * k + 1.0) / (6.0 * k - 1.0) * r; - } - - if (x > 0.0) { - sai = 1.0; - sad = 1.0; - r = 1.0; - for (k = 1; k <= km; k++) { - r *= -xr1; - sai += ck[k - 1] * r; - sad += dk[k - 1] * r; - } - sbi = 1.0; - sbd = 1.0; - r = 1.0; - for (k = 1; k <= km; k++) { - r *= xr1; - sbi += ck[k - 1] * r; - sbd += dk[k - 1] * r; - } - xp1 = exp(-xe); - *ai = 0.5 * rp * xf * xp1 * sai; - *bi = rp * xf / xp1 * sbi; - *ad = -0.5 * rp / xf * xp1 * sad; - *bd = rp / xf / xp1 * sbd; - } else { - xcs = cos(xe + pi / 4.0); - xss = sin(xe + pi / 4.0); - ssa = 1.0; - sda = 1.0; - r = 1.0; - xr2 = 1.0 / (xe * xe); - for (k = 1; k <= km; k++) { - r *= -xr2; - ssa += ck[2 * k - 1] * r; - sda += dk[2 * k - 1] * r; - } - ssb = ck[0] * xr1; - sdb = dk[0] * xr1; - r = xr1; - for (k = 1; k <= km2; k++) { - r *= -xr2; - ssb += ck[2 * k] * r; - sdb += dk[2 * k] * r; - } - - *ai = rp * xf * (xss * ssa - xcs * ssb); - *bi = rp * xf * (xcs * ssa + xss * ssb); - *ad = -rp / xf * (xcs * sda + xss * sdb); - *bd = rp / xf * (xss * sda - xcs * sdb); - } - } - return; -} - -inline void airyzo(int nt, int kf, double *xa, double *xb, double *xc, double *xd) { - - // ======================================================== - // Purpose: Compute the first NT zeros of Airy functions - // Ai(x) and Ai'(x), a and a', and the associated - // values of Ai(a') and Ai'(a); and the first NT - // zeros of Airy functions Bi(x) and Bi'(x), b and - // b', and the associated values of Bi(b') and - // Bi'(b) - // Input : NT --- Total number of zeros - // KF --- Function code - // KF=1 for Ai(x) and Ai'(x) - // KF=2 for Bi(x) and Bi'(x) - // Output: XA(m) --- a, the m-th zero of Ai(x) or - // b, the m-th zero of Bi(x) - // XB(m) --- a', the m-th zero of Ai'(x) or - // b', the m-th zero of Bi'(x) - // XC(m) --- Ai(a') or Bi(b') - // XD(m) --- Ai'(a) or Bi'(b) - // ( m --- Serial number of zeros ) - // Routine called: AIRYB for computing Airy functions and - // their derivatives - // ======================================================= - - const double pi = 3.141592653589793; - int i; - double rt = 0.0, rt0, u = 0.0, u1 = 0.0, x, ai, bi, ad, bd, err; - - for (i = 1; i <= nt; ++i) { - rt0 = 0.0; - if (kf == 1) { - u = 3.0 * pi * (4.0 * i - 1) / 8.0; - u1 = 1 / (u * u); - } else if (kf == 2) { - if (i == 1) { - rt0 = -1.17371; - } else { - u = 3.0 * pi * (4.0 * i - 3.0) / 8.0; - u1 = 1 / (u * u); - } - } - - if (rt0 == 0) { - // DLMF 9.9.18 - rt0 = -pow(u * u, 1.0 / 3.0) * - (1.0 + - u1 * (5.0 / 48.0 + u1 * (-5.0 / 36.0 + u1 * (77125.0 / 82944.0 + u1 * (-108056875.0 / 6967296.0))))); - } - - while (1) { - x = rt0; - airyb(x, &ai, &bi, &ad, &bd); - - if (kf == 1) { - rt = rt0 - ai / ad; - } else if (kf == 2) { - rt = rt0 - bi / bd; - } - - err = fabs((rt - rt0) / rt); - if (err <= 1.0e-12) { - break; - } else { - rt0 = rt; - } - } - - xa[i - 1] = rt; - if (err > 1.0e-14) { - airyb(rt, &ai, &bi, &ad, &bd); - } - - if (kf == 1) { - xd[i - 1] = ad; - } else if (kf == 2) { - xd[i - 1] = bd; - } - } - - for (i = 1; i <= nt; ++i) { - rt0 = 0.0; - - if (kf == 1) { - if (i == 1) { - rt0 = -1.01879; - } else { - u = 3.0 * pi * (4.0 * i - 3.0) / 8.0; - u1 = 1 / (u * u); - } - } else if (kf == 2) { - if (i == 1) { - rt0 = -2.29444; - } else { - u = 3.0 * pi * (4.0 * i - 1.0) / 8.0; - u1 = 1 / (u * u); - } - } - - if (rt0 == 0) { - // DLMF 9.9.19 - rt0 = -pow(u * u, 1.0 / 3.0) * - (1.0 + u1 * (-7.0 / 48.0 + - u1 * (35.0 / 288.0 + u1 * (-181223.0 / 207360.0 + u1 * (18683371.0 / 1244160.0))))); - } - - while (1) { - x = rt0; - airyb(x, &ai, &bi, &ad, &bd); - - if (kf == 1) { - rt = rt0 - ad / (ai * x); - } else if (kf == 2) { - rt = rt0 - bd / (bi * x); - } - - err = fabs((rt - rt0) / rt); - if (err <= 1.0e-12) { - break; - } else { - rt0 = rt; - } - } - xb[i - 1] = rt; - - if (err > 1.0e-14) { - airyb(rt, &ai, &bi, &ad, &bd); - } - - if (kf == 1) { - xc[i - 1] = ai; - } else if (kf == 2) { - xc[i - 1] = bi; - } - } - return; -} - -template -void airy(std::complex z, std::complex &ai, std::complex &aip, std::complex &bi, std::complex &bip) { - int id = 0; - int ierr = 0; - int kode = 1; - int nz; - - ai = amos::airy(z, id, kode, &nz, &ierr); - set_error_and_nan("airy:", ierr_to_sferr(nz, ierr), ai); - - nz = 0; - bi = amos::biry(z, id, kode, &ierr); - set_error_and_nan("airy:", ierr_to_sferr(nz, ierr), bi); - - id = 1; - aip = amos::airy(z, id, kode, &nz, &ierr); - set_error_and_nan("airy:", ierr_to_sferr(nz, ierr), aip); - - nz = 0; - bip = amos::biry(z, id, kode, &ierr); - set_error_and_nan("airy:", ierr_to_sferr(nz, ierr), bip); -} - -template -void airye(std::complex z, std::complex &ai, std::complex &aip, std::complex &bi, std::complex &bip) { - int id = 0; - int kode = 2; /* Exponential scaling */ - int nz, ierr; - - ai = amos::airy(z, id, kode, &nz, &ierr); - set_error_and_nan("airye:", ierr_to_sferr(nz, ierr), ai); - - nz = 0; - bi = amos::biry(z, id, kode, &ierr); - set_error_and_nan("airye:", ierr_to_sferr(nz, ierr), bi); - - id = 1; - aip = amos::airy(z, id, kode, &nz, &ierr); - set_error_and_nan("airye:", ierr_to_sferr(nz, ierr), aip); - - nz = 0; - bip = amos::biry(z, id, kode, &ierr); - set_error_and_nan("airye:", ierr_to_sferr(nz, ierr), bip); -} - -template -void airye(T z, T &ai, T &aip, T &bi, T &bip) { - int id = 0; - int kode = 2; /* Exponential scaling */ - int nz, ierr; - std::complex cai, caip, cbi, cbip; - - cai.real(NAN); - cai.imag(NAN); - cbi.real(NAN); - cbi.imag(NAN); - caip.real(NAN); - caip.imag(NAN); - cbip.real(NAN); - cbip.real(NAN); - - if (z < 0) { - ai = NAN; - } else { - cai = amos::airy(z, id, kode, &nz, &ierr); - set_error_and_nan("airye:", ierr_to_sferr(nz, ierr), cai); - ai = std::real(cai); - } - - nz = 0; - cbi = amos::biry(z, id, kode, &ierr); - set_error_and_nan("airye:", ierr_to_sferr(nz, ierr), cbi); - bi = std::real(cbi); - - id = 1; - if (z < 0) { - aip = NAN; - } else { - caip = amos::airy(z, id, kode, &nz, &ierr); - set_error_and_nan("airye:", ierr_to_sferr(nz, ierr), caip); - aip = std::real(caip); - } - - nz = 0; - cbip = amos::biry(z, id, kode, &ierr); - set_error_and_nan("airye:", ierr_to_sferr(nz, ierr), cbip); - bip = std::real(cbip); -} - -template -void airy(T x, T &ai, T &aip, T &bi, T &bip) { - /* For small arguments, use Cephes as it's slightly faster. - * For large arguments, use AMOS as it's more accurate. - */ - if (x < -10 || x > 10) { - std::complex zai, zaip, zbi, zbip; - airy(std::complex(x), zai, zaip, zbi, zbip); - ai = std::real(zai); - aip = std::real(zaip); - bi = std::real(zbi); - bip = std::real(zbip); - } else { - cephes::airy(x, &ai, &aip, &bi, &bip); - } -} - -template -void itairy(T x, T &apt, T &bpt, T &ant, T &bnt) { - bool x_signbit = std::signbit(x); - if (x_signbit) { - x = -x; - } - - detail::itairy(x, apt, bpt, ant, bnt); - if (x_signbit) { /* negative limit -- switch signs and roles */ - T tmp = apt; - apt = -ant; - ant = -tmp; - - tmp = bpt; - bpt = -bnt; - bnt = -tmp; - } -} - -} // namespace xsf diff --git a/scipy/special/xsf/alg.h b/scipy/special/xsf/alg.h deleted file mode 100644 index 28ea0ef5a52a..000000000000 --- a/scipy/special/xsf/alg.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "cephes/cbrt.h" - -namespace xsf { - -XSF_HOST_DEVICE inline double cbrt(double x) { return cephes::detail::cbrt(x); } - -XSF_HOST_DEVICE inline float cbrt(float x) { return cbrt(static_cast(x)); } - -} // namespace xsf diff --git a/scipy/special/xsf/amos.h b/scipy/special/xsf/amos.h deleted file mode 100644 index 4de81208bb99..000000000000 --- a/scipy/special/xsf/amos.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include "amos/amos.h" -#include "error.h" - -namespace xsf { - -// -// Return sf_error equivalents for AMOS ierr values. -// 'ierr' refers to the last parameter of the AMOS functions -// airy(), besh(), besi(), besj(), besk(), besy(), and biry(). -// -inline sf_error_t ierr_to_sferr(int nz, int ierr) { - if (nz != 0) { - return SF_ERROR_UNDERFLOW; - } - - switch (ierr) { - case 1: - return SF_ERROR_DOMAIN; - case 2: - return SF_ERROR_OVERFLOW; - case 3: - return SF_ERROR_LOSS; - case 4: - return SF_ERROR_NO_RESULT; - case 5: /* Algorithm termination condition not met */ - return SF_ERROR_NO_RESULT; - case 6: /* Memory allocation failed */ - return SF_ERROR_MEMORY; - } - - return SF_ERROR_OK; -} -} // namespace xsf diff --git a/scipy/special/xsf/amos/amos.h b/scipy/special/xsf/amos/amos.h deleted file mode 100644 index fad49b25c6a0..000000000000 --- a/scipy/special/xsf/amos/amos.h +++ /dev/null @@ -1,6245 +0,0 @@ -/* - * - * This file is a C++ translation of the Fortran code written by - * D.E. Amos with the following original description: - * - * - * A Portable Package for Bessel Functions of a Complex Argument - * and Nonnegative Order - * - * This algorithm is a package of subroutines for computing Bessel - * functions and Airy functions. The routines are updated - * versions of those routines found in TOMS algorithm 644. - * - * Disclaimer: - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * ISSUED BY SANDIA LABORATORIES, - * A PRIME CONTRACTOR TO THE - * UNITED STATES DEPARTMENT OF ENERGY - * * * * * * * * * * * * * * NOTICE * * * * * * * * * * * * * * * - * THIS REPORT WAS PREPARED AS AN ACCOUNT OF WORK SPONSORED BY THE - * UNITED STATES GOVERNMENT. NEITHER THE UNITED STATES NOR THE - * UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR - * EMPLOYEES, NOR ANY OF THEIR CONTRACTORS, SUBCONTRACTORS, OR THEIR - * EMPLOYEES, MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR ASSUMES ANY - * LEGAL LIABILITY OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS - * OR USEFULNESS OF ANY INFORMATION, APPARATUS, PRODUCT OR PROCESS - * DISCLOSED, OR REPRESENTS THAT ITS USE WOULD NOT INFRINGE - * PRIVATELY OWNED RIGHTS. - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * THIS CODE HAS BEEN APPROVED FOR UNLIMITED RELEASE. - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * - * - * - * The original Fortran code can be found at https://www.netlib.org/amos/ - * - * References: - * - * [1]: Abramowitz, M. and Stegun, I. A., Handbook of Mathematical - * Functions, NBS Applied Math Series 55, U.S. Dept. of Commerce, - * Washington, D.C., 1955 - * - * [2]: Amos, D. E., Algorithm 644, A Portable Package For Bessel - * Functions of a Complex Argument and Nonnegative Order, ACM - * Transactions on Mathematical Software, Vol. 12, No. 3, - * September 1986, Pages 265-273, DOI:10.1145/7921.214331 - * - * [3]: Amos, D. E., Remark on Algorithm 644, ACM Transactions on - * Mathematical Software, Vol. 16, No. 4, December 1990, Page - * 404, DOI:10.1145/98267.98299 - * - * [4]: Amos, D. E., A remark on Algorithm 644: "A portable package - * for Bessel functions of a complex argument and nonnegative order", - * ACM Transactions on Mathematical Software, Vol. 21, No. 4, - * December 1995, Pages 388-393, DOI:10.1145/212066.212078 - * - * [5]: Cody, W. J., Algorithm 665, MACHAR: A Subroutine to - * Dynamically Determine Machine Parameters, ACM Transactions on - * Mathematical Software, Vol. 14, No. 4, December 1988, Pages - * 303-311, DOI:10.1145/50063.51907 - * - */ - -/* - * Copyright (C) 2024 SciPy developers - * - * 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. Names of the SciPy Developers may not 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 COPYRIGHT HOLDERS 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. - */ -#pragma once - -#include - -#include -#include -#include // unique_ptr - -namespace xsf { -namespace amos { - -int acai(std::complex, double, int, int, int, std::complex *, double, double, double, double); -int acon(std::complex, double, int, int, int, std::complex *, double, double, double, double, double); -int asyi(std::complex, double, int, int, std::complex *, double, double, double, double); -int binu(std::complex, double fnu, int, int, std::complex *, double, double, double, double, double); -int bknu(std::complex, double, int, int, std::complex *, double, double, double); -int buni(std::complex, double, int, int, std::complex *, int, int *, double, double, double, double); -int bunk(std::complex, double, int, int, int, std::complex *, double, double, double); -double gamln(double); -int kscl(std::complex, double, int, std::complex *, std::complex, double *, double, double); -int mlri(std::complex, double, int, int, std::complex *, double); -void rati(std::complex, double, int, std::complex *, double); -int seri(std::complex, double, int, int, std::complex *, double, double, double); -int s1s2(std::complex, std::complex *, std::complex *, double, double, int *); -int uchk(std::complex, double, double); -void unhj(std::complex, double, int, double, std::complex *, std::complex *, std::complex *, std::complex *, std::complex *, std::complex *); -void uni1(std::complex, double, int, int, std::complex *, int *, int *, double, double, double, double); -void uni2(std::complex, double, int, int, std::complex *, int *, int *, double, double, double, double); -void unik(std::complex, double, int, int, double, int *, std::complex *, std::complex *, std::complex *, std::complex *, std::complex *); -int unk1(std::complex, double, int, int, int, std::complex *, double, double, double); -int unk2(std::complex, double, int, int, int, std::complex *, double, double, double); -int uoik(std::complex, double, int, int, int, std::complex *, double, double, double); -int wrsk(std::complex, double, int, int, std::complex *, std::complex *, double, double, double); - - -constexpr double d1mach[5] = { - 2.2250738585072014e-308, /* np.finfo(np.float64).tiny */ - 1.7976931348623157e+308, /* np.finfo(np.float64).max */ - 1.1102230246251565e-16, /* 0.5 * np.finfo(np.float64).eps */ - 2.220446049250313e-16, /* np.finfo(np.float64).eps */ - 0.3010299956639812 /* np.log10(2) */ -}; - -constexpr double i1mach[16] = { - 5, /* standard input */ - 6, /* standard output */ - 7, /* standard punch */ - 0, /* standard error */ - 32, /* bits per integer */ - 4, /* sizeof(int); */ - 2, /* base for integers */ - 31, /* digits of integer base */ - 2147483647, /* LONG MAX 2**31 - 1 */ - 2, /* FLT_RADIX; */ - 24, /* FLT_MANT_DIG; */ - -126, /* FLT_MIN_EXP; */ - 128, /* FLT_MAX_EXP; */ - 53, /* DBL_MANT_DIG; */ - -1021, /* DBL_MIN_EXP; */ - 1024 /* DBL_MAX_EXP; */ -}; - -constexpr double zunhj_ar[14] = { - 1.00000000000000000e+00, 1.04166666666666667e-01, 8.35503472222222222e-02, 1.28226574556327160e-01, // 0 - 2.91849026464140464e-01, 8.81627267443757652e-01, 3.32140828186276754e+00, 1.49957629868625547e+01, // 4 - 7.89230130115865181e+01, 4.74451538868264323e+02, 3.20749009089066193e+03, 2.40865496408740049e+04, // 8 - 1.98923119169509794e+05, 1.79190200777534383e+06 // 12 -}; - -constexpr double zunhj_br[14] = { - 1.00000000000000000e+00, -1.45833333333333333e-01, -9.87413194444444444e-02, -1.43312053915895062e-01, // 0 - -3.17227202678413548e-01, -9.42429147957120249e-01, -3.51120304082635426e+00, -1.57272636203680451e+01, // 4 - -8.22814390971859444e+01, -4.92355370523670524e+02, -3.31621856854797251e+03, -2.48276742452085896e+04, // 8 - -2.04526587315129788e+05, -1.83844491706820990e+06 // 12 -}; - -constexpr double zunhj_c[105] = { - 1.00000000000000000e+00, -2.08333333333333333e-01, 1.25000000000000000e-01, 3.34201388888888889e-01, // 0 - -4.01041666666666667e-01, 7.03125000000000000e-02, -1.02581259645061728e+00, 1.84646267361111111e+00, // 4 - -8.91210937500000000e-01, 7.32421875000000000e-02, 4.66958442342624743e+00, -1.12070026162229938e+01, // 8 - 8.78912353515625000e+00, -2.36408691406250000e+00, 1.12152099609375000e-01, -2.82120725582002449e+01, // 12 - 8.46362176746007346e+01, -9.18182415432400174e+01, 4.25349987453884549e+01, -7.36879435947963170e+00, // 16 - 2.27108001708984375e-01, 2.12570130039217123e+02, -7.65252468141181642e+02, 1.05999045252799988e+03, // 20 - -6.99579627376132541e+02, 2.18190511744211590e+02, -2.64914304869515555e+01, 5.72501420974731445e-01, // 24 - -1.91945766231840700e+03, 8.06172218173730938e+03, -1.35865500064341374e+04, 1.16553933368645332e+04, // 28 - -5.30564697861340311e+03, 1.20090291321635246e+03, -1.08090919788394656e+02, 1.72772750258445740e+00, // 32 - 2.02042913309661486e+04, -9.69805983886375135e+04, 1.92547001232531532e+05, -2.03400177280415534e+05, // 36 - 1.22200464983017460e+05, -4.11926549688975513e+04, 7.10951430248936372e+03, -4.93915304773088012e+02, // 40 - 6.07404200127348304e+00, -2.42919187900551333e+05, 1.31176361466297720e+06, -2.99801591853810675e+06, // 44 - 3.76327129765640400e+06, -2.81356322658653411e+06, 1.26836527332162478e+06, -3.31645172484563578e+05, // 48 - 4.52187689813627263e+04, -2.49983048181120962e+03, 2.43805296995560639e+01, 3.28446985307203782e+06, // 52 - -1.97068191184322269e+07, 5.09526024926646422e+07, -7.41051482115326577e+07, 6.63445122747290267e+07, // 56 - -3.75671766607633513e+07, 1.32887671664218183e+07, -2.78561812808645469e+06, 3.08186404612662398e+05, // 60 - -1.38860897537170405e+04, 1.10017140269246738e+02, -4.93292536645099620e+07, 3.25573074185765749e+08, // 64 - -9.39462359681578403e+08, 1.55359689957058006e+09, -1.62108055210833708e+09, 1.10684281682301447e+09, // 68 - -4.95889784275030309e+08, 1.42062907797533095e+08, -2.44740627257387285e+07, 2.24376817792244943e+06, // 72 - -8.40054336030240853e+04, 5.51335896122020586e+02, 8.14789096118312115e+08, -5.86648149205184723e+09, // 76 - 1.86882075092958249e+10, -3.46320433881587779e+10, 4.12801855797539740e+10, -3.30265997498007231e+10, // 80 - 1.79542137311556001e+10, -6.56329379261928433e+09, 1.55927986487925751e+09, -2.25105661889415278e+08, // 84 - 1.73951075539781645e+07, -5.49842327572288687e+05, 3.03809051092238427e+03, -1.46792612476956167e+10, // 88 - 1.14498237732025810e+11, -3.99096175224466498e+11, 8.19218669548577329e+11, -1.09837515608122331e+12, // 92 - 1.00815810686538209e+12, -6.45364869245376503e+11, 2.87900649906150589e+11, -8.78670721780232657e+10, // 96 - 1.76347306068349694e+10, -2.16716498322379509e+09, 1.43157876718888981e+08, -3.87183344257261262e+06, // 100 - 1.82577554742931747e+04 // 104 -}; - -constexpr double zunhj_alfa[180] = { - -4.44444444444444444e-03, -9.22077922077922078e-04, -8.84892884892884893e-05, 1.65927687832449737e-04, // 0 - 2.46691372741792910e-04, 2.65995589346254780e-04, 2.61824297061500945e-04, 2.48730437344655609e-04, // 4 - 2.32721040083232098e-04, 2.16362485712365082e-04, 2.00738858762752355e-04, 1.86267636637545172e-04, // 8 - 1.73060775917876493e-04, 1.61091705929015752e-04, 1.50274774160908134e-04, 1.40503497391269794e-04, // 12 - 1.31668816545922806e-04, 1.23667445598253261e-04, 1.16405271474737902e-04, 1.09798298372713369e-04, // 16 - 1.03772410422992823e-04, 9.82626078369363448e-05, 9.32120517249503256e-05, 8.85710852478711718e-05, // 20 - 8.42963105715700223e-05, 8.03497548407791151e-05, 7.66981345359207388e-05, 7.33122157481777809e-05, // 24 - 7.01662625163141333e-05, 6.72375633790160292e-05, 6.93735541354588974e-04, 2.32241745182921654e-04, // 28 - -1.41986273556691197e-05, -1.16444931672048640e-04, -1.50803558053048762e-04, -1.55121924918096223e-04, // 32 - -1.46809756646465549e-04, -1.33815503867491367e-04, -1.19744975684254051e-04, -1.06184319207974020e-04, // 36 - -9.37699549891194492e-05, -8.26923045588193274e-05, -7.29374348155221211e-05, -6.44042357721016283e-05, // 40 - -5.69611566009369048e-05, -5.04731044303561628e-05, -4.48134868008882786e-05, -3.98688727717598864e-05, // 44 - -3.55400532972042498e-05, -3.17414256609022480e-05, -2.83996793904174811e-05, -2.54522720634870566e-05, // 48 - -2.28459297164724555e-05, -2.05352753106480604e-05, -1.84816217627666085e-05, -1.66519330021393806e-05, // 52 - -1.50179412980119482e-05, -1.35554031379040526e-05, -1.22434746473858131e-05, -1.10641884811308169e-05, // 56 - -3.54211971457743841e-04, -1.56161263945159416e-04, 3.04465503594936410e-05, 1.30198655773242693e-04, // 60 - 1.67471106699712269e-04, 1.70222587683592569e-04, 1.56501427608594704e-04, 1.36339170977445120e-04, // 64 - 1.14886692029825128e-04, 9.45869093034688111e-05, 7.64498419250898258e-05, 6.07570334965197354e-05, // 68 - 4.74394299290508799e-05, 3.62757512005344297e-05, 2.69939714979224901e-05, 1.93210938247939253e-05, // 72 - 1.30056674793963203e-05, 7.82620866744496661e-06, 3.59257485819351583e-06, 1.44040049814251817e-07, // 76 - -2.65396769697939116e-06, -4.91346867098485910e-06, -6.72739296091248287e-06, -8.17269379678657923e-06, // 80 - -9.31304715093561232e-06, -1.02011418798016441e-05, -1.08805962510592880e-05, -1.13875481509603555e-05, // 84 - -1.17519675674556414e-05, -1.19987364870944141e-05, 3.78194199201772914e-04, 2.02471952761816167e-04, // 88 - -6.37938506318862408e-05, -2.38598230603005903e-04, -3.10916256027361568e-04, -3.13680115247576316e-04, // 92 - -2.78950273791323387e-04, -2.28564082619141374e-04, -1.75245280340846749e-04, -1.25544063060690348e-04, // 96 - -8.22982872820208365e-05, -4.62860730588116458e-05, -1.72334302366962267e-05, 5.60690482304602267e-06, // 100 - 2.31395443148286800e-05, 3.62642745856793957e-05, 4.58006124490188752e-05, 5.24595294959114050e-05, // 104 - 5.68396208545815266e-05, 5.94349820393104052e-05, 6.06478527578421742e-05, 6.08023907788436497e-05, // 108 - 6.01577894539460388e-05, 5.89199657344698500e-05, 5.72515823777593053e-05, 5.52804375585852577e-05, // 112 - 5.31063773802880170e-05, 5.08069302012325706e-05, 4.84418647620094842e-05, 4.60568581607475370e-05, // 116 - -6.91141397288294174e-04, -4.29976633058871912e-04, 1.83067735980039018e-04, 6.60088147542014144e-04, // 120 - 8.75964969951185931e-04, 8.77335235958235514e-04, 7.49369585378990637e-04, 5.63832329756980918e-04, // 124 - 3.68059319971443156e-04, 1.88464535514455599e-04, 3.70663057664904149e-05, -8.28520220232137023e-05, // 128 - -1.72751952869172998e-04, -2.36314873605872983e-04, -2.77966150694906658e-04, -3.02079514155456919e-04, // 132 - -3.12594712643820127e-04, -3.12872558758067163e-04, -3.05678038466324377e-04, -2.93226470614557331e-04, // 136 - -2.77255655582934777e-04, -2.59103928467031709e-04, -2.39784014396480342e-04, -2.20048260045422848e-04, // 140 - -2.00443911094971498e-04, -1.81358692210970687e-04, -1.63057674478657464e-04, -1.45712672175205844e-04, // 144 - -1.29425421983924587e-04, -1.14245691942445952e-04, 1.92821964248775885e-03, 1.35592576302022234e-03, // 148 - -7.17858090421302995e-04, -2.58084802575270346e-03, -3.49271130826168475e-03, -3.46986299340960628e-03, // 152 - -2.82285233351310182e-03, -1.88103076404891354e-03, -8.89531718383947600e-04, 3.87912102631035228e-06, // 156 - 7.28688540119691412e-04, 1.26566373053457758e-03, 1.62518158372674427e-03, 1.83203153216373172e-03, // 160 - 1.91588388990527909e-03, 1.90588846755546138e-03, 1.82798982421825727e-03, 1.70389506421121530e-03, // 164 - 1.55097127171097686e-03, 1.38261421852276159e-03, 1.20881424230064774e-03, 1.03676532638344962e-03, // 168 - 8.71437918068619115e-04, 7.16080155297701002e-04, 5.72637002558129372e-04, 4.42089819465802277e-04, // 172 - 3.24724948503090564e-04, 2.20342042730246599e-04, 1.28412898401353882e-04, 4.82005924552095464e-05 // 176 -}; - -constexpr double zunhj_beta[210] = { - 1.79988721413553309e-02, 5.59964911064388073e-03, 2.88501402231132779e-03, 1.80096606761053941e-03, // 0 - 1.24753110589199202e-03, 9.22878876572938311e-04, 7.14430421727287357e-04, 5.71787281789704872e-04, // 4 - 4.69431007606481533e-04, 3.93232835462916638e-04, 3.34818889318297664e-04, 2.88952148495751517e-04, // 8 - 2.52211615549573284e-04, 2.22280580798883327e-04, 1.97541838033062524e-04, 1.76836855019718004e-04, // 12 - 1.59316899661821081e-04, 1.44347930197333986e-04, 1.31448068119965379e-04, 1.20245444949302884e-04, // 16 - 1.10449144504599392e-04, 1.01828770740567258e-04, 9.41998224204237509e-05, 8.74130545753834437e-05, // 20 - 8.13466262162801467e-05, 7.59002269646219339e-05, 7.09906300634153481e-05, 6.65482874842468183e-05, // 24 - 6.25146958969275078e-05, 5.88403394426251749e-05, -1.49282953213429172e-03, -8.78204709546389328e-04, // 28 - -5.02916549572034614e-04, -2.94822138512746025e-04, -1.75463996970782828e-04, -1.04008550460816434e-04, // 32 - -5.96141953046457895e-05, -3.12038929076098340e-05, -1.26089735980230047e-05, -2.42892608575730389e-07, // 36 - 8.05996165414273571e-06, 1.36507009262147391e-05, 1.73964125472926261e-05, 1.98672978842133780e-05, // 40 - 2.14463263790822639e-05, 2.23954659232456514e-05, 2.28967783814712629e-05, 2.30785389811177817e-05, // 44 - 2.30321976080909144e-05, 2.28236073720348722e-05, 2.25005881105292418e-05, 2.20981015361991429e-05, // 48 - 2.16418427448103905e-05, 2.11507649256220843e-05, 2.06388749782170737e-05, 2.01165241997081666e-05, // 52 - 1.95913450141179244e-05, 1.90689367910436740e-05, 1.85533719641636667e-05, 1.80475722259674218e-05, // 56 - 5.52213076721292790e-04, 4.47932581552384646e-04, 2.79520653992020589e-04, 1.52468156198446602e-04, // 60 - 6.93271105657043598e-05, 1.76258683069991397e-05, -1.35744996343269136e-05, -3.17972413350427135e-05, // 64 - -4.18861861696693365e-05, -4.69004889379141029e-05, -4.87665447413787352e-05, -4.87010031186735069e-05, // 68 - -4.74755620890086638e-05, -4.55813058138628452e-05, -4.33309644511266036e-05, -4.09230193157750364e-05, // 72 - -3.84822638603221274e-05, -3.60857167535410501e-05, -3.37793306123367417e-05, -3.15888560772109621e-05, // 76 - -2.95269561750807315e-05, -2.75978914828335759e-05, -2.58006174666883713e-05, -2.41308356761280200e-05, // 80 - -2.25823509518346033e-05, -2.11479656768912971e-05, -1.98200638885294927e-05, -1.85909870801065077e-05, // 84 - -1.74532699844210224e-05, -1.63997823854497997e-05, -4.74617796559959808e-04, -4.77864567147321487e-04, // 88 - -3.20390228067037603e-04, -1.61105016119962282e-04, -4.25778101285435204e-05, 3.44571294294967503e-05, // 92 - 7.97092684075674924e-05, 1.03138236708272200e-04, 1.12466775262204158e-04, 1.13103642108481389e-04, // 96 - 1.08651634848774268e-04, 1.01437951597661973e-04, 9.29298396593363896e-05, 8.40293133016089978e-05, // 100 - 7.52727991349134062e-05, 6.69632521975730872e-05, 5.92564547323194704e-05, 5.22169308826975567e-05, // 104 - 4.58539485165360646e-05, 4.01445513891486808e-05, 3.50481730031328081e-05, 3.05157995034346659e-05, // 108 - 2.64956119950516039e-05, 2.29363633690998152e-05, 1.97893056664021636e-05, 1.70091984636412623e-05, // 112 - 1.45547428261524004e-05, 1.23886640995878413e-05, 1.04775876076583236e-05, 8.79179954978479373e-06, // 116 - 7.36465810572578444e-04, 8.72790805146193976e-04, 6.22614862573135066e-04, 2.85998154194304147e-04, // 120 - 3.84737672879366102e-06, -1.87906003636971558e-04, -2.97603646594554535e-04, -3.45998126832656348e-04, // 124 - -3.53382470916037712e-04, -3.35715635775048757e-04, -3.04321124789039809e-04, -2.66722723047612821e-04, // 128 - -2.27654214122819527e-04, -1.89922611854562356e-04, -1.55058918599093870e-04, -1.23778240761873630e-04, // 132 - -9.62926147717644187e-05, -7.25178327714425337e-05, -5.22070028895633801e-05, -3.50347750511900522e-05, // 136 - -2.06489761035551757e-05, -8.70106096849767054e-06, 1.13698686675100290e-06, 9.16426474122778849e-06, // 140 - 1.56477785428872620e-05, 2.08223629482466847e-05, 2.48923381004595156e-05, 2.80340509574146325e-05, // 144 - 3.03987774629861915e-05, 3.21156731406700616e-05, -1.80182191963885708e-03, -2.43402962938042533e-03, // 148 - -1.83422663549856802e-03, -7.62204596354009765e-04, 2.39079475256927218e-04, 9.49266117176881141e-04, // 152 - 1.34467449701540359e-03, 1.48457495259449178e-03, 1.44732339830617591e-03, 1.30268261285657186e-03, // 156 - 1.10351597375642682e-03, 8.86047440419791759e-04, 6.73073208165665473e-04, 4.77603872856582378e-04, // 160 - 3.05991926358789362e-04, 1.60315694594721630e-04, 4.00749555270613286e-05, -5.66607461635251611e-05, // 164 - -1.32506186772982638e-04, -1.90296187989614057e-04, -2.32811450376937408e-04, -2.62628811464668841e-04, // 168 - -2.82050469867598672e-04, -2.93081563192861167e-04, -2.97435962176316616e-04, -2.96557334239348078e-04, // 172 - -2.91647363312090861e-04, -2.83696203837734166e-04, -2.73512317095673346e-04, -2.61750155806768580e-04, // 176 - 6.38585891212050914e-03, 9.62374215806377941e-03, 7.61878061207001043e-03, 2.83219055545628054e-03, // 180 - -2.09841352012720090e-03, -5.73826764216626498e-03, -7.70804244495414620e-03, -8.21011692264844401e-03, // 184 - -7.65824520346905413e-03, -6.47209729391045177e-03, -4.99132412004966473e-03, -3.45612289713133280e-03, // 188 - -2.01785580014170775e-03, -7.59430686781961401e-04, 2.84173631523859138e-04, 1.10891667586337403e-03, // 192 - 1.72901493872728771e-03, 2.16812590802684701e-03, 2.45357710494539735e-03, 2.61281821058334862e-03, // 196 - 2.67141039656276912e-03, 2.65203073395980430e-03, 2.57411652877287315e-03, 2.45389126236094427e-03, // 200 - 2.30460058071795494e-03, 2.13684837686712662e-03, 1.95896528478870911e-03, 1.77737008679454412e-03, // 204 - 1.59690280765839059e-03, 1.42111975664438546e-03 // 208 -}; - -constexpr double zunhj_gama[30] = { - 6.29960524947436582e-01, 2.51984209978974633e-01, 1.54790300415655846e-01, 1.10713062416159013e-01, // 0 - 8.57309395527394825e-02, 6.97161316958684292e-02, 5.86085671893713576e-02, 5.04698873536310685e-02, // 4 - 4.42600580689154809e-02, 3.93720661543509966e-02, 3.54283195924455368e-02, 3.21818857502098231e-02, // 8 - 2.94646240791157679e-02, 2.71581677112934479e-02, 2.51768272973861779e-02, 2.34570755306078891e-02, // 12 - 2.19508390134907203e-02, 2.06210828235646240e-02, 1.94388240897880846e-02, 1.83810633800683158e-02, // 16 - 1.74293213231963172e-02, 1.65685837786612353e-02, 1.57865285987918445e-02, 1.50729501494095594e-02, // 20 - 1.44193250839954639e-02, 1.38184805735341786e-02, 1.32643378994276568e-02, 1.27517121970498651e-02, // 24 - 1.22761545318762767e-02, 1.18338262398482403e-02 // 28 -}; - -constexpr double zunik_c[120] = { - 1.00000000000000000e+00, -2.08333333333333333e-01, 1.25000000000000000e-01, 3.34201388888888889e-01, // 0 - -4.01041666666666667e-01, 7.03125000000000000e-02, -1.02581259645061728e+00, 1.84646267361111111e+00, // 4 - -8.91210937500000000e-01, 7.32421875000000000e-02, 4.66958442342624743e+00, -1.12070026162229938e+01, // 8 - 8.78912353515625000e+00, -2.36408691406250000e+00, 1.12152099609375000e-01, -2.82120725582002449e+01, // 12 - 8.46362176746007346e+01, -9.18182415432400174e+01, 4.25349987453884549e+01, -7.36879435947963170e+00, // 16 - 2.27108001708984375e-01, 2.12570130039217123e+02, -7.65252468141181642e+02, 1.05999045252799988e+03, // 20 - -6.99579627376132541e+02, 2.18190511744211590e+02, -2.64914304869515555e+01, 5.72501420974731445e-01, // 24 - -1.91945766231840700e+03, 8.06172218173730938e+03, -1.35865500064341374e+04, 1.16553933368645332e+04, // 28 - -5.30564697861340311e+03, 1.20090291321635246e+03, -1.08090919788394656e+02, 1.72772750258445740e+00, // 32 - 2.02042913309661486e+04, -9.69805983886375135e+04, 1.92547001232531532e+05, -2.03400177280415534e+05, // 36 - 1.22200464983017460e+05, -4.11926549688975513e+04, 7.10951430248936372e+03, -4.93915304773088012e+02, // 40 - 6.07404200127348304e+00, -2.42919187900551333e+05, 1.31176361466297720e+06, -2.99801591853810675e+06, // 44 - 3.76327129765640400e+06, -2.81356322658653411e+06, 1.26836527332162478e+06, -3.31645172484563578e+05, // 48 - 4.52187689813627263e+04, -2.49983048181120962e+03, 2.43805296995560639e+01, 3.28446985307203782e+06, // 52 - -1.97068191184322269e+07, 5.09526024926646422e+07, -7.41051482115326577e+07, 6.63445122747290267e+07, // 56 - -3.75671766607633513e+07, 1.32887671664218183e+07, -2.78561812808645469e+06, 3.08186404612662398e+05, // 60 - -1.38860897537170405e+04, 1.10017140269246738e+02, -4.93292536645099620e+07, 3.25573074185765749e+08, // 64 - -9.39462359681578403e+08, 1.55359689957058006e+09, -1.62108055210833708e+09, 1.10684281682301447e+09, // 68 - -4.95889784275030309e+08, 1.42062907797533095e+08, -2.44740627257387285e+07, 2.24376817792244943e+06, // 72 - -8.40054336030240853e+04, 5.51335896122020586e+02, 8.14789096118312115e+08, -5.86648149205184723e+09, // 76 - 1.86882075092958249e+10, -3.46320433881587779e+10, 4.12801855797539740e+10, -3.30265997498007231e+10, // 80 - 1.79542137311556001e+10, -6.56329379261928433e+09, 1.55927986487925751e+09, -2.25105661889415278e+08, // 84 - 1.73951075539781645e+07, -5.49842327572288687e+05, 3.03809051092238427e+03, -1.46792612476956167e+10, // 88 - 1.14498237732025810e+11, -3.99096175224466498e+11, 8.19218669548577329e+11, -1.09837515608122331e+12, // 92 - 1.00815810686538209e+12, -6.45364869245376503e+11, 2.87900649906150589e+11, -8.78670721780232657e+10, // 96 - 1.76347306068349694e+10, -2.16716498322379509e+09, 1.43157876718888981e+08, -3.87183344257261262e+06, // 100 - 1.82577554742931747e+04, 2.86464035717679043e+11, -2.40629790002850396e+12, 9.10934118523989896e+12, // 104 - -2.05168994109344374e+13, 3.05651255199353206e+13, -3.16670885847851584e+13, 2.33483640445818409e+13, // 108 - -1.23204913055982872e+13, 4.61272578084913197e+12, -1.19655288019618160e+12, 2.05914503232410016e+11, // 112 - -2.18229277575292237e+10, 1.24700929351271032e+09, -2.91883881222208134e+07, 1.18838426256783253e+05 // 116 -}; - -constexpr double dgamln_gln[100] = { - 0.00000000000000000e+00, 0.00000000000000000e+00, 6.93147180559945309e-01, 1.79175946922805500e+00, // 0 - 3.17805383034794562e+00, 4.78749174278204599e+00, 6.57925121201010100e+00, 8.52516136106541430e+00, // 4 - 1.06046029027452502e+01, 1.28018274800814696e+01, 1.51044125730755153e+01, 1.75023078458738858e+01, // 8 - 1.99872144956618861e+01, 2.25521638531234229e+01, 2.51912211827386815e+01, 2.78992713838408916e+01, // 12 - 3.06718601060806728e+01, 3.35050734501368889e+01, 3.63954452080330536e+01, 3.93398841871994940e+01, // 16 - 4.23356164607534850e+01, 4.53801388984769080e+01, 4.84711813518352239e+01, 5.16066755677643736e+01, // 20 - 5.47847293981123192e+01, 5.80036052229805199e+01, 6.12617017610020020e+01, 6.45575386270063311e+01, // 24 - 6.78897431371815350e+01, 7.12570389671680090e+01, 7.46582363488301644e+01, 7.80922235533153106e+01, // 28 - 8.15579594561150372e+01, 8.50544670175815174e+01, 8.85808275421976788e+01, 9.21361756036870925e+01, // 32 - 9.57196945421432025e+01, 9.93306124547874269e+01, 1.02968198614513813e+02, 1.06631760260643459e+02, // 36 - 1.10320639714757395e+02, 1.14034211781461703e+02, 1.17771881399745072e+02, 1.21533081515438634e+02, // 40 - 1.25317271149356895e+02, 1.29123933639127215e+02, 1.32952575035616310e+02, 1.36802722637326368e+02, // 44 - 1.40673923648234259e+02, 1.44565743946344886e+02, 1.48477766951773032e+02, 1.52409592584497358e+02, // 48 - 1.56360836303078785e+02, 1.60331128216630907e+02, 1.64320112263195181e+02, 1.68327445448427652e+02, // 52 - 1.72352797139162802e+02, 1.76395848406997352e+02, 1.80456291417543771e+02, 1.84533828861449491e+02, // 56 - 1.88628173423671591e+02, 1.92739047287844902e+02, 1.96866181672889994e+02, 2.01009316399281527e+02, // 60 - 2.05168199482641199e+02, 2.09342586752536836e+02, 2.13532241494563261e+02, 2.17736934113954227e+02, // 64 - 2.21956441819130334e+02, 2.26190548323727593e+02, 2.30439043565776952e+02, 2.34701723442818268e+02, // 68 - 2.38978389561834323e+02, 2.43268849002982714e+02, 2.47572914096186884e+02, 2.51890402209723194e+02, // 72 - 2.56221135550009525e+02, 2.60564940971863209e+02, 2.64921649798552801e+02, 2.69291097651019823e+02, // 76 - 2.73673124285693704e+02, 2.78067573440366143e+02, 2.82474292687630396e+02, 2.86893133295426994e+02, // 80 - 2.91323950094270308e+02, 2.95766601350760624e+02, 3.00220948647014132e+02, 3.04686856765668715e+02, // 84 - 3.09164193580146922e+02, 3.13652829949879062e+02, 3.18152639620209327e+02, 3.22663499126726177e+02, // 88 - 3.27185287703775217e+02, 3.31717887196928473e+02, 3.36261181979198477e+02, 3.40815058870799018e+02, // 92 - 3.45379407062266854e+02, 3.49954118040770237e+02, 3.54539085519440809e+02, 3.59134205369575399e+02 // 96 -}; - -constexpr double dgamln_cf[22] = { - 8.33333333333333333e-02, -2.77777777777777778e-03, 7.93650793650793651e-04, -5.95238095238095238e-04, // 0 - 8.41750841750841751e-04, -1.91752691752691753e-03, 6.41025641025641026e-03, -2.95506535947712418e-02, // 4 - 1.79644372368830573e-01, -1.39243221690590112e+00, 1.34028640441683920e+01, -1.56848284626002017e+02, // 8 - 2.19310333333333333e+03, -3.61087712537249894e+04, 6.91472268851313067e+05, -1.52382215394074162e+07, // 12 - 3.82900751391414141e+08, -1.08822660357843911e+10, 3.47320283765002252e+11, -1.23696021422692745e+13, // 16 - 4.88788064793079335e+14, -2.13203339609193739e+16 // 20 -}; - - -inline int acai( - std::complex z, - double fnu, - int kode, - int mr, - int n, - std::complex *y, - double rl, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZACAI - //***REFER TO ZAIRY - // - // ZACAI APPLIES THE ANALYTIC CONTINUATION FORMULA - // - // K(FNU,ZN*EXP(MP))=K(FNU,ZN)*EXP(-MP*FNU) - MP*I(FNU,ZN) - // MP=PI*MR*std::complex(0.0,1.0) - // - // TO CONTINUE THE K FUNCTION FROM THE RIGHT HALF TO THE LEFT - // HALF Z PLANE FOR USE WITH ZAIRY WHERE FNU=1/3 OR 2/3 AND N=1. - // ZACAI IS THE SAME AS ZACON WITH THE PARTS FOR LARGER ORDERS AND - // RECURRENCE REMOVED. A RECURSIVE CALL TO ZACON CAN RESULT IF ZACON - // IS CALLED FROM ZAIRY. - // - //***ROUTINES CALLED ZASYI,ZBKNU,ZMLRI,ZSERI,ZS1S2,D1MACH,AZABS - //***END PROLOGUE ZACAI - - std::complex csgn, cspn, c1, c2, zn, cy[2]; - double arg, ascle, az, cpn, dfnu, fmr, sgn, spn, yy; - int inu, iuf, nn, nw; - double pi = 3.14159265358979324; - int nz = 0; - zn = -z; - az = std::abs(z); - nn = n; - dfnu = fnu + (n-1); - if ((az > 2.0) && (az*az*0.25 > dfnu+1.0)) { - /* 20 */ - if (az >= rl) { - // - // ASYMPTOTIC EXPANSION FOR LARGE Z FOR THE I FUNCTION - // - nw = asyi(zn, fnu, kode, nn, y, rl, tol, elim, alim); - } else { - // - // MILLER ALGORITHM NORMALIZED BY THE SERIES FOR THE I FUNCTION - // - nw = mlri(zn, fnu, kode, nn, y, tol); - } - if (nw < 0) { - nz = -1; - if (nw == -2) { nz = -2; } - return nz; - } - } else{ - // - // POWER SERIES FOR THE I FUNCTION - // - seri(zn, fnu, kode, nn, y, tol, elim, alim); - } - /* 40 */ - // - // ANALYTIC CONTINUATION TO THE LEFT HALF PLANE FOR THE K FUNCTION - // - nw = bknu(zn, fnu, kode, 1, &cy[0], tol, elim, alim); - if (nw != 0) { - nz = -1; - if (nw == -2) { nz = -2; } - return nz; - } - fmr = mr; - sgn = (fmr < 0.0 ? pi : -pi); - csgn = std::complex(0.0, sgn); - if (kode != 1) { - yy = -std::imag(zn); - cpn = cos(yy); - spn = sin(yy); - csgn *= std::complex(cpn, spn); - } - // - // CALCULATE CSPN=EXP(FNU*PI*I) TO MINIMIZE LOSSES OF SIGNIFICANCE - // WHEN FNU IS LARGE - // - inu = (int)fnu; - arg = (fnu - inu)*sgn; - cpn = cos(arg); - spn = sin(arg); - cspn = std::complex(cpn, spn); - if (inu % 2 == 1) { cspn = -cspn; } - c1 = cy[0]; - c2 = y[0]; - if (kode != 1) { - iuf = 0; - ascle = 1e3 * d1mach[0] / tol; - nw = s1s2(zn, &c1, &c2, ascle, alim, &iuf); - nz += nw; - } - y[0] = cspn*c1 + csgn*c2; - return nz; -} - - -inline int acon( - std::complex z, - double fnu, - int kode, - int mr, - int n, - std::complex *y, - double rl, - double fnul, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZACON - //***REFER TO ZBESK,ZBESH - // - // ZACON APPLIES THE ANALYTIC CONTINUATION FORMULA - // - // K(FNU,ZN*EXP(MP))=K(FNU,ZN)*EXP(-MP*FNU) - MP*I(FNU,ZN) - // MP=PI*MR*std::complex(0.0,1.0) - // - // TO CONTINUE THE K FUNCTION FROM THE RIGHT HALF TO THE LEFT - // HALF Z PLANE - // - //***ROUTINES CALLED ZBINU,ZBKNU,ZS1S2,D1MACH,AZABS,ZMLT - //***END PROLOGUE ZACON - - std::complex ck, cs, cscl, cscr, csgn, cspn, c1, c2, rz, sc1, sc2 = 0.0,\ - st, s1, s2, zn; - double arg, ascle, as2, bscle, c1i, c1m, c1r, fmr, sgn, yy; - int i, inu, iuf, kflag, nn, nw, nz; - double pi = 3.14159265358979324; - std::complex cy[2] = { 0.0 }; - std::complex css[3] = { 0.0 }; - std::complex csr[3] = { 0.0 }; - double bry[3] = { 0.0 }; - - nz = 0; - zn = -z; - nn = n; - nw = binu(zn, fnu, kode, nn, y, rl, fnul, tol, elim, alim); - if (nw >= 0) { - // - // ANALYTIC CONTINUATION TO THE LEFT HALF PLANE FOR THE K FUNCTION - // - nn = (n > 2 ? 2 : n); - nw = bknu(zn, fnu, kode, nn, cy, tol, elim, alim); - if (nw == 0) { - s1 = cy[0]; - fmr = mr; - sgn = ( fmr < 0 ? pi : -pi ); - csgn = std::complex(0.0, sgn); - if (kode != 1) { - yy = -std::imag(zn); - csgn *= std::complex(cos(yy), sin(yy)); - } - inu = (int)fnu; - arg = (fnu - inu)*sgn; - cspn = std::complex(cos(arg), sin(arg)); - if (inu % 2 == 1) { cspn = -cspn; } - iuf = 0; - c1 = s1; - c2 = y[0]; - ascle = 1e3*d1mach[0]/tol; - if (kode != 1) { - nw = s1s2(zn, &c1, &c2, ascle, alim, &iuf); - nz += nw; - sc1 = c1; - } - y[0] = cspn*c1 + csgn*c2; - if (n == 1) { return nz; } - cspn = -cspn; - s2 = cy[1]; - c1 = s2; - c2 = y[1]; - if (kode != 1) { - nw = s1s2(zn, &c1, &c2, ascle, alim, &iuf); - nz += nw; - sc2 = c1; - } - y[1] = cspn*c1 + csgn*c2; - if (n == 2) { return nz; } - cspn = -cspn; - rz = 2.0 / zn; - ck = (fnu + 1.0)*rz; - // - // SCALE NEAR EXPONENT EXTREMES DURING RECURRENCE ON K FUNCTIONS - // - cscl = 1.0 / tol; - cscr = tol; - css[0] = cscl; - css[1] = 1.0; - css[2] = cscr; - csr[0] = cscr; - csr[1] = 1.0; - csr[2] = cscl; - bry[0] = ascle; - bry[1] = 1.0 / ascle; - bry[2] = d1mach[1]; - as2 = std::abs(s2); - kflag = 2; - if (as2 <= bry[0] ) { - kflag = 1; - } else { - if (as2 >= bry[1]) { - kflag = 3; - } - } - bscle = bry[kflag-1]; - s1 *= css[kflag-1]; - s2 *= css[kflag-1]; - cs = csr[kflag-1]; - for (i = 3; i < (n+1); i++) - { - st = s2; - s2 = ck*s2 + s1; - s1 = st; - c1 = s2*cs; - st = c1; - c2 = y[i-1]; - if (kode != 1) { - if (iuf >= 0) { - nw = s1s2(zn, &c1, &c2, ascle, alim, &iuf); - nz += nw; - sc1 = sc2; - sc2 = c1; - if (iuf == 3){ - iuf = -4; - s1 = sc1 * css[kflag-1]; - s2 = sc2 * css[kflag-1]; - st = sc2; - } - } - } - y[i-1] = cspn*c1 + csgn*c2; - ck += rz; - cspn = -cspn; - if (kflag < 3) { - c1r = fabs(std::real(c1)); - c1i = fabs(std::imag(c1)); - c1m = fmax(c1r, c1i); - if (c1m > bscle) { - kflag += 1; - bscle = bry[kflag-1]; - s1 *= cs; - s2 = st; - s1 *= css[kflag-1]; - s2 *= css[kflag-1]; - cs = csr[kflag-1]; - } - } - } - return nz; - } - } - nz = -1; - if (nw == -2) { nz = -2; } - return nz; -} - - -inline std::complex airy( - std::complex z, - int id, - int kode, - int *nz, - int *ierr -) { - - //***BEGIN PROLOGUE ZAIRY - //***DATE WRITTEN 830501 (YYMMDD) - //***REVISION DATE 890801 (YYMMDD) - //***CATEGORY NO. B5K - //***KEYWORDS AIRY FUNCTION,BESSEL FUNCTIONS OF ORDER ONE THIRD - //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES - //***PURPOSE TO COMPUTE AIRY FUNCTIONS AI(Z) AND DAI(Z) FOR COMPLEX Z - //***DESCRIPTION - // - // ***A DOUBLE PRECISION ROUTINE*** - // ON KODE=1, ZAIRY COMPUTES THE COMPLEX AIRY FUNCTION AI(Z) OR - // ITS DERIVATIVE DAI(Z)/DZ ON ID=0 OR ID=1 RESPECTIVELY. ON - // KODE=2, A SCALING OPTION CEXP(ZTA)*AI(Z) OR CEXP(ZTA)* - // DAI(Z)/DZ IS PROVIDED TO REMOVE THE EXPONENTIAL DECAY IN - // -PI/3.LT.ARG(Z).LT.PI/3 AND THE EXPONENTIAL GROWTH IN - // PI/3.LT.ABS(ARG(Z)).LT.PI WHERE ZTA=(2/3)*Z*CSQRT(Z). - // - // WHILE THE AIRY FUNCTIONS AI(Z) AND DAI(Z)/DZ ARE ANALYTIC IN - // THE WHOLE Z PLANE, THE CORRESPONDING SCALED FUNCTIONS DEFINED - // FOR KODE=2 HAVE A CUT ALONG THE NEGATIVE REAL AXIS. - // DEFINITIONS AND NOTATION ARE FOUND IN THE NBS HANDBOOK OF - // MATHEMATICAL FUNCTIONS (REF. 1). - // - // INPUT ZR,ZI ARE DOUBLE PRECISION - // ZR,ZI - Z=std::complex(ZR,ZI) - // ID - ORDER OF DERIVATIVE, ID=0 OR ID=1 - // KODE - A PARAMETER TO INDICATE THE SCALING OPTION - // KODE= 1 RETURNS - // AI=AI(Z) ON ID=0 OR - // AI=DAI(Z)/DZ ON ID=1 - // = 2 RETURNS - // AI=CEXP(ZTA)*AI(Z) ON ID=0 OR - // AI=CEXP(ZTA)*DAI(Z)/DZ ON ID=1 WHERE - // ZTA=(2/3)*Z*CSQRT(Z) - // - // OUTPUT AIR,AII ARE DOUBLE PRECISION - // AIR,AII- COMPLEX ANSWER DEPENDING ON THE CHOICES FOR ID AND - // KODE - // NZ - UNDERFLOW INDICATOR - // NZ= 0 , NORMAL RETURN - // NZ= 1 , AI=std::complex(0.0D0,0.0D0) DUE TO UNDERFLOW IN - // -PI/3.LT.ARG(Z).LT.PI/3 ON KODE=1 - // IERR - ERROR FLAG - // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED - // IERR=1, INPUT ERROR - NO COMPUTATION - // IERR=2, OVERFLOW - NO COMPUTATION, REAL(ZTA) - // TOO LARGE ON KODE=1 - // IERR=3, CABS(Z) LARGE - COMPUTATION COMPLETED - // LOSSES OF SIGNIFCANCE BY ARGUMENT REDUCTION - // PRODUCE LESS THAN HALF OF MACHINE ACCURACY - // IERR=4, CABS(Z) TOO LARGE - NO COMPUTATION - // COMPLETE LOSS OF ACCURACY BY ARGUMENT - // REDUCTION - // IERR=5, ERROR - NO COMPUTATION, - // ALGORITHM TERMINATION CONDITION NOT MET - // - //***LONG DESCRIPTION - // - // AI AND DAI ARE COMPUTED FOR CABS(Z).GT.1.0 FROM THE K BESSEL - // FUNCTIONS BY - // - // AI(Z)=C*SQRT(Z)*K(1/3,ZTA) , DAI(Z)=-C*Z*K(2/3,ZTA) - // C=1.0/(PI*SQRT(3.0)) - // ZTA=(2/3)*Z**(3/2) - // - // WITH THE POWER SERIES FOR CABS(Z).LE.1.0. - // - // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- - // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z IS LARGE, LOSSES - // OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. CONSEQUENTLY, IF - // THE MAGNITUDE OF ZETA=(2/3)*Z**1.5 EXCEEDS U1=SQRT(0.5/UR), - // THEN LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR - // FLAG IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS - // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. - // ALSO, IF THE MAGNITUDE OF ZETA IS LARGER THAN U2=0.5/UR, THEN - // ALL SIGNIFICANCE IS LOST AND IERR=4. IN ORDER TO USE THE INT - // FUNCTION, ZETA MUST BE FURTHER RESTRICTED NOT TO EXCEED THE - // LARGEST INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF ZETA - // MUST BE RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, - // AND U3 ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE - // PRECISION ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE - // PRECISION ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMIT- - // ING IN THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT THE MAG- - // NITUDE OF Z CANNOT EXCEED 3.1E+4 IN SINGLE AND 2.1E+6 IN - // DOUBLE PRECISION ARITHMETIC. THIS ALSO MEANS THAT ONE CAN - // EXPECT TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, - // NO DIGITS IN SINGLE PRECISION AND ONLY 7 DIGITS IN DOUBLE - // PRECISION ARITHMETIC. SIMILAR CONSIDERATIONS HOLD FOR OTHER - // MACHINES. - // - // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX - // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT - // ROUNDOFF,1.0E-18) IS THE NOMINAL PRECISION AND 10**S REPRE- - // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE - // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), - // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF - // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY - // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN - // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY - // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER - // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, - // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS - // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER - // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY - // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER - // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE - // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, - // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, - // OR -PI/2+P. - // - //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ - // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF - // COMMERCE, 1955. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983 - // - // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- - // 1018, MAY, 1985 - // - // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. - // MATH. SOFTWARE, 1986 - // - //***ROUTINES CALLED ZACAI,ZBKNU,AZEXP,AZSQRT,I1MACH,D1MACH - //***END PROLOGUE ZAIRY - - std::complex ai, csq, cy[1], s1, s2, trm1, trm2, zta, z3; - double aa, ad, ak, alim, atrm, az, az3, bk, ck, dig, dk, d1, d2,\ - elim, fid, fnu, rl, r1m5, sfac, tol, zi, zr, bb, alaz; - int iflag, k, k1, k2, mr, nn; - double tth = 2. / 3.; - double c1 = 0.35502805388781723926; /* 1/(Gamma(2/3) * 3**(2/3)) */ - double c2 = 0.25881940379280679841; /* 1/(Gamma(1/3) * 3**(1/3)) */ - double coef = 0.18377629847393068317; /* 1 / (sqrt(3) * PI) */ - - *ierr = 0; - *nz = 0; - ai = 0.; - if ((id < 0) || (id > 1)) { *ierr = 1; } - if ((kode < 1) || (kode > 2)) { *ierr = 1; } - if (*ierr != 0) return 0.; - az = std::abs(z); - tol = d1mach[3]; - fid = id; - - if (az <= 1.0) { - // - // POWER SERIES FOR ABS(Z) <= 1. - // - s1 = 1.0; - s2 = 1.0; - if (az < tol) { - aa = 1e3*d1mach[0]; - s1 = 0.; - if (id != 1) { - if (az > aa) { s1 = c2 * z; } - ai = c1 - s1; - return ai; - } - ai = -c2; - aa = sqrt(aa); - if (az > aa) { s1 = z * z * 0.5; } - ai += s1 * c1; - return ai; - } - aa = az*az; - if (aa >= tol/az) { - trm1 = 1.0; - trm2 = 1.0; - atrm = 1.0; - z3 = z*z*z; - az3 = az * aa; - ak = 2.0 + fid; - bk = 3.0 - fid - fid; - ck = 4.0 - fid; - dk = 3.0 + fid + fid; - d1 = ak * dk; - d2 = bk * ck; - ad = (d1 > d2 ? d2 : d1); - ak = 24.0 + 9.0*fid; - bk = 30.0 - 9.0*fid; - for (int k = 1; k < 26; k++) - { - trm1 *= z3/d1; - s1 += trm1; - trm2 *= z3/d2; - s2 += trm2; - atrm *= az3 / ad; - d1 += ak; - d2 += bk; - ad = (d1 > d2 ? d2 : d1); - if (atrm < tol*ad) { break; } - ak += 18.0; - bk += 18.0; - } - } - if (id != 1) { - ai = s1*c1 - z*s2*c2; - if (kode == 1) { return ai; } - zta = z*std::sqrt(z)*tth; - ai *= std::exp(zta); - return ai; - } - ai = -s2*c2; - if (az > tol) { ai += z*z*s1*c1/(1. + fid); } - if (kode == 1) { return ai; } - zta = z*std::sqrt(z)*tth; - return ai*std::exp(zta); - } - // - // CASE FOR ABS(Z) > 1.0 - // - fnu = (1.0 + fid) / 3.0; - // - // SET PARAMETERS RELATED TO MACHINE CONSTANTS. - // TOL IS THE APPROXIMATE UNIT ROUNDOFF LIMITED TO 1.0E-18. - // ELIM IS THE APPROXIMATE EXPONENTIAL OVER- AND UNDERFLOW LIMIT. - // EXP(-ELIM) < EXP(-ALIM)=EXP(-ELIM)/TOL AND - // EXP(ELIM) > EXP(ALIM)=EXP(ELIM)*TOL ARE INTERVALS NEAR - // UNDERFLOW AND OVERFLOW LIMITS WHERE SCALED ARITHMETIC IS DONE. - // RL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC EXPANSION FOR LARGE Z. - // DIG = NUMBER OF BASE 10 DIGITS IN TOL = 10**(-DIG). - // - k1 = i1mach[14]; - k2 = i1mach[15]; - r1m5 = d1mach[4]; - k = ( abs(k1) > abs(k2) ? abs(k2) : abs(k1) ); - elim = 2.303 * (k*r1m5 - 3.0); - k1 = i1mach[13] - 1; - aa = r1m5*k1; - dig = (aa > 18.0 ? 18.0 : aa); - aa *= 2.303; - alim = elim + (-aa > -41.45 ? -aa : -41.45); - rl = 1.2*dig + 3.0; - alaz = log(az); - // - // TEST FOR RANGE - // - aa = 0.5 / tol; - bb = i1mach[8] * 0.5; - aa = (aa > bb ? bb : aa); - aa = pow(aa, tth); - if (az > aa) { - *ierr = 4; - *nz = 0; - return 0.; - } - aa = sqrt(aa); - if (az > aa) { *ierr = 3; } - csq = std::sqrt(z); - zta = z * csq * tth; - // - // RE(ZTA) <= 0 WHEN RE(Z) < 0, ESPECIALLY WHEN IM(Z) IS SMALL - // - iflag = 0; - sfac = 1.0; - zi = std::imag(z); - zr = std::real(z); - ak = std::imag(zta); - if (zr < 0.0) { - bk = std::real(zta); - ck = -fabs(bk); - zta = std::complex(ck, ak); - } - if ((zi == 0.0) && (zr <= 0.0)) { - zta = std::complex(0.0, ak); - } - aa = std::real(zta); - if ((aa < 0.0) || (zr <= 0.0)) { - if (kode != 2) { - // - // OVERFLOW TEST - // - if (aa <= -alim) { - aa = -aa + 0.25*alaz; - iflag = 1; - sfac = tol; - if (aa > elim) { - /* 270 */ - *nz = 0; - *ierr = 2; - return ai; - } - } - } - // - // CBKNU AND CACAI RETURN EXP(ZTA)*K(FNU,ZTA) ON KODE=2 - // - mr = 1; - if (zi < 0.0) { mr = -1; } - nn = acai(zta, fnu, kode, mr, 1, &cy[0], rl, tol, elim, alim); - if (nn < 0) { - if (nn == -1) { - *nz = 1; - return 0.; - } else { - *nz = 0; - *ierr = 5; - return 0.; - } - } - *nz += nn; - } else { - if (kode != 2) { - // - // UNDERFLOW TEST - // - if (aa >= alim) { - aa = -aa - 0.25 * alaz; - iflag = 2; - sfac = 1.0 / tol; - if (aa < -elim) { - *nz = 1; - return 0.; - } - } - } - *nz = bknu(zta, fnu, kode, 1, &cy[0], tol, elim, alim); - } - s1 = cy[0]*coef; - - if (iflag == 0) { - if (id != 1) { - return csq *s1; - } - return (-z*s1); - } - s1 *= sfac; - if (id != 1) { - s1 *= csq; - return (s1/sfac); - } - s1 *= -z; - return (s1/sfac); -} - - -inline int asyi( - std::complex z, - double fnu, - int kode, - int n, - std::complex *y, - double rl, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZASYI - //***REFER TO ZBESI,ZBESK - // - // ZASYI COMPUTES THE I BESSEL FUNCTION FOR REAL(Z).GE.0.0 BY - // MEANS OF THE ASYMPTOTIC EXPANSION FOR LARGE CABS(Z) IN THE - // REGION CABS(Z).GT.MAX(RL,FNU*FNU/2). NZ=0 IS A NORMAL RETURN. - // NZ.LT.0 INDICATES AN OVERFLOW ON KODE=1. - // - //***ROUTINES CALLED D1MACH,AZABS,ZDIV,AZEXP,ZMLT,AZSQRT - //***END PROLOGUE ZASYI - - std::complex ak1, ck, cs1, cs2, cz, dk, ez, p1, rz, s2; - double aa, acz, aez, ak, arg, arm, atol, az, bb, bk, dfnu; - double dnu2, fdn, rtr1, s, sgn, sqk, x, yy; - int ib, il, inu, j, jl, k, koded, m, nn; - double pi = 3.14159265358979324; - double rpi = 0.159154943091895336; /* (1 / pi) */ - int nz = 0; - az = std::abs(z); - x = std::real(z); - arm = 1e3*d1mach[0]; - rtr1 = sqrt(arm); - il = (n > 2 ? 2 : n); - dfnu = fnu + (n - il); - // OVERFLOW TEST - ak1 = std::sqrt(rpi / z); - cz = z; - if (kode == 2) { cz = std::complex(0.0, std::imag(z)); } - acz = std::real(cz); - if (fabs(acz) <= elim) { - dnu2 = dfnu + dfnu; - koded = 1; - if (!((fabs(acz) > alim) && (n > 2))) { - koded = 0; - ak1 *= std::exp(cz); - } - fdn = 0.; - if (dnu2 > rtr1) { fdn = dnu2 * dnu2; } - ez = z * 8.; - // WHEN Z IS IMAGINARY, THE ERROR TEST MUST BE MADE - // RELATIVE TO THE FIRST RECIPROCAL POWER SINCE THIS - // IS THE LEADING TERM OF THE EXPANSION FOR THE - // IMAGINARY PART. - aez = 8. * az; - s = tol / aez; - jl = (int)(rl + rl) + 2; - yy = std::imag(z); - p1 = 0.; - if (yy != 0.) { - inu = (int)fnu; - arg = (fnu - inu) * pi; - inu += n - il; - ak = -sin(arg); - bk = cos(arg); - if (yy < 0.) { bk = -bk; } - p1 = std::complex(ak, bk); - if (inu % 2 == 1) { p1 = -p1; } - } - for (int k = 1; k < (il+1); k++) - { - sqk = fdn - 1.; - atol = s*fabs(sqk); - sgn = 1.; - cs1 = 1.; - cs2 = 1.; - ck = 1.; - ak = 0.; - aa = 1.; - bb = aez; - dk = ez; - j = 1; - for (j = 1; j < (jl+1); j++) - { - ck *= sqk / dk; - cs2 += ck; - sgn = -sgn; - cs1 += ck*sgn; - dk += ez; - aa *= fabs(sqk) / bb; - bb += aez; - ak += 8.; - sqk -= ak; - if (aa <= atol) { break; } - } - if ((j == (jl+1)) && (aa > atol)) { return -2; } - - /* 50 */ - s2 = cs1; - if (x + x < elim) { s2 += p1*cs2*std::exp(-z-z); } - fdn += 8. * dfnu + 4.; - p1 = -p1; - m = n - il + k; - y[m - 1] = s2 * ak1; - } - if (n <= 2) { return nz; } - nn = n; - k = nn - 2; - ak = k; - rz = 2. / z; - ib = 3; - for (int i = ib; i < (nn+1); i++) - { - y[k-1] = (ak + fnu)*rz*y[k] + y[k+1]; - ak -= 1.; - k -=1; - } - if (koded == 0) { return nz; } - ck = std::exp(cz); - for (int i = 0; i < (nn + 1); i++) { y[i] *= ck; } - /* 90 */ - return nz; - } - /* 100 */ - return -1; -} - - -inline int besh( - std::complex z, - double fnu, - int kode, - int m, - int n, - std::complex *cy, - int *ierr -) { - - //***BEGIN PROLOGUE ZBESH - //***DATE WRITTEN 830501 (YYMMDD) - //***REVISION DATE 890801 (YYMMDD) - //***CATEGORY NO. B5K - //***KEYWORDS H-BESSEL FUNCTIONS,BESSEL FUNCTIONS OF COMPLEX ARGUMENT, - // BESSEL FUNCTIONS OF THIRD KIND,HANKEL FUNCTIONS - //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES - //***PURPOSE TO COMPUTE THE H-BESSEL FUNCTIONS OF A COMPLEX ARGUMENT - //***DESCRIPTION - // - // ***A DOUBLE PRECISION ROUTINE*** - // ON KODE=1, ZBESH COMPUTES AN N MEMBER SEQUENCE OF COMPLEX - // HANKEL (BESSEL) FUNCTIONS CY(J)=H(M,FNU+J-1,Z) FOR KINDS M=1 - // OR 2, REAL, NONNEGATIVE ORDERS FNU+J-1, J=1,...,N, AND COMPLEX - // Z.NE.std::complex(0.0,0.0) IN THE CUT PLANE -PI.LT.ARG(Z).LE.PI. - // ON KODE=2, ZBESH RETURNS THE SCALED HANKEL FUNCTIONS - // - // CY(I)=EXP(-MM*Z*I)*H(M,FNU+J-1,Z) MM=3-2*M, I**2=-1. - // - // WHICH REMOVES THE EXPONENTIAL BEHAVIOR IN BOTH THE UPPER AND - // LOWER HALF PLANES. DEFINITIONS AND NOTATION ARE FOUND IN THE - // NBS HANDBOOK OF MATHEMATICAL FUNCTIONS (REF. 1). - // - // INPUT ZR,ZI,FNU ARE DOUBLE PRECISION - // ZR,ZI - Z=std::complex(ZR,ZI), Z.NE.std::complex(0.0D0,0.0D0), - // -PT.LT.ARG(Z).LE.PI - // FNU - ORDER OF INITIAL H FUNCTION, FNU.GE.0.0D0 - // KODE - A PARAMETER TO INDICATE THE SCALING OPTION - // KODE= 1 RETURNS - // CY(J)=H(M,FNU+J-1,Z), J=1,...,N - // = 2 RETURNS - // CY(J)=H(M,FNU+J-1,Z)*EXP(-I*Z*(3-2M)) - // J=1,...,N , I**2=-1 - // M - KIND OF HANKEL FUNCTION, M=1 OR 2 - // N - NUMBER OF MEMBERS IN THE SEQUENCE, N.GE.1 - // - // OUTPUT CYR,CYI ARE DOUBLE PRECISION - // CYR,CYI- DOUBLE PRECISION VECTORS WHOSE FIRST N COMPONENTS - // CONTAIN REAL AND IMAGINARY PARTS FOR THE SEQUENCE - // CY(J)=H(M,FNU+J-1,Z) OR - // CY(J)=H(M,FNU+J-1,Z)*EXP(-I*Z*(3-2M)) J=1,...,N - // DEPENDING ON KODE, I**2=-1. - // NZ - NUMBER OF COMPONENTS SET TO ZERO DUE TO UNDERFLOW, - // NZ= 0 , NORMAL RETURN - // NZ.GT.0 , FIRST NZ COMPONENTS OF CY SET TO ZERO DUE - // TO UNDERFLOW, CY(J)=std::complex(0.0D0,0.0D0) - // J=1,...,NZ WHEN Y.GT.0.0 AND M=1 OR - // Y.LT.0.0 AND M=2. FOR THE COMPLMENTARY - // HALF PLANES, NZ STATES ONLY THE NUMBER - // OF UNDERFLOWS. - // IERR - ERROR FLAG - // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED - // IERR=1, INPUT ERROR - NO COMPUTATION - // IERR=2, OVERFLOW - NO COMPUTATION, FNU TOO - // LARGE OR CABS(Z) TOO SMALL OR BOTH - // IERR=3, CABS(Z) OR FNU+N-1 LARGE - COMPUTATION DONE - // BUT LOSSES OF SIGNIFCANCE BY ARGUMENT - // REDUCTION PRODUCE LESS THAN HALF OF MACHINE - // ACCURACY - // IERR=4, CABS(Z) OR FNU+N-1 TOO LARGE - NO COMPUTA- - // TION BECAUSE OF COMPLETE LOSSES OF SIGNIFI- - // CANCE BY ARGUMENT REDUCTION - // IERR=5, ERROR - NO COMPUTATION, - // ALGORITHM TERMINATION CONDITION NOT MET - // - //***LONG DESCRIPTION - // - // THE COMPUTATION IS CARRIED OUT BY THE RELATION - // - // H(M,FNU,Z)=(1/MP)*EXP(-MP*FNU)*K(FNU,Z*EXP(-MP)) - // MP=MM*HPI*I, MM=3-2*M, HPI=PI/2, I**2=-1 - // - // FOR M=1 OR 2 WHERE THE K BESSEL FUNCTION IS COMPUTED FOR THE - // RIGHT HALF PLANE RE(Z).GE.0.0. THE K FUNCTION IS CONTINUED - // TO THE LEFT HALF PLANE BY THE RELATION - // - // K(FNU,Z*EXP(MP)) = EXP(-MP*FNU)*K(FNU,Z)-MP*I(FNU,Z) - // MP=MR*PI*I, MR=+1 OR -1, RE(Z).GT.0, I**2=-1 - // - // WHERE I(FNU,Z) IS THE I BESSEL FUNCTION. - // - // EXPONENTIAL DECAY OF H(M,FNU,Z) OCCURS IN THE UPPER HALF Z - // PLANE FOR M=1 AND THE LOWER HALF Z PLANE FOR M=2. EXPONENTIAL - // GROWTH OCCURS IN THE COMPLEMENTARY HALF PLANES. SCALING - // BY EXP(-MM*Z*I) REMOVES THE EXPONENTIAL BEHAVIOR IN THE - // WHOLE Z PLANE FOR Z TO INFINITY. - // - // FOR NEGATIVE ORDERS,THE FORMULAE - // - // H(1,-FNU,Z) = H(1,FNU,Z)*CEXP( PI*FNU*I) - // H(2,-FNU,Z) = H(2,FNU,Z)*CEXP(-PI*FNU*I) - // I**2=-1 - // - // CAN BE USED. - // - // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- - // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z OR FNU+N-1 IS - // LARGE, LOSSES OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. - // CONSEQUENTLY, IF EITHER ONE EXCEEDS U1=SQRT(0.5/UR), THEN - // LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR FLAG - // IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS - // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. - // IF EITHER IS LARGER THAN U2=0.5/UR, THEN ALL SIGNIFICANCE IS - // LOST AND IERR=4. IN ORDER TO USE THE INT FUNCTION, ARGUMENTS - // MUST BE FURTHER RESTRICTED NOT TO EXCEED THE LARGEST MACHINE - // INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF Z AND FNU+N-1 IS - // RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, AND U3 - // ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE PRECISION - // ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE PRECISION - // ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMITING IN - // THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT ONE CAN EXPECT - // TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, NO DIGITS - // IN SINGLE AND ONLY 7 DIGITS IN DOUBLE PRECISION ARITHMETIC. - // SIMILAR CONSIDERATIONS HOLD FOR OTHER MACHINES. - // - // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX - // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT - // ROUNDOFF,1.0D-18) IS THE NOMINAL PRECISION AND 10**S REPRE- - // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE - // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), - // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF - // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY - // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN - // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY - // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER - // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, - // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS - // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER - // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY - // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER - // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE - // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, - // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, - // OR -PI/2+P. - // - //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ - // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF - // COMMERCE, 1955. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // BY D. E. AMOS, SAND83-0083, MAY, 1983. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983 - // - // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- - // 1018, MAY, 1985 - // - // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. - // MATH. SOFTWARE, 1986 - // - //***ROUTINES CALLED ZACON,ZBKNU,ZBUNK,ZUOIK,AZABS,I1MACH,D1MACH - //***END PROLOGUE ZBESH - - std::complex zn, zt, csgn; - double aa, alim, aln, arg, az, cpn, dig, elim, fmm, fn, fnul, - rhpi, rl, r1m5, sgn, spn, tol, ufl, xn, xx, yn, yy, - bb, ascle, rtol, atol; - int i, inu, inuh, ir, k, k1, k2, mm, mr, nn, nuf, nw, nz; - - double hpi = 1.57079632679489662; /* 0.5 PI */ - - nz = 0; - xx = std::real(z); - yy = std::imag(z); - *ierr = 0; - - if ((xx == 0.0) && (yy == 0.0)) { *ierr = 1; } - if (fnu < 0.0) { *ierr = 1; } - if ((m < 1) || (m > 2)) { *ierr = 1; } - if ((kode < 1) || (kode > 2)) { *ierr = 1; } - if (n < 1) { *ierr = 1; } - if (*ierr != 0) { return nz; } - nn = n; - // - // SET PARAMETERS RELATED TO MACHINE CONSTANTS. - // TOL IS THE APPROXIMATE UNIT ROUNDOFF LIMITED TO 1.0E-18. - // ELIM IS THE APPROXIMATE EXPONENTIAL OVER- AND UNDERFLOW LIMIT. - // EXP(-ELIM).LT.EXP(-ALIM)=EXP(-ELIM)/TOL AND - // EXP(ELIM).GT.EXP(ALIM)=EXP(ELIM)*TOL ARE INTERVALS NEAR - // UNDERFLOW AND OVERFLOW LIMITS WHERE SCALED ARITHMETIC IS DONE. - // RL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC EXPANSION FOR LARGE Z. - // DIG = NUMBER OF BASE 10 DIGITS IN TOL = 10**(-DIG). - // FNUL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC SERIES FOR LARGE FNU - // - tol = fmax(d1mach[3], 1e-18); - k1 = i1mach[14]; - k2 = i1mach[15]; - r1m5 = d1mach[4]; - k = ( abs(k1) > abs(k2) ? abs(k2) : abs(k1) ); - elim = 2.303 * (k*r1m5 - 3.0); - k1 = i1mach[13] - 1; - aa = r1m5*k1; - dig = fmin(aa, 18.0); - aa *= 2.303; - alim = elim + fmax(-aa, -41.45); - fnul = 10.0 + 6.0 * (dig - 3.0); - rl = 1.2*dig + 3.0; - fn = fnu + (nn - 1); - mm = 3 - m - m; - fmm = mm; - zn = z * std::complex(0.0, -fmm); - xn = std::real(zn); - yn = std::imag(zn); - // - // TEST FOR PROPER RANGE - // - az = std::abs(z); - bb = d1mach[1] * 0.5; - aa = fmin(0.5 / tol, bb); - if ((az > aa) || (fn > aa)){ *ierr =4; return 0; } /* GO TO 260 */ - aa = sqrt(aa); - if (az > aa) { *ierr = 3; } - if (fn > aa) { *ierr = 3; } - // - // OVERFLOW TEST ON THE LAST MEMBER OF THE SEQUENCE - // - ufl = d1mach[0] * 1.0e3; - if (az < ufl) { *ierr = 2; return 0; } /* GO TO 230 */ - if (fnu <= fnul) { - // - // Untangling GOTOs with explicit conditions - // - if ((fn > 1.0) && (fn <= 2.0) && (az <= tol)) { - /* Failed through all checks */ - arg = 0.5 * az; - aln = -fn * log(arg); - if (aln > elim) { *ierr = 2; return 0; } /* GO TO 230 */ - /* GO TO 70 */ - } else if ((fn > 1.0) && (fn <= 2.0) && (az > tol)) { - /* Failed all but the az > tol hence do nothing and GO TO 70 */ - } else if ((fn > 1.0) && (fn > 2.0)) { - /* GO TO 60 */ - nuf = uoik(zn, fnu, kode, 2, nn, cy, tol, elim, alim); - if (nuf < 0) { *ierr = 2; return 0; } /* GO TO 230 */ - nz += nuf; - nn -= nuf; - // - // HERE NN=N OR NN=0 SINCE NUF=0,NN, OR -1 ON RETURN FROM CUOIK - // IF NUF=NN, THEN CY(I)=CZERO FOR ALL I - // - if (nn == 0) { - /* GO TO 140 */ - if (xn < 0.0) { *ierr = 2; return 0; } /* GO TO 230 */ - return nz; - } - /* GO TO 70 */ - } else { - /* Passed the first hence GO TO 70 */ - } - - /* GO TO 70 */ - // - // More GOTOs untangling - // - if ((xn < 0.0) || ((xn == 0.0) && (yn < 0.0) && (m == 2))) { - /* GO TO 80 */ - mr = -mm; - nw = acon(zn, fnu, kode, mr, nn, cy, rl, fnul, tol, elim, alim); - if (nw < 0) { - /* GO TO 240 */ - if (nw == -1) { *ierr = 2; return 0; } /* GO TO 230 */ - *ierr = 5; - return 0; - } - nz = nw; - /* GO TO 110 */ - } else { - // - // RIGHT HALF PLANE COMPUTATION, XN >= 0. .AND. (XN.NE.0. .OR. - // YN >= 0. .OR. M=1) - // - nz = bknu(zn, fnu, kode, nn, cy, tol, elim, alim); - /* GO TO 110 */ - } - } else { - /* GO TO 90 */ - // - // UNIFORM ASYMPTOTIC EXPANSIONS FOR FNU > FNUL - // - mr = 0; - if (!((xn >= 0.0) && ((xn != 0.0) || (yn >= 0.0) || (m != 2)))) { - mr = -mm; - if ((xn == 0.0) && (yn < 0.0)) { zn = -zn; } - } - /* GO TO 100 */ - nw = bunk(zn, fnu, kode, mr, nn, cy, tol, elim, alim); - if (nw < 0) { - /* GO TO 240 */ - if (nw == -1) { *ierr = 2; return 0; } /* GO TO 230 */ - *ierr = 5; - return 0; - } - nz += nw; - } - /* 110 */ - // - // H(M,FNU,Z) = -FMM*(I/HPI)*(ZT**FNU)*K(FNU,-Z*ZT) - // ZT=EXP(-FMM*HPI*I) = std::complex(0.0,-FMM), FMM=3-2*M, M=1,2 - // - sgn = (-fmm < 0 ? -hpi : hpi); - // - // CALCULATE EXP(FNU*HPI*I) TO MINIMIZE LOSSES OF SIGNIFICANCE - // WHEN FNU IS LARGE - // - inu = (int)fnu; - inuh = inu / 2; - ir = inu - 2 * inuh; - arg = (fnu - (inu - ir)) * sgn; - rhpi = 1.0 / sgn; - cpn = rhpi * cos(arg); - spn = -rhpi * sin(arg); - csgn = std::complex(spn, cpn); - if (inuh % 2 == 1) { csgn = -csgn; } - zt = std::complex(0.0, -fmm); - rtol = 1.0 / tol; - ascle = ufl * rtol; - for (i = 1; i < (nn+1); i++) { - zn = cy[i-1]; - atol = 1.0; - if (fmax(fabs(std::real(zn)), fabs(std::imag(zn))) <= ascle) { - zn *= rtol; - atol = tol; - } - zn *= csgn; - cy[i-1] = zn * atol; - csgn *= zt; - } - return nz; -} - - -inline int besi( - std::complex z, - double fnu, - int kode, - int n, - std::complex *cy, - int *ierr -) { - - //***BEGIN PROLOGUE ZBESI - //***DATE WRITTEN 830501 (YYMMDD) - //***REVISION DATE 890801 (YYMMDD) - //***CATEGORY NO. B5K - //***KEYWORDS I-BESSEL FUNCTION,COMPLEX BESSEL FUNCTION, - // MODIFIED BESSEL FUNCTION OF THE FIRST KIND - //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES - //***PURPOSE TO COMPUTE I-BESSEL FUNCTIONS OF COMPLEX ARGUMENT - //***DESCRIPTION - // - // ***A DOUBLE PRECISION ROUTINE*** - // ON KODE=1, ZBESI COMPUTES AN N MEMBER SEQUENCE OF COMPLEX - // BESSEL FUNCTIONS CY(J)=I(FNU+J-1,Z) FOR REAL, NONNEGATIVE - // ORDERS FNU+J-1, J=1,...,N AND COMPLEX Z IN THE CUT PLANE - // -PI.LT.ARG(Z).LE.PI. ON KODE=2, ZBESI RETURNS THE SCALED - // FUNCTIONS - // - // CY(J)=EXP(-ABS(X))*I(FNU+J-1,Z) J = 1,...,N , X=REAL(Z) - // - // WITH THE EXPONENTIAL GROWTH REMOVED IN BOTH THE LEFT AND - // RIGHT HALF PLANES FOR Z TO INFINITY. DEFINITIONS AND NOTATION - // ARE FOUND IN THE NBS HANDBOOK OF MATHEMATICAL FUNCTIONS - // (REF. 1). - // - // INPUT ZR,ZI,FNU ARE DOUBLE PRECISION - // ZR,ZI - Z=std::complex(ZR,ZI), -PI.LT.ARG(Z).LE.PI - // FNU - ORDER OF INITIAL I FUNCTION, FNU.GE.0.0D0 - // KODE - A PARAMETER TO INDICATE THE SCALING OPTION - // KODE= 1 RETURNS - // CY(J)=I(FNU+J-1,Z), J=1,...,N - // = 2 RETURNS - // CY(J)=I(FNU+J-1,Z)*EXP(-ABS(X)), J=1,...,N - // N - NUMBER OF MEMBERS OF THE SEQUENCE, N.GE.1 - // - // OUTPUT CYR,CYI ARE DOUBLE PRECISION - // CYR,CYI- DOUBLE PRECISION VECTORS WHOSE FIRST N COMPONENTS - // CONTAIN REAL AND IMAGINARY PARTS FOR THE SEQUENCE - // CY(J)=I(FNU+J-1,Z) OR - // CY(J)=I(FNU+J-1,Z)*EXP(-ABS(X)) J=1,...,N - // DEPENDING ON KODE, X=REAL(Z) - // NZ - NUMBER OF COMPONENTS SET TO ZERO DUE TO UNDERFLOW, - // NZ= 0 , NORMAL RETURN - // NZ.GT.0 , LAST NZ COMPONENTS OF CY SET TO ZERO - // TO UNDERFLOW, CY(J)=std::complex(0.0D0,0.0D0) - // J = N-NZ+1,...,N - // IERR - ERROR FLAG - // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED - // IERR=1, INPUT ERROR - NO COMPUTATION - // IERR=2, OVERFLOW - NO COMPUTATION, REAL(Z) TOO - // LARGE ON KODE=1 - // IERR=3, CABS(Z) OR FNU+N-1 LARGE - COMPUTATION DONE - // BUT LOSSES OF SIGNIFCANCE BY ARGUMENT - // REDUCTION PRODUCE LESS THAN HALF OF MACHINE - // ACCURACY - // IERR=4, CABS(Z) OR FNU+N-1 TOO LARGE - NO COMPUTA- - // TION BECAUSE OF COMPLETE LOSSES OF SIGNIFI- - // CANCE BY ARGUMENT REDUCTION - // IERR=5, ERROR - NO COMPUTATION, - // ALGORITHM TERMINATION CONDITION NOT MET - // - //***LONG DESCRIPTION - // - // THE COMPUTATION IS CARRIED OUT BY THE POWER SERIES FOR - // SMALL CABS(Z), THE ASYMPTOTIC EXPANSION FOR LARGE CABS(Z), - // THE MILLER ALGORITHM NORMALIZED BY THE WRONSKIAN AND A - // NEUMANN SERIES FOR IMTERMEDIATE MAGNITUDES, AND THE - // UNIFORM ASYMPTOTIC EXPANSIONS FOR I(FNU,Z) AND J(FNU,Z) - // FOR LARGE ORDERS. BACKWARD RECURRENCE IS USED TO GENERATE - // SEQUENCES OR REDUCE ORDERS WHEN NECESSARY. - // - // THE CALCULATIONS ABOVE ARE DONE IN THE RIGHT HALF PLANE AND - // CONTINUED INTO THE LEFT HALF PLANE BY THE FORMULA - // - // I(FNU,Z*EXP(M*PI)) = EXP(M*PI*FNU)*I(FNU,Z) REAL(Z).GT.0.0 - // M = +I OR -I, I**2=-1 - // - // FOR NEGATIVE ORDERS,THE FORMULA - // - // I(-FNU,Z) = I(FNU,Z) + (2/PI)*SIN(PI*FNU)*K(FNU,Z) - // - // CAN BE USED. HOWEVER,FOR LARGE ORDERS CLOSE TO INTEGERS, THE - // THE FUNCTION CHANGES RADICALLY. WHEN FNU IS A LARGE POSITIVE - // INTEGER,THE MAGNITUDE OF I(-FNU,Z)=I(FNU,Z) IS A LARGE - // NEGATIVE POWER OF TEN. BUT WHEN FNU IS NOT AN INTEGER, - // K(FNU,Z) DOMINATES IN MAGNITUDE WITH A LARGE POSITIVE POWER OF - // TEN AND THE MOST THAT THE SECOND TERM CAN BE REDUCED IS BY - // UNIT ROUNDOFF FROM THE COEFFICIENT. THUS, WIDE CHANGES CAN - // OCCUR WITHIN UNIT ROUNDOFF OF A LARGE INTEGER FOR FNU. HERE, - // LARGE MEANS FNU.GT.CABS(Z). - // - // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- - // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z OR FNU+N-1 IS - // LARGE, LOSSES OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. - // CONSEQUENTLY, IF EITHER ONE EXCEEDS U1=SQRT(0.5/UR), THEN - // LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR FLAG - // IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS - // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. - // IF EITHER IS LARGER THAN U2=0.5/UR, THEN ALL SIGNIFICANCE IS - // LOST AND IERR=4. IN ORDER TO USE THE INT FUNCTION, ARGUMENTS - // MUST BE FURTHER RESTRICTED NOT TO EXCEED THE LARGEST MACHINE - // INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF Z AND FNU+N-1 IS - // RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, AND U3 - // ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE PRECISION - // ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE PRECISION - // ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMITING IN - // THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT ONE CAN EXPECT - // TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, NO DIGITS - // IN SINGLE AND ONLY 7 DIGITS IN DOUBLE PRECISION ARITHMETIC. - // SIMILAR CONSIDERATIONS HOLD FOR OTHER MACHINES. - // - // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX - // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT - // ROUNDOFF,1.0E-18) IS THE NOMINAL PRECISION AND 10**S REPRE- - // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE - // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), - // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF - // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY - // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN - // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY - // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER - // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, - // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS - // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER - // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY - // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER - // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE - // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, - // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, - // OR -PI/2+P. - // - //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ - // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF - // COMMERCE, 1955. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // BY D. E. AMOS, SAND83-0083, MAY, 1983. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983 - // - // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- - // 1018, MAY, 1985 - // - // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. - // MATH. SOFTWARE, 1986 - // - //***ROUTINES CALLED ZBINU,I1MACH,D1MACH - //***END PROLOGUE ZBESI - - std::complex csgn, zn; - double aa, alim, arg, atol, ascle, az, bb, dig, elim, fn, fnul, rl, rtol,\ - r1m5, tol, xx, yy; - int i, inu, k, k1, k2, nn, nz; - double pi = 3.14159265358979324; - - *ierr = 0; - nz = 0; - if (fnu < 0.0) { *ierr = 1; } - if ((kode < 1) || (kode > 2)) { *ierr = 1; } - if (n < 1) { *ierr = 1; } - if (*ierr != 0) { return nz; } - xx = std::real(z); - yy = std::imag(z); - // - // SET PARAMETERS RELATED TO MACHINE CONSTANTS. - // TOL IS THE APPROXIMATE UNIT ROUNDOFF LIMITED TO 1.0E-18. - // ELIM IS THE APPROXIMATE EXPONENTIAL OVER- AND UNDERFLOW LIMIT. - // EXP(-ELIM).LT.EXP(-ALIM)=EXP(-ELIM)/TOL AND - // EXP(ELIM).GT.EXP(ALIM)=EXP(ELIM)*TOL ARE INTERVALS NEAR - // UNDERFLOW AND OVERFLOW LIMITS WHERE SCALED ARITHMETIC IS DONE. - // RL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC EXPANSION FOR LARGE Z. - // DIG = NUMBER OF BASE 10 DIGITS IN TOL = 10**(-DIG). - // FNUL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC SERIES FOR LARGE FNU - // - tol = fmax(d1mach[3], 1e-18); - k1 = i1mach[14]; - k2 = i1mach[15]; - r1m5 = d1mach[4]; - k = ( abs(k1) > abs(k2) ? abs(k2) : abs(k1) ); - elim = 2.303 * (k*r1m5 - 3.0); - k1 = i1mach[13] - 1; - aa = r1m5*k1; - dig = fmin(aa, 18.0); - aa *= 2.303; - alim = elim + fmax(-aa, -41.45); - rl = 1.2 * dig + 3.0; - fnul = 10.0 + 6.0 * (dig - 3.0); - // - // TEST FOR PROPER RANGE - // - az = std::abs(z); - fn = fnu + (n - 1); - aa = 0.5 / tol; - bb = i1mach[8]*0.5; - aa = fmin(aa, bb); - if ((az > aa) || (fn > aa)) { - *ierr = 4; - return 0; - } - aa = sqrt(aa); - if (az > aa) { *ierr = 3; } - if (fn > aa) { *ierr = 3; } - zn = z; - csgn = 1.0; - if (xx < 0.0) { - zn = -z; - // - // CALCULATE CSGN=EXP(FNU*PI*I) TO MINIMIZE LOSSES OF SIGNIFICANCE - // WHEN FNU IS LARGE - // - inu = (int)fnu; - arg = (fnu - inu)*pi; - if (yy < 0.0) { arg = -arg; } - csgn = std::complex(cos(arg), sin(arg)); - if (inu % 2 == 1) { csgn = -csgn; } - } - /* 40 */ - nz = binu(zn, fnu, kode, n, cy, rl, fnul, tol, elim, alim); - if (nz < 0) { - if (nz == -2) { - *ierr = 5; - return 0; - } - *ierr = 2; - return 0; - } - if (xx > 0.0) { return nz; } - // - // ANALYTIC CONTINUATION TO THE LEFT HALF PLANE - // - nn = n - nz; - if (nn == 0) { return nz; } - rtol = 1.0 / tol; - ascle = d1mach[0]*rtol*1e3; - for (i = 1; i < (nn+1); i++) - { - zn = cy[i-1]; - atol = 1.0; - if (fmax(fabs(std::real(zn)), fabs(std::imag(zn))) <= ascle) { - zn *= rtol; - atol = tol; - } - cy[i-1] = atol*(zn*csgn); - csgn = -csgn; - } - *ierr = 0; - return nz; -} - - -inline int besj( - std::complex z, - double fnu, - int kode, - int n, - std::complex *cy, - int *ierr -) { - - //***BEGIN PROLOGUE ZBESJ - //***DATE WRITTEN 830501 (YYMMDD) - //***REVISION DATE 890801 (YYMMDD) - //***CATEGORY NO. B5K - //***KEYWORDS J-BESSEL FUNCTION,BESSEL FUNCTION OF COMPLEX ARGUMENT, - // BESSEL FUNCTION OF FIRST KIND - //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES - //***PURPOSE TO COMPUTE THE J-BESSEL FUNCTION OF A COMPLEX ARGUMENT - //***DESCRIPTION - // - // ***A DOUBLE PRECISION ROUTINE*** - // ON KODE=1, CBESJ COMPUTES AN N MEMBER SEQUENCE OF COMPLEX - // BESSEL FUNCTIONS CY(I)=J(FNU+I-1,Z) FOR REAL, NONNEGATIVE - // ORDERS FNU+I-1, I=1,...,N AND COMPLEX Z IN THE CUT PLANE - // -PI.LT.ARG(Z).LE.PI. ON KODE=2, CBESJ RETURNS THE SCALED - // FUNCTIONS - // - // CY(I)=EXP(-ABS(Y))*J(FNU+I-1,Z) I = 1,...,N , Y=AIMAG(Z) - // - // WHICH REMOVE THE EXPONENTIAL GROWTH IN BOTH THE UPPER AND - // LOWER HALF PLANES FOR Z TO INFINITY. DEFINITIONS AND NOTATION - // ARE FOUND IN THE NBS HANDBOOK OF MATHEMATICAL FUNCTIONS - // (REF. 1). - // - // INPUT ZR,ZI,FNU ARE DOUBLE PRECISION - // ZR,ZI - Z=std::complex(ZR,ZI), -PI.LT.ARG(Z).LE.PI - // FNU - ORDER OF INITIAL J FUNCTION, FNU.GE.0.0D0 - // KODE - A PARAMETER TO INDICATE THE SCALING OPTION - // KODE= 1 RETURNS - // CY(I)=J(FNU+I-1,Z), I=1,...,N - // = 2 RETURNS - // CY(I)=J(FNU+I-1,Z)EXP(-ABS(Y)), I=1,...,N - // N - NUMBER OF MEMBERS OF THE SEQUENCE, N.GE.1 - // - // OUTPUT CYR,CYI ARE DOUBLE PRECISION - // CYR,CYI- DOUBLE PRECISION VECTORS WHOSE FIRST N COMPONENTS - // CONTAIN REAL AND IMAGINARY PARTS FOR THE SEQUENCE - // CY(I)=J(FNU+I-1,Z) OR - // CY(I)=J(FNU+I-1,Z)EXP(-ABS(Y)) I=1,...,N - // DEPENDING ON KODE, Y=AIMAG(Z). - // NZ - NUMBER OF COMPONENTS SET TO ZERO DUE TO UNDERFLOW, - // NZ= 0 , NORMAL RETURN - // NZ.GT.0 , LAST NZ COMPONENTS OF CY SET ZERO DUE - // TO UNDERFLOW, CY(I)=std::complex(0.0D0,0.0D0), - // I = N-NZ+1,...,N - // IERR - ERROR FLAG - // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED - // IERR=1, INPUT ERROR - NO COMPUTATION - // IERR=2, OVERFLOW - NO COMPUTATION, AIMAG(Z) - // TOO LARGE ON KODE=1 - // IERR=3, CABS(Z) OR FNU+N-1 LARGE - COMPUTATION DONE - // BUT LOSSES OF SIGNIFCANCE BY ARGUMENT - // REDUCTION PRODUCE LESS THAN HALF OF MACHINE - // ACCURACY - // IERR=4, CABS(Z) OR FNU+N-1 TOO LARGE - NO COMPUTA- - // TION BECAUSE OF COMPLETE LOSSES OF SIGNIFI- - // CANCE BY ARGUMENT REDUCTION - // IERR=5, ERROR - NO COMPUTATION, - // ALGORITHM TERMINATION CONDITION NOT MET - // - //***LONG DESCRIPTION - // - // THE COMPUTATION IS CARRIED OUT BY THE FORMULA - // - // J(FNU,Z)=EXP( FNU*PI*I/2)*I(FNU,-I*Z) AIMAG(Z).GE.0.0 - // - // J(FNU,Z)=EXP(-FNU*PI*I/2)*I(FNU, I*Z) AIMAG(Z).LT.0.0 - // - // WHERE I**2 = -1 AND I(FNU,Z) IS THE I BESSEL FUNCTION. - // - // FOR NEGATIVE ORDERS,THE FORMULA - // - // J(-FNU,Z) = J(FNU,Z)*COS(PI*FNU) - Y(FNU,Z)*SIN(PI*FNU) - // - // CAN BE USED. HOWEVER,FOR LARGE ORDERS CLOSE TO INTEGERS, THE - // THE FUNCTION CHANGES RADICALLY. WHEN FNU IS A LARGE POSITIVE - // INTEGER,THE MAGNITUDE OF J(-FNU,Z)=J(FNU,Z)*COS(PI*FNU) IS A - // LARGE NEGATIVE POWER OF TEN. BUT WHEN FNU IS NOT AN INTEGER, - // Y(FNU,Z) DOMINATES IN MAGNITUDE WITH A LARGE POSITIVE POWER OF - // TEN AND THE MOST THAT THE SECOND TERM CAN BE REDUCED IS BY - // UNIT ROUNDOFF FROM THE COEFFICIENT. THUS, WIDE CHANGES CAN - // OCCUR WITHIN UNIT ROUNDOFF OF A LARGE INTEGER FOR FNU. HERE, - // LARGE MEANS FNU.GT.CABS(Z). - // - // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- - // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z OR FNU+N-1 IS - // LARGE, LOSSES OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. - // CONSEQUENTLY, IF EITHER ONE EXCEEDS U1=SQRT(0.5/UR), THEN - // LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR FLAG - // IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS - // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. - // IF EITHER IS LARGER THAN U2=0.5/UR, THEN ALL SIGNIFICANCE IS - // LOST AND IERR=4. IN ORDER TO USE THE INT FUNCTION, ARGUMENTS - // MUST BE FURTHER RESTRICTED NOT TO EXCEED THE LARGEST MACHINE - // INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF Z AND FNU+N-1 IS - // RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, AND U3 - // ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE PRECISION - // ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE PRECISION - // ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMITING IN - // THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT ONE CAN EXPECT - // TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, NO DIGITS - // IN SINGLE AND ONLY 7 DIGITS IN DOUBLE PRECISION ARITHMETIC. - // SIMILAR CONSIDERATIONS HOLD FOR OTHER MACHINES. - // - // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX - // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT - // ROUNDOFF,1.0E-18) IS THE NOMINAL PRECISION AND 10**S REPRE- - // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE - // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), - // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF - // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY - // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN - // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY - // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER - // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, - // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS - // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER - // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY - // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER - // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE - // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, - // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, - // OR -PI/2+P. - // - //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ - // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF - // COMMERCE, 1955. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // BY D. E. AMOS, SAND83-0083, MAY, 1983. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983 - // - // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- - // 1018, MAY, 1985 - // - // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. - // MATH. SOFTWARE, 1986 - // - //***ROUTINES CALLED ZBINU,I1MACH,D1MACH - //***END PROLOGUE ZBESJ - - std::complex ci, csgn, zn; - double aa, alim, arg, dig, elim, fnul, rl, r1, r1m5, r2, - tol, yy, az, fn, bb, ascle, rtol, atol; - int i, inu, inuh, ir, k1, k2, nl, nz, k; - double hpi = 1.570796326794896619; - - *ierr = 0; - nz = 0; - if (fnu < 0.0) *ierr = 1; - if (kode < 1 || kode > 2) *ierr = 1; - if (n < 1) *ierr = 1; - if (*ierr != 0) return nz; - // - // SET PARAMETERS RELATED TO MACHINE CONSTANTS. - // TOL IS THE APPROXIMATE UNIT ROUNDOFF LIMITED TO 1.0E-18. - // ELIM IS THE APPROXIMATE EXPONENTIAL OVER- AND UNDERFLOW LIMIT. - // EXP(-ELIM).LT.EXP(-ALIM)=EXP(-ELIM)/TOL AND - // EXP(ELIM).GT.EXP(ALIM)=EXP(ELIM)*TOL ARE INTERVALS NEAR - // UNDERFLOW AND OVERFLOW LIMITS WHERE SCALED ARITHMETIC IS DONE. - // RL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC EXPANSION FOR LARGE Z. - // DIG = NUMBER OF BASE 10 DIGITS IN TOL = 10**(-DIG). - // FNUL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC SERIES FOR LARGE FNU. - // - tol = fmax(d1mach[3], 1e-18); - k1 = i1mach[14]; - k2 = i1mach[15]; - r1m5 = d1mach[4]; - k = ( abs(k1) > abs(k2) ? abs(k2) : abs(k1) ); - elim = 2.303 * (k*r1m5 - 3.0); - k1 = i1mach[13] - 1; - aa = r1m5*k1; - dig = fmin(aa, 18.0); - aa *= 2.303; - alim = elim + fmax(-aa, -41.45); - fnul = 10.0 + 6.0 * (dig - 3.0); - rl = 1.2*dig + 3.0; - // - // TEST FOR PROPER RANGE - // - yy = std::imag(z); - az = std::abs(z); - fn = fnu + (n - 1); - - aa = 0.5 / tol; - bb = d1mach[1] * 0.5; - aa = fmin(aa, bb); - if ((az > aa) || (fn > aa)) { - *ierr = 4; - return 0; - } - aa = sqrt(aa); - if (az > aa) { *ierr = 3; } - if (fn > aa) { *ierr = 3; } - // - // CALCULATE CSGN = EXP(FNU*HPI*I) TO MINIMIZE LOSSES OF SIGNIFICANCE - // WHEN FNU IS LARGE - // - ci.imag(1); - inu = (int)fnu; - inuh = inu / 2; - ir = inu - 2*inuh; - arg = (fnu - (inu - ir)) * hpi; - r1 = cos(arg); - r2 = sin(arg); - csgn = std::complex(r1, r2); - if (inuh % 2 == 1) { csgn = -csgn; } - // - // ZN IS IN THE RIGHT HALF PLANE - // - zn = -z * ci; - if (yy < 0.0) { - zn = -zn; - csgn = conj(csgn); - ci = conj(ci); - } - nz = binu(zn, fnu, kode, n, cy, rl, fnul, tol, elim, alim); - if (nz < 0) { - if (nz == -2) { *ierr = 5; return 0; } - *ierr = 2; - return 0; - } - nl = n - nz; - if (nl == 0) { return nz; } - rtol = 1.0 / tol; - ascle = d1mach[0]*rtol*1e3; - for (i = 1; i < (nl+1); i++) - { - zn = cy[i-1]; - aa = std::real(zn); - bb = std::imag(zn); - atol = 1.0; - if (fmax(fabs(aa), fabs(bb)) <= ascle) { - zn *= rtol; - atol = tol; - } - cy[i-1] = atol*(zn * csgn); - csgn = csgn * ci; - } - return nz; -} - - -inline int besk( - std::complex z, - double fnu, - int kode, - int n, - std::complex *cy, - int *ierr -) { - - //***BEGIN PROLOGUE ZBESK - //***DATE WRITTEN 830501 (YYMMDD) - //***REVISION DATE 890801 (YYMMDD) - //***CATEGORY NO. B5K - //***KEYWORDS K-BESSEL FUNCTION,COMPLEX BESSEL FUNCTION, - // MODIFIED BESSEL FUNCTION OF THE SECOND KIND, - // BESSEL FUNCTION OF THE THIRD KIND - //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES - //***PURPOSE TO COMPUTE K-BESSEL FUNCTIONS OF COMPLEX ARGUMENT - //***DESCRIPTION - // - // ***A DOUBLE PRECISION ROUTINE*** - // - // ON KODE=1, CBESK COMPUTES AN N MEMBER SEQUENCE OF COMPLEX - // BESSEL FUNCTIONS CY(J)=K(FNU+J-1,Z) FOR REAL, NONNEGATIVE - // ORDERS FNU+J-1, J=1,...,N AND COMPLEX Z.NE.std::complex(0.0,0.0) - // IN THE CUT PLANE -PI.LT.ARG(Z).LE.PI. ON KODE=2, CBESK - // RETURNS THE SCALED K FUNCTIONS, - // - // CY(J)=EXP(Z)*K(FNU+J-1,Z) , J=1,...,N, - // - // WHICH REMOVE THE EXPONENTIAL BEHAVIOR IN BOTH THE LEFT AND - // RIGHT HALF PLANES FOR Z TO INFINITY. DEFINITIONS AND - // NOTATION ARE FOUND IN THE NBS HANDBOOK OF MATHEMATICAL - // FUNCTIONS (REF. 1). - // - // INPUT ZR,ZI,FNU ARE DOUBLE PRECISION - // ZR,ZI - Z=std::complex(ZR,ZI), Z.NE.std::complex(0.0D0,0.0D0), - // -PI.LT.ARG(Z).LE.PI - // FNU - ORDER OF INITIAL K FUNCTION, FNU.GE.0.0D0 - // N - NUMBER OF MEMBERS OF THE SEQUENCE, N.GE.1 - // KODE - A PARAMETER TO INDICATE THE SCALING OPTION - // KODE= 1 RETURNS - // CY(I)=K(FNU+I-1,Z), I=1,...,N - // = 2 RETURNS - // CY(I)=K(FNU+I-1,Z)*EXP(Z), I=1,...,N - // - // OUTPUT CYR,CYI ARE DOUBLE PRECISION - // CYR,CYI- DOUBLE PRECISION VECTORS WHOSE FIRST N COMPONENTS - // CONTAIN REAL AND IMAGINARY PARTS FOR THE SEQUENCE - // CY(I)=K(FNU+I-1,Z), I=1,...,N OR - // CY(I)=K(FNU+I-1,Z)*EXP(Z), I=1,...,N - // DEPENDING ON KODE - // NZ - NUMBER OF COMPONENTS SET TO ZERO DUE TO UNDERFLOW. - // NZ= 0 , NORMAL RETURN - // NZ.GT.0 , FIRST NZ COMPONENTS OF CY SET TO ZERO DUE - // TO UNDERFLOW, CY(I)=std::complex(0.0D0,0.0D0), - // I=1,...,N WHEN X.GE.0.0. WHEN X.LT.0.0 - // NZ STATES ONLY THE NUMBER OF UNDERFLOWS - // IN THE SEQUENCE. - // - // IERR - ERROR FLAG - // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED - // IERR=1, INPUT ERROR - NO COMPUTATION - // IERR=2, OVERFLOW - NO COMPUTATION, FNU IS - // TOO LARGE OR CABS(Z) IS TOO SMALL OR BOTH - // IERR=3, CABS(Z) OR FNU+N-1 LARGE - COMPUTATION DONE - // BUT LOSSES OF SIGNIFCANCE BY ARGUMENT - // REDUCTION PRODUCE LESS THAN HALF OF MACHINE - // ACCURACY - // IERR=4, CABS(Z) OR FNU+N-1 TOO LARGE - NO COMPUTA- - // TION BECAUSE OF COMPLETE LOSSES OF SIGNIFI- - // CANCE BY ARGUMENT REDUCTION - // IERR=5, ERROR - NO COMPUTATION, - // ALGORITHM TERMINATION CONDITION NOT MET - // - //***LONG DESCRIPTION - // - // EQUATIONS OF THE REFERENCE ARE IMPLEMENTED FOR SMALL ORDERS - // DNU AND DNU+1.0 IN THE RIGHT HALF PLANE X.GE.0.0. FORWARD - // RECURRENCE GENERATES HIGHER ORDERS. K IS CONTINUED TO THE LEFT - // HALF PLANE BY THE RELATION - // - // K(FNU,Z*EXP(MP)) = EXP(-MP*FNU)*K(FNU,Z)-MP*I(FNU,Z) - // MP=MR*PI*I, MR=+1 OR -1, RE(Z).GT.0, I**2=-1 - // - // WHERE I(FNU,Z) IS THE I BESSEL FUNCTION. - // - // FOR LARGE ORDERS, FNU.GT.FNUL, THE K FUNCTION IS COMPUTED - // BY MEANS OF ITS UNIFORM ASYMPTOTIC EXPANSIONS. - // - // FOR NEGATIVE ORDERS, THE FORMULA - // - // K(-FNU,Z) = K(FNU,Z) - // - // CAN BE USED. - // - // CBESK ASSUMES THAT A SIGNIFICANT DIGIT SINH(X) FUNCTION IS - // AVAILABLE. - // - // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- - // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z OR FNU+N-1 IS - // LARGE, LOSSES OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. - // CONSEQUENTLY, IF EITHER ONE EXCEEDS U1=SQRT(0.5/UR), THEN - // LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR FLAG - // IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS - // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. - // IF EITHER IS LARGER THAN U2=0.5/UR, THEN ALL SIGNIFICANCE IS - // LOST AND IERR=4. IN ORDER TO USE THE INT FUNCTION, ARGUMENTS - // MUST BE FURTHER RESTRICTED NOT TO EXCEED THE LARGEST MACHINE - // INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF Z AND FNU+N-1 IS - // RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, AND U3 - // ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE PRECISION - // ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE PRECISION - // ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMITING IN - // THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT ONE CAN EXPECT - // TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, NO DIGITS - // IN SINGLE AND ONLY 7 DIGITS IN DOUBLE PRECISION ARITHMETIC. - // SIMILAR CONSIDERATIONS HOLD FOR OTHER MACHINES. - // - // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX - // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT - // ROUNDOFF,1.0E-18) IS THE NOMINAL PRECISION AND 10**S REPRE- - // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE - // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), - // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF - // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY - // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN - // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY - // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER - // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, - // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS - // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER - // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY - // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER - // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE - // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, - // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, - // OR -PI/2+P. - // - //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ - // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF - // COMMERCE, 1955. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // BY D. E. AMOS, SAND83-0083, MAY, 1983. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983. - // - // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- - // 1018, MAY, 1985 - // - // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. - // MATH. SOFTWARE, 1986 - // - //***ROUTINES CALLED ZACON,ZBKNU,ZBUNK,ZUOIK,AZABS,I1MACH,D1MACH - //***END PROLOGUE ZBESK - - double xx = std::real(z); - double yy = std::imag(z); - double aa, alim, aln, arg, az, dig, elim, fn, fnul, rl, r1m5, tol, ufl, bb; - int k, k1, k2, mr, nn, nuf, nw, nz; - - *ierr = 0; - nz = 0; - - if ((yy == 0.0) && (xx == 0.0)) { *ierr = 1; } - if (fnu < 0.0) { *ierr = 1; } - if (kode < 1 || kode > 2) { *ierr = 1; } - if (n < 1) { *ierr = 1; } - if (*ierr != 0) { return nz; } - - nn = n; - // - // SET PARAMETERS RELATED TO MACHINE CONSTANTS. - // TOL IS THE APPROXIMATE UNIT ROUNDOFF LIMITED TO 1.0E-18. - // ELIM IS THE APPROXIMATE EXPONENTIAL OVER- AND UNDERFLOW LIMIT. - // EXP(-ELIM).LT.EXP(-ALIM)=EXP(-ELIM)/TOL AND - // EXP(ELIM).GT.EXP(ALIM)=EXP(ELIM)*TOL ARE INTERVALS NEAR - // UNDERFLOW AND OVERFLOW LIMITS WHERE SCALED ARITHMETIC IS DONE. - // RL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC EXPANSION FOR LARGE Z. - // DIG = NUMBER OF BASE 10 DIGITS IN TOL = 10**(-DIG). - // FNUL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC SERIES FOR LARGE FNU - // - tol = fmax(d1mach[3], 1e-18); - k1 = i1mach[14]; - k2 = i1mach[15]; - r1m5 = d1mach[4]; - k = ( abs(k1) > abs(k2) ? abs(k2) : abs(k1) ); - elim = 2.303 * (k*r1m5 - 3.0); - k1 = i1mach[13] - 1; - aa = r1m5*k1; - dig = fmin(aa, 18.0); - aa *= 2.303; - alim = elim + fmax(-aa, -41.45); - fnul = 10.0 + 6.0 * (dig - 3.0); - rl = 1.2 * dig + 3.0; - // - // TEST FOR PROPER RANGE - // - az = std::abs(z); - fn = fnu + (nn - 1); - aa = 0.5 / tol; - bb = i1mach[8] * 0.5; - aa = fmin(aa, bb); - if ((az > aa) || (fn > aa)) { - *ierr = 4; - return 0; - } - aa = sqrt(aa); - if (az > aa) { *ierr = 3; } - if (fn > aa) { *ierr = 3; } - // - // OVERFLOW TEST ON THE LAST MEMBER OF THE SEQUENCE - // - ufl = d1mach[0] * 1.0E+3; - if (az < ufl) { - *ierr = 2; - return 0; - } - if (fnu <= fnul) { - if (fn > 1.0) { - if (fn <= 2.0) { - if (az <= tol) { - arg = 0.5 * az; - aln = -fn * log(arg); - if (aln > elim) { *ierr = 2; return 0; } - } - /* GO TO 60 */ - } else { - nuf = uoik(z, fnu, kode, 2, nn, cy, tol, elim, alim); - if (nuf < 0) { *ierr = 2; return 0; } - nz += nuf; - nn -= nuf; - // - // HERE NN=N OR NN=0 SINCE NUF=0,NN, OR -1 ON RETURN FROM CUOIK - // IF NUF=NN, THEN CY(I)=CZERO FOR ALL I - // - if (nn == 0) { - if (xx < 0.0) { *ierr = 2; return 0; } - return nz; - } - } - } - - /* 60 */ - if (xx >= 0.0) { - // - // RIGHT HALF PLANE COMPUTATION, REAL(Z) >= 0. - // - nw = bknu(z, fnu, kode, nn, cy, tol, elim, alim); - if (nw < 0) { - if (nw == -1) { - *ierr = 2; - } else { - *ierr = 5; - } - return 0; - } - return nw; - } - /* 70 */ - // - // LEFT HALF PLANE COMPUTATION - // PI/2 < ARG(Z) <= PI AND -PI < ARG(Z) < -PI/2. - // - if (nz != 0) { *ierr = 2; return 0; } - mr = 1; - if (yy < 0.0) { mr = -1; } - nw = acon(z, fnu, kode, mr, nn, cy, rl, fnul, tol, elim, alim); - if (nw < 0) { - if (nw == -1) { - *ierr = 2; - } else { - *ierr = 5; - } - return 0; - } - return nw; - } - - /* 80 */ - // - // UNIFORM ASYMPTOTIC EXPANSIONS FOR FNU > FNUL - // - mr = 0; - if (xx < 0.0) { - mr = 1; - if (yy < 0.0) { mr = -1; } - } - nw = bunk(z, fnu, kode, mr, nn, cy, tol, elim, alim); - if (nw < 0) { - if (nw == -1) { - *ierr = 2; - } else { - *ierr = 5; - } - return 0; - } - nz += nw; - return nz; -} - - -inline int besy( - std::complex z, - double fnu, - int kode, - int n, - std::complex *cy, - int *ierr -) { - - //***BEGIN PROLOGUE ZBESY - //***DATE WRITTEN 830501 (YYMMDD) - //***REVISION DATE 890801 (YYMMDD) - //***CATEGORY NO. B5K - //***KEYWORDS Y-BESSEL FUNCTION,BESSEL FUNCTION OF COMPLEX ARGUMENT, - // BESSEL FUNCTION OF SECOND KIND - //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES - //***PURPOSE TO COMPUTE THE Y-BESSEL FUNCTION OF A COMPLEX ARGUMENT - //***DESCRIPTION - // - // ***A DOUBLE PRECISION ROUTINE*** - // - // ON KODE=1, CBESY COMPUTES AN N MEMBER SEQUENCE OF COMPLEX - // BESSEL FUNCTIONS CY(I)=Y(FNU+I-1,Z) FOR REAL, NONNEGATIVE - // ORDERS FNU+I-1, I=1,...,N AND COMPLEX Z IN THE CUT PLANE - // -PI.LT.ARG(Z).LE.PI. ON KODE=2, CBESY RETURNS THE SCALED - // FUNCTIONS - // - // CY(I)=EXP(-ABS(Y))*Y(FNU+I-1,Z) I = 1,...,N , Y=AIMAG(Z) - // - // WHICH REMOVE THE EXPONENTIAL GROWTH IN BOTH THE UPPER AND - // LOWER HALF PLANES FOR Z TO INFINITY. DEFINITIONS AND NOTATION - // ARE FOUND IN THE NBS HANDBOOK OF MATHEMATICAL FUNCTIONS - // (REF. 1). - // - // INPUT ZR,ZI,FNU ARE DOUBLE PRECISION - // ZR,ZI - Z=std::complex(ZR,ZI), Z.NE.std::complex(0.0D0,0.0D0), - // -PI.LT.ARG(Z).LE.PI - // FNU - ORDER OF INITIAL Y FUNCTION, FNU.GE.0.0D0 - // KODE - A PARAMETER TO INDICATE THE SCALING OPTION - // KODE= 1 RETURNS - // CY(I)=Y(FNU+I-1,Z), I=1,...,N - // = 2 RETURNS - // CY(I)=Y(FNU+I-1,Z)*EXP(-ABS(Y)), I=1,...,N - // WHERE Y=AIMAG(Z) - // N - NUMBER OF MEMBERS OF THE SEQUENCE, N.GE.1 - // CWRKR, - DOUBLE PRECISION WORK VECTORS OF DIMENSION AT - // CWRKI AT LEAST N - // - // OUTPUT CYR,CYI ARE DOUBLE PRECISION - // CYR,CYI- DOUBLE PRECISION VECTORS WHOSE FIRST N COMPONENTS - // CONTAIN REAL AND IMAGINARY PARTS FOR THE SEQUENCE - // CY(I)=Y(FNU+I-1,Z) OR - // CY(I)=Y(FNU+I-1,Z)*EXP(-ABS(Y)) I=1,...,N - // DEPENDING ON KODE. - // NZ - NZ=0 , A NORMAL RETURN - // NZ.GT.0 , NZ COMPONENTS OF CY SET TO ZERO DUE TO - // UNDERFLOW (GENERALLY ON KODE=2) - // IERR - ERROR FLAG - // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED - // IERR=1, INPUT ERROR - NO COMPUTATION - // IERR=2, OVERFLOW - NO COMPUTATION, FNU IS - // TOO LARGE OR CABS(Z) IS TOO SMALL OR BOTH - // IERR=3, CABS(Z) OR FNU+N-1 LARGE - COMPUTATION DONE - // BUT LOSSES OF SIGNIFCANCE BY ARGUMENT - // REDUCTION PRODUCE LESS THAN HALF OF MACHINE - // ACCURACY - // IERR=4, CABS(Z) OR FNU+N-1 TOO LARGE - NO COMPUTA- - // TION BECAUSE OF COMPLETE LOSSES OF SIGNIFI- - // CANCE BY ARGUMENT REDUCTION - // IERR=5, ERROR - NO COMPUTATION, - // ALGORITHM TERMINATION CONDITION NOT MET - // IERR=6, Memory allocation failed. - // - //***LONG DESCRIPTION - // - // THE COMPUTATION IS CARRIED OUT BY THE FORMULA - // - // Y(FNU,Z)=0.5*(H(1,FNU,Z)-H(2,FNU,Z))/I - // - // WHERE I**2 = -1 AND THE HANKEL BESSEL FUNCTIONS H(1,FNU,Z) - // AND H(2,FNU,Z) ARE CALCULATED IN CBESH. - // - // FOR NEGATIVE ORDERS,THE FORMULA - // - // Y(-FNU,Z) = Y(FNU,Z)*COS(PI*FNU) + J(FNU,Z)*SIN(PI*FNU) - // - // CAN BE USED. HOWEVER,FOR LARGE ORDERS CLOSE TO HALF ODD - // INTEGERS THE FUNCTION CHANGES RADICALLY. WHEN FNU IS A LARGE - // POSITIVE HALF ODD INTEGER,THE MAGNITUDE OF Y(-FNU,Z)=J(FNU,Z)* - // SIN(PI*FNU) IS A LARGE NEGATIVE POWER OF TEN. BUT WHEN FNU IS - // NOT A HALF ODD INTEGER, Y(FNU,Z) DOMINATES IN MAGNITUDE WITH A - // LARGE POSITIVE POWER OF TEN AND THE MOST THAT THE SECOND TERM - // CAN BE REDUCED IS BY UNIT ROUNDOFF FROM THE COEFFICIENT. THUS, - // WIDE CHANGES CAN OCCUR WITHIN UNIT ROUNDOFF OF A LARGE HALF - // ODD INTEGER. HERE, LARGE MEANS FNU.GT.CABS(Z). - // - // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- - // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z OR FNU+N-1 IS - // LARGE, LOSSES OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. - // CONSEQUENTLY, IF EITHER ONE EXCEEDS U1=SQRT(0.5/UR), THEN - // LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR FLAG - // IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS - // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. - // IF EITHER IS LARGER THAN U2=0.5/UR, THEN ALL SIGNIFICANCE IS - // LOST AND IERR=4. IN ORDER TO USE THE INT FUNCTION, ARGUMENTS - // MUST BE FURTHER RESTRICTED NOT TO EXCEED THE LARGEST MACHINE - // INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF Z AND FNU+N-1 IS - // RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, AND U3 - // ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE PRECISION - // ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE PRECISION - // ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMITING IN - // THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT ONE CAN EXPECT - // TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, NO DIGITS - // IN SINGLE AND ONLY 7 DIGITS IN DOUBLE PRECISION ARITHMETIC. - // SIMILAR CONSIDERATIONS HOLD FOR OTHER MACHINES. - // - // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX - // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT - // ROUNDOFF,1.0E-18) IS THE NOMINAL PRECISION AND 10**S REPRE- - // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE - // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), - // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF - // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY - // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN - // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY - // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER - // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, - // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS - // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER - // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY - // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER - // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE - // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, - // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, - // OR -PI/2+P. - // - //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ - // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF - // COMMERCE, 1955. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // BY D. E. AMOS, SAND83-0083, MAY, 1983. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983 - // - // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- - // 1018, MAY, 1985 - // - // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. - // MATH. SOFTWARE, 1986 - // - //***ROUTINES CALLED ZBESH,I1MACH,D1MACH - //***END PROLOGUE ZBESY - - std::complex c1, c2, hci, st; - double elim, exr, exi, ey, tay, xx, yy, ascle, rtol, atol, tol, aa, bb, r1m5; - int i, k, k1, k2, nz, nz1, nz2; - - xx = std::real(z); - yy = std::imag(z); - *ierr = 0; - nz = 0; - - if ((xx == 0.0) && (yy == 0.0)) { *ierr = 1; } - if (fnu < 0.0) { *ierr = 1; } - if ((kode < 1) || (kode > 2)) { *ierr = 1; } - if (n < 1) { *ierr = 1; } - if (*ierr != 0) { return nz; } - - hci = std::complex(0.0, 0.5); - nz1 = besh(z, fnu, kode, 1, n, cy, ierr); - if ((*ierr != 0) && (*ierr != 3)) { return 0; } - - auto cwrk = std::unique_ptr[]> - {new (std::nothrow) std::complex[n]}; - if (cwrk == nullptr) { - *ierr = 6; // Memory allocation failed. - return 0; - } - - nz2 = besh(z, fnu, kode, 2, n, cwrk.get(), ierr); - if ((*ierr != 0) && (*ierr != 3)) { return 0; } - - nz = (nz1 > nz2 ? nz2 : nz1); - if (kode != 2) { - for (i = 1; i < (n+1); i++) - { - cy[i-1] = hci * (cwrk[i-1] - cy[i-1]); - } - return nz; - } - - tol = fmax(d1mach[3], 1e-18); - k1 = i1mach[14]; - k2 = i1mach[15]; - r1m5 = d1mach[4]; - k = ( abs(k1) > abs(k2) ? abs(k2) : abs(k1) ); - // - // ELIM IS THE APPROXIMATE EXPONENTIAL UNDER- AND OVERFLOW LIMIT - // - elim = 2.303 * (k*r1m5 - 3.0); - exr = cos(xx); - exi = sin(xx); - ey = 0.0; - tay = fabs(yy + yy); - if (tay < elim) { ey = exp(-tay); } - if (yy < 0.0) { - /* 90 */ - c1 = std::complex(exr, exi); - c2 = ey*std::complex(exr, -exi); - } else { - c1 = ey*std::complex(exr, exi); - c2 = std::complex(exr, -exi); - } - - nz = 0; - rtol = 1.0 / tol; - ascle = 1e3*d1mach[0]*rtol; - for (i = 1; i< (n+1); i++) - { - aa = std::real(cwrk[i-1]); - bb = std::imag(cwrk[i-1]); - atol = 1.0; - if (fmax(fabs(aa), fabs(bb)) <= ascle) { - aa *= rtol; - bb *= rtol; - atol = tol; - } - - st = std::complex(aa, bb) * c2 * atol; - aa = std::real(cy[i-1]); - bb = std::imag(cy[i-1]); - atol = 1.0; - if (fmax(fabs(aa), fabs(bb)) <= ascle) { - aa *= rtol; - bb *= rtol; - atol = tol; - } - - st -= std::complex(aa, bb) * c1 * atol; - cy[i-1] = st*hci; - if ((st == 0.0) && (ey == 0.0)) { nz += 1; } - } - - return nz; -} - - -inline int binu( - std::complex z, - double fnu, - int kode, - int n, - std::complex *cy, - double rl, - double fnul, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZBINU - //***REFER TO ZBESH,ZBESI,ZBESJ,ZBESK,ZAIRY,ZBIRY - // - // ZBINU COMPUTES THE I FUNCTION IN THE RIGHT HALF Z PLANE - // - //***ROUTINES CALLED AZABS,ZASYI,ZBUNI,ZMLRI,ZSERI,ZUOIK,ZWRSK - //***END PROLOGUE ZBINU - - std::complex cw[2] = { 0. }; - double az, dfnu; - int inw, nlast, nn, nui, nw, nz; - - nz = 0; - az = std::abs(z); - nn = n; - dfnu = fnu + n - 1; - if ((az <= 2.) || (az*az*0.25 <= (dfnu + 1.0))) { - /* GOTO 10 */ - nw = seri(z,fnu, kode, n, cy, tol, elim, alim); - inw = abs(nw); - nz += inw; - nn -= inw; - if (nn == 0) { return nz; } - if (nw >= 0) { return nz; } - dfnu = fnu + nn - 1; - } - /* GOTO 30 conditions*/ - // - // ASYMPTOTIC EXPANSION FOR LARGE Z - // - if (az < rl) { - /* 40 */ - if (dfnu <= 1.0) { - /* 70 */ - // - // MILLER ALGORITHM NORMALIZED BY THE SERIES - // - nw = mlri(z, fnu, kode, n, cy, tol); - if (nw < 0) { - nz = -1; - if (nw == -2) { - nz = -2; - } - return nz; - } - return nz; - } - /* GO TO 50 */ - } else { - if ((dfnu <= 1.0) || (az+az >= dfnu*dfnu)) { - /* 30 */ - // - // ASYMPTOTIC EXPANSION FOR LARGE Z - // - nw = asyi(z, fnu, kode, n, cy, rl, tol, elim, alim); - if (nw < 0) { - nz = -1; - if (nw == -2) { - nz = -2; - } - return nz; - } - return nz; - } - /* GO TO 50 */ - } - /* 50 */ - // - // OVERFLOW AND UNDERFLOW TEST ON I SEQUENCE FOR MILLER ALGORITHM - // - nw = uoik(z, fnu, kode, 1, nn, cy, tol, elim, alim); - if (nw < 0) { - nz = -1; - if (nw == -2) { nz = -2; } - return nz; - } - nz += nw; - nn -= nw; - if (nn == 0) { return nz; } - dfnu = fnu + (nn -1); - /* GOTO 110s handled here */ - if ((dfnu > fnul) || (az > fnul)) { - // - // INCREMENT FNU+NN-1 UP TO FNUL, COMPUTE AND RECUR BACKWARD - // - nui = (int)(fnul-dfnu) + 1; - nui = (nui > 0 ? nui : 0); - nw = buni(z, fnu, kode, nn, cy, nui, &nlast, fnul, tol, elim, alim); - if (nw < 0) { - nz = -1; - if (nw == -2) { nz = -2; } - return nz; - } - nz += nw; - if (nlast == 0) { return nz; } - nn = nlast; - } - /* 60 */ - if (az <= rl) { - /* 70 */ - nw = mlri(z, fnu, kode, n, cy, tol); - if (nw < 0) { - nz = -1; - if (nw == -2) { nz = -2; } - return nz; - } - return nz; - } - /* 80 */ - // - // MILLER ALGORITHM NORMALIZED BY THE WRONSKIAN - // - // - // OVERFLOW TEST ON K FUNCTIONS USED IN WRONSKIAN - // - nw = uoik(z, fnu, kode, 2, 2, cw, tol, elim, alim); - if (nw < 0) { - nz = nn; - /* 90 */ - for (int i=0; i < nn; i++) { cy[i] = 0.0; } - return nz; - } - /* 100 */ - if (nw > 0) { - return -1; - } - nw = wrsk(z, fnu, kode, nn, cy, cw, tol, elim, alim); - if (nw < 0) { - nz = -1; - if (nw == -2) { - nz = -2; - } - return nz; - } - return nz; -} - - -inline std::complex biry( - std::complex z, - int id, - int kode, - int *ierr -) { - - //***BEGIN PROLOGUE ZBIRY - //***DATE WRITTEN 830501 (YYMMDD) - //***REVISION DATE 890801 (YYMMDD) - //***CATEGORY NO. B5K - //***KEYWORDS AIRY FUNCTION,BESSEL FUNCTIONS OF ORDER ONE THIRD - //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES - //***PURPOSE TO COMPUTE AIRY FUNCTIONS BI(Z) AND DBI(Z) FOR COMPLEX Z - //***DESCRIPTION - // - // ***A DOUBLE PRECISION ROUTINE*** - // ON KODE=1, CBIRY COMPUTES THE COMPLEX AIRY FUNCTION BI(Z) OR - // ITS DERIVATIVE DBI(Z)/DZ ON ID=0 OR ID=1 RESPECTIVELY. ON - // KODE=2, A SCALING OPTION CEXP(-AXZTA)*BI(Z) OR CEXP(-AXZTA)* - // DBI(Z)/DZ IS PROVIDED TO REMOVE THE EXPONENTIAL BEHAVIOR IN - // BOTH THE LEFT AND RIGHT HALF PLANES WHERE - // ZTA=(2/3)*Z*CSQRT(Z)=std::complex(XZTA,YZTA) AND AXZTA=ABS(XZTA). - // DEFINITIONS AND NOTATION ARE FOUND IN THE NBS HANDBOOK OF - // MATHEMATICAL FUNCTIONS (REF. 1). - // - // INPUT ZR,ZI ARE DOUBLE PRECISION - // ZR,ZI - Z=std::complex(ZR,ZI) - // ID - ORDER OF DERIVATIVE, ID=0 OR ID=1 - // KODE - A PARAMETER TO INDICATE THE SCALING OPTION - // KODE= 1 RETURNS - // BI=BI(Z) ON ID=0 OR - // BI=DBI(Z)/DZ ON ID=1 - // = 2 RETURNS - // BI=CEXP(-AXZTA)*BI(Z) ON ID=0 OR - // BI=CEXP(-AXZTA)*DBI(Z)/DZ ON ID=1 WHERE - // ZTA=(2/3)*Z*CSQRT(Z)=std::complex(XZTA,YZTA) - // AND AXZTA=ABS(XZTA) - // - // OUTPUT BIR,BII ARE DOUBLE PRECISION - // BIR,BII- COMPLEX ANSWER DEPENDING ON THE CHOICES FOR ID AND - // KODE - // IERR - ERROR FLAG - // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED - // IERR=1, INPUT ERROR - NO COMPUTATION - // IERR=2, OVERFLOW - NO COMPUTATION, REAL(Z) - // TOO LARGE ON KODE=1 - // IERR=3, CABS(Z) LARGE - COMPUTATION COMPLETED - // LOSSES OF SIGNIFCANCE BY ARGUMENT REDUCTION - // PRODUCE LESS THAN HALF OF MACHINE ACCURACY - // IERR=4, CABS(Z) TOO LARGE - NO COMPUTATION - // COMPLETE LOSS OF ACCURACY BY ARGUMENT - // REDUCTION - // IERR=5, ERROR - NO COMPUTATION, - // ALGORITHM TERMINATION CONDITION NOT MET - // - //***LONG DESCRIPTION - // - // BI AND DBI ARE COMPUTED FOR CABS(Z).GT.1.0 FROM THE I BESSEL - // FUNCTIONS BY - // - // BI(Z)=C*SQRT(Z)*( I(-1/3,ZTA) + I(1/3,ZTA) ) - // DBI(Z)=C * Z * ( I(-2/3,ZTA) + I(2/3,ZTA) ) - // C=1.0/SQRT(3.0) - // ZTA=(2/3)*Z**(3/2) - // - // WITH THE POWER SERIES FOR CABS(Z).LE.1.0. - // - // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- - // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z IS LARGE, LOSSES - // OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. CONSEQUENTLY, IF - // THE MAGNITUDE OF ZETA=(2/3)*Z**1.5 EXCEEDS U1=SQRT(0.5/UR), - // THEN LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR - // FLAG IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS - // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. - // ALSO, IF THE MAGNITUDE OF ZETA IS LARGER THAN U2=0.5/UR, THEN - // ALL SIGNIFICANCE IS LOST AND IERR=4. IN ORDER TO USE THE INT - // FUNCTION, ZETA MUST BE FURTHER RESTRICTED NOT TO EXCEED THE - // LARGEST INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF ZETA - // MUST BE RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, - // AND U3 ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE - // PRECISION ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE - // PRECISION ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMIT- - // ING IN THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT THE MAG- - // NITUDE OF Z CANNOT EXCEED 3.1E+4 IN SINGLE AND 2.1E+6 IN - // DOUBLE PRECISION ARITHMETIC. THIS ALSO MEANS THAT ONE CAN - // EXPECT TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, - // NO DIGITS IN SINGLE PRECISION AND ONLY 7 DIGITS IN DOUBLE - // PRECISION ARITHMETIC. SIMILAR CONSIDERATIONS HOLD FOR OTHER - // MACHINES. - // - // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX - // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT - // ROUNDOFF,1.0E-18) IS THE NOMINAL PRECISION AND 10**S REPRE- - // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE - // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), - // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF - // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY - // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN - // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY - // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER - // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, - // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS - // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER - // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY - // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER - // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE - // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, - // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, - // OR -PI/2+P. - // - //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ - // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF - // COMMERCE, 1955. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983 - // - // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- - // 1018, MAY, 1985 - // - // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. - // MATH. SOFTWARE, 1986 - // - //***ROUTINES CALLED ZBINU,AZABS,ZDIV,AZSQRT,D1MACH,I1MACH - //***END PROLOGUE ZBIRY - - std::complex bi, csq, s1, s2, trm1, trm2, zta, z3; - double aa, ad, ak, alim, atrm, az, az3, bb, bk, ck, dig, dk, d1, d2,\ - elim, fid, fmr, fnu, fnul, rl, r1m5, sfac, tol, zi, zr; - int k, k1, k2, nz; - std::complex cy[2] = { 0.0 }; - double tth = 2. / 3.; - double c1 = 0.614926627446000735150922369; /* 1/( 3**(1/6) Gamma(2/3)) */ - double c2 = 0.448288357353826357914823710; /* 3**(1/6) / Gamma(1/3) */ - double coef = 0.577350269189625764509148780; /* sqrt( 1 / 3) */ - double pi = 3.141592653589793238462643383; - - *ierr = 0; - nz = 0; - if ((id < 0) || (id > 1)) { *ierr= 1; } - if ((kode < 1) || (kode > 2)) { *ierr= 1; } - if ( *ierr != 0) { return 0.0;} - az = std::abs(z); - tol = fmax(d1mach[3], 1e-18); - fid = id; - if (az <= 1.0) { - // - // POWER SERIES FOR ABS(Z) <= 1. - // - s1 = 1.0; - s2 = 1.0; - if (az < tol) { - aa = c1*(1.0 - fid) + fid*c2; - return aa; - } - aa = az*az; - if (aa >= tol/az) { - trm1 = 1.0; - trm2 = 1.0; - atrm = 1.0; - z3 = z*z*z; - az3 = az * aa; - ak = 2.0 + fid; - bk = 3.0 - fid - fid; - ck = 4.0 - fid; - dk = 3.0 + fid + fid; - d1 = ak * dk; - d2 = bk * ck; - ad = fmin(d1,d2); - ak = 24.0 + 9.0*fid; - bk = 30.0 - 9.0*fid; - for (k = 1; k < 26; k++) - { - trm1 *= z3/d1; - s1 += trm1; - trm2 *= z3/d2; - s2 += trm2; - atrm *= az3 / ad; - d1 += ak; - d2 += bk; - ad = fmin(d1, d2); - if (atrm < tol*ad) { break; } - ak += 18.0; - bk += 18.0; - } - /* 30 */ - } - /* 40 */ - if (id != 1) { - bi = s1*c1 + z*s2*c2; - if (kode == 1) { return bi; } - zta = z*std::sqrt(z)*tth; - aa = -fabs(std::real(zta)); - bi *= exp(aa); - return bi; - } - /* 50 */ - bi = s2*c2; - if (az > tol) { bi += z*z*s1*c1/(1.0 + fid ); } - if (kode == 1) { return bi; } - zta = z*std::sqrt(z)*tth; - aa = -fabs(std::real(zta)); - bi *= exp(aa); - return bi; - } - /* 70 */ - // - // CASE FOR ABS(Z) > 1.0 - // - fnu = (1.0 + fid) / 3.0; - // - // SET PARAMETERS RELATED TO MACHINE CONSTANTS. - // TOL IS THE APPROXIMATE UNIT ROUNDOFF LIMITED TO 1.0E-18. - // ELIM IS THE APPROXIMATE EXPONENTIAL OVER- AND UNDERFLOW LIMIT. - // EXP(-ELIM) < EXP(-ALIM)=EXP(-ELIM)/TOL AND - // EXP(ELIM) > EXP(ALIM)=EXP(ELIM)*TOL ARE INTERVALS NEAR - // UNDERFLOW AND OVERFLOW LIMITS WHERE SCALED ARITHMETIC IS DONE. - // RL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC EXPANSION FOR LARGE Z. - // DIG = NUMBER OF BASE 10 DIGITS IN TOL = 10**(-DIG). - // FNUL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC SERIES FOR LARGE FNU. - // - k1 = i1mach[14]; - k2 = i1mach[15]; - r1m5 = d1mach[4]; - k = ( abs(k1) > abs(k2) ? abs(k2) : abs(k1) ); - elim = 2.303 * (k*r1m5 - 3.0); - k1 = i1mach[13] - 1; - aa = r1m5*k1; - dig = fmin(aa, 18.0); - aa *= 2.303; - alim = elim + fmax(-aa, -41.45); - rl = 1.2*dig + 3.0; - fnul = 10.0 + 6.0*(dig - 3.0); - // - // TEST FOR RANGE - // - aa = 0.5 / tol; - bb = i1mach[8] * 0.5; - aa = fmin(aa, bb); - aa = pow(aa, tth); - if (az > aa) { *ierr = 4; return 0.0; } - aa = sqrt(aa); - if (az > aa) { *ierr = 3; } - csq = std::sqrt(z); - zta = z*csq*tth; - // - // RE(ZTA) <= 0 WHEN RE(Z) < 0, ESPECIALLY WHEN IM(Z) IS SMALL - // - sfac = 1.0; - zi = std::imag(z); - zr = std::real(z); - ak = std::imag(zta); - if (zr < 0.0) { - bk = std::real(zta); - ck = -fabs(bk); - zta = std::complex(ck, ak); - } - /* 80 */ - if ((zi == 0.0) && (zr <= 0.0)) { zta = std::complex(0.0, ak); } - /* 90 */ - aa = std::real(zta); - if (kode != 2) { - // - // OVERFLOW TEST - // - bb = fabs(aa); - if (bb >= alim) { - bb += 0.25*log(az); - sfac = tol; - if (bb > elim) { *ierr = 2; return 0.0; } - } - } - /* 100 */ - fmr = 0.0; - if ((aa < 0.0) || (zr <= 0.0)) { - fmr = pi; - if (zi < 0.0) { fmr = -pi; } - zta = -zta; - } - /* 110 */ - // - // AA=FACTOR FOR ANALYTIC CONTINUATION OF I(FNU,ZTA) - // KODE=2 RETURNS EXP(-ABS(XZTA))*I(FNU,ZTA) FROM CBINU - // - nz = binu(zta, fnu, kode, 1, cy, rl, fnul, tol, elim, alim); - if (nz < 0) { - if (nz == -1) { - *ierr = 2; - } else { - *ierr = 5; - } - return 0.0; - } - aa = fmr*fnu; - z3 = sfac; - s1 = cy[0] * std::complex(cos(aa), sin(aa)) * z3; - fnu = (2.0 - fid) / 3.0; - nz = binu(zta, fnu, kode, 2, cy, rl, fnul, tol, elim, alim); - cy[0] *= z3; - cy[1] *= z3; - // - // BACKWARD RECUR ONE STEP FOR ORDERS -1/3 OR -2/3 - // - s2 = cy[0] * (fnu+fnu) / zta + cy[1]; - aa = fmr * (fnu - 1.0); - s1 = (s1 + s2*std::complex(cos(aa), sin(aa)))*coef; - if (id != 1) { - s1 *= csq; - bi = s1 / sfac; - return bi; - } - /* 120 */ - s1 *= z; - bi = s1 / sfac; - return bi; -} - - -inline int bknu( - std::complex z, - double fnu, - int kode, - int n, - std::complex *y, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZBKNU - //***REFER TO ZBESI,ZBESK,ZAIRY,ZBESH - // - // ZBKNU COMPUTES THE K BESSEL FUNCTION IN THE RIGHT HALF Z PLANE. - // - //***ROUTINES CALLED DGAMLN,I1MACH,D1MACH,ZKSCL,ZSHCH,ZUCHK,AZABS,ZDIV, - // AZEXP,AZLOG,ZMLT,AZSQRT - //***END PROLOGUE ZBKNU - - std::complex cch, ck, coef, crsc, cs, cscl, csh, cz,\ - f, fmu, p, pt, p1, p2, q, rz, smu, st, s1, s2, zd; - double aa, ak, ascle, a1, a2, bb, bk, caz, dnu, dnu2, etest, fc, fhs,\ - fk, fks, g1, g2, p2i, p2m, p2r, rk, s, tm, t1, t2, xx, yy,\ - elm, xd, yd, alas, as; - int iflag, inu, k, kflag, kk, koded, j, ic, inub, i = 1; - std::complex cy[2]; - - int kmax =30; - double r1 = 2.; - double pi = 3.14159265358979324; - double rthpi = 1.25331413731550025; - double spi = 1.90985931710274403; - double hpi = 1.57079632679489662; - double fpi = 1.89769999331517738; - double tth = 2. / 3.; - double cc[8] = { - 5.77215664901532861e-01, -4.20026350340952355e-02, - -4.21977345555443367e-02, 7.21894324666309954e-03, - -2.15241674114950973e-04, -2.01348547807882387e-05, - 1.13302723198169588e-06, 6.11609510448141582e-09 - }; - xx = std::real(z); - yy = std::imag(z); - caz = std::abs(z); - cscl = 1. / tol; - crsc = tol; - std::complex css[3] = {cscl, 1., crsc}; - std::complex csr[3] = {crsc, 1., cscl}; - double bry[3] = {1e3*d1mach[0]/tol, tol/(1e3*d1mach[0]), d1mach[1]}; - int nz = 0; - iflag = 0; - koded = kode; - rz = 2. / z; - inu = (int)(fnu + 0.5); - dnu = fnu - inu; - // Definitions for silencing initialization warnings. - s1 = 0.0; - s2 = 0.0; - ck = 0.0; - dnu2 = 0.0; - if (fabs(dnu) != 0.5) { - if (fabs(dnu) > tol) { dnu2 = dnu * dnu; } - if (caz <= r1) { - // - // SERIES FOR ABS(Z) <= R1 - // - fc = 1.; - smu = std::log(rz); - fmu = smu * dnu; - csh = std::sinh(fmu); - cch = std::cosh(fmu); - if (dnu != 0.0) { - fc = dnu * pi; - fc *= 1. / sin(fc); - smu = csh / dnu; - } - a2 = 1. + dnu; - // - // GAM(1-Z)*GAM(1+Z)=PI*Z/SIN(PI*Z), T1=1/GAM(1-DNU), T2=1/GAM(1+DNU) - // - t2 = exp(-gamln(a2)); - t1 = 1. / (t2*fc); - if (fabs(dnu) <= 0.1) { - // - // SERIES FOR F0 TO RESOLVE INDETERMINACY FOR SMALL ABS(DNU) - // - ak = 1.; - s = cc[0]; - for (int k = 2; k < 9; k++) - { - ak *= dnu2; - tm = cc[k-1] * ak; - s += tm; - if (fabs(tm) < tol) { break; } - } - g1 = -s; - } else { - g1 = (t1-t2) / (dnu+dnu); - } - g2 = 0.5 * (t1+t2); - f = fc*(g1*cch + smu*g2); - pt = std::exp(fmu); - p = (0.5 / t2) * pt; - q = (0.5 / t1) / pt; - s1 = f; - s2 = p; - ak = 1.0; - a1 = 1.0; - ck = 1.0; - bk = 1.0 - dnu2; - if ((inu <= 0) && (n <= 1)) { - // - // GENERATE K(FNU,Z), 0.0D0 <= FNU < 0.5D0 AND N=1 - // - if (caz >= tol) { - cz = z * z * 0.25; - t1 = 0.25 * caz * caz; - do { - f = (f*ak + p + q) / bk; - p = p / (ak-dnu); - q = q / (ak+dnu); - rk = 1.0 / ak; - ck *= cz * rk; - s1 += ck * f; - a1 *= t1 * rk; - bk += ak + ak + 1.0; - ak += 1.0; - } while (a1 > tol); - } - y[0] = s1; - if (koded == 1) { return nz; } - y[0] = s1 * std::exp(z); - return nz; - } - // - // GENERATE K(DNU,Z) AND K(DNU+1,Z) FOR FORWARD RECURRENCE - // - if (caz >= tol) { - cz = z * z * 0.25; - t1 = 0.25 * caz * caz; - do { - f = (f*ak + p + q) / bk; - p *= 1.0 / (ak - dnu); - q *= 1.0 / (ak + dnu); - rk = 1. / ak; - ck *= cz * rk; - s1 += ck * f; - s2 += ck * (p - f*ak); - a1 *= t1 * rk; - bk += ak + ak + 1.0; - ak += 1.0; - } while (a1 > tol); - } - kflag = 2; - bk = std::real(smu); - a1 = fnu + 1.; - ak = a1 * fabs(bk); - if (ak > alim) { kflag = 3; } - p2 = s2 * css[kflag-1]; - s2 = p2 * rz; - s1 *= css[kflag-1]; - if (koded != 1) { - f = std::exp(z); - s1 *= f; - s2 *= f; - } - goto L100; - } - } - // - // IFLAG=0 MEANS NO UNDERFLOW OCCURRED - // IFLAG=1 MEANS AN UNDERFLOW OCCURRED- COMPUTATION PROCEEDS WITH - // KODED=2 AND A TEST FOR ON SCALE VALUES IS MADE DURING FORWARD - // RECURSION - // - coef = rthpi / std::sqrt(z); - kflag = 2; - if (koded != 2) { - if (xx > alim) { - koded = 2; - iflag = 1; - kflag = 2; - } else { - a1 = exp(-xx)*std::real(css[kflag-1]); - pt = a1*std::complex(cos(yy), -sin(yy)); - coef *= pt; - } - } - - if (fabs(dnu) == 0.5) { - s1 = coef; - s2 = coef; - goto L100; - } -// -// MILLER ALGORITHM FOR ABS(Z) > R1 -// - ak = fabs(cos(pi*dnu)); - if (ak == 0.) { - s1 = coef; - s2 = coef; - goto L100; - } - fhs = fabs(0.25 - dnu2); - if (fhs == 0.) { - s1 = coef; - s2 = coef; - goto L100; - } -// -// COMPUTE R2=F(E). IF ABS(Z) >= R2, USE FORWARD RECURRENCE TO -// DETERMINE THE BACKWARD INDEX K. R2=F(E) IS A STRAIGHT LINE ON -// 12 <= E <= 60. E IS COMPUTED FROM 2**(-E)=B**(1-DIGITS(0.0_dp))= -// TOL WHERE B IS THE BASE OF THE ARITHMETIC. -// - t1 = (i1mach[13] - 1)*d1mach[4]*(log(10)/log(2)); - t1 = fmin(fmax(t1, 12.0), 60.0); - t2 = tth * t1 - 6.0; - if (xx == 0.) { - t1 = hpi; - } else { - t1 = fabs(atan(yy/xx)); - } - if (t2 <= caz) { - // - // FORWARD RECURRENCE LOOP WHEN ABS(Z) >= R2 - // - etest = ak / (pi*caz*tol); - fk = 1.0; - if (etest < 1.0) { goto L80; } - fks = 2.0; - rk = caz + caz + 2.0; - a1 = 0.0; - a2 = 1.0; - for (i = 1; i < (kmax+1); i++) - { - ak = fhs / fks; - bk = rk / (fk + 1.0); - tm = a2; - a2 = bk * a2 - ak * a1; - a1 = tm; - rk += 2.; - fks += fk + fk + 2.0; - fhs += fk + fk; - fk += 1.0; - tm = fabs(a2)*fk; - if (etest < tm) { - /* goto 160 */ - break; - } - if (i == kmax) { - /* Didn't break so goes to 310 */ - return -2; - } - } - - /* 160 */ - fk += spi * t1 * sqrt(t2/caz); - fhs = fabs(0.25 - dnu2); - } else { - // - // COMPUTE BACKWARD INDEX K FOR ABS(Z) < R2 - // - a2 = sqrt(caz); - ak *= fpi / (tol*sqrt(a2)); - aa = 3.0 * t1 / (1.0 + caz); - bb = 14.7 * t1 / (28.0 + caz); - ak = (log(ak) + caz*cos(aa)/(1.0 + 0.008*caz)) / cos(bb); - fk = 0.12125 * ak * ak / caz + 1.5; - } -L80: - // - // BACKWARD RECURRENCE LOOP FOR MILLER ALGORITHM - // - k = (int)fk; - fk = (double)k; - fks = fk * fk; - p1 = 0.0; - p2 = tol; - cs = p2; - for (i=1; i < (k+1); i++) - { - a1 = fks - fk; - a2 = (fks+fk) / (a1+fhs); - rk = 2.0 / (fk + 1.); - t1 = (fk + xx) * rk; - t2 = yy * rk; - pt = p2; - p2 = (p2 * std::complex(t1, t2) - p1) * a2; - p1 = pt; - cs += p2; - fks = a1 - fk + 1.0; - fk -= 1.0; - } - - // - // COMPUTE (P2/CS)=(P2/ABS(CS))*(CONJG(CS)/ABS(CS)) FOR BETTER SCALING - // - tm = std::abs(cs); - pt = 1.0 / tm; - s1 = pt * p2; - cs = conj(cs) * pt; - s1 *= coef * cs; - if ((inu <= 0) && (n <= 1)) { - zd = z; - if (iflag == 1) { goto L190; } - goto L130; - } - // - // COMPUTE P1/P2=(P1/ABS(P2)*CONJG(P2)/ABS(P2) FOR SCALING - // - tm = std::abs(p2); - pt = 1.0 / tm; - p1 = pt * p1; - p2 = conj(p2) * pt; - pt = p1 * p2; - s2 = s1 * (1. + (dnu+0.5 - pt)/z); - // - // FORWARD RECURSION ON THE THREE TERM RECURSION RELATION WITH - // SCALING NEAR EXPONENT EXTREMES ON KFLAG=1 OR KFLAG=3 - // -L100: - ck = (dnu + 1.)*rz; - if (n == 1) { inu -= 1; } - if (inu <= 0) { - if (n <= 1) { s1 = s2; } - zd = z; - if (iflag == 1) { goto L190; } - goto L130; - } - inub = 1; - if (iflag == 1) { goto L160; } -L110: - p1 = csr[kflag-1]; - ascle = bry[kflag-1]; - for (i = inub; i < inu+1; i++) - { - st = s2; - s2 = ck*s2 + s1; - s1 = st; - ck += rz; - if (kflag < 3) { - p2 = s2*p1; - p2m = fmax(fabs(std::real(p2)), fabs(std::imag(p2))); - if (p2m > ascle) { - kflag += 1; - ascle = bry[kflag-1]; - s1 *= p1; - s2 = p2; - s1 *= css[kflag-1]; - s2 *= css[kflag-1]; - p1 = csr[kflag-1]; - } - } - } - if (n == 1) { s1 = s2; } - -L130: - y[0] = s1 * csr[kflag-1]; - if (n == 1) { return nz; } - y[1] = s2 * csr[kflag-1]; - if (n == 2) { return nz; } - kk = 2; -L140: - kk += 1; - if (kk > n) { return nz; } - p1 = csr[kflag-1]; - ascle = bry[kflag-1]; - for (i = kk; i < (n+1); i++) - { - p2 = s2; - s2 = ck*s2 + s1; - s1 = p2; - ck += rz; - p2 = s2*p1; - y[i-1] = p2; - if (kflag < 3) { - p2m = fmax(fabs(std::real(p2)), fabs(std::imag(p2))); - if (p2m > ascle) { - kflag += 1; - ascle = bry[kflag-1]; - s1 *= p1; - s2 = p2; - s1 *= css[kflag-1]; - s2 *= css[kflag-1]; - p1 = csr[kflag-1]; - } - } - } - return nz; -// -// IFLAG=1 CASES, FORWARD RECURRENCE ON SCALED VALUES ON UNDERFLOW -// -L160: - elm = exp(-elim); - ascle = bry[0]; - zd = z; - xd = xx; - yd = yy; - ic = -1; - j = 2; - for (i = 1; i < (inu+1); i++) - { - st = s2; - s2 = ck*s2 + s1; - s1 = st; - ck += rz; - as = std::abs(s2); - alas = log(as); - p2r = alas - xd; - if (p2r >= -elim) { - p2 = -zd + std::log(s2); - p2r = std::real(p2); - p2i = std::imag(p2); - p2m = exp(p2r) / tol; - p1 = p2m * std::complex(cos(p2i), sin(p2i)); - if (!(uchk(p1, ascle, tol))) { - j = 3 - j; - cy[j-1] = p1; - if (ic == i-1) { goto L180; } - ic = i; - continue; - } - } - if (alas >= 0.5 * elim) { - xd -= elim; - s1 *= elm; - s2 *= elm; - zd = std::complex(xd, yd); - } - } - if (n == 1) { s1 = s2; } - goto L190; -L180: - kflag = 1; - inub = i + 1; - s2 = cy[j-1]; - j = 3 - j; - s1 = cy[j-1]; - if (inub <= inu) { goto L110; } - if (n == 1) { s1 = s2; } - goto L130; -L190: - y[0] = s1; - if (n != 1) { y[1] = s2; } - ascle = bry[0]; - nz = kscl(zd, fnu, n, &y[0], rz, &ascle, tol, elim); - inu = n - nz; - if (inu <= 0) { return nz; } - kk = nz + 1; - s1 = y[kk-1]; - y[kk-1] = s1 * csr[0]; - if (inu == 1) { return nz; } - kk = nz + 2; - s2 = y[kk-1]; - y[kk-1] = s2 * csr[0]; - if (inu == 2) { return nz; } - t2 = fnu + (kk-1); - ck = t2 * rz; - kflag = 1; - goto L140; -} - - -inline int buni( - std::complex z, - double fnu, - int kode, - int n, - std::complex *y, - int nui, - int *nlast, - double fnul, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZBUNI - //***REFER TO ZBESI,ZBESK - // - // ZBUNI COMPUTES THE I BESSEL FUNCTION FOR LARGE CABS(Z).GT. - // FNUL AND FNU+N-1.LT.FNUL. THE ORDER IS INCREASED FROM - // FNU+N-1 GREATER THAN FNUL BY ADDING NUI AND COMPUTING - // ACCORDING TO THE UNIFORM ASYMPTOTIC EXPANSION FOR I(FNU,Z) - // ON IFORM=1 AND THE EXPANSION FOR J(FNU,Z) ON IFORM=2 - // - //***ROUTINES CALLED ZUNI1,ZUNI2,AZABS,D1MACH - //***END PROLOGUE ZBUNI - - std::complex cscl, cscr, rz, st, s1, s2; - double ax, ay, dfnu, fnui, gnu, xx, yy, ascle, str, sti, stm; - int i, iflag, iform, k, nl, nw, nz; - std::complex cy[2] = { 0.0 }; - double bry[3] = { 0.0 }; - - nz = 0; - xx = std::real(z); - yy = std::imag(z); - ax = fabs(xx) + sqrt(3.); - ay = fabs(yy); - iform = 1; - if (ay > ax) { iform = 2; } - if (nui == 0) { - if (iform != 2) { - uni1(z, fnu, kode, n, y, &nw, nlast, fnul, tol, elim, alim); - } else { - uni2(z, fnu, kode, n, y, &nw, nlast, fnul, tol, elim, alim); - } - if (nw < 0) { - nz = -1; - if (nw == -2) { nz = -2; } - return nz; - } - return nw; - } - - fnui = nui; - dfnu = fnu + (n - 1); - gnu = dfnu + fnui; - if (iform != 2) { - // - // ASYMPTOTIC EXPANSION FOR I(FNU,Z) FOR LARGE FNU APPLIED IN - // -PI/3 <= ARG(Z) <= PI/3 - // - uni1(z, gnu, kode, 2, cy, &nw, nlast, fnul, tol, elim, alim); - } else { - uni2(z, gnu, kode, 2, cy, &nw, nlast, fnul, tol, elim, alim); - } - if (nw >= 0) { - if (nw != 0) { *nlast = n; return nz; } - ay = std::abs(cy[0]); - // - // SCALE BACKWARD RECURRENCE, BRY(3) IS DEFINED BUT NEVER USED - // - bry[0] = 1e3*d1mach[0] / tol; - bry[1] = tol / 1e3*d1mach[0]; - bry[2] = bry[1]; - iflag = 2; - ascle = bry[1]; - ax = 1.0; - cscl = ax; - if (ay <= bry[0]) { - iflag = 1; - ascle = bry[0]; - ax = 1.0 / tol; - cscl = ax; - } else { - if (ay >= bry[1]) { - iflag = 3; - ascle = bry[2]; - ax = tol; - cscl = ax; - - } - } - ay = 1.0 / ax; - cscr = ay; - s1 = cy[1] * cscl; - s2 = cy[0] * cscl; - rz = 2.0 / z; - for (i = 1; i < (nui+1); i++) - { - st = s2; - s2 = (dfnu +fnui)*rz*st + s1; - s1 = st; - fnui -= 1.0; - if (iflag < 3) { - st = s2 * cscr; - str = fabs(std::real(st)); - sti = fabs(std::imag(st)); - stm = fmax(str, sti); - if (stm > ascle) { - iflag += 1; - ascle = bry[iflag-1]; - s1 *= cscr; - s2 = st; - ax *= tol; - ay = 1.0 / ax; - cscl = ax; - cscr = ay; - s1 *= cscl; - s2 *= cscl; - } - } - } - y[n-1] = s2*cscr; - if (n == 1) { return nz; } - nl = n-1; - fnui = nl; - k = nl; - for (i = 0; i < (nl+1); i++) - { - st = s2; - s2 = (fnu + fnui)*rz*s2 + s1; - s1 = st; - st = s2 * cscr; - y[k-1] = st; - fnui -= 1.0; - k -= 1; - if (iflag < 3) { - st = s2 * cscr; - str = fabs(std::real(st)); - sti = fabs(std::imag(st)); - stm = fmax(str, sti); - if (stm > ascle) { - iflag += 1; - ascle = bry[iflag-1]; - s1 *= cscr; - s2 = st; - ax *= tol; - ay = 1.0 / ax; - cscl = ax; - cscr = ay; - s1 *= cscl; - s2 *= cscl; - } - } - } - return nz; - } - nz = -1; - if (nw == -2) { nz = -2; } - return nz; -} - - -inline int bunk( - std::complex z, - double fnu, - int kode, - int mr, - int n, - std::complex *y, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZBUNK - //***REFER TO ZBESK,ZBESH - // - // ZBUNK COMPUTES THE K BESSEL FUNCTION FOR FNU.GT.FNUL. - // ACCORDING TO THE UNIFORM ASYMPTOTIC EXPANSION FOR K(FNU,Z) - // IN ZUNK1 AND THE EXPANSION FOR H(2,FNU,Z) IN ZUNK2 - // - //***ROUTINES CALLED ZUNK1,ZUNK2 - //***END PROLOGUE ZBUNK - - double ax, ay; - - int nz = 0; - ax = fabs(std::real(z)) * 1.7321; - ay = fabs(std::imag(z)); - - if (ay <= ax) { - // - // Asymptotic expansion for K(FNU,Z) for large FNU applied in - // -PI/3 <= ARG(Z) <= PI/3 - // - nz = unk1(z, fnu, kode, mr, n, y, tol, elim, alim); - } else { - // - // Asymptotic expansion for H(2, FNU, Z*EXP(M*HPI)) for large FNU - // applied in PI/3 < ABS(ARG(Z)) <= PI/2 where M = +I or -I and HPI = PI/2 - // - nz = unk2(z, fnu, kode, mr, n, y, tol, elim, alim); - } - return nz; -} - - -inline double gamln(double z) { - - //***BEGIN PROLOGUE DGAMLN - //***DATE WRITTEN 830501 (YYMMDD) - //***REVISION DATE 830501 (YYMMDD) - //***CATEGORY NO. B5F - //***KEYWORDS GAMMA FUNCTION,LOGARITHM OF GAMMA FUNCTION - //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES - //***PURPOSE TO COMPUTE THE LOGARITHM OF THE GAMMA FUNCTION - //***DESCRIPTION - // - // **** A DOUBLE PRECISION ROUTINE **** - // DGAMLN COMPUTES THE NATURAL LOG OF THE GAMMA FUNCTION FOR - // Z.GT.0. THE ASYMPTOTIC EXPANSION IS USED TO GENERATE VALUES - // GREATER THAN ZMIN WHICH ARE ADJUSTED BY THE RECURSION - // G(Z+1)=Z*G(Z) FOR Z.LE.ZMIN. THE FUNCTION WAS MADE AS - // PORTABLE AS POSSIBLE BY COMPUTIMG ZMIN FROM THE NUMBER OF BASE - // 10 DIGITS IN A WORD, RLN=AMAX1(-ALOG10(R1MACH(4)),0.5E-18) - // LIMITED TO 18 DIGITS OF (RELATIVE) ACCURACY. - // - // SINCE INTEGER ARGUMENTS ARE COMMON, A TABLE LOOK UP ON 100 - // VALUES IS USED FOR SPEED OF EXECUTION. - // - // DESCRIPTION OF ARGUMENTS - // - // INPUT Z IS D0UBLE PRECISION - // Z - ARGUMENT, Z.GT.0.0D0 - // - // OUTPUT DGAMLN IS DOUBLE PRECISION - // DGAMLN - NATURAL LOG OF THE GAMMA FUNCTION AT Z.NE.0.0D0 - // IERR - ERROR FLAG - // IERR=0, NORMAL RETURN, COMPUTATION COMPLETED - // IERR=1, Z.LE.0.0D0, NO COMPUTATION - // - // - //***REFERENCES COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // BY D. E. AMOS, SAND83-0083, MAY, 1983. - //***ROUTINES CALLED I1MACH,D1MACH - //***END PROLOGUE DGAMLN - - int i1m, mz; - double fln, fz, rln, s, tlg, trm, tst, t1, wdtol, zdmy, zinc, zm, zmin, zp, zsq; - const double con = 1.83787706640934548; /* LN(2*PI) */ - int nz = 0; - if (z > 0.0) { - if (z <= 101.0) { - nz = (int)z; - fz = z - nz; - if (fz <= 0.0) { - if (nz <= 100) { - return dgamln_gln[nz-1]; - } - } - } - wdtol = fmax(d1mach[3], 1e-18); - i1m = i1mach[13]; - rln = d1mach[4]*i1m; - fln = fmax(fmin(rln, 20.), 3.0) - 3.0; - zm = 1.8 + 0.3875*fln; - mz = ((int)zm) + 1; - zmin = mz; - zdmy = z; - zinc = 0.0; - if (z < zmin){ - zinc = zmin - nz; - zdmy = z + zinc; - } - zp = 1. / zdmy; - t1 = dgamln_cf[0]*zp; - s = t1; - if (zp >= wdtol) { - zsq = zp*zp; - tst = t1*wdtol; - for (int i = 2; i < 23; i++) - { - zp *= zsq; - trm = dgamln_cf[i-1] * zp; - if (fabs(trm) < tst) { break; } - s += trm; - } - } - - if (zinc == 0.) { - tlg = log(z); - return z*(tlg-1.0) + 0.5*(con - tlg) + s; - } - zp = 1.0; - nz = (int)zinc; - for (int i = 0; i < nz; i++) - { - zp *= (z + i); - } - tlg = log(zdmy); - return zdmy*(tlg-1.0) - log(zp) + 0.5*(con-tlg) + s; - } - // Zero or negative argument - return NAN; -} - - -inline int mlri( - std::complex z, - double fnu, - int kode, - int n, - std::complex *y, - double tol -) { - - //***BEGIN PROLOGUE ZMLRI - //***REFER TO ZBESI,ZBESK - // - // ZMLRI COMPUTES THE I BESSEL FUNCTION FOR RE(Z).GE.0.0 BY THE - // MILLER ALGORITHM NORMALIZED BY A NEUMANN SERIES. - // - //***ROUTINES CALLED DGAMLN,D1MACH,AZABS,AZEXP,AZLOG,ZMLT - //***END PROLOGUE ZMLRI - - std::complex ck, cnorm, pt, p1, p2, rz, sum; - double ack, ak, ap, at, az, bk, fkap, fkk, flam, fnf, rho,\ - rho2, scle, tfnf, tst, x; - int i, iaz, ifnu, inu, itime, k, kk, km, m, nz; - scle = d1mach[0] / tol; - nz = 0; - az = std::abs(z); - x = std::real(z); - iaz = (int)az; - ifnu = (int)fnu; - inu = ifnu + n - 1; - at = iaz + 1; - ck = at / z; - rz = 2. / z; - p1 = 0.; - p2 = 1.; - ack = (at + 1.0) / az; - rho = ack + sqrt(ack*ack - 1.); - rho2 = rho * rho; - tst = (rho2 + rho2) / ((rho2 - 1.0)*(rho - 1.0)); - tst /= tol; - // - // COMPUTE RELATIVE TRUNCATION ERROR INDEX FOR SERIES - // - ak = at; - i = 1; - for (i = 1; i < 81; i++ ) - { - pt = p2; - p2 = p1 - ck * p2; - p1 = pt; - ck += rz; - ap = std::abs(p2); - if (ap > tst*ak*ak) { break; } - ak += 1.0; - if (i == 80) { - /* Exhausted loop without break */ - return -2; - } - } - i += 1; - k = 0; - if (inu >= iaz) { - // - // COMPUTE RELATIVE TRUNCATION ERROR FOR RATIOS - // - p1 = 0.0; - p2 = 1.0; - at = inu + 1; - ck = at / z; - ack = at / az; - tst = sqrt(ack / tol); - itime = 1; - k = 1; - for (k = 1; k < 81; k++ ) - { - pt = p2; - p2 = p1 - ck * p2; - p1 = pt; - ck += rz; - ap = std::abs(p2); - if (ap >= tst) { - if (itime == 2) { break; } - ack = std::abs(ck); - flam = ack + sqrt(ack*ack - 1.0); - fkap = ap / std::abs(p1); - rho = fmin(flam, fkap); - tst *= sqrt(rho / (rho*rho - 1.0)); - itime = 2; - } - if (k == 80) { - /* Exhausted loop without break */ - return -2; - } - } - } - // - // BACKWARD RECURRENCE AND SUM NORMALIZING RELATION - // - k += 1; - kk = fmax(i+iaz, k+inu); - fkk = kk; - p1 = 0.0; - // - // SCALE P2 AND SUM BY SCLE - // - p2 = scle; - fnf = fnu - ifnu; - tfnf = fnf + fnf; - bk = gamln(fkk + tfnf + 1.0) - gamln(fkk + 1.0) - gamln(tfnf + 1.0); - bk = exp(bk); - sum = 0.; - km = kk - inu; - for (i = 1; i < (km+1); i++) - { - pt = p2; - p2 = p1 + (fkk + fnf)*rz*p2; - p1 = pt; - ak = 1. - tfnf / (fkk+tfnf); - ack = bk*ak; - sum += (ack + bk)*p1; - bk = ack; - fkk -= 1.; - } - y[n-1] = p2; - if (n != 1) { - for (i = 2; i < (n+1); i++) - { - pt = p2; - p2 = p1 + (fkk + fnf)*rz*p2; - p1 = pt; - ak = 1. - tfnf / (fkk+tfnf); - ack = bk*ak; - sum += (ack + bk)*p1; - bk = ack; - fkk -= 1.; - m = n - i + 1; - y[m-1] = p2; - } - } - if (ifnu > 0) { - for (i = 1; i < (ifnu+1); i++) - { - pt = p2; - p2 = p1 + (fkk + fnf)*rz*p2; - p1 = pt; - ak = 1. - tfnf / (fkk+tfnf); - ack = bk*ak; - sum += (ack + bk)*p1; - bk = ack; - fkk -= 1.; - } - } - pt = z; - if (kode == 2) { pt -= x; } - p1 = -fnf * std::log(rz) + pt; - ap = gamln(1. + fnf); - pt = p1 - ap; - // - // THE DIVISION EXP(PT)/(SUM+P2) IS ALTERED TO AVOID OVERFLOW - // IN THE DENOMINATOR BY SQUARING LARGE QUANTITIES - // - p2 += sum; - ap = std::abs(p2); - p1 = 1. / ap; - ck = std::exp(pt) * p1; - pt = conj(p2)*p1; - cnorm = ck * pt; - for (int i = 0; i < n; i++) { y[i] *= cnorm; } - return nz; -} - - -inline int kscl( - std::complex zr, - double fnu, - int n, - std::complex *y, - std::complex rz, - double *ascle, - double tol, - double elim -) { - - //***BEGIN PROLOGUE ZKSCL - //***REFER TO ZBESK - // - // SET K FUNCTIONS TO ZERO ON UNDERFLOW, CONTINUE RECURRENCE - // ON SCALED FUNCTIONS UNTIL TWO MEMBERS COME ON SCALE, THEN - // RETURN WITH MIN(NZ+2,N) VALUES SCALED BY 1/TOL. - // - //***ROUTINES CALLED ZUCHK,AZABS,AZLOG - //***END PROLOGUE ZKSCL - - std::complex cy[2] = { 0. }; - double as, acs, alas, fn, zri, xx; - std::complex s1, s2, cs, ck, zd; - int nz = 0; - int ic = 0; - int nn = ( n > 2 ? 2 : n ); - int kk = 0; - int i; - double elm = exp(-elim); - xx = std::real(zr); - - for (i = 1; i < (nn + 1); i++) - { - s1 = y[i-1]; - cy[i-1] = s1; - as = std::abs(s1); - acs = -std::real(zr) + log(as); - nz += 1; - y[i-1] = 0.; - if (acs < -elim) { - continue; - } - cs = -zr + std::log(s1); - cs = (exp(std::real(cs))/tol)*(cos(std::imag(cs)) + sin(std::imag(cs)*std::complex(0, 1))); - if (!uchk(cs, *ascle, tol)) { - y[i-1] = cs; - nz -= 1; - ic = i; - } - } - if (n == 1) { - return nz; - } - if (ic <= 1) { - y[0] = 0.; - nz = 2; - } - if (n == 2) { - return nz; - } - if (nz == 0) { - return nz; - } - - fn = fnu + 1.; - ck = fn*rz; - s1 = cy[0]; - s2 = cy[1]; - zri = std::imag(zr); - zd = zr; - for (i = 3; i < (n+1); i++) - { - kk = i; - cs = s2; - s2 *= ck; - s2 += s1; - s1 = cs; - ck += rz; - as = std::abs(s2); - alas = log(as); - acs = alas - xx; - nz += 1; - y[i-1] = 0.; - if (acs >= -elim) { - cs = std::log(s2); - cs -= zd; - cs = (exp(std::real(cs))/tol)*std::complex(cos(std::imag(cs)), sin(std::imag(cs))); - if (!uchk(cs, *ascle, tol)) { - y[i-1] = cs; - nz -= 1; - if (ic == kk-1) { - nz = kk - 2; - for (int i = 0; i < nz; i++) { y[i] = 0.; } - return nz; - } - ic = kk; - continue; - } - } - if (alas >= 0.5*elim){ - xx -= elim; - zd = std::complex(xx, zri); - s1 *= elm; - s2 *= elm; - } - } - nz = n; - if (ic == n) { - nz = n-1; - } - - for (int i = 0; i < nz; i++) { y[i] = 0.; } - return nz; -} - - -inline void rati( - std::complex z, - double fnu, - int n, - std::complex *cy, - double tol -) { - - //***BEGIN PROLOGUE ZRATI - //***REFER TO ZBESI,ZBESK,ZBESH - // - // ZRATI COMPUTES RATIOS OF I BESSEL FUNCTIONS BY BACKWARD - // RECURRENCE. THE STARTING INDEX IS DETERMINED BY FORWARD - // RECURRENCE AS DESCRIBED IN J. RES. OF NAT. BUR. OF STANDARDS-B, - // MATHEMATICAL SCIENCES, VOL 77B, P111-114, SEPTEMBER, 1973, - // BESSEL FUNCTIONS I AND J OF COMPLEX ARGUMENT AND INTEGER ORDER, - // BY D. J. SOOKNE. - // - //***ROUTINES CALLED AZABS,ZDIV - //***END PROLOGUE ZRATI - - std::complex cdfnu, pt, p1, p2, rz, t1; - double ak, amagz, ap1, ap2, arg, az, dfnu, fdnu, flam, fnup, rap1, rho, test, test1; - int i, id, idnu, inu, itime, k, kk, magz; - - az = std::abs(z); - inu = (int)fnu; - idnu = inu + n - 1; - fdnu = idnu; - magz = az; - amagz = magz + 1; - fnup = fmax(amagz, fdnu); - id = idnu - magz - 1; - itime = 1; - k = 1; - rz = 2.0 / z; - t1 = fnup * rz; - p2 = -t1; - p1 = 1.0; - t1 += rz; - if (id > 0) { - id = 0; - } - ap2 = std::abs(p2); - ap1 = std::abs(p1); - - // - // THE OVERFLOW TEST ON K(FNU+I-1,Z) BEFORE THE CALL TO CBKNX - // GUARANTEES THAT P2 IS ON SCALE. SCALE TEST1 AND ALL SUBSEQUENT P2 - // VALUES BY AP1 TO ENSURE THAT AN OVERFLOW DOES NOT OCCUR PREMATURELY. - // - arg = (ap2 + ap2) / (ap1 * tol); - test1 = sqrt(arg); - test = test1; - rap1 = 1.0 / ap1; - p1 *= rap1; - p2 *= rap1; - ap2 *= rap1; - - while (1) { - k += 1; - ap1 = ap2; - pt = p2; - p2 = p1 - t1*p2; - p1 = pt; - t1 += rz; - ap2 = std::abs(p2); - if (ap1 > test) { break; } - if (itime != 2) { - ak = std::abs(t1)*0.5; - flam = ak + sqrt(ak*ak - 1.0); - rho = fmin(ap2/ap1, flam); - test = test1*sqrt(rho / (rho*rho - 1.0)); - itime = 2; - } - } - kk = k + 1 - id; - ak = kk; - dfnu = fnu + n - 1; - cdfnu = dfnu; - t1 = ak; - p1 = 1.0 / ap2; - p2 = 0.0; - - for (i = 1; i < (kk+1); i++) { - pt = p1; - p1 = rz*(cdfnu+t1)*p1 + p2; - p2 = pt; - t1 -= 1.0; - } - - if (p1 == 0.) { - p1 = std::complex(0, 0); - } - - cy[n-1] = p2 / p1; - if (n == 1) { return; } - k = n - 1; - ak = k; - t1 = ak; - cdfnu = fnu*rz; - - for (i = 2; i < (n+1); i++) { - pt = cdfnu + t1*rz*cy[k]; - if (pt == 0.0) { - pt = std::complex(tol, tol); - } - cy[k-1] = 1.0 / pt; - t1 -= 1.0; - k -= 1; - } - return; -} - - -inline int seri( - std::complex z, - double fnu, - int kode, - int n, - std::complex *y, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZSERI - //***REFER TO ZBESI,ZBESK - // - // ZSERI COMPUTES THE I BESSEL FUNCTION FOR REAL(Z).GE.0.0 BY - // MEANS OF THE POWER SERIES FOR LARGE CABS(Z) IN THE - // REGION CABS(Z).LE.2*SQRT(FNU+1). NZ=0 IS A NORMAL RETURN. - // NZ.GT.0 MEANS THAT THE LAST NZ COMPONENTS WERE SET TO ZERO - // DUE TO UNDERFLOW. NZ.LT.0 MEANS UNDERFLOW OCCURRED, BUT THE - // CONDITION CABS(Z).LE.2*SQRT(FNU+1) WAS VIOLATED AND THE - // COMPUTATION MUST BE COMPLETED IN ANOTHER ROUTINE WITH N=N-ABS(NZ). - // - //***ROUTINES CALLED DGAMLN,D1MACH,ZUCHK,AZABS,ZDIV,AZLOG,ZMLT - //***END PROLOGUE ZSERI - - std::complex ak1, ck, coef, crsc, cz, half_z, rz, s1, s2, w[2]; - double aa, acz, ak, arm, ascle, atol, az, dfnu, fnup, rak1,\ - rs, rtr1, s, ss, x; - int ib, iflag, il, k, l, m, nn; - - int nz = 0; - az = std::abs(z); - if (az == 0.0) { - y[0] = 0.0; - if (fnu == 0.) { y[0] = 1.0; } - if (n == 1) { return nz; } - for (int i = 1; i < n; i++) { y[i] = 0.0; } - return nz; - } - x = std::real(z); - arm = 1e3*d1mach[0]; - rtr1 = sqrt(arm); - crsc = 1.0; - iflag = 0; - if (az >= arm) { - half_z = 0.5*z; - cz = 0.0; - if (az > rtr1) { cz = half_z*half_z; } - acz = std::abs(cz); - nn = n; - ck = std::log(half_z); -L10: - dfnu = fnu + (nn-1); - fnup = dfnu + 1.0; - // - // UNDERFLOW TEST - // - ak1 = ck * dfnu; - ak = gamln(fnup); - ak1 -= ak; - if (kode == 2) { ak1 -= x; } - rak1 = std::real(ak1); - if (rak1 > -elim) { goto L30; } -L20: - nz += 1; - y[nn - 1] = 0.0; - // - // RETURN WITH NZ < 0 IF ABS(Z*Z/4) > FNU+N-NZ-1 COMPLETE - // THE CALCULATION IN CBINU WITH N=N-ABS(NZ) - // - if (acz > dfnu) { return -nz; } - nn -= 1; - if (nn == 0) { return nz; } - goto L10; -L30: - if (rak1 <= -alim) { - iflag = 1; - ss = 1.0 / tol; - crsc = tol; - ascle = arm * ss; - } - ak = std::imag(ak1); - aa = exp(rak1); - if (iflag == 1) { aa *= ss; } - coef = aa * std::complex(cos(ak), sin(ak)); - atol = tol * acz / fnup; - il = (nn > 2 ? 2 : nn); - for (int i = 1; i < (il +1); i++) - { - dfnu = fnu + (nn-i); - fnup = dfnu + 1.0; - s1 = 1.0; - if (acz >= tol*fnup) { - ak1 = 1.0; - ak = fnup + 2.0; - s = fnup; - aa = 2.0; - while (1) { - rs = 1.0 / s; - ak1 *= cz; - ak1 *= rs; - s1 += ak1; - s += ak; - ak += 2.0; - aa *= acz; - aa *= rs; - if (aa <= atol) { break; } - } - } - s2 = s1 * coef; - w[i-1] = s2; - if (iflag != 0) { - if (uchk(s2, ascle, tol)) { goto L20; } - } - m = nn - i + 1; - y[m-1] = s2 * crsc; - if (i != il) { coef *= dfnu / half_z; } - } - if (nn <= 2) { return nz; } - k = nn - 2; - ak = k; - rz = 2.0 / z; - if (iflag == 1) { goto L80; } - ib = 3; -L60: - for (int i = ib; i < (nn+1); i++) - { - y[k-1] = (ak+fnu)*rz*y[k] + y[k+1]; - ak -= 1.0; - k -= 1; - } - return nz; -L80: - // - // RECUR BACKWARD WITH SCALED VALUES - // - // - // EXP(-ALIM)=EXP(-ELIM)/TOL=APPROX. ONE PRECISION ABOVE THE - // UNDERFLOW LIMIT = ASCLE = D1MACH(1)*SS*1000 - // - s1 = w[0]; - s2 = w[1]; - l = 3; - for (int l = 3; l < (nn+1); l++) - { - ck = s2; - s2 = s1 + (ak+fnu)*rz*s2; - s1= ck; - ck = s2*crsc; - y[k-1] = ck; - ak -= 1.0; - k -= 1; - if (std::abs(ck) > ascle) { goto L100; } - } - return nz; -L100: - ib = l+1; - if (ib > nn) { return nz; } - goto L60; - } - nz = n; - if (fnu == 0.0) { nz -= 1; } - y[0] = 0.0; - if (fnu == 0.) { y[0] = 1.0; } - if (n == 1) { return nz; } - for (int i = 1; i < n; i++) { y[i] = 0.0; } - return nz; -} - - -inline int s1s2( - std::complex zr, - std::complex *s1, - std::complex *s2, - double ascle, - double alim, - int *iuf -) { - - //***BEGIN PROLOGUE ZS1S2 - //***REFER TO ZBESK,ZAIRY - // - // ZS1S2 TESTS FOR A POSSIBLE UNDERFLOW RESULTING FROM THE - // ADDITION OF THE I AND K FUNCTIONS IN THE ANALYTIC CON- - // TINUATION FORMULA WHERE S1=K FUNCTION AND S2=I FUNCTION. - // ON KODE=1 THE I AND K FUNCTIONS ARE DIFFERENT ORDERS OF - // MAGNITUDE, BUT FOR KODE=2 THEY CAN BE OF THE SAME ORDER - // OF MAGNITUDE AND THE MAXIMUM MUST BE AT LEAST ONE - // PRECISION ABOVE THE UNDERFLOW LIMIT. - // - //***ROUTINES CALLED AZABS,AZEXP,AZLOG - //***END PROLOGUE ZS1S2 - - std::complex c1, s1d; - double aa, aln, as1, as2, xx; - int nz = 0; - as1 = std::abs(*s1); - as2 = std::abs(*s2); - aa = std::real(*s1); - aln = std::imag(*s1); - - if ((aa != 0.) || (aln != 0.)) { - if (as1 != 0.){ - xx = std::real(zr); - aln = -xx - xx + log(as1); - s1d = *s1; - *s1 = 0.; - as1 = 0.; - if (aln >= -alim) { - c1 = std::log(s1d) - zr - zr; - *s1 = std::exp(c1); - as1 = std::abs(*s1); - *iuf += 1; - } - } - } - aa = fmax(as1, as2); - if (aa > ascle) { - return nz; - } - *s1 = 0.; - *s2 = 0.; - *iuf = 0; - return 1; -} - - -inline int uchk( - std::complex y, - double ascle, - double tol -) { - - //***BEGIN PROLOGUE ZUCHK - //***REFER TO ZSERI,ZUOIK,ZUNK1,ZUNK2,ZUNI1,ZUNI2,ZKSCL - // - // Y ENTERS AS A SCALED QUANTITY WHOSE MAGNITUDE IS GREATER THAN - // EXP(-ALIM)=ASCLE=1.0E+3*D1MACH(1)/TOL. THE TEST IS MADE TO SEE - // IF THE MAGNITUDE OF THE REAL OR IMAGINARY PART WOULD UNDERFLOW - // WHEN Y IS SCALED (BY TOL) TO ITS PROPER VALUE. Y IS ACCEPTED - // IF THE UNDERFLOW IS AT LEAST ONE PRECISION BELOW THE MAGNITUDE - // OF THE LARGEST COMPONENT; OTHERWISE THE PHASE ANGLE DOES NOT HAVE - // ABSOLUTE ACCURACY AND AN UNDERFLOW IS ASSUMED. - // - //***ROUTINES CALLED (NONE) - //***END PROLOGUE ZUCHK - - double yr = fabs(std::real(y)); - double yi = fabs(std::imag(y)); - double ss = fmax(yr, yi); - double st = fmin(yr, yi); - if (st > ascle) { - return 0; - } else { - st /= tol; - if (ss < st) { - return 1; - } else { - return 0; - } - } -} - - -inline void unhj( - std::complex z, - double fnu, - int ipmtr, - double tol, - std::complex *phi, - std::complex *arg, - std::complex *zeta1, - std::complex *zeta2, - std::complex *asum, - std::complex *bsum -) { - - //***BEGIN PROLOGUE ZUNHJ - //***REFER TO ZBESI,ZBESK - // - // REFERENCES - // HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ AND I.A. - // STEGUN, AMS55, NATIONAL BUREAU OF STANDARDS, 1965, CHAPTER 9. - // - // ASYMPTOTICS AND SPECIAL FUNCTIONS BY F.W.J. OLVER, ACADEMIC - // PRESS, N.Y., 1974, PAGE 420 - // - // ABSTRACT - // ZUNHJ COMPUTES PARAMETERS FOR BESSEL FUNCTIONS C(FNU,Z) = - // J(FNU,Z), Y(FNU,Z) OR H(I,FNU,Z) I=1,2 FOR LARGE ORDERS FNU - // BY MEANS OF THE UNIFORM ASYMPTOTIC EXPANSION - // - // C(FNU,Z)=C1*PHI*( ASUM*AIRY(ARG) + C2*BSUM*DAIRY(ARG) ) - // - // FOR PROPER CHOICES OF C1, C2, AIRY AND DAIRY WHERE AIRY IS - // AN AIRY FUNCTION AND DAIRY IS ITS DERIVATIVE. - // - // (2/3)*FNU*ZETA**1.5 = ZETA1-ZETA2, - // - // ZETA1=0.5*FNU*CLOG((1+W)/(1-W)), ZETA2=FNU*W FOR SCALING - // PURPOSES IN AIRY FUNCTIONS FROM CAIRY OR CBIRY. - // - // MCONJ=SIGN OF AIMAG(Z), BUT IS AMBIGUOUS WHEN Z IS REAL AND - // MUST BE SPECIFIED. IPMTR=0 RETURNS ALL PARAMETERS. IPMTR= - // 1 COMPUTES ALL EXCEPT ASUM AND BSUM. - // - //***ROUTINES CALLED AZABS,ZDIV,AZLOG,AZSQRT,D1MACH - //***END PROLOGUE ZUNHJ - - std::complex cfnu, przth, ptfn, rtzta, rzth, suma, sumb; - std::complex tfn, t2, w, w2, za, zb, zc, zeta, zth; - double ang, atol, aw2, azth, btol, fn13, fn23, pp, rfn13; - double rfnu, rfnu2, wi, wr, zci, zcr, zetai, zetar, zthi; - double zthr, asumr, asumi, bsumr, bsumi, test, ac; - double ex1 = 1./3.; - double ex2 = 2./3.; - double hpi = 1.57079632679489662; - double pi = 3.14159265358979324; - double thpi = 4.71238898038468986; - int ias, ibs, j, ju, k, kmax, kp1, ks, l, lrp1, l1, l2, m; - /* array vars */ - std::complex cr[14] = { 0. }; - std::complex dr[14] = { 0. }; - std::complex up[14] = { 0. }; - std::complex p[30] = { 0. }; - double ap[30] = { 0. }; - - rfnu = 1. / fnu; - // - // OVERFLOW TEST (Z/FNU TOO SMALL) - // - test = d1mach[0] * 1e3; - ac = fnu*test; - if ((fabs(std::real(z)) <= ac) && (fabs(std::imag(z)) <= ac)) { - *zeta1 = 2.0*fabs(log(test)) + fnu; - *zeta2 = fnu; - *phi = 1.; - *arg = 1.; - return; - } - zb = z*rfnu; - rfnu2 = rfnu*rfnu; - // - // COMPUTE IN THE FOURTH QUADRANT - // - fn13 = pow(fnu, ex1); - fn23 = fn13 * fn13; - rfn13 = 1.0/fn13; - w2 = 1.0 - zb*zb; - /* AMOS AZSQRT and C CSQRT differs when imaginary 0.0 swaps sign */ - w2 = 1.0 - zb*zb; - if (std::imag(w2) == -0.0) { w2 = std::real(w2); } - aw2 = std::abs(w2); - if (aw2 <= 0.25) { - // - // POWER SERIES FOR ABS(W2) <= 0.25 - // - k = 1; - p[0] = 1.; - suma = zunhj_gama[0]; - ap[0] = 1.; - if (aw2 >= tol) { - for (k = 2; k < 31; k++) - { - p[k-1] = p[k-2]*w2; - suma += p[k-1]*zunhj_gama[k-1]; - ap[k-1] = ap[k-2]*aw2; - if (ap[k-1] < tol) { break; } - } - } - /* Check for exhausted loop */ - if (k == 31) { k = 30; } - - kmax = k; - zeta = w2*suma; - *arg = zeta*fn23; - za = std::sqrt(suma); - *zeta2 = std::sqrt(w2)*fnu; - *zeta1 = (*zeta2) * (1. + zeta*za*ex2); - za = za + za; - *phi = std::sqrt(za)*rfn13; - if (ipmtr == 1) { return; } - // - // SUM SERIES FOR ASUM AND BSUM - // - sumb = 0.0; - for (k = 1; k < (kmax+1); k++) { - sumb += p[k-1]*zunhj_beta[k-1]; - } - *asum = 0.0; - *bsum = sumb; - l1 = 0; - l2 = 30; - btol = tol * (fabs(std::real(*bsum)) + fabs(std::imag(*bsum))); - atol = tol; - pp = 1.0; - ias = 0; - ibs = 0; - if (rfnu2 < tol) { - /* 110 */ - *asum += 1.; - *bsum *= rfnu*rfn13; - /* 120 */ - return; - } - for (int is = 2; is < 8; is++) - { - atol /= rfnu2; - pp *= rfnu2; - if (ias != 1) { - suma = 0.0; - for (k = 1; k < (kmax+1); k++) - { - m = l1 + k; - suma += p[k-1]*zunhj_alfa[m-1]; - if (ap[k-1] < atol) { break; } - } - *asum += suma*pp; - if (pp < tol) { ias = 1; } - } - if (ibs != 1) { - sumb = 0.0; - for (k = 1; k < (kmax+1); k++) - { - m = l2 + k; - sumb += p[k-1]*zunhj_beta[m-1]; - if (ap[k-1] < atol) { break; } - } - *bsum += sumb*pp; - if (pp < btol) { ibs = 1; } - } - if ((ias == 1) && (ibs == 1)) { break; } - l1 += 30; - l2 += 30; - } - *asum += 1.; - *bsum *= rfnu*rfn13; - return; - } else { - // - // ABS(W2) > 0.25 - w = std::sqrt(w2); - wr = std::real(w); - wi = std::imag(w); - if (wr < 0) { wr = 0.;} - if (wi < 0) { wi = 0.;} - za = (1. + w) / zb; - zc = std::log(za); - zcr = std::real(zc); - zci = std::imag(zc); - if (zci < 0) { zci = 0.;} - if (zci > hpi) { zci = hpi;} - if (zcr < 0) { zcr = 0.;} - zc = std::complex(zcr, zci); - zth = (zc-w)*1.5; - cfnu = fnu; - *zeta1 = zc*cfnu; - *zeta2 = w*cfnu; - azth = std::abs(zth); - zthr = std::real(zth); - zthi = std::imag(zth); - ang = thpi; - if ((zthr < 0.) || (zthi >= 0.)) { - ang = hpi; - if (zthr != 0.) { - ang = atan(zthi/zthr); - if (zthr < 0.) { ang += pi; } - } - } - pp = pow(azth, ex2); - ang *= ex2; - zetar = pp * cos(ang); - zetai = pp * sin(ang); - if (zetai < 0.) { zetai = 0.; } - zeta = std::complex(zetar, zetai); - *arg = zeta*fn23; - rtzta = zth / zeta; - za = rtzta / w; - *phi = std::sqrt(za + za) * rfn13; - if (ipmtr == 1) { return; } - tfn = rfnu / w; - rzth = rfnu / zth; - zc = rzth * zunhj_ar[1]; - t2 = 1. / w2; - up[1] = (t2*zunhj_c[1] + zunhj_c[2])*tfn; - *bsum = up[1] + zc; - *asum = 0.; - - if (rfnu < tol) { - *asum += 1.; - *bsum *= -rfn13 / rtzta; - return; - } - - przth = rzth; - ptfn = tfn; - up[0] = 1.0; - pp = 1.0; - bsumr = std::real(*bsum); - bsumi = std::imag(*bsum); - btol = tol * (fabs(bsumr) + fabs(bsumi)); - ks = 0; - kp1 = 2; - l = 3; - ias = 0; - ibs = 0; - - for (int lr = 2; lr < 13; lr += 2) - { - lrp1 = lr + 1; - // - // COMPUTE TWO ADDITIONAL CR, DR, AND UP FOR TWO MORE TERMS IN - // NEXT SUMA AND SUMB - // - for (k = lr; k < (lrp1+1); k++) - { - ks += 1; - kp1 += 1; - l += 1; - za = zunhj_c[l-1]; - for (j = 2; j < (kp1+1); j++) - { - l += 1; - za = za*t2 + zunhj_c[l-1]; - } - ptfn *= tfn; - up[kp1-1] = ptfn*za; - cr[ks-1] = przth*zunhj_br[ks]; - przth *= rzth; - dr[ks-1] = przth*zunhj_ar[ks+1]; - } - pp *= rfnu2; - if (ias != 1) { - suma = up[lr]; - ju = lrp1; - for (int jr = 1; jr < lrp1; jr++) - { - ju -= 1; - suma += cr[jr-1] * up[ju-1]; - } - *asum += suma; - asumr = std::real(*asum); - asumi = std::imag(*asum); - test = fabs(asumr) + fabs(asumi); - if ((pp < tol) && (test < tol)) { ias = 1; } - } - if (ibs != 1) { - sumb = up[lr+1] + up[lr]*zc; - ju = lrp1; - for (int jr = 1; jr < lrp1; jr++) - { - ju -= 1; - sumb += dr[jr-1] * up[ju-1]; - } - *bsum += sumb; - bsumr = std::real(*bsum); - bsumi = std::imag(*bsum); - test = fabs(bsumr) + fabs(bsumi); - if ((pp < tol) && (test < tol)) { ibs = 1; } - } - if ((ias == 1) && (ibs == 1)) { break; } - } - *asum += 1.; - *bsum *= -rfn13 / rtzta; - return; - } -} - - -inline void uni1( - std::complex z, - double fnu, - int kode, - int n, - std::complex *y, - int *nz, - int *nlast, - double fnul, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZUNI1 - //***REFER TO ZBESI,ZBESK - // - // ZUNI1 COMPUTES I(FNU,Z) BY MEANS OF THE UNIFORM ASYMPTOTIC - // EXPANSION FOR I(FNU,Z) IN -PI/3.LE.ARG Z.LE.PI/3. - // - // FNUL IS THE SMALLEST ORDER PERMITTED FOR THE ASYMPTOTIC - // EXPANSION. NLAST=0 MEANS ALL OF THE Y VALUES WERE SET. - // NLAST.NE.0 IS THE NUMBER LEFT TO BE COMPUTED BY ANOTHER - // FORMULA FOR ORDERS FNU TO FNU+NLAST-1 BECAUSE FNU+NLAST-1.LT.FNUL. - // Y(I)=CZERO FOR I=NLAST+1,N - // - //***ROUTINES CALLED ZUCHK,ZUNIK,ZUOIK,D1MACH,AZABS - //***END PROLOGUE ZUNI1 - - std::complex c2, phi, rz, sum, s1, s2, zeta1 = 0, zeta2 = 0; - double aphi, ascle, c1r, crsc, cscl, fn, rs1; - int i, iflag, init, k, m, nd, nn, nuf; - std::complex cwrk[16] = { 0. }; - std::complex cy[2] = { 0. }; - *nz = 0; - nd = n; - *nlast = 0; - // - // COMPUTED VALUES WITH EXPONENTS BETWEEN ALIM AND ELIM IN - // MAGNITUDE ARE SCALED TO KEEP INTERMEDIATE ARITHMETIC ON SCALE, - // EXP(ALIM)=EXP(ELIM)*TOL - // - cscl = 1.0 / tol; - crsc = tol; - double css[3] = {cscl, 1., crsc}; - double csr[3] = {crsc, 1., cscl}; - double bry[3] = {1e3*d1mach[0]/tol, 0., 0.}; - bry[1] = 1.0 / bry[0]; - bry[2] = d1mach[1]; - // - // CHECK FOR UNDERFLOW AND OVERFLOW ON FIRST MEMBER - // - fn = fmax(fnu, 1.0); - init = 0; - unik(z, fn, 1, 1, tol, &init, &phi, &zeta1, &zeta2, &sum, &cwrk[0]); - if (kode != 1) { - s1 = -zeta1 + fn*(fn / (z + zeta2)); - } else { - s1 = -zeta1 + zeta2 ; - } - - rs1 = std::real(s1); - if (fabs(rs1) > elim) { - if (rs1 > 0) { - *nz = -1; - return; - } - *nz = n; - for (i = 0; i < n; i++) { y[i] = 0.0; } - } -L30: - nn = ( nd > 2 ? 2 : nd); - for (i = 1; i < (nn+1); i++) - { - fn = fnu + nd - i; - init = 0; - unik(z, fn, 1, 0, tol, &init, &phi, &zeta1, &zeta2, &sum, &cwrk[0]); - if (kode != 1) { - s1 = -zeta1 + fn*(fn / (z + zeta2)) + std::complex(0.0, std::imag(z)); - } else { - s1 = -zeta1 + zeta2; - } - // - // TEST FOR UNDERFLOW AND OVERFLOW - // - rs1 = std::real(s1); - if (fabs(rs1) > elim) { goto L110; } - if (i == 1) { iflag = 2; } - if (fabs(rs1) >= alim) { - // - // REFINE TEST AND SCALE - // - aphi = std::abs(phi); - rs1 += log(aphi); - if (fabs(rs1) > elim) { goto L110; } - if (i == 1) { iflag = 1; } - if (rs1 >= 0.0) { if (i == 1) { iflag = 3; } } - } - /* 60 */ - // - // SCALE S1 IF CABS(S1) < ASCLE - // - s2 = phi*sum; - s1 = exp(std::real(s1))*css[iflag-1]*std::complex(cos(std::imag(s1)), sin(std::imag(s1))); - s2 *= s1; - if (iflag == 1) { if (uchk(s2, bry[0], tol)) { goto L110; } } - /* 70 */ - cy[i-1] = s2; - m = nd - i + 1; - y[m-1] = s2*csr[iflag-1]; - } - /* 80 */ - if (nd > 2) { - rz = 1.0 / z; - s1 = cy[0]; - s2 = cy[1]; - c1r = csr[iflag-1]; - ascle = bry[iflag-1]; - k = nd - 2; - fn = k; - for (i = 3; i < (nd+1); i++) - { - c2 = s2; - s2 = s1 + (fnu + fn)*rz*c2; - s1 = c2; - c2 = s2*c1r; - y[k-1] = c2; - k -= 1; - fn -= 1.0; - if (iflag >= 3) { continue; } - if (fmax(fabs(std::real(c2)), fabs(std::imag(c2))) <= ascle) { continue; } - iflag += 1; - ascle = bry[iflag-1]; - s1 *= c1r; - s2 = c2; - s1 *= css[iflag-1]; - s2 *= css[iflag-1]; - c1r = csr[iflag-1]; - } - /* 90 */ - } - /* 100 */ - return; -L110: - if (rs1 > 0.0) { *nz = -1; return; } - y[nd - 1] = 0.0; - *nz += 1; - nd -= 1; - if (nd == 0) { return; } - nuf = uoik(z, fnu, kode, 1, nd, y, tol, elim, alim); - if (nuf < 0) { *nz = -1; return; } - nd -= nuf; - *nz += nuf; - if (nd == 0) { return; } - fn = fnu + nd - 1; - if (fn >= fnul) { goto L30; } - *nlast = nd; - return; -} - - -inline void uni2( - std::complex z, - double fnu, - int kode, - int n, - std::complex *y, - int *nz, - int *nlast, - double fnul, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZUNI2 - //***REFER TO ZBESI,ZBESK - // - // ZUNI2 COMPUTES I(FNU,Z) IN THE RIGHT HALF PLANE BY MEANS OF - // UNIFORM ASYMPTOTIC EXPANSION FOR J(FNU,ZN) WHERE ZN IS Z*I - // OR -Z*I AND ZN IS IN THE RIGHT HALF PLANE ALSO. - // - // FNUL IS THE SMALLEST ORDER PERMITTED FOR THE ASYMPTOTIC - // EXPANSION. NLAST=0 MEANS ALL OF THE Y VALUES WERE SET. - // NLAST.NE.0 IS THE NUMBER LEFT TO BE COMPUTED BY ANOTHER - // FORMULA FOR ORDERS FNU TO FNU+NLAST-1 BECAUSE FNU+NLAST-1.LT.FNUL. - // Y(I)=CZERO FOR I=NLAST+1,N - // - //***ROUTINES CALLED ZAIRY,ZUCHK,ZUNHJ,ZUOIK,D1MACH,AZABS - //***END PROLOGUE ZUNI2 - - std::complex ai, arg, asum, bsum, cfn, cid, crsc, cscl, c1, c2, dai, phi, rz,\ - s1, s2, zb, zeta1, zeta2, zn, zar; - double aarg, ang, aphi, ascle, ay, c2i, c2m, c2r, fn, rs1, yy; - int i, iflag, in, inu, j, k, nai, nd, ndai, nn, nuf, idum; - double hpi = 1.57079632679489662; /* 0.5 pi */ - double aic = 1.265512123484645396; /* log(2 sqrt(pi)) */ - std::complex cip[4] = { 1.0, std::complex(0, 1), -1.0, -std::complex(0, 1) }; - std::complex ci = std::complex(0, 1); - // - // COMPUTED VALUES WITH EXPONENTS BETWEEN ALIM AND ELIM IN MAGNITUDE - // ARE SCALED TO KEEP INTERMEDIATE ARITHMETIC ON SCALE, - // EXP(ALIM) = EXP(ELIM)*TOL - // - cscl = 1.0 / tol; - crsc = tol; - std::complex csr[3] = { crsc, 1.0, cscl }; - std::complex css[3] = { cscl, 1.0, crsc }; - double bry[3] = { 1e3*d1mach[0]/tol, 0.0, 0.0 }; - std::complex cy[2] = { 0.0 }; - yy = std::imag(z); - *nz = 0; - nd = n; - *nlast = 0; - // - // ZN IS IN THE RIGHT HALF PLANE AFTER ROTATION BY CI OR -CI - // - zn = -z * ci; - zb = z; - cid = -ci; - inu = (int)fnu; - ang = hpi * (fnu - inu); - c2 = std::complex(cos(ang), sin(ang)); - zar = c2; - in = inu + n - 1; - in = in % 4; - c2 *= cip[in]; - if (yy <= 0.0) { - zn = conj(-zn); - zb = conj(zb); - cid = -cid; - c2 = conj(c2); - } - // - // CHECK FOR UNDERFLOW AND OVERFLOW ON FIRST MEMBER - // - fn = fmax(fnu, 1.0); - unhj(zn, fn, 0, tol, &phi, &arg, &zeta1, &zeta2, &asum, &bsum); - if (kode != 1) { - cfn = fnu; - s1 = -zeta1 + cfn*(cfn/(zb + zeta2)); - } else { - s1 = -zeta1 + zeta2; - } - rs1 = std::real(s1); - if (fabs(rs1) > elim) { - if (rs1 > 0.) { - *nz = -1; - return; - } - for (i = 0; i < n; i++) { - y[i] = 0.0; - } - return; - } -L10: - nn = (nd > 2 ? 2 : nd); - i = 1; - for (i = 1; i < (nn+1); i++) - { - fn = fnu + (nd-i); - unhj(zn, fn, 0, tol, &phi, &arg, &zeta1, &zeta2, &asum, &bsum); - if (kode != 1) { - cfn = fn; - ay = fabs(yy); - s1 = -zeta1 + cfn*(cfn/(zb + zeta2)) + ay*std::complex(0, 1); - } else { - s1 = -zeta1 + zeta2; - } - // - // TEST FOR UNDERFLOW AND OVERFLOW - // - rs1 = std::real(s1); - if (fabs(rs1) > elim) { goto L50; } - if (i == 1) { iflag = 2; } - if (fabs(rs1) >= alim) { - // - // REFINE TEST AND SCALE - // - aphi = std::abs(phi); - aarg = std::abs(arg); - rs1 += log(aphi) - 0.25*log(aarg) - aic; - if (fabs(rs1) > elim) { goto L50; } - if (i == 1) { iflag = 1; } - if (rs1 >= 0.0){ if (i== 1) { iflag = 3; }} - } - // - // SCALE S1 TO KEEP INTERMEDIATE ARITHMETIC ON SCALE NEAR - // EXPONENT EXTREMES - // - ai = airy(arg, 0, 2, &nai, &idum); - dai = airy(arg, 1, 2, &ndai, &idum); - s2 = phi * (ai*asum + dai*bsum); - c2r = std::exp(std::real(s1))*std::real(css[iflag-1]); - c2i = std::imag(s1); - s1 = c2r*std::complex(cos(c2i), sin(c2i)); - s2 *= s1; - if (iflag == 1) { if (uchk(s2, bry[0], tol)) { goto L50; } } - if (yy <= 0.0) { s2 = conj(s2); } - j = nd - i + 1; - s2 *= c2; - cy[i-1] = s2; - y[j-1] = s2*csr[iflag-1]; - c2 *= cid; - } - if (nd > 2) { - rz = 2.0 / z; - bry[1] = 1.0 / bry[0]; - bry[2] = d1mach[1]; - s1 = cy[0]; - s2 = cy[1]; - c1 = csr[iflag-1]; - ascle = bry[iflag-1]; - k = nd - 2; - fn = k; - for (i = 3; i < (nd+1); i++) { - c2 = s2; - s2 = s1 + (fnu+fn)*rz*s2; - s1 = c2; - c2 = s2*c1; - y[k-1] = c2; - k -= 1; - fn -= 1.0; - if (iflag < 3) { - c2r = fabs(std::real(c2)); - c2i = fabs(std::imag(c2)); - c2m = fmax(c2r, c2i); - if (c2m > ascle) { - iflag += 1; - ascle = bry[iflag-1]; - s1 *= c1; - s2 = c2; - s1 *= css[iflag-1]; - s2 *= css[iflag-1]; - c1 = csr[iflag-1]; - } - } - } - } - return; -L50: - if (rs1 <= 0.0) { - // - // SET UNDERFLOW AND UPDATE PARAMETERS - // - y[nd-1] = 0.0; - nz += 1; - nd -= 1; - if (nd == 0) { return; } - nuf = uoik(z, fnu, kode, 1, nd, y, tol, elim, alim); - if (nuf >= 0) { - nd -= nuf; - nz += nuf; - if (nd == 0) { return; } - fn = fnu + nd - 1; - if (fn >= fnul) { - // The following was commented out in the original F77 code - // C FN = CIDI - // C J = NUF + 1 - // C K = MOD(J,4) + 1 - // C S1R = CIPR(K) - // C S1I = CIPI(K) - // C IF (FN.LT.0.0D0) S1I = -S1I - // C STR = C2R*S1R - C2I*S1I - // C C2I = C2R*S1I + C2I*S1R - // C C2R = STR - in = (inu + nd - 1) % 4; - c2 = zar*cip[in]; - if (yy <= 0.0) { c2 = conj(c2); } - goto L10; - } - *nlast = nd; - return; - } - } - *nz = -1; - return; -} - - -inline void unik( - std::complex zr, - double fnu, - int ikflg, - int ipmtr, - double tol, - int *init, - std::complex *phi, - std::complex *zeta1, - std::complex *zeta2, - std::complex *total, - std::complex *cwrk -) { - - //***BEGIN PROLOGUE ZUNIK - //***REFER TO ZBESI,ZBESK - // - // ZUNIK COMPUTES PARAMETERS FOR THE UNIFORM ASYMPTOTIC - // EXPANSIONS OF THE I AND K FUNCTIONS ON IKFLG= 1 OR 2 - // RESPECTIVELY BY - // - // W(FNU,ZR) = PHI*EXP(ZETA)*SUM - // - // WHERE ZETA=-ZETA1 + ZETA2 OR - // ZETA1 - ZETA2 - // - // THE FIRST CALL MUST HAVE INIT=0. SUBSEQUENT CALLS WITH THE - // SAME ZR AND FNU WILL RETURN THE I OR K FUNCTION ON IKFLG= - // 1 OR 2 WITH NO CHANGE IN INIT. CWRK IS A COMPLEX WORK - // ARRAY. IPMTR=0 COMPUTES ALL PARAMETERS. IPMTR=1 COMPUTES PHI, - // ZETA1,ZETA2. - // - //***ROUTINES CALLED ZDIV,AZLOG,AZSQRT,D1MACH - //***END PROLOGUE ZUNIK - - std::complex cfn, crfn, s, sr, t, t2, zn; - double ac, rfn, test, tstr, tsti; - int i, j, k, l; - /* ( 1/sqrt(2 PI), sqrt(PI/2) ) */ - double con[2] = { 3.98942280401432678e-01, 1.25331413731550025 }; - - if (*init == 0) { - rfn = 1. / fnu; - crfn = rfn; - - tstr = std::real(zr); - tsti = std::imag(zr); - test = d1mach[0] * 1e3; - ac = fnu * test; - if ((fabs(tstr) <= ac) && (fabs(tsti) <= ac)) { - ac = 2.0 * fabs(log(test)) + fnu; - *zeta1 = ac; - *zeta2 = fnu; - *phi = 1.0; - } - t = zr * crfn; - s = 1.0 + t*t; - sr = std::sqrt(s); - cfn = fnu; - zn = (1. + sr) / t; - *zeta1 = cfn * std::log(zn); - *zeta2 = cfn * sr; - t = 1.0 / sr; - sr = t*crfn; - cwrk[15] = std::sqrt(sr); - *phi = cwrk[15]*con[ikflg-1]; - if (ipmtr != 0) { return; } - t2 = 1. / s; - cwrk[0] = 1.; - crfn = 1.; - ac = 1.; - l = 1; - k = 2; - for (k = 2; k < 16; k++) - { - s = 0.0; - for (j = 1; j < (k+1); j++) - { - l += 1; - s = s*t2 + zunik_c[l-1]; - } - crfn *= sr; - cwrk[k-1] = crfn*s; - ac *= rfn; - tstr = std::real(cwrk[k-1]); - tsti = std::imag(cwrk[k-1]); - test = fabs(tstr) + fabs(tsti); - if ((ac < tol) && (test < tol)) { - break; - } - } - /* Guard against exhausted loop */ - if (k == 16) { k-=1; } - *init = k; - } - - *total = 0.0; - t = 1.0; - if (ikflg != 2) { - - for (i = 0; i < (*init); i++) { - *total += cwrk[i]; - } - *phi = cwrk[15] * con[0]; - - } else { - - for (i = 1; i < (*init + 1); i++) { - *total += t * cwrk[i-1]; - t = -t; - } - *phi = cwrk[15] * con[1]; - - } - return; -} - - -inline int unk1( - std::complex z, - double fnu, - int kode, - int mr, - int n, - std::complex *y, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZUNK1 - //***REFER TO ZBESK - // - // ZUNK1 COMPUTES K(FNU,Z) AND ITS ANALYTIC CONTINUATION FROM THE - // RIGHT HALF PLANE TO THE LEFT HALF PLANE BY MEANS OF THE - // UNIFORM ASYMPTOTIC EXPANSION. - // MR INDICATES THE DIRECTION OF ROTATION FOR ANALYTIC CONTINUATION. - // NZ=-1 MEANS AN OVERFLOW WILL OCCUR - // - //***ROUTINES CALLED ZKSCL,ZS1S2,ZUCHK,ZUNIK,D1MACH,AZABS - //***END PROLOGUE ZUNK1 - - std::complex cfn, ck, crsc, cs, cscl, csgn, cspn, c1, c2, rz, s1, s2, zr,\ - phid, zeta1d = 0.0, zeta2d = 0.0, sumd; - double ang, aphi, asc, ascle, c2i, c2m, c2r, fmr, fn, fnf, rs1, sgn, x; - int i, ib, iflag = 0, ifn, il, inu, iuf, k, kdflg, kflag, kk, m, nw, nz, j,\ - jc, ipard, initd, ic; - - cscl = 1.0 / tol; - crsc = tol; - std::complex css[3] = {cscl, 1.0, crsc }; - std::complex csr[3] = {crsc, 1.0, cscl }; - std::complex cwrk[3][16] = {{ 0.0 }}; - std::complex phi[2] = { 0.0 }; - std::complex sum[2] = { 0.0 }; - std::complex zeta1[2] = { 0.0 }; - std::complex zeta2[2] = { 0.0 }; - std::complex cy[2] = { 0.0 }; - double bry[3] = { 1e3*d1mach[0] / tol, tol / 1e3*d1mach[0], d1mach[1]}; - int init[2] = { 0 }; - double pi = 3.14159265358979324; - - kdflg = 1; - kflag = 1; - fn = fnu; - nz = 0; - x = std::real(z); - zr = z; - if (x < 0.0) { zr = -z; } - j = 2; - for (i = 1; i < (n+1); i++) - { - j = 3 - j; /* j flip flops between 1, 2 */ - jc = j - 1; /* dummy index for 0-indexing */ - fn = fnu + (i - 1); - init[jc] = 0; - unik(zr, fn, 2, 0, tol, &init[jc], &phi[jc], &zeta1[jc], &zeta2[jc], &sum[jc], &cwrk[jc][0]); - if (kode != 1) { - cfn = fn; - s1 = zeta1[jc] - cfn*(cfn / (zr + zeta2[jc])); - } else { - s1 = zeta1[jc] - zeta2[jc]; - } - // - // TEST FOR UNDERFLOW AND OVERFLOW - // - rs1 = std::real(s1); - if (fabs(rs1) <= elim) { - if (kdflg == 1) { kflag = 2; } - if (fabs(rs1) >= alim) { - // - // REFINE TEST AND SCALE - // - aphi = std::abs(phi[jc]); - rs1 += log(aphi); - if (fabs(rs1) > elim) { goto L10; } - if (kdflg == 1) { kflag = 1; } - if (rs1 >= 0.0) { if (kdflg == 1) { kflag = 3; } } - } - - // - // SCALE S1 TO KEEP INTERMEDIATE ARITHMETIC ON SCALE NEAR - // EXPONENT EXTREMES - // - s2 = phi[jc]*sum[jc]; - c2r = std::real(s1); - c2i = std::imag(s1); - c2m = exp(c2r)*std::real(css[kflag-1]); - s1 = c2m * std::complex(cos(c2i), sin(c2i)); - s2 *= s1; - if (!((kflag == 1) && (uchk(s2, bry[0], tol)))) { - cy[kdflg-1] = s2; - y[i-1] = s2*csr[kflag-1]; - if (kdflg == 2) { break; } - kdflg = 2; - continue; - } - } -L10: - if (rs1 > 0.0 ) { return -1; } - if (x < 0.0) { return -1; } - kdflg = 1; - y[i-1] = 0.0; - nz += 1; - if (i > 1) { - if (y[i-2] != 0.0) { - y[i-2] = 0.0; - nz += 1; - } - } - } - /* Check for exhausted loop */ - if (i == (n+1)) { i = n; } - - rz = 2.0 / zr; - ck = fn * rz; - ib = i + 1; - if (n >= ib) { - // - // TEST LAST MEMBER FOR UNDERFLOW AND OVERFLOW, SET SEQUENCE TO ZERO - // ON UNDERFLOW - // - fn = fnu + (n-1); - ipard = 1; - if (mr != 0) { ipard = 0; } - initd = 0; - unik(zr, fn, 2, ipard, tol, &initd, &phid, &zeta1d, &zeta2d, &sumd, &cwrk[2][0]); - if (kode != 1) { - cfn = fn; - s1 = zeta1d - cfn*(cfn / (zr + zeta2d)); - } else { - s1 = zeta1d - zeta2d; - } - rs1 = std::real(s1); - if (fabs(rs1) <= elim) { - if (fabs(rs1) < alim) { goto L50; } - // - // REFINE ESTIMATE AND TEST - // - aphi = std::abs(phid); - rs1 += log(aphi); - if (fabs(rs1) < elim) { goto L50; } - } - if (rs1 > 0.0) { return -1; } - // - // FOR X < 0.0, THE I FUNCTION TO BE ADDED WILL OVERFLOW - // - if (x < 0.0) { return -1; } - nz = n; - for (i = 0; i < (n+1); i++) { y[i] = 0.0; } - return nz; -L50: - // - // RECUR FORWARD FOR REMAINDER OF THE SEQUENCE - // - s1 = cy[0]; - s2 = cy[1]; - c1 = csr[kflag-1]; - ascle = bry[kflag-1]; - for (i = ib; i < (n+1); i++) - { - c2 = s2; - s2 = ck*s2 + s1; - s1 = c2; - ck += rz; - c2 = s2*c1; - y[i-1] = c2; - if (kflag < 3) { - c2m = fmax(fabs(std::real(c2)), fabs(std::imag(c2))); - if (c2m > ascle) { - kflag += 1; - ascle = bry[kflag-1]; - s1 *= c1; - s2 = c2; - s1 *= css[kflag-1]; - s2 *= css[kflag-1]; - c1 = csr[kflag-1]; - } - } - } - } - if (mr == 0) { return nz; } - // - // ANALYTIC CONTINUATION FOR RE(Z) < 0.0 - // - nz = 0; - fmr = mr; - sgn = (fmr < 0.0 ? pi : -pi ); - // - // CSPN AND CSGN ARE COEFF OF K AND I FUNCIONS RESP. - // - csgn = std::complex(0.0, sgn); - inu = (int)fnu; - fnf = fnu - inu; - ifn = inu + n - 1; - ang = fnf * sgn; - cspn = std::complex(cos(ang), sin(ang)); - if (ifn % 2 == 1) { cspn = -cspn; } - asc = bry[0]; - kk = n; - iuf = 0; - kdflg = 1; - ib -= 1; - ic = ib - 1; - k = 1; - for (k = 1; k < (n+1); k++) - { - fn = fnu + (kk-1); - // - // LOGIC TO SORT OUT CASES WHOSE PARAMETERS WERE SET FOR THE K - // FUNCTION ABOVE - // - m = 3; - if (n > 2) { goto L80; } -L70: - initd = init[j-1]; - phid = phi[j -1]; - zeta1d = zeta1[j-1]; - zeta2d = zeta2[j-1]; - sumd = sum[j-1]; - m = j; - j = 3 - j; - goto L90; -L80: - if (!((kk == n) && (ib < n))) { - if ((kk == ib) || (kk == ic)){ goto L70; } - initd = 0; - } -L90: - unik(zr, fn, 1, 0, tol, &initd, &phid, &zeta1d, &zeta2d, &sumd, &cwrk[m-1][0]); - if (kode != 1) { - cfn = fn; - s1 = -zeta1d + cfn * (cfn/(zr + zeta2d)); - } else { - s1 = -zeta1d + zeta2d; - } - // - // TEST FOR UNDERFLOW AND OVERFLOW - // - rs1 = std::real(s1); - if (fabs(rs1) > elim) { goto L110; } - if (kdflg == 1) { iflag = 2; } - if (fabs(rs1) >= alim) { - // - // REFINE TEST AND SCALE - // - aphi = std::abs(phid); - rs1 += log(aphi); - if (fabs(rs1) > elim) { goto L110; } - if (kdflg == 1) { iflag = 1; } - if (rs1 >= 0.0) { if (kdflg == 1) { iflag = 3; } } - } - - s2 = csgn * phid * sumd; - c2r = std::real(s1); - c2i = std::imag(s1); - c2m = exp(c2r) * std::real(css[iflag-1]); - s1 = c2m * std::complex(cos(c2i), sin(c2i)); - s2 *= s1; - if (iflag == 1) { if (uchk(s2, bry[0], tol)) { s2 = 0.0; } } -L100: - cy[kdflg -1] = s2; - c2 = s2; - s2 *= csr[iflag-1]; - // - // ADD I AND K FUNCTIONS, K SEQUENCE IN Y(I), I=1,N - // - s1 = y[kk-1]; - if (kode != 1) { - nw = s1s2(zr, &s1, &s2, asc, alim, &iuf); - nz += nw; - } - y[kk-1] = s1*cspn + s2; - kk -= 1; - cspn = -cspn; - if (c2 == 0.0) { - kdflg = 1; - continue; - } - if (kdflg == 2) { goto L130; } - kdflg = 2; - continue; -L110: - if (rs1 > 0.0) { return -1; } - s2 = 0.0; - goto L100; - } - /* If loop is exhausted */ - if (k == n+1) { k -= 1; } -L130: - il = n-k; - if (il == 0) { return nz; }; - s1 = cy[0]; - s2 = cy[1]; - cs = csr[iflag-1]; - ascle = bry[iflag-1]; - fn = inu + il; - for (i = 1; i < (il+1); i++) - { - c2 = s2; - s2 = s1 + (fn + fnf) * rz * s2; - s1 = c2; - fn -= 1.0; - c2 = s2 * cs; - ck = c2; - c1 = y[kk-1]; - if (kode != 1) { - nw = s1s2(zr, &c1, &c2, asc, alim, &iuf); - nz = nz + nw; - } - y[kk-1] = c1 * cspn + c2; - kk -= 1; - cspn = -cspn; - if (iflag < 3) { - c2m = fmax(fabs(std::real(c2)), fabs(std::imag(c2))); - if (c2m > ascle) { - iflag += 1; - ascle = bry[iflag-1]; - s1 *= cs; - s2 = ck; - s1 *= css[iflag-1]; - s2 *= css[iflag-1]; - cs = csr[iflag-1]; - } - } - } - return nz; -} - - -inline int unk2( - std::complex z, - double fnu, - int kode, - int mr, - int n, - std::complex *y, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZUNK2 - //***REFER TO ZBESK - // - // ZUNK2 COMPUTES K(FNU,Z) AND ITS ANALYTIC CONTINUATION FROM THE - // RIGHT HALF PLANE TO THE LEFT HALF PLANE BY MEANS OF THE - // UNIFORM ASYMPTOTIC EXPANSIONS FOR H(KIND,FNU,ZN) AND J(FNU,ZN) - // WHERE ZN IS IN THE RIGHT HALF PLANE, KIND=(3-MR)/2, MR=+1 OR - // -1. HERE ZN=ZR*I OR -ZR*I WHERE ZR=Z IF Z IS IN THE RIGHT - // HALF PLANE OR ZR=-Z IF Z IS IN THE LEFT HALF PLANE. MR INDIC- - // ATES THE DIRECTION OF ROTATION FOR ANALYTIC CONTINUATION. - // NZ=-1 MEANS AN OVERFLOW WILL OCCUR - // - //***ROUTINES CALLED ZAIRY,ZKSCL,ZS1S2,ZUCHK,ZUNHJ,D1MACH,AZABS - //***END PROLOGUE ZUNK2 - - std::complex ai, cfn, ck, cs, csgn, cspn, c1, c2, dai, rz, s1, s2,\ - zb, zn, zr, phid, argd, zeta1d, zeta2d, asumd, bsumd; - double aarg, ang, aphi, asc, ascle, car, cpn, c2i, c2m, c2r, crsc, cscl,\ - fmr, fn, fnf, rs1, sar, sgn, spn, x, yy; - int i, ib, iflag = 0, ifn, il, in, inu, iuf, k, kdflg, kflag, kk, nai, ndai,\ - nw, nz, idum, j, ipard, ic; - - std::complex cr1 = std::complex(1.0, 1.73205080756887729); /* 1 + sqrt(3)i */ - std::complex cr2 = std::complex(-0.5, -8.66025403784438647e-1); /* 0.5 cr1 */ - double hpi = 1.57079632679489662; /* 0.5 pi */ - double pi = 3.14159265358979324; - double aic = 1.26551212348464539; /* log(2 sqrt(pi)) */ - std::complex cip[4] = {1.0, -std::complex(0, 1), -1.0, std::complex(0, 1)}; - cscl = 1.0 / tol; - crsc = tol; - std::complex css[3] = {cscl, 1.0, crsc }; - std::complex csr[3] = {crsc, 1.0, cscl }; - std::complex phi[2] = { 0.0 }; - std::complex arg[2] = { 0.0 }; - std::complex zeta1[2] = { 0.0 }; - std::complex zeta2[2] = { 0.0 }; - std::complex asum[2] = { 0.0 }; - std::complex bsum[2] = { 0.0 }; - std::complex cy[2] = { 0.0 }; - double bry[3] = { (1.0 + 1e3*d1mach[0] / tol), 1.0/(1.0 + 1e3*d1mach[0] / tol), d1mach[1]}; - - kdflg = 1; - kflag = 1; - fn = fnu; - nz = 0; - // - // EXP(-ALIM)=EXP(-ELIM)/TOL=APPROX. ONE PRECISION GREATER THAN - // THE UNDERFLOW LIMIT - // - x = std::real(z); - zr = z; - if (x < 0.0) { zr = -z; } - yy = std::imag(zr); - zn = -zr*std::complex(0, 1); - zb = zr; - inu = (int)fnu; - fnf = fnu - inu; - ang = -hpi * fnf; - car = cos(ang); - sar = sin(ang); - cpn = hpi * car; - spn = hpi * sar; - c2 = std::complex(spn, -cpn); - kk = (inu % 4) + 1; - cs = cr1 * c2 * cip[kk - 1]; - if (yy <= 0.0) { - zn = conj(-zn); - zb = conj(zb); - } - // - // K(FNU,Z) IS COMPUTED FROM H(2,FNU,-I*Z) WHERE Z IS IN THE FIRST - // QUADRANT. FOURTH QUADRANT VALUES (YY <= 0.0_dp) ARE COMPUTED BY - // CONJUGATION SINCE THE K FUNCTION IS REAL ON THE POSITIVE REAL AXIS - // - j = 2; - for (i = 1; i < (n+1); i++) - { - j = 3 - j; - fn = fnu + (i-1); - unhj(zn, fn, 0, tol, &phi[j-1], &arg[j-1], &zeta1[j-1], &zeta2[j-1], &asum[j-1], &bsum[j-1]); - if (kode != 1) { - cfn = fn; - s1 = zeta1[j-1] - cfn*(cfn/(zb + zeta2[j-1])); - } else { - s1 = zeta1[j-1] - zeta2[j-1]; - } - // - // TEST FOR UNDERFLOW AND OVERFLOW - // - rs1 = std::real(s1); - if (fabs(rs1) <= elim) { - if (kdflg == 1) { kflag = 2; } - if (fabs(rs1) >= alim) { - // - // REFINE TEST AND SCALE - // - aphi = std::abs(phi[j-1]); - aarg = std::abs(arg[j-1]); - rs1 += log(aphi) - 0.25 * log(aarg) - aic; - if (fabs(rs1) > elim) { - /* GO TO 70 */ - if (rs1 > 0.0) { return -1; } - /* FOR X < 0.0, THE I FUNCTION TO BE ADDED WILL OVERFLOW */ - if (x < 0.0) { return -1; } - kdflg = 1; - y[i-1] = 0.0; - cs *= -std::complex(0, 1); - nz += 1; - if (i != 1) { if (y[i-2] != 0.0) { y[i-2] = 0.0;nz += 1; } } - continue; - } - if (kdflg == 1) { kflag = 1; } - if (rs1 >= 0.0) { if (kdflg == 1) { kflag = 3; } } - } - // - // SCALE S1 TO KEEP INTERMEDIATE ARITHMETIC ON SCALE NEAR - // EXPONENT EXTREMES - // - c2 = arg[j-1] * cr2; - ai = airy(c2, 0, 2, &nai, &idum); - dai = airy(c2, 1, 2, &ndai, &idum); - s2 = cs * phi[j-1] * (ai*asum[j-1] + cr2*dai*bsum[j-1]); - c2r = std::real(s1); - c2i = std::imag(s1); - c2m = exp(c2r) * std::real(css[kflag-1]); - s1 = c2m * std::complex(cos(c2i), sin(c2i)); - s2 *= s1; - if (kflag == 1) { - if (uchk(s2, bry[0], tol)) { - /* GO TO 70 */ - if (rs1 > 0.0) { return -1; } - /* FOR X < 0.0, THE I FUNCTION TO BE ADDED WILL OVERFLOW */ - if (x < 0.0) { return -1; } - kdflg = 1; - y[i-1] = 0.0; - cs *= -std::complex(0, 1); - nz += 1; - if (i != 1) { if (y[i-2] != 0.0) { y[i-2] = 0.0;nz += 1; } } - continue; - } - } - if (yy <= 0.0) { s2 = conj(s2); } - cy[kdflg-1] = s2; - y[i-1] = s2 * csr[kflag-1]; - cs *= -std::complex(0, 1); - if (kdflg == 2) { break; } - kdflg = 2; - continue; - } - /* GO TO 70 */ - if (rs1 > 0.0) { return -1; } - /* FOR X < 0.0, THE I FUNCTION TO BE ADDED WILL OVERFLOW */ - if (x < 0.0) { return -1; } - kdflg = 1; - y[i-1] = 0.0; - cs *= -std::complex(0, 1); - nz += 1; - if (i != 1) { if (y[i-2] != 0.0) { y[i-2] = 0.0;nz += 1; } } - continue; - } - /* Check for exhausted loop */ - if (i == n+1) { i = n; } - - rz = 2.0 / zr; - ck = fn * rz; - ib = i + 1; - if (n >= ib) { - fn = fnu + (n - 1); - ipard = 1; - if (mr != 0) { ipard = 0; } - unhj(zn, fn, ipard, tol, &phid, &argd, &zeta1d, &zeta2d, &asumd, &bsumd); - if (kode != 1) { - cfn = fn; - s1 = zeta1d - cfn * (cfn / (zb + zeta2d)); - } else { - s1 = zeta1d - zeta2d; - } - rs1 = std::real(s1); - if (fabs(rs1) <= elim) { - if (fabs(rs1) < alim) { goto L120; } - // - // REFINE ESTIMATE AND TEST - // - aphi = std::abs(phid); - aarg = std::abs(argd); - rs1 += log(aphi) - 0.25 * log(aarg) - aic; - if (fabs(rs1) < elim) { goto L120; } - } - if (rs1 > 0.0) { return -1; } - // - // FOR X < 0.0, THE I FUNCTION TO BE ADDED WILL OVERFLOW - // - if (x < 0.0) { return -1; } - nz = n; - for (i = 0; i < n; i++) { y[i] = 0.0; } - return nz; -L120: - // - // SCALED FORWARD RECURRENCE FOR REMAINDER OF THE SEQUENCE - // - s1 = cy[0]; - s2 = cy[1]; - c1 = csr[kflag-1]; - ascle = bry[kflag-1]; - for (i = ib; i < (n+1); i++) - { - c2 = s2; - s2 = ck * s2 + s1; - s1 = c2; - ck += rz; - c2 = s2 * c1; - y[i-1] = c2; - if (kflag < 3) { - c2m = fmax(fabs(std::real(c2)), fabs(std::imag(c2))); - if (c2m > ascle) { - kflag += 1; - ascle = bry[kflag-1]; - s1 *= c1; - s2 = c2; - s1 *= css[kflag-1]; - s2 *= css[kflag-1]; - c1 = csr[kflag-1]; - } - } - } - } - if (mr == 0) { return nz; } - // - // ANALYTIC CONTINUATION FOR RE(Z) < 0.0 - // - nz = 0; - fmr = mr; - sgn = ( fmr < 0.0 ? pi : -pi); - // - // CSPN AND CSGN ARE COEFF OF K AND I FUNCTIONS RESP. - // - csgn = std::complex(0.0, sgn); - if (yy <= 0.0) { csgn = -csgn; } - ifn = inu + n - 1; - ang = fnf*sgn; - cspn = std::complex(cos(ang), sin(ang)); - if (ifn % 2 == 1) { cspn = -cspn; } - // - // CS=COEFF OF THE J FUNCTION TO GET THE I FUNCTION. I(FNU,Z) IS - // COMPUTED FROM EXP(I*FNU*HPI)*J(FNU,-I*Z) WHERE Z IS IN THE FIRST - // QUADRANT. FOURTH QUADRANT VALUES (YY <= 0.0_dp) ARE COMPUTED BY - // CONJUGATION SINCE THE I FUNCTION IS REAL ON THE POSITIVE REAL AXIS - // - cs = std::complex(car, -sar) * csgn; - in = (ifn % 4) + 1; - c2 = cip[in-1]; - cs *= conj(c2); - asc = bry[0]; - iuf = 0; - kk = n; - kdflg = 1; - ib -= 1; - ic = ib - 1; - for (k = 1; k < (n+1); k++) { - fn = fnu + (kk-1); - if (n > 2) { goto L175; } -L172: - phid = phi[j-1]; - argd = arg[j-1]; - zeta1d = zeta1[j-1]; - zeta2d = zeta2[j-1]; - asumd = asum[j-1]; - bsumd = bsum[j-1]; - j = 3 - j; - goto L210; -L175: - if (!((kk == n) && (ib < n))) { - if ((kk == ib) || (kk == ic)) { goto L172; } - unhj(zn, fn, 0, tol, &phid, &argd, &zeta1d, &zeta2d, &asumd, &bsumd); - } -L210: - if (kode != 1) { - cfn = fn; - s1 = -zeta1d + cfn * (cfn/(zb + zeta2d)); - } else { - s1 = -zeta1d + zeta2d; - } - // - // TEST FOR UNDERFLOW AND OVERFLOW - // - rs1 = std::real(s1); - if (fabs(rs1) > elim) { - if (rs1 > 0.0) { return -1; } - s2 = 0.0; - goto L250; - } - if (kdflg == 1) { iflag = 2; } - if (fabs(rs1) >= alim) { - // - // REFINE TEST AND SCALE - // - aphi = std::abs(phid); - aarg = std::abs(argd); - rs1 += log(aphi) - 0.25f * log(aarg) - aic; - if (fabs(rs1) > elim) { - if (rs1 > 0.0) { return -1; } - s2 = 0.0; - goto L250; - } - if (kdflg == 1) { iflag = 1; } - if (rs1 >= 0.0) { if (kdflg == 1) {iflag = 3;} } - } - - ai = airy(argd, 0, 2, &nai, &idum); - dai = airy(argd, 1, 2, &ndai, &idum); - s2 = cs * phid * (ai*asumd + dai*bsumd); - c2r = std::real(s1); - c2i = std::imag(s1); - c2m = exp(c2r) * std::real(css[iflag-1]); - s1 = c2m * std::complex(cos(c2i), sin(c2i)); - s2 *= s1; - if (iflag == 1) { if (uchk(s2, bry[0], tol)) { s2 = 0.0; } } -L250: - if (yy <= 0.0) { s2 = conj(s2); } - cy[kdflg-1] = s2; - c2 = s2; - s2 *= csr[iflag-1]; - // - // ADD I AND K FUNCTIONS, K SEQUENCE IN Y(I), I=1,N - // - s1 = y[kk-1]; - if (kode != 1) { - nw = s1s2(zr, &s1, &s2, asc, alim, &iuf); - nz += nw; - } - y[kk-1] = s1 * cspn + s2; - kk -= 1; - cspn = -cspn; - cs *= -std::complex(0, 1); - if (c2 == 0.0) { - kdflg = 1; - continue; - } - if (kdflg == 2) { break; } - kdflg = 2; - continue; - } - /* Check for exhausted loop */ - if (k == n+1) { k = n; } - - il = n - k; - if (il == 0) { return nz; } - // - // RECUR BACKWARD FOR REMAINDER OF I SEQUENCE AND ADD IN THE - // K FUNCTIONS, SCALING THE I SEQUENCE DURING RECURRENCE TO KEEP - // INTERMEDIATE ARITHMETIC ON SCALE NEAR EXPONENT EXTREMES. - // - s1 = cy[0]; - s2 = cy[1]; - cs = csr[iflag-1]; - ascle = bry[iflag-1]; - fn = inu + il; - for (i = 1; i < (il+1); i++) { - c2 = s2; - s2 = s1 + (fn + fnf) * rz * c2; - s1 = c2; - fn -= 1.0; - c2 = s2 * cs; - ck = c2; - c1 = y[kk-1]; - if (kode != 1) { - nw = s1s2(zr, &c1, &c2, asc, alim, &iuf); - nz = nz + nw; - } - y[kk-1] = c1 * cspn + c2; - kk -= 1; - cspn = -cspn; - if (iflag < 3) { - c2m = fmax(fabs(std::real(ck)), fabs(std::imag(ck))); - if (c2m > ascle) { - iflag += 1; - ascle = bry[iflag-1]; - s1 *= cs; - s2 = ck; - s1 *= css[iflag-1]; - s2 *= css[iflag-1]; - cs = csr[iflag-1]; - } - } - } - return nz; -} - - -inline int uoik( - std::complex z, - double fnu, - int kode, - int ikflg, - int n, - std::complex *y, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZUOIK - //***REFER TO ZBESI,ZBESK,ZBESH - // - // ZUOIK COMPUTES THE LEADING TERMS OF THE UNIFORM ASYMPTOTIC - // EXPANSIONS FOR THE I AND K FUNCTIONS AND COMPARES THEM - // (IN LOGARITHMIC FORM) TO ALIM AND ELIM FOR OVER AND UNDERFLOW - // WHERE ALIM.LT.ELIM. IF THE MAGNITUDE, BASED ON THE LEADING - // EXPONENTIAL, IS LESS THAN ALIM OR GREATER THAN -ALIM, THEN - // THE RESULT IS ON SCALE. IF NOT, THEN A REFINED TEST USING OTHER - // MULTIPLIERS (IN LOGARITHMIC FORM) IS MADE BASED ON ELIM. HERE - // EXP(-ELIM)=SMALLEST MACHINE NUMBER*1.0E+3 AND EXP(-ALIM)= - // EXP(-ELIM)/TOL - // - // IKFLG=1 MEANS THE I SEQUENCE IS TESTED - // =2 MEANS THE K SEQUENCE IS TESTED - // NUF = 0 MEANS THE LAST MEMBER OF THE SEQUENCE IS ON SCALE - // =-1 MEANS AN OVERFLOW WOULD OCCUR - // IKFLG=1 AND NUF.GT.0 MEANS THE LAST NUF Y VALUES WERE SET TO ZERO - // THE FIRST N-NUF VALUES MUST BE SET BY ANOTHER ROUTINE - // IKFLG=2 AND NUF.EQ.N MEANS ALL Y VALUES WERE SET TO ZERO - // IKFLG=2 AND 0.LT.NUF.LT.N NOT CONSIDERED. Y MUST BE SET BY - // ANOTHER ROUTINE - // - //***ROUTINES CALLED ZUCHK,ZUNHJ,ZUNIK,D1MACH,AZABS,AZLOG - //***END PROLOGUE ZUOIK - - std::complex arg, asum, bsum, cz, phi, sum, zb, zeta1; - std::complex zeta2, zn, zr; - double aarg, aphi, ascle, ax, ay, fnn, gnn, gnu, rcz, x, yy; - int iform, init, nn; - double aic = 1.265512123484645396; - std::complex cwrk[16] = { 0. }; - - int nuf = 0; - nn = n; - x = std::real(z); - zr = z; - if (x < 0.) { zr = -z; } - zb = zr; - yy = std::imag(zr); - ax = fabs(x) * sqrt(3.); - ay = fabs(yy); - iform = 1; - if (ay > ax) { iform = 2; } - gnu = fmax(fnu, 1.); - if (ikflg != 1) { - fnn = nn; - gnn = fnu + fnn -1; - gnu = fmax(gnn, fnn); - } - - if (iform != 2) { - init = 0; - unik(zr, gnu, ikflg, 1, tol, &init, &phi, &zeta1, &zeta2, &sum, &cwrk[0]); - cz = -zeta1 + zeta2; - } else { - zn = -zr * std::complex(0, 1); - if (yy <= 0.) { - zn = conj(zn); - } - unhj(zn, gnu, 1, tol, &phi, &arg, &zeta1, &zeta2, &asum, &bsum); - cz = zeta2 - zeta1; - aarg = std::abs(arg); - } - if (kode == 2) { cz -= zb; } - if (ikflg == 2) { cz = -cz; } - aphi = std::abs(phi); - rcz = std::real(cz); - - /* OVERFLOW TEST */ - if (rcz > elim) { return -1; } - if (rcz >= alim) { - rcz += log(aphi); - if (iform == 2) { rcz -= 0.25*log(aarg) + aic; } - if (rcz > elim) { return -1; } - } else { - /* UNDERFLOW TEST */ - if (rcz >= -elim) { - if (rcz > -alim) { - /* pass */ - } else { - rcz += log(aphi); - if (iform == 2) { rcz -= 0.25*log(aarg) + aic; } - if (rcz > -elim) { - /* goto 30 */ - ascle = 1e3*d1mach[0] / tol; - cz += std::log(phi); - if (iform != 1) { cz -= 0.25*log(arg) + aic;} - ax = exp(rcz) / tol; - ay = std::imag(cz); - cz = ax*std::exp(ay); - if (uchk(cz, ascle, tol)) { - for (int i = 0; i < nn; i++){ y[i] = 0.; } - return nn; - } - } else { - for (int i = 0; i < nn; i++){ y[i] = 0.; } - return nn; - } - } - } else { - for (int i = 0; i < nn; i++){ y[i] = 0.; } - return nn; - } - } - if ((ikflg == 2) || (n == 1)) { return nuf; } - /* 140 */ - while (1) { - gnu = fnu + (nn -1); - if (iform != 2) { - init = 0; - unik(zr, gnu, ikflg, 1, tol, &init, &phi, &zeta1, &zeta2, &sum, &cwrk[0]); - cz = zeta2 - zeta1; - } else { - unhj(zn, gnu, 1, tol, &phi, &arg, &zeta1, &zeta2, &asum, &bsum); - cz = zeta2 - zeta1; - aarg = std::abs(phi); - } - if (kode == 2) { cz -= zb; } - - /* 170 */ - aphi = std::abs(phi); - rcz = std::real(cz); - - if (rcz >= -elim) { - if (rcz > -alim) { return nuf; } - rcz += log(aphi); - if (iform == 2) { rcz -= 0.25*log(aarg) + aic; } - if (rcz > -elim) { - ascle = 1e3 * d1mach[0] / tol; - cz = std::log(phi); - if (iform != 1) { cz -= 0.25*std::log(arg) + aic; } - ax = exp(rcz)/tol; - ay = std::imag(cz); - cz = ax*(cos(ay)+sin(ay*std::complex(0, 1))); - if (!(uchk(cz, ascle, tol))) { return nuf; } - } - } - - y[nn-1] = 0.; - nn -= 1; - nuf += 1; - if (nn == 0) { return nuf; } - } - return -1; -} - - -inline int wrsk( - std::complex zr, - double fnu, - int kode, - int n, - std::complex *y, - std::complex *cw, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZWRSK - //***REFER TO ZBESI,ZBESK - // - // ZWRSK COMPUTES THE I BESSEL FUNCTION FOR RE(Z).GE.0.0 BY - // NORMALIZING THE I FUNCTION RATIOS FROM ZRATI BY THE WRONSKIAN - // - //***ROUTINES CALLED D1MACH,ZBKNU,ZRATI,AZABS - //***END PROLOGUE ZWRSK - - std::complex cinu, cscl, ct, c1, c2, rct, st; - double act, acw, ascle, yy; - int i, nw, nz; - - // - // I(FNU+I-1,Z) BY BACKWARD RECURRENCE FOR RATIOS - // Y(I)=I(FNU+I,Z)/I(FNU+I-1,Z) FROM CRATI NORMALIZED BY THE - // WRONSKIAN WITH K(FNU,Z) AND K(FNU+1,Z) FROM CBKNU. - // - nz = 0; - nw = bknu(zr, fnu, kode, 2, cw, tol, elim, alim); - if (nw != 0) { - /* 50 */ - nz = -1; - if (nw == -2) { - nz = -2; - } - return nz; - } - rati(zr, fnu, n, y, tol); - // - // RECUR FORWARD ON I(FNU+1,Z) = R(FNU,Z)*I(FNU,Z), - // R(FNU+J-1,Z)=Y(J), J=1,...,N - // - cinu = 1.0; - if (kode != 1) { - yy = std::imag(zr); - cinu = std::complex(cos(yy), sin(yy)); - } - // - // ON LOW EXPONENT MACHINES THE K FUNCTIONS CAN BE CLOSE TO BOTH THE - // UNDER AND OVERFLOW LIMITS AND THE NORMALIZATION MUST BE SCALED TO - // PREVENT OVER OR UNDERFLOW. CUOIK HAS DETERMINED THAT THE RESULT - // IS ON SCALE. - // - acw = std::abs(cw[1]); - ascle = 1e3*d1mach[0]/tol; - cscl = 1.0; - - if (acw <= ascle) { - cscl = 1.0 / tol; - } else { - ascle = 1.0 / ascle; - if (acw >= ascle) { - cscl = tol; - } - } - - c1 = cw[0]*cscl; - c2 = cw[1]*cscl; - st = y[0]; - // - // CINU=CINU*(CONJG(CT)/ABS(CT))*(1.0_dp/ABS(CT) PREVENTS - // UNDER- OR OVERFLOW PREMATURELY BY SQUARING ABS(CT) - // - ct = zr * (c2 + st*c1); - act = std::abs(ct); - rct = 1.0 / act; - ct = conj(ct)*rct; - cinu *= ct*rct; - y[0] = cinu*cscl; - if (n == 1) { return nz; } - for (i = 2; i < (n+1); i++) { - cinu *= st; - st = y[i-1]; - y[i-1] = cinu*cscl; - } - return nz; -} -} -} diff --git a/scipy/special/xsf/bessel.h b/scipy/special/xsf/bessel.h deleted file mode 100644 index 74473e8dbbc8..000000000000 --- a/scipy/special/xsf/bessel.h +++ /dev/null @@ -1,1202 +0,0 @@ -#pragma once - -#include "amos.h" -#include "cephes/besselpoly.h" -#include "cephes/i0.h" -#include "cephes/i1.h" -#include "cephes/jv.h" -#include "cephes/k0.h" -#include "cephes/k1.h" -#include "cephes/scipy_iv.h" -#include "cephes/yv.h" -#include "error.h" -#include "specfun.h" -#include "trig.h" - -extern "C" double cephes_iv(double v, double x); - -namespace xsf { -namespace detail { - - template - std::complex rotate(std::complex z, T v) { - T c = cospi(v); - T s = sinpi(v); - - return {c * std::real(z) - s * std::imag(z), s * std::real(z) + c * std::imag(z)}; - } - - template - std::complex rotate_jy(std::complex j, std::complex y, T v) { - T c = cospi(v); - T s = sinpi(v); - - return c * j - s * y; - } - - template - int reflect_jy(std::complex *jy, T v) { - /* NB: Y_v may be huge near negative integers -- so handle exact - * integers carefully - */ - int i; - if (v != floor(v)) - return 0; - - i = v - 16384.0 * floor(v / 16384.0); - if (i & 1) { - *jy = -(*jy); - } - return 1; - } - - template - int reflect_i(std::complex *ik, T v) { - if (v != floor(v)) { - return 0; - } - - return 1; /* I is symmetric for integer v */ - } - - template - std::complex rotate_i(std::complex i, std::complex k, T v) { - T s = std::sin(v * M_PI) * (2 / M_PI); - - return i + s * k; - } - - template - void itika(T x, T *ti, T *tk) { - - // ======================================================= - // Purpose: Integrate modified Bessel functions I0(t) and - // K0(t) with respect to t from 0 to x - // Input : x --- Upper limit of the integral ( x ≥ 0 ) - // Output: TI --- Integration of I0(t) from 0 to x - // TK --- Integration of K0(t) from 0 to x - // ======================================================= - - int k; - T rc1, rc2, e0, b1, b2, rs, r, tw, x2; - static const T a[10] = { - 0.625, - 1.0078125, - 2.5927734375, - 9.1868591308594, - 4.1567974090576e+1, - 2.2919635891914e+2, - 1.491504060477e+3, - 1.1192354495579e+4, - 9.515939374212e+4, - 9.0412425769041e+5 - }; - const T pi = 3.141592653589793; - const T el = 0.5772156649015329; - - if (x == 0.0) { - *ti = 0.0; - *tk = 0.0; - return; - } else if (x < 20.0) { - x2 = x * x; - *ti = 1.0; - r = 1.0; - for (k = 1; k <= 50; k++) { - r = 0.25 * r * (2 * k - 1.0) / (2 * k + 1.0) / (k * k) * x2; - *ti += r; - if (fabs(r / (*ti)) < 1.0e-12) { - break; - } - } - *ti *= x; - } else { - x2 = 0.0; - *ti = 1.0; - r = 1.0; - for (k = 1; k <= 10; k++) { - r = r / x; - *ti += a[k - 1] * r; - } - rc1 = 1.0 / sqrt(2.0 * pi * x); - *ti = rc1 * exp(x) * (*ti); - } - - if (x < 12.0) { - e0 = el + log(x / 2.0); - b1 = 1.0 - e0; - b2 = 0.0; - rs = 0.0; - r = 1.0; - tw = 0.0; - for (k = 1; k <= 50; k++) { - r = 0.25 * r * (2 * k - 1.0) / (2 * k + 1.0) / (k * k) * x2; - b1 += r * (1.0 / (2 * k + 1) - e0); - rs += 1.0 / k; - b2 += r * rs; - *tk = b1 + b2; - if (fabs((*tk - tw) / (*tk)) < 1.0e-12) { - break; - } - tw = *tk; - } - *tk *= x; - } else { - *tk = 1.0; - r = 1.0; - for (k = 1; k <= 10; k++) { - r = -r / x; - *tk = *tk + a[k - 1] * r; - } - rc2 = sqrt(pi / (2.0 * x)); - *tk = pi / 2.0 - rc2 * (*tk) * exp(-x); - } - return; - } - - template - void itjya(T x, T *tj, T *ty) { - int k; - T a[18], a0, a1, af, bf, bg, r, r2, rc, rs, ty1, ty2, x2, xp; - const T pi = 3.141592653589793; - const T el = 0.5772156649015329; - const T eps = 1.0e-12; - - if (x == 0.0) { - *tj = 0.0; - *ty = 0.0; - } else if (x <= 20.0) { - x2 = x * x; - *tj = x; - r = x; - for (k = 1; k < 61; k++) { - r = -0.25 * r * (2.0 * k - 1.0) / (2.0 * k + 1.0) / (k * k) * x2; - *tj += r; - if (fabs(r) < fabs(*tj) * eps) { - break; - } - } - ty1 = (el + log(x / 2.0)) * (*tj); - rs = 0.0; - ty2 = 1.0; - r = 1.0; - for (k = 1; k < 61; k++) { - r = -0.25 * r * (2.0 * k - 1.0) / (2.0 * k + 1.0) / (k * k) * x2; - rs += 1.0 / k; - r2 = r * (rs + 1.0 / (2.0 * k + 1.0)); - ty2 += r2; - if (fabs(r2) < fabs(ty2) * eps) { - break; - } - } - *ty = (ty1 - x * ty2) * 2.0 / pi; - } else { - a0 = 1.0; - a1 = 5.0 / 8.0; - a[0] = a1; - - for (int k = 1; k <= 16; k++) { - af = ((1.5 * (k + 0.5) * (k + 5.0 / 6.0) * a1 - 0.5 * (k + 0.5) * (k + 0.5) * (k - 0.5) * a0)) / - (k + 1.0); - a[k] = af; - a0 = a1; - a1 = af; - } - bf = 1.0; - r = 1.0; - for (int k = 1; k <= 8; k++) { - r = -r / (x * x); - bf += a[2 * k - 1] * r; - } - bg = a[0] / x; - r = 1.0 / x; - for (int k = 1; k <= 8; k++) { - r = -1.0 / (x * x); - bg += a[2 * k] * r; - } - xp = x + 0.25 * pi; - rc = sqrt(2.0 / (pi * x)); - *tj = 1.0 - rc * (bf * cos(xp) + bg * sin(xp)); - *ty = rc * (bg * cos(xp) - bf * sin(xp)); - } - return; - } - - template - void ittika(T x, T *tti, T *ttk) { - - // ========================================================= - // Purpose: Integrate [I0(t)-1]/t with respect to t from 0 - // to x, and K0(t)/t with respect to t from x to ∞ - // Input : x --- Variable in the limits ( x ≥ 0 ) - // Output: TTI --- Integration of [I0(t)-1]/t from 0 to x - // TTK --- Integration of K0(t)/t from x to ∞ - // ========================================================= - - int k; - T b1, e0, r, r2, rc, rs; - const T pi = 3.141592653589793; - const T el = 0.5772156649015329; - static const T c[8] = {1.625, 4.1328125, 1.45380859375, 6.553353881835, - 3.6066157150269, 2.3448727161884, 1.7588273098916, 1.4950639538279}; - - if (x == 0.0) { - *tti = 0.0; - *ttk = 1.0e+300; - return; - } - if (x < 40.0) { - *tti = 1.0; - r = 1.0; - for (int k = 2; k <= 50; k++) { - r = 0.25 * r * (k - 1.0) / (k * k * k) * x * x; - *tti += r; - if (fabs(r / (*tti)) < 1.0e-12) { - break; - } - } - *tti *= 0.125 * x * x; - } else { - *tti = 1.0; - r = 1.0; - for (int k = 1; k <= 8; k++) { - r = r / x; - *tti += c[k - 1] * r; - } - rc = x * sqrt(2.0 * pi * x); - *tti = (*tti) * exp(x) / rc; - } - if (x <= 12.0) { - e0 = (0.5 * log(x / 2.0) + el) * log(x / 2.0) + pi * pi / 24.0 + 0.5 * el * el; - b1 = 1.5 - (el + log(x / 2.0)); - rs = 1.0; - r = 1.0; - for (k = 2; k <= 50; k++) { - r = 0.25 * r * (k - 1.0) / (k * k * k) * x * x; - rs += 1.0 / k; - r2 = r * (rs + 1.0 / (2.0 * k) - (el + log(x / 2.0))); - b1 += r2; - if (fabs(r2 / b1) < 1.0e-12) { - break; - } - } - *ttk = e0 - 0.125 * x * x * b1; - } else { - *ttk = 1.0; - r = 1.0; - for (k = 1; k <= 8; k++) { - r = -r / x; - *ttk += c[k - 1] * r; - } - rc = x * sqrt(2.0 / (pi * x)); - *ttk = (*ttk) * exp(-x) / rc; - } - return; - } - - template - void ittjya(T x, T *ttj, T *tty) { - - // ========================================================= - // Purpose: Integrate [1-J0(t)]/t with respect to t from 0 - // to x, and Y0(t)/t with respect to t from x to ∞ - // Input : x --- Variable in the limits ( x ≥ 0 ) - // Output: TTJ --- Integration of [1-J0(t)]/t from 0 to x - // TTY --- Integration of Y0(t)/t from x to ∞ - // ========================================================= - - int k, l; - T a0, bj0, bj1, by0, by1, e0, b1, g0, g1, px, qx, r, r0, r1, r2, rs, t, vt, xk; - const T pi = 3.141592653589793; - const T el = 0.5772156649015329; - - if (x == 0.0) { - *ttj = 0.0; - *tty = -1.0e+300; - } else if (x <= 20.0) { - *ttj = 1.0; - r = 1.0; - for (k = 2; k <= 100; k++) { - r = -0.25 * r * (k - 1.0) / (k * k * k) * x * x; - *ttj += r; - if (fabs(r) < fabs(*ttj) * 1.0e-12) { - break; - } - } - *ttj *= 0.125 * x * x; - e0 = 0.5 * (pi * pi / 6.0 - el * el) - (0.5 * log(x / 2.0) + el) * log(x / 2.0); - b1 = el + log(x / 2.0) - 1.5; - rs = 1.0; - r = -1.0; - for (k = 2; k <= 100; k++) { - r = -0.25 * r * (k - 1.0) / (k * k * k) * x * x; - rs = rs + 1.0 / k; - r2 = r * (rs + 1.0 / (2.0 * k) - (el + log(x / 2.0))); - b1 = b1 + r2; - if (fabs(r2) < fabs(b1) * 1.0e-12) { - break; - } - } - *tty = 2.0 / pi * (e0 + 0.125 * x * x * b1); - } else { - a0 = sqrt(2.0 / (pi * x)); - bj0 = 0.0; - by0 = 0.0; - bj1 = 0.0; - for (l = 0; l <= 1; l++) { - vt = 4.0 * l * l; - px = 1.0; - r = 1.0; - for (k = 1; k <= 14; k++) { - r = -0.0078125 * r * (vt - pow((4.0 * k - 3.0), 2)) / (x * k) * (vt - pow((4.0 * k - 1.0), 2)) / - ((2.0 * k - 1.0) * x); - px += r; - if (fabs(r) < fabs(px) * 1.0e-12) { - break; - } - } - qx = 1.0; - r = 1.0; - for (k = 1; k <= 14; k++) { - r = -0.0078125 * r * (vt - pow((4.0 * k - 1.0), 2)) / (x * k) * (vt - pow((4.0 * k + 1.0), 2)) / - ((2.0 * k + 1.0) * x); - qx += r; - if (fabs(r) < fabs(qx) * 1.0e-12) { - break; - } - } - qx = 0.125 * (vt - 1.0) / x * qx; - xk = x - (0.25 + 0.5 * l) * pi; - bj1 = a0 * (px * cos(xk) - qx * sin(xk)); - by1 = a0 * (px * sin(xk) + qx * cos(xk)); - if (l == 0) { - bj0 = bj1; - by0 = by1; - } - } - t = 2.0 / x; - g0 = 1.0; - r0 = 1.0; - for (k = 1; k <= 10; k++) { - r0 = -r0 * k * k * t * t; - g0 += r0; - } - g1 = 1.0; - r1 = 1.0; - for (k = 1; k <= 10; k++) { - r1 = -r1 * k * (k + 1.0) * t * t; - g1 += r1; - } - *ttj = 2.0 * g1 * bj0 / (x * x) - g0 * bj1 / x + el + log(x / 2.0); - *tty = 2.0 * g1 * by0 / (x * x) - g0 * by1 / x; - } - return; - } - -} // namespace detail - -/* Integrals of bessel functions */ - -/* int(j0(t),t=0..x) */ -/* int(y0(t),t=0..x) */ - -template -void it1j0y0(T x, T &j0int, T &y0int) { - int flag = 0; - - if (x < 0) { - x = -x; - flag = 1; - } - detail::itjya(x, &j0int, &y0int); - if (flag) { - j0int = -j0int; - y0int = std::numeric_limits::quiet_NaN(); /* domain error */ - } -} - -/* int((1-j0(t))/t,t=0..x) */ -/* int(y0(t)/t,t=x..inf) */ - -template -void it2j0y0(T x, T &j0int, T &y0int) { - int flag = 0; - - if (x < 0) { - x = -x; - flag = 1; - } - detail::ittjya(x, &j0int, &y0int); - if (flag) { - y0int = std::numeric_limits::quiet_NaN(); /* domain error */ - } -} - -/* Integrals of modified bessel functions */ - -template -void it1i0k0(T x, T &i0int, T &k0int) { - int flag = 0; - - if (x < 0) { - x = -x; - flag = 1; - } - detail::itika(x, &i0int, &k0int); - if (flag) { - i0int = -i0int; - k0int = std::numeric_limits::quiet_NaN(); /* domain error */ - } -} - -template -void it2i0k0(T x, T &i0int, T &k0int) { - int flag = 0; - - if (x < 0) { - x = -x; - flag = 1; - } - detail::ittika(x, &i0int, &k0int); - if (flag) { - k0int = std::numeric_limits::quiet_NaN(); /* domain error */ - } -} - -template -void rctj(T x, int *nm, OutputVec1 rj, OutputVec2 dj) { - - // ======================================================== - // Purpose: Compute Riccati-Bessel functions of the first - // kind and their derivatives - // Input: x --- Argument of Riccati-Bessel function - // n --- Order of jn(x) ( n = 0,1,2,... ) - // Output: RJ(n) --- x·jn(x) - // DJ(n) --- [x·jn(x)]' - // NM --- Highest order computed - // Routines called: - // MSTA1 and MSTA2 for computing the starting - // point for backward recurrence - // ======================================================== - - int n = rj.extent(0) - 1; - - int k, m; - T cs, f, f0, f1, rj0, rj1; - - *nm = n; - if (fabs(x) < 1.0e-100) { - for (int k = 0; k <= n; k++) { - rj[k] = 0.0; - dj[k] = 0.0; - } - dj[0] = 1.0; - return; - } - rj[0] = sin(x); - rj[1] = rj[0] / x - cos(x); - rj0 = rj[0]; - rj1 = rj[1]; - cs = 0.0; - f = 0.0; - - if (n >= 2) { - m = specfun::msta1(x, 200); - if (m < n) { - *nm = m; - } else { - m = specfun::msta2(x, n, 15); - } - - f0 = 0.0; - f1 = 1.0e-100; - - for (k = m; k >= 0; k--) { - f = (2.0 * k + 3.0) * f1 / x - f0; - if (k <= *nm) { - rj[k] = f; - } - f0 = f1; - f1 = f; - } - cs = (fabs(rj0) > fabs(rj1) ? rj0 / f : rj1 / f0); - for (k = 0; k <= *nm; k++) { - rj[k] = cs * rj[k]; - } - } - dj[0] = cos(x); - for (int k = 1; k <= *nm; k++) { - dj[k] = -k * rj[k] / x + rj[k - 1]; - } -} - -template -void rctj(T x, OutputVec1 rj, OutputVec2 dj) { - int nm; - rctj(x, &nm, rj, dj); -} - -template -void rcty(T x, int *nm, OutputVec1 ry, OutputVec2 dy) { - - // ======================================================== - // Purpose: Compute Riccati-Bessel functions of the second - // kind and their derivatives - // Input: x --- Argument of Riccati-Bessel function - // n --- Order of yn(x) - // Output: RY(n) --- x·yn(x) - // DY(n) --- [x·yn(x)]' - // NM --- Highest order computed - // ======================================================== - - int n = ry.extent(0) - 1; - - int k; - T rf0, rf1, rf2; - *nm = n; - if (x < 1.0e-60) { - for (k = 0; k <= n; k++) { - ry[k] = -1.0e+300; - dy[k] = 1.0e+300; - } - ry[0] = -1.0; - dy[0] = 0.0; - return; - } - - ry[0] = -cos(x); - ry[1] = ry[0] / x - sin(x); - rf0 = ry[0]; - rf1 = ry[1]; - - for (k = 2; k <= n; k++) { - rf2 = (2.0 * k - 1.0) * rf1 / x - rf0; - if (fabs(rf2) > 1.0e+300) { - break; - } - ry[k] = rf2; - rf0 = rf1; - rf1 = rf2; - } - - *nm = k - 1; - dy[0] = sin(x); - for (k = 1; k <= *nm; k++) { - dy[k] = -k * ry[k] / x + ry[k - 1]; - } - return; -} - -template -void rcty(T x, OutputVec1 ry, OutputVec2 dy) { - int nm; - rcty(x, &nm, ry, dy); -} - -inline std::complex cyl_bessel_je(double v, std::complex z) { - int n = 1; - int kode = 2; - int nz, ierr; - int sign = 1; - std::complex cy_j, cy_y; - - cy_j.real(NAN); - cy_j.imag(NAN); - cy_y.real(NAN); - cy_y.imag(NAN); - - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy_j; - } - if (v < 0) { - v = -v; - sign = -1; - } - nz = amos::besj(z, v, kode, n, &cy_j, &ierr); - set_error_and_nan("jve:", ierr_to_sferr(nz, ierr), cy_j); - if (sign == -1) { - if (!detail::reflect_jy(&cy_j, v)) { - nz = amos::besy(z, v, kode, n, &cy_y, &ierr); - set_error_and_nan("jve(yve):", ierr_to_sferr(nz, ierr), cy_y); - cy_j = detail::rotate_jy(cy_j, cy_y, v); - } - } - return cy_j; -} - -inline std::complex cyl_bessel_je(float v, std::complex x) { - return static_cast>(cyl_bessel_je(static_cast(v), static_cast>(x)) - ); -} - -template -T cyl_bessel_je(T v, T x) { - if (v != floor(v) && x < 0) { - return std::numeric_limits::quiet_NaN(); - } - - return std::real(cyl_bessel_je(v, std::complex(x))); -} - -inline double cyl_bessel_y0(double x) { return cephes::y0(x); } - -inline float cyl_bessel_y0(float x) { return cyl_bessel_y0(static_cast(x)); } - -inline double cyl_bessel_y1(double x) { return cephes::y1(x); } - -inline float cyl_bessel_y1(float x) { return cyl_bessel_y1(static_cast(x)); } - -inline std::complex cyl_bessel_ye(double v, std::complex z) { - int n = 1; - int kode = 2; - int nz, ierr; - int sign = 1; - std::complex cy_y, cy_j; - - cy_j.real(NAN); - cy_j.imag(NAN); - cy_y.real(NAN); - cy_y.imag(NAN); - - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy_y; - } - if (v < 0) { - v = -v; - sign = -1; - } - nz = amos::besy(z, v, kode, n, &cy_y, &ierr); - set_error_and_nan("yve:", ierr_to_sferr(nz, ierr), cy_y); - if (ierr == 2) { - if (z.real() >= 0 && z.imag() == 0) { - /* overflow */ - cy_y.real(INFINITY); - cy_y.imag(0); - } - } - - if (sign == -1) { - if (!detail::reflect_jy(&cy_y, v)) { - nz = amos::besj(z, v, kode, n, &cy_j, &ierr); - set_error_and_nan("yv(jv):", ierr_to_sferr(nz, ierr), cy_j); - cy_y = detail::rotate_jy(cy_y, cy_j, -v); - } - } - return cy_y; -} - -inline std::complex cyl_bessel_ye(float v, std::complex x) { - return static_cast>(cyl_bessel_ye(static_cast(v), static_cast>(x)) - ); -} - -template -T cyl_bessel_ye(T v, T x) { - if (x < 0) { - return std::numeric_limits::quiet_NaN(); - } - - return std::real(cyl_bessel_ye(v, std::complex(x))); -} - -inline std::complex cyl_bessel_ie(double v, std::complex z) { - int n = 1; - int kode = 2; - int sign = 1; - int nz, ierr; - std::complex cy, cy_k; - - cy.real(NAN); - cy.imag(NAN); - cy_k.real(NAN); - cy_k.imag(NAN); - - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy; - } - if (v < 0) { - v = -v; - sign = -1; - } - nz = amos::besi(z, v, kode, n, &cy, &ierr); - set_error_and_nan("ive:", ierr_to_sferr(nz, ierr), cy); - - if (sign == -1) { - if (!detail::reflect_i(&cy, v)) { - nz = amos::besk(z, v, kode, n, &cy_k, &ierr); - set_error_and_nan("ive(kv):", ierr_to_sferr(nz, ierr), cy_k); - /* adjust scaling to match zbesi */ - cy_k = detail::rotate(cy_k, -z.imag() / M_PI); - if (z.real() > 0) { - cy_k.real(cy_k.real() * exp(-2 * z.real())); - cy_k.imag(cy_k.imag() * exp(-2 * z.real())); - } - /* v -> -v */ - cy = detail::rotate_i(cy, cy_k, v); - } - } - - return cy; -} - -inline std::complex cyl_bessel_ie(float v, std::complex x) { - return static_cast>(cyl_bessel_ie(static_cast(v), static_cast>(x)) - ); -} - -template -T cyl_bessel_ie(T v, T x) { - if (v != floor(v) && x < 0) { - return std::numeric_limits::quiet_NaN(); - } - - return std::real(cyl_bessel_ie(v, std::complex(x))); -} - -inline std::complex cyl_bessel_ke(double v, std::complex z) { - std::complex cy{NAN, NAN}; - if (std::isnan(v) || std::isnan(std::real(z)) || std::isnan(std::imag(z))) { - return cy; - } - - if (v < 0) { - /* K_v == K_{-v} even for non-integer v */ - v = -v; - } - - int n = 1; - int kode = 2; - int ierr; - int nz = amos::besk(z, v, kode, n, &cy, &ierr); - set_error_and_nan("kve:", ierr_to_sferr(nz, ierr), cy); - if (ierr == 2) { - if (std::real(z) >= 0 && std::imag(z) == 0) { - /* overflow */ - cy = INFINITY; - } - } - - return cy; -} - -inline std::complex cyl_bessel_ke(float v, std::complex x) { - return static_cast>(cyl_bessel_ke(static_cast(v), static_cast>(x)) - ); -} - -template -T cyl_bessel_ke(T v, T x) { - if (x < 0) { - return std::numeric_limits::quiet_NaN(); - } - - if (x == 0) { - return std::numeric_limits::infinity(); - } - - return std::real(cyl_bessel_ke(v, std::complex(x))); -} - -inline std::complex cyl_hankel_1e(double v, std::complex z) { - int n = 1; - int kode = 2; - int m = 1; - int nz, ierr; - int sign = 1; - std::complex cy; - - cy.real(NAN); - cy.imag(NAN); - - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy; - } - if (v < 0) { - v = -v; - sign = -1; - } - nz = amos::besh(z, v, kode, m, n, &cy, &ierr); - set_error_and_nan("hankel1e:", ierr_to_sferr(nz, ierr), cy); - if (sign == -1) { - cy = detail::rotate(cy, v); - } - return cy; -} - -inline std::complex cyl_hankel_1e(float v, std::complex z) { - return static_cast>(cyl_hankel_1e(static_cast(v), static_cast>(z)) - ); -} - -inline std::complex cyl_hankel_2e(double v, std::complex z) { - int n = 1; - int kode = 2; - int m = 2; - int nz, ierr; - int sign = 1; - - std::complex cy{NAN, NAN}; - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy; - } - - if (v < 0) { - v = -v; - sign = -1; - } - nz = amos::besh(z, v, kode, m, n, &cy, &ierr); - set_error_and_nan("hankel2e:", ierr_to_sferr(nz, ierr), cy); - if (sign == -1) { - cy = detail::rotate(cy, -v); - } - return cy; -} - -inline std::complex cyl_hankel_2e(float v, std::complex z) { - return static_cast>(cyl_hankel_2e(static_cast(v), static_cast>(z)) - ); -} - -inline double cyl_bessel_j0(double x) { return cephes::j0(x); } - -inline float cyl_bessel_j0(float x) { return cyl_bessel_j0(static_cast(x)); } - -inline double cyl_bessel_j1(double x) { return cephes::j1(x); } - -inline float cyl_bessel_j1(float x) { return cyl_bessel_j1(static_cast(x)); } - -inline std::complex cyl_bessel_j(double v, std::complex z) { - int n = 1; - int kode = 1; - int nz, ierr; - int sign = 1; - std::complex cy_j, cy_y; - - cy_j.real(NAN); - cy_j.imag(NAN); - cy_y.real(NAN); - cy_y.imag(NAN); - - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy_j; - } - if (v < 0) { - v = -v; - sign = -1; - } - nz = amos::besj(z, v, kode, n, &cy_j, &ierr); - set_error_and_nan("jv:", ierr_to_sferr(nz, ierr), cy_j); - if (ierr == 2) { - /* overflow */ - cy_j = cyl_bessel_je(v, z); - cy_j.real(cy_j.real() * INFINITY); - cy_j.imag(cy_j.imag() * INFINITY); - } - - if (sign == -1) { - if (!detail::reflect_jy(&cy_j, v)) { - nz = amos::besy(z, v, kode, n, &cy_y, &ierr); - set_error_and_nan("jv(yv):", ierr_to_sferr(nz, ierr), cy_y); - cy_j = detail::rotate_jy(cy_j, cy_y, v); - } - } - return cy_j; -} - -inline std::complex cyl_bessel_j(float v, std::complex x) { - return static_cast>(cyl_bessel_j(static_cast(v), static_cast>(x))); -} - -template -T cyl_bessel_j(T v, T x) { - if (v != static_cast(v) && x < 0) { - set_error("jv", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - std::complex res = cyl_bessel_j(v, std::complex(x)); - if (std::real(res) != std::real(res)) { - /* AMOS returned NaN, possibly due to overflow */ - return cephes::jv(v, x); - } - - return std::real(res); -} - -inline std::complex cyl_bessel_y(double v, std::complex z) { - int n = 1; - int kode = 1; - int nz, ierr; - int sign = 1; - std::complex cy_y, cy_j; - - cy_j.real(NAN); - cy_j.imag(NAN); - cy_y.real(NAN); - cy_y.imag(NAN); - - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy_y; - } - if (v < 0) { - v = -v; - sign = -1; - } - - if (z.real() == 0 && z.imag() == 0) { - /* overflow */ - cy_y.real(-INFINITY); - cy_y.imag(0); - set_error("yv", SF_ERROR_OVERFLOW, NULL); - } else { - nz = amos::besy(z, v, kode, n, &cy_y, &ierr); - set_error_and_nan("yv:", ierr_to_sferr(nz, ierr), cy_y); - if (ierr == 2) { - if (z.real() >= 0 && z.imag() == 0) { - /* overflow */ - cy_y.real(-INFINITY); - cy_y.imag(0); - } - } - } - - if (sign == -1) { - if (!detail::reflect_jy(&cy_y, v)) { - nz = amos::besj(z, v, kode, n, &cy_j, &ierr); - // F_FUNC(zbesj,ZBESJ)(CADDR(z), &v, &kode, &n, CADDR(cy_j), &nz, &ierr); - set_error_and_nan("yv(jv):", ierr_to_sferr(nz, ierr), cy_j); - cy_y = detail::rotate_jy(cy_y, cy_j, -v); - } - } - return cy_y; -} - -inline std::complex cyl_bessel_y(float v, std::complex x) { - return static_cast>(cyl_bessel_y(static_cast(v), static_cast>(x))); -} - -template -T cyl_bessel_y(T v, T x) { - if (x < 0) { - set_error("yv", SF_ERROR_DOMAIN, nullptr); - return std::numeric_limits::quiet_NaN(); - } - - std::complex res = cyl_bessel_y(v, std::complex(x)); - if (std::real(res) != std::real(res)) { - return cephes::yv(v, x); - } - - return std::real(res); -} - -inline double cyl_bessel_i(double v, double x) { return cephes::iv(v, x); } - -inline float cyl_bessel_i(float v, float x) { return cyl_bessel_i(static_cast(v), static_cast(x)); } - -inline double cyl_bessel_i0(double x) { return cephes::i0(x); } - -inline float cyl_bessel_i0(float x) { return cyl_bessel_i0(static_cast(x)); } - -inline double cyl_bessel_i0e(double x) { return cephes::i0e(x); } - -inline float cyl_bessel_i0e(float x) { return cyl_bessel_i0e(static_cast(x)); } - -inline double cyl_bessel_i1(double x) { return cephes::i1(x); } - -inline float cyl_bessel_i1(float x) { return cyl_bessel_i1(static_cast(x)); } - -inline double cyl_bessel_i1e(double x) { return cephes::i1e(x); } - -inline float cyl_bessel_i1e(float x) { return cyl_bessel_i1e(static_cast(x)); } - -inline std::complex cyl_bessel_i(double v, std::complex z) { - int n = 1; - int kode = 1; - int sign = 1; - int nz, ierr; - std::complex cy, cy_k; - - cy.real(NAN); - cy.imag(NAN); - cy_k.real(NAN); - cy_k.imag(NAN); - - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy; - } - if (v < 0) { - v = -v; - sign = -1; - } - nz = amos::besi(z, v, kode, n, &cy, &ierr); - set_error_and_nan("iv:", ierr_to_sferr(nz, ierr), cy); - if (ierr == 2) { - /* overflow */ - if (z.imag() == 0 && (z.real() >= 0 || v == floor(v))) { - if (z.real() < 0 && v / 2 != floor(v / 2)) - cy.real(-INFINITY); - else - cy.real(INFINITY); - cy.imag(0); - } else { - cy = cyl_bessel_ie(v * sign, z); - cy.real(cy.real() * INFINITY); - cy.imag(cy.imag() * INFINITY); - } - } - - if (sign == -1) { - if (!detail::reflect_i(&cy, v)) { - nz = amos::besk(z, v, kode, n, &cy_k, &ierr); - set_error_and_nan("iv(kv):", ierr_to_sferr(nz, ierr), cy_k); - cy = detail::rotate_i(cy, cy_k, v); - } - } - - return cy; -} - -inline std::complex cyl_bessel_i(float v, std::complex x) { - return static_cast>(cyl_bessel_i(static_cast(v), static_cast>(x))); -} - -inline std::complex cyl_bessel_k(double v, std::complex z) { - std::complex cy(NAN, NAN); - if (std::isnan(v) || std::isnan(std::real(z)) || isnan(std::imag(z))) { - return cy; - } - - if (v < 0) { - /* K_v == K_{-v} even for non-integer v */ - v = -v; - } - - int n = 1; - int kode = 1; - int ierr; - int nz = amos::besk(z, v, kode, n, &cy, &ierr); - set_error_and_nan("kv:", ierr_to_sferr(nz, ierr), cy); - if (ierr == 2) { - if (std::real(z) >= 0 && std::imag(z) == 0) { - /* overflow */ - cy = INFINITY; - } - } - - return cy; -} - -inline std::complex cyl_bessel_k(float v, std::complex x) { - return static_cast>(cyl_bessel_k(static_cast(v), static_cast>(x))); -} - -template -T cyl_bessel_k(T v, T z) { - if (z < 0) { - return std::numeric_limits::quiet_NaN(); - } - - if (z == 0) { - return std::numeric_limits::infinity(); - } - - if (z > 710 * (1 + std::abs(v))) { - /* Underflow. See uniform expansion https://dlmf.nist.gov/10.41 - * This condition is not a strict bound (it can underflow earlier), - * rather, we are here working around a restriction in AMOS. - */ - return 0; - } - - return std::real(cyl_bessel_k(v, std::complex(z))); -} - -inline double cyl_bessel_k0(double x) { return cephes::k0(x); } - -inline float cyl_bessel_k0(float x) { return cyl_bessel_k0(static_cast(x)); } - -inline double cyl_bessel_k0e(double x) { return cephes::k0e(x); } - -inline float cyl_bessel_k0e(float x) { return cyl_bessel_k0e(static_cast(x)); } - -inline double cyl_bessel_k1(double x) { return cephes::k1(x); } - -inline float cyl_bessel_k1(float x) { return cyl_bessel_k1(static_cast(x)); } - -inline double cyl_bessel_k1e(double x) { return cephes::k1e(x); } - -inline float cyl_bessel_k1e(float x) { return cyl_bessel_k1e(static_cast(x)); } - -inline std::complex cyl_hankel_1(double v, std::complex z) { - int n = 1; - int kode = 1; - int m = 1; - int nz, ierr; - int sign = 1; - std::complex cy; - - cy.real(NAN); - cy.imag(NAN); - - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy; - } - if (v < 0) { - v = -v; - sign = -1; - } - nz = amos::besh(z, v, kode, m, n, &cy, &ierr); - set_error_and_nan("hankel1:", ierr_to_sferr(nz, ierr), cy); - if (sign == -1) { - cy = detail::rotate(cy, v); - } - return cy; -} - -inline std::complex cyl_hankel_1(float v, std::complex z) { - return static_cast>(cyl_hankel_1(static_cast(v), static_cast>(z))); -} - -inline std::complex cyl_hankel_2(double v, std::complex z) { - int n = 1; - int kode = 1; - int m = 2; - int nz, ierr; - int sign = 1; - std::complex cy; - - cy.real(NAN); - cy.imag(NAN); - - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy; - } - if (v == 0 && z == 0.0) { - cy.real(NAN); - cy.imag(INFINITY); - return cy; - } - if (v < 0) { - v = -v; - sign = -1; - } - nz = amos::besh(z, v, kode, m, n, &cy, &ierr); - set_error_and_nan("hankel2:", ierr_to_sferr(nz, ierr), cy); - if (sign == -1) { - cy = detail::rotate(cy, -v); - } - return cy; -} - -inline std::complex cyl_hankel_2(float v, std::complex z) { - return static_cast>(cyl_hankel_2(static_cast(v), static_cast>(z))); -} - -inline double besselpoly(double a, double lambda, double nu) { return cephes::besselpoly(a, lambda, nu); } - -inline float besselpoly(float a, float lambda, float nu) { - return besselpoly(static_cast(a), static_cast(lambda), static_cast(nu)); -} - -} // namespace xsf diff --git a/scipy/special/xsf/beta.h b/scipy/special/xsf/beta.h deleted file mode 100644 index 83aa80694e29..000000000000 --- a/scipy/special/xsf/beta.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "cephes/beta.h" - -namespace xsf { - -XSF_HOST_DEVICE inline double beta(double a, double b) { return cephes::beta(a, b); } - -XSF_HOST_DEVICE inline float beta(float a, float b) { return beta(static_cast(a), static_cast(b)); } - -XSF_HOST_DEVICE inline double betaln(double a, double b) { return cephes::lbeta(a, b); } - -XSF_HOST_DEVICE inline float betaln(float a, float b) { return betaln(static_cast(a), static_cast(b)); } - -} // namespace xsf diff --git a/scipy/special/xsf/binom.h b/scipy/special/xsf/binom.h deleted file mode 100644 index 6a9b9ead9d7d..000000000000 --- a/scipy/special/xsf/binom.h +++ /dev/null @@ -1,89 +0,0 @@ -/* Translated from Cython into C++ by SciPy developers in 2024. - * - * Original authors: Pauli Virtanen, Eric Moore - */ - -// Binomial coefficient - -#pragma once - -#include "config.h" - -#include "cephes/beta.h" -#include "cephes/gamma.h" - -namespace xsf { - -XSF_HOST_DEVICE inline double binom(double n, double k) { - double kx, nx, num, den, dk, sgn; - - if (n < 0) { - nx = std::floor(n); - if (n == nx) { - // Undefined - return std::numeric_limits::quiet_NaN(); - } - } - - kx = std::floor(k); - if (k == kx && (std::abs(n) > 1E-8 || n == 0)) { - /* Integer case: use multiplication formula for less rounding - * error for cases where the result is an integer. - * - * This cannot be used for small nonzero n due to loss of - * precision. */ - nx = std::floor(n); - if (nx == n && kx > nx / 2 && nx > 0) { - // Reduce kx by symmetry - kx = nx - kx; - } - - if (kx >= 0 && kx < 20) { - num = 1.0; - den = 1.0; - for (int i = 1; i < 1 + static_cast(kx); i++) { - num *= i + n - kx; - den *= i; - if (std::abs(num) > 1E50) { - num /= den; - den = 1.0; - } - } - return num / den; - } - } - - // general case - if (n >= 1E10 * k and k > 0) { - // avoid under/overflows intermediate results - return std::exp(-cephes::lbeta(1 + n - k, 1 + k) - std::log(n + 1)); - } - if (k > 1E8 * std::abs(n)) { - // avoid loss of precision - num = cephes::Gamma(1 + n) / std::abs(k) + cephes::Gamma(1 + n) * n / (2 * k * k); // + ... - num /= M_PI * std::pow(std::abs(k), n); - if (k > 0) { - kx = std::floor(k); - if (static_cast(kx) == kx) { - dk = k - kx; - sgn = (static_cast(kx) % 2 == 0) ? 1 : -1; - } else { - dk = k; - sgn = 1; - } - return num * std::sin((dk - n) * M_PI) * sgn; - } - kx = std::floor(k); - if (static_cast(kx) == kx) { - return 0; - } - return num * std::sin(k * M_PI); - } - return 1 / (n + 1) / cephes::beta(1 + n - k, 1 + k); -} - -XSF_HOST_DEVICE inline float binom(float n, float k) { - return binom(static_cast(n), static_cast(k)); -} - -} // namespace xsf diff --git a/scipy/special/xsf/cdflib.h b/scipy/special/xsf/cdflib.h deleted file mode 100644 index 1ce5550efb6d..000000000000 --- a/scipy/special/xsf/cdflib.h +++ /dev/null @@ -1,100 +0,0 @@ - -#pragma once - -#include "cephes/igam.h" -#include "config.h" -#include "error.h" -#include "tools.h" - -namespace xsf { - -XSF_HOST_DEVICE inline double gdtrib(double a, double p, double x) { - if (std::isnan(p) || std::isnan(a) || std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - if (!((0 <= p) && (p <= 1))) { - set_error("gdtrib", SF_ERROR_DOMAIN, "Input parameter p is out of range"); - return std::numeric_limits::quiet_NaN(); - } - if (!(a > 0) || std::isinf(a)) { - set_error("gdtrib", SF_ERROR_DOMAIN, "Input parameter a is out of range"); - return std::numeric_limits::quiet_NaN(); - } - if (!(x >= 0) || std::isinf(x)) { - set_error("gdtrib", SF_ERROR_DOMAIN, "Input parameter x is out of range"); - return std::numeric_limits::quiet_NaN(); - } - if (x == 0.0) { - if (p == 0.0) { - set_error("gdtrib", SF_ERROR_DOMAIN, "Indeterminate result for (x, p) == (0, 0)."); - return std::numeric_limits::quiet_NaN(); - } - /* gdtrib(a, p, x) tends to 0 as x -> 0 when p > 0 */ - return 0.0; - } - if (p == 0.0) { - /* gdtrib(a, p, x) tends to infinity as p -> 0 from the right when x > 0. */ - set_error("gdtrib", SF_ERROR_SINGULAR, NULL); - return std::numeric_limits::infinity(); - } - if (p == 1.0) { - /* gdtrib(a, p, x) tends to 0 as p -> 1.0 from the left when x > 0. */ - return 0.0; - } - double q = 1.0 - p; - auto func = [a, p, q, x](double b) { - if (p <= q) { - return cephes::igam(b, a * x) - p; - } - return q - cephes::igamc(b, a * x); - }; - double lower_bound = std::numeric_limits::min(); - double upper_bound = std::numeric_limits::max(); - /* To explain the magic constants used below: - * 1.0 is the initial guess for the root. -0.875 is the initial step size - * for the leading bracket endpoint if the bracket search will proceed to the - * left, likewise 7.0 is the initial step size when the bracket search will - * proceed to the right. 0.125 is the scale factor for a left moving bracket - * search and 8.0 the scale factor for a right moving bracket search. These - * constants are chosen so that: - * - * 1. The scale factor and bracket endpoints remain powers of 2, allowing for - * exact arithmetic, preventing roundoff error from causing numerical catastrophe - * which could lead to unexpected results. - * 2. The bracket sizes remain constant in a relative sense. Each candidate bracket - * will contain roughly the same number of floating point values. This means that - * the number of necessary function evaluations in the worst case scenario for - * Chandrupatla's algorithm will remain constant. - * - * false specifies that the function is not decreasing. 342 is equal to - * max(ceil(log_8(DBL_MAX)), ceil(log_(1/8)(DBL_MIN))). An upper bound for the - * number of iterations needed in this bracket search to check all normalized - * floating point values. - */ - auto [xl, xr, f_xl, f_xr, bracket_status] = detail::bracket_root_for_cdf_inversion( - func, 1.0, lower_bound, upper_bound, -0.875, 7.0, 0.125, 8, false, 342 - ); - if (bracket_status == 1) { - set_error("gdtrib", SF_ERROR_UNDERFLOW, NULL); - return 0.0; - } - if (bracket_status == 2) { - set_error("gdtrib", SF_ERROR_OVERFLOW, NULL); - return std::numeric_limits::infinity(); - } - if (bracket_status >= 3) { - set_error("gdtrib", SF_ERROR_OTHER, "Computational Error"); - return std::numeric_limits::quiet_NaN(); - } - auto [result, root_status] = detail::find_root_chandrupatla( - func, xl, xr, f_xl, f_xr, std::numeric_limits::epsilon(), 1e-100, 100 - ); - if (root_status) { - /* The root finding return should only fail if there's a bug in our code. */ - set_error("gdtrib", SF_ERROR_OTHER, "Computational Error, (%.17g, %.17g, %.17g)", a, p, x); - return std::numeric_limits::quiet_NaN(); - } - return result; -} - -} // namespace xsf diff --git a/scipy/special/xsf/cephes/airy.h b/scipy/special/xsf/cephes/airy.h deleted file mode 100644 index 8db31fa9b383..000000000000 --- a/scipy/special/xsf/cephes/airy.h +++ /dev/null @@ -1,307 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* airy.c - * - * Airy function - * - * - * - * SYNOPSIS: - * - * double x, ai, aip, bi, bip; - * int airy(); - * - * airy( x, _&ai, _&aip, _&bi, _&bip ); - * - * - * - * DESCRIPTION: - * - * Solution of the differential equation - * - * y"(x) = xy. - * - * The function returns the two independent solutions Ai, Bi - * and their first derivatives Ai'(x), Bi'(x). - * - * Evaluation is by power series summation for small x, - * by rational minimax approximations for large x. - * - * - * - * ACCURACY: - * Error criterion is absolute when function <= 1, relative - * when function > 1, except * denotes relative error criterion. - * For large negative x, the absolute error increases as x^1.5. - * For large positive x, the relative error increases as x^1.5. - * - * Arithmetic domain function # trials peak rms - * IEEE -10, 0 Ai 10000 1.6e-15 2.7e-16 - * IEEE 0, 10 Ai 10000 2.3e-14* 1.8e-15* - * IEEE -10, 0 Ai' 10000 4.6e-15 7.6e-16 - * IEEE 0, 10 Ai' 10000 1.8e-14* 1.5e-15* - * IEEE -10, 10 Bi 30000 4.2e-15 5.3e-16 - * IEEE -10, 10 Bi' 30000 4.9e-15 7.3e-16 - * - */ -/* airy.c */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double airy_c1 = 0.35502805388781723926; - constexpr double airy_c2 = 0.258819403792806798405; - constexpr double MAXAIRY = 103.892; - - constexpr double airy_AN[8] = { - 3.46538101525629032477E-1, 1.20075952739645805542E1, 7.62796053615234516538E1, 1.68089224934630576269E2, - 1.59756391350164413639E2, 7.05360906840444183113E1, 1.40264691163389668864E1, 9.99999999999999995305E-1, - }; - - constexpr double airy_AD[8] = { - 5.67594532638770212846E-1, 1.47562562584847203173E1, 8.45138970141474626562E1, 1.77318088145400459522E2, - 1.64234692871529701831E2, 7.14778400825575695274E1, 1.40959135607834029598E1, 1.00000000000000000470E0, - }; - - constexpr double airy_APN[8] = { - 6.13759184814035759225E-1, 1.47454670787755323881E1, 8.20584123476060982430E1, 1.71184781360976385540E2, - 1.59317847137141783523E2, 6.99778599330103016170E1, 1.39470856980481566958E1, 1.00000000000000000550E0, - }; - - constexpr double airy_APD[8] = { - 3.34203677749736953049E-1, 1.11810297306158156705E1, 7.11727352147859965283E1, 1.58778084372838313640E2, - 1.53206427475809220834E2, 6.86752304592780337944E1, 1.38498634758259442477E1, 9.99999999999999994502E-1, - }; - - constexpr double airy_BN16[5] = { - -2.53240795869364152689E-1, 5.75285167332467384228E-1, -3.29907036873225371650E-1, - 6.44404068948199951727E-2, -3.82519546641336734394E-3, - }; - - constexpr double airy_BD16[5] = { - /* 1.00000000000000000000E0, */ - -7.15685095054035237902E0, 1.06039580715664694291E1, -5.23246636471251500874E0, - 9.57395864378383833152E-1, -5.50828147163549611107E-2, - }; - - constexpr double airy_BPPN[5] = { - 4.65461162774651610328E-1, -1.08992173800493920734E0, 6.38800117371827987759E-1, - -1.26844349553102907034E-1, 7.62487844342109852105E-3, - }; - - constexpr double airy_BPPD[5] = { - /* 1.00000000000000000000E0, */ - -8.70622787633159124240E0, 1.38993162704553213172E1, -7.14116144616431159572E0, - 1.34008595960680518666E0, -7.84273211323341930448E-2, - }; - - constexpr double airy_AFN[9] = { - -1.31696323418331795333E-1, -6.26456544431912369773E-1, -6.93158036036933542233E-1, - -2.79779981545119124951E-1, -4.91900132609500318020E-2, -4.06265923594885404393E-3, - -1.59276496239262096340E-4, -2.77649108155232920844E-6, -1.67787698489114633780E-8, - }; - - constexpr double airy_AFD[9] = { - /* 1.00000000000000000000E0, */ - 1.33560420706553243746E1, 3.26825032795224613948E1, 2.67367040941499554804E1, - 9.18707402907259625840E0, 1.47529146771666414581E0, 1.15687173795188044134E-1, - 4.40291641615211203805E-3, 7.54720348287414296618E-5, 4.51850092970580378464E-7, - }; - - constexpr double airy_AGN[11] = { - 1.97339932091685679179E-2, 3.91103029615688277255E-1, 1.06579897599595591108E0, 9.39169229816650230044E-1, - 3.51465656105547619242E-1, 6.33888919628925490927E-2, 5.85804113048388458567E-3, 2.82851600836737019778E-4, - 6.98793669997260967291E-6, 8.11789239554389293311E-8, 3.41551784765923618484E-10, - }; - - constexpr double airy_AGD[10] = { - /* 1.00000000000000000000E0, */ - 9.30892908077441974853E0, 1.98352928718312140417E1, 1.55646628932864612953E1, 5.47686069422975497931E0, - 9.54293611618961883998E-1, 8.64580826352392193095E-2, 4.12656523824222607191E-3, 1.01259085116509135510E-4, - 1.17166733214413521882E-6, 4.91834570062930015649E-9, - }; - - constexpr double airy_APFN[9] = { - 1.85365624022535566142E-1, 8.86712188052584095637E-1, 9.87391981747398547272E-1, - 4.01241082318003734092E-1, 7.10304926289631174579E-2, 5.90618657995661810071E-3, - 2.33051409401776799569E-4, 4.08718778289035454598E-6, 2.48379932900442457853E-8, - }; - - constexpr double airy_APFD[9] = { - /* 1.00000000000000000000E0, */ - 1.47345854687502542552E1, 3.75423933435489594466E1, 3.14657751203046424330E1, - 1.09969125207298778536E1, 1.78885054766999417817E0, 1.41733275753662636873E-1, - 5.44066067017226003627E-3, 9.39421290654511171663E-5, 5.65978713036027009243E-7, - }; - - constexpr double airy_APGN[11] = { - -3.55615429033082288335E-2, -6.37311518129435504426E-1, -1.70856738884312371053E0, - -1.50221872117316635393E0, -5.63606665822102676611E-1, -1.02101031120216891789E-1, - -9.48396695961445269093E-3, -4.60325307486780994357E-4, -1.14300836484517375919E-5, - -1.33415518685547420648E-7, -5.63803833958893494476E-10, - }; - - constexpr double airy_APGD[11] = { - /* 1.00000000000000000000E0, */ - 9.85865801696130355144E0, 2.16401867356585941885E1, 1.73130776389749389525E1, 6.17872175280828766327E0, - 1.08848694396321495475E0, 9.95005543440888479402E-2, 4.78468199683886610842E-3, 1.18159633322838625562E-4, - 1.37480673554219441465E-6, 5.79912514929147598821E-9, - }; - - } // namespace detail - - XSF_HOST_DEVICE inline int airy(double x, double *ai, double *aip, double *bi, double *bip) { - double z, zz, t, f, g, uf, ug, k, zeta, theta; - int domflg; - - domflg = 0; - if (x > detail::MAXAIRY) { - *ai = 0; - *aip = 0; - *bi = std::numeric_limits::infinity(); - *bip = std::numeric_limits::infinity(); - return (-1); - } - - if (x < -2.09) { - domflg = 15; - t = std::sqrt(-x); - zeta = -2.0 * x * t / 3.0; - t = std::sqrt(t); - k = detail::SQRT1OPI / t; - z = 1.0 / zeta; - zz = z * z; - uf = 1.0 + zz * polevl(zz, detail::airy_AFN, 8) / p1evl(zz, detail::airy_AFD, 9); - ug = z * polevl(zz, detail::airy_AGN, 10) / p1evl(zz, detail::airy_AGD, 10); - theta = zeta + 0.25 * M_PI; - f = std::sin(theta); - g = std::cos(theta); - *ai = k * (f * uf - g * ug); - *bi = k * (g * uf + f * ug); - uf = 1.0 + zz * polevl(zz, detail::airy_APFN, 8) / p1evl(zz, detail::airy_APFD, 9); - ug = z * polevl(zz, detail::airy_APGN, 10) / p1evl(zz, detail::airy_APGD, 10); - k = detail::SQRT1OPI * t; - *aip = -k * (g * uf + f * ug); - *bip = k * (f * uf - g * ug); - return (0); - } - - if (x >= 2.09) { /* cbrt(9) */ - domflg = 5; - t = std::sqrt(x); - zeta = 2.0 * x * t / 3.0; - g = std::exp(zeta); - t = std::sqrt(t); - k = 2.0 * t * g; - z = 1.0 / zeta; - f = polevl(z, detail::airy_AN, 7) / polevl(z, detail::airy_AD, 7); - *ai = detail::SQRT1OPI * f / k; - k = -0.5 * detail::SQRT1OPI * t / g; - f = polevl(z, detail::airy_APN, 7) / polevl(z, detail::airy_APD, 7); - *aip = f * k; - - if (x > 8.3203353) { /* zeta > 16 */ - f = z * polevl(z, detail::airy_BN16, 4) / p1evl(z, detail::airy_BD16, 5); - k = detail::SQRT1OPI * g; - *bi = k * (1.0 + f) / t; - f = z * polevl(z, detail::airy_BPPN, 4) / p1evl(z, detail::airy_BPPD, 5); - *bip = k * t * (1.0 + f); - return (0); - } - } - - f = 1.0; - g = x; - t = 1.0; - uf = 1.0; - ug = x; - k = 1.0; - z = x * x * x; - while (t > detail::MACHEP) { - uf *= z; - k += 1.0; - uf /= k; - ug *= z; - k += 1.0; - ug /= k; - uf /= k; - f += uf; - k += 1.0; - ug /= k; - g += ug; - t = std::abs(uf / f); - } - uf = detail::airy_c1 * f; - ug = detail::airy_c2 * g; - if ((domflg & 1) == 0) { - *ai = uf - ug; - } - if ((domflg & 2) == 0) { - *bi = detail::SQRT3 * (uf + ug); - } - - /* the deriviative of ai */ - k = 4.0; - uf = x * x / 2.0; - ug = z / 3.0; - f = uf; - g = 1.0 + ug; - uf /= 3.0; - t = 1.0; - - while (t > detail::MACHEP) { - uf *= z; - ug /= k; - k += 1.0; - ug *= z; - uf /= k; - f += uf; - k += 1.0; - ug /= k; - uf /= k; - g += ug; - k += 1.0; - t = std::abs(ug / g); - } - - uf = detail::airy_c1 * f; - ug = detail::airy_c2 * g; - if ((domflg & 4) == 0) { - *aip = uf - ug; - } - if ((domflg & 8) == 0) { - *bip = detail::SQRT3 * (uf + ug); - }; - return (0); - } - - inline int airy(float xf, float *aif, float *aipf, float *bif, float *bipf) { - double ai; - double aip; - double bi; - double bip; - int res = cephes::airy(xf, &ai, &aip, &bi, &bip); - - *aif = ai; - *aipf = aip; - *bif = bi; - *bipf = bip; - return res; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/bdtr.h b/scipy/special/xsf/cephes/bdtr.h deleted file mode 100644 index 3487c06c20a6..000000000000 --- a/scipy/special/xsf/cephes/bdtr.h +++ /dev/null @@ -1,262 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* bdtr.c - * - * Binomial distribution - * - * - * - * SYNOPSIS: - * - * int k, n; - * double p, y, bdtr(); - * - * y = bdtr( k, n, p ); - * - * DESCRIPTION: - * - * Returns the sum of the terms 0 through k of the Binomial - * probability density: - * - * k - * -- ( n ) j n-j - * > ( ) p (1-p) - * -- ( j ) - * j=0 - * - * The terms are not summed directly; instead the incomplete - * beta integral is employed, according to the formula - * - * y = bdtr( k, n, p ) = incbet( n-k, k+1, 1-p ). - * - * The arguments must be positive, with p ranging from 0 to 1. - * - * ACCURACY: - * - * Tested at random points (a,b,p), with p between 0 and 1. - * - * a,b Relative error: - * arithmetic domain # trials peak rms - * For p between 0.001 and 1: - * IEEE 0,100 100000 4.3e-15 2.6e-16 - * See also incbet.c. - * - * ERROR MESSAGES: - * - * message condition value returned - * bdtr domain k < 0 0.0 - * n < k - * x < 0, x > 1 - */ -/* bdtrc() - * - * Complemented binomial distribution - * - * - * - * SYNOPSIS: - * - * int k, n; - * double p, y, bdtrc(); - * - * y = bdtrc( k, n, p ); - * - * DESCRIPTION: - * - * Returns the sum of the terms k+1 through n of the Binomial - * probability density: - * - * n - * -- ( n ) j n-j - * > ( ) p (1-p) - * -- ( j ) - * j=k+1 - * - * The terms are not summed directly; instead the incomplete - * beta integral is employed, according to the formula - * - * y = bdtrc( k, n, p ) = incbet( k+1, n-k, p ). - * - * The arguments must be positive, with p ranging from 0 to 1. - * - * ACCURACY: - * - * Tested at random points (a,b,p). - * - * a,b Relative error: - * arithmetic domain # trials peak rms - * For p between 0.001 and 1: - * IEEE 0,100 100000 6.7e-15 8.2e-16 - * For p between 0 and .001: - * IEEE 0,100 100000 1.5e-13 2.7e-15 - * - * ERROR MESSAGES: - * - * message condition value returned - * bdtrc domain x<0, x>1, n 1 - */ - -/* bdtr() */ - -/* - * Cephes Math Library Release 2.3: March, 1995 - * Copyright 1984, 1987, 1995 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "incbet.h" -#include "incbi.h" -#include "unity.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE inline double bdtrc(double k, int n, double p) { - double dk, dn; - double fk = std::floor(k); - - if (std::isnan(p) || std::isnan(k)) { - return std::numeric_limits::quiet_NaN(); - } - - if (p < 0.0 || p > 1.0 || n < fk) { - set_error("bdtrc", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - if (fk < 0) { - return 1.0; - } - - if (fk == n) { - return 0.0; - } - - dn = n - fk; - if (k == 0) { - if (p < .01) - dk = -expm1(dn * std::log1p(-p)); - else - dk = 1.0 - std::pow(1.0 - p, dn); - } else { - dk = fk + 1; - dk = incbet(dk, dn, p); - } - return dk; - } - - XSF_HOST_DEVICE inline double bdtr(double k, int n, double p) { - double dk, dn; - double fk = std::floor(k); - - if (std::isnan(p) || std::isnan(k)) { - return std::numeric_limits::quiet_NaN(); - } - - if (p < 0.0 || p > 1.0 || fk < 0 || n < fk) { - set_error("bdtr", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - if (fk == n) { - return 1.0; - } - - dn = n - fk; - if (fk == 0) { - dk = std::pow(1.0 - p, dn); - } else { - dk = fk + 1.; - dk = incbet(dn, dk, 1.0 - p); - } - return dk; - } - - XSF_HOST_DEVICE inline double bdtri(double k, int n, double y) { - double p, dn, dk; - double fk = std::floor(k); - - if (std::isnan(k)) { - return std::numeric_limits::quiet_NaN(); - } - - if (y < 0.0 || y > 1.0 || fk < 0.0 || n <= fk) { - set_error("bdtri", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - dn = n - fk; - - if (fk == n) { - return 1.0; - } - - if (fk == 0) { - if (y > 0.8) { - p = -expm1(std::log1p(y - 1.0) / dn); - } else { - p = 1.0 - std::pow(y, 1.0 / dn); - } - } else { - dk = fk + 1; - p = incbet(dn, dk, 0.5); - if (p > 0.5) { - p = incbi(dk, dn, 1.0 - y); - } else { - p = 1.0 - incbi(dn, dk, y); - } - } - return p; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/besselpoly.h b/scipy/special/xsf/cephes/besselpoly.h deleted file mode 100644 index d113b8b7d0f4..000000000000 --- a/scipy/special/xsf/cephes/besselpoly.h +++ /dev/null @@ -1,51 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * - * This was not part of the original cephes library. - */ -#pragma once - -#include "../config.h" -#include "gamma.h" - -namespace xsf { -namespace cephes { - namespace detail { - - constexpr double besselpoly_EPS = 1.0e-17; - } - - XSF_HOST_DEVICE inline double besselpoly(double a, double lambda, double nu) { - - int m, factor = 0; - double Sm, relerr, Sol; - double sum = 0.0; - - /* Special handling for a = 0.0 */ - if (a == 0.0) { - if (nu == 0.0) { - return 1.0 / (lambda + 1); - } else { - return 0.0; - } - } - /* Special handling for negative and integer nu */ - if ((nu < 0) && (std::floor(nu) == nu)) { - nu = -nu; - factor = static_cast(nu) % 2; - } - Sm = std::exp(nu * std::log(a)) / (Gamma(nu + 1) * (lambda + nu + 1)); - m = 0; - do { - sum += Sm; - Sol = Sm; - Sm *= -a * a * (lambda + nu + 1 + 2 * m) / ((nu + m + 1) * (m + 1) * (lambda + nu + 1 + 2 * m + 2)); - m++; - relerr = std::abs((Sm - Sol) / Sm); - } while (relerr > detail::besselpoly_EPS && m < 1000); - if (!factor) - return sum; - else - return -sum; - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/beta.h b/scipy/special/xsf/cephes/beta.h deleted file mode 100644 index 437262793e8f..000000000000 --- a/scipy/special/xsf/cephes/beta.h +++ /dev/null @@ -1,257 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* beta.c - * - * Beta function - * - * - * - * SYNOPSIS: - * - * double a, b, y, beta(); - * - * y = beta( a, b ); - * - * - * - * DESCRIPTION: - * - * - - - * | (a) | (b) - * beta( a, b ) = -----------. - * - - * | (a+b) - * - * For large arguments the logarithm of the function is - * evaluated using lgam(), then exponentiated. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,30 30000 8.1e-14 1.1e-14 - * - * ERROR MESSAGES: - * - * message condition value returned - * beta overflow log(beta) > MAXLOG 0.0 - * a or b <0 integer 0.0 - * - */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1984, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "const.h" -#include "gamma.h" -#include "rgamma.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE double beta(double, double); - XSF_HOST_DEVICE double lbeta(double, double); - - namespace detail { - constexpr double beta_ASYMP_FACTOR = 1e6; - - /* - * Asymptotic expansion for ln(|B(a, b)|) for a > ASYMP_FACTOR*max(|b|, 1). - */ - XSF_HOST_DEVICE inline double lbeta_asymp(double a, double b, int *sgn) { - double r = lgam_sgn(b, sgn); - r -= b * std::log(a); - - r += b * (1 - b) / (2 * a); - r += b * (1 - b) * (1 - 2 * b) / (12 * a * a); - r += -b * b * (1 - b) * (1 - b) / (12 * a * a * a); - - return r; - } - - /* - * Special case for a negative integer argument - */ - - XSF_HOST_DEVICE inline double beta_negint(int a, double b) { - int sgn; - if (b == static_cast(b) && 1 - a - b > 0) { - sgn = (static_cast(b) % 2 == 0) ? 1 : -1; - return sgn * xsf::cephes::beta(1 - a - b, b); - } else { - set_error("lbeta", SF_ERROR_OVERFLOW, NULL); - return std::numeric_limits::infinity(); - } - } - - XSF_HOST_DEVICE inline double lbeta_negint(int a, double b) { - double r; - if (b == static_cast(b) && 1 - a - b > 0) { - r = xsf::cephes::lbeta(1 - a - b, b); - return r; - } else { - set_error("lbeta", SF_ERROR_OVERFLOW, NULL); - return std::numeric_limits::infinity(); - } - } - } // namespace detail - - XSF_HOST_DEVICE inline double beta(double a, double b) { - double y; - int sign = 1; - - if (a <= 0.0) { - if (a == std::floor(a)) { - if (a == static_cast(a)) { - return detail::beta_negint(static_cast(a), b); - } else { - goto overflow; - } - } - } - - if (b <= 0.0) { - if (b == std::floor(b)) { - if (b == static_cast(b)) { - return detail::beta_negint(static_cast(b), a); - } else { - goto overflow; - } - } - } - - if (std::abs(a) < std::abs(b)) { - y = a; - a = b; - b = y; - } - - if (std::abs(a) > detail::beta_ASYMP_FACTOR * std::abs(b) && a > detail::beta_ASYMP_FACTOR) { - /* Avoid loss of precision in lgam(a + b) - lgam(a) */ - y = detail::lbeta_asymp(a, b, &sign); - return sign * std::exp(y); - } - - y = a + b; - if (std::abs(y) > detail::MAXGAM || std::abs(a) > detail::MAXGAM || std::abs(b) > detail::MAXGAM) { - int sgngam; - y = detail::lgam_sgn(y, &sgngam); - sign *= sgngam; /* keep track of the sign */ - y = detail::lgam_sgn(b, &sgngam) - y; - sign *= sgngam; - y = detail::lgam_sgn(a, &sgngam) + y; - sign *= sgngam; - if (y > detail::MAXLOG) { - goto overflow; - } - return (sign * std::exp(y)); - } - - y = rgamma(y); - a = Gamma(a); - b = Gamma(b); - if (std::isinf(y)) { - goto overflow; - } - - if (std::abs(std::abs(a*y) - 1.0) > std::abs(std::abs(b*y) - 1.0)) { - y = b * y; - y *= a; - } else { - y = a * y; - y *= b; - } - - return (y); - - overflow: - set_error("beta", SF_ERROR_OVERFLOW, NULL); - return (sign * std::numeric_limits::infinity()); - } - - /* Natural log of |beta|. */ - - XSF_HOST_DEVICE inline double lbeta(double a, double b) { - double y; - int sign; - - sign = 1; - - if (a <= 0.0) { - if (a == std::floor(a)) { - if (a == static_cast(a)) { - return detail::lbeta_negint(static_cast(a), b); - } else { - goto over; - } - } - } - - if (b <= 0.0) { - if (b == std::floor(b)) { - if (b == static_cast(b)) { - return detail::lbeta_negint(static_cast(b), a); - } else { - goto over; - } - } - } - - if (std::abs(a) < std::abs(b)) { - y = a; - a = b; - b = y; - } - - if (std::abs(a) > detail::beta_ASYMP_FACTOR * std::abs(b) && a > detail::beta_ASYMP_FACTOR) { - /* Avoid loss of precision in lgam(a + b) - lgam(a) */ - y = detail::lbeta_asymp(a, b, &sign); - return y; - } - - y = a + b; - if (std::abs(y) > detail::MAXGAM || std::abs(a) > detail::MAXGAM || std::abs(b) > detail::MAXGAM) { - int sgngam; - y = detail::lgam_sgn(y, &sgngam); - sign *= sgngam; /* keep track of the sign */ - y = detail::lgam_sgn(b, &sgngam) - y; - sign *= sgngam; - y = detail::lgam_sgn(a, &sgngam) + y; - sign *= sgngam; - return (y); - } - - y = rgamma(y); - a = Gamma(a); - b = Gamma(b); - if (std::isinf(y)) { - over: - set_error("lbeta", SF_ERROR_OVERFLOW, NULL); - return (sign * std::numeric_limits::infinity()); - } - - if (std::abs(std::abs(a*y) - 1.0) > std::abs(std::abs(b*y) - 1.0)) { - y = b * y; - y *= a; - } else { - y = a * y; - y *= b; - } - - if (y < 0) { - y = -y; - } - - return (std::log(y)); - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/cbrt.h b/scipy/special/xsf/cephes/cbrt.h deleted file mode 100644 index 3e9fbd4eab45..000000000000 --- a/scipy/special/xsf/cephes/cbrt.h +++ /dev/null @@ -1,131 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* cbrt.c - * - * Cube root - * - * - * - * SYNOPSIS: - * - * double x, y, cbrt(); - * - * y = cbrt( x ); - * - * - * - * DESCRIPTION: - * - * Returns the cube root of the argument, which may be negative. - * - * Range reduction involves determining the power of 2 of - * the argument. A polynomial of degree 2 applied to the - * mantissa, and multiplication by the cube root of 1, 2, or 4 - * approximates the root to within about 0.1%. Then Newton's - * iteration is used three times to converge to an accurate - * result. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,1e308 30000 1.5e-16 5.0e-17 - * - */ -/* cbrt.c */ - -/* - * Cephes Math Library Release 2.2: January, 1991 - * Copyright 1984, 1991 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double CBRT2 = 1.2599210498948731647672; - constexpr double CBRT4 = 1.5874010519681994747517; - constexpr double CBRT2I = 0.79370052598409973737585; - constexpr double CBRT4I = 0.62996052494743658238361; - - XSF_HOST_DEVICE inline double cbrt(double x) { - int e, rem, sign; - double z; - - if (!std::isfinite(x)) { - return x; - } - if (x == 0) { - return (x); - } - if (x > 0) { - sign = 1; - } else { - sign = -1; - x = -x; - } - - z = x; - /* extract power of 2, leaving - * mantissa between 0.5 and 1 - */ - x = std::frexp(x, &e); - - /* Approximate cube root of number between .5 and 1, - * peak relative error = 9.2e-6 - */ - x = (((-1.3466110473359520655053e-1 * x + 5.4664601366395524503440e-1) * x - 9.5438224771509446525043e-1) * - x + - 1.1399983354717293273738e0) * - x + - 4.0238979564544752126924e-1; - - /* exponent divided by 3 */ - if (e >= 0) { - rem = e; - e /= 3; - rem -= 3 * e; - if (rem == 1) { - x *= CBRT2; - } else if (rem == 2) { - x *= CBRT4; - } - } - /* argument less than 1 */ - else { - e = -e; - rem = e; - e /= 3; - rem -= 3 * e; - if (rem == 1) { - x *= CBRT2I; - } else if (rem == 2) { - x *= CBRT4I; - } - e = -e; - } - - /* multiply by power of 2 */ - x = std::ldexp(x, e); - - /* Newton iteration */ - x -= (x - (z / (x * x))) * 0.33333333333333333333; - x -= (x - (z / (x * x))) * 0.33333333333333333333; - - if (sign < 0) - x = -x; - return (x); - } - } // namespace detail - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/chbevl.h b/scipy/special/xsf/cephes/chbevl.h deleted file mode 100644 index caaa74fc7b81..000000000000 --- a/scipy/special/xsf/cephes/chbevl.h +++ /dev/null @@ -1,85 +0,0 @@ -/* chbevl.c - * - * Evaluate Chebyshev series - * - * - * - * SYNOPSIS: - * - * int N; - * double x, y, coef[N], chebevl(); - * - * y = chbevl( x, coef, N ); - * - * - * - * DESCRIPTION: - * - * Evaluates the series - * - * N-1 - * - ' - * y = > coef[i] T (x/2) - * - i - * i=0 - * - * of Chebyshev polynomials Ti at argument x/2. - * - * Coefficients are stored in reverse order, i.e. the zero - * order term is last in the array. Note N is the number of - * coefficients, not the order. - * - * If coefficients are for the interval a to b, x must - * have been transformed to x -> 2(2x - b - a)/(b-a) before - * entering the routine. This maps x from (a, b) to (-1, 1), - * over which the Chebyshev polynomials are defined. - * - * If the coefficients are for the inverted interval, in - * which (a, b) is mapped to (1/b, 1/a), the transformation - * required is x -> 2(2ab/x - b - a)/(b-a). If b is infinity, - * this becomes x -> 4a/x - 1. - * - * - * - * SPEED: - * - * Taking advantage of the recurrence properties of the - * Chebyshev polynomials, the routine requires one more - * addition per loop than evaluating a nested polynomial of - * the same degree. - * - */ -/* chbevl.c */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1985, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE double chbevl(double x, const double array[], int n) { - double b0, b1, b2; - const double *p; - int i; - - p = array; - b0 = *p++; - b1 = 0.0; - i = n - 1; - - do { - b2 = b1; - b1 = b0; - b0 = x * b1 - b2 + *p++; - } while (--i); - - return (0.5 * (b0 - b2)); - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/chdtr.h b/scipy/special/xsf/cephes/chdtr.h deleted file mode 100644 index 68f2fbe52b12..000000000000 --- a/scipy/special/xsf/cephes/chdtr.h +++ /dev/null @@ -1,194 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* chdtr.c - * - * Chi-square distribution - * - * - * - * SYNOPSIS: - * - * double df, x, y, chdtr(); - * - * y = chdtr( df, x ); - * - * - * - * DESCRIPTION: - * - * Returns the area under the left hand tail (from 0 to x) - * of the Chi square probability density function with - * v degrees of freedom. - * - * - * inf. - * - - * 1 | | v/2-1 -t/2 - * P( x | v ) = ----------- | t e dt - * v/2 - | | - * 2 | (v/2) - - * x - * - * where x is the Chi-square variable. - * - * The incomplete Gamma integral is used, according to the - * formula - * - * y = chdtr( v, x ) = igam( v/2.0, x/2.0 ). - * - * - * The arguments must both be positive. - * - * - * - * ACCURACY: - * - * See igam(). - * - * ERROR MESSAGES: - * - * message condition value returned - * chdtr domain x < 0 or v < 1 0.0 - */ -/* chdtrc() - * - * Complemented Chi-square distribution - * - * - * - * SYNOPSIS: - * - * double v, x, y, chdtrc(); - * - * y = chdtrc( v, x ); - * - * - * - * DESCRIPTION: - * - * Returns the area under the right hand tail (from x to - * infinity) of the Chi square probability density function - * with v degrees of freedom: - * - * - * inf. - * - - * 1 | | v/2-1 -t/2 - * P( x | v ) = ----------- | t e dt - * v/2 - | | - * 2 | (v/2) - - * x - * - * where x is the Chi-square variable. - * - * The incomplete Gamma integral is used, according to the - * formula - * - * y = chdtr( v, x ) = igamc( v/2.0, x/2.0 ). - * - * - * The arguments must both be positive. - * - * - * - * ACCURACY: - * - * See igamc(). - * - * ERROR MESSAGES: - * - * message condition value returned - * chdtrc domain x < 0 or v < 1 0.0 - */ -/* chdtri() - * - * Inverse of complemented Chi-square distribution - * - * - * - * SYNOPSIS: - * - * double df, x, y, chdtri(); - * - * x = chdtri( df, y ); - * - * - * - * - * DESCRIPTION: - * - * Finds the Chi-square argument x such that the integral - * from x to infinity of the Chi-square density is equal - * to the given cumulative probability y. - * - * This is accomplished using the inverse Gamma integral - * function and the relation - * - * x/2 = igamci( df/2, y ); - * - * - * - * - * ACCURACY: - * - * See igami.c. - * - * ERROR MESSAGES: - * - * message condition value returned - * chdtri domain y < 0 or y > 1 0.0 - * v < 1 - * - */ - -/* chdtr() */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1984, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "igam.h" -#include "igami.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE inline double chdtrc(double df, double x) { - if (x < 0.0) { - set_error("chdtr", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - return (igamc(df / 2.0, x / 2.0)); - } - - XSF_HOST_DEVICE inline double chdtr(double df, double x) { - - if ((x < 0.0)) { /* || (df < 1.0) ) */ - set_error("chdtr", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - return (igam(df / 2.0, x / 2.0)); - } - - XSF_HOST_DEVICE double chdtri(double df, double y) { - double x; - - if ((y < 0.0) || (y > 1.0)) { /* || (df < 1.0) ) */ - set_error("chdtri", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - - x = igamci(0.5 * df, y); - return (2.0 * x); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/const.h b/scipy/special/xsf/cephes/const.h deleted file mode 100644 index d7b162c5efc8..000000000000 --- a/scipy/special/xsf/cephes/const.h +++ /dev/null @@ -1,87 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - * - * Since we support only IEEE-754 floating point numbers, conditional logic - * supporting other arithmetic types has been removed. - */ - -/* - * - * - * const.c - * - * Globally declared constants - * - * - * - * SYNOPSIS: - * - * extern double nameofconstant; - * - * - * - * - * DESCRIPTION: - * - * This file contains a number of mathematical constants and - * also some needed size parameters of the computer arithmetic. - * The values are supplied as arrays of hexadecimal integers - * for IEEE arithmetic, and in a normal decimal scientific notation for - * other machines. The particular notation used is determined - * by a symbol (IBMPC, or UNK) defined in the include file - * mconf.h. - * - * The default size parameters are as follows. - * - * For UNK mode: - * MACHEP = 1.38777878078144567553E-17 2**-56 - * MAXLOG = 8.8029691931113054295988E1 log(2**127) - * MINLOG = -8.872283911167299960540E1 log(2**-128) - * - * For IEEE arithmetic (IBMPC): - * MACHEP = 1.11022302462515654042E-16 2**-53 - * MAXLOG = 7.09782712893383996843E2 log(2**1024) - * MINLOG = -7.08396418532264106224E2 log(2**-1022) - * - * The global symbols for mathematical constants are - * SQ2OPI = 7.9788456080286535587989E-1 sqrt( 2/pi ) - * LOGSQ2 = 3.46573590279972654709E-1 log(2)/2 - * THPIO4 = 2.35619449019234492885 3*pi/4 - * - * These lists are subject to change. - */ -/* const.c */ - -/* - * Cephes Math Library Release 2.3: March, 1995 - * Copyright 1984, 1995 by Stephen L. Moshier - */ -#pragma once - -namespace xsf { -namespace cephes { - namespace detail { - constexpr std::uint64_t MAXITER = 500; - constexpr double MACHEP = 1.11022302462515654042E-16; // 2**-53 - constexpr double MAXLOG = 7.09782712893383996732E2; // log(DBL_MAX) - constexpr double MINLOG = -7.451332191019412076235E2; // log 2**-1022 - constexpr double SQRT1OPI = 5.64189583547756286948E-1; // sqrt( 1/pi) - constexpr double SQRT2OPI = 7.9788456080286535587989E-1; // sqrt( 2/pi ) - constexpr double SQRT2PI = 0.79788456080286535587989; // sqrt(2pi) - constexpr double LOGSQ2 = 3.46573590279972654709E-1; // log(2)/2 - constexpr double THPIO4 = 2.35619449019234492885; // 3*pi/4 - constexpr double SQRT3 = 1.732050807568877293527; // sqrt(3) - constexpr double PI180 = 1.74532925199432957692E-2; // pi/180 - constexpr double SQRTPI = 2.50662827463100050242E0; // sqrt(pi) - constexpr double LOGPI = 1.14472988584940017414; // log(pi) - constexpr double MAXGAM = 171.624376956302725; - constexpr double LOGSQRT2PI = 0.9189385332046727; // log(sqrt(pi)) - - // Following two added by SciPy developers. - // Euler's constant - constexpr double SCIPY_EULER = 0.577215664901532860606512090082402431; - // e as long double - constexpr long double SCIPY_El = 2.718281828459045235360287471352662498L; - } // namespace detail -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/dd_real.h b/scipy/special/xsf/cephes/dd_real.h deleted file mode 100644 index 5217a3460119..000000000000 --- a/scipy/special/xsf/cephes/dd_real.h +++ /dev/null @@ -1,576 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - * - * The parts of the qd double-double floating point package used in SciPy - * have been reworked in a more modern C++ style using operator overloading. - */ - -/* - * include/double2.h - * - * This work was supported by the Director, Office of Science, Division - * of Mathematical, Information, and Computational Sciences of the - * U.S. Department of Energy under contract numbers DE-AC03-76SF00098 and - * DE-AC02-05CH11231. - * - * Copyright (c) 2003-2009, The Regents of the University of California, - * through Lawrence Berkeley National Laboratory (subject to receipt of - * any required approvals from U.S. Dept. of Energy) All rights reserved. - * - * By downloading or using this software you are agreeing to the modified - * BSD license "BSD-LBNL-License.doc" (see LICENSE.txt). - */ -/* - * Double-double precision (>= 106-bit significand) floating point - * arithmetic package based on David Bailey's Fortran-90 double-double - * package, with some changes. See - * - * http://www.nersc.gov/~dhbailey/mpdist/mpdist.html - * - * for the original Fortran-90 version. - * - * Overall structure is similar to that of Keith Brigg's C++ double-double - * package. See - * - * http://www-epidem.plansci.cam.ac.uk/~kbriggs/doubledouble.html - * - * for more details. In particular, the fix for x86 computers is borrowed - * from his code. - * - * Yozo Hida - */ - -/* - * This code taken from v2.3.18 of the qd package. - */ - -#pragma once - -#include "../config.h" - -#include "unity.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double __DD_SPLITTER = 134217729.0; // = 2^27 + 1 - constexpr double __DD_SPLIT_THRESH = 6.69692879491417e+299; // = 2^996 - - /************************************************************************* - * The basic routines taking double arguments, returning 1 (or 2) doubles - *************************************************************************/ - - /* volatile is used below to prevent aggressive optimizations which may change - * the result of the error calculations. These volatiles wer e included in the - * original C code and may perhaps still be useful, e.g. if someone compiles with - * --ffastmath. - */ - - /* Computes fl(a+b) and err(a+b). Assumes |a| >= |b|. */ - XSF_HOST_DEVICE inline double quick_two_sum(double a, double b, double *err) { - volatile double s = a + b; - volatile double c = s - a; - *err = b - c; - return s; - } - - /* Computes fl(a+b) and err(a+b). */ - XSF_HOST_DEVICE inline double two_sum(double a, double b, double *err) { - volatile double s = a + b; - volatile double c = s - a; - volatile double d = b - c; - volatile double e = s - c; - *err = (a - e) + d; - return s; - } - - /* Computes fl(a*b) and err(a*b). */ - XSF_HOST_DEVICE inline double two_prod(double a, double b, double *err) { - volatile double p = a * b; - *err = std::fma(a, b, -p); - return p; - } - - /* Computes fl(a*a) and err(a*a). Faster than the above method. */ - XSF_HOST_DEVICE inline double two_sqr(double a, double *err) { - volatile double p = a * a; - *err = std::fma(a, a, -p); - return p; - } - - /* Computes the nearest integer to d. */ - XSF_HOST_DEVICE inline double two_nint(double d) { - if (d == std::floor(d)) { - return d; - } - return std::floor(d + 0.5); - } - - struct double_double { - double hi, lo; - - double_double() = default; - double_double(double high, double low) : hi(high), lo(low) {} - explicit double_double(double high) : hi(high), lo(0.0) {} - - XSF_HOST_DEVICE explicit operator double() const { return hi; } - XSF_HOST_DEVICE explicit operator int() const { return static_cast(hi); } - }; - - // Arithmetic operations - - XSF_HOST_DEVICE inline double_double operator-(const double_double &x) { - return double_double(-x.hi, -x.lo); - } - - XSF_HOST_DEVICE inline double_double operator+(const double_double &lhs, const double_double &rhs) { - /* This one satisfies IEEE style error bound, - due to K. Briggs and W. Kahan. */ - double s1, s2, t1, t2; - - s1 = two_sum(lhs.hi, rhs.hi, &s2); - t1 = two_sum(lhs.lo, rhs.lo, &t2); - s2 += t1; - s1 = quick_two_sum(s1, s2, &s2); - s2 += t2; - s1 = quick_two_sum(s1, s2, &s2); - return double_double(s1, s2); - } - - XSF_HOST_DEVICE inline double_double operator+(const double_double &lhs, const double rhs) { - double s1, s2; - s1 = two_sum(lhs.hi, rhs, &s2); - s2 += lhs.lo; - s1 = quick_two_sum(s1, s2, &s2); - return double_double(s1, s2); - } - - XSF_HOST_DEVICE inline double_double operator+(const double lhs, const double_double &rhs) { - double s1, s2; - s1 = two_sum(lhs, rhs.hi, &s2); - s2 += rhs.lo; - s1 = quick_two_sum(s1, s2, &s2); - return double_double(s1, s2); - } - - XSF_HOST_DEVICE inline double_double operator-(const double_double &lhs, const double_double &rhs) { - return lhs + (-rhs); - } - - XSF_HOST_DEVICE inline double_double operator-(const double_double &lhs, const double rhs) { - double s1, s2; - s1 = two_sum(lhs.hi, -rhs, &s2); - s2 += lhs.lo; - s1 = quick_two_sum(s1, s2, &s2); - return double_double(s1, s2); - } - - XSF_HOST_DEVICE inline double_double operator-(const double lhs, const double_double &rhs) { - double s1, s2; - s1 = two_sum(lhs, -rhs.hi, &s2); - s2 -= rhs.lo; - s1 = quick_two_sum(s1, s2, &s2); - return double_double(s1, s2); - } - - XSF_HOST_DEVICE inline double_double operator*(const double_double &lhs, const double_double &rhs) { - double p1, p2; - p1 = two_prod(lhs.hi, rhs.hi, &p2); - p2 += (lhs.hi * rhs.lo + lhs.lo * rhs.hi); - p1 = quick_two_sum(p1, p2, &p2); - return double_double(p1, p2); - } - - XSF_HOST_DEVICE inline double_double operator*(const double_double &lhs, const double rhs) { - double p1, p2, e1, e2; - p1 = two_prod(lhs.hi, rhs, &e1); - p2 = two_prod(lhs.lo, rhs, &e2); - p1 = quick_two_sum(p1, e2 + p2 + e1, &e1); - return double_double(p1, e1); - } - - XSF_HOST_DEVICE inline double_double operator*(const double lhs, const double_double &rhs) { - double p1, p2, e1, e2; - p1 = two_prod(lhs, rhs.hi, &e1); - p2 = two_prod(lhs, rhs.lo, &e2); - p1 = quick_two_sum(p1, e2 + p2 + e1, &e1); - return double_double(p1, e1); - } - - XSF_HOST_DEVICE inline double_double operator/(const double_double &lhs, const double_double &rhs) { - double q1, q2, q3; - double_double r; - - q1 = lhs.hi / rhs.hi; /* approximate quotient */ - - r = lhs - rhs * q1; - - q2 = r.hi / rhs.hi; - r = r - rhs * q2; - - q3 = r.hi / rhs.hi; - - q1 = quick_two_sum(q1, q2, &q2); - r = double_double(q1, q2) + q3; - return r; - } - - XSF_HOST_DEVICE inline double_double operator/(const double_double &lhs, const double rhs) { - return lhs / double_double(rhs); - } - - XSF_HOST_DEVICE inline double_double operator/(const double lhs, const double_double &rhs) { - return double_double(lhs) / rhs; - } - - XSF_HOST_DEVICE inline bool operator==(const double_double &lhs, const double_double &rhs) { - return (lhs.hi == rhs.hi && lhs.lo == rhs.lo); - } - - XSF_HOST_DEVICE inline bool operator==(const double_double &lhs, const double rhs) { - return (lhs.hi == rhs && lhs.lo == 0.0); - } - - XSF_HOST_DEVICE inline bool operator==(const double lhs, const double_double &rhs) { - return (lhs == rhs.hi) && (rhs.lo == 0.0); - } - - XSF_HOST_DEVICE inline bool operator!=(const double_double &lhs, const double_double &rhs) { - return (lhs.hi != rhs.hi) || (lhs.lo != rhs.lo); - } - - XSF_HOST_DEVICE inline bool operator!=(const double_double &lhs, const double rhs) { - return (lhs.hi != rhs) || (lhs.lo != 0.0); - } - - XSF_HOST_DEVICE inline bool operator!=(const double lhs, const double_double &rhs) { - return (rhs.hi != lhs) || (rhs.lo != 0.0); - } - - XSF_HOST_DEVICE inline bool operator<(const double_double &lhs, const double_double &rhs) { - if (lhs.hi < rhs.hi) { - return true; - } - if (lhs.hi > rhs.hi) { - return false; - } - return lhs.lo < rhs.lo; - } - - XSF_HOST_DEVICE inline bool operator<(const double_double &lhs, const double rhs) { - if (lhs.hi < rhs) { - return true; - } - if (lhs.hi > rhs) { - return false; - } - return lhs.lo < 0.0; - } - - template - XSF_HOST_DEVICE bool operator>(const double_double &lhs, const T &rhs) { - return rhs < lhs; - } - - XSF_HOST_DEVICE inline bool operator<(const double lhs, const double_double &rhs) { return rhs > lhs; } - - XSF_HOST_DEVICE inline bool operator>(const double lhs, const double_double &rhs) { return rhs < lhs; } - - XSF_HOST_DEVICE inline bool operator<=(const double_double &lhs, const double_double &rhs) { - if (lhs.hi < rhs.hi) { - return true; - } - if (lhs.hi > rhs.hi) { - return false; - } - return lhs.lo <= rhs.lo; - } - - XSF_HOST_DEVICE inline bool operator<=(const double_double &lhs, const double rhs) { - if (lhs.hi < rhs) { - return true; - } - if (lhs.hi > rhs) { - return false; - } - return lhs.lo <= 0.0; - } - - template - XSF_HOST_DEVICE bool operator>=(const double_double &lhs, const T &rhs) { - return rhs <= lhs; - } - - XSF_HOST_DEVICE inline bool operator>=(const double lhs, const double_double &rhs) { return rhs <= lhs; } - - XSF_HOST_DEVICE inline bool operator<=(const double lhs, const double_double &rhs) { return rhs >= lhs; } - - // Math functions - - XSF_HOST_DEVICE inline double_double mul_pwr2(const double_double &lhs, double rhs) { - /* double-double * double, where double is a power of 2. */ - return double_double(lhs.hi * rhs, lhs.lo * rhs); - } - - XSF_HOST_DEVICE inline bool isfinite(const double_double &a) { return std::isfinite(a.hi); } - - XSF_HOST_DEVICE inline bool isinf(const double_double &a) { return std::isinf(a.hi); } - - XSF_HOST_DEVICE inline double_double round(const double_double &a) { - double hi = two_nint(a.hi); - double lo; - - if (hi == a.hi) { - /* High word is an integer already. Round the low word.*/ - lo = two_nint(a.lo); - - /* Renormalize. This is needed if a.hi = some integer, a.lo = 1/2.*/ - hi = quick_two_sum(hi, lo, &lo); - } else { - /* High word is not an integer. */ - lo = 0.0; - if (std::abs(hi - a.hi) == 0.5 && a.lo < 0.0) { - /* There is a tie in the high word, consult the low word - to break the tie. */ - hi -= 1.0; /* NOTE: This does not cause INEXACT. */ - } - } - return double_double(hi, lo); - } - - XSF_HOST_DEVICE inline double_double floor(const double_double &a) { - double hi = std::floor(a.hi); - double lo = 0.0; - - if (hi == a.hi) { - /* High word is integer already. Round the low word. */ - lo = std::floor(a.lo); - hi = quick_two_sum(hi, lo, &lo); - } - - return double_double(hi, lo); - } - - XSF_HOST_DEVICE inline double_double ceil(const double_double &a) { - double hi = std::ceil(a.hi); - double lo = 0.0; - - if (hi == a.hi) { - /* High word is integer already. Round the low word. */ - lo = std::ceil(a.lo); - hi = quick_two_sum(hi, lo, &lo); - } - - return double_double(hi, lo); - } - - XSF_HOST_DEVICE inline double_double trunc(const double_double &a) { - return (a.hi >= 0.0) ? floor(a) : ceil(a); - } - - XSF_HOST_DEVICE inline double_double abs(const double_double &a) { return (a.hi < 0.0 ? -a : a); } - - XSF_HOST_DEVICE inline double_double fmod(const double_double &lhs, const double_double &rhs) { - double_double n = trunc(lhs / rhs); - return lhs - rhs * n; - } - - XSF_HOST_DEVICE inline double_double remainder(const double_double &lhs, const double_double &rhs) { - double_double n = round(lhs / rhs); - return lhs - rhs * n; - } - - XSF_HOST_DEVICE inline std::pair divrem(const double_double &lhs, - const double_double &rhs) { - double_double n = round(lhs / rhs); - double_double remainder = lhs - n * rhs; - return {n, remainder}; - } - - XSF_HOST_DEVICE inline double_double fma( - const double_double &a, const double_double &b, const double_double &c) { - // TODO: make an accurate fma - return a * b + c; - } - - XSF_HOST_DEVICE inline double_double square(const double_double &a) { - double p1, p2; - double s1, s2; - p1 = two_sqr(a.hi, &p2); - p2 += 2.0 * a.hi * a.lo; - p2 += a.lo * a.lo; - s1 = quick_two_sum(p1, p2, &s2); - return double_double(s1, s2); - } - - XSF_HOST_DEVICE inline double_double square(const double a) { - double p1, p2; - p1 = two_sqr(a, &p2); - return double_double(p1, p2); - } - - XSF_HOST_DEVICE inline double_double ldexp(const double_double &a, int expt) { - // float128 * (2.0 ^ expt) - return double_double(std::ldexp(a.hi, expt), std::ldexp(a.lo, expt)); - } - - XSF_HOST_DEVICE inline double_double frexp(const double_double &a, int *expt) { - // r"""return b and l s.t. 0.5<=|b|<1 and 2^l == a - // 0.5<=|b[0]|<1.0 or |b[0]| == 1.0 and b[0]*b[1]<0 - // """ - int exponent; - double man = std::frexp(a.hi, &exponent); - double b1 = std::ldexp(a.lo, -exponent); - if (std::abs(man) == 0.5 && man * b1 < 0) { - man *= 2; - b1 *= 2; - exponent -= 1; - } - *expt = exponent; - return double_double(man, b1); - } - - // Numeric limits - - XSF_HOST_DEVICE inline double_double quiet_NaN() { - return double_double(std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()); - } - - XSF_HOST_DEVICE inline double_double infinity() { - return double_double(std::numeric_limits::infinity(), std::numeric_limits::infinity()); - } - - const double_double inv_fact[] = {double_double(1.66666666666666657e-01, 9.25185853854297066e-18), - double_double(4.16666666666666644e-02, 2.31296463463574266e-18), - double_double(8.33333333333333322e-03, 1.15648231731787138e-19), - double_double(1.38888888888888894e-03, -5.30054395437357706e-20), - double_double(1.98412698412698413e-04, 1.72095582934207053e-22), - double_double(2.48015873015873016e-05, 2.15119478667758816e-23), - double_double(2.75573192239858925e-06, -1.85839327404647208e-22), - double_double(2.75573192239858883e-07, 2.37677146222502973e-23), - double_double(2.50521083854417202e-08, -1.44881407093591197e-24), - double_double(2.08767569878681002e-09, -1.20734505911325997e-25), - double_double(1.60590438368216133e-10, 1.25852945887520981e-26), - double_double(1.14707455977297245e-11, 2.06555127528307454e-28), - double_double(7.64716373181981641e-13, 7.03872877733453001e-30), - double_double(4.77947733238738525e-14, 4.39920548583408126e-31), - double_double(2.81145725434552060e-15, 1.65088427308614326e-31)}; - - // Math constants - const double_double E = double_double(2.718281828459045091e+00, 1.445646891729250158e-16); - const double_double LOG2 = double_double(6.931471805599452862e-01, 2.319046813846299558e-17); - const double EPS = 4.93038065763132e-32; // 2^-104 - - /* Exponential. Computes exp(x) in double-double precision. */ - XSF_HOST_DEVICE inline double_double exp(const double_double &a) { - /* Strategy: We first reduce the size of x by noting that - - exp(kr + m * log(2)) = 2^m * exp(r)^k - - where m and k are integers. By choosing m appropriately - we can make |kr| <= log(2) / 2 = 0.347. Then exp(r) is - evaluated using the familiar Taylor series. Reducing the - argument substantially speeds up the convergence. */ - - constexpr double k = 512.0; - constexpr double inv_k = 1.0 / k; - double m; - double_double r, s, t, p; - int i = 0; - - if (a.hi <= -709.0) { - return double_double(0.0); - } - - if (a.hi >= 709.0) { - return infinity(); - } - - if (a == 0.0) { - return double_double(1.0); - } - - if (a == 1.0) { - return E; - } - - m = std::floor(a.hi / LOG2.hi + 0.5); - r = mul_pwr2(double_double(a) - LOG2 * m, inv_k); - - p = square(r); - s = r + mul_pwr2(p, 0.5); - p = p * r; - t = p * inv_fact[0]; - do { - s = s + t; - p = p * r; - ++i; - t = p * inv_fact[i]; - } while ((std::abs(static_cast(t)) > inv_k * EPS) && i < 5); - - s = s + t; - - for (int j = 0; j < 9; j++) { - s = mul_pwr2(s, 2.0) + square(s); - } - s = s + 1.0; - - return ldexp(s, static_cast(m)); - } - - /* Logarithm. Computes log(x) in double-double precision. - This is a natural logarithm (i.e., base e). */ - XSF_HOST_DEVICE inline double_double log(const double_double &a) { - /* Strategy. The Taylor series for log converges much more - slowly than that of exp, due to the lack of the factorial - term in the denominator. Hence this routine instead tries - to determine the root of the function - - f(x) = exp(x) - a - - using Newton iteration. The iteration is given by - - x' = x - f(x)/f'(x) - = x - (1 - a * exp(-x)) - = x + a * exp(-x) - 1. - - Only one iteration is needed, since Newton's iteration - approximately doubles the number of digits per iteration. */ - double_double x; - - if (a == 1.0) { - return double_double(0.0); - } - - if (a.hi <= 0.0) { - return quiet_NaN(); - } - - x = double_double(std::log(a.hi)); /* Initial approximation */ - - /* x = x + a * exp(-x) - 1.0; */ - x = x + a * exp(-x) - 1.0; - return x; - } - - XSF_HOST_DEVICE inline double_double log1p(const double_double &a) { - double_double ans; - double la, elam1, ll; - if (a.hi <= -1.0) { - return -infinity(); - } - la = std::log1p(a.hi); - elam1 = xsf::cephes::expm1(la); - ll = std::log1p(a.lo / (1 + a.hi)); - if (a.hi > 0) { - ll -= (elam1 - a.hi) / (elam1 + 1); - } - ans = double_double(la) + ll; - return ans; - } - } // namespace detail - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/ellie.h b/scipy/special/xsf/cephes/ellie.h deleted file mode 100644 index a455599b4a95..000000000000 --- a/scipy/special/xsf/cephes/ellie.h +++ /dev/null @@ -1,293 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* ellie.c - * - * Incomplete elliptic integral of the second kind - * - * - * - * SYNOPSIS: - * - * double phi, m, y, ellie(); - * - * y = ellie( phi, m ); - * - * - * - * DESCRIPTION: - * - * Approximates the integral - * - * - * phi - * - - * | | - * | 2 - * E(phi_\m) = | sqrt( 1 - m sin t ) dt - * | - * | | - * - - * 0 - * - * of amplitude phi and modulus m, using the arithmetic - - * geometric mean algorithm. - * - * - * - * ACCURACY: - * - * Tested at random arguments with phi in [-10, 10] and m in - * [0, 1]. - * Relative error: - * arithmetic domain # trials peak rms - * IEEE -10,10 150000 3.3e-15 1.4e-16 - */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1984, 1987, 1993 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -/* Copyright 2014, Eric W. Moore */ - -/* Incomplete elliptic integral of second kind */ -#pragma once - -#include "../config.h" -#include "const.h" -#include "ellpe.h" -#include "ellpk.h" -#include "unity.h" - -namespace xsf { -namespace cephes { - namespace detail { - - /* To calculate legendre's incomplete elliptical integral of the second kind for - * negative m, we use a power series in phi for small m*phi*phi, an asymptotic - * series in m for large m*phi*phi* and the relation to Carlson's symmetric - * integrals, R_F(x,y,z) and R_D(x,y,z). - * - * E(phi, m) = sin(phi) * R_F(cos(phi)^2, 1 - m * sin(phi)^2, 1.0) - * - m * sin(phi)^3 * R_D(cos(phi)^2, 1 - m * sin(phi)^2, 1.0) / 3 - * - * = R_F(c-1, c-m, c) - m * R_D(c-1, c-m, c) / 3 - * - * where c = csc(phi)^2. We use the second form of this for (approximately) - * phi > 1/(sqrt(DBL_MAX) ~ 1e-154, where csc(phi)^2 overflows. Elsewhere we - * use the first form, accounting for the smallness of phi. - * - * The algorithm used is described in Carlson, B. C. Numerical computation of - * real or complex elliptic integrals. (1994) https://arxiv.org/abs/math/9409227 - * Most variable names reflect Carlson's usage. - * - * In this routine, we assume m < 0 and 0 > phi > pi/2. - */ - XSF_HOST_DEVICE inline double ellie_neg_m(double phi, double m) { - double x, y, z, x1, y1, z1, ret, Q; - double A0f, Af, Xf, Yf, Zf, E2f, E3f, scalef; - double A0d, Ad, seriesn, seriesd, Xd, Yd, Zd, E2d, E3d, E4d, E5d, scaled; - int n = 0; - double mpp = (m * phi) * phi; - - if (-mpp < 1e-6 && phi < -m) { - return phi + (mpp * phi * phi / 30.0 - mpp * mpp / 40.0 - mpp / 6.0) * phi; - } - - if (-mpp > 1e6) { - double sm = std::sqrt(-m); - double sp = std::sin(phi); - double cp = std::cos(phi); - - double a = -cosm1(phi); - double b1 = std::log(4 * sp * sm / (1 + cp)); - double b = -(0.5 + b1) / 2.0 / m; - double c = (0.75 + cp / sp / sp - b1) / 16.0 / m / m; - return (a + b + c) * sm; - } - - if (phi > 1e-153 && m > -1e200) { - double s = std::sin(phi); - double csc2 = 1.0 / s / s; - scalef = 1.0; - scaled = m / 3.0; - x = 1.0 / std::tan(phi) / std::tan(phi); - y = csc2 - m; - z = csc2; - } else { - scalef = phi; - scaled = mpp * phi / 3.0; - x = 1.0; - y = 1 - mpp; - z = 1.0; - } - - if (x == y && x == z) { - return (scalef + scaled / x) / std::sqrt(x); - } - - A0f = (x + y + z) / 3.0; - Af = A0f; - A0d = (x + y + 3.0 * z) / 5.0; - Ad = A0d; - x1 = x; - y1 = y; - z1 = z; - seriesd = 0.0; - seriesn = 1.0; - /* Carlson gives 1/pow(3*r, 1.0/6.0) for this constant. if r == eps, - * it is ~338.38. */ - - /* N.B. This will evaluate its arguments multiple times. */ - Q = 400.0 * std::fmax(std::abs(A0f - x), std::fmax(std::abs(A0f - y), std::abs(A0f - z))); - - while (Q > std::abs(Af) && Q > std::abs(Ad) && n <= 100) { - double sx = std::sqrt(x1); - double sy = std::sqrt(y1); - double sz = std::sqrt(z1); - double lam = sx * sy + sx * sz + sy * sz; - seriesd += seriesn / (sz * (z1 + lam)); - x1 = (x1 + lam) / 4.0; - y1 = (y1 + lam) / 4.0; - z1 = (z1 + lam) / 4.0; - Af = (x1 + y1 + z1) / 3.0; - Ad = (Ad + lam) / 4.0; - n += 1; - Q /= 4.0; - seriesn /= 4.0; - } - - Xf = (A0f - x) / Af / (1 << 2 * n); - Yf = (A0f - y) / Af / (1 << 2 * n); - Zf = -(Xf + Yf); - - E2f = Xf * Yf - Zf * Zf; - E3f = Xf * Yf * Zf; - - ret = scalef * (1.0 - E2f / 10.0 + E3f / 14.0 + E2f * E2f / 24.0 - 3.0 * E2f * E3f / 44.0) / sqrt(Af); - - Xd = (A0d - x) / Ad / (1 << 2 * n); - Yd = (A0d - y) / Ad / (1 << 2 * n); - Zd = -(Xd + Yd) / 3.0; - - E2d = Xd * Yd - 6.0 * Zd * Zd; - E3d = (3 * Xd * Yd - 8.0 * Zd * Zd) * Zd; - E4d = 3.0 * (Xd * Yd - Zd * Zd) * Zd * Zd; - E5d = Xd * Yd * Zd * Zd * Zd; - - ret -= scaled * - (1.0 - 3.0 * E2d / 14.0 + E3d / 6.0 + 9.0 * E2d * E2d / 88.0 - 3.0 * E4d / 22.0 - - 9.0 * E2d * E3d / 52.0 + 3.0 * E5d / 26.0) / - (1 << 2 * n) / Ad / sqrt(Ad); - ret -= 3.0 * scaled * seriesd; - return ret; - } - - } // namespace detail - - XSF_HOST_DEVICE inline double ellie(double phi, double m) { - double a, b, c, e, temp; - double lphi, t, E, denom, npio2; - int d, mod, sign; - - if (std::isnan(phi) || std::isnan(m)) - return std::numeric_limits::quiet_NaN(); - if (m > 1.0) - return std::numeric_limits::quiet_NaN(); - ; - if (std::isinf(phi)) - return phi; - if (std::isinf(m)) - return -m; - if (m == 0.0) - return (phi); - lphi = phi; - npio2 = std::floor(lphi / M_PI_2); - if (std::fmod(std::abs(npio2), 2.0) == 1.0) - npio2 += 1; - lphi = lphi - npio2 * M_PI_2; - if (lphi < 0.0) { - lphi = -lphi; - sign = -1; - } else { - sign = 1; - } - a = 1.0 - m; - E = ellpe(m); - if (a == 0.0) { - temp = std::sin(lphi); - goto done; - } - if (a > 1.0) { - temp = detail::ellie_neg_m(lphi, m); - goto done; - } - - if (lphi < 0.135) { - double m11 = (((((-7.0 / 2816.0) * m + (5.0 / 1056.0)) * m - (7.0 / 2640.0)) * m + (17.0 / 41580.0)) * m - - (1.0 / 155925.0)) * - m; - double m9 = ((((-5.0 / 1152.0) * m + (1.0 / 144.0)) * m - (1.0 / 360.0)) * m + (1.0 / 5670.0)) * m; - double m7 = ((-m / 112.0 + (1.0 / 84.0)) * m - (1.0 / 315.0)) * m; - double m5 = (-m / 40.0 + (1.0 / 30)) * m; - double m3 = -m / 6.0; - double p2 = lphi * lphi; - - temp = ((((m11 * p2 + m9) * p2 + m7) * p2 + m5) * p2 + m3) * p2 * lphi + lphi; - goto done; - } - t = std::tan(lphi); - b = std::sqrt(a); - /* Thanks to Brian Fitzgerald - * for pointing out an instability near odd multiples of pi/2. */ - if (std::abs(t) > 10.0) { - /* Transform the amplitude */ - e = 1.0 / (b * t); - /* ... but avoid multiple recursions. */ - if (std::abs(e) < 10.0) { - e = std::atan(e); - temp = E + m * std::sin(lphi) * std::sin(e) - ellie(e, m); - goto done; - } - } - c = std::sqrt(m); - a = 1.0; - d = 1; - e = 0.0; - mod = 0; - - while (std::abs(c / a) > detail::MACHEP) { - temp = b / a; - lphi = lphi + atan(t * temp) + mod * M_PI; - denom = 1 - temp * t * t; - if (std::abs(denom) > 10 * detail::MACHEP) { - t = t * (1.0 + temp) / denom; - mod = (lphi + M_PI_2) / M_PI; - } else { - t = std::tan(lphi); - mod = static_cast(std::floor((lphi - std::atan(t)) / M_PI)); - } - c = (a - b) / 2.0; - temp = std::sqrt(a * b); - a = (a + b) / 2.0; - b = temp; - d += d; - e += c * std::sin(lphi); - } - - temp = E / ellpk(1.0 - m); - temp *= (std::atan(t) + mod * M_PI) / (d * a); - temp += e; - - done: - - if (sign < 0) - temp = -temp; - temp += npio2 * E; - return (temp); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/ellik.h b/scipy/special/xsf/cephes/ellik.h deleted file mode 100644 index c05b3ec76c2e..000000000000 --- a/scipy/special/xsf/cephes/ellik.h +++ /dev/null @@ -1,251 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* ellik.c - * - * Incomplete elliptic integral of the first kind - * - * - * - * SYNOPSIS: - * - * double phi, m, y, ellik(); - * - * y = ellik( phi, m ); - * - * - * - * DESCRIPTION: - * - * Approximates the integral - * - * - * - * phi - * - - * | | - * | dt - * F(phi | m) = | ------------------ - * | 2 - * | | sqrt( 1 - m sin t ) - * - - * 0 - * - * of amplitude phi and modulus m, using the arithmetic - - * geometric mean algorithm. - * - * - * - * - * ACCURACY: - * - * Tested at random points with m in [0, 1] and phi as indicated. - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE -10,10 200000 7.4e-16 1.0e-16 - * - * - */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1984, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -/* Copyright 2014, Eric W. Moore */ - -/* Incomplete elliptic integral of first kind */ -#pragma once - -#include "../config.h" -#include "../error.h" -#include "const.h" -#include "ellpk.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* To calculate legendre's incomplete elliptical integral of the first kind for - * negative m, we use a power series in phi for small m*phi*phi, an asymptotic - * series in m for large m*phi*phi* and the relation to Carlson's symmetric - * integral of the first kind. - * - * F(phi, m) = sin(phi) * R_F(cos(phi)^2, 1 - m * sin(phi)^2, 1.0) - * = R_F(c-1, c-m, c) - * - * where c = csc(phi)^2. We use the second form of this for (approximately) - * phi > 1/(sqrt(DBL_MAX) ~ 1e-154, where csc(phi)^2 overflows. Elsewhere we - * use the first form, accounting for the smallness of phi. - * - * The algorithm used is described in Carlson, B. C. Numerical computation of - * real or complex elliptic integrals. (1994) https://arxiv.org/abs/math/9409227 - * Most variable names reflect Carlson's usage. - * - * In this routine, we assume m < 0 and 0 > phi > pi/2. - */ - XSF_HOST_DEVICE inline double ellik_neg_m(double phi, double m) { - double x, y, z, x1, y1, z1, A0, A, Q, X, Y, Z, E2, E3, scale; - int n = 0; - double mpp = (m * phi) * phi; - - if (-mpp < 1e-6 && phi < -m) { - return phi + (-mpp * phi * phi / 30.0 + 3.0 * mpp * mpp / 40.0 + mpp / 6.0) * phi; - } - - if (-mpp > 4e7) { - double sm = std::sqrt(-m); - double sp = std::sin(phi); - double cp = std::cos(phi); - - double a = std::log(4 * sp * sm / (1 + cp)); - double b = -(1 + cp / sp / sp - a) / 4 / m; - return (a + b) / sm; - } - - if (phi > 1e-153 && m > -1e305) { - double s = std::sin(phi); - double csc2 = 1.0 / (s * s); - scale = 1.0; - x = 1.0 / (std::tan(phi) * std::tan(phi)); - y = csc2 - m; - z = csc2; - } else { - scale = phi; - x = 1.0; - y = 1 - m * scale * scale; - z = 1.0; - } - - if (x == y && x == z) { - return scale / std::sqrt(x); - } - - A0 = (x + y + z) / 3.0; - A = A0; - x1 = x; - y1 = y; - z1 = z; - /* Carlson gives 1/pow(3*r, 1.0/6.0) for this constant. if r == eps, - * it is ~338.38. */ - Q = 400.0 * std::fmax(std::abs(A0 - x), std::fmax(std::abs(A0 - y), std::abs(A0 - z))); - - while (Q > std::abs(A) && n <= 100) { - double sx = std::sqrt(x1); - double sy = std::sqrt(y1); - double sz = std::sqrt(z1); - double lam = sx * sy + sx * sz + sy * sz; - x1 = (x1 + lam) / 4.0; - y1 = (y1 + lam) / 4.0; - z1 = (z1 + lam) / 4.0; - A = (x1 + y1 + z1) / 3.0; - n += 1; - Q /= 4; - } - X = (A0 - x) / A / (1 << 2 * n); - Y = (A0 - y) / A / (1 << 2 * n); - Z = -(X + Y); - - E2 = X * Y - Z * Z; - E3 = X * Y * Z; - - return scale * (1.0 - E2 / 10.0 + E3 / 14.0 + E2 * E2 / 24.0 - 3.0 * E2 * E3 / 44.0) / sqrt(A); - } - - } // namespace detail - - XSF_HOST_DEVICE inline double ellik(double phi, double m) { - double a, b, c, e, temp, t, K, denom, npio2; - int d, mod, sign; - - if (std::isnan(phi) || std::isnan(m)) - return std::numeric_limits::quiet_NaN(); - if (m > 1.0) - return std::numeric_limits::quiet_NaN(); - if (std::isinf(phi) || std::isinf(m)) { - if (std::isinf(m) && std::isfinite(phi)) - return 0.0; - else if (std::isinf(phi) && std::isfinite(m)) - return phi; - else - return std::numeric_limits::quiet_NaN(); - } - if (m == 0.0) - return (phi); - a = 1.0 - m; - if (a == 0.0) { - if (std::abs(phi) >= (double) M_PI_2) { - set_error("ellik", SF_ERROR_SINGULAR, NULL); - return (std::numeric_limits::infinity()); - } - /* DLMF 19.6.8, and 4.23.42 */ - return std::asinh(std::tan(phi)); - } - npio2 = floor(phi / M_PI_2); - if (std::fmod(std::abs(npio2), 2.0) == 1.0) - npio2 += 1; - if (npio2 != 0.0) { - K = ellpk(a); - phi = phi - npio2 * M_PI_2; - } else - K = 0.0; - if (phi < 0.0) { - phi = -phi; - sign = -1; - } else - sign = 0; - if (a > 1.0) { - temp = detail::ellik_neg_m(phi, m); - goto done; - } - b = std::sqrt(a); - t = std::tan(phi); - if (std::abs(t) > 10.0) { - /* Transform the amplitude */ - e = 1.0 / (b * t); - /* ... but avoid multiple recursions. */ - if (std::abs(e) < 10.0) { - e = std::atan(e); - if (npio2 == 0) - K = ellpk(a); - temp = K - ellik(e, m); - goto done; - } - } - a = 1.0; - c = std::sqrt(m); - d = 1; - mod = 0; - - while (std::abs(c / a) > detail::MACHEP) { - temp = b / a; - phi = phi + atan(t * temp) + mod * M_PI; - denom = 1.0 - temp * t * t; - if (std::abs(denom) > 10 * detail::MACHEP) { - t = t * (1.0 + temp) / denom; - mod = (phi + M_PI_2) / M_PI; - } else { - t = std::tan(phi); - mod = static_cast(std::floor((phi - std::atan(t)) / M_PI)); - } - c = (a - b) / 2.0; - temp = std::sqrt(a * b); - a = (a + b) / 2.0; - b = temp; - d += d; - } - - temp = (std::atan(t) + mod * M_PI) / (d * a); - - done: - if (sign < 0) - temp = -temp; - temp += npio2 * K; - return (temp); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/ellpe.h b/scipy/special/xsf/cephes/ellpe.h deleted file mode 100644 index bc7c51f11acb..000000000000 --- a/scipy/special/xsf/cephes/ellpe.h +++ /dev/null @@ -1,107 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* ellpe.c - * - * Complete elliptic integral of the second kind - * - * - * - * SYNOPSIS: - * - * double m, y, ellpe(); - * - * y = ellpe( m ); - * - * - * - * DESCRIPTION: - * - * Approximates the integral - * - * - * pi/2 - * - - * | | 2 - * E(m) = | sqrt( 1 - m sin t ) dt - * | | - * - - * 0 - * - * Where m = 1 - m1, using the approximation - * - * P(x) - x log x Q(x). - * - * Though there are no singularities, the argument m1 is used - * internally rather than m for compatibility with ellpk(). - * - * E(1) = 1; E(0) = pi/2. - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0, 1 10000 2.1e-16 7.3e-17 - * - * - * ERROR MESSAGES: - * - * message condition value returned - * ellpe domain x<0, x>1 0.0 - * - */ - -/* ellpe.c */ - -/* Elliptic integral of second kind */ - -/* - * Cephes Math Library, Release 2.1: February, 1989 - * Copyright 1984, 1987, 1989 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - * - * Feb, 2002: altered by Travis Oliphant - * so that it is called with argument m - * (which gets immediately converted to m1 = 1-m) - */ -#pragma once - -#include "../config.h" -#include "../error.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double ellpe_P[] = {1.53552577301013293365E-4, 2.50888492163602060990E-3, 8.68786816565889628429E-3, - 1.07350949056076193403E-2, 7.77395492516787092951E-3, 7.58395289413514708519E-3, - 1.15688436810574127319E-2, 2.18317996015557253103E-2, 5.68051945617860553470E-2, - 4.43147180560990850618E-1, 1.00000000000000000299E0}; - - constexpr double ellpe_Q[] = {3.27954898576485872656E-5, 1.00962792679356715133E-3, 6.50609489976927491433E-3, - 1.68862163993311317300E-2, 2.61769742454493659583E-2, 3.34833904888224918614E-2, - 4.27180926518931511717E-2, 5.85936634471101055642E-2, 9.37499997197644278445E-2, - 2.49999999999888314361E-1}; - - } // namespace detail - - XSF_HOST_DEVICE inline double ellpe(double x) { - x = 1.0 - x; - if (x <= 0.0) { - if (x == 0.0) - return (1.0); - set_error("ellpe", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - if (x > 1.0) { - return ellpe(1.0 - 1 / x) * std::sqrt(x); - } - return (polevl(x, detail::ellpe_P, 10) - std::log(x) * (x * polevl(x, detail::ellpe_Q, 9))); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/ellpj.h b/scipy/special/xsf/cephes/ellpj.h deleted file mode 100644 index 7221b7b9232d..000000000000 --- a/scipy/special/xsf/cephes/ellpj.h +++ /dev/null @@ -1,162 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* ellpj.c - * - * Jacobian Elliptic Functions - * - * - * - * SYNOPSIS: - * - * double u, m, sn, cn, dn, phi; - * int ellpj(); - * - * ellpj( u, m, _&sn, _&cn, _&dn, _&phi ); - * - * - * - * DESCRIPTION: - * - * - * Evaluates the Jacobian elliptic functions sn(u|m), cn(u|m), - * and dn(u|m) of parameter m between 0 and 1, and real - * argument u. - * - * These functions are periodic, with quarter-period on the - * real axis equal to the complete elliptic integral - * ellpk(m). - * - * Relation to incomplete elliptic integral: - * If u = ellik(phi,m), then sn(u|m) = sin(phi), - * and cn(u|m) = cos(phi). Phi is called the amplitude of u. - * - * Computation is by means of the arithmetic-geometric mean - * algorithm, except when m is within 1e-9 of 0 or 1. In the - * latter case with m close to 1, the approximation applies - * only for phi < pi/2. - * - * ACCURACY: - * - * Tested at random points with u between 0 and 10, m between - * 0 and 1. - * - * Absolute error (* = relative error): - * arithmetic function # trials peak rms - * IEEE phi 10000 9.2e-16* 1.4e-16* - * IEEE sn 50000 4.1e-15 4.6e-16 - * IEEE cn 40000 3.6e-15 4.4e-16 - * IEEE dn 10000 1.3e-12 1.8e-14 - * - * Peak error observed in consistency check using addition - * theorem for sn(u+v) was 4e-16 (absolute). Also tested by - * the above relation to the incomplete elliptic integral. - * Accuracy deteriorates when u is large. - * - */ - -/* ellpj.c */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1984, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ - -/* Scipy changes: - * - 07-18-2016: improve evaluation of dn near quarter periods - */ -#pragma once - -#include "../config.h" -#include "../error.h" -#include "const.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE inline int ellpj(double u, double m, double *sn, double *cn, double *dn, double *ph) { - double ai, b, phi, t, twon, dnfac; - double a[9], c[9]; - int i; - - /* Check for special cases */ - if (m < 0.0 || m > 1.0 || std::isnan(m)) { - set_error("ellpj", SF_ERROR_DOMAIN, NULL); - *sn = std::numeric_limits::quiet_NaN(); - *cn = std::numeric_limits::quiet_NaN(); - *ph = std::numeric_limits::quiet_NaN(); - *dn = std::numeric_limits::quiet_NaN(); - return (-1); - } - if (m < 1.0e-9) { - t = std::sin(u); - b = std::cos(u); - ai = 0.25 * m * (u - t * b); - *sn = t - ai * b; - *cn = b + ai * t; - *ph = u - ai; - *dn = 1.0 - 0.5 * m * t * t; - return (0); - } - if (m >= 0.9999999999) { - ai = 0.25 * (1.0 - m); - b = std::cosh(u); - t = std::tanh(u); - phi = 1.0 / b; - twon = b * std::sinh(u); - *sn = t + ai * (twon - u) / (b * b); - *ph = 2.0 * std::atan(exp(u)) - M_PI_2 + ai * (twon - u) / b; - ai *= t * phi; - *cn = phi - ai * (twon - u); - *dn = phi + ai * (twon + u); - return (0); - } - - /* A. G. M. scale. See DLMF 22.20(ii) */ - a[0] = 1.0; - b = std::sqrt(1.0 - m); - c[0] = std::sqrt(m); - twon = 1.0; - i = 0; - - while (std::abs(c[i] / a[i]) > detail::MACHEP) { - if (i > 7) { - set_error("ellpj", SF_ERROR_OVERFLOW, NULL); - goto done; - } - ai = a[i]; - ++i; - c[i] = (ai - b) / 2.0; - t = std::sqrt(ai * b); - a[i] = (ai + b) / 2.0; - b = t; - twon *= 2.0; - } - - done: - /* backward recurrence */ - phi = twon * a[i] * u; - do { - t = c[i] * std::sin(phi) / a[i]; - b = phi; - phi = (std::asin(t) + phi) / 2.0; - } while (--i); - - *sn = std::sin(phi); - t = std::cos(phi); - *cn = t; - dnfac = std::cos(phi - b); - /* See discussion after DLMF 22.20.5 */ - if (std::abs(dnfac) < 0.1) { - *dn = std::sqrt(1 - m * (*sn) * (*sn)); - } else { - *dn = t / dnfac; - } - *ph = phi; - return (0); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/ellpk.h b/scipy/special/xsf/cephes/ellpk.h deleted file mode 100644 index 39ebf7e80b19..000000000000 --- a/scipy/special/xsf/cephes/ellpk.h +++ /dev/null @@ -1,117 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* ellpk.c - * - * Complete elliptic integral of the first kind - * - * - * - * SYNOPSIS: - * - * double m1, y, ellpk(); - * - * y = ellpk( m1 ); - * - * - * - * DESCRIPTION: - * - * Approximates the integral - * - * - * - * pi/2 - * - - * | | - * | dt - * K(m) = | ------------------ - * | 2 - * | | sqrt( 1 - m sin t ) - * - - * 0 - * - * where m = 1 - m1, using the approximation - * - * P(x) - log x Q(x). - * - * The argument m1 is used internally rather than m so that the logarithmic - * singularity at m = 1 will be shifted to the origin; this - * preserves maximum accuracy. - * - * K(0) = pi/2. - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,1 30000 2.5e-16 6.8e-17 - * - * ERROR MESSAGES: - * - * message condition value returned - * ellpk domain x<0, x>1 0.0 - * - */ - -/* ellpk.c */ - -/* - * Cephes Math Library, Release 2.0: April, 1987 - * Copyright 1984, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "../error.h" -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double ellpk_P[] = {1.37982864606273237150E-4, 2.28025724005875567385E-3, 7.97404013220415179367E-3, - 9.85821379021226008714E-3, 6.87489687449949877925E-3, 6.18901033637687613229E-3, - 8.79078273952743772254E-3, 1.49380448916805252718E-2, 3.08851465246711995998E-2, - 9.65735902811690126535E-2, 1.38629436111989062502E0}; - - constexpr double ellpk_Q[] = {2.94078955048598507511E-5, 9.14184723865917226571E-4, 5.94058303753167793257E-3, - 1.54850516649762399335E-2, 2.39089602715924892727E-2, 3.01204715227604046988E-2, - 3.73774314173823228969E-2, 4.88280347570998239232E-2, 7.03124996963957469739E-2, - 1.24999999999870820058E-1, 4.99999999999999999821E-1}; - - constexpr double ellpk_C1 = 1.3862943611198906188E0; /* log(4) */ - - } // namespace detail - - XSF_HOST_DEVICE inline double ellpk(double x) { - - if (x < 0.0) { - set_error("ellpk", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - - if (x > 1.0) { - if (std::isinf(x)) { - return 0.0; - } - return ellpk(1 / x) / std::sqrt(x); - } - - if (x > detail::MACHEP) { - return (polevl(x, detail::ellpk_P, 10) - std::log(x) * polevl(x, detail::ellpk_Q, 10)); - } else { - if (x == 0.0) { - set_error("ellpk", SF_ERROR_SINGULAR, NULL); - return (std::numeric_limits::infinity()); - } else { - return (detail::ellpk_C1 - 0.5 * std::log(x)); - } - } - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/erfinv.h b/scipy/special/xsf/cephes/erfinv.h deleted file mode 100644 index a7e88e08a528..000000000000 --- a/scipy/special/xsf/cephes/erfinv.h +++ /dev/null @@ -1,76 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "ndtri.h" - -namespace xsf { -namespace cephes { - - /* - * Inverse of the error function. - * - * Computes the inverse of the error function on the restricted domain - * -1 < y < 1. This restriction ensures the existence of a unique result - * such that erf(erfinv(y)) = y. - */ - XSF_HOST_DEVICE inline double erfinv(double y) { - constexpr double domain_lb = -1; - constexpr double domain_ub = 1; - - constexpr double thresh = 1e-7; - - /* - * For small arguments, use the Taylor expansion - * erf(y) = 2/\sqrt{\pi} (y - y^3 / 3 + O(y^5)), y\to 0 - * where we only retain the linear term. - * Otherwise, y + 1 loses precision for |y| << 1. - */ - if ((-thresh < y) && (y < thresh)) { - return y / M_2_SQRTPI; - } - if ((domain_lb < y) && (y < domain_ub)) { - return ndtri(0.5 * (y + 1)) * M_SQRT1_2; - } else if (y == domain_lb) { - return -std::numeric_limits::infinity(); - } else if (y == domain_ub) { - return std::numeric_limits::infinity(); - } else if (std::isnan(y)) { - set_error("erfinv", SF_ERROR_DOMAIN, NULL); - return y; - } else { - set_error("erfinv", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - } - - /* - * Inverse of the complementary error function. - * - * Computes the inverse of the complimentary error function on the restricted - * domain 0 < y < 2. This restriction ensures the existence of a unique result - * such that erfc(erfcinv(y)) = y. - */ - XSF_HOST_DEVICE inline double erfcinv(double y) { - constexpr double domain_lb = 0; - constexpr double domain_ub = 2; - - if ((domain_lb < y) && (y < domain_ub)) { - return -ndtri(0.5 * y) * M_SQRT1_2; - } else if (y == domain_lb) { - return std::numeric_limits::infinity(); - } else if (y == domain_ub) { - return -std::numeric_limits::infinity(); - } else if (std::isnan(y)) { - set_error("erfcinv", SF_ERROR_DOMAIN, NULL); - return y; - } else { - set_error("erfcinv", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/exp10.h b/scipy/special/xsf/cephes/exp10.h deleted file mode 100644 index 56e8e62866c9..000000000000 --- a/scipy/special/xsf/cephes/exp10.h +++ /dev/null @@ -1,130 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* exp10.c - * - * Base 10 exponential function - * (Common antilogarithm) - * - * - * - * SYNOPSIS: - * - * double x, y, exp10(); - * - * y = exp10( x ); - * - * - * - * DESCRIPTION: - * - * Returns 10 raised to the x power. - * - * Range reduction is accomplished by expressing the argument - * as 10**x = 2**n 10**f, with |f| < 0.5 log10(2). - * The Pade' form - * - * 1 + 2x P(x**2)/( Q(x**2) - P(x**2) ) - * - * is used to approximate 10**f. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE -307,+307 30000 2.2e-16 5.5e-17 - * - * ERROR MESSAGES: - * - * message condition value returned - * exp10 underflow x < -MAXL10 0.0 - * exp10 overflow x > MAXL10 INFINITY - * - * IEEE arithmetic: MAXL10 = 308.2547155599167. - * - */ - -/* - * Cephes Math Library Release 2.2: January, 1991 - * Copyright 1984, 1991 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double exp10_P[] = { - 4.09962519798587023075E-2, - 1.17452732554344059015E1, - 4.06717289936872725516E2, - 2.39423741207388267439E3, - }; - - constexpr double exp10_Q[] = { - /* 1.00000000000000000000E0, */ - 8.50936160849306532625E1, - 1.27209271178345121210E3, - 2.07960819286001865907E3, - }; - - /* static double LOG102 = 3.01029995663981195214e-1; */ - constexpr double exp10_LOG210 = 3.32192809488736234787e0; - constexpr double exp10_LG102A = 3.01025390625000000000E-1; - constexpr double exp10_LG102B = 4.60503898119521373889E-6; - - /* static double MAXL10 = 38.230809449325611792; */ - constexpr double exp10_MAXL10 = 308.2547155599167; - - } // namespace detail - - XSF_HOST_DEVICE inline double exp10(double x) { - double px, xx; - short n; - - if (std::isnan(x)) { - return (x); - } - if (x > detail::exp10_MAXL10) { - return (std::numeric_limits::infinity()); - } - - if (x < -detail::exp10_MAXL10) { /* Would like to use MINLOG but can't */ - set_error("exp10", SF_ERROR_UNDERFLOW, NULL); - return (0.0); - } - - /* Express 10**x = 10**g 2**n - * = 10**g 10**( n log10(2) ) - * = 10**( g + n log10(2) ) - */ - px = std::floor(detail::exp10_LOG210 * x + 0.5); - n = px; - x -= px * detail::exp10_LG102A; - x -= px * detail::exp10_LG102B; - - /* rational approximation for exponential - * of the fractional part: - * 10**x = 1 + 2x P(x**2)/( Q(x**2) - P(x**2) ) - */ - xx = x * x; - px = x * polevl(xx, detail::exp10_P, 3); - x = px / (p1evl(xx, detail::exp10_Q, 3) - px); - x = 1.0 + std::ldexp(x, 1); - - /* multiply by power of 2 */ - x = std::ldexp(x, n); - - return (x); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/exp2.h b/scipy/special/xsf/cephes/exp2.h deleted file mode 100644 index 144fd26bc8ca..000000000000 --- a/scipy/special/xsf/cephes/exp2.h +++ /dev/null @@ -1,122 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* exp2.c - * - * Base 2 exponential function - * - * - * - * SYNOPSIS: - * - * double x, y, exp2(); - * - * y = exp2( x ); - * - * - * - * DESCRIPTION: - * - * Returns 2 raised to the x power. - * - * Range reduction is accomplished by separating the argument - * into an integer k and fraction f such that - * x k f - * 2 = 2 2. - * - * A Pade' form - * - * 1 + 2x P(x**2) / (Q(x**2) - x P(x**2) ) - * - * approximates 2**x in the basic range [-0.5, 0.5]. - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE -1022,+1024 30000 1.8e-16 5.4e-17 - * - * - * See exp.c for comments on error amplification. - * - * - * ERROR MESSAGES: - * - * message condition value returned - * exp underflow x < -MAXL2 0.0 - * exp overflow x > MAXL2 INFINITY - * - * For IEEE arithmetic, MAXL2 = 1024. - */ - -/* - * Cephes Math Library Release 2.3: March, 1995 - * Copyright 1984, 1995 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" - -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double exp2_P[] = { - 2.30933477057345225087E-2, - 2.02020656693165307700E1, - 1.51390680115615096133E3, - }; - - constexpr double exp2_Q[] = { - /* 1.00000000000000000000E0, */ - 2.33184211722314911771E2, - 4.36821166879210612817E3, - }; - - constexpr double exp2_MAXL2 = 1024.0; - constexpr double exp2_MINL2 = -1024.0; - - } // namespace detail - - XSF_HOST_DEVICE inline double exp2(double x) { - double px, xx; - short n; - - if (std::isnan(x)) { - return (x); - } - if (x > detail::exp2_MAXL2) { - return (std::numeric_limits::infinity()); - } - - if (x < detail::exp2_MINL2) { - return (0.0); - } - - xx = x; /* save x */ - /* separate into integer and fractional parts */ - px = std::floor(x + 0.5); - n = px; - x = x - px; - - /* rational approximation - * exp2(x) = 1 + 2xP(xx)/(Q(xx) - P(xx)) - * where xx = x**2 - */ - xx = x * x; - px = x * polevl(xx, detail::exp2_P, 2); - x = px / (p1evl(xx, detail::exp2_Q, 2) - px); - x = 1.0 + std::ldexp(x, 1); - - /* scale by power of 2 */ - x = std::ldexp(x, n); - return (x); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/expn.h b/scipy/special/xsf/cephes/expn.h deleted file mode 100644 index 8b0b07eab7a9..000000000000 --- a/scipy/special/xsf/cephes/expn.h +++ /dev/null @@ -1,260 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* expn.c - * - * Exponential integral En - * - * - * - * SYNOPSIS: - * - * int n; - * double x, y, expn(); - * - * y = expn( n, x ); - * - * - * - * DESCRIPTION: - * - * Evaluates the exponential integral - * - * inf. - * - - * | | -xt - * | e - * E (x) = | ---- dt. - * n | n - * | | t - * - - * 1 - * - * - * Both n and x must be nonnegative. - * - * The routine employs either a power series, a continued - * fraction, or an asymptotic formula depending on the - * relative values of n and x. - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 10000 1.7e-15 3.6e-16 - * - */ - -/* expn.c */ - -/* Cephes Math Library Release 1.1: March, 1985 - * Copyright 1985 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 */ - -/* Sources - * [1] NIST, "The Digital Library of Mathematical Functions", dlmf.nist.gov - */ - -/* Scipy changes: - * - 09-10-2016: improved asymptotic expansion for large n - */ - -#pragma once - -#include "../config.h" -#include "../error.h" -#include "const.h" -#include "rgamma.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr int expn_nA = 13; - constexpr double expn_A0[] = {1.00000000000000000}; - constexpr double expn_A1[] = {1.00000000000000000}; - constexpr double expn_A2[] = {-2.00000000000000000, 1.00000000000000000}; - constexpr double expn_A3[] = {6.00000000000000000, -8.00000000000000000, 1.00000000000000000}; - constexpr double expn_A4[] = {-24.0000000000000000, 58.0000000000000000, -22.0000000000000000, - 1.00000000000000000}; - constexpr double expn_A5[] = {120.000000000000000, -444.000000000000000, 328.000000000000000, - -52.0000000000000000, 1.00000000000000000}; - constexpr double expn_A6[] = {-720.000000000000000, 3708.00000000000000, -4400.00000000000000, - 1452.00000000000000, -114.000000000000000, 1.00000000000000000}; - constexpr double expn_A7[] = {5040.00000000000000, -33984.0000000000000, 58140.0000000000000, - -32120.0000000000000, 5610.00000000000000, -240.000000000000000, - 1.00000000000000000}; - constexpr double expn_A8[] = {-40320.0000000000000, 341136.000000000000, -785304.000000000000, - 644020.000000000000, -195800.000000000000, 19950.0000000000000, - -494.000000000000000, 1.00000000000000000}; - constexpr double expn_A9[] = {362880.000000000000, -3733920.00000000000, 11026296.0000000000, - -12440064.0000000000, 5765500.00000000000, -1062500.00000000000, - 67260.0000000000000, -1004.00000000000000, 1.00000000000000000}; - constexpr double expn_A10[] = {-3628800.00000000000, 44339040.0000000000, -162186912.000000000, - 238904904.000000000, -155357384.000000000, 44765000.0000000000, - -5326160.00000000000, 218848.000000000000, -2026.00000000000000, - 1.00000000000000000}; - constexpr double expn_A11[] = {39916800.0000000000, -568356480.000000000, 2507481216.00000000, - -4642163952.00000000, 4002695088.00000000, -1648384304.00000000, - 314369720.000000000, -25243904.0000000000, 695038.000000000000, - -4072.00000000000000, 1.00000000000000000}; - constexpr double expn_A12[] = {-479001600.000000000, 7827719040.00000000, -40788301824.0000000, - 92199790224.0000000, -101180433024.000000, 56041398784.0000000, - -15548960784.0000000, 2051482776.00000000, -114876376.000000000, - 2170626.00000000000, -8166.00000000000000, 1.00000000000000000}; - constexpr const double *expn_A[] = {expn_A0, expn_A1, expn_A2, expn_A3, expn_A4, expn_A5, expn_A6, - expn_A7, expn_A8, expn_A9, expn_A10, expn_A11, expn_A12}; - constexpr int expn_Adegs[] = {0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; - - /* Asymptotic expansion for large n, DLMF 8.20(ii) */ - XSF_HOST_DEVICE double expn_large_n(int n, double x) { - int k; - double p = n; - double lambda = x / p; - double multiplier = 1 / p / (lambda + 1) / (lambda + 1); - double fac = 1; - double res = 1; /* A[0] = 1 */ - double expfac, term; - - expfac = std::exp(-lambda * p) / (lambda + 1) / p; - if (expfac == 0) { - set_error("expn", SF_ERROR_UNDERFLOW, NULL); - return 0; - } - - /* Do the k = 1 term outside the loop since A[1] = 1 */ - fac *= multiplier; - res += fac; - - for (k = 2; k < expn_nA; k++) { - fac *= multiplier; - term = fac * polevl(lambda, expn_A[k], expn_Adegs[k]); - res += term; - if (std::abs(term) < MACHEP * std::abs(res)) { - break; - } - } - - return expfac * res; - } - } // namespace detail - - XSF_HOST_DEVICE double expn(int n, double x) { - double ans, r, t, yk, xk; - double pk, pkm1, pkm2, qk, qkm1, qkm2; - double psi, z; - int i, k; - constexpr double big = 1.44115188075855872E+17; - - if (std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } else if (n < 0 || x < 0) { - set_error("expn", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - if (x > detail::MAXLOG) { - return (0.0); - } - - if (x == 0.0) { - if (n < 2) { - set_error("expn", SF_ERROR_SINGULAR, NULL); - return std::numeric_limits::infinity(); - } else { - return (1.0 / (n - 1.0)); - } - } - - if (n == 0) { - return (std::exp(-x) / x); - } - - /* Asymptotic expansion for large n, DLMF 8.20(ii) */ - if (n > 50) { - ans = detail::expn_large_n(n, x); - return ans; - } - - /* Continued fraction, DLMF 8.19.17 */ - if (x > 1.0) { - k = 1; - pkm2 = 1.0; - qkm2 = x; - pkm1 = 1.0; - qkm1 = x + n; - ans = pkm1 / qkm1; - - do { - k += 1; - if (k & 1) { - yk = 1.0; - xk = n + (k - 1) / 2; - } else { - yk = x; - xk = k / 2; - } - pk = pkm1 * yk + pkm2 * xk; - qk = qkm1 * yk + qkm2 * xk; - if (qk != 0) { - r = pk / qk; - t = std::abs((ans - r) / r); - ans = r; - } else { - t = 1.0; - } - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - if (std::abs(pk) > big) { - pkm2 /= big; - pkm1 /= big; - qkm2 /= big; - qkm1 /= big; - } - } while (t > detail::MACHEP); - - ans *= std::exp(-x); - return ans; - } - - /* Power series expansion, DLMF 8.19.8 */ - psi = -detail::SCIPY_EULER - std::log(x); - for (i = 1; i < n; i++) { - psi = psi + 1.0 / i; - } - - z = -x; - xk = 0.0; - yk = 1.0; - pk = 1.0 - n; - if (n == 1) { - ans = 0.0; - } else { - ans = 1.0 / pk; - } - do { - xk += 1.0; - yk *= z / xk; - pk += 1.0; - if (pk != 0.0) { - ans += yk / pk; - } - if (ans != 0.0) - t = std::abs(yk / ans); - else - t = 1.0; - } while (t > detail::MACHEP); - k = xk; - t = n; - r = n - 1; - ans = (std::pow(z, r) * psi * rgamma(t)) - ans; - return ans; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/fdtr.h b/scipy/special/xsf/cephes/fdtr.h deleted file mode 100644 index 27cd992e02d8..000000000000 --- a/scipy/special/xsf/cephes/fdtr.h +++ /dev/null @@ -1,223 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* fdtr.c - * - * F distribution - * - * - * - * SYNOPSIS: - * - * double df1, df2; - * double x, y, fdtr(); - * - * y = fdtr( df1, df2, x ); - * - * DESCRIPTION: - * - * Returns the area from zero to x under the F density - * function (also known as Snedcor's density or the - * variance ratio density). This is the density - * of x = (u1/df1)/(u2/df2), where u1 and u2 are random - * variables having Chi square distributions with df1 - * and df2 degrees of freedom, respectively. - * - * The incomplete beta integral is used, according to the - * formula - * - * P(x) = incbet( df1/2, df2/2, (df1*x/(df2 + df1*x) ). - * - * - * The arguments a and b are greater than zero, and x is - * nonnegative. - * - * ACCURACY: - * - * Tested at random points (a,b,x). - * - * x a,b Relative error: - * arithmetic domain domain # trials peak rms - * IEEE 0,1 0,100 100000 9.8e-15 1.7e-15 - * IEEE 1,5 0,100 100000 6.5e-15 3.5e-16 - * IEEE 0,1 1,10000 100000 2.2e-11 3.3e-12 - * IEEE 1,5 1,10000 100000 1.1e-11 1.7e-13 - * See also incbet.c. - * - * - * ERROR MESSAGES: - * - * message condition value returned - * fdtr domain a<0, b<0, x<0 0.0 - * - */ - -/* fdtrc() - * - * Complemented F distribution - * - * - * - * SYNOPSIS: - * - * double df1, df2; - * double x, y, fdtrc(); - * - * y = fdtrc( df1, df2, x ); - * - * DESCRIPTION: - * - * Returns the area from x to infinity under the F density - * function (also known as Snedcor's density or the - * variance ratio density). - * - * - * inf. - * - - * 1 | | a-1 b-1 - * 1-P(x) = ------ | t (1-t) dt - * B(a,b) | | - * - - * x - * - * - * The incomplete beta integral is used, according to the - * formula - * - * P(x) = incbet( df2/2, df1/2, (df2/(df2 + df1*x) ). - * - * - * ACCURACY: - * - * Tested at random points (a,b,x) in the indicated intervals. - * x a,b Relative error: - * arithmetic domain domain # trials peak rms - * IEEE 0,1 1,100 100000 3.7e-14 5.9e-16 - * IEEE 1,5 1,100 100000 8.0e-15 1.6e-15 - * IEEE 0,1 1,10000 100000 1.8e-11 3.5e-13 - * IEEE 1,5 1,10000 100000 2.0e-11 3.0e-12 - * See also incbet.c. - * - * ERROR MESSAGES: - * - * message condition value returned - * fdtrc domain a<0, b<0, x<0 0.0 - * - */ - -/* fdtri() - * - * Inverse of F distribution - * - * - * - * SYNOPSIS: - * - * double df1, df2; - * double x, p, fdtri(); - * - * x = fdtri( df1, df2, p ); - * - * DESCRIPTION: - * - * Finds the F density argument x such that the integral - * from -infinity to x of the F density is equal to the - * given probability p. - * - * This is accomplished using the inverse beta integral - * function and the relations - * - * z = incbi( df2/2, df1/2, p ) - * x = df2 (1-z) / (df1 z). - * - * Note: the following relations hold for the inverse of - * the uncomplemented F distribution: - * - * z = incbi( df1/2, df2/2, p ) - * x = df2 z / (df1 (1-z)). - * - * ACCURACY: - * - * Tested at random points (a,b,p). - * - * a,b Relative error: - * arithmetic domain # trials peak rms - * For p between .001 and 1: - * IEEE 1,100 100000 8.3e-15 4.7e-16 - * IEEE 1,10000 100000 2.1e-11 1.4e-13 - * For p between 10^-6 and 10^-3: - * IEEE 1,100 50000 1.3e-12 8.4e-15 - * IEEE 1,10000 50000 3.0e-12 4.8e-14 - * See also fdtrc.c. - * - * ERROR MESSAGES: - * - * message condition value returned - * fdtri domain p <= 0 or p > 1 NaN - * v < 1 - * - */ - -/* - * Cephes Math Library Release 2.3: March, 1995 - * Copyright 1984, 1987, 1995 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "incbet.h" -#include "incbi.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE inline double fdtrc(double a, double b, double x) { - double w; - - if ((a <= 0.0) || (b <= 0.0) || (x < 0.0)) { - set_error("fdtrc", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - w = b / (b + a * x); - return incbet(0.5 * b, 0.5 * a, w); - } - - XSF_HOST_DEVICE inline double fdtr(double a, double b, double x) { - double w; - - if ((a <= 0.0) || (b <= 0.0) || (x < 0.0)) { - set_error("fdtr", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - w = a * x; - w = w / (b + w); - return incbet(0.5 * a, 0.5 * b, w); - } - - XSF_HOST_DEVICE inline double fdtri(double a, double b, double y) { - double w, x; - - if ((a <= 0.0) || (b <= 0.0) || (y <= 0.0) || (y > 1.0)) { - set_error("fdtri", SF_ERROR_DOMAIN, NULL); - return NAN; - } - y = 1.0 - y; - /* Compute probability for x = 0.5. */ - w = incbet(0.5 * b, 0.5 * a, 0.5); - /* If that is greater than y, then the solution w < .5. - * Otherwise, solve at 1-y to remove cancellation in (b - b*w). */ - if (w > y || y < 0.001) { - w = incbi(0.5 * b, 0.5 * a, y); - x = (b - b * w) / (a * w); - } else { - w = incbi(0.5 * a, 0.5 * b, 1.0 - y); - x = b * w / (a * (1.0 - w)); - } - return x; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/fresnl.h b/scipy/special/xsf/cephes/fresnl.h deleted file mode 100644 index 6ac27ffa884a..000000000000 --- a/scipy/special/xsf/cephes/fresnl.h +++ /dev/null @@ -1,191 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* fresnl.c - * - * Fresnel integral - * - * - * - * SYNOPSIS: - * - * double x, S, C; - * void fresnl(); - * - * fresnl( x, _&S, _&C ); - * - * - * DESCRIPTION: - * - * Evaluates the Fresnel integrals - * - * x - * - - * | | - * C(x) = | cos(pi/2 t**2) dt, - * | | - * - - * 0 - * - * x - * - - * | | - * S(x) = | sin(pi/2 t**2) dt. - * | | - * - - * 0 - * - * - * The integrals are evaluated by a power series for x < 1. - * For x >= 1 auxiliary functions f(x) and g(x) are employed - * such that - * - * C(x) = 0.5 + f(x) sin( pi/2 x**2 ) - g(x) cos( pi/2 x**2 ) - * S(x) = 0.5 - f(x) cos( pi/2 x**2 ) - g(x) sin( pi/2 x**2 ) - * - * - * - * ACCURACY: - * - * Relative error. - * - * Arithmetic function domain # trials peak rms - * IEEE S(x) 0, 10 10000 2.0e-15 3.2e-16 - * IEEE C(x) 0, 10 10000 1.8e-15 3.3e-16 - */ - -/* - * Cephes Math Library Release 2.1: January, 1989 - * Copyright 1984, 1987, 1989 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" - -#include "const.h" -#include "polevl.h" -#include "trig.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* S(x) for small x */ - constexpr double fresnl_sn[6] = { - -2.99181919401019853726E3, 7.08840045257738576863E5, -6.29741486205862506537E7, - 2.54890880573376359104E9, -4.42979518059697779103E10, 3.18016297876567817986E11, - }; - - constexpr double fresnl_sd[6] = { - /* 1.00000000000000000000E0, */ - 2.81376268889994315696E2, 4.55847810806532581675E4, 5.17343888770096400730E6, - 4.19320245898111231129E8, 2.24411795645340920940E10, 6.07366389490084639049E11, - }; - - /* C(x) for small x */ - constexpr double fresnl_cn[6] = { - -4.98843114573573548651E-8, 9.50428062829859605134E-6, -6.45191435683965050962E-4, - 1.88843319396703850064E-2, -2.05525900955013891793E-1, 9.99999999999999998822E-1, - }; - - constexpr double fresnl_cd[7] = { - 3.99982968972495980367E-12, 9.15439215774657478799E-10, 1.25001862479598821474E-7, - 1.22262789024179030997E-5, 8.68029542941784300606E-4, 4.12142090722199792936E-2, - 1.00000000000000000118E0, - }; - - /* Auxiliary function f(x) */ - constexpr double fresnl_fn[10] = { - 4.21543555043677546506E-1, 1.43407919780758885261E-1, 1.15220955073585758835E-2, - 3.45017939782574027900E-4, 4.63613749287867322088E-6, 3.05568983790257605827E-8, - 1.02304514164907233465E-10, 1.72010743268161828879E-13, 1.34283276233062758925E-16, - 3.76329711269987889006E-20, - }; - - constexpr double fresnl_fd[10] = { - /* 1.00000000000000000000E0, */ - 7.51586398353378947175E-1, 1.16888925859191382142E-1, 6.44051526508858611005E-3, - 1.55934409164153020873E-4, 1.84627567348930545870E-6, 1.12699224763999035261E-8, - 3.60140029589371370404E-11, 5.88754533621578410010E-14, 4.52001434074129701496E-17, - 1.25443237090011264384E-20, - }; - - /* Auxiliary function g(x) */ - constexpr double fresnl_gn[11] = { - 5.04442073643383265887E-1, 1.97102833525523411709E-1, 1.87648584092575249293E-2, - 6.84079380915393090172E-4, 1.15138826111884280931E-5, 9.82852443688422223854E-8, - 4.45344415861750144738E-10, 1.08268041139020870318E-12, 1.37555460633261799868E-15, - 8.36354435630677421531E-19, 1.86958710162783235106E-22, - }; - - constexpr double fresnl_gd[11] = { - /* 1.00000000000000000000E0, */ - 1.47495759925128324529E0, 3.37748989120019970451E-1, 2.53603741420338795122E-2, - 8.14679107184306179049E-4, 1.27545075667729118702E-5, 1.04314589657571990585E-7, - 4.60680728146520428211E-10, 1.10273215066240270757E-12, 1.38796531259578871258E-15, - 8.39158816283118707363E-19, 1.86958710162783236342E-22, - }; - - } // namespace detail - - XSF_HOST_DEVICE inline int fresnl(double xxa, double *ssa, double *cca) { - double f, g, cc, ss, c, s, t, u; - double x, x2; - - if (std::isinf(xxa)) { - cc = 0.5; - ss = 0.5; - goto done; - } - - x = std::abs(xxa); - x2 = x * x; - if (x2 < 2.5625) { - t = x2 * x2; - ss = x * x2 * polevl(t, detail::fresnl_sn, 5) / p1evl(t, detail::fresnl_sd, 6); - cc = x * polevl(t, detail::fresnl_cn, 5) / polevl(t, detail::fresnl_cd, 6); - goto done; - } - - if (x > 36974.0) { - /* - * http://functions.wolfram.com/GammaBetaErf/FresnelC/06/02/ - * http://functions.wolfram.com/GammaBetaErf/FresnelS/06/02/ - */ - cc = 0.5 + 1 / (M_PI * x) * sinpi(x * x / 2); - ss = 0.5 - 1 / (M_PI * x) * cospi(x * x / 2); - goto done; - } - - /* Asymptotic power series auxiliary functions - * for large argument - */ - x2 = x * x; - t = M_PI * x2; - u = 1.0 / (t * t); - t = 1.0 / t; - f = 1.0 - u * polevl(u, detail::fresnl_fn, 9) / p1evl(u, detail::fresnl_fd, 10); - g = t * polevl(u, detail::fresnl_gn, 10) / p1evl(u, detail::fresnl_gd, 11); - - c = cospi(x2 / 2); - s = sinpi(x2 / 2); - t = M_PI * x; - cc = 0.5 + (f * s - g * c) / t; - ss = 0.5 - (f * c + g * s) / t; - - done: - if (xxa < 0.0) { - cc = -cc; - ss = -ss; - } - - *cca = cc; - *ssa = ss; - return (0); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/gamma.h b/scipy/special/xsf/cephes/gamma.h deleted file mode 100644 index 1ede1571a67e..000000000000 --- a/scipy/special/xsf/cephes/gamma.h +++ /dev/null @@ -1,398 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* - * Gamma function - * - * - * - * SYNOPSIS: - * - * double x, y, Gamma(); - * - * y = Gamma( x ); - * - * - * - * DESCRIPTION: - * - * Returns Gamma function of the argument. The result is - * correctly signed. - * - * Arguments |x| <= 34 are reduced by recurrence and the function - * approximated by a rational function of degree 6/7 in the - * interval (2,3). Large arguments are handled by Stirling's - * formula. Large negative arguments are made positive using - * a reflection formula. - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE -170,-33 20000 2.3e-15 3.3e-16 - * IEEE -33, 33 20000 9.4e-16 2.2e-16 - * IEEE 33, 171.6 20000 2.3e-15 3.2e-16 - * - * Error for arguments outside the test range will be larger - * owing to error amplification by the exponential function. - * - */ - -/* lgam() - * - * Natural logarithm of Gamma function - * - * - * - * SYNOPSIS: - * - * double x, y, lgam(); - * - * y = lgam( x ); - * - * - * - * DESCRIPTION: - * - * Returns the base e (2.718...) logarithm of the absolute - * value of the Gamma function of the argument. - * - * For arguments greater than 13, the logarithm of the Gamma - * function is approximated by the logarithmic version of - * Stirling's formula using a polynomial approximation of - * degree 4. Arguments between -33 and +33 are reduced by - * recurrence to the interval [2,3] of a rational approximation. - * The cosecant reflection formula is employed for arguments - * less than -33. - * - * Arguments greater than MAXLGM return INFINITY and an error - * message. MAXLGM = 2.556348e305 for IEEE arithmetic. - * - * - * - * ACCURACY: - * - * - * arithmetic domain # trials peak rms - * IEEE 0, 3 28000 5.4e-16 1.1e-16 - * IEEE 2.718, 2.556e305 40000 3.5e-16 8.3e-17 - * The error criterion was relative when the function magnitude - * was greater than one but absolute when it was less than one. - * - * The following test used the relative error criterion, though - * at certain points the relative error could be much higher than - * indicated. - * IEEE -200, -4 10000 4.8e-16 1.3e-16 - * - */ - -/* - * Cephes Math Library Release 2.2: July, 1992 - * Copyright 1984, 1987, 1989, 1992 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "../error.h" -#include "const.h" -#include "polevl.h" -#include "trig.h" - -namespace xsf { -namespace cephes { - namespace detail { - constexpr double gamma_P[] = {1.60119522476751861407E-4, 1.19135147006586384913E-3, 1.04213797561761569935E-2, - 4.76367800457137231464E-2, 2.07448227648435975150E-1, 4.94214826801497100753E-1, - 9.99999999999999996796E-1}; - - constexpr double gamma_Q[] = {-2.31581873324120129819E-5, 5.39605580493303397842E-4, -4.45641913851797240494E-3, - 1.18139785222060435552E-2, 3.58236398605498653373E-2, -2.34591795718243348568E-1, - 7.14304917030273074085E-2, 1.00000000000000000320E0}; - - /* Stirling's formula for the Gamma function */ - constexpr double gamma_STIR[5] = { - 7.87311395793093628397E-4, -2.29549961613378126380E-4, -2.68132617805781232825E-3, - 3.47222221605458667310E-3, 8.33333333333482257126E-2, - }; - - constexpr double MAXSTIR = 143.01608; - - /* Gamma function computed by Stirling's formula. - * The polynomial STIR is valid for 33 <= x <= 172. - */ - XSF_HOST_DEVICE inline double stirf(double x) { - double y, w, v; - - if (x >= MAXGAM) { - return (std::numeric_limits::infinity()); - } - w = 1.0 / x; - w = 1.0 + w * xsf::cephes::polevl(w, gamma_STIR, 4); - y = std::exp(x); - if (x > MAXSTIR) { /* Avoid overflow in pow() */ - v = std::pow(x, 0.5 * x - 0.25); - y = v * (v / y); - } else { - y = std::pow(x, x - 0.5) / y; - } - y = SQRTPI * y * w; - return (y); - } - } // namespace detail - - XSF_HOST_DEVICE inline double Gamma(double x) { - double p, q, z; - int i; - int sgngam = 1; - - if (!std::isfinite(x)) { - if (x > 0) { - // gamma(+inf) = +inf - return x; - } - // gamma(NaN) and gamma(-inf) both should equal NaN. - return std::numeric_limits::quiet_NaN(); - } - - if (x == 0) { - /* For pole at zero, value depends on sign of zero. - * +inf when approaching from right, -inf when approaching - * from left. */ - return std::copysign(std::numeric_limits::infinity(), x); - } - - q = std::abs(x); - - if (q > 33.0) { - if (x < 0.0) { - p = std::floor(q); - if (p == q) { - // x is a negative integer. This is a pole. - set_error("Gamma", SF_ERROR_SINGULAR, NULL); - return (std::numeric_limits::quiet_NaN()); - } - i = p; - if ((i & 1) == 0) { - sgngam = -1; - } - z = q - p; - if (z > 0.5) { - p += 1.0; - z = q - p; - } - z = q * sinpi(z); - if (z == 0.0) { - return (sgngam * std::numeric_limits::infinity()); - } - z = std::abs(z); - z = M_PI / (z * detail::stirf(q)); - } else { - z = detail::stirf(x); - } - return (sgngam * z); - } - - z = 1.0; - while (x >= 3.0) { - x -= 1.0; - z *= x; - } - - while (x < 0.0) { - if (x > -1.E-9) { - goto small; - } - z /= x; - x += 1.0; - } - - while (x < 2.0) { - if (x < 1.e-9) { - goto small; - } - z /= x; - x += 1.0; - } - - if (x == 2.0) { - return (z); - } - - x -= 2.0; - p = polevl(x, detail::gamma_P, 6); - q = polevl(x, detail::gamma_Q, 7); - return (z * p / q); - - small: - if (x == 0.0) { - /* For this to have happened, x must have started as a negative integer. */ - set_error("Gamma", SF_ERROR_SINGULAR, NULL); - return (std::numeric_limits::quiet_NaN()); - } else - return (z / ((1.0 + 0.5772156649015329 * x) * x)); - } - - namespace detail { - /* A[]: Stirling's formula expansion of log Gamma - * B[], C[]: log Gamma function between 2 and 3 - */ - constexpr double gamma_A[] = {8.11614167470508450300E-4, -5.95061904284301438324E-4, 7.93650340457716943945E-4, - -2.77777777730099687205E-3, 8.33333333333331927722E-2}; - - constexpr double gamma_B[] = {-1.37825152569120859100E3, -3.88016315134637840924E4, -3.31612992738871184744E5, - -1.16237097492762307383E6, -1.72173700820839662146E6, -8.53555664245765465627E5}; - - constexpr double gamma_C[] = { - /* 1.00000000000000000000E0, */ - -3.51815701436523470549E2, -1.70642106651881159223E4, -2.20528590553854454839E5, - -1.13933444367982507207E6, -2.53252307177582951285E6, -2.01889141433532773231E6}; - - /* log( sqrt( 2*pi ) ) */ - constexpr double LS2PI = 0.91893853320467274178; - - constexpr double MAXLGM = 2.556348e305; - - /* Disable optimizations for this function on 32 bit systems when compiling with GCC. - * We've found that enabling optimizations can result in degraded precision - * for this asymptotic approximation in that case. */ -#if defined(__GNUC__) && defined(__i386__) -#pragma GCC push_options -#pragma GCC optimize("00") -#endif - XSF_HOST_DEVICE inline double lgam_large_x(double x) { - double q = (x - 0.5) * std::log(x) - x + LS2PI; - if (x > 1.0e8) { - return (q); - } - double p = 1.0 / (x * x); - p = ((7.9365079365079365079365e-4 * p - 2.7777777777777777777778e-3) * p + 0.0833333333333333333333) / x; - return q + p; - } -#if defined(__GNUC__) && defined(__i386__) -#pragma GCC pop_options -#endif - - XSF_HOST_DEVICE inline double lgam_sgn(double x, int *sign) { - double p, q, u, w, z; - int i; - - *sign = 1; - - if (!std::isfinite(x)) { - return x; - } - - if (x < -34.0) { - q = -x; - w = lgam_sgn(q, sign); - p = std::floor(q); - if (p == q) { - lgsing: - set_error("lgam", SF_ERROR_SINGULAR, NULL); - return (std::numeric_limits::infinity()); - } - i = p; - if ((i & 1) == 0) { - *sign = -1; - } else { - *sign = 1; - } - z = q - p; - if (z > 0.5) { - p += 1.0; - z = p - q; - } - z = q * sinpi(z); - if (z == 0.0) { - goto lgsing; - } - /* z = log(M_PI) - log( z ) - w; */ - z = LOGPI - std::log(z) - w; - return (z); - } - - if (x < 13.0) { - z = 1.0; - p = 0.0; - u = x; - while (u >= 3.0) { - p -= 1.0; - u = x + p; - z *= u; - } - while (u < 2.0) { - if (u == 0.0) { - goto lgsing; - } - z /= u; - p += 1.0; - u = x + p; - } - if (z < 0.0) { - *sign = -1; - z = -z; - } else { - *sign = 1; - } - if (u == 2.0) { - return (std::log(z)); - } - p -= 2.0; - x = x + p; - p = x * polevl(x, gamma_B, 5) / p1evl(x, gamma_C, 6); - return (std::log(z) + p); - } - - if (x > MAXLGM) { - return (*sign * std::numeric_limits::infinity()); - } - - if (x >= 1000.0) { - return lgam_large_x(x); - } - - q = (x - 0.5) * std::log(x) - x + LS2PI; - p = 1.0 / (x * x); - return q + polevl(p, gamma_A, 4) / x; - } - } // namespace detail - - /* Logarithm of Gamma function */ - XSF_HOST_DEVICE inline double lgam(double x) { - int sign; - return detail::lgam_sgn(x, &sign); - } - - /* Sign of the Gamma function */ - XSF_HOST_DEVICE inline double gammasgn(double x) { - double fx; - - if (std::isnan(x)) { - return x; - } - if (x > 0) { - return 1.0; - } - if (x == 0) { - return std::copysign(1.0, x); - } - if (std::isinf(x)) { - // x > 0 case handled, so x must be negative infinity. - return std::numeric_limits::quiet_NaN(); - } - fx = std::floor(x); - if (x - fx == 0.0) { - return std::numeric_limits::quiet_NaN(); - } - // sign of gamma for x in (-n, -n+1) for positive integer n is (-1)^n. - if (static_cast(fx) % 2) { - return -1.0; - } - return 1.0; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/gdtr.h b/scipy/special/xsf/cephes/gdtr.h deleted file mode 100644 index 5123e1690443..000000000000 --- a/scipy/special/xsf/cephes/gdtr.h +++ /dev/null @@ -1,140 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* gdtr.c - * - * Gamma distribution function - * - * - * - * SYNOPSIS: - * - * double a, b, x, y, gdtr(); - * - * y = gdtr( a, b, x ); - * - * - * - * DESCRIPTION: - * - * Returns the integral from zero to x of the Gamma probability - * density function: - * - * - * x - * b - - * a | | b-1 -at - * y = ----- | t e dt - * - | | - * | (b) - - * 0 - * - * The incomplete Gamma integral is used, according to the - * relation - * - * y = igam( b, ax ). - * - * - * ACCURACY: - * - * See igam(). - * - * ERROR MESSAGES: - * - * message condition value returned - * gdtr domain x < 0 0.0 - * - */ -/* gdtrc.c - * - * Complemented Gamma distribution function - * - * - * - * SYNOPSIS: - * - * double a, b, x, y, gdtrc(); - * - * y = gdtrc( a, b, x ); - * - * - * - * DESCRIPTION: - * - * Returns the integral from x to infinity of the Gamma - * probability density function: - * - * - * inf. - * b - - * a | | b-1 -at - * y = ----- | t e dt - * - | | - * | (b) - - * x - * - * The incomplete Gamma integral is used, according to the - * relation - * - * y = igamc( b, ax ). - * - * - * ACCURACY: - * - * See igamc(). - * - * ERROR MESSAGES: - * - * message condition value returned - * gdtrc domain x < 0 0.0 - * - */ - -/* gdtr() */ - -/* - * Cephes Math Library Release 2.3: March,1995 - * Copyright 1984, 1987, 1995 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "igam.h" -#include "igami.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE inline double gdtr(double a, double b, double x) { - - if (x < 0.0) { - sf_error("gdtr", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - return (igam(b, a * x)); - } - - XSF_HOST_DEVICE inline double gdtrc(double a, double b, double x) { - - if (x < 0.0) { - set_error("gdtrc", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - return (igamc(b, a * x)); - } - - XSF_HOST_DEVICE inline double gdtri(double a, double b, double y) { - - if ((y < 0.0) || (y > 1.0) || (a <= 0.0) || (b < 0.0)) { - sf_error("gdtri", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - - return (igamci(b, 1.0 - y) / a); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/hyp2f1.h b/scipy/special/xsf/cephes/hyp2f1.h deleted file mode 100644 index f9ec54bb2032..000000000000 --- a/scipy/special/xsf/cephes/hyp2f1.h +++ /dev/null @@ -1,596 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* hyp2f1.c - * - * Gauss hypergeometric function F - * 2 1 - * - * - * SYNOPSIS: - * - * double a, b, c, x, y, hyp2f1(); - * - * y = hyp2f1( a, b, c, x ); - * - * - * DESCRIPTION: - * - * - * hyp2f1( a, b, c, x ) = F ( a, b; c; x ) - * 2 1 - * - * inf. - * - a(a+1)...(a+k) b(b+1)...(b+k) k+1 - * = 1 + > ----------------------------- x . - * - c(c+1)...(c+k) (k+1)! - * k = 0 - * - * Cases addressed are - * Tests and escapes for negative integer a, b, or c - * Linear transformation if c - a or c - b negative integer - * Special case c = a or c = b - * Linear transformation for x near +1 - * Transformation for x < -0.5 - * Psi function expansion if x > 0.5 and c - a - b integer - * Conditionally, a recurrence on c to make c-a-b > 0 - * - * x < -1 AMS 15.3.7 transformation applied (Travis Oliphant) - * valid for b,a,c,(b-a) != integer and (c-a),(c-b) != negative integer - * - * x >= 1 is rejected (unless special cases are present) - * - * The parameters a, b, c are considered to be integer - * valued if they are within 1.0e-14 of the nearest integer - * (1.0e-13 for IEEE arithmetic). - * - * ACCURACY: - * - * - * Relative error (-1 < x < 1): - * arithmetic domain # trials peak rms - * IEEE -1,7 230000 1.2e-11 5.2e-14 - * - * Several special cases also tested with a, b, c in - * the range -7 to 7. - * - * ERROR MESSAGES: - * - * A "partial loss of precision" message is printed if - * the internally estimated relative error exceeds 1^-12. - * A "singularity" message is printed on overflow or - * in cases not addressed (such as x < -1). - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 1992, 2000 by Stephen L. Moshier - */ - -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "gamma.h" -#include "rgamma.h" -#include "psi.h" - -namespace xsf { -namespace cephes { - - namespace detail { - constexpr double hyp2f1_EPS = 1.0e-13; - - constexpr double hyp2f1_ETHRESH = 1.0e-12; - constexpr std::uint64_t hyp2f1_MAXITER = 10000; - - /* hys2f1 and hyp2f1ra depend on each other, so we need this prototype */ - XSF_HOST_DEVICE double hyp2f1ra(double a, double b, double c, double x, double *loss); - - /* Defining power series expansion of Gauss hypergeometric function */ - /* The `loss` parameter estimates loss of significance */ - XSF_HOST_DEVICE double hys2f1(double a, double b, double c, double x, double *loss) { - double f, g, h, k, m, s, u, umax; - std::uint64_t i; - int ib, intflag = 0; - - if (std::abs(b) > std::abs(a)) { - /* Ensure that |a| > |b| ... */ - f = b; - b = a; - a = f; - } - - ib = std::round(b); - - if (std::abs(b - ib) < hyp2f1_EPS && ib <= 0 && std::abs(b) < std::abs(a)) { - /* .. except when `b` is a smaller negative integer */ - f = b; - b = a; - a = f; - intflag = 1; - } - - if ((std::abs(a) > std::abs(c) + 1 || intflag) && std::abs(c - a) > 2 && std::abs(a) > 2) { - /* |a| >> |c| implies that large cancellation error is to be expected. - * - * We try to reduce it with the recurrence relations - */ - return hyp2f1ra(a, b, c, x, loss); - } - - i = 0; - umax = 0.0; - f = a; - g = b; - h = c; - s = 1.0; - u = 1.0; - k = 0.0; - do { - if (std::abs(h) < hyp2f1_EPS) { - *loss = 1.0; - return std::numeric_limits::infinity(); - } - m = k + 1.0; - u = u * ((f + k) * (g + k) * x / ((h + k) * m)); - s += u; - k = std::abs(u); /* remember largest term summed */ - if (k > umax) - umax = k; - k = m; - if (++i > hyp2f1_MAXITER) { /* should never happen */ - *loss = 1.0; - return (s); - } - } while (s == 0 || std::abs(u / s) > MACHEP); - - /* return estimated relative error */ - *loss = (MACHEP * umax) / fabs(s) + (MACHEP * i); - - return (s); - } - - /* Apply transformations for |x| near 1 then call the power series */ - XSF_HOST_DEVICE double hyt2f1(double a, double b, double c, double x, double *loss) { - double p, q, r, s, t, y, w, d, err, err1; - double ax, id, d1, d2, e, y1; - int i, aid, sign; - - int ia, ib, neg_int_a = 0, neg_int_b = 0; - - ia = std::round(a); - ib = std::round(b); - - if (a <= 0 && std::abs(a - ia) < hyp2f1_EPS) { /* a is a negative integer */ - neg_int_a = 1; - } - - if (b <= 0 && std::abs(b - ib) < hyp2f1_EPS) { /* b is a negative integer */ - neg_int_b = 1; - } - - err = 0.0; - s = 1.0 - x; - if (x < -0.5 && !(neg_int_a || neg_int_b)) { - if (b > a) - y = std::pow(s, -a) * hys2f1(a, c - b, c, -x / s, &err); - - else - y = std::pow(s, -b) * hys2f1(c - a, b, c, -x / s, &err); - - goto done; - } - - d = c - a - b; - id = std::round(d); /* nearest integer to d */ - - if (x > 0.9 && !(neg_int_a || neg_int_b)) { - if (std::abs(d - id) > MACHEP) { - int sgngam; - - /* test for integer c-a-b */ - /* Try the power series first */ - y = hys2f1(a, b, c, x, &err); - if (err < hyp2f1_ETHRESH) { - goto done; - } - /* If power series fails, then apply AMS55 #15.3.6 */ - q = hys2f1(a, b, 1.0 - d, s, &err); - sign = 1; - w = lgam_sgn(d, &sgngam); - sign *= sgngam; - w -= lgam_sgn(c - a, &sgngam); - sign *= sgngam; - w -= lgam_sgn(c - b, &sgngam); - sign *= sgngam; - q *= sign * std::exp(w); - r = std::pow(s, d) * hys2f1(c - a, c - b, d + 1.0, s, &err1); - sign = 1; - w = lgam_sgn(-d, &sgngam); - sign *= sgngam; - w -= lgam_sgn(a, &sgngam); - sign *= sgngam; - w -= lgam_sgn(b, &sgngam); - sign *= sgngam; - r *= sign * std::exp(w); - y = q + r; - - q = std::abs(q); /* estimate cancellation error */ - r = std::abs(r); - if (q > r) { - r = q; - } - err += err1 + (MACHEP * r) / y; - - y *= xsf::cephes::Gamma(c); - goto done; - } else { - /* Psi function expansion, AMS55 #15.3.10, #15.3.11, #15.3.12 - * - * Although AMS55 does not explicitly state it, this expansion fails - * for negative integer a or b, since the psi and Gamma functions - * involved have poles. - */ - - if (id >= 0.0) { - e = d; - d1 = d; - d2 = 0.0; - aid = id; - } else { - e = -d; - d1 = 0.0; - d2 = d; - aid = -id; - } - - ax = std::log(s); - - /* sum for t = 0 */ - y = xsf::cephes::psi(1.0) + xsf::cephes::psi(1.0 + e) - xsf::cephes::psi(a + d1) - - xsf::cephes::psi(b + d1) - ax; - y *= xsf::cephes::rgamma(e + 1.0); - - p = (a + d1) * (b + d1) * s * xsf::cephes::rgamma(e + 2.0); /* Poch for t=1 */ - t = 1.0; - do { - r = xsf::cephes::psi(1.0 + t) + xsf::cephes::psi(1.0 + t + e) - - xsf::cephes::psi(a + t + d1) - xsf::cephes::psi(b + t + d1) - ax; - q = p * r; - y += q; - p *= s * (a + t + d1) / (t + 1.0); - p *= (b + t + d1) / (t + 1.0 + e); - t += 1.0; - if (t > hyp2f1_MAXITER) { /* should never happen */ - set_error("hyp2f1", SF_ERROR_SLOW, NULL); - *loss = 1.0; - return std::numeric_limits::quiet_NaN(); - } - } while (y == 0 || std::abs(q / y) > hyp2f1_EPS); - - if (id == 0.0) { - y *= xsf::cephes::Gamma(c) / (xsf::cephes::Gamma(a) * xsf::cephes::Gamma(b)); - goto psidon; - } - - y1 = 1.0; - - if (aid == 1) - goto nosum; - - t = 0.0; - p = 1.0; - for (i = 1; i < aid; i++) { - r = 1.0 - e + t; - p *= s * (a + t + d2) * (b + t + d2) / r; - t += 1.0; - p /= t; - y1 += p; - } - nosum: - p = xsf::cephes::Gamma(c); - y1 *= xsf::cephes::Gamma(e) * p * - (xsf::cephes::rgamma(a + d1) * xsf::cephes::rgamma(b + d1)); - - y *= p * (xsf::cephes::rgamma(a + d2) * xsf::cephes::rgamma(b + d2)); - if ((aid & 1) != 0) - y = -y; - - q = std::pow(s, id); /* s to the id power */ - if (id > 0.0) - y *= q; - else - y1 *= q; - - y += y1; - psidon: - goto done; - } - } - - /* Use defining power series if no special cases */ - y = hys2f1(a, b, c, x, &err); - - done: - *loss = err; - return (y); - } - - /* - 15.4.2 Abramowitz & Stegun. - */ - XSF_HOST_DEVICE double hyp2f1_neg_c_equal_bc(double a, double b, double x) { - double k; - double collector = 1; - double sum = 1; - double collector_max = 1; - - if (!(std::abs(b) < 1e5)) { - return std::numeric_limits::quiet_NaN(); - } - - for (k = 1; k <= -b; k++) { - collector *= (a + k - 1) * x / k; - collector_max = std::fmax(std::abs(collector), collector_max); - sum += collector; - } - - if (1e-16 * (1 + collector_max / std::abs(sum)) > 1e-7) { - return std::numeric_limits::quiet_NaN(); - } - - return sum; - } - - /* - * Evaluate hypergeometric function by two-term recurrence in `a`. - * - * This avoids some of the loss of precision in the strongly alternating - * hypergeometric series, and can be used to reduce the `a` and `b` parameters - * to smaller values. - * - * AMS55 #15.2.10 - */ - XSF_HOST_DEVICE double hyp2f1ra(double a, double b, double c, double x, double *loss) { - double f2, f1, f0; - int n; - double t, err, da; - - /* Don't cross c or zero */ - if ((c < 0 && a <= c) || (c >= 0 && a >= c)) { - da = std::round(a - c); - } else { - da = std::round(a); - } - t = a - da; - - *loss = 0; - - XSF_ASSERT(da != 0); - - if (std::abs(da) > hyp2f1_MAXITER) { - /* Too expensive to compute this value, so give up */ - set_error("hyp2f1", SF_ERROR_NO_RESULT, NULL); - *loss = 1.0; - return std::numeric_limits::quiet_NaN(); - } - - if (da < 0) { - /* Recurse down */ - f2 = 0; - f1 = hys2f1(t, b, c, x, &err); - *loss += err; - f0 = hys2f1(t - 1, b, c, x, &err); - *loss += err; - t -= 1; - for (n = 1; n < -da; ++n) { - f2 = f1; - f1 = f0; - f0 = -(2 * t - c - t * x + b * x) / (c - t) * f1 - t * (x - 1) / (c - t) * f2; - t -= 1; - } - } else { - /* Recurse up */ - f2 = 0; - f1 = hys2f1(t, b, c, x, &err); - *loss += err; - f0 = hys2f1(t + 1, b, c, x, &err); - *loss += err; - t += 1; - for (n = 1; n < da; ++n) { - f2 = f1; - f1 = f0; - f0 = -((2 * t - c - t * x + b * x) * f1 + (c - t) * f2) / (t * (x - 1)); - t += 1; - } - } - - return f0; - } - } // namespace detail - - XSF_HOST_DEVICE double hyp2f1(double a, double b, double c, double x) { - double d, d1, d2, e; - double p, q, r, s, y, ax; - double ia, ib, ic, id, err; - double t1; - int i, aid; - int neg_int_a = 0, neg_int_b = 0; - int neg_int_ca_or_cb = 0; - - err = 0.0; - ax = std::abs(x); - s = 1.0 - x; - ia = std::round(a); /* nearest integer to a */ - ib = std::round(b); - - if (x == 0.0) { - return 1.0; - } - - d = c - a - b; - id = std::round(d); - - if ((a == 0 || b == 0) && c != 0) { - return 1.0; - } - - if (a <= 0 && std::abs(a - ia) < detail::hyp2f1_EPS) { /* a is a negative integer */ - neg_int_a = 1; - } - - if (b <= 0 && std::abs(b - ib) < detail::hyp2f1_EPS) { /* b is a negative integer */ - neg_int_b = 1; - } - - if (d <= -1 && !(std::abs(d - id) > detail::hyp2f1_EPS && s < 0) && !(neg_int_a || neg_int_b)) { - return std::pow(s, d) * hyp2f1(c - a, c - b, c, x); - } - if (d <= 0 && x == 1 && !(neg_int_a || neg_int_b)) - goto hypdiv; - - if (ax < 1.0 || x == -1.0) { - /* 2F1(a,b;b;x) = (1-x)**(-a) */ - if (std::abs(b - c) < detail::hyp2f1_EPS) { /* b = c */ - if (neg_int_b) { - y = detail::hyp2f1_neg_c_equal_bc(a, b, x); - } else { - y = std::pow(s, -a); /* s to the -a power */ - } - goto hypdon; - } - if (std::abs(a - c) < detail::hyp2f1_EPS) { /* a = c */ - y = std::pow(s, -b); /* s to the -b power */ - goto hypdon; - } - } - - if (c <= 0.0) { - ic = std::round(c); /* nearest integer to c */ - if (std::abs(c - ic) < detail::hyp2f1_EPS) { /* c is a negative integer */ - /* check if termination before explosion */ - if (neg_int_a && (ia > ic)) - goto hypok; - if (neg_int_b && (ib > ic)) - goto hypok; - goto hypdiv; - } - } - - if (neg_int_a || neg_int_b) /* function is a polynomial */ - goto hypok; - - t1 = std::abs(b - a); - if (x < -2.0 && std::abs(t1 - round(t1)) > detail::hyp2f1_EPS) { - /* This transform has a pole for b-a integer, and - * may produce large cancellation errors for |1/x| close 1 - */ - p = hyp2f1(a, 1 - c + a, 1 - b + a, 1.0 / x); - q = hyp2f1(b, 1 - c + b, 1 - a + b, 1.0 / x); - p *= std::pow(-x, -a); - q *= std::pow(-x, -b); - t1 = Gamma(c); - s = t1 * Gamma(b - a) * (rgamma(b) * rgamma(c - a)); - y = t1 * Gamma(a - b) * (rgamma(a) * rgamma(c - b)); - return s * p + y * q; - } else if (x < -1.0) { - if (std::abs(a) < std::abs(b)) { - return std::pow(s, -a) * hyp2f1(a, c - b, c, x / (x - 1)); - } else { - return std::pow(s, -b) * hyp2f1(b, c - a, c, x / (x - 1)); - } - } - - if (ax > 1.0) /* series diverges */ - goto hypdiv; - - p = c - a; - ia = std::round(p); /* nearest integer to c-a */ - if ((ia <= 0.0) && (std::abs(p - ia) < detail::hyp2f1_EPS)) /* negative int c - a */ - neg_int_ca_or_cb = 1; - - r = c - b; - ib = std::round(r); /* nearest integer to c-b */ - if ((ib <= 0.0) && (std::abs(r - ib) < detail::hyp2f1_EPS)) /* negative int c - b */ - neg_int_ca_or_cb = 1; - - id = std::round(d); /* nearest integer to d */ - q = std::abs(d - id); - - /* Thanks to Christian Burger - * for reporting a bug here. */ - if (std::abs(ax - 1.0) < detail::hyp2f1_EPS) { /* |x| == 1.0 */ - if (x > 0.0) { - if (neg_int_ca_or_cb) { - if (d >= 0.0) - goto hypf; - else - goto hypdiv; - } - if (d <= 0.0) - goto hypdiv; - y = Gamma(c) * Gamma(d) * (rgamma(p) * rgamma(r)); - goto hypdon; - } - if (d <= -1.0) - goto hypdiv; - } - - /* Conditionally make d > 0 by recurrence on c - * AMS55 #15.2.27 - */ - if (d < 0.0) { - /* Try the power series first */ - y = detail::hyt2f1(a, b, c, x, &err); - if (err < detail::hyp2f1_ETHRESH) - goto hypdon; - /* Apply the recurrence if power series fails */ - err = 0.0; - aid = 2 - id; - e = c + aid; - d2 = hyp2f1(a, b, e, x); - d1 = hyp2f1(a, b, e + 1.0, x); - q = a + b + 1.0; - for (i = 0; i < aid; i++) { - r = e - 1.0; - y = (e * (r - (2.0 * e - q) * x) * d2 + (e - a) * (e - b) * x * d1) / (e * r * s); - e = r; - d1 = d2; - d2 = y; - } - goto hypdon; - } - - if (neg_int_ca_or_cb) { - goto hypf; /* negative integer c-a or c-b */ - } - - hypok: - y = detail::hyt2f1(a, b, c, x, &err); - - hypdon: - if (err > detail::hyp2f1_ETHRESH) { - set_error("hyp2f1", SF_ERROR_LOSS, NULL); - /* printf( "Estimated err = %.2e\n", err ); */ - } - return (y); - - /* The transformation for c-a or c-b negative integer - * AMS55 #15.3.3 - */ - hypf: - y = std::pow(s, d) * detail::hys2f1(c - a, c - b, c, x, &err); - goto hypdon; - - /* The alarm exit */ - hypdiv: - set_error("hyp2f1", SF_ERROR_OVERFLOW, NULL); - return std::numeric_limits::infinity(); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/hyperg.h b/scipy/special/xsf/cephes/hyperg.h deleted file mode 100644 index 18ebcff69ba4..000000000000 --- a/scipy/special/xsf/cephes/hyperg.h +++ /dev/null @@ -1,361 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* hyperg.c - * - * Confluent hypergeometric function - * - * - * - * SYNOPSIS: - * - * double a, b, x, y, hyperg(); - * - * y = hyperg( a, b, x ); - * - * - * - * DESCRIPTION: - * - * Computes the confluent hypergeometric function - * - * 1 2 - * a x a(a+1) x - * F ( a,b;x ) = 1 + ---- + --------- + ... - * 1 1 b 1! b(b+1) 2! - * - * Many higher transcendental functions are special cases of - * this power series. - * - * As is evident from the formula, b must not be a negative - * integer or zero unless a is an integer with 0 >= a > b. - * - * The routine attempts both a direct summation of the series - * and an asymptotic expansion. In each case error due to - * roundoff, cancellation, and nonconvergence is estimated. - * The result with smaller estimated error is returned. - * - * - * - * ACCURACY: - * - * Tested at random points (a, b, x), all three variables - * ranging from 0 to 30. - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,30 30000 1.8e-14 1.1e-15 - * - * Larger errors can be observed when b is near a negative - * integer or zero. Certain combinations of arguments yield - * serious cancellation error in the power series summation - * and also are not in the region of near convergence of the - * asymptotic series. An error message is printed if the - * self-estimated relative error is greater than 1.0e-12. - * - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 1988, 2000 by Stephen L. Moshier - */ - -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "gamma.h" -#include "rgamma.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* the `type` parameter determines what converging factor to use */ - XSF_HOST_DEVICE inline double hyp2f0(double a, double b, double x, int type, double *err) { - double a0, alast, t, tlast, maxt; - double n, an, bn, u, sum, temp; - - an = a; - bn = b; - a0 = 1.0e0; - alast = 1.0e0; - sum = 0.0; - n = 1.0e0; - t = 1.0e0; - tlast = 1.0e9; - maxt = 0.0; - - do { - if (an == 0) - goto pdone; - if (bn == 0) - goto pdone; - - u = an * (bn * x / n); - - /* check for blowup */ - temp = std::abs(u); - if ((temp > 1.0) && (maxt > (std::numeric_limits::max() / temp))) - goto error; - - a0 *= u; - t = std::abs(a0); - - /* terminating condition for asymptotic series: - * the series is divergent (if a or b is not a negative integer), - * but its leading part can be used as an asymptotic expansion - */ - if (t > tlast) - goto ndone; - - tlast = t; - sum += alast; /* the sum is one term behind */ - alast = a0; - - if (n > 200) - goto ndone; - - an += 1.0e0; - bn += 1.0e0; - n += 1.0e0; - if (t > maxt) - maxt = t; - } while (t > MACHEP); - - pdone: /* series converged! */ - - /* estimate error due to roundoff and cancellation */ - *err = std::abs(MACHEP * (n + maxt)); - - alast = a0; - goto done; - - ndone: /* series did not converge */ - - /* The following "Converging factors" are supposed to improve accuracy, - * but do not actually seem to accomplish very much. */ - - n -= 1.0; - x = 1.0 / x; - - switch (type) { /* "type" given as subroutine argument */ - case 1: - alast *= (0.5 + (0.125 + 0.25 * b - 0.5 * a + 0.25 * x - 0.25 * n) / x); - break; - - case 2: - alast *= 2.0 / 3.0 - b + 2.0 * a + x - n; - break; - - default:; - } - - /* estimate error due to roundoff, cancellation, and nonconvergence */ - *err = MACHEP * (n + maxt) + std::abs(a0); - - done: - sum += alast; - return (sum); - - /* series blew up: */ - error: - *err = std::numeric_limits::infinity(); - set_error("hyperg", SF_ERROR_NO_RESULT, NULL); - return (sum); - } - - /* asymptotic formula for hypergeometric function: - * - * ( -a - * -- ( |z| - * | (b) ( -------- 2f0( a, 1+a-b, -1/x ) - * ( -- - * ( | (b-a) - * - * - * x a-b ) - * e |x| ) - * + -------- 2f0( b-a, 1-a, 1/x ) ) - * -- ) - * | (a) ) - */ - - XSF_HOST_DEVICE inline double hy1f1a(double a, double b, double x, double *err) { - double h1, h2, t, u, temp, acanc, asum, err1, err2; - - if (x == 0) { - acanc = 1.0; - asum = std::numeric_limits::infinity(); - goto adone; - } - temp = std::log(std::abs(x)); - t = x + temp * (a - b); - u = -temp * a; - - if (b > 0) { - temp = xsf::cephes::lgam(b); - t += temp; - u += temp; - } - - h1 = hyp2f0(a, a - b + 1, -1.0 / x, 1, &err1); - - temp = std::exp(u) * xsf::cephes::rgamma(b - a); - h1 *= temp; - err1 *= temp; - - h2 = hyp2f0(b - a, 1.0 - a, 1.0 / x, 2, &err2); - - if (a < 0) - temp = std::exp(t) * xsf::cephes::rgamma(a); - else - temp = std::exp(t - xsf::cephes::lgam(a)); - - h2 *= temp; - err2 *= temp; - - if (x < 0.0) - asum = h1; - else - asum = h2; - - acanc = std::abs(err1) + std::abs(err2); - - if (b < 0) { - temp = xsf::cephes::Gamma(b); - asum *= temp; - acanc *= std::abs(temp); - } - - if (asum != 0.0) - acanc /= std::abs(asum); - - if (acanc != acanc) - /* nan */ - acanc = 1.0; - - if (std::isinf(asum)) - /* infinity */ - acanc = 0; - - acanc *= 30.0; /* fudge factor, since error of asymptotic formula - * often seems this much larger than advertised */ - adone: - *err = acanc; - return (asum); - } - - /* Power series summation for confluent hypergeometric function */ - XSF_HOST_DEVICE inline double hy1f1p(double a, double b, double x, double *err) { - double n, a0, sum, t, u, temp, maxn; - double an, bn, maxt; - double y, c, sumc; - - /* set up for power series summation */ - an = a; - bn = b; - a0 = 1.0; - sum = 1.0; - c = 0.0; - n = 1.0; - t = 1.0; - maxt = 0.0; - *err = 1.0; - - maxn = 200.0 + 2 * fabs(a) + 2 * fabs(b); - - while (t > MACHEP) { - if (bn == 0) { /* check bn first since if both */ - sf_error("hyperg", SF_ERROR_SINGULAR, NULL); - return (std::numeric_limits::infinity()); /* an and bn are zero it is */ - } - if (an == 0) /* a singularity */ - return (sum); - if (n > maxn) { - /* too many terms; take the last one as error estimate */ - c = std::abs(c) + std::abs(t) * 50.0; - goto pdone; - } - u = x * (an / (bn * n)); - - /* check for blowup */ - temp = std::abs(u); - if ((temp > 1.0) && (maxt > (std::numeric_limits::max() / temp))) { - *err = 1.0; /* blowup: estimate 100% error */ - return sum; - } - - a0 *= u; - - y = a0 - c; - sumc = sum + y; - c = (sumc - sum) - y; - sum = sumc; - - t = std::abs(a0); - - an += 1.0; - bn += 1.0; - n += 1.0; - } - - pdone: - - /* estimate error due to roundoff and cancellation */ - if (sum != 0.0) { - *err = std::abs(c / sum); - } else { - *err = std::abs(c); - } - - if (*err != *err) { - /* nan */ - *err = 1.0; - } - - return (sum); - } - - } // namespace detail - - XSF_HOST_DEVICE inline double hyperg(double a, double b, double x) { - double asum, psum, acanc, pcanc, temp; - - /* See if a Kummer transformation will help */ - temp = b - a; - if (std::abs(temp) < 0.001 * std::abs(a)) - return (exp(x) * hyperg(temp, b, -x)); - - /* Try power & asymptotic series, starting from the one that is likely OK */ - if (std::abs(x) < 10 + std::abs(a) + std::abs(b)) { - psum = detail::hy1f1p(a, b, x, &pcanc); - if (pcanc < 1.0e-15) - goto done; - asum = detail::hy1f1a(a, b, x, &acanc); - } else { - psum = detail::hy1f1a(a, b, x, &pcanc); - if (pcanc < 1.0e-15) - goto done; - asum = detail::hy1f1p(a, b, x, &acanc); - } - - /* Pick the result with less estimated error */ - - if (acanc < pcanc) { - pcanc = acanc; - psum = asum; - } - - done: - if (pcanc > 1.0e-12) - set_error("hyperg", SF_ERROR_LOSS, NULL); - - return (psum); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/i0.h b/scipy/special/xsf/cephes/i0.h deleted file mode 100644 index f61e7b12fd22..000000000000 --- a/scipy/special/xsf/cephes/i0.h +++ /dev/null @@ -1,149 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* i0.c - * - * Modified Bessel function of order zero - * - * - * - * SYNOPSIS: - * - * double x, y, i0(); - * - * y = i0( x ); - * - * - * - * DESCRIPTION: - * - * Returns modified Bessel function of order zero of the - * argument. - * - * The function is defined as i0(x) = j0( ix ). - * - * The range is partitioned into the two intervals [0,8] and - * (8, infinity). Chebyshev polynomial expansions are employed - * in each interval. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,30 30000 5.8e-16 1.4e-16 - * - */ -/* i0e.c - * - * Modified Bessel function of order zero, - * exponentially scaled - * - * - * - * SYNOPSIS: - * - * double x, y, i0e(); - * - * y = i0e( x ); - * - * - * - * DESCRIPTION: - * - * Returns exponentially scaled modified Bessel function - * of order zero of the argument. - * - * The function is defined as i0e(x) = exp(-|x|) j0( ix ). - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,30 30000 5.4e-16 1.2e-16 - * See i0(). - * - */ - -/* i0.c */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 2000 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "chbevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* Chebyshev coefficients for exp(-x) I0(x) - * in the interval [0,8]. - * - * lim(x->0){ exp(-x) I0(x) } = 1. - */ - constexpr double i0_A[] = { - -4.41534164647933937950E-18, 3.33079451882223809783E-17, -2.43127984654795469359E-16, - 1.71539128555513303061E-15, -1.16853328779934516808E-14, 7.67618549860493561688E-14, - -4.85644678311192946090E-13, 2.95505266312963983461E-12, -1.72682629144155570723E-11, - 9.67580903537323691224E-11, -5.18979560163526290666E-10, 2.65982372468238665035E-9, - -1.30002500998624804212E-8, 6.04699502254191894932E-8, -2.67079385394061173391E-7, - 1.11738753912010371815E-6, -4.41673835845875056359E-6, 1.64484480707288970893E-5, - -5.75419501008210370398E-5, 1.88502885095841655729E-4, -5.76375574538582365885E-4, - 1.63947561694133579842E-3, -4.32430999505057594430E-3, 1.05464603945949983183E-2, - -2.37374148058994688156E-2, 4.93052842396707084878E-2, -9.49010970480476444210E-2, - 1.71620901522208775349E-1, -3.04682672343198398683E-1, 6.76795274409476084995E-1}; - - /* Chebyshev coefficients for exp(-x) sqrt(x) I0(x) - * in the inverted interval [8,infinity]. - * - * lim(x->inf){ exp(-x) sqrt(x) I0(x) } = 1/sqrt(2pi). - */ - constexpr double i0_B[] = { - -7.23318048787475395456E-18, -4.83050448594418207126E-18, 4.46562142029675999901E-17, - 3.46122286769746109310E-17, -2.82762398051658348494E-16, -3.42548561967721913462E-16, - 1.77256013305652638360E-15, 3.81168066935262242075E-15, -9.55484669882830764870E-15, - -4.15056934728722208663E-14, 1.54008621752140982691E-14, 3.85277838274214270114E-13, - 7.18012445138366623367E-13, -1.79417853150680611778E-12, -1.32158118404477131188E-11, - -3.14991652796324136454E-11, 1.18891471078464383424E-11, 4.94060238822496958910E-10, - 3.39623202570838634515E-9, 2.26666899049817806459E-8, 2.04891858946906374183E-7, - 2.89137052083475648297E-6, 6.88975834691682398426E-5, 3.36911647825569408990E-3, - 8.04490411014108831608E-1}; - } // namespace detail - - XSF_HOST_DEVICE inline double i0(double x) { - double y; - - if (x < 0) - x = -x; - if (x <= 8.0) { - y = (x / 2.0) - 2.0; - return (std::exp(x) * chbevl(y, detail::i0_A, 30)); - } - - return (std::exp(x) * chbevl(32.0 / x - 2.0, detail::i0_B, 25) / sqrt(x)); - } - - XSF_HOST_DEVICE inline double i0e(double x) { - double y; - - if (x < 0) - x = -x; - if (x <= 8.0) { - y = (x / 2.0) - 2.0; - return (chbevl(y, detail::i0_A, 30)); - } - - return (chbevl(32.0 / x - 2.0, detail::i0_B, 25) / std::sqrt(x)); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/i1.h b/scipy/special/xsf/cephes/i1.h deleted file mode 100644 index 49e2690391cf..000000000000 --- a/scipy/special/xsf/cephes/i1.h +++ /dev/null @@ -1,158 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* i1.c - * - * Modified Bessel function of order one - * - * - * - * SYNOPSIS: - * - * double x, y, i1(); - * - * y = i1( x ); - * - * - * - * DESCRIPTION: - * - * Returns modified Bessel function of order one of the - * argument. - * - * The function is defined as i1(x) = -i j1( ix ). - * - * The range is partitioned into the two intervals [0,8] and - * (8, infinity). Chebyshev polynomial expansions are employed - * in each interval. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 1.9e-15 2.1e-16 - * - * - */ -/* i1e.c - * - * Modified Bessel function of order one, - * exponentially scaled - * - * - * - * SYNOPSIS: - * - * double x, y, i1e(); - * - * y = i1e( x ); - * - * - * - * DESCRIPTION: - * - * Returns exponentially scaled modified Bessel function - * of order one of the argument. - * - * The function is defined as i1(x) = -i exp(-|x|) j1( ix ). - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 2.0e-15 2.0e-16 - * See i1(). - * - */ - -/* i1.c 2 */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1985, 1987, 2000 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "chbevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* Chebyshev coefficients for exp(-x) I1(x) / x - * in the interval [0,8]. - * - * lim(x->0){ exp(-x) I1(x) / x } = 1/2. - */ - - constexpr double i1_A[] = { - 2.77791411276104639959E-18, -2.11142121435816608115E-17, 1.55363195773620046921E-16, - -1.10559694773538630805E-15, 7.60068429473540693410E-15, -5.04218550472791168711E-14, - 3.22379336594557470981E-13, -1.98397439776494371520E-12, 1.17361862988909016308E-11, - -6.66348972350202774223E-11, 3.62559028155211703701E-10, -1.88724975172282928790E-9, - 9.38153738649577178388E-9, -4.44505912879632808065E-8, 2.00329475355213526229E-7, - -8.56872026469545474066E-7, 3.47025130813767847674E-6, -1.32731636560394358279E-5, - 4.78156510755005422638E-5, -1.61760815825896745588E-4, 5.12285956168575772895E-4, - -1.51357245063125314899E-3, 4.15642294431288815669E-3, -1.05640848946261981558E-2, - 2.47264490306265168283E-2, -5.29459812080949914269E-2, 1.02643658689847095384E-1, - -1.76416518357834055153E-1, 2.52587186443633654823E-1}; - - /* Chebyshev coefficients for exp(-x) sqrt(x) I1(x) - * in the inverted interval [8,infinity]. - * - * lim(x->inf){ exp(-x) sqrt(x) I1(x) } = 1/sqrt(2pi). - */ - constexpr double i1_B[] = { - 7.51729631084210481353E-18, 4.41434832307170791151E-18, -4.65030536848935832153E-17, - -3.20952592199342395980E-17, 2.96262899764595013876E-16, 3.30820231092092828324E-16, - -1.88035477551078244854E-15, -3.81440307243700780478E-15, 1.04202769841288027642E-14, - 4.27244001671195135429E-14, -2.10154184277266431302E-14, -4.08355111109219731823E-13, - -7.19855177624590851209E-13, 2.03562854414708950722E-12, 1.41258074366137813316E-11, - 3.25260358301548823856E-11, -1.89749581235054123450E-11, -5.58974346219658380687E-10, - -3.83538038596423702205E-9, -2.63146884688951950684E-8, -2.51223623787020892529E-7, - -3.88256480887769039346E-6, -1.10588938762623716291E-4, -9.76109749136146840777E-3, - 7.78576235018280120474E-1}; - - } // namespace detail - - XSF_HOST_DEVICE inline double i1(double x) { - double y, z; - - z = std::abs(x); - if (z <= 8.0) { - y = (z / 2.0) - 2.0; - z = chbevl(y, detail::i1_A, 29) * z * std::exp(z); - } else { - z = std::exp(z) * chbevl(32.0 / z - 2.0, detail::i1_B, 25) / std::sqrt(z); - } - if (x < 0.0) - z = -z; - return (z); - } - - /* i1e() */ - - XSF_HOST_DEVICE inline double i1e(double x) { - double y, z; - - z = std::abs(x); - if (z <= 8.0) { - y = (z / 2.0) - 2.0; - z = chbevl(y, detail::i1_A, 29) * z; - } else { - z = chbevl(32.0 / z - 2.0, detail::i1_B, 25) / std::sqrt(z); - } - if (x < 0.0) - z = -z; - return (z); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/igam.h b/scipy/special/xsf/cephes/igam.h deleted file mode 100644 index 356ebd13ce72..000000000000 --- a/scipy/special/xsf/cephes/igam.h +++ /dev/null @@ -1,429 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* igam.c - * - * Incomplete Gamma integral - * - * - * - * SYNOPSIS: - * - * double a, x, y, igam(); - * - * y = igam( a, x ); - * - * DESCRIPTION: - * - * The function is defined by - * - * x - * - - * 1 | | -t a-1 - * igam(a,x) = ----- | e t dt. - * - | | - * | (a) - - * 0 - * - * - * In this implementation both arguments must be positive. - * The integral is evaluated by either a power series or - * continued fraction expansion, depending on the relative - * values of a and x. - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,30 200000 3.6e-14 2.9e-15 - * IEEE 0,100 300000 9.9e-14 1.5e-14 - */ -/* igamc() - * - * Complemented incomplete Gamma integral - * - * - * - * SYNOPSIS: - * - * double a, x, y, igamc(); - * - * y = igamc( a, x ); - * - * DESCRIPTION: - * - * The function is defined by - * - * - * igamc(a,x) = 1 - igam(a,x) - * - * inf. - * - - * 1 | | -t a-1 - * = ----- | e t dt. - * - | | - * | (a) - - * x - * - * - * In this implementation both arguments must be positive. - * The integral is evaluated by either a power series or - * continued fraction expansion, depending on the relative - * values of a and x. - * - * ACCURACY: - * - * Tested at random a, x. - * a x Relative error: - * arithmetic domain domain # trials peak rms - * IEEE 0.5,100 0,100 200000 1.9e-14 1.7e-15 - * IEEE 0.01,0.5 0,100 200000 1.4e-13 1.6e-15 - */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1985, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ - -/* Sources - * [1] "The Digital Library of Mathematical Functions", dlmf.nist.gov - * [2] Maddock et. al., "Incomplete Gamma Functions", - * https://www.boost.org/doc/libs/1_61_0/libs/math/doc/html/math_toolkit/sf_gamma/igamma.html - */ - -/* Scipy changes: - * - 05-01-2016: added asymptotic expansion for igam to improve the - * a ~ x regime. - * - 06-19-2016: additional series expansion added for igamc to - * improve accuracy at small arguments. - * - 06-24-2016: better choice of domain for the asymptotic series; - * improvements in accuracy for the asymptotic series when a and x - * are very close. - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "gamma.h" -#include "igam_asymp_coeff.h" -#include "lanczos.h" -#include "ndtr.h" -#include "unity.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr int igam_MAXITER = 2000; - constexpr int IGAM = 1; - constexpr int IGAMC = 0; - constexpr double igam_SMALL = 20; - constexpr double igam_LARGE = 200; - constexpr double igam_SMALLRATIO = 0.3; - constexpr double igam_LARGERATIO = 4.5; - - constexpr double igam_big = 4.503599627370496e15; - constexpr double igam_biginv = 2.22044604925031308085e-16; - - /* Compute - * - * x^a * exp(-x) / gamma(a) - * - * corrected from (15) and (16) in [2] by replacing exp(x - a) with - * exp(a - x). - */ - XSF_HOST_DEVICE inline double igam_fac(double a, double x) { - double ax, fac, res, num; - - if (std::abs(a - x) > 0.4 * std::abs(a)) { - ax = a * std::log(x) - x - xsf::cephes::lgam(a); - if (ax < -MAXLOG) { - set_error("igam", SF_ERROR_UNDERFLOW, NULL); - return 0.0; - } - return std::exp(ax); - } - - fac = a + xsf::cephes::lanczos_g - 0.5; - res = std::sqrt(fac / std::exp(1)) / xsf::cephes::lanczos_sum_expg_scaled(a); - - if ((a < 200) && (x < 200)) { - res *= std::exp(a - x) * std::pow(x / fac, a); - } else { - num = x - a - xsf::cephes::lanczos_g + 0.5; - res *= std::exp(a * xsf::cephes::log1pmx(num / fac) + x * (0.5 - xsf::cephes::lanczos_g) / fac); - } - - return res; - } - - /* Compute igamc using DLMF 8.9.2. */ - XSF_HOST_DEVICE inline double igamc_continued_fraction(double a, double x) { - int i; - double ans, ax, c, yc, r, t, y, z; - double pk, pkm1, pkm2, qk, qkm1, qkm2; - - ax = igam_fac(a, x); - if (ax == 0.0) { - return 0.0; - } - - /* continued fraction */ - y = 1.0 - a; - z = x + y + 1.0; - c = 0.0; - pkm2 = 1.0; - qkm2 = x; - pkm1 = x + 1.0; - qkm1 = z * x; - ans = pkm1 / qkm1; - - for (i = 0; i < igam_MAXITER; i++) { - c += 1.0; - y += 1.0; - z += 2.0; - yc = y * c; - pk = pkm1 * z - pkm2 * yc; - qk = qkm1 * z - qkm2 * yc; - if (qk != 0) { - r = pk / qk; - t = std::abs((ans - r) / r); - ans = r; - } else - t = 1.0; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - if (std::abs(pk) > igam_big) { - pkm2 *= igam_biginv; - pkm1 *= igam_biginv; - qkm2 *= igam_biginv; - qkm1 *= igam_biginv; - } - if (t <= MACHEP) { - break; - } - } - - return (ans * ax); - } - - /* Compute igam using DLMF 8.11.4. */ - XSF_HOST_DEVICE inline double igam_series(double a, double x) { - int i; - double ans, ax, c, r; - - ax = igam_fac(a, x); - if (ax == 0.0) { - return 0.0; - } - - /* power series */ - r = a; - c = 1.0; - ans = 1.0; - - for (i = 0; i < igam_MAXITER; i++) { - r += 1.0; - c *= x / r; - ans += c; - if (c <= MACHEP * ans) { - break; - } - } - - return (ans * ax / a); - } - - /* Compute igamc using DLMF 8.7.3. This is related to the series in - * igam_series but extra care is taken to avoid cancellation. - */ - XSF_HOST_DEVICE inline double igamc_series(double a, double x) { - int n; - double fac = 1; - double sum = 0; - double term, logx; - - for (n = 1; n < igam_MAXITER; n++) { - fac *= -x / n; - term = fac / (a + n); - sum += term; - if (std::abs(term) <= MACHEP * std::abs(sum)) { - break; - } - } - - logx = std::log(x); - term = -xsf::cephes::expm1(a * logx - xsf::cephes::lgam1p(a)); - return term - std::exp(a * logx - xsf::cephes::lgam(a)) * sum; - } - - /* Compute igam/igamc using DLMF 8.12.3/8.12.4. */ - XSF_HOST_DEVICE inline double asymptotic_series(double a, double x, int func) { - int k, n, sgn; - int maxpow = 0; - double lambda = x / a; - double sigma = (x - a) / a; - double eta, res, ck, ckterm, term, absterm; - double absoldterm = std::numeric_limits::infinity(); - double etapow[detail::igam_asymp_coeff_N] = {1}; - double sum = 0; - double afac = 1; - - if (func == detail::IGAM) { - sgn = -1; - } else { - sgn = 1; - } - - if (lambda > 1) { - eta = std::sqrt(-2 * xsf::cephes::log1pmx(sigma)); - } else if (lambda < 1) { - eta = -std::sqrt(-2 * xsf::cephes::log1pmx(sigma)); - } else { - eta = 0; - } - res = 0.5 * xsf::cephes::erfc(sgn * eta * std::sqrt(a / 2)); - - for (k = 0; k < igam_asymp_coeff_K; k++) { - ck = igam_asymp_coeff_d[k][0]; - for (n = 1; n < igam_asymp_coeff_N; n++) { - if (n > maxpow) { - etapow[n] = eta * etapow[n - 1]; - maxpow += 1; - } - ckterm = igam_asymp_coeff_d[k][n] * etapow[n]; - ck += ckterm; - if (std::abs(ckterm) < MACHEP * std::abs(ck)) { - break; - } - } - term = ck * afac; - absterm = std::abs(term); - if (absterm > absoldterm) { - break; - } - sum += term; - if (absterm < MACHEP * std::abs(sum)) { - break; - } - absoldterm = absterm; - afac /= a; - } - res += sgn * std::exp(-0.5 * a * eta * eta) * sum / std::sqrt(2 * M_PI * a); - - return res; - } - - } // namespace detail - - XSF_HOST_DEVICE inline double igamc(double a, double x); - - XSF_HOST_DEVICE inline double igam(double a, double x) { - double absxma_a; - - if (std::isnan(a) || std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - - if (x < 0 || a < 0) { - set_error("gammainc", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } else if (a == 0) { - if (x > 0) { - return 1; - } else { - return std::numeric_limits::quiet_NaN(); - } - } else if (x == 0) { - /* Zero integration limit */ - return 0; - } else if (std::isinf(a)) { - if (std::isinf(x)) { - return std::numeric_limits::quiet_NaN(); - } - return 0; - } else if (std::isinf(x)) { - return 1; - } - - /* Asymptotic regime where a ~ x; see [2]. */ - absxma_a = std::abs(x - a) / a; - if ((a > detail::igam_SMALL) && (a < detail::igam_LARGE) && (absxma_a < detail::igam_SMALLRATIO)) { - return detail::asymptotic_series(a, x, detail::IGAM); - } else if ((a > detail::igam_LARGE) && (absxma_a < detail::igam_LARGERATIO / std::sqrt(a))) { - return detail::asymptotic_series(a, x, detail::IGAM); - } - - if ((x > 1.0) && (x > a)) { - return (1.0 - igamc(a, x)); - } - - return detail::igam_series(a, x); - } - - XSF_HOST_DEVICE double igamc(double a, double x) { - double absxma_a; - - if (std::isnan(a) || std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - - if (x < 0 || a < 0) { - set_error("gammaincc", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } else if (a == 0) { - if (x > 0) { - return 0; - } else { - return std::numeric_limits::quiet_NaN(); - } - } else if (x == 0) { - return 1; - } else if (std::isinf(a)) { - if (std::isinf(x)) { - return std::numeric_limits::quiet_NaN(); - } - return 1; - } else if (std::isinf(x)) { - return 0; - } - - /* Asymptotic regime where a ~ x; see [2]. */ - absxma_a = std::abs(x - a) / a; - if ((a > detail::igam_SMALL) && (a < detail::igam_LARGE) && (absxma_a < detail::igam_SMALLRATIO)) { - return detail::asymptotic_series(a, x, detail::IGAMC); - } else if ((a > detail::igam_LARGE) && (absxma_a < detail::igam_LARGERATIO / std::sqrt(a))) { - return detail::asymptotic_series(a, x, detail::IGAMC); - } - - /* Everywhere else; see [2]. */ - if (x > 1.1) { - if (x < a) { - return 1.0 - detail::igam_series(a, x); - } else { - return detail::igamc_continued_fraction(a, x); - } - } else if (x <= 0.5) { - if (-0.4 / std::log(x) < a) { - return 1.0 - detail::igam_series(a, x); - } else { - return detail::igamc_series(a, x); - } - } else { - if (x * 1.1 < a) { - return 1.0 - detail::igam_series(a, x); - } else { - return detail::igamc_series(a, x); - } - } - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/igam_asymp_coeff.h b/scipy/special/xsf/cephes/igam_asymp_coeff.h deleted file mode 100644 index 98404c65ebca..000000000000 --- a/scipy/special/xsf/cephes/igam_asymp_coeff.h +++ /dev/null @@ -1,195 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. */ - -/* This file was automatically generated by _precomp/gammainc.py. - * Do not edit it manually! - */ -#pragma once - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr int igam_asymp_coeff_K = 25; - constexpr int igam_asymp_coeff_N = 25; - - static const double igam_asymp_coeff_d[igam_asymp_coeff_K][igam_asymp_coeff_N] = { - {-3.3333333333333333e-1, 8.3333333333333333e-2, -1.4814814814814815e-2, 1.1574074074074074e-3, - 3.527336860670194e-4, -1.7875514403292181e-4, 3.9192631785224378e-5, -2.1854485106799922e-6, - -1.85406221071516e-6, 8.296711340953086e-7, -1.7665952736826079e-7, 6.7078535434014986e-9, - 1.0261809784240308e-8, -4.3820360184533532e-9, 9.1476995822367902e-10, -2.551419399494625e-11, - -5.8307721325504251e-11, 2.4361948020667416e-11, -5.0276692801141756e-12, 1.1004392031956135e-13, - 3.3717632624009854e-13, -1.3923887224181621e-13, 2.8534893807047443e-14, -5.1391118342425726e-16, - -1.9752288294349443e-15}, - {-1.8518518518518519e-3, -3.4722222222222222e-3, 2.6455026455026455e-3, -9.9022633744855967e-4, - 2.0576131687242798e-4, -4.0187757201646091e-7, -1.8098550334489978e-5, 7.6491609160811101e-6, - -1.6120900894563446e-6, 4.6471278028074343e-9, 1.378633446915721e-7, -5.752545603517705e-8, - 1.1951628599778147e-8, -1.7543241719747648e-11, -1.0091543710600413e-9, 4.1627929918425826e-10, - -8.5639070264929806e-11, 6.0672151016047586e-14, 7.1624989648114854e-12, -2.9331866437714371e-12, - 5.9966963656836887e-13, -2.1671786527323314e-16, -4.9783399723692616e-14, 2.0291628823713425e-14, - -4.13125571381061e-15}, - {4.1335978835978836e-3, -2.6813271604938272e-3, 7.7160493827160494e-4, 2.0093878600823045e-6, - -1.0736653226365161e-4, 5.2923448829120125e-5, -1.2760635188618728e-5, 3.4235787340961381e-8, - 1.3721957309062933e-6, -6.298992138380055e-7, 1.4280614206064242e-7, -2.0477098421990866e-10, - -1.4092529910867521e-8, 6.228974084922022e-9, -1.3670488396617113e-9, 9.4283561590146782e-13, - 1.2872252400089318e-10, -5.5645956134363321e-11, 1.1975935546366981e-11, -4.1689782251838635e-15, - -1.0940640427884594e-12, 4.6622399463901357e-13, -9.905105763906906e-14, 1.8931876768373515e-17, - 8.8592218725911273e-15}, - {6.4943415637860082e-4, 2.2947209362139918e-4, -4.6918949439525571e-4, 2.6772063206283885e-4, - -7.5618016718839764e-5, -2.3965051138672967e-7, 1.1082654115347302e-5, -5.6749528269915966e-6, - 1.4230900732435884e-6, -2.7861080291528142e-11, -1.6958404091930277e-7, 8.0994649053880824e-8, - -1.9111168485973654e-8, 2.3928620439808118e-12, 2.0620131815488798e-9, -9.4604966618551322e-10, - 2.1541049775774908e-10, -1.388823336813903e-14, -2.1894761681963939e-11, 9.7909989511716851e-12, - -2.1782191880180962e-12, 6.2088195734079014e-17, 2.126978363279737e-13, -9.3446887915174333e-14, - 2.0453671226782849e-14}, - {-8.618882909167117e-4, 7.8403922172006663e-4, -2.9907248030319018e-4, -1.4638452578843418e-6, - 6.6414982154651222e-5, -3.9683650471794347e-5, 1.1375726970678419e-5, 2.5074972262375328e-10, - -1.6954149536558306e-6, 8.9075075322053097e-7, -2.2929348340008049e-7, 2.956794137544049e-11, - 2.8865829742708784e-8, -1.4189739437803219e-8, 3.4463580499464897e-9, -2.3024517174528067e-13, - -3.9409233028046405e-10, 1.8602338968504502e-10, -4.356323005056618e-11, 1.2786001016296231e-15, - 4.6792750266579195e-12, -2.1492464706134829e-12, 4.9088156148096522e-13, -6.3385914848915603e-18, - -5.0453320690800944e-14}, - {-3.3679855336635815e-4, -6.9728137583658578e-5, 2.7727532449593921e-4, -1.9932570516188848e-4, - 6.7977804779372078e-5, 1.419062920643967e-7, -1.3594048189768693e-5, 8.0184702563342015e-6, - -2.2914811765080952e-6, -3.252473551298454e-10, 3.4652846491085265e-7, -1.8447187191171343e-7, - 4.8240967037894181e-8, -1.7989466721743515e-14, -6.3061945000135234e-9, 3.1624176287745679e-9, - -7.8409242536974293e-10, 5.1926791652540407e-15, 9.3589442423067836e-11, -4.5134262161632782e-11, - 1.0799129993116827e-11, -3.661886712685252e-17, -1.210902069055155e-12, 5.6807435849905643e-13, - -1.3249659916340829e-13}, - {5.3130793646399222e-4, -5.9216643735369388e-4, 2.7087820967180448e-4, 7.9023532326603279e-7, - -8.1539693675619688e-5, 5.6116827531062497e-5, -1.8329116582843376e-5, -3.0796134506033048e-9, - 3.4651553688036091e-6, -2.0291327396058604e-6, 5.7887928631490037e-7, 2.338630673826657e-13, - -8.8286007463304835e-8, 4.7435958880408128e-8, -1.2545415020710382e-8, 8.6496488580102925e-14, - 1.6846058979264063e-9, -8.5754928235775947e-10, 2.1598224929232125e-10, -7.6132305204761539e-16, - -2.6639822008536144e-11, 1.3065700536611057e-11, -3.1799163902367977e-12, 4.7109761213674315e-18, - 3.6902800842763467e-13}, - {3.4436760689237767e-4, 5.1717909082605922e-5, -3.3493161081142236e-4, 2.812695154763237e-4, - -1.0976582244684731e-4, -1.2741009095484485e-7, 2.7744451511563644e-5, -1.8263488805711333e-5, - 5.7876949497350524e-6, 4.9387589339362704e-10, -1.0595367014026043e-6, 6.1667143761104075e-7, - -1.7562973359060462e-7, -1.2974473287015439e-12, 2.695423606288966e-8, -1.4578352908731271e-8, - 3.887645959386175e-9, -3.8810022510194121e-17, -5.3279941738772867e-10, 2.7437977643314845e-10, - -6.9957960920705679e-11, 2.5899863874868481e-17, 8.8566890996696381e-12, -4.403168815871311e-12, - 1.0865561947091654e-12}, - {-6.5262391859530942e-4, 8.3949872067208728e-4, -4.3829709854172101e-4, -6.969091458420552e-7, - 1.6644846642067548e-4, -1.2783517679769219e-4, 4.6299532636913043e-5, 4.5579098679227077e-9, - -1.0595271125805195e-5, 6.7833429048651666e-6, -2.1075476666258804e-6, -1.7213731432817145e-11, - 3.7735877416110979e-7, -2.1867506700122867e-7, 6.2202288040189269e-8, 6.5977038267330006e-16, - -9.5903864974256858e-9, 5.2132144922808078e-9, -1.3991589583935709e-9, 5.382058999060575e-16, - 1.9484714275467745e-10, -1.0127287556389682e-10, 2.6077347197254926e-11, -5.0904186999932993e-18, - -3.3721464474854592e-12}, - {-5.9676129019274625e-4, -7.2048954160200106e-5, 6.7823088376673284e-4, -6.4014752602627585e-4, - 2.7750107634328704e-4, 1.8197008380465151e-7, -8.4795071170685032e-5, 6.105192082501531e-5, - -2.1073920183404862e-5, -8.8585890141255994e-10, 4.5284535953805377e-6, -2.8427815022504408e-6, - 8.7082341778646412e-7, 3.6886101871706965e-12, -1.5344695190702061e-7, 8.862466778790695e-8, - -2.5184812301826817e-8, -1.0225912098215092e-14, 3.8969470758154777e-9, -2.1267304792235635e-9, - 5.7370135528051385e-10, -1.887749850169741e-19, -8.0931538694657866e-11, 4.2382723283449199e-11, - -1.1002224534207726e-11}, - {1.3324454494800656e-3, -1.9144384985654775e-3, 1.1089369134596637e-3, 9.932404122642299e-7, - -5.0874501293093199e-4, 4.2735056665392884e-4, -1.6858853767910799e-4, -8.1301893922784998e-9, - 4.5284402370562147e-5, -3.127053674781734e-5, 1.044986828530338e-5, 4.8435226265680926e-11, - -2.1482565873456258e-6, 1.329369701097492e-6, -4.0295693092101029e-7, -1.7567877666323291e-13, - 7.0145043163668257e-8, -4.040787734999483e-8, 1.1474026743371963e-8, 3.9642746853563325e-18, - -1.7804938269892714e-9, 9.7480262548731646e-10, -2.6405338676507616e-10, 5.794875163403742e-18, - 3.7647749553543836e-11}, - {1.579727660730835e-3, 1.6251626278391582e-4, -2.0633421035543276e-3, 2.1389686185689098e-3, - -1.0108559391263003e-3, -3.9912705529919201e-7, 3.6235025084764691e-4, -2.8143901463712154e-4, - 1.0449513336495887e-4, 2.1211418491830297e-9, -2.5779417251947842e-5, 1.7281818956040463e-5, - -5.6413773872904282e-6, -1.1024320105776174e-11, 1.1223224418895175e-6, -6.8693396379526735e-7, - 2.0653236975414887e-7, 4.6714772409838506e-14, -3.5609886164949055e-8, 2.0470855345905963e-8, - -5.8091738633283358e-9, -1.332821287582869e-16, 9.0354604391335133e-10, -4.9598782517330834e-10, - 1.3481607129399749e-10}, - {-4.0725121195140166e-3, 6.4033628338080698e-3, -4.0410161081676618e-3, -2.183732802866233e-6, - 2.1740441801254639e-3, -1.9700440518418892e-3, 8.3595469747962458e-4, 1.9445447567109655e-8, - -2.5779387120421696e-4, 1.9009987368139304e-4, -6.7696499937438965e-5, -1.4440629666426572e-10, - 1.5712512518742269e-5, -1.0304008744776893e-5, 3.304517767401387e-6, 7.9829760242325709e-13, - -6.4097794149313004e-7, 3.8894624761300056e-7, -1.1618347644948869e-7, -2.816808630596451e-15, - 1.9878012911297093e-8, -1.1407719956357511e-8, 3.2355857064185555e-9, 4.1759468293455945e-20, - -5.0423112718105824e-10}, - {-5.9475779383993003e-3, -5.4016476789260452e-4, 8.7910413550767898e-3, -9.8576315587856125e-3, - 5.0134695031021538e-3, 1.2807521786221875e-6, -2.0626019342754683e-3, 1.7109128573523058e-3, - -6.7695312714133799e-4, -6.9011545676562133e-9, 1.8855128143995902e-4, -1.3395215663491969e-4, - 4.6263183033528039e-5, 4.0034230613321351e-11, -1.0255652921494033e-5, 6.612086372797651e-6, - -2.0913022027253008e-6, -2.0951775649603837e-13, 3.9756029041993247e-7, -2.3956211978815887e-7, - 7.1182883382145864e-8, 8.925574873053455e-16, -1.2101547235064676e-8, 6.9350618248334386e-9, - -1.9661464453856102e-9}, - {1.7402027787522711e-2, -2.9527880945699121e-2, 2.0045875571402799e-2, 7.0289515966903407e-6, - -1.2375421071343148e-2, 1.1976293444235254e-2, -5.4156038466518525e-3, -6.3290893396418616e-8, - 1.8855118129005065e-3, -1.473473274825001e-3, 5.5515810097708387e-4, 5.2406834412550662e-10, - -1.4357913535784836e-4, 9.9181293224943297e-5, -3.3460834749478311e-5, -3.5755837291098993e-12, - 7.1560851960630076e-6, -4.5516802628155526e-6, 1.4236576649271475e-6, 1.8803149082089664e-14, - -2.6623403898929211e-7, 1.5950642189595716e-7, -4.7187514673841102e-8, -6.5107872958755177e-17, - 7.9795091026746235e-9}, - {3.0249124160905891e-2, 2.4817436002649977e-3, -4.9939134373457022e-2, 5.9915643009307869e-2, - -3.2483207601623391e-2, -5.7212968652103441e-6, 1.5085251778569354e-2, -1.3261324005088445e-2, - 5.5515262632426148e-3, 3.0263182257030016e-8, -1.7229548406756723e-3, 1.2893570099929637e-3, - -4.6845138348319876e-4, -1.830259937893045e-10, 1.1449739014822654e-4, -7.7378565221244477e-5, - 2.5625836246985201e-5, 1.0766165333192814e-12, -5.3246809282422621e-6, 3.349634863064464e-6, - -1.0381253128684018e-6, -5.608909920621128e-15, 1.9150821930676591e-7, -1.1418365800203486e-7, - 3.3654425209171788e-8}, - {-9.9051020880159045e-2, 1.7954011706123486e-1, -1.2989606383463778e-1, -3.1478872752284357e-5, - 9.0510635276848131e-2, -9.2828824411184397e-2, 4.4412112839877808e-2, 2.7779236316835888e-7, - -1.7229543805449697e-2, 1.4182925050891573e-2, -5.6214161633747336e-3, -2.39598509186381e-9, - 1.6029634366079908e-3, -1.1606784674435773e-3, 4.1001337768153873e-4, 1.8365800754090661e-11, - -9.5844256563655903e-5, 6.3643062337764708e-5, -2.076250624489065e-5, -1.1806020912804483e-13, - 4.2131808239120649e-6, -2.6262241337012467e-6, 8.0770620494930662e-7, 6.0125912123632725e-16, - -1.4729737374018841e-7}, - {-1.9994542198219728e-1, -1.5056113040026424e-2, 3.6470239469348489e-1, -4.6435192311733545e-1, - 2.6640934719197893e-1, 3.4038266027147191e-5, -1.3784338709329624e-1, 1.276467178337056e-1, - -5.6213828755200985e-2, -1.753150885483011e-7, 1.9235592956768113e-2, -1.5088821281095315e-2, - 5.7401854451350123e-3, 1.0622382710310225e-9, -1.5335082692563998e-3, 1.0819320643228214e-3, - -3.7372510193945659e-4, -6.6170909729031985e-12, 8.4263617380909628e-5, -5.5150706827483479e-5, - 1.7769536448348069e-5, 3.8827923210205533e-14, -3.53513697488768e-6, 2.1865832130045269e-6, - -6.6812849447625594e-7}, - {7.2438608504029431e-1, -1.3918010932653375, 1.0654143352413968, 1.876173868950258e-4, - -8.2705501176152696e-1, 8.9352433347828414e-1, -4.4971003995291339e-1, -1.6107401567546652e-6, - 1.9235590165271091e-1, -1.6597702160042609e-1, 6.8882222681814333e-2, 1.3910091724608687e-8, - -2.146911561508663e-2, 1.6228980898865892e-2, -5.9796016172584256e-3, -1.1287469112826745e-10, - 1.5167451119784857e-3, -1.0478634293553899e-3, 3.5539072889126421e-4, 8.1704322111801517e-13, - -7.7773013442452395e-5, 5.0291413897007722e-5, -1.6035083867000518e-5, 1.2469354315487605e-14, - 3.1369106244517615e-6}, - {1.6668949727276811, 1.165462765994632e-1, -3.3288393225018906, 4.4692325482864037, - -2.6977693045875807, -2.600667859891061e-4, 1.5389017615694539, -1.4937962361134612, - 6.8881964633233148e-1, 1.3077482004552385e-6, -2.5762963325596288e-1, 2.1097676102125449e-1, - -8.3714408359219882e-2, -7.7920428881354753e-9, 2.4267923064833599e-2, -1.7813678334552311e-2, - 6.3970330388900056e-3, 4.9430807090480523e-11, -1.5554602758465635e-3, 1.0561196919903214e-3, - -3.5277184460472902e-4, 9.3002334645022459e-14, 7.5285855026557172e-5, -4.8186515569156351e-5, - 1.5227271505597605e-5}, - {-6.6188298861372935, 1.3397985455142589e+1, -1.0789350606845146e+1, -1.4352254537875018e-3, - 9.2333694596189809, -1.0456552819547769e+1, 5.5105526029033471, 1.2024439690716742e-5, - -2.5762961164755816, 2.3207442745387179, -1.0045728797216284, -1.0207833290021914e-7, - 3.3975092171169466e-1, -2.6720517450757468e-1, 1.0235252851562706e-1, 8.4329730484871625e-10, - -2.7998284958442595e-2, 2.0066274144976813e-2, -7.0554368915086242e-3, 1.9402238183698188e-12, - 1.6562888105449611e-3, -1.1082898580743683e-3, 3.654545161310169e-4, -5.1290032026971794e-11, - -7.6340103696869031e-5}, - {-1.7112706061976095e+1, -1.1208044642899116, 3.7131966511885444e+1, -5.2298271025348962e+1, - 3.3058589696624618e+1, 2.4791298976200222e-3, -2.061089403411526e+1, 2.088672775145582e+1, - -1.0045703956517752e+1, -1.2238783449063012e-5, 4.0770134274221141, -3.473667358470195, - 1.4329352617312006, 7.1359914411879712e-8, -4.4797257159115612e-1, 3.4112666080644461e-1, - -1.2699786326594923e-1, -2.8953677269081528e-10, 3.3125776278259863e-2, -2.3274087021036101e-2, - 8.0399993503648882e-3, -1.177805216235265e-9, -1.8321624891071668e-3, 1.2108282933588665e-3, - -3.9479941246822517e-4}, - {7.389033153567425e+1, -1.5680141270402273e+2, 1.322177542759164e+2, 1.3692876877324546e-2, - -1.2366496885920151e+2, 1.4620689391062729e+2, -8.0365587724865346e+1, -1.1259851148881298e-4, - 4.0770132196179938e+1, -3.8210340013273034e+1, 1.719522294277362e+1, 9.3519707955168356e-7, - -6.2716159907747034, 5.1168999071852637, -2.0319658112299095, -4.9507215582761543e-9, - 5.9626397294332597e-1, -4.4220765337238094e-1, 1.6079998700166273e-1, -2.4733786203223402e-8, - -4.0307574759979762e-2, 2.7849050747097869e-2, -9.4751858992054221e-3, 6.419922235909132e-6, - 2.1250180774699461e-3}, - {2.1216837098382522e+2, 1.3107863022633868e+1, -4.9698285932871748e+2, 7.3121595266969204e+2, - -4.8213821720890847e+2, -2.8817248692894889e-2, 3.2616720302947102e+2, -3.4389340280087117e+2, - 1.7195193870816232e+2, 1.4038077378096158e-4, -7.52594195897599e+1, 6.651969984520934e+1, - -2.8447519748152462e+1, -7.613702615875391e-7, 9.5402237105304373, -7.5175301113311376, - 2.8943997568871961, -4.6612194999538201e-7, -8.0615149598794088e-1, 5.8483006570631029e-1, - -2.0845408972964956e-1, 1.4765818959305817e-4, 5.1000433863753019e-2, -3.3066252141883665e-2, - 1.5109265210467774e-2}, - {-9.8959643098322368e+2, 2.1925555360905233e+3, -1.9283586782723356e+3, -1.5925738122215253e-1, - 1.9569985945919857e+3, -2.4072514765081556e+3, 1.3756149959336496e+3, 1.2920735237496668e-3, - -7.525941715948055e+2, 7.3171668742208716e+2, -3.4137023466220065e+2, -9.9857390260608043e-6, - 1.3356313181291573e+2, -1.1276295161252794e+2, 4.6310396098204458e+1, -7.9237387133614756e-6, - -1.4510726927018646e+1, 1.1111771248100563e+1, -4.1690817945270892, 3.1008219800117808e-3, - 1.1220095449981468, -7.6052379926149916e-1, 3.6262236505085254e-1, 2.216867741940747e-1, - 4.8683443692930507e-1}}; - - } // namespace detail -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/igami.h b/scipy/special/xsf/cephes/igami.h deleted file mode 100644 index ff82c35f682b..000000000000 --- a/scipy/special/xsf/cephes/igami.h +++ /dev/null @@ -1,313 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* - * (C) Copyright John Maddock 2006. - * Use, modification and distribution are subject to the - * Boost Software License, Version 1.0. (See accompanying file - * LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "gamma.h" -#include "igam.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - XSF_HOST_DEVICE double find_inverse_s(double p, double q) { - /* - * Computation of the Incomplete Gamma Function Ratios and their Inverse - * ARMIDO R. DIDONATO and ALFRED H. MORRIS, JR. - * ACM Transactions on Mathematical Software, Vol. 12, No. 4, - * December 1986, Pages 377-393. - * - * See equation 32. - */ - double s, t; - constexpr double a[4] = {0.213623493715853, 4.28342155967104, 11.6616720288968, 3.31125922108741}; - constexpr double b[5] = {0.3611708101884203e-1, 1.27364489782223, 6.40691597760039, 6.61053765625462, 1}; - - if (p < 0.5) { - t = std::sqrt(-2 * std::log(p)); - } else { - t = std::sqrt(-2 * std::log(q)); - } - s = t - polevl(t, a, 3) / polevl(t, b, 4); - if (p < 0.5) - s = -s; - return s; - } - - XSF_HOST_DEVICE inline double didonato_SN(double a, double x, unsigned N, double tolerance) { - /* - * Computation of the Incomplete Gamma Function Ratios and their Inverse - * ARMIDO R. DIDONATO and ALFRED H. MORRIS, JR. - * ACM Transactions on Mathematical Software, Vol. 12, No. 4, - * December 1986, Pages 377-393. - * - * See equation 34. - */ - double sum = 1.0; - - if (N >= 1) { - unsigned i; - double partial = x / (a + 1); - - sum += partial; - for (i = 2; i <= N; ++i) { - partial *= x / (a + i); - sum += partial; - if (partial < tolerance) { - break; - } - } - } - return sum; - } - - XSF_HOST_DEVICE inline double find_inverse_gamma(double a, double p, double q) { - /* - * In order to understand what's going on here, you will - * need to refer to: - * - * Computation of the Incomplete Gamma Function Ratios and their Inverse - * ARMIDO R. DIDONATO and ALFRED H. MORRIS, JR. - * ACM Transactions on Mathematical Software, Vol. 12, No. 4, - * December 1986, Pages 377-393. - */ - double result; - - if (a == 1) { - if (q > 0.9) { - result = -std::log1p(-p); - } else { - result = -std::log(q); - } - } else if (a < 1) { - double g = xsf::cephes::Gamma(a); - double b = q * g; - - if ((b > 0.6) || ((b >= 0.45) && (a >= 0.3))) { - /* DiDonato & Morris Eq 21: - * - * There is a slight variation from DiDonato and Morris here: - * the first form given here is unstable when p is close to 1, - * making it impossible to compute the inverse of Q(a,x) for small - * q. Fortunately the second form works perfectly well in this case. - */ - double u; - if ((b * q > 1e-8) && (q > 1e-5)) { - u = std::pow(p * g * a, 1 / a); - } else { - u = std::exp((-q / a) - SCIPY_EULER); - } - result = u / (1 - (u / (a + 1))); - } else if ((a < 0.3) && (b >= 0.35)) { - /* DiDonato & Morris Eq 22: */ - double t = std::exp(-SCIPY_EULER - b); - double u = t * std::exp(t); - result = t * std::exp(u); - } else if ((b > 0.15) || (a >= 0.3)) { - /* DiDonato & Morris Eq 23: */ - double y = -std::log(b); - double u = y - (1 - a) * std::log(y); - result = y - (1 - a) * std::log(u) - std::log(1 + (1 - a) / (1 + u)); - } else if (b > 0.1) { - /* DiDonato & Morris Eq 24: */ - double y = -std::log(b); - double u = y - (1 - a) * std::log(y); - result = y - (1 - a) * std::log(u) - - std::log((u * u + 2 * (3 - a) * u + (2 - a) * (3 - a)) / (u * u + (5 - a) * u + 2)); - } else { - /* DiDonato & Morris Eq 25: */ - double y = -std::log(b); - double c1 = (a - 1) * std::log(y); - double c1_2 = c1 * c1; - double c1_3 = c1_2 * c1; - double c1_4 = c1_2 * c1_2; - double a_2 = a * a; - double a_3 = a_2 * a; - - double c2 = (a - 1) * (1 + c1); - double c3 = (a - 1) * (-(c1_2 / 2) + (a - 2) * c1 + (3 * a - 5) / 2); - double c4 = (a - 1) * ((c1_3 / 3) - (3 * a - 5) * c1_2 / 2 + (a_2 - 6 * a + 7) * c1 + - (11 * a_2 - 46 * a + 47) / 6); - double c5 = (a - 1) * (-(c1_4 / 4) + (11 * a - 17) * c1_3 / 6 + (-3 * a_2 + 13 * a - 13) * c1_2 + - (2 * a_3 - 25 * a_2 + 72 * a - 61) * c1 / 2 + - (25 * a_3 - 195 * a_2 + 477 * a - 379) / 12); - - double y_2 = y * y; - double y_3 = y_2 * y; - double y_4 = y_2 * y_2; - result = y + c1 + (c2 / y) + (c3 / y_2) + (c4 / y_3) + (c5 / y_4); - } - } else { - /* DiDonato and Morris Eq 31: */ - double s = find_inverse_s(p, q); - - double s_2 = s * s; - double s_3 = s_2 * s; - double s_4 = s_2 * s_2; - double s_5 = s_4 * s; - double ra = std::sqrt(a); - - double w = a + s * ra + (s_2 - 1) / 3; - w += (s_3 - 7 * s) / (36 * ra); - w -= (3 * s_4 + 7 * s_2 - 16) / (810 * a); - w += (9 * s_5 + 256 * s_3 - 433 * s) / (38880 * a * ra); - - if ((a >= 500) && (std::abs(1 - w / a) < 1e-6)) { - result = w; - } else if (p > 0.5) { - if (w < 3 * a) { - result = w; - } else { - double D = std::fmax(2, a * (a - 1)); - double lg = xsf::cephes::lgam(a); - double lb = std::log(q) + lg; - if (lb < -D * 2.3) { - /* DiDonato and Morris Eq 25: */ - double y = -lb; - double c1 = (a - 1) * std::log(y); - double c1_2 = c1 * c1; - double c1_3 = c1_2 * c1; - double c1_4 = c1_2 * c1_2; - double a_2 = a * a; - double a_3 = a_2 * a; - - double c2 = (a - 1) * (1 + c1); - double c3 = (a - 1) * (-(c1_2 / 2) + (a - 2) * c1 + (3 * a - 5) / 2); - double c4 = (a - 1) * ((c1_3 / 3) - (3 * a - 5) * c1_2 / 2 + (a_2 - 6 * a + 7) * c1 + - (11 * a_2 - 46 * a + 47) / 6); - double c5 = - (a - 1) * (-(c1_4 / 4) + (11 * a - 17) * c1_3 / 6 + (-3 * a_2 + 13 * a - 13) * c1_2 + - (2 * a_3 - 25 * a_2 + 72 * a - 61) * c1 / 2 + - (25 * a_3 - 195 * a_2 + 477 * a - 379) / 12); - - double y_2 = y * y; - double y_3 = y_2 * y; - double y_4 = y_2 * y_2; - result = y + c1 + (c2 / y) + (c3 / y_2) + (c4 / y_3) + (c5 / y_4); - } else { - /* DiDonato and Morris Eq 33: */ - double u = -lb + (a - 1) * std::log(w) - std::log(1 + (1 - a) / (1 + w)); - result = -lb + (a - 1) * std::log(u) - std::log(1 + (1 - a) / (1 + u)); - } - } - } else { - double z = w; - double ap1 = a + 1; - double ap2 = a + 2; - if (w < 0.15 * ap1) { - /* DiDonato and Morris Eq 35: */ - double v = std::log(p) + xsf::cephes::lgam(ap1); - z = std::exp((v + w) / a); - s = std::log1p(z / ap1 * (1 + z / ap2)); - z = std::exp((v + z - s) / a); - s = std::log1p(z / ap1 * (1 + z / ap2)); - z = std::exp((v + z - s) / a); - s = std::log1p(z / ap1 * (1 + z / ap2 * (1 + z / (a + 3)))); - z = std::exp((v + z - s) / a); - } - - if ((z <= 0.01 * ap1) || (z > 0.7 * ap1)) { - result = z; - } else { - /* DiDonato and Morris Eq 36: */ - double ls = std::log(didonato_SN(a, z, 100, 1e-4)); - double v = std::log(p) + xsf::cephes::lgam(ap1); - z = std::exp((v + z - ls) / a); - result = z * (1 - (a * std::log(z) - z - v + ls) / (a - z)); - } - } - } - return result; - } - - } // namespace detail - - XSF_HOST_DEVICE inline double igamci(double a, double q); - - XSF_HOST_DEVICE inline double igami(double a, double p) { - int i; - double x, fac, f_fp, fpp_fp; - - if (std::isnan(a) || std::isnan(p)) { - return std::numeric_limits::quiet_NaN(); - ; - } else if ((a < 0) || (p < 0) || (p > 1)) { - set_error("gammaincinv", SF_ERROR_DOMAIN, NULL); - } else if (p == 0.0) { - return 0.0; - } else if (p == 1.0) { - return std::numeric_limits::infinity(); - } else if (p > 0.9) { - return igamci(a, 1 - p); - } - - x = detail::find_inverse_gamma(a, p, 1 - p); - /* Halley's method */ - for (i = 0; i < 3; i++) { - fac = detail::igam_fac(a, x); - if (fac == 0.0) { - return x; - } - f_fp = (igam(a, x) - p) * x / fac; - /* The ratio of the first and second derivatives simplifies */ - fpp_fp = -1.0 + (a - 1) / x; - if (std::isinf(fpp_fp)) { - /* Resort to Newton's method in the case of overflow */ - x = x - f_fp; - } else { - x = x - f_fp / (1.0 - 0.5 * f_fp * fpp_fp); - } - } - - return x; - } - - XSF_HOST_DEVICE inline double igamci(double a, double q) { - int i; - double x, fac, f_fp, fpp_fp; - - if (std::isnan(a) || std::isnan(q)) { - return std::numeric_limits::quiet_NaN(); - } else if ((a < 0.0) || (q < 0.0) || (q > 1.0)) { - set_error("gammainccinv", SF_ERROR_DOMAIN, NULL); - } else if (q == 0.0) { - return std::numeric_limits::infinity(); - } else if (q == 1.0) { - return 0.0; - } else if (q > 0.9) { - return igami(a, 1 - q); - } - - x = detail::find_inverse_gamma(a, 1 - q, q); - for (i = 0; i < 3; i++) { - fac = detail::igam_fac(a, x); - if (fac == 0.0) { - return x; - } - f_fp = (igamc(a, x) - q) * x / (-fac); - fpp_fp = -1.0 + (a - 1) / x; - if (std::isinf(fpp_fp)) { - x = x - f_fp; - } else { - x = x - f_fp / (1.0 - 0.5 * f_fp * fpp_fp); - } - } - - return x; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/incbet.h b/scipy/special/xsf/cephes/incbet.h deleted file mode 100644 index aa56dfd58d5d..000000000000 --- a/scipy/special/xsf/cephes/incbet.h +++ /dev/null @@ -1,386 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* incbet.c - * - * Incomplete beta integral - * - * - * SYNOPSIS: - * - * double a, b, x, y, incbet(); - * - * y = incbet( a, b, x ); - * - * - * DESCRIPTION: - * - * Returns incomplete beta integral of the arguments, evaluated - * from zero to x. The function is defined as - * - * x - * - - - * | (a+b) | | a-1 b-1 - * ----------- | t (1-t) dt. - * - - | | - * | (a) | (b) - - * 0 - * - * The domain of definition is 0 <= x <= 1. In this - * implementation a and b are restricted to positive values. - * The integral from x to 1 may be obtained by the symmetry - * relation - * - * 1 - incbet( a, b, x ) = incbet( b, a, 1-x ). - * - * The integral is evaluated by a continued fraction expansion - * or, when b*x is small, by a power series. - * - * ACCURACY: - * - * Tested at uniformly distributed random points (a,b,x) with a and b - * in "domain" and x between 0 and 1. - * Relative error - * arithmetic domain # trials peak rms - * IEEE 0,5 10000 6.9e-15 4.5e-16 - * IEEE 0,85 250000 2.2e-13 1.7e-14 - * IEEE 0,1000 30000 5.3e-12 6.3e-13 - * IEEE 0,10000 250000 9.3e-11 7.1e-12 - * IEEE 0,100000 10000 8.7e-10 4.8e-11 - * Outputs smaller than the IEEE gradual underflow threshold - * were excluded from these statistics. - * - * ERROR MESSAGES: - * message condition value returned - * incbet domain x<0, x>1 0.0 - * incbet underflow 0.0 - */ - -/* - * Cephes Math Library, Release 2.3: March, 1995 - * Copyright 1984, 1995 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "beta.h" -#include "const.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* Compute (u * v) * w, disabling optimizations for gcc on 32 bit systems. - * Used below in incbet_pseries to prevent aggressive optimizations from - * degrading accuracy. - */ -#if defined(__GNUC__) && defined(__i386__) -#pragma GCC push_options -#pragma GCC optimize("00") -#endif - XSF_HOST_DEVICE inline double triple_product(double u, double v, double w) { - return (u * v) * w; - } -#if defined(__GNUC__) && defined(__i386__) -#pragma GCC pop_options -#endif - - constexpr double incbet_big = 4.503599627370496e15; - constexpr double incbet_biginv = 2.22044604925031308085e-16; - - /* Power series for incomplete beta integral. - * Use when b*x is small and x not too close to 1. */ - - XSF_HOST_DEVICE inline double incbet_pseries(double a, double b, double x) { - double s, t, u, v, n, t1, z, ai; - - ai = 1.0 / a; - u = (1.0 - b) * x; - v = u / (a + 1.0); - t1 = v; - t = u; - n = 2.0; - s = 0.0; - z = MACHEP * ai; - while (std::abs(v) > z) { - u = (n - b) * x / n; - t *= u; - v = t / (a + n); - s += v; - n += 1.0; - } - s += t1; - s += ai; - - u = a * std::log(x); - if ((a + b) < MAXGAM && std::abs(u) < MAXLOG) { - t = 1.0 / beta(a, b); - s = triple_product(s, t, std::pow(x, a)); // (s * t) * std::pow(x, a) - } else { - t = -lbeta(a, b) + u + std::log(s); - if (t < MINLOG) { - s = 0.0; - } else { - s = std::exp(t); - } - } - return (s); - } - - /* Continued fraction expansion #1 for incomplete beta integral */ - XSF_HOST_DEVICE inline double incbcf(double a, double b, double x) { - double xk, pk, pkm1, pkm2, qk, qkm1, qkm2; - double k1, k2, k3, k4, k5, k6, k7, k8; - double r, t, ans, thresh; - int n; - - k1 = a; - k2 = a + b; - k3 = a; - k4 = a + 1.0; - k5 = 1.0; - k6 = b - 1.0; - k7 = k4; - k8 = a + 2.0; - - pkm2 = 0.0; - qkm2 = 1.0; - pkm1 = 1.0; - qkm1 = 1.0; - ans = 1.0; - r = 1.0; - n = 0; - thresh = 3.0 * MACHEP; - do { - - xk = -(x * k1 * k2) / (k3 * k4); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - - xk = (x * k5 * k6) / (k7 * k8); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - - if (qk != 0) { - r = pk / qk; - } - if (r != 0) { - t = std::abs((ans - r) / r); - ans = r; - } else { - t = 1.0; - } - if (t < thresh) { - goto cdone; - } - - k1 += 1.0; - k2 += 1.0; - k3 += 2.0; - k4 += 2.0; - k5 += 1.0; - k6 -= 1.0; - k7 += 2.0; - k8 += 2.0; - - if ((std::abs(qk) + std::abs(pk)) > incbet_big) { - pkm2 *= incbet_biginv; - pkm1 *= incbet_biginv; - qkm2 *= incbet_biginv; - qkm1 *= incbet_biginv; - } - if ((std::abs(qk) < incbet_biginv) || (fabs(pk) < incbet_biginv)) { - pkm2 *= incbet_big; - pkm1 *= incbet_big; - qkm2 *= incbet_big; - qkm1 *= incbet_big; - } - } while (++n < 300); - - cdone: - return (ans); - } - - /* Continued fraction expansion #2 for incomplete beta integral */ - XSF_HOST_DEVICE inline double incbd(double a, double b, double x) { - double xk, pk, pkm1, pkm2, qk, qkm1, qkm2; - double k1, k2, k3, k4, k5, k6, k7, k8; - double r, t, ans, z, thresh; - int n; - - k1 = a; - k2 = b - 1.0; - k3 = a; - k4 = a + 1.0; - k5 = 1.0; - k6 = a + b; - k7 = a + 1.0; - ; - k8 = a + 2.0; - - pkm2 = 0.0; - qkm2 = 1.0; - pkm1 = 1.0; - qkm1 = 1.0; - z = x / (1.0 - x); - ans = 1.0; - r = 1.0; - n = 0; - thresh = 3.0 * MACHEP; - do { - - xk = -(z * k1 * k2) / (k3 * k4); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - - xk = (z * k5 * k6) / (k7 * k8); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - - if (qk != 0) - r = pk / qk; - if (r != 0) { - t = std::abs((ans - r) / r); - ans = r; - } else { - t = 1.0; - } - if (t < thresh) { - goto cdone; - } - - k1 += 1.0; - k2 -= 1.0; - k3 += 2.0; - k4 += 2.0; - k5 += 1.0; - k6 += 1.0; - k7 += 2.0; - k8 += 2.0; - - if ((std::abs(qk) + std::abs(pk)) > incbet_big) { - pkm2 *= incbet_biginv; - pkm1 *= incbet_biginv; - qkm2 *= incbet_biginv; - qkm1 *= incbet_biginv; - } - if ((std::abs(qk) < incbet_biginv) || (std::abs(pk) < incbet_biginv)) { - pkm2 *= incbet_big; - pkm1 *= incbet_big; - qkm2 *= incbet_big; - qkm1 *= incbet_big; - } - } while (++n < 300); - cdone: - return (ans); - } - - } // namespace detail - - XSF_HOST_DEVICE inline double incbet(double aa, double bb, double xx) { - double a, b, t, x, xc, w, y; - int flag; - - if (aa <= 0.0 || bb <= 0.0) - goto domerr; - - if ((xx <= 0.0) || (xx >= 1.0)) { - if (xx == 0.0) - return (0.0); - if (xx == 1.0) - return (1.0); - domerr: - set_error("incbet", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - - flag = 0; - if ((bb * xx) <= 1.0 && xx <= 0.95) { - t = detail::incbet_pseries(aa, bb, xx); - goto done; - } - - w = 1.0 - xx; - - /* Reverse a and b if x is greater than the mean. */ - if (xx > (aa / (aa + bb))) { - flag = 1; - a = bb; - b = aa; - xc = xx; - x = w; - } else { - a = aa; - b = bb; - xc = w; - x = xx; - } - - if (flag == 1 && (b * x) <= 1.0 && x <= 0.95) { - t = detail::incbet_pseries(a, b, x); - goto done; - } - - /* Choose expansion for better convergence. */ - y = x * (a + b - 2.0) - (a - 1.0); - if (y < 0.0) { - w = detail::incbcf(a, b, x); - } else { - w = detail::incbd(a, b, x) / xc; - } - - /* Multiply w by the factor - * a b _ _ _ - * x (1-x) | (a+b) / ( a | (a) | (b) ) . */ - - y = a * std::log(x); - t = b * std::log(xc); - if ((a + b) < detail::MAXGAM && std::abs(y) < detail::MAXLOG && std::abs(t) < detail::MAXLOG) { - t = std::pow(xc, b); - t *= std::pow(x, a); - t /= a; - t *= w; - t *= 1.0 / beta(a, b); - goto done; - } - /* Resort to logarithms. */ - y += t - lbeta(a, b); - y += std::log(w / a); - if (y < detail::MINLOG) { - t = 0.0; - } else { - t = exp(y); - } - done: - if (flag == 1) { - if (t <= detail::MACHEP) { - t = 1.0 - detail::MACHEP; - } else { - t = 1.0 - t; - } - } - return (t); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/incbi.h b/scipy/special/xsf/cephes/incbi.h deleted file mode 100644 index 3fd747c16f3e..000000000000 --- a/scipy/special/xsf/cephes/incbi.h +++ /dev/null @@ -1,293 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* incbi() - * - * Inverse of incomplete beta integral - * - * - * - * SYNOPSIS: - * - * double a, b, x, y, incbi(); - * - * x = incbi( a, b, y ); - * - * - * - * DESCRIPTION: - * - * Given y, the function finds x such that - * - * incbet( a, b, x ) = y . - * - * The routine performs interval halving or Newton iterations to find the - * root of incbet(a,b,x) - y = 0. - * - * - * ACCURACY: - * - * Relative error: - * x a,b - * arithmetic domain domain # trials peak rms - * IEEE 0,1 .5,10000 50000 5.8e-12 1.3e-13 - * IEEE 0,1 .25,100 100000 1.8e-13 3.9e-15 - * IEEE 0,1 0,5 50000 1.1e-12 5.5e-15 - * VAX 0,1 .5,100 25000 3.5e-14 1.1e-15 - * With a and b constrained to half-integer or integer values: - * IEEE 0,1 .5,10000 50000 5.8e-12 1.1e-13 - * IEEE 0,1 .5,100 100000 1.7e-14 7.9e-16 - * With a = .5, b constrained to half-integer or integer values: - * IEEE 0,1 .5,10000 10000 8.3e-11 1.0e-11 - */ - -/* - * Cephes Math Library Release 2.4: March,1996 - * Copyright 1984, 1996 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "gamma.h" -#include "incbet.h" -#include "ndtri.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE inline double incbi(double aa, double bb, double yy0) { - double a, b, y0, d, y, x, x0, x1, lgm, yp, di, dithresh, yl, yh, xt; - int i, rflg, dir, nflg; - - i = 0; - if (yy0 <= 0) { - return (0.0); - } - if (yy0 >= 1.0) { - return (1.0); - } - x0 = 0.0; - yl = 0.0; - x1 = 1.0; - yh = 1.0; - nflg = 0; - - if (aa <= 1.0 || bb <= 1.0) { - dithresh = 1.0e-6; - rflg = 0; - a = aa; - b = bb; - y0 = yy0; - x = a / (a + b); - y = incbet(a, b, x); - goto ihalve; - } else { - dithresh = 1.0e-4; - } - /* approximation to inverse function */ - - yp = -ndtri(yy0); - - if (yy0 > 0.5) { - rflg = 1; - a = bb; - b = aa; - y0 = 1.0 - yy0; - yp = -yp; - } else { - rflg = 0; - a = aa; - b = bb; - y0 = yy0; - } - - lgm = (yp * yp - 3.0) / 6.0; - x = 2.0 / (1.0 / (2.0 * a - 1.0) + 1.0 / (2.0 * b - 1.0)); - d = yp * std::sqrt(x + lgm) / x - - (1.0 / (2.0 * b - 1.0) - 1.0 / (2.0 * a - 1.0)) * (lgm + 5.0 / 6.0 - 2.0 / (3.0 * x)); - d = 2.0 * d; - if (d < detail::MINLOG) { - x = 1.0; - goto under; - } - x = a / (a + b * std::exp(d)); - y = incbet(a, b, x); - yp = (y - y0) / y0; - if (std::abs(yp) < 0.2) { - goto newt; - } - - /* Resort to interval halving if not close enough. */ - ihalve: - - dir = 0; - di = 0.5; - for (i = 0; i < 100; i++) { - if (i != 0) { - x = x0 + di * (x1 - x0); - if (x == 1.0) { - x = 1.0 - detail::MACHEP; - } - if (x == 0.0) { - di = 0.5; - x = x0 + di * (x1 - x0); - if (x == 0.0) { - goto under; - } - } - y = incbet(a, b, x); - yp = (x1 - x0) / (x1 + x0); - if (std::abs(yp) < dithresh) { - goto newt; - } - yp = (y - y0) / y0; - if (std::abs(yp) < dithresh) { - goto newt; - } - } - if (y < y0) { - x0 = x; - yl = y; - if (dir < 0) { - dir = 0; - di = 0.5; - } else if (dir > 3) { - di = 1.0 - (1.0 - di) * (1.0 - di); - } else if (dir > 1) { - di = 0.5 * di + 0.5; - } else { - di = (y0 - y) / (yh - yl); - } - dir += 1; - if (x0 > 0.75) { - if (rflg == 1) { - rflg = 0; - a = aa; - b = bb; - y0 = yy0; - } else { - rflg = 1; - a = bb; - b = aa; - y0 = 1.0 - yy0; - } - x = 1.0 - x; - y = incbet(a, b, x); - x0 = 0.0; - yl = 0.0; - x1 = 1.0; - yh = 1.0; - goto ihalve; - } - } else { - x1 = x; - if (rflg == 1 && x1 < detail::MACHEP) { - x = 0.0; - goto done; - } - yh = y; - if (dir > 0) { - dir = 0; - di = 0.5; - } else if (dir < -3) { - di = di * di; - } else if (dir < -1) { - di = 0.5 * di; - } else { - di = (y - y0) / (yh - yl); - } - dir -= 1; - } - } - set_error("incbi", SF_ERROR_LOSS, NULL); - if (x0 >= 1.0) { - x = 1.0 - detail::MACHEP; - goto done; - } - if (x <= 0.0) { - under: - set_error("incbi", SF_ERROR_UNDERFLOW, NULL); - x = 0.0; - goto done; - } - - newt: - - if (nflg) { - goto done; - } - nflg = 1; - lgm = lgam(a + b) - lgam(a) - lgam(b); - - for (i = 0; i < 8; i++) { - /* Compute the function at this point. */ - if (i != 0) - y = incbet(a, b, x); - if (y < yl) { - x = x0; - y = yl; - } else if (y > yh) { - x = x1; - y = yh; - } else if (y < y0) { - x0 = x; - yl = y; - } else { - x1 = x; - yh = y; - } - if (x == 1.0 || x == 0.0) { - break; - } - /* Compute the derivative of the function at this point. */ - d = (a - 1.0) * std::log(x) + (b - 1.0) * std::log(1.0 - x) + lgm; - if (d < detail::MINLOG) { - goto done; - } - if (d > detail::MAXLOG) { - break; - } - d = std::exp(d); - /* Compute the step to the next approximation of x. */ - d = (y - y0) / d; - xt = x - d; - if (xt <= x0) { - y = (x - x0) / (x1 - x0); - xt = x0 + 0.5 * y * (x - x0); - if (xt <= 0.0) { - break; - } - } - if (xt >= x1) { - y = (x1 - x) / (x1 - x0); - xt = x1 - 0.5 * y * (x1 - x); - if (xt >= 1.0) - break; - } - x = xt; - if (std::abs(d / x) < 128.0 * detail::MACHEP) { - goto done; - } - } - /* Did not converge. */ - dithresh = 256.0 * detail::MACHEP; - goto ihalve; - - done: - - if (rflg) { - if (x <= detail::MACHEP) { - x = 1.0 - detail::MACHEP; - } else { - x = 1.0 - x; - } - } - return (x); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/j0.h b/scipy/special/xsf/cephes/j0.h deleted file mode 100644 index 29236ef966e0..000000000000 --- a/scipy/special/xsf/cephes/j0.h +++ /dev/null @@ -1,225 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* j0.c - * - * Bessel function of order zero - * - * - * - * SYNOPSIS: - * - * double x, y, j0(); - * - * y = j0( x ); - * - * - * - * DESCRIPTION: - * - * Returns Bessel function of order zero of the argument. - * - * The domain is divided into the intervals [0, 5] and - * (5, infinity). In the first interval the following rational - * approximation is used: - * - * - * 2 2 - * (w - r ) (w - r ) P (w) / Q (w) - * 1 2 3 8 - * - * 2 - * where w = x and the two r's are zeros of the function. - * - * In the second interval, the Hankel asymptotic expansion - * is employed with two rational functions of degree 6/6 - * and 7/7. - * - * - * - * ACCURACY: - * - * Absolute error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 60000 4.2e-16 1.1e-16 - * - */ -/* y0.c - * - * Bessel function of the second kind, order zero - * - * - * - * SYNOPSIS: - * - * double x, y, y0(); - * - * y = y0( x ); - * - * - * - * DESCRIPTION: - * - * Returns Bessel function of the second kind, of order - * zero, of the argument. - * - * The domain is divided into the intervals [0, 5] and - * (5, infinity). In the first interval a rational approximation - * R(x) is employed to compute - * y0(x) = R(x) + 2 * log(x) * j0(x) / M_PI. - * Thus a call to j0() is required. - * - * In the second interval, the Hankel asymptotic expansion - * is employed with two rational functions of degree 6/6 - * and 7/7. - * - * - * - * ACCURACY: - * - * Absolute error, when y0(x) < 1; else relative error: - * - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 1.3e-15 1.6e-16 - * - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier - */ - -/* Note: all coefficients satisfy the relative error criterion - * except YP, YQ which are designed for absolute error. */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double j0_PP[7] = { - 7.96936729297347051624E-4, 8.28352392107440799803E-2, 1.23953371646414299388E0, 5.44725003058768775090E0, - 8.74716500199817011941E0, 5.30324038235394892183E0, 9.99999999999999997821E-1, - }; - - constexpr double j0_PQ[7] = { - 9.24408810558863637013E-4, 8.56288474354474431428E-2, 1.25352743901058953537E0, 5.47097740330417105182E0, - 8.76190883237069594232E0, 5.30605288235394617618E0, 1.00000000000000000218E0, - }; - - constexpr double j0_QP[8] = { - -1.13663838898469149931E-2, -1.28252718670509318512E0, -1.95539544257735972385E1, -9.32060152123768231369E1, - -1.77681167980488050595E2, -1.47077505154951170175E2, -5.14105326766599330220E1, -6.05014350600728481186E0, - }; - - constexpr double j0_QQ[7] = { - /* 1.00000000000000000000E0, */ - 6.43178256118178023184E1, 8.56430025976980587198E2, 3.88240183605401609683E3, 7.24046774195652478189E3, - 5.93072701187316984827E3, 2.06209331660327847417E3, 2.42005740240291393179E2, - }; - - constexpr double j0_YP[8] = { - 1.55924367855235737965E4, -1.46639295903971606143E7, 5.43526477051876500413E9, - -9.82136065717911466409E11, 8.75906394395366999549E13, -3.46628303384729719441E15, - 4.42733268572569800351E16, -1.84950800436986690637E16, - }; - - constexpr double j0_YQ[7] = { - /* 1.00000000000000000000E0, */ - 1.04128353664259848412E3, 6.26107330137134956842E5, 2.68919633393814121987E8, 8.64002487103935000337E10, - 2.02979612750105546709E13, 3.17157752842975028269E15, 2.50596256172653059228E17, - }; - - /* 5.783185962946784521175995758455807035071 */ - constexpr double j0_DR1 = 5.78318596294678452118E0; - - /* 30.47126234366208639907816317502275584842 */ - constexpr double j0_DR2 = 3.04712623436620863991E1; - - constexpr double j0_RP[4] = { - -4.79443220978201773821E9, - 1.95617491946556577543E12, - -2.49248344360967716204E14, - 9.70862251047306323952E15, - }; - - constexpr double j0_RQ[8] = { - /* 1.00000000000000000000E0, */ - 4.99563147152651017219E2, 1.73785401676374683123E5, 4.84409658339962045305E7, 1.11855537045356834862E10, - 2.11277520115489217587E12, 3.10518229857422583814E14, 3.18121955943204943306E16, 1.71086294081043136091E18, - }; - - } // namespace detail - - XSF_HOST_DEVICE inline double j0(double x) { - double w, z, p, q, xn; - - if (x < 0) { - x = -x; - } - - if (x <= 5.0) { - z = x * x; - if (x < 1.0e-5) { - return (1.0 - z / 4.0); - } - - p = (z - detail::j0_DR1) * (z - detail::j0_DR2); - p = p * polevl(z, detail::j0_RP, 3) / p1evl(z, detail::j0_RQ, 8); - return (p); - } - - w = 5.0 / x; - q = 25.0 / (x * x); - p = polevl(q, detail::j0_PP, 6) / polevl(q, detail::j0_PQ, 6); - q = polevl(q, detail::j0_QP, 7) / p1evl(q, detail::j0_QQ, 7); - xn = x - M_PI_4; - p = p * std::cos(xn) - w * q * std::sin(xn); - return (p * detail::SQRT2OPI / std::sqrt(x)); - } - - /* y0() 2 */ - /* Bessel function of second kind, order zero */ - - /* Rational approximation coefficients YP[], YQ[] are used here. - * The function computed is y0(x) - 2 * log(x) * j0(x) / M_PI, - * whose value at x = 0 is 2 * ( log(0.5) + EUL ) / M_PI - * = 0.073804295108687225. - */ - - XSF_HOST_DEVICE inline double y0(double x) { - double w, z, p, q, xn; - - if (x <= 5.0) { - if (x == 0.0) { - set_error("y0", SF_ERROR_SINGULAR, NULL); - return -std::numeric_limits::infinity(); - } else if (x < 0.0) { - set_error("y0", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - z = x * x; - w = polevl(z, detail::j0_YP, 7) / p1evl(z, detail::j0_YQ, 7); - w += M_2_PI * std::log(x) * j0(x); - return (w); - } - - w = 5.0 / x; - z = 25.0 / (x * x); - p = polevl(z, detail::j0_PP, 6) / polevl(z, detail::j0_PQ, 6); - q = polevl(z, detail::j0_QP, 7) / p1evl(z, detail::j0_QQ, 7); - xn = x - M_PI_4; - p = p * std::sin(xn) + w * q * std::cos(xn); - return (p * detail::SQRT2OPI / std::sqrt(x)); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/j1.h b/scipy/special/xsf/cephes/j1.h deleted file mode 100644 index 46532249550d..000000000000 --- a/scipy/special/xsf/cephes/j1.h +++ /dev/null @@ -1,198 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* j1.c - * - * Bessel function of order one - * - * - * - * SYNOPSIS: - * - * double x, y, j1(); - * - * y = j1( x ); - * - * - * - * DESCRIPTION: - * - * Returns Bessel function of order one of the argument. - * - * The domain is divided into the intervals [0, 8] and - * (8, infinity). In the first interval a 24 term Chebyshev - * expansion is used. In the second, the asymptotic - * trigonometric representation is employed using two - * rational functions of degree 5/5. - * - * - * - * ACCURACY: - * - * Absolute error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 2.6e-16 1.1e-16 - * - * - */ -/* y1.c - * - * Bessel function of second kind of order one - * - * - * - * SYNOPSIS: - * - * double x, y, y1(); - * - * y = y1( x ); - * - * - * - * DESCRIPTION: - * - * Returns Bessel function of the second kind of order one - * of the argument. - * - * The domain is divided into the intervals [0, 8] and - * (8, infinity). In the first interval a 25 term Chebyshev - * expansion is used, and a call to j1() is required. - * In the second, the asymptotic trigonometric representation - * is employed using two rational functions of degree 5/5. - * - * - * - * ACCURACY: - * - * Absolute error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 1.0e-15 1.3e-16 - * - * (error criterion relative when |y1| > 1). - * - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier - */ - -/* - * #define PIO4 .78539816339744830962 - * #define THPIO4 2.35619449019234492885 - * #define SQ2OPI .79788456080286535588 - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - constexpr double j1_RP[4] = { - -8.99971225705559398224E8, - 4.52228297998194034323E11, - -7.27494245221818276015E13, - 3.68295732863852883286E15, - }; - - constexpr double j1_RQ[8] = { - /* 1.00000000000000000000E0, */ - 6.20836478118054335476E2, 2.56987256757748830383E5, 8.35146791431949253037E7, 2.21511595479792499675E10, - 4.74914122079991414898E12, 7.84369607876235854894E14, 8.95222336184627338078E16, 5.32278620332680085395E18, - }; - - constexpr double j1_PP[7] = { - 7.62125616208173112003E-4, 7.31397056940917570436E-2, 1.12719608129684925192E0, 5.11207951146807644818E0, - 8.42404590141772420927E0, 5.21451598682361504063E0, 1.00000000000000000254E0, - }; - - constexpr double j1_PQ[7] = { - 5.71323128072548699714E-4, 6.88455908754495404082E-2, 1.10514232634061696926E0, 5.07386386128601488557E0, - 8.39985554327604159757E0, 5.20982848682361821619E0, 9.99999999999999997461E-1, - }; - - constexpr double j1_QP[8] = { - 5.10862594750176621635E-2, 4.98213872951233449420E0, 7.58238284132545283818E1, 3.66779609360150777800E2, - 7.10856304998926107277E2, 5.97489612400613639965E2, 2.11688757100572135698E2, 2.52070205858023719784E1, - }; - - constexpr double j1_QQ[7] = { - /* 1.00000000000000000000E0, */ - 7.42373277035675149943E1, 1.05644886038262816351E3, 4.98641058337653607651E3, 9.56231892404756170795E3, - 7.99704160447350683650E3, 2.82619278517639096600E3, 3.36093607810698293419E2, - }; - - constexpr double j1_YP[6] = { - 1.26320474790178026440E9, -6.47355876379160291031E11, 1.14509511541823727583E14, - -8.12770255501325109621E15, 2.02439475713594898196E17, -7.78877196265950026825E17, - }; - - constexpr double j1_YQ[8] = { - /* 1.00000000000000000000E0, */ - 5.94301592346128195359E2, 2.35564092943068577943E5, 7.34811944459721705660E7, 1.87601316108706159478E10, - 3.88231277496238566008E12, 6.20557727146953693363E14, 6.87141087355300489866E16, 3.97270608116560655612E18, - }; - - constexpr double j1_Z1 = 1.46819706421238932572E1; - constexpr double j1_Z2 = 4.92184563216946036703E1; - - } // namespace detail - - XSF_HOST_DEVICE inline double j1(double x) { - double w, z, p, q, xn; - - w = x; - if (x < 0) { - return -j1(-x); - } - - if (w <= 5.0) { - z = x * x; - w = polevl(z, detail::j1_RP, 3) / p1evl(z, detail::j1_RQ, 8); - w = w * x * (z - detail::j1_Z1) * (z - detail::j1_Z2); - return (w); - } - - w = 5.0 / x; - z = w * w; - p = polevl(z, detail::j1_PP, 6) / polevl(z, detail::j1_PQ, 6); - q = polevl(z, detail::j1_QP, 7) / p1evl(z, detail::j1_QQ, 7); - xn = x - detail::THPIO4; - p = p * std::cos(xn) - w * q * std::sin(xn); - return (p * detail::SQRT2OPI / std::sqrt(x)); - } - - XSF_HOST_DEVICE inline double y1(double x) { - double w, z, p, q, xn; - - if (x <= 5.0) { - if (x == 0.0) { - set_error("y1", SF_ERROR_SINGULAR, NULL); - return -std::numeric_limits::infinity(); - } else if (x <= 0.0) { - set_error("y1", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - z = x * x; - w = x * (polevl(z, detail::j1_YP, 5) / p1evl(z, detail::j1_YQ, 8)); - w += M_2_PI * (j1(x) * std::log(x) - 1.0 / x); - return (w); - } - - w = 5.0 / x; - z = w * w; - p = polevl(z, detail::j1_PP, 6) / polevl(z, detail::j1_PQ, 6); - q = polevl(z, detail::j1_QP, 7) / p1evl(z, detail::j1_QQ, 7); - xn = x - detail::THPIO4; - p = p * std::sin(xn) + w * q * std::cos(xn); - return (p * detail::SQRT2OPI / std::sqrt(x)); - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/jv.h b/scipy/special/xsf/cephes/jv.h deleted file mode 100644 index db5272f27fb4..000000000000 --- a/scipy/special/xsf/cephes/jv.h +++ /dev/null @@ -1,715 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* jv.c - * - * Bessel function of noninteger order - * - * - * - * SYNOPSIS: - * - * double v, x, y, jv(); - * - * y = jv( v, x ); - * - * - * - * DESCRIPTION: - * - * Returns Bessel function of order v of the argument, - * where v is real. Negative x is allowed if v is an integer. - * - * Several expansions are included: the ascending power - * series, the Hankel expansion, and two transitional - * expansions for large v. If v is not too large, it - * is reduced by recurrence to a region of best accuracy. - * The transitional expansions give 12D accuracy for v > 500. - * - * - * - * ACCURACY: - * Results for integer v are indicated by *, where x and v - * both vary from -125 to +125. Otherwise, - * x ranges from 0 to 125, v ranges as indicated by "domain." - * Error criterion is absolute, except relative when |jv()| > 1. - * - * arithmetic v domain x domain # trials peak rms - * IEEE 0,125 0,125 100000 4.6e-15 2.2e-16 - * IEEE -125,0 0,125 40000 5.4e-11 3.7e-13 - * IEEE 0,500 0,500 20000 4.4e-15 4.0e-16 - * Integer v: - * IEEE -125,125 -125,125 50000 3.5e-15* 1.9e-16* - * - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "airy.h" -#include "cbrt.h" -#include "rgamma.h" -#include "j0.h" -#include "j1.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double jv_BIG = 1.44115188075855872E+17; - - /* Reduce the order by backward recurrence. - * AMS55 #9.1.27 and 9.1.73. - */ - - XSF_HOST_DEVICE inline double jv_recur(double *n, double x, double *newn, int cancel) { - double pkm2, pkm1, pk, qkm2, qkm1; - - /* double pkp1; */ - double k, ans, qk, xk, yk, r, t, kf; - constexpr double big = jv_BIG; - int nflag, ctr; - int miniter, maxiter; - - /* Continued fraction for Jn(x)/Jn-1(x) - * AMS 9.1.73 - * - * x -x^2 -x^2 - * ------ --------- --------- ... - * 2 n + 2(n+1) + 2(n+2) + - * - * Compute it with the simplest possible algorithm. - * - * This continued fraction starts to converge when (|n| + m) > |x|. - * Hence, at least |x|-|n| iterations are necessary before convergence is - * achieved. There is a hard limit set below, m <= 30000, which is chosen - * so that no branch in `jv` requires more iterations to converge. - * The exact maximum number is (500/3.6)^2 - 500 ~ 19000 - */ - - maxiter = 22000; - miniter = std::abs(x) - std::abs(*n); - if (miniter < 1) { - miniter = 1; - } - - if (*n < 0.0) { - nflag = 1; - } else { - nflag = 0; - } - - fstart: - pkm2 = 0.0; - qkm2 = 1.0; - pkm1 = x; - qkm1 = *n + *n; - xk = -x * x; - yk = qkm1; - ans = 0.0; /* ans=0.0 ensures that t=1.0 in the first iteration */ - ctr = 0; - do { - yk += 2.0; - pk = pkm1 * yk + pkm2 * xk; - qk = qkm1 * yk + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - - /* check convergence */ - if (qk != 0 && ctr > miniter) - r = pk / qk; - else - r = 0.0; - - if (r != 0) { - t = std::abs((ans - r) / r); - ans = r; - } else { - t = 1.0; - } - - if (++ctr > maxiter) { - set_error("jv", SF_ERROR_UNDERFLOW, NULL); - goto done; - } - if (t < MACHEP) { - goto done; - } - - /* renormalize coefficients */ - if (std::abs(pk) > big) { - pkm2 /= big; - pkm1 /= big; - qkm2 /= big; - qkm1 /= big; - } - } while (t > MACHEP); - - done: - if (ans == 0) - ans = 1.0; - - /* Change n to n-1 if n < 0 and the continued fraction is small */ - if (nflag > 0) { - if (std::abs(ans) < 0.125) { - nflag = -1; - *n = *n - 1.0; - goto fstart; - } - } - - kf = *newn; - - /* backward recurrence - * 2k - * J (x) = --- J (x) - J (x) - * k-1 x k k+1 - */ - - pk = 1.0; - pkm1 = 1.0 / ans; - k = *n - 1.0; - r = 2 * k; - do { - pkm2 = (pkm1 * r - pk * x) / x; - /* pkp1 = pk; */ - pk = pkm1; - pkm1 = pkm2; - r -= 2.0; - /* - * t = fabs(pkp1) + fabs(pk); - * if( (k > (kf + 2.5)) && (fabs(pkm1) < 0.25*t) ) - * { - * k -= 1.0; - * t = x*x; - * pkm2 = ( (r*(r+2.0)-t)*pk - r*x*pkp1 )/t; - * pkp1 = pk; - * pk = pkm1; - * pkm1 = pkm2; - * r -= 2.0; - * } - */ - k -= 1.0; - } while (k > (kf + 0.5)); - - /* Take the larger of the last two iterates - * on the theory that it may have less cancellation error. - */ - - if (cancel) { - if ((kf >= 0.0) && (std::abs(pk) > std::abs(pkm1))) { - k += 1.0; - pkm2 = pk; - } - } - *newn = k; - return (pkm2); - } - - /* Ascending power series for Jv(x). - * AMS55 #9.1.10. - */ - - XSF_HOST_DEVICE inline double jv_jvs(double n, double x) { - double t, u, y, z, k; - int ex, sgngam; - - z = -x * x / 4.0; - u = 1.0; - y = u; - k = 1.0; - t = 1.0; - - while (t > MACHEP) { - u *= z / (k * (n + k)); - y += u; - k += 1.0; - if (y != 0) - t = std::abs(u / y); - } - t = std::frexp(0.5 * x, &ex); - ex = ex * n; - if ((ex > -1023) && (ex < 1023) && (n > 0.0) && (n < (MAXGAM - 1.0))) { - t = std::pow(0.5 * x, n) * xsf::cephes::rgamma(n + 1.0); - y *= t; - } else { - t = n * std::log(0.5 * x) - lgam_sgn(n + 1.0, &sgngam); - if (y < 0) { - sgngam = -sgngam; - y = -y; - } - t += std::log(y); - if (t < -MAXLOG) { - return (0.0); - } - if (t > MAXLOG) { - set_error("Jv", SF_ERROR_OVERFLOW, NULL); - return (std::numeric_limits::infinity()); - } - y = sgngam * std::exp(t); - } - return (y); - } - - /* Hankel's asymptotic expansion - * for large x. - * AMS55 #9.2.5. - */ - - XSF_HOST_DEVICE inline double jv_hankel(double n, double x) { - double t, u, z, k, sign, conv; - double p, q, j, m, pp, qq; - int flag; - - m = 4.0 * n * n; - j = 1.0; - z = 8.0 * x; - k = 1.0; - p = 1.0; - u = (m - 1.0) / z; - q = u; - sign = 1.0; - conv = 1.0; - flag = 0; - t = 1.0; - pp = 1.0e38; - qq = 1.0e38; - - while (t > MACHEP) { - k += 2.0; - j += 1.0; - sign = -sign; - u *= (m - k * k) / (j * z); - p += sign * u; - k += 2.0; - j += 1.0; - u *= (m - k * k) / (j * z); - q += sign * u; - t = std::abs(u / p); - if (t < conv) { - conv = t; - qq = q; - pp = p; - flag = 1; - } - /* stop if the terms start getting larger */ - if ((flag != 0) && (t > conv)) { - goto hank1; - } - } - - hank1: - u = x - (0.5 * n + 0.25) * M_PI; - t = std::sqrt(2.0 / (M_PI * x)) * (pp * std::cos(u) - qq * std::sin(u)); - return (t); - } - - /* Asymptotic expansion for transition region, - * n large and x close to n. - * AMS55 #9.3.23. - */ - - constexpr double jv_PF2[] = {-9.0000000000000000000e-2, 8.5714285714285714286e-2}; - - constexpr double jv_PF3[] = {1.3671428571428571429e-1, -5.4920634920634920635e-2, -4.4444444444444444444e-3}; - - constexpr double jv_PF4[] = {1.3500000000000000000e-3, -1.6036054421768707483e-1, 4.2590187590187590188e-2, - 2.7330447330447330447e-3}; - - constexpr double jv_PG1[] = {-2.4285714285714285714e-1, 1.4285714285714285714e-2}; - - constexpr double jv_PG2[] = {-9.0000000000000000000e-3, 1.9396825396825396825e-1, -1.1746031746031746032e-2}; - - constexpr double jv_PG3[] = {1.9607142857142857143e-2, -1.5983694083694083694e-1, 6.3838383838383838384e-3}; - - XSF_HOST_DEVICE inline double jv_jnt(double n, double x) { - double z, zz, z3; - double cbn, n23, cbtwo; - double ai, aip, bi, bip; /* Airy functions */ - double nk, fk, gk, pp, qq; - double F[5], G[4]; - int k; - - cbn = cbrt(n); - z = (x - n) / cbn; - cbtwo = cbrt(2.0); - - /* Airy function */ - zz = -cbtwo * z; - xsf::cephes::airy(zz, &ai, &aip, &bi, &bip); - - /* polynomials in expansion */ - zz = z * z; - z3 = zz * z; - F[0] = 1.0; - F[1] = -z / 5.0; - F[2] = xsf::cephes::polevl(z3, jv_PF2, 1) * zz; - F[3] = xsf::cephes::polevl(z3, jv_PF3, 2); - F[4] = xsf::cephes::polevl(z3, jv_PF4, 3) * z; - G[0] = 0.3 * zz; - G[1] = xsf::cephes::polevl(z3, jv_PG1, 1); - G[2] = xsf::cephes::polevl(z3, jv_PG2, 2) * z; - G[3] = xsf::cephes::polevl(z3, jv_PG3, 2) * zz; - - pp = 0.0; - qq = 0.0; - nk = 1.0; - n23 = cbrt(n * n); - - for (k = 0; k <= 4; k++) { - fk = F[k] * nk; - pp += fk; - if (k != 4) { - gk = G[k] * nk; - qq += gk; - } - nk /= n23; - } - - fk = cbtwo * ai * pp / cbn + cbrt(4.0) * aip * qq / n; - return (fk); - } - - /* Asymptotic expansion for large n. - * AMS55 #9.3.35. - */ - - constexpr double jv_lambda[] = {1.0, - 1.041666666666666666666667E-1, - 8.355034722222222222222222E-2, - 1.282265745563271604938272E-1, - 2.918490264641404642489712E-1, - 8.816272674437576524187671E-1, - 3.321408281862767544702647E+0, - 1.499576298686255465867237E+1, - 7.892301301158651813848139E+1, - 4.744515388682643231611949E+2, - 3.207490090890661934704328E+3}; - - constexpr double jv_mu[] = {1.0, - -1.458333333333333333333333E-1, - -9.874131944444444444444444E-2, - -1.433120539158950617283951E-1, - -3.172272026784135480967078E-1, - -9.424291479571202491373028E-1, - -3.511203040826354261542798E+0, - -1.572726362036804512982712E+1, - -8.228143909718594444224656E+1, - -4.923553705236705240352022E+2, - -3.316218568547972508762102E+3}; - - constexpr double jv_P1[] = {-2.083333333333333333333333E-1, 1.250000000000000000000000E-1}; - - constexpr double jv_P2[] = {3.342013888888888888888889E-1, -4.010416666666666666666667E-1, - 7.031250000000000000000000E-2}; - - constexpr double jv_P3[] = {-1.025812596450617283950617E+0, 1.846462673611111111111111E+0, - -8.912109375000000000000000E-1, 7.324218750000000000000000E-2}; - - constexpr double jv_P4[] = {4.669584423426247427983539E+0, -1.120700261622299382716049E+1, - 8.789123535156250000000000E+0, -2.364086914062500000000000E+0, - 1.121520996093750000000000E-1}; - - constexpr double jv_P5[] = {-2.8212072558200244877E1, 8.4636217674600734632E1, -9.1818241543240017361E1, - 4.2534998745388454861E1, -7.3687943594796316964E0, 2.27108001708984375E-1}; - - constexpr double jv_P6[] = {2.1257013003921712286E2, -7.6525246814118164230E2, 1.0599904525279998779E3, - -6.9957962737613254123E2, 2.1819051174421159048E2, -2.6491430486951555525E1, - 5.7250142097473144531E-1}; - - constexpr double jv_P7[] = {-1.9194576623184069963E3, 8.0617221817373093845E3, -1.3586550006434137439E4, - 1.1655393336864533248E4, -5.3056469786134031084E3, 1.2009029132163524628E3, - -1.0809091978839465550E2, 1.7277275025844573975E0}; - - XSF_HOST_DEVICE inline double jv_jnx(double n, double x) { - double zeta, sqz, zz, zp, np; - double cbn, n23, t, z, sz; - double pp, qq, z32i, zzi; - double ak, bk, akl, bkl; - int sign, doa, dob, nflg, k, s, tk, tkp1, m; - double u[8]; - double ai, aip, bi, bip; - - /* Test for x very close to n. Use expansion for transition region if so. */ - cbn = cbrt(n); - z = (x - n) / cbn; - if (std::abs(z) <= 0.7) { - return (jv_jnt(n, x)); - } - - z = x / n; - zz = 1.0 - z * z; - if (zz == 0.0) { - return (0.0); - } - - if (zz > 0.0) { - sz = std::sqrt(zz); - t = 1.5 * (std::log((1.0 + sz) / z) - sz); /* zeta ** 3/2 */ - zeta = cbrt(t * t); - nflg = 1; - } else { - sz = std::sqrt(-zz); - t = 1.5 * (sz - std::acos(1.0 / z)); - zeta = -cbrt(t * t); - nflg = -1; - } - z32i = std::abs(1.0 / t); - sqz = cbrt(t); - - /* Airy function */ - n23 = cbrt(n * n); - t = n23 * zeta; - - xsf::cephes::airy(t, &ai, &aip, &bi, &bip); - - /* polynomials in expansion */ - u[0] = 1.0; - zzi = 1.0 / zz; - u[1] = xsf::cephes::polevl(zzi, jv_P1, 1) / sz; - u[2] = xsf::cephes::polevl(zzi, jv_P2, 2) / zz; - u[3] = xsf::cephes::polevl(zzi, jv_P3, 3) / (sz * zz); - pp = zz * zz; - u[4] = xsf::cephes::polevl(zzi, jv_P4, 4) / pp; - u[5] = xsf::cephes::polevl(zzi, jv_P5, 5) / (pp * sz); - pp *= zz; - u[6] = xsf::cephes::polevl(zzi, jv_P6, 6) / pp; - u[7] = xsf::cephes::polevl(zzi, jv_P7, 7) / (pp * sz); - - pp = 0.0; - qq = 0.0; - np = 1.0; - /* flags to stop when terms get larger */ - doa = 1; - dob = 1; - akl = std::numeric_limits::infinity(); - bkl = std::numeric_limits::infinity(); - - for (k = 0; k <= 3; k++) { - tk = 2 * k; - tkp1 = tk + 1; - zp = 1.0; - ak = 0.0; - bk = 0.0; - for (s = 0; s <= tk; s++) { - if (doa) { - if ((s & 3) > 1) - sign = nflg; - else - sign = 1; - ak += sign * jv_mu[s] * zp * u[tk - s]; - } - - if (dob) { - m = tkp1 - s; - if (((m + 1) & 3) > 1) - sign = nflg; - else - sign = 1; - bk += sign * jv_lambda[s] * zp * u[m]; - } - zp *= z32i; - } - - if (doa) { - ak *= np; - t = std::abs(ak); - if (t < akl) { - akl = t; - pp += ak; - } else - doa = 0; - } - - if (dob) { - bk += jv_lambda[tkp1] * zp * u[0]; - bk *= -np / sqz; - t = std::abs(bk); - if (t < bkl) { - bkl = t; - qq += bk; - } else - dob = 0; - } - if (np < MACHEP) - break; - np /= n * n; - } - - /* normalizing factor ( 4*zeta/(1 - z**2) )**1/4 */ - t = 4.0 * zeta / zz; - t = sqrt(sqrt(t)); - - t *= ai * pp / cbrt(n) + aip * qq / (n23 * n); - return (t); - } - - } // namespace detail - - XSF_HOST_DEVICE inline double jv(double n, double x) { - double k, q, t, y, an; - int i, sign, nint; - - nint = 0; /* Flag for integer n */ - sign = 1; /* Flag for sign inversion */ - an = std::abs(n); - y = std::floor(an); - if (y == an) { - nint = 1; - i = an - 16384.0 * std::floor(an / 16384.0); - if (n < 0.0) { - if (i & 1) - sign = -sign; - n = an; - } - if (x < 0.0) { - if (i & 1) - sign = -sign; - x = -x; - } - if (n == 0.0) - return (j0(x)); - if (n == 1.0) - return (sign * j1(x)); - } - - if ((x < 0.0) && (y != an)) { - set_error("Jv", SF_ERROR_DOMAIN, NULL); - y = std::numeric_limits::quiet_NaN(); - goto done; - } - - if (x == 0 && n < 0 && !nint) { - set_error("Jv", SF_ERROR_OVERFLOW, NULL); - return std::numeric_limits::infinity() * rgamma(n + 1); - } - - y = std::abs(x); - - if (y * y < std::abs(n + 1) * detail::MACHEP) { - return std::pow(0.5 * x, n) * rgamma(n + 1); - } - - k = 3.6 * std::sqrt(y); - t = 3.6 * std::sqrt(an); - if ((y < t) && (an > 21.0)) { - return (sign * detail::jv_jvs(n, x)); - } - if ((an < k) && (y > 21.0)) - return (sign * detail::jv_hankel(n, x)); - - if (an < 500.0) { - /* Note: if x is too large, the continued fraction will fail; but then the - * Hankel expansion can be used. */ - if (nint != 0) { - k = 0.0; - q = detail::jv_recur(&n, x, &k, 1); - if (k == 0.0) { - y = j0(x) / q; - goto done; - } - if (k == 1.0) { - y = j1(x) / q; - goto done; - } - } - - if (an > 2.0 * y) - goto rlarger; - - if ((n >= 0.0) && (n < 20.0) && (y > 6.0) && (y < 20.0)) { - /* Recur backwards from a larger value of n */ - rlarger: - k = n; - - y = y + an + 1.0; - if (y < 30.0) - y = 30.0; - y = n + std::floor(y - n); - q = detail::jv_recur(&y, x, &k, 0); - y = detail::jv_jvs(y, x) * q; - goto done; - } - - if (k <= 30.0) { - k = 2.0; - } else if (k < 90.0) { - k = (3 * k) / 4; - } - if (an > (k + 3.0)) { - if (n < 0.0) { - k = -k; - } - q = n - std::floor(n); - k = std::floor(k) + q; - if (n > 0.0) { - q = detail::jv_recur(&n, x, &k, 1); - } else { - t = k; - k = n; - q = detail::jv_recur(&t, x, &k, 1); - k = t; - } - if (q == 0.0) { - y = 0.0; - goto done; - } - } else { - k = n; - q = 1.0; - } - - /* boundary between convergence of - * power series and Hankel expansion - */ - y = std::abs(k); - if (y < 26.0) - t = (0.0083 * y + 0.09) * y + 12.9; - else - t = 0.9 * y; - - if (x > t) - y = detail::jv_hankel(k, x); - else - y = detail::jv_jvs(k, x); - if (n > 0.0) - y /= q; - else - y *= q; - } - - else { - /* For large n, use the uniform expansion or the transitional expansion. - * But if x is of the order of n**2, these may blow up, whereas the - * Hankel expansion will then work. - */ - if (n < 0.0) { - set_error("jv", SF_ERROR_LOSS, NULL); - y = std::numeric_limits::quiet_NaN(); - goto done; - } - t = x / n; - t /= n; - if (t > 0.3) - y = detail::jv_hankel(n, x); - else - y = detail::jv_jnx(n, x); - } - - done: - return (sign * y); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/k0.h b/scipy/special/xsf/cephes/k0.h deleted file mode 100644 index f617b93c7300..000000000000 --- a/scipy/special/xsf/cephes/k0.h +++ /dev/null @@ -1,164 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* k0.c - * - * Modified Bessel function, third kind, order zero - * - * - * - * SYNOPSIS: - * - * double x, y, k0(); - * - * y = k0( x ); - * - * - * - * DESCRIPTION: - * - * Returns modified Bessel function of the third kind - * of order zero of the argument. - * - * The range is partitioned into the two intervals [0,8] and - * (8, infinity). Chebyshev polynomial expansions are employed - * in each interval. - * - * - * - * ACCURACY: - * - * Tested at 2000 random points between 0 and 8. Peak absolute - * error (relative when K0 > 1) was 1.46e-14; rms, 4.26e-15. - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 1.2e-15 1.6e-16 - * - * ERROR MESSAGES: - * - * message condition value returned - * K0 domain x <= 0 INFINITY - * - */ -/* k0e() - * - * Modified Bessel function, third kind, order zero, - * exponentially scaled - * - * - * - * SYNOPSIS: - * - * double x, y, k0e(); - * - * y = k0e( x ); - * - * - * - * DESCRIPTION: - * - * Returns exponentially scaled modified Bessel function - * of the third kind of order zero of the argument. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 1.4e-15 1.4e-16 - * See k0(). - * - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 2000 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "chbevl.h" -#include "i0.h" - -namespace xsf { -namespace cephes { - - namespace detail { - /* Chebyshev coefficients for K0(x) + log(x/2) I0(x) - * in the interval [0,2]. The odd order coefficients are all - * zero; only the even order coefficients are listed. - * - * lim(x->0){ K0(x) + log(x/2) I0(x) } = -EUL. - */ - - constexpr double k0_A[] = {1.37446543561352307156E-16, 4.25981614279661018399E-14, 1.03496952576338420167E-11, - 1.90451637722020886025E-9, 2.53479107902614945675E-7, 2.28621210311945178607E-5, - 1.26461541144692592338E-3, 3.59799365153615016266E-2, 3.44289899924628486886E-1, - -5.35327393233902768720E-1}; - - /* Chebyshev coefficients for exp(x) sqrt(x) K0(x) - * in the inverted interval [2,infinity]. - * - * lim(x->inf){ exp(x) sqrt(x) K0(x) } = sqrt(pi/2). - */ - constexpr double k0_B[] = { - 5.30043377268626276149E-18, -1.64758043015242134646E-17, 5.21039150503902756861E-17, - -1.67823109680541210385E-16, 5.51205597852431940784E-16, -1.84859337734377901440E-15, - 6.34007647740507060557E-15, -2.22751332699166985548E-14, 8.03289077536357521100E-14, - -2.98009692317273043925E-13, 1.14034058820847496303E-12, -4.51459788337394416547E-12, - 1.85594911495471785253E-11, -7.95748924447710747776E-11, 3.57739728140030116597E-10, - -1.69753450938905987466E-9, 8.57403401741422608519E-9, -4.66048989768794782956E-8, - 2.76681363944501510342E-7, -1.83175552271911948767E-6, 1.39498137188764993662E-5, - -1.28495495816278026384E-4, 1.56988388573005337491E-3, -3.14481013119645005427E-2, - 2.44030308206595545468E0}; - - } // namespace detail - - XSF_HOST_DEVICE inline double k0(double x) { - double y, z; - - if (x == 0.0) { - set_error("k0", SF_ERROR_SINGULAR, NULL); - return std::numeric_limits::infinity(); - } else if (x < 0.0) { - set_error("k0", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - if (x <= 2.0) { - y = x * x - 2.0; - y = chbevl(y, detail::k0_A, 10) - std::log(0.5 * x) * i0(x); - return (y); - } - z = 8.0 / x - 2.0; - y = std::exp(-x) * chbevl(z, detail::k0_B, 25) / std::sqrt(x); - return (y); - } - - XSF_HOST_DEVICE double inline k0e(double x) { - double y; - - if (x == 0.0) { - set_error("k0e", SF_ERROR_SINGULAR, NULL); - return std::numeric_limits::infinity(); - } else if (x < 0.0) { - set_error("k0e", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - if (x <= 2.0) { - y = x * x - 2.0; - y = chbevl(y, detail::k0_A, 10) - std::log(0.5 * x) * i0(x); - return (y * exp(x)); - } - - y = chbevl(8.0 / x - 2.0, detail::k0_B, 25) / std::sqrt(x); - return (y); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/k1.h b/scipy/special/xsf/cephes/k1.h deleted file mode 100644 index 96594fd9c345..000000000000 --- a/scipy/special/xsf/cephes/k1.h +++ /dev/null @@ -1,163 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* k1.c - * - * Modified Bessel function, third kind, order one - * - * - * - * SYNOPSIS: - * - * double x, y, k1(); - * - * y = k1( x ); - * - * - * - * DESCRIPTION: - * - * Computes the modified Bessel function of the third kind - * of order one of the argument. - * - * The range is partitioned into the two intervals [0,2] and - * (2, infinity). Chebyshev polynomial expansions are employed - * in each interval. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 1.2e-15 1.6e-16 - * - * ERROR MESSAGES: - * - * message condition value returned - * k1 domain x <= 0 INFINITY - * - */ -/* k1e.c - * - * Modified Bessel function, third kind, order one, - * exponentially scaled - * - * - * - * SYNOPSIS: - * - * double x, y, k1e(); - * - * y = k1e( x ); - * - * - * - * DESCRIPTION: - * - * Returns exponentially scaled modified Bessel function - * of the third kind of order one of the argument: - * - * k1e(x) = exp(x) * k1(x). - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 7.8e-16 1.2e-16 - * See k1(). - * - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 2000 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "chbevl.h" -#include "const.h" - -namespace xsf { -namespace cephes { - - namespace detail { - /* Chebyshev coefficients for x(K1(x) - log(x/2) I1(x)) - * in the interval [0,2]. - * - * lim(x->0){ x(K1(x) - log(x/2) I1(x)) } = 1. - */ - - constexpr double k1_A[] = { - -7.02386347938628759343E-18, -2.42744985051936593393E-15, -6.66690169419932900609E-13, - -1.41148839263352776110E-10, -2.21338763073472585583E-8, -2.43340614156596823496E-6, - -1.73028895751305206302E-4, -6.97572385963986435018E-3, -1.22611180822657148235E-1, - -3.53155960776544875667E-1, 1.52530022733894777053E0}; - - /* Chebyshev coefficients for exp(x) sqrt(x) K1(x) - * in the interval [2,infinity]. - * - * lim(x->inf){ exp(x) sqrt(x) K1(x) } = sqrt(pi/2). - */ - constexpr double k1_B[] = { - -5.75674448366501715755E-18, 1.79405087314755922667E-17, -5.68946255844285935196E-17, - 1.83809354436663880070E-16, -6.05704724837331885336E-16, 2.03870316562433424052E-15, - -7.01983709041831346144E-15, 2.47715442448130437068E-14, -8.97670518232499435011E-14, - 3.34841966607842919884E-13, -1.28917396095102890680E-12, 5.13963967348173025100E-12, - -2.12996783842756842877E-11, 9.21831518760500529508E-11, -4.19035475934189648750E-10, - 2.01504975519703286596E-9, -1.03457624656780970260E-8, 5.74108412545004946722E-8, - -3.50196060308781257119E-7, 2.40648494783721712015E-6, -1.93619797416608296024E-5, - 1.95215518471351631108E-4, -2.85781685962277938680E-3, 1.03923736576817238437E-1, - 2.72062619048444266945E0}; - - } // namespace detail - - XSF_HOST_DEVICE inline double k1(double x) { - double y, z; - - if (x == 0.0) { - set_error("k1", SF_ERROR_SINGULAR, NULL); - return std::numeric_limits::infinity(); - } else if (x < 0.0) { - set_error("k1", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - z = 0.5 * x; - - if (x <= 2.0) { - y = x * x - 2.0; - y = std::log(z) * i1(x) + chbevl(y, detail::k1_A, 11) / x; - return (y); - } - - return (std::exp(-x) * chbevl(8.0 / x - 2.0, detail::k1_B, 25) / std::sqrt(x)); - } - - XSF_HOST_DEVICE double k1e(double x) { - double y; - - if (x == 0.0) { - set_error("k1e", SF_ERROR_SINGULAR, NULL); - return std::numeric_limits::infinity(); - } else if (x < 0.0) { - set_error("k1e", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - if (x <= 2.0) { - y = x * x - 2.0; - y = std::log(0.5 * x) * i1(x) + chbevl(y, detail::k1_A, 11) / x; - return (y * exp(x)); - } - - return (chbevl(8.0 / x - 2.0, detail::k1_B, 25) / std::sqrt(x)); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/kn.h b/scipy/special/xsf/cephes/kn.h deleted file mode 100644 index 31bc9fd7f735..000000000000 --- a/scipy/special/xsf/cephes/kn.h +++ /dev/null @@ -1,243 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* kn.c - * - * Modified Bessel function, third kind, integer order - * - * - * - * SYNOPSIS: - * - * double x, y, kn(); - * int n; - * - * y = kn( n, x ); - * - * - * - * DESCRIPTION: - * - * Returns modified Bessel function of the third kind - * of order n of the argument. - * - * The range is partitioned into the two intervals [0,9.55] and - * (9.55, infinity). An ascending power series is used in the - * low range, and an asymptotic expansion in the high range. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,30 90000 1.8e-8 3.0e-10 - * - * Error is high only near the crossover point x = 9.55 - * between the two expansions used. - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 1988, 2000 by Stephen L. Moshier - */ - -/* - * Algorithm for Kn. - * n-1 - * -n - (n-k-1)! 2 k - * K (x) = 0.5 (x/2) > -------- (-x /4) - * n - k! - * k=0 - * - * inf. 2 k - * n n - (x /4) - * + (-1) 0.5(x/2) > {p(k+1) + p(n+k+1) - 2log(x/2)} --------- - * - k! (n+k)! - * k=0 - * - * where p(m) is the psi function: p(1) = -EUL and - * - * m-1 - * - - * p(m) = -EUL + > 1/k - * - - * k=1 - * - * For large x, - * 2 2 2 - * u-1 (u-1 )(u-3 ) - * K (z) = sqrt(pi/2z) exp(-z) { 1 + ------- + ------------ + ...} - * v 1 2 - * 1! (8z) 2! (8z) - * asymptotically, where - * - * 2 - * u = 4 v . - * - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr int kn_MAXFAC = 31; - - } - - XSF_HOST_DEVICE inline double kn(int nn, double x) { - double k, kf, nk1f, nkf, zn, t, s, z0, z; - double ans, fn, pn, pk, zmn, tlg, tox; - int i, n; - - if (nn < 0) - n = -nn; - else - n = nn; - - if (n > detail::kn_MAXFAC) { - overf: - set_error("kn", SF_ERROR_OVERFLOW, NULL); - return (std::numeric_limits::infinity()); - } - - if (x <= 0.0) { - if (x < 0.0) { - set_error("kn", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } else { - set_error("kn", SF_ERROR_SINGULAR, NULL); - return std::numeric_limits::infinity(); - } - } - - if (x > 9.55) - goto asymp; - - ans = 0.0; - z0 = 0.25 * x * x; - fn = 1.0; - pn = 0.0; - zmn = 1.0; - tox = 2.0 / x; - - if (n > 0) { - /* compute factorial of n and psi(n) */ - pn = -detail::SCIPY_EULER; - k = 1.0; - for (i = 1; i < n; i++) { - pn += 1.0 / k; - k += 1.0; - fn *= k; - } - - zmn = tox; - - if (n == 1) { - ans = 1.0 / x; - } else { - nk1f = fn / n; - kf = 1.0; - s = nk1f; - z = -z0; - zn = 1.0; - for (i = 1; i < n; i++) { - nk1f = nk1f / (n - i); - kf = kf * i; - zn *= z; - t = nk1f * zn / kf; - s += t; - if ((std::numeric_limits::max() - std::abs(t)) < std::abs(s)) { - goto overf; - } - if ((tox > 1.0) && ((std::numeric_limits::max() / tox) < zmn)) { - goto overf; - } - zmn *= tox; - } - s *= 0.5; - t = std::abs(s); - if ((zmn > 1.0) && ((std::numeric_limits::max() / zmn) < t)) { - goto overf; - } - if ((t > 1.0) && ((std::numeric_limits::max() / t) < zmn)) { - goto overf; - } - ans = s * zmn; - } - } - - tlg = 2.0 * log(0.5 * x); - pk = -detail::SCIPY_EULER; - if (n == 0) { - pn = pk; - t = 1.0; - } else { - pn = pn + 1.0 / n; - t = 1.0 / fn; - } - s = (pk + pn - tlg) * t; - k = 1.0; - do { - t *= z0 / (k * (k + n)); - pk += 1.0 / k; - pn += 1.0 / (k + n); - s += (pk + pn - tlg) * t; - k += 1.0; - } while (fabs(t / s) > detail::MACHEP); - - s = 0.5 * s / zmn; - if (n & 1) { - s = -s; - } - ans += s; - - return (ans); - - /* Asymptotic expansion for Kn(x) */ - /* Converges to 1.4e-17 for x > 18.4 */ - - asymp: - - if (x > detail::MAXLOG) { - set_error("kn", SF_ERROR_UNDERFLOW, NULL); - return (0.0); - } - k = n; - pn = 4.0 * k * k; - pk = 1.0; - z0 = 8.0 * x; - fn = 1.0; - t = 1.0; - s = t; - nkf = std::numeric_limits::infinity(); - i = 0; - do { - z = pn - pk * pk; - t = t * z / (fn * z0); - nk1f = std::abs(t); - if ((i >= n) && (nk1f > nkf)) { - goto adone; - } - nkf = nk1f; - s += t; - fn += 1.0; - pk += 2.0; - i += 1; - } while (std::abs(t / s) > detail::MACHEP); - - adone: - ans = std::exp(-x) * std::sqrt(M_PI / (2.0 * x)) * s; - return (ans); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/kolmogorov.h b/scipy/special/xsf/cephes/kolmogorov.h deleted file mode 100644 index 20fba8b9d427..000000000000 --- a/scipy/special/xsf/cephes/kolmogorov.h +++ /dev/null @@ -1,1041 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* File altered for inclusion in cephes module for Python: - * Main loop commented out.... */ -/* Travis Oliphant Nov. 1998 */ - -/* Re Kolmogorov statistics, here is Birnbaum and Tingey's (actually it was already present - * in Smirnov's paper) formula for the - * distribution of D+, the maximum of all positive deviations between a - * theoretical distribution function P(x) and an empirical one Sn(x) - * from n samples. - * - * + - * D = sup [P(x) - S (x)] - * n -inf < x < inf n - * - * - * [n(1-d)] - * + - v-1 n-v - * Pr{D > d} = > C d (d + v/n) (1 - d - v/n) - * n - n v - * v=0 - * - * (also equals the following sum, but note the terms may be large and alternating in sign) - * See Smirnov 1944, Dwass 1959 - * n - * - v-1 n-v - * = 1 - > C d (d + v/n) (1 - d - v/n) - * - n v - * v=[n(1-d)]+1 - * - * [n(1-d)] is the largest integer not exceeding n(1-d). - * nCv is the number of combinations of n things taken v at a time. - - * Sources: - * [1] Smirnov, N.V. "Approximate laws of distribution of random variables from empirical data" - * Usp. Mat. Nauk, 1944. http://mi.mathnet.ru/umn8798 - * [2] Birnbaum, Z. W. and Tingey, Fred H. - * "One-Sided Confidence Contours for Probability Distribution Functions", - * Ann. Math. Statist. 1951. https://doi.org/10.1214/aoms/1177729550 - * [3] Dwass, Meyer, "The Distribution of a Generalized $\mathrm{D}^+_n$ Statistic", - * Ann. Math. Statist., 1959. https://doi.org/10.1214/aoms/1177706085 - * [4] van Mulbregt, Paul, "Computing the Cumulative Distribution Function and Quantiles of the One-sided - Kolmogorov-Smirnov Statistic" - * http://arxiv.org/abs/1802.06966 - * [5] van Mulbregt, Paul, "Computing the Cumulative Distribution Function and Quantiles of the limit of the Two-sided - Kolmogorov-Smirnov Statistic" - * https://arxiv.org/abs/1803.00426 - * - */ - -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "dd_real.h" -#include "unity.h" - -namespace xsf { -namespace cephes { - - namespace detail { - /* ************************************************************************ */ - /* Algorithm Configuration */ - - constexpr int KOLMOG_MAXITER = 500; - - /* - * Kolmogorov Two-sided: - * Switchover between the two series to compute K(x) - * 0 <= x <= KOLMOG_CUTOVER and - * KOLMOG_CUTOVER < x < infty - */ - constexpr double KOLMOG_CUTOVER = 0.82; - - /* - * Smirnov One-sided: - * n larger than SMIRNOV_MAX_COMPUTE_N will result in an approximation - */ - constexpr int SMIRNOV_MAX_COMPUTE_N = 1000000; - - /* - * Use the upper sum formula, if the number of terms is at most SM_UPPER_MAX_TERMS, - * and n is at least SM_UPPERSUM_MIN_N - * Don't use the upper sum if lots of terms are involved as the series alternates - * sign and the terms get much bigger than 1. - */ - constexpr int SM_UPPER_MAX_TERMS = 3; - constexpr int SM_UPPERSUM_MIN_N = 10; - - /* ************************************************************************ */ - /* ************************************************************************ */ - - /* exp() of anything below this returns 0 */ - constexpr int MIN_EXPABLE = (-708 - 38); - - /* Class to hold the CDF, SF and PDF, which are computed simultaneously */ - struct ThreeProbs { - double sf; - double cdf; - double pdf; - }; - - constexpr double _xtol = std::numeric_limits::epsilon(); - constexpr double _rtol = 2 * _xtol; - - XSF_HOST_DEVICE inline bool _within_tol(double x, double y, double atol, double rtol) { - double diff = std::abs(x - y); - bool result = (diff <= (atol + rtol * std::abs(y))); - return result; - } - - /* ************************************************************************ */ - /* Kolmogorov : Two-sided **************************** */ - /* ************************************************************************ */ - - XSF_HOST_DEVICE inline ThreeProbs _kolmogorov(double x) { - double P = 1.0; - double D = 0; - double sf, cdf, pdf; - - if (std::isnan(x)) { - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN()}; - } - if (x <= 0) { - return {1.0, 0.0, 0}; - } - /* x <= 0.040611972203751713 */ - if (x <= M_PI / std::sqrt(-MIN_EXPABLE * 8)) { - return {1.0, 0.0, 0}; - } - - P = 1.0; - if (x <= KOLMOG_CUTOVER) { - /* - * u = e^(-pi^2/(8x^2)) - * w = sqrt(2pi)/x - * P = w*u * (1 + u^8 + u^24 + u^48 + ...) - */ - double w = std::sqrt(2 * M_PI) / x; - double logu8 = -M_PI * M_PI / (x * x); /* log(u^8) */ - double u = std::exp(logu8 / 8); - if (u == 0) { - /* - * P = w*u, but u < 1e-308, and w > 1, - * so compute as logs, then exponentiate - */ - double logP = logu8 / 8 + std::log(w); - P = std::exp(logP); - } else { - /* Just unroll the loop, 3 iterations */ - double u8 = std::exp(logu8); - double u8cub = std::pow(u8, 3); - P = 1 + u8cub * P; - D = 5 * 5 + u8cub * D; - P = 1 + u8 * u8 * P; - D = 3 * 3 + u8 * u8 * D; - P = 1 + u8 * P; - D = 1 * 1 + u8 * D; - - D = M_PI * M_PI / 4 / (x * x) * D - P; - D *= w * u / x; - P = w * u * P; - } - cdf = P; - sf = 1 - P; - pdf = D; - } else { - /* - * v = e^(-2x^2) - * P = 2 (v - v^4 + v^9 - v^16 + ...) - * = 2v(1 - v^3*(1 - v^5*(1 - v^7*(1 - ...))) - */ - double logv = -2 * x * x; - double v = std::exp(logv); - /* - * Want q^((2k-1)^2)(1-q^(4k-1)) / q(1-q^3) < epsilon to break out of loop. - * With KOLMOG_CUTOVER ~ 0.82, k <= 4. Just unroll the loop, 4 iterations - */ - double vsq = v * v; - double v3 = std::pow(v, 3); - double vpwr; - - vpwr = v3 * v3 * v; /* v**7 */ - P = 1 - vpwr * P; /* P <- 1 - (1-v**(2k-1)) * P */ - D = 3 * 3 - vpwr * D; - - vpwr = v3 * vsq; - P = 1 - vpwr * P; - D = 2 * 2 - vpwr * D; - - vpwr = v3; - P = 1 - vpwr * P; - D = 1 * 1 - vpwr * D; - - P = 2 * v * P; - D = 8 * v * x * D; - sf = P; - cdf = 1 - sf; - pdf = D; - } - pdf = std::fmax(0, pdf); - cdf = std::clamp(cdf, 0.0, 1.0); - sf = std::clamp(sf, 0.0, 1.0); - return {sf, cdf, pdf}; - } - - /* Find x such kolmogorov(x)=psf, kolmogc(x)=pcdf */ - XSF_HOST_DEVICE inline double _kolmogi(double psf, double pcdf) { - double x, t; - double xmin = 0; - double xmax = std::numeric_limits::infinity(); - int iterations; - double a = xmin, b = xmax; - - if (!(psf >= 0.0 && pcdf >= 0.0 && pcdf <= 1.0 && psf <= 1.0)) { - set_error("kolmogi", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - if (std::abs(1.0 - pcdf - psf) > 4 * std::numeric_limits::epsilon()) { - set_error("kolmogi", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - if (pcdf == 0.0) { - return 0.0; - } - if (psf == 0.0) { - return std::numeric_limits::infinity(); - } - - if (pcdf <= 0.5) { - /* p ~ (sqrt(2pi)/x) *exp(-pi^2/8x^2). Generate lower and upper bounds */ - double logpcdf = std::log(pcdf); - /* Now that 1 >= x >= sqrt(p) */ - /* Iterate twice: x <- pi/(sqrt(8) sqrt(log(sqrt(2pi)) - log(x) - log(pdf))) */ - a = M_PI / (2 * M_SQRT2 * std::sqrt(-(logpcdf + logpcdf / 2 - LOGSQRT2PI))); - b = M_PI / (2 * M_SQRT2 * std::sqrt(-(logpcdf + 0 - LOGSQRT2PI))); - a = M_PI / (2 * M_SQRT2 * std::sqrt(-(logpcdf + std::log(a) - LOGSQRT2PI))); - b = M_PI / (2 * M_SQRT2 * std::sqrt(-(logpcdf + std::log(b) - LOGSQRT2PI))); - x = (a + b) / 2.0; - } else { - /* - * Based on the approximation p ~ 2 exp(-2x^2) - * Found that needed to replace psf with a slightly smaller number in the second element - * as otherwise _kolmogorov(b) came back as a very small number but with - * the same sign as _kolmogorov(a) - * kolmogi(0.5) = 0.82757355518990772 - * so (1-q^(-(4-1)*2*x^2)) = (1-exp(-6*0.8275^2) ~ (1-exp(-4.1) - */ - constexpr double jiggerb = 256 * std::numeric_limits::epsilon(); - double pba = psf / (1.0 - std::exp(-4)) / 2, pbb = psf * (1 - jiggerb) / 2; - double q0; - a = std::sqrt(-0.5 * std::log(pba)); - b = std::sqrt(-0.5 * std::log(pbb)); - /* - * Use inversion of - * p = q - q^4 + q^9 - q^16 + ...: - * q = p + p^4 + 4p^7 - p^9 + 22p^10 - 13p^12 + 140*p^13 ... - */ - { - double p = psf / 2.0; - double p2 = p * p; - double p3 = p * p * p; - q0 = 1 + p3 * (1 + p3 * (4 + p2 * (-1 + p * (22 + p2 * (-13 + 140 * p))))); - q0 *= p; - } - x = std::sqrt(-std::log(q0) / 2); - if (x < a || x > b) { - x = (a + b) / 2; - } - } - XSF_ASSERT(a <= b); - - iterations = 0; - do { - double x0 = x; - ThreeProbs probs = _kolmogorov(x0); - double df = ((pcdf < 0.5) ? (pcdf - probs.cdf) : (probs.sf - psf)); - double dfdx; - - if (std::abs(df) == 0) { - break; - } - /* Update the bracketing interval */ - if (df > 0 && x > a) { - a = x; - } else if (df < 0 && x < b) { - b = x; - } - - dfdx = -probs.pdf; - if (std::abs(dfdx) <= 0.0) { - x = (a + b) / 2; - t = x0 - x; - } else { - t = df / dfdx; - x = x0 - t; - } - - /* - * Check out-of-bounds. - * Not expecting this to happen often --- kolmogorov is convex near x=infinity and - * concave near x=0, and we should be approaching from the correct side. - * If out-of-bounds, replace x with a midpoint of the bracket. - */ - if (x >= a && x <= b) { - if (_within_tol(x, x0, _xtol, _rtol)) { - break; - } - if ((x == a) || (x == b)) { - x = (a + b) / 2.0; - /* If the bracket is already so small ... */ - if (x == a || x == b) { - break; - } - } - } else { - x = (a + b) / 2.0; - if (_within_tol(x, x0, _xtol, _rtol)) { - break; - } - } - - if (++iterations > KOLMOG_MAXITER) { - set_error("kolmogi", SF_ERROR_SLOW, NULL); - break; - } - } while (1); - return (x); - } - - } // namespace detail - - XSF_HOST_DEVICE inline double kolmogorov(double x) { - if (std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - return detail::_kolmogorov(x).sf; - } - - XSF_HOST_DEVICE inline double kolmogc(double x) { - if (std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - return detail::_kolmogorov(x).cdf; - } - - XSF_HOST_DEVICE inline double kolmogp(double x) { - if (std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - if (x <= 0) { - return -0.0; - } - return -detail::_kolmogorov(x).pdf; - } - - /* Functional inverse of Kolmogorov survival statistic for two-sided test. - * Finds x such that kolmogorov(x) = p. - */ - XSF_HOST_DEVICE inline double kolmogi(double p) { - if (std::isnan(p)) { - return std::numeric_limits::quiet_NaN(); - } - return detail::_kolmogi(p, 1 - p); - } - - /* Functional inverse of Kolmogorov cumulative statistic for two-sided test. - * Finds x such that kolmogc(x) = p = (or kolmogorov(x) = 1-p). - */ - XSF_HOST_DEVICE inline double kolmogci(double p) { - if (std::isnan(p)) { - return std::numeric_limits::quiet_NaN(); - } - return detail::_kolmogi(1 - p, p); - } - - namespace detail { - - /* ************************************************************************ */ - /* ********** Smirnov : One-sided ***************************************** */ - /* ************************************************************************ */ - - XSF_HOST_DEVICE inline double nextPowerOf2(double x) { - double q = std::ldexp(x, 1 - std::numeric_limits::digits); - double L = std::abs(q + x); - if (L == 0) { - L = std::abs(x); - } else { - int Lint = (int) (L); - if (Lint == L) { - L = Lint; - } - } - return L; - } - - XSF_HOST_DEVICE inline double modNX(int n, double x, int *pNXFloor, double *pNX) { - /* - * Compute floor(n*x) and remainder *exactly*. - * If remainder is too close to 1 (E.g. (1, -std::numeric_limits::epsilon()/2)) - * round up and adjust */ - double_double alphaD, nxD, nxfloorD; - int nxfloor; - double alpha; - - nxD = static_cast(n) * double_double(x); - nxfloorD = floor(nxD); - alphaD = nxD - nxfloorD; - alpha = alphaD.hi; - nxfloor = static_cast(nxfloorD); - XSF_ASSERT(alpha >= 0); - XSF_ASSERT(alpha <= 1); - if (alpha == 1) { - nxfloor += 1; - alpha = 0; - } - XSF_ASSERT(alpha < 1.0); - *pNX = static_cast(nxD); - *pNXFloor = nxfloor; - return alpha; - } - - /* - * The binomial coefficient C overflows a 64 bit double, as the 11-bit - * exponent is too small. - * Store C as (Cman:double_double, Cexpt:int). - * I.e a Mantissa/significand, and an exponent. - * Cman lies between 0.5 and 1, and the exponent has >=32-bit. - */ - XSF_HOST_DEVICE inline void updateBinomial(double_double *Cman, int *Cexpt, int n, int j) { - int expt; - double_double rat = double_double(n - j) / (j + 1.0); - double_double man2 = *Cman * rat; - man2 = frexp(man2, &expt); - XSF_ASSERT(man2 != 0.0); - *Cexpt += expt; - *Cman = man2; - } - - XSF_HOST_DEVICE double_double pow_D(const double_double &a, int m) { - /* - * Using dd_npwr() here would be quite time-consuming. - * Tradeoff accuracy-time by using pow(). - */ - - double ans, r, adj; - if (m <= 0) { - if (m == 0) { - return double_double(1.0); - } - return 1.0 / pow_D(a, -m); - } - if (a == 0.0) { - return double_double(0.0); - } - ans = std::pow(a.hi, m); - r = a.lo / a.hi; - adj = m * r; - if (std::abs(adj) > 1e-8) { - if (std::abs(adj) < 1e-4) { - /* Take 1st two terms of Taylor Series for (1+r)^m */ - adj += (m * r) * ((m - 1) / 2.0 * r); - } else { - /* Take exp of scaled log */ - adj = xsf::cephes::expm1(m * std::log1p(r)); - } - } - return double_double(ans) + ans * adj; - } - - XSF_HOST_DEVICE inline double pow2(double a, double b, int m) { - return static_cast(pow_D(double_double(a) + b, m)); - } - - /* - * Not 1024 as too big. Want _MAX_EXPONENT < 1023-52 so as to keep both - * elements of the double_double normalized - */ - constexpr int SM_MAX_EXPONENT = 960; - - XSF_HOST_DEVICE double_double pow2Scaled_D(const double_double &a, int m, int *pExponent) { - /* Compute a^m = significand*2^expt and return as (significand, expt) */ - double_double ans, y; - int ansE, yE; - int maxExpt = SM_MAX_EXPONENT; - int q, r, y2mE, y2rE, y2mqE; - double_double y2r, y2m, y2mq; - - if (m <= 0) { - int aE1, aE2; - if (m == 0) { - *pExponent = 0.0; - return double_double(1.0); - } - ans = pow2Scaled_D(a, -m, &aE1); - ans = frexp(1.0 / ans, &aE2); - ansE = -aE1 + aE2; - *pExponent = ansE; - return ans; - } - y = frexp(a, &yE); - if (m == 1) { - *pExponent = yE; - return y; - } - /* - * y ^ maxExpt >= 2^{-960} - * => maxExpt = 960 / log2(y.x[0]) = 708 / log(y.x[0]) - * = 665/((1-y.x[0] + y.x[0]^2/2 - ...) - * <= 665/(1-y.x[0]) - * Quick check to see if we might need to break up the exponentiation - */ - if (m * (y.hi - 1) / y.hi < -SM_MAX_EXPONENT * M_LN2) { - /* Now do it carefully, calling log() */ - double lg2y = std::log(y.hi) / M_LN2; - double lgAns = m * lg2y; - if (lgAns <= -SM_MAX_EXPONENT) { - maxExpt = static_cast(nextPowerOf2(-SM_MAX_EXPONENT / lg2y + 1) / 2); - } - } - if (m <= maxExpt) { - double_double ans1 = pow_D(y, m); - ans = frexp(ans1, &ansE); - ansE += m * yE; - *pExponent = ansE; - return ans; - } - - q = m / maxExpt; - r = m % maxExpt; - /* y^m = (y^maxExpt)^q * y^r */ - y2r = pow2Scaled_D(y, r, &y2rE); - y2m = pow2Scaled_D(y, maxExpt, &y2mE); - y2mq = pow2Scaled_D(y2m, q, &y2mqE); - ans = frexp(y2r * y2mq, &ansE); - y2mqE += y2mE * q; - ansE += y2mqE + y2rE; - ansE += m * yE; - *pExponent = ansE; - return ans; - } - - XSF_HOST_DEVICE inline double_double pow4_D(double a, double b, double c, double d, int m) { - /* Compute ((a+b)/(c+d)) ^ m */ - double_double A, C, X; - if (m <= 0) { - if (m == 0) { - return double_double(1.0); - } - return pow4_D(c, d, a, b, -m); - } - A = double_double(a) + b; - C = double_double(c) + d; - if (A == 0.0) { - return (C == 0.0) ? quiet_NaN() : double_double(0.0); - } - if (C == 0.0) { - return ((A < 0) ? -infinity() : infinity()); - } - X = A / C; - return pow_D(X, m); - } - - XSF_HOST_DEVICE inline double pow4(double a, double b, double c, double d, int m) { - double_double ret = pow4_D(a, b, c, d, m); - return static_cast(ret); - } - - XSF_HOST_DEVICE inline double_double logpow4_D(double a, double b, double c, double d, int m) { - /* - * Compute log(((a+b)/(c+d)) ^ m) - * == m * log((a+b)/(c+d)) - * == m * log( 1 + (a+b-c-d)/(c+d)) - */ - double_double ans; - double_double A, C, X; - if (m == 0) { - return double_double(0.0); - } - A = double_double(a) + b; - C = double_double(c) + d; - if (A == 0.0) { - return ((C == 0.0) ? double_double(0.0) : -infinity()); - } - if (C == 0.0) { - return infinity(); - } - X = A / C; - XSF_ASSERT(X.hi >= 0); - if (0.5 <= X.hi && X.hi <= 1.5) { - double_double A1 = A - C; - double_double X1 = A1 / C; - ans = log1p(X1); - } else { - ans = log(X); - } - ans = m * ans; - return ans; - } - - XSF_HOST_DEVICE inline double logpow4(double a, double b, double c, double d, int m) { - double_double ans = logpow4_D(a, b, c, d, m); - return static_cast(ans); - } - - /* - * Compute a single term in the summation, A_v(n, x): - * A_v(n, x) = Binomial(n,v) * (1-x-v/n)^(n-v) * (x+v/n)^(v-1) - */ - XSF_HOST_DEVICE inline void computeAv(int n, double x, int v, const double_double &Cman, int Cexpt, - double_double *pt1, double_double *pt2, double_double *pAv) { - int t1E, t2E, ansE; - double_double Av; - double_double t2x = double_double(n - v) / n - x; /* 1 - x - v/n */ - double_double t2 = pow2Scaled_D(t2x, n - v, &t2E); - double_double t1x = double_double(v) / n + x; /* x + v/n */ - double_double t1 = pow2Scaled_D(t1x, v - 1, &t1E); - double_double ans = t1 * t2; - ans = ans * Cman; - ansE = Cexpt + t1E + t2E; - Av = ldexp(ans, ansE); - *pAv = Av; - *pt1 = t1; - *pt2 = t2; - } - - XSF_HOST_DEVICE inline ThreeProbs _smirnov(int n, double x) { - double nx, alpha; - double_double AjSum = double_double(0.0); - double_double dAjSum = double_double(0.0); - double cdf, sf, pdf; - - int bUseUpperSum; - int nxfl, n1mxfl, n1mxceil; - - if (!(n > 0 && x >= 0.0 && x <= 1.0)) { - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN()}; - } - if (n == 1) { - return {1 - x, x, 1.0}; - } - if (x == 0.0) { - return {1.0, 0.0, 1.0}; - } - if (x == 1.0) { - return {0.0, 1.0, 0.0}; - } - - alpha = modNX(n, x, &nxfl, &nx); - n1mxfl = n - nxfl - (alpha == 0 ? 0 : 1); - n1mxceil = n - nxfl; - /* - * If alpha is 0, don't actually want to include the last term - * in either the lower or upper summations. - */ - if (alpha == 0) { - n1mxfl -= 1; - n1mxceil += 1; - } - - /* Special case: x <= 1/n */ - if (nxfl == 0 || (nxfl == 1 && alpha == 0)) { - double t = pow2(1, x, n - 1); - pdf = (nx + 1) * t / (1 + x); - cdf = x * t; - sf = 1 - cdf; - /* Adjust if x=1/n *exactly* */ - if (nxfl == 1) { - XSF_ASSERT(alpha == 0); - pdf -= 0.5; - } - return {sf, cdf, pdf}; - } - /* Special case: x is so big, the sf underflows double64 */ - if (-2 * n * x * x < MINLOG) { - return {0, 1, 0}; - } - /* Special case: x >= 1 - 1/n */ - if (nxfl >= n - 1) { - sf = pow2(1, -x, n); - cdf = 1 - sf; - pdf = n * sf / (1 - x); - return {sf, cdf, pdf}; - } - /* Special case: n is so big, take too long to compute */ - if (n > SMIRNOV_MAX_COMPUTE_N) { - /* p ~ e^(-(6nx+1)^2 / 18n) */ - double logp = -std::pow(6.0 * n * x + 1, 2) / 18.0 / n; - /* Maximise precision for small p-value. */ - if (logp < -M_LN2) { - sf = std::exp(logp); - cdf = 1 - sf; - } else { - cdf = -xsf::cephes::expm1(logp); - sf = 1 - cdf; - } - pdf = (6.0 * n * x + 1) * 2 * sf / 3; - return {sf, cdf, pdf}; - } - { - /* - * Use the upper sum if n is large enough, and x is small enough and - * the number of terms is going to be small enough. - * Otherwise it just drops accuracy, about 1.6bits * nUpperTerms - */ - int nUpperTerms = n - n1mxceil + 1; - bUseUpperSum = (nUpperTerms <= 1 && x < 0.5); - bUseUpperSum = (bUseUpperSum || ((n >= SM_UPPERSUM_MIN_N) && (nUpperTerms <= SM_UPPER_MAX_TERMS) && - (x <= 0.5 / std::sqrt(n)))); - } - { - int start = 0, step = 1, nTerms = n1mxfl + 1; - int j, firstJ = 0; - int vmid = n / 2; - double_double Cman = double_double(1.0); - int Cexpt = 0; - double_double Aj, dAj, t1, t2, dAjCoeff; - double_double oneOverX = double_double(1.0) / x; - - if (bUseUpperSum) { - start = n; - step = -1; - nTerms = n - n1mxceil + 1; - - t1 = pow4_D(1, x, 1, 0, n - 1); - t2 = double_double(1.0); - Aj = t1; - - dAjCoeff = (n - 1) / (double_double(1.0) + x); - dAjCoeff = dAjCoeff + oneOverX; - } else { - t1 = oneOverX; - t2 = pow4_D(1, -x, 1, 0, n); - Aj = t2 / x; - - dAjCoeff = (-1 - double_double(n - 1) * x) / (double_double(1.0) - x); - dAjCoeff = dAjCoeff / x; - dAjCoeff = dAjCoeff + oneOverX; - } - - dAj = Aj * dAjCoeff; - AjSum = AjSum + Aj; - dAjSum = dAjSum + dAj; - - updateBinomial(&Cman, &Cexpt, n, 0); - firstJ++; - - for (j = firstJ; j < nTerms; j += 1) { - int v = start + j * step; - - computeAv(n, x, v, Cman, Cexpt, &t1, &t2, &Aj); - - if (isfinite(Aj) && (Aj != 0.0)) { - /* coeff = 1/x + (j-1)/(x+j/n) - (n-j)/(1-x-j/n) */ - dAjCoeff = (n * (v - 1)) / (double_double(nxfl + v) + alpha) - - ((n - v) * n) / (double_double(n - nxfl - v) - alpha); - dAjCoeff = dAjCoeff + oneOverX; - dAj = Aj * dAjCoeff; - - XSF_ASSERT(isfinite(Aj)); - AjSum = AjSum + Aj; - dAjSum = dAjSum + dAj; - } - /* Safe to terminate early? */ - if (Aj != 0.0) { - if (((4 * (nTerms - j) * std::abs(static_cast(Aj))) < - (std::numeric_limits::epsilon() * static_cast(AjSum))) && - (j != nTerms - 1)) { - break; - } - } else if (j > vmid) { - XSF_ASSERT(Aj == 0.0); - break; - } - updateBinomial(&Cman, &Cexpt, n, j); - } - XSF_ASSERT(isfinite(AjSum)); - XSF_ASSERT(isfinite(dAjSum)); - { - double_double derivD = x * dAjSum; - double_double probD = x * AjSum; - double deriv = static_cast(derivD); - double prob = static_cast(probD); - - XSF_ASSERT(nx != 1 || alpha > 0); - if (step < 0) { - cdf = prob; - sf = 1 - prob; - pdf = deriv; - } else { - cdf = 1 - prob; - sf = prob; - pdf = -deriv; - } - } - } - pdf = std::fmax(0, pdf); - cdf = std::clamp(cdf, 0.0, 1.0); - sf = std::clamp(sf, 0.0, 1.0); - return {sf, cdf, pdf}; - } - - /* - * Functional inverse of Smirnov distribution - * finds x such that smirnov(n, x) = psf; smirnovc(n, x) = pcdf). - */ - XSF_HOST_DEVICE inline double _smirnovi(int n, double psf, double pcdf) { - /* - * Need to use a bracketing NR algorithm here and be very careful - * about the starting point. - */ - double x, logpcdf; - int iterations = 0; - double a = 0, b = 1; - double maxlogpcdf, psfrootn; - double dx, dxold; - - if (!(n > 0 && psf >= 0.0 && pcdf >= 0.0 && pcdf <= 1.0 && psf <= 1.0)) { - set_error("smirnovi", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - if (std::abs(1.0 - pcdf - psf) > 4 * std::numeric_limits::epsilon()) { - set_error("smirnovi", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - /* STEP 1: Handle psf==0, or pcdf == 0 */ - if (pcdf == 0.0) { - return 0.0; - } - if (psf == 0.0) { - return 1.0; - } - /* STEP 2: Handle n=1 */ - if (n == 1) { - return pcdf; - } - - /* STEP 3 Handle psf *very* close to 0. Correspond to (n-1)/n < x < 1 */ - psfrootn = std::pow(psf, 1.0 / n); - /* xmin > 1 - 1.0 / n */ - if (n < 150 && n * psfrootn <= 1) { - /* Solve exactly. */ - x = 1 - psfrootn; - return x; - } - - logpcdf = (pcdf < 0.5 ? std::log(pcdf) : std::log1p(-psf)); - - /* - * STEP 4 Find bracket and initial estimate for use in N-R - * 4(a) Handle 0 < x <= 1/n: pcdf = x * (1+x)^*(n-1) - */ - maxlogpcdf = logpow4(1, 0.0, n, 0, 1) + logpow4(n, 1, n, 0, n - 1); - if (logpcdf <= maxlogpcdf) { - double xmin = pcdf / SCIPY_El; - double xmax = pcdf; - double P1 = pow4(n, 1, n, 0, n - 1) / n; - double R = pcdf / P1; - double z0 = R; - /* - * Do one iteration of N-R solving: z*e^(z-1) = R, with z0=pcdf/P1 - * z <- z - (z exp(z-1) - pcdf)/((z+1)exp(z-1)) - * If z_0 = R, z_1 = R(1-exp(1-R))/(R+1) - */ - if (R >= 1) { - /* - * R=1 is OK; - * R>1 can happen due to truncation error for x = (1-1/n)+-eps - */ - R = 1; - x = R / n; - return x; - } - z0 = (z0 * z0 + R * std::exp(1 - z0)) / (1 + z0); - x = z0 / n; - a = xmin * (1 - 4 * std::numeric_limits::epsilon()); - a = std::fmax(a, 0); - b = xmax * (1 + 4 * std::numeric_limits::epsilon()); - b = std::fmin(b, 1.0 / n); - x = std::clamp(x, a, b); - } else { - /* 4(b) : 1/n < x < (n-1)/n */ - double xmin = 1 - psfrootn; - double logpsf = (psf < 0.5 ? std::log(psf) : std::log1p(-pcdf)); - double xmax = std::sqrt(-logpsf / (2.0L * n)); - double xmax6 = xmax - 1.0L / (6 * n); - a = xmin; - b = xmax; - /* Allow for a little rounding error */ - a *= 1 - 4 * std::numeric_limits::epsilon(); - b *= 1 + 4 * std::numeric_limits::epsilon(); - a = std::fmax(xmin, 1.0 / n); - b = std::fmin(xmax, 1 - 1.0 / n); - x = xmax6; - } - if (x < a || x > b) { - x = (a + b) / 2; - } - XSF_ASSERT(x < 1); - - /* - * Skip computing fa, fb as that takes cycles and the exact values - * are not needed. - */ - - /* STEP 5 Run N-R. - * smirnov should be well-enough behaved for NR starting at this location. - * Use smirnov(n, x)-psf, or pcdf - smirnovc(n, x), whichever has smaller p. - */ - dxold = b - a; - dx = dxold; - do { - double dfdx, x0 = x, deltax, df; - XSF_ASSERT(x < 1); - XSF_ASSERT(x > 0); - { - ThreeProbs probs = _smirnov(n, x0); - df = ((pcdf < 0.5) ? (pcdf - probs.cdf) : (probs.sf - psf)); - dfdx = -probs.pdf; - } - if (df == 0) { - return x; - } - /* Update the bracketing interval */ - if (df > 0 && x > a) { - a = x; - } else if (df < 0 && x < b) { - b = x; - } - - if (dfdx == 0) { - /* - * x was not within tolerance, but now we hit a 0 derivative. - * This implies that x >> 1/sqrt(n), and even then |smirnovp| >= |smirnov| - * so this condition is unexpected. Do a bisection step. - */ - x = (a + b) / 2; - deltax = x0 - x; - } else { - deltax = df / dfdx; - x = x0 - deltax; - } - /* - * Check out-of-bounds. - * Not expecting this to happen ofen --- smirnov is convex near x=1 and - * concave near x=0, and we should be approaching from the correct side. - * If out-of-bounds, replace x with a midpoint of the bracket. - * Also check fast enough convergence. - */ - if ((a <= x) && (x <= b) && - (std::abs(2 * deltax) <= std::abs(dxold) || - std::abs(dxold) < 256 * std::numeric_limits::epsilon())) { - dxold = dx; - dx = deltax; - } else { - dxold = dx; - dx = dx / 2; - x = (a + b) / 2; - deltax = x0 - x; - } - /* - * Note that if psf is close to 1, f(x) -> 1, f'(x) -> -1. - * => abs difference |x-x0| is approx |f(x)-p| >= std::numeric_limits::epsilon(), - * => |x-x0|/x >= std::numeric_limits::epsilon()/x. - * => cannot use a purely relative criteria as it will fail for x close to 0. - */ - if (_within_tol(x, x0, (psf < 0.5 ? 0 : _xtol), _rtol)) { - break; - } - if (++iterations > KOLMOG_MAXITER) { - set_error("smirnovi", SF_ERROR_SLOW, NULL); - return (x); - } - } while (1); - return x; - } - - } // namespace detail - - XSF_HOST_DEVICE inline double smirnov(int n, double d) { - if (std::isnan(d)) { - return std::numeric_limits::quiet_NaN(); - } - return detail::_smirnov(n, d).sf; - } - - XSF_HOST_DEVICE inline double smirnovc(int n, double d) { - if (std::isnan(d)) { - return NAN; - } - return detail::_smirnov(n, d).cdf; - } - - /* - * Derivative of smirnov(n, d) - * One interior point of discontinuity at d=1/n. - */ - XSF_HOST_DEVICE inline double smirnovp(int n, double d) { - if (!(n > 0 && d >= 0.0 && d <= 1.0)) { - return (std::numeric_limits::quiet_NaN()); - } - if (n == 1) { - /* Slope is always -1 for n=1, even at d = 1.0 */ - return -1.0; - } - if (d == 1.0) { - return -0.0; - } - /* - * If d is 0, the derivative is discontinuous, but approaching - * from the right the limit is -1 - */ - if (d == 0.0) { - return -1.0; - } - return -detail::_smirnov(n, d).pdf; - } - - XSF_HOST_DEVICE inline double smirnovi(int n, double p) { - if (std::isnan(p)) { - return std::numeric_limits::quiet_NaN(); - } - return detail::_smirnovi(n, p, 1 - p); - } - - XSF_HOST_DEVICE inline double smirnovci(int n, double p) { - if (std::isnan(p)) { - return std::numeric_limits::quiet_NaN(); - } - return detail::_smirnovi(n, 1 - p, p); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/lanczos.h b/scipy/special/xsf/cephes/lanczos.h deleted file mode 100644 index a8cbbe1d693f..000000000000 --- a/scipy/special/xsf/cephes/lanczos.h +++ /dev/null @@ -1,112 +0,0 @@ -/* (C) Copyright John Maddock 2006. - * Use, modification and distribution are subject to the - * Boost Software License, Version 1.0. (See accompanying file - * LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) - */ - -/* Both lanczos.h and lanczos.c were formed from Boost's lanczos.hpp - * - * Scipy changes: - * - 06-22-2016: Removed all code not related to double precision and - * ported to c for use in Cephes. Note that the order of the - * coefficients is reversed to match the behavior of polevl. - */ - -/* - * Optimal values for G for each N are taken from - * https://web.viu.ca/pughg/phdThesis/phdThesis.pdf, - * as are the theoretical error bounds. - * - * Constants calculated using the method described by Godfrey - * https://my.fit.edu/~gabdo/gamma.txt and elaborated by Toth at - * https://www.rskey.org/gamma.htm using NTL::RR at 1000 bit precision. - */ - -/* - * Lanczos Coefficients for N=13 G=6.024680040776729583740234375 - * Max experimental error (with arbitrary precision arithmetic) 1.196214e-17 - * Generated with compiler: Microsoft Visual C++ version 8.0 on Win32 at Mar 23 2006 - * - * Use for double precision. - */ - -#pragma once - -#include "../config.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double lanczos_num[] = { - 2.506628274631000270164908177133837338626, 210.8242777515793458725097339207133627117, - 8071.672002365816210638002902272250613822, 186056.2653952234950402949897160456992822, - 2876370.628935372441225409051620849613599, 31426415.58540019438061423162831820536287, - 248874557.8620541565114603864132294232163, 1439720407.311721673663223072794912393972, - 6039542586.35202800506429164430729792107, 17921034426.03720969991975575445893111267, - 35711959237.35566804944018545154716670596, 42919803642.64909876895789904700198885093, - 23531376880.41075968857200767445163675473}; - - constexpr double lanczos_denom[] = {1, 66, 1925, 32670, 357423, 2637558, 13339535, - 45995730, 105258076, 150917976, 120543840, 39916800, 0}; - - constexpr double lanczos_sum_expg_scaled_num[] = { - 0.006061842346248906525783753964555936883222, 0.5098416655656676188125178644804694509993, - 19.51992788247617482847860966235652136208, 449.9445569063168119446858607650988409623, - 6955.999602515376140356310115515198987526, 75999.29304014542649875303443598909137092, - 601859.6171681098786670226533699352302507, 3481712.15498064590882071018964774556468, - 14605578.08768506808414169982791359218571, 43338889.32467613834773723740590533316085, - 86363131.28813859145546927288977868422342, 103794043.1163445451906271053616070238554, - 56906521.91347156388090791033559122686859}; - - constexpr double lanczos_sum_expg_scaled_denom[] = { - 1, 66, 1925, 32670, 357423, 2637558, 13339535, 45995730, 105258076, 150917976, 120543840, 39916800, 0}; - - constexpr double lanczos_sum_near_1_d[] = { - 0.3394643171893132535170101292240837927725e-9, -0.2499505151487868335680273909354071938387e-8, - 0.8690926181038057039526127422002498960172e-8, -0.1933117898880828348692541394841204288047e-7, - 0.3075580174791348492737947340039992829546e-7, -0.2752907702903126466004207345038327818713e-7, - -0.1515973019871092388943437623825208095123e-5, 0.004785200610085071473880915854204301886437, - -0.1993758927614728757314233026257810172008, 1.483082862367253753040442933770164111678, - -3.327150580651624233553677113928873034916, 2.208709979316623790862569924861841433016}; - - constexpr double lanczos_sum_near_2_d[] = { - 0.1009141566987569892221439918230042368112e-8, -0.7430396708998719707642735577238449585822e-8, - 0.2583592566524439230844378948704262291927e-7, -0.5746670642147041587497159649318454348117e-7, - 0.9142922068165324132060550591210267992072e-7, -0.8183698410724358930823737982119474130069e-7, - -0.4506604409707170077136555010018549819192e-5, 0.01422519127192419234315002746252160965831, - -0.5926941084905061794445733628891024027949, 4.408830289125943377923077727900630927902, - -9.8907772644920670589288081640128194231, 6.565936202082889535528455955485877361223}; - - XSF_HOST_DEVICE double lanczos_sum(double x) { return ratevl(x, lanczos_num, 12, lanczos_denom, 12); } - - XSF_HOST_DEVICE double lanczos_sum_near_1(double dx) { - double result = 0; - unsigned k; - - for (k = 1; k <= 12; ++k) { - result += (-lanczos_sum_near_1_d[k - 1] * dx) / (k * dx + k * k); - } - return result; - } - - XSF_HOST_DEVICE double lanczos_sum_near_2(double dx) { - double result = 0; - double x = dx + 2; - unsigned k; - - for (k = 1; k <= 12; ++k) { - result += (-lanczos_sum_near_2_d[k - 1] * dx) / (x + k * x + k * k - 1); - } - return result; - } - } // namespace detail - - constexpr double lanczos_g = 6.024680040776729583740234375; - XSF_HOST_DEVICE double lanczos_sum_expg_scaled(double x) { - return ratevl(x, detail::lanczos_sum_expg_scaled_num, 12, detail::lanczos_sum_expg_scaled_denom, 12); - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/nbdtr.h b/scipy/special/xsf/cephes/nbdtr.h deleted file mode 100644 index 9fc50d35cb81..000000000000 --- a/scipy/special/xsf/cephes/nbdtr.h +++ /dev/null @@ -1,218 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* nbdtr.c - * - * Negative binomial distribution - * - * - * - * SYNOPSIS: - * - * int k, n; - * double p, y, nbdtr(); - * - * y = nbdtr( k, n, p ); - * - * DESCRIPTION: - * - * Returns the sum of the terms 0 through k of the negative - * binomial distribution: - * - * k - * -- ( n+j-1 ) n j - * > ( ) p (1-p) - * -- ( j ) - * j=0 - * - * In a sequence of Bernoulli trials, this is the probability - * that k or fewer failures precede the nth success. - * - * The terms are not computed individually; instead the incomplete - * beta integral is employed, according to the formula - * - * y = nbdtr( k, n, p ) = incbet( n, k+1, p ). - * - * The arguments must be positive, with p ranging from 0 to 1. - * - * ACCURACY: - * - * Tested at random points (a,b,p), with p between 0 and 1. - * - * a,b Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,100 100000 1.7e-13 8.8e-15 - * See also incbet.c. - * - */ -/* nbdtrc.c - * - * Complemented negative binomial distribution - * - * - * - * SYNOPSIS: - * - * int k, n; - * double p, y, nbdtrc(); - * - * y = nbdtrc( k, n, p ); - * - * DESCRIPTION: - * - * Returns the sum of the terms k+1 to infinity of the negative - * binomial distribution: - * - * inf - * -- ( n+j-1 ) n j - * > ( ) p (1-p) - * -- ( j ) - * j=k+1 - * - * The terms are not computed individually; instead the incomplete - * beta integral is employed, according to the formula - * - * y = nbdtrc( k, n, p ) = incbet( k+1, n, 1-p ). - * - * The arguments must be positive, with p ranging from 0 to 1. - * - * ACCURACY: - * - * Tested at random points (a,b,p), with p between 0 and 1. - * - * a,b Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,100 100000 1.7e-13 8.8e-15 - * See also incbet.c. - */ - -/* nbdtrc - * - * Complemented negative binomial distribution - * - * - * - * SYNOPSIS: - * - * int k, n; - * double p, y, nbdtrc(); - * - * y = nbdtrc( k, n, p ); - * - * DESCRIPTION: - * - * Returns the sum of the terms k+1 to infinity of the negative - * binomial distribution: - * - * inf - * -- ( n+j-1 ) n j - * > ( ) p (1-p) - * -- ( j ) - * j=k+1 - * - * The terms are not computed individually; instead the incomplete - * beta integral is employed, according to the formula - * - * y = nbdtrc( k, n, p ) = incbet( k+1, n, 1-p ). - * - * The arguments must be positive, with p ranging from 0 to 1. - * - * ACCURACY: - * - * See incbet.c. - */ -/* nbdtri - * - * Functional inverse of negative binomial distribution - * - * - * - * SYNOPSIS: - * - * int k, n; - * double p, y, nbdtri(); - * - * p = nbdtri( k, n, y ); - * - * DESCRIPTION: - * - * Finds the argument p such that nbdtr(k,n,p) is equal to y. - * - * ACCURACY: - * - * Tested at random points (a,b,y), with y between 0 and 1. - * - * a,b Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,100 100000 1.5e-14 8.5e-16 - * See also incbi.c. - */ - -/* - * Cephes Math Library Release 2.3: March, 1995 - * Copyright 1984, 1987, 1995 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "incbet.h" -#include "incbi.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE inline double nbdtrc(int k, int n, double p) { - double dk, dn; - - if ((p < 0.0) || (p > 1.0)) { - goto domerr; - } - if (k < 0) { - domerr: - set_error("nbdtr", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - - dk = k + 1; - dn = n; - return (incbet(dk, dn, 1.0 - p)); - } - - XSF_HOST_DEVICE inline double nbdtr(int k, int n, double p) { - double dk, dn; - - if ((p < 0.0) || (p > 1.0)) { - goto domerr; - } - if (k < 0) { - domerr: - set_error("nbdtr", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - dk = k + 1; - dn = n; - return (incbet(dn, dk, p)); - } - - XSF_HOST_DEVICE inline double nbdtri(int k, int n, double p) { - double dk, dn, w; - - if ((p < 0.0) || (p > 1.0)) { - goto domerr; - } - if (k < 0) { - domerr: - set_error("nbdtri", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - dk = k + 1; - dn = n; - w = incbi(dn, dk, p); - return (w); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/ndtr.h b/scipy/special/xsf/cephes/ndtr.h deleted file mode 100644 index a3611d26ba44..000000000000 --- a/scipy/special/xsf/cephes/ndtr.h +++ /dev/null @@ -1,275 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* ndtr.c - * - * Normal distribution function - * - * - * - * SYNOPSIS: - * - * double x, y, ndtr(); - * - * y = ndtr( x ); - * - * - * - * DESCRIPTION: - * - * Returns the area under the Gaussian probability density - * function, integrated from minus infinity to x: - * - * x - * - - * 1 | | 2 - * ndtr(x) = --------- | exp( - t /2 ) dt - * sqrt(2pi) | | - * - - * -inf. - * - * = ( 1 + erf(z) ) / 2 - * = erfc(z) / 2 - * - * where z = x/sqrt(2). Computation is via the functions - * erf and erfc. - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE -13,0 30000 3.4e-14 6.7e-15 - * - * - * ERROR MESSAGES: - * - * message condition value returned - * erfc underflow x > 37.519379347 0.0 - * - */ -/* erf.c - * - * Error function - * - * - * - * SYNOPSIS: - * - * double x, y, erf(); - * - * y = erf( x ); - * - * - * - * DESCRIPTION: - * - * The integral is - * - * x - * - - * 2 | | 2 - * erf(x) = -------- | exp( - t ) dt. - * sqrt(pi) | | - * - - * 0 - * - * For 0 <= |x| < 1, erf(x) = x * P4(x**2)/Q5(x**2); otherwise - * erf(x) = 1 - erfc(x). - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,1 30000 3.7e-16 1.0e-16 - * - */ -/* erfc.c - * - * Complementary error function - * - * - * - * SYNOPSIS: - * - * double x, y, erfc(); - * - * y = erfc( x ); - * - * - * - * DESCRIPTION: - * - * - * 1 - erf(x) = - * - * inf. - * - - * 2 | | 2 - * erfc(x) = -------- | exp( - t ) dt - * sqrt(pi) | | - * - - * x - * - * - * For small x, erfc(x) = 1 - erf(x); otherwise rational - * approximations are computed. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,26.6417 30000 5.7e-14 1.5e-14 - */ - -/* - * Cephes Math Library Release 2.2: June, 1992 - * Copyright 1984, 1987, 1988, 1992 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" - -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double ndtr_P[] = {2.46196981473530512524E-10, 5.64189564831068821977E-1, 7.46321056442269912687E0, - 4.86371970985681366614E1, 1.96520832956077098242E2, 5.26445194995477358631E2, - 9.34528527171957607540E2, 1.02755188689515710272E3, 5.57535335369399327526E2}; - - constexpr double ndtr_Q[] = { - /* 1.00000000000000000000E0, */ - 1.32281951154744992508E1, 8.67072140885989742329E1, 3.54937778887819891062E2, 9.75708501743205489753E2, - 1.82390916687909736289E3, 2.24633760818710981792E3, 1.65666309194161350182E3, 5.57535340817727675546E2}; - - constexpr double ndtr_R[] = {5.64189583547755073984E-1, 1.27536670759978104416E0, 5.01905042251180477414E0, - 6.16021097993053585195E0, 7.40974269950448939160E0, 2.97886665372100240670E0}; - - constexpr double ndtr_S[] = { - /* 1.00000000000000000000E0, */ - 2.26052863220117276590E0, 9.39603524938001434673E0, 1.20489539808096656605E1, - 1.70814450747565897222E1, 9.60896809063285878198E0, 3.36907645100081516050E0}; - - constexpr double ndtr_T[] = {9.60497373987051638749E0, 9.00260197203842689217E1, 2.23200534594684319226E3, - 7.00332514112805075473E3, 5.55923013010394962768E4}; - - constexpr double ndtr_U[] = { - /* 1.00000000000000000000E0, */ - 3.35617141647503099647E1, 5.21357949780152679795E2, 4.59432382970980127987E3, 2.26290000613890934246E4, - 4.92673942608635921086E4}; - - constexpr double ndtri_UTHRESH = 37.519379347; - - } // namespace detail - - XSF_HOST_DEVICE inline double erf(double x); - - XSF_HOST_DEVICE inline double erfc(double a) { - double p, q, x, y, z; - - if (std::isnan(a)) { - set_error("erfc", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - if (a < 0.0) { - x = -a; - } else { - x = a; - } - - if (x < 1.0) { - return 1.0 - erf(a); - } - - z = -a * a; - - if (z < -detail::MAXLOG) { - goto under; - } - - z = std::exp(z); - - if (x < 8.0) { - p = polevl(x, detail::ndtr_P, 8); - q = p1evl(x, detail::ndtr_Q, 8); - } else { - p = polevl(x, detail::ndtr_R, 5); - q = p1evl(x, detail::ndtr_S, 6); - } - y = (z * p) / q; - - if (a < 0) { - y = 2.0 - y; - } - - if (y != 0.0) { - return y; - } - - under: - set_error("erfc", SF_ERROR_UNDERFLOW, NULL); - if (a < 0) { - return 2.0; - } else { - return 0.0; - } - } - - XSF_HOST_DEVICE inline double erf(double x) { - double y, z; - - if (std::isnan(x)) { - set_error("erf", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - if (x < 0.0) { - return -erf(-x); - } - - if (std::abs(x) > 1.0) { - return (1.0 - erfc(x)); - } - z = x * x; - - y = x * polevl(z, detail::ndtr_T, 4) / p1evl(z, detail::ndtr_U, 5); - return y; - } - - XSF_HOST_DEVICE inline double ndtr(double a) { - double x, y, z; - - if (std::isnan(a)) { - set_error("ndtr", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - x = a * M_SQRT1_2; - z = std::abs(x); - - if (z < 1.0) { - y = 0.5 + 0.5 * erf(x); - } else { - y = 0.5 * erfc(z); - if (x > 0) { - y = 1.0 - y; - } - } - - return y; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/ndtri.h b/scipy/special/xsf/cephes/ndtri.h deleted file mode 100644 index 49f25833f3e0..000000000000 --- a/scipy/special/xsf/cephes/ndtri.h +++ /dev/null @@ -1,160 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* ndtri.c - * - * Inverse of Normal distribution function - * - * - * - * SYNOPSIS: - * - * double x, y, ndtri(); - * - * x = ndtri( y ); - * - * - * - * DESCRIPTION: - * - * Returns the argument, x, for which the area under the - * Gaussian probability density function (integrated from - * minus infinity to x) is equal to y. - * - * - * For small arguments 0 < y < exp(-2), the program computes - * z = sqrt( -2.0 * log(y) ); then the approximation is - * x = z - log(z)/z - (1/z) P(1/z) / Q(1/z). - * There are two rational functions P/Q, one for 0 < y < exp(-32) - * and the other for y up to exp(-2). For larger arguments, - * w = y - 0.5, and x/sqrt(2pi) = w + w**3 R(w**2)/S(w**2)). - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0.125, 1 20000 7.2e-16 1.3e-16 - * IEEE 3e-308, 0.135 50000 4.6e-16 9.8e-17 - * - * - * ERROR MESSAGES: - * - * message condition value returned - * ndtri domain x < 0 NAN - * ndtri domain x > 1 NAN - * - */ - -/* - * Cephes Math Library Release 2.1: January, 1989 - * Copyright 1984, 1987, 1989 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* approximation for 0 <= |y - 0.5| <= 3/8 */ - constexpr double ndtri_P0[5] = { - -5.99633501014107895267E1, 9.80010754185999661536E1, -5.66762857469070293439E1, - 1.39312609387279679503E1, -1.23916583867381258016E0, - }; - - constexpr double ndtri_Q0[8] = { - /* 1.00000000000000000000E0, */ - 1.95448858338141759834E0, 4.67627912898881538453E0, 8.63602421390890590575E1, -2.25462687854119370527E2, - 2.00260212380060660359E2, -8.20372256168333339912E1, 1.59056225126211695515E1, -1.18331621121330003142E0, - }; - - /* Approximation for interval z = sqrt(-2 log y ) between 2 and 8 - * i.e., y between exp(-2) = .135 and exp(-32) = 1.27e-14. - */ - constexpr double ndtri_P1[9] = { - 4.05544892305962419923E0, 3.15251094599893866154E1, 5.71628192246421288162E1, - 4.40805073893200834700E1, 1.46849561928858024014E1, 2.18663306850790267539E0, - -1.40256079171354495875E-1, -3.50424626827848203418E-2, -8.57456785154685413611E-4, - }; - - constexpr double ndtri_Q1[8] = { - /* 1.00000000000000000000E0, */ - 1.57799883256466749731E1, 4.53907635128879210584E1, 4.13172038254672030440E1, - 1.50425385692907503408E1, 2.50464946208309415979E0, -1.42182922854787788574E-1, - -3.80806407691578277194E-2, -9.33259480895457427372E-4, - }; - - /* Approximation for interval z = sqrt(-2 log y ) between 8 and 64 - * i.e., y between exp(-32) = 1.27e-14 and exp(-2048) = 3.67e-890. - */ - - constexpr double ndtri_P2[9] = { - 3.23774891776946035970E0, 6.91522889068984211695E0, 3.93881025292474443415E0, - 1.33303460815807542389E0, 2.01485389549179081538E-1, 1.23716634817820021358E-2, - 3.01581553508235416007E-4, 2.65806974686737550832E-6, 6.23974539184983293730E-9, - }; - - constexpr double ndtri_Q2[8] = { - /* 1.00000000000000000000E0, */ - 6.02427039364742014255E0, 3.67983563856160859403E0, 1.37702099489081330271E0, 2.16236993594496635890E-1, - 1.34204006088543189037E-2, 3.28014464682127739104E-4, 2.89247864745380683936E-6, 6.79019408009981274425E-9, - }; - - } // namespace detail - - XSF_HOST_DEVICE inline double ndtri(double y0) { - double x, y, z, y2, x0, x1; - int code; - - if (y0 == 0.0) { - return -std::numeric_limits::infinity(); - } - if (y0 == 1.0) { - return std::numeric_limits::infinity(); - } - if (y0 < 0.0 || y0 > 1.0) { - set_error("ndtri", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - code = 1; - y = y0; - if (y > (1.0 - 0.13533528323661269189)) { /* 0.135... = exp(-2) */ - y = 1.0 - y; - code = 0; - } - - if (y > 0.13533528323661269189) { - y = y - 0.5; - y2 = y * y; - x = y + y * (y2 * polevl(y2, detail::ndtri_P0, 4) / p1evl(y2, detail::ndtri_Q0, 8)); - x = x * detail::SQRTPI; - return (x); - } - - x = std::sqrt(-2.0 * std::log(y)); - x0 = x - std::log(x) / x; - - z = 1.0 / x; - if (x < 8.0) { /* y > exp(-32) = 1.2664165549e-14 */ - x1 = z * polevl(z, detail::ndtri_P1, 8) / p1evl(z, detail::ndtri_Q1, 8); - } else { - x1 = z * polevl(z, detail::ndtri_P2, 8) / p1evl(z, detail::ndtri_Q2, 8); - } - x = x0 - x1; - if (code != 0) { - x = -x; - } - return (x); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/owens_t.h b/scipy/special/xsf/cephes/owens_t.h deleted file mode 100644 index 3a30c24a1dfb..000000000000 --- a/scipy/special/xsf/cephes/owens_t.h +++ /dev/null @@ -1,352 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* Copyright Benjamin Sobotta 2012 - * - * Use, modification and distribution are subject to the - * Boost Software License, Version 1.0. (See accompanying file - * LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) - */ - -/* - * Reference: - * Mike Patefield, David Tandy - * FAST AND ACCURATE CALCULATION OF OWEN'S T-FUNCTION - * Journal of Statistical Software, 5 (5), 1-25 - */ -#pragma once - -#include "../config.h" - -#include "ndtr.h" -#include "unity.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr int owens_t_SELECT_METHOD[] = { - 0, 0, 1, 12, 12, 12, 12, 12, 12, 12, 12, 15, 15, 15, 8, 0, 1, 1, 2, 2, 4, 4, 13, 13, - 14, 14, 15, 15, 15, 8, 1, 1, 2, 2, 2, 4, 4, 14, 14, 14, 14, 15, 15, 15, 9, 1, 1, 2, - 4, 4, 4, 4, 6, 6, 15, 15, 15, 15, 15, 9, 1, 2, 2, 4, 4, 5, 5, 7, 7, 16, 16, 16, - 11, 11, 10, 1, 2, 4, 4, 4, 5, 5, 7, 7, 16, 16, 16, 11, 11, 11, 1, 2, 3, 3, 5, 5, - 7, 7, 16, 16, 16, 16, 16, 11, 11, 1, 2, 3, 3, 5, 5, 17, 17, 17, 17, 16, 16, 16, 11, 11}; - - constexpr double owens_t_HRANGE[] = {0.02, 0.06, 0.09, 0.125, 0.26, 0.4, 0.6, - 1.6, 1.7, 2.33, 2.4, 3.36, 3.4, 4.8}; - - constexpr double owens_t_ARANGE[] = {0.025, 0.09, 0.15, 0.36, 0.5, 0.9, 0.99999}; - - constexpr double owens_t_ORD[] = {2, 3, 4, 5, 7, 10, 12, 18, 10, 20, 30, 0, 4, 7, 8, 20, 0, 0}; - - constexpr int owens_t_METHODS[] = {1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4, 4, 4, 4, 5, 6}; - - constexpr double owens_t_C[] = { - 1.0, - -1.0, - 1.0, - -0.9999999999999998, - 0.9999999999999839, - -0.9999999999993063, - 0.9999999999797337, - -0.9999999995749584, - 0.9999999933226235, - -0.9999999188923242, - 0.9999992195143483, - -0.9999939351372067, - 0.9999613559769055, - -0.9997955636651394, - 0.9990927896296171, - -0.9965938374119182, - 0.9891001713838613, - -0.9700785580406933, - 0.9291143868326319, - -0.8542058695956156, - 0.737965260330301, - -0.585234698828374, - 0.4159977761456763, - -0.25882108752419436, - 0.13755358251638927, - -0.060795276632595575, - 0.021633768329987153, - -0.005934056934551867, - 0.0011743414818332946, - -0.0001489155613350369, - 9.072354320794358e-06, - }; - - constexpr double owens_t_PTS[] = { - 0.35082039676451715489E-02, 0.31279042338030753740E-01, 0.85266826283219451090E-01, - 0.16245071730812277011E+00, 0.25851196049125434828E+00, 0.36807553840697533536E+00, - 0.48501092905604697475E+00, 0.60277514152618576821E+00, 0.71477884217753226516E+00, - 0.81475510988760098605E+00, 0.89711029755948965867E+00, 0.95723808085944261843E+00, - 0.99178832974629703586E+00}; - - constexpr double owens_t_WTS[] = { - 0.18831438115323502887E-01, 0.18567086243977649478E-01, 0.18042093461223385584E-01, - 0.17263829606398753364E-01, 0.16243219975989856730E-01, 0.14994592034116704829E-01, - 0.13535474469662088392E-01, 0.11886351605820165233E-01, 0.10070377242777431897E-01, - 0.81130545742299586629E-02, 0.60419009528470238773E-02, 0.38862217010742057883E-02, - 0.16793031084546090448E-02}; - - XSF_HOST_DEVICE inline int get_method(double h, double a) { - int ihint, iaint, i; - - ihint = 14; - iaint = 7; - - for (i = 0; i < 14; i++) { - if (h <= owens_t_HRANGE[i]) { - ihint = i; - break; - } - } - - for (i = 0; i < 7; i++) { - if (a <= owens_t_ARANGE[i]) { - iaint = i; - break; - } - } - return owens_t_SELECT_METHOD[iaint * 15 + ihint]; - } - - XSF_HOST_DEVICE inline double owens_t_norm1(double x) { return xsf::cephes::erf(x / std::sqrt(2)) / 2; } - - XSF_HOST_DEVICE inline double owens_t_norm2(double x) { - return xsf::cephes::erfc(x / std::sqrt(2)) / 2; - } - - XSF_HOST_DEVICE inline double owensT1(double h, double a, double m) { - int j = 1; - int jj = 1; - - double hs = -0.5 * h * h; - double dhs = std::exp(hs); - double as = a * a; - double aj = a / (2 * M_PI); - double dj = xsf::cephes::expm1(hs); - double gj = hs * dhs; - - double val = std::atan(a) / (2 * M_PI); - - while (1) { - val += dj * aj / jj; - - if (m <= j) { - break; - } - j++; - jj += 2; - aj *= as; - dj = gj - dj; - gj *= hs / j; - } - - return val; - } - - XSF_HOST_DEVICE inline double owensT2(double h, double a, double ah, double m) { - int i = 1; - int maxi = 2 * m + 1; - double hs = h * h; - double as = -a * a; - double y = 1.0 / hs; - double val = 0.0; - double vi = a * std::exp(-0.5 * ah * ah) / std::sqrt(2 * M_PI); - double z = (xsf::cephes::ndtr(ah) - 0.5) / h; - - while (1) { - val += z; - if (maxi <= i) { - break; - } - z = y * (vi - i * z); - vi *= as; - i += 2; - } - val *= std::exp(-0.5 * hs) / std::sqrt(2 * M_PI); - - return val; - } - - XSF_HOST_DEVICE inline double owensT3(double h, double a, double ah) { - double aa, hh, y, vi, zi, result; - int i; - - aa = a * a; - hh = h * h; - y = 1 / hh; - - vi = a * std::exp(-ah * ah / 2) / std::sqrt(2 * M_PI); - zi = owens_t_norm1(ah) / h; - result = 0; - - for (i = 0; i <= 30; i++) { - result += zi * owens_t_C[i]; - zi = y * ((2 * i + 1) * zi - vi); - vi *= aa; - } - - result *= std::exp(-hh / 2) / std::sqrt(2 * M_PI); - - return result; - } - - XSF_HOST_DEVICE inline double owensT4(double h, double a, double m) { - double maxi, hh, naa, ai, yi, result; - int i; - - maxi = 2 * m + 1; - hh = h * h; - naa = -a * a; - - i = 1; - ai = a * std::exp(-hh * (1 - naa) / 2) / (2 * M_PI); - yi = 1; - result = 0; - - while (1) { - result += ai * yi; - - if (maxi <= i) { - break; - } - - i += 2; - yi = (1 - hh * yi) / i; - ai *= naa; - } - - return result; - } - - XSF_HOST_DEVICE inline double owensT5(double h, double a) { - double result, r, aa, nhh; - int i; - - result = 0; - r = 0; - aa = a * a; - nhh = -0.5 * h * h; - - for (i = 1; i < 14; i++) { - r = 1 + aa * owens_t_PTS[i - 1]; - result += owens_t_WTS[i - 1] * std::exp(nhh * r) / r; - } - - result *= a; - - return result; - } - - XSF_HOST_DEVICE inline double owensT6(double h, double a) { - double normh, y, r, result; - - normh = owens_t_norm2(h); - y = 1 - a; - r = std::atan2(y, (1 + a)); - result = normh * (1 - normh) / 2; - - if (r != 0) { - result -= r * std::exp(-y * h * h / (2 * r)) / (2 * M_PI); - } - - return result; - } - - XSF_HOST_DEVICE inline double owens_t_dispatch(double h, double a, double ah) { - int index, meth_code; - double m, result; - - if (h == 0) { - return std::atan(a) / (2 * M_PI); - } - if (a == 0) { - return 0; - } - if (a == 1) { - return owens_t_norm2(-h) * owens_t_norm2(h) / 2; - } - - index = get_method(h, a); - m = owens_t_ORD[index]; - meth_code = owens_t_METHODS[index]; - - switch (meth_code) { - case 1: - result = owensT1(h, a, m); - break; - case 2: - result = owensT2(h, a, ah, m); - break; - case 3: - result = owensT3(h, a, ah); - break; - case 4: - result = owensT4(h, a, m); - break; - case 5: - result = owensT5(h, a); - break; - case 6: - result = owensT6(h, a); - break; - default: - result = std::numeric_limits::quiet_NaN(); - } - - return result; - } - - } // namespace detail - - XSF_HOST_DEVICE inline double owens_t(double h, double a) { - double result, fabs_a, fabs_ah, normh, normah; - - if (std::isnan(h) || std::isnan(a)) { - return std::numeric_limits::quiet_NaN(); - } - - /* exploit that T(-h,a) == T(h,a) */ - h = std::abs(h); - - /* - * Use equation (2) in the paper to remap the arguments such that - * h >= 0 and 0 <= a <= 1 for the call of the actual computation - * routine. - */ - fabs_a = std::abs(a); - fabs_ah = fabs_a * h; - - if (fabs_a == std::numeric_limits::infinity()) { - /* See page 13 in the paper */ - result = 0.5 * detail::owens_t_norm2(h); - } else if (h == std::numeric_limits::infinity()) { - result = 0; - } else if (fabs_a <= 1) { - result = detail::owens_t_dispatch(h, fabs_a, fabs_ah); - } else { - if (fabs_ah <= 0.67) { - normh = detail::owens_t_norm1(h); - normah = detail::owens_t_norm1(fabs_ah); - result = 0.25 - normh * normah - detail::owens_t_dispatch(fabs_ah, (1 / fabs_a), h); - } else { - normh = detail::owens_t_norm2(h); - normah = detail::owens_t_norm2(fabs_ah); - result = (normh + normah) / 2 - normh * normah - detail::owens_t_dispatch(fabs_ah, (1 / fabs_a), h); - } - } - - if (a < 0) { - /* exploit that T(h,-a) == -T(h,a) */ - return -result; - } - - return result; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/pdtr.h b/scipy/special/xsf/cephes/pdtr.h deleted file mode 100644 index 909e5eb1f90d..000000000000 --- a/scipy/special/xsf/cephes/pdtr.h +++ /dev/null @@ -1,183 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* pdtr.c - * - * Poisson distribution - * - * - * - * SYNOPSIS: - * - * int k; - * double m, y, pdtr(); - * - * y = pdtr( k, m ); - * - * - * - * DESCRIPTION: - * - * Returns the sum of the first k terms of the Poisson - * distribution: - * - * k j - * -- -m m - * > e -- - * -- j! - * j=0 - * - * The terms are not summed directly; instead the incomplete - * Gamma integral is employed, according to the relation - * - * y = pdtr( k, m ) = igamc( k+1, m ). - * - * The arguments must both be nonnegative. - * - * - * - * ACCURACY: - * - * See igamc(). - * - */ -/* pdtrc() - * - * Complemented poisson distribution - * - * - * - * SYNOPSIS: - * - * int k; - * double m, y, pdtrc(); - * - * y = pdtrc( k, m ); - * - * - * - * DESCRIPTION: - * - * Returns the sum of the terms k+1 to infinity of the Poisson - * distribution: - * - * inf. j - * -- -m m - * > e -- - * -- j! - * j=k+1 - * - * The terms are not summed directly; instead the incomplete - * Gamma integral is employed, according to the formula - * - * y = pdtrc( k, m ) = igam( k+1, m ). - * - * The arguments must both be nonnegative. - * - * - * - * ACCURACY: - * - * See igam.c. - * - */ -/* pdtri() - * - * Inverse Poisson distribution - * - * - * - * SYNOPSIS: - * - * int k; - * double m, y, pdtr(); - * - * m = pdtri( k, y ); - * - * - * - * - * DESCRIPTION: - * - * Finds the Poisson variable x such that the integral - * from 0 to x of the Poisson density is equal to the - * given probability y. - * - * This is accomplished using the inverse Gamma integral - * function and the relation - * - * m = igamci( k+1, y ). - * - * - * - * - * ACCURACY: - * - * See igami.c. - * - * ERROR MESSAGES: - * - * message condition value returned - * pdtri domain y < 0 or y >= 1 0.0 - * k < 0 - * - */ - -/* - * Cephes Math Library Release 2.3: March, 1995 - * Copyright 1984, 1987, 1995 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "igam.h" -#include "igami.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE inline double pdtrc(double k, double m) { - double v; - - if (k < 0.0 || m < 0.0) { - set_error("pdtrc", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - if (m == 0.0) { - return 0.0; - } - v = std::floor(k) + 1; - return (igam(v, m)); - } - - XSF_HOST_DEVICE inline double pdtr(double k, double m) { - double v; - - if (k < 0 || m < 0) { - set_error("pdtr", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - if (m == 0.0) { - return 1.0; - } - v = std::floor(k) + 1; - return (igamc(v, m)); - } - - XSF_HOST_DEVICE inline double pdtri(int k, double y) { - double v; - - if ((k < 0) || (y < 0.0) || (y >= 1.0)) { - set_error("pdtri", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - v = k + 1; - v = igamci(v, y); - return (v); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/poch.h b/scipy/special/xsf/cephes/poch.h deleted file mode 100644 index add3a995f388..000000000000 --- a/scipy/special/xsf/cephes/poch.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Pochhammer symbol (a)_m = gamma(a + m) / gamma(a) - */ - -#pragma once - -#include "../config.h" -#include "gamma.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - XSF_HOST_DEVICE inline double is_nonpos_int(double x) { - return x <= 0 && x == std::ceil(x) && std::abs(x) < 1e13; - } - } // namespace detail - - XSF_HOST_DEVICE inline double poch(double a, double m) { - double r = 1.0; - - /* - * 1. Reduce magnitude of `m` to |m| < 1 by using recurrence relations. - * - * This may end up in over/underflow, but then the function itself either - * diverges or goes to zero. In case the remainder goes to the opposite - * direction, we end up returning 0*INF = NAN, which is OK. - */ - - /* Recurse down */ - while (m >= 1.0) { - if (a + m == 1) { - break; - } - m -= 1.0; - r *= (a + m); - if (!std::isfinite(r) || r == 0) { - break; - } - } - - /* Recurse up */ - while (m <= -1.0) { - if (a + m == 0) { - break; - } - r /= (a + m); - m += 1.0; - if (!std::isfinite(r) || r == 0) { - break; - } - } - - /* - * 2. Evaluate function with reduced `m` - * - * Now either `m` is not big, or the `r` product has over/underflown. - * If so, the function itself does similarly. - */ - - if (m == 0) { - /* Easy case */ - return r; - } else if (a > 1e4 && std::abs(m) <= 1) { - /* Avoid loss of precision */ - return r * std::pow(a, m) * - (1 + m * (m - 1) / (2 * a) + m * (m - 1) * (m - 2) * (3 * m - 1) / (24 * a * a) + - m * m * (m - 1) * (m - 1) * (m - 2) * (m - 3) / (48 * a * a * a)); - } - - /* Check for infinity */ - if (detail::is_nonpos_int(a + m) && !detail::is_nonpos_int(a) && a + m != m) { - return std::numeric_limits::infinity(); - } - - /* Check for zero */ - if (!detail::is_nonpos_int(a + m) && detail::is_nonpos_int(a)) { - return 0; - } - - return r * std::exp(lgam(a + m) - lgam(a)) * gammasgn(a + m) * gammasgn(a); - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/polevl.h b/scipy/special/xsf/cephes/polevl.h deleted file mode 100644 index 912a506cfb6c..000000000000 --- a/scipy/special/xsf/cephes/polevl.h +++ /dev/null @@ -1,167 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* polevl.c - * p1evl.c - * - * Evaluate polynomial - * - * - * - * SYNOPSIS: - * - * int N; - * double x, y, coef[N+1], polevl[]; - * - * y = polevl( x, coef, N ); - * - * - * - * DESCRIPTION: - * - * Evaluates polynomial of degree N: - * - * 2 N - * y = C + C x + C x +...+ C x - * 0 1 2 N - * - * Coefficients are stored in reverse order: - * - * coef[0] = C , ..., coef[N] = C . - * N 0 - * - * The function p1evl() assumes that c_N = 1.0 so that coefficent - * is omitted from the array. Its calling arguments are - * otherwise the same as polevl(). - * - * - * SPEED: - * - * In the interest of speed, there are no checks for out - * of bounds arithmetic. This routine is used by most of - * the functions in the library. Depending on available - * equipment features, the user may wish to rewrite the - * program in microcode or assembly language. - * - */ - -/* - * Cephes Math Library Release 2.1: December, 1988 - * Copyright 1984, 1987, 1988 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ - -/* Sources: - * [1] Holin et. al., "Polynomial and Rational Function Evaluation", - * https://www.boost.org/doc/libs/1_61_0/libs/math/doc/html/math_toolkit/roots/rational.html - */ - -/* Scipy changes: - * - 06-23-2016: add code for evaluating rational functions - */ - -#pragma once - -#include "../config.h" - -namespace xsf { -namespace cephes { - XSF_HOST_DEVICE inline double polevl(double x, const double coef[], int N) { - double ans; - int i; - const double *p; - - p = coef; - ans = *p++; - i = N; - - do { - ans = ans * x + *p++; - } while (--i); - - return (ans); - } - - /* p1evl() */ - /* N - * Evaluate polynomial when coefficient of x is 1.0. - * That is, C_{N} is assumed to be 1, and that coefficient - * is not included in the input array coef. - * coef must have length N and contain the polynomial coefficients - * stored as - * coef[0] = C_{N-1} - * coef[1] = C_{N-2} - * ... - * coef[N-2] = C_1 - * coef[N-1] = C_0 - * Otherwise same as polevl. - */ - - XSF_HOST_DEVICE inline double p1evl(double x, const double coef[], int N) { - double ans; - const double *p; - int i; - - p = coef; - ans = x + *p++; - i = N - 1; - - do - ans = ans * x + *p++; - while (--i); - - return (ans); - } - - /* Evaluate a rational function. See [1]. */ - - /* The function ratevl is only used once in cephes/lanczos.h. */ - XSF_HOST_DEVICE inline double ratevl(double x, const double num[], int M, const double denom[], int N) { - int i, dir; - double y, num_ans, denom_ans; - double absx = std::abs(x); - const double *p; - - if (absx > 1) { - /* Evaluate as a polynomial in 1/x. */ - dir = -1; - p = num + M; - y = 1 / x; - } else { - dir = 1; - p = num; - y = x; - } - - /* Evaluate the numerator */ - num_ans = *p; - p += dir; - for (i = 1; i <= M; i++) { - num_ans = num_ans * y + *p; - p += dir; - } - - /* Evaluate the denominator */ - if (absx > 1) { - p = denom + N; - } else { - p = denom; - } - - denom_ans = *p; - p += dir; - for (i = 1; i <= N; i++) { - denom_ans = denom_ans * y + *p; - p += dir; - } - - if (absx > 1) { - i = M - N; - return std::pow(x, i) * num_ans / denom_ans; - } else { - return num_ans / denom_ans; - } - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/psi.h b/scipy/special/xsf/cephes/psi.h deleted file mode 100644 index c028e9ea14e0..000000000000 --- a/scipy/special/xsf/cephes/psi.h +++ /dev/null @@ -1,194 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* psi.c - * - * Psi (digamma) function - * - * - * SYNOPSIS: - * - * double x, y, psi(); - * - * y = psi( x ); - * - * - * DESCRIPTION: - * - * d - - * psi(x) = -- ln | (x) - * dx - * - * is the logarithmic derivative of the gamma function. - * For integer x, - * n-1 - * - - * psi(n) = -EUL + > 1/k. - * - - * k=1 - * - * This formula is used for 0 < n <= 10. If x is negative, it - * is transformed to a positive argument by the reflection - * formula psi(1-x) = psi(x) + pi cot(pi x). - * For general positive x, the argument is made greater than 10 - * using the recurrence psi(x+1) = psi(x) + 1/x. - * Then the following asymptotic expansion is applied: - * - * inf. B - * - 2k - * psi(x) = log(x) - 1/2x - > ------- - * - 2k - * k=1 2k x - * - * where the B2k are Bernoulli numbers. - * - * ACCURACY: - * Relative error (except absolute when |psi| < 1): - * arithmetic domain # trials peak rms - * IEEE 0,30 30000 1.3e-15 1.4e-16 - * IEEE -30,0 40000 1.5e-15 2.2e-16 - * - * ERROR MESSAGES: - * message condition value returned - * psi singularity x integer <=0 INFINITY - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 1992, 2000 by Stephen L. Moshier - */ - -/* - * Code for the rational approximation on [1, 2] is: - * - * (C) Copyright John Maddock 2006. - * Use, modification and distribution are subject to the - * Boost Software License, Version 1.0. (See accompanying file - * LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) - */ -#pragma once - -#include "../config.h" -#include "../error.h" -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - namespace detail { - constexpr double psi_A[] = {8.33333333333333333333E-2, -2.10927960927960927961E-2, 7.57575757575757575758E-3, - -4.16666666666666666667E-3, 3.96825396825396825397E-3, -8.33333333333333333333E-3, - 8.33333333333333333333E-2}; - - constexpr float psi_Y = 0.99558162689208984f; - - constexpr double psi_root1 = 1569415565.0 / 1073741824.0; - constexpr double psi_root2 = (381566830.0 / 1073741824.0) / 1073741824.0; - constexpr double psi_root3 = 0.9016312093258695918615325266959189453125e-19; - - constexpr double psi_P[] = {-0.0020713321167745952, -0.045251321448739056, -0.28919126444774784, - -0.65031853770896507, -0.32555031186804491, 0.25479851061131551}; - constexpr double psi_Q[] = {-0.55789841321675513e-6, - 0.0021284987017821144, - 0.054151797245674225, - 0.43593529692665969, - 1.4606242909763515, - 2.0767117023730469, - 1.0}; - - XSF_HOST_DEVICE double digamma_imp_1_2(double x) { - /* - * Rational approximation on [1, 2] taken from Boost. - * - * Now for the approximation, we use the form: - * - * digamma(x) = (x - root) * (Y + R(x-1)) - * - * Where root is the location of the positive root of digamma, - * Y is a constant, and R is optimised for low absolute error - * compared to Y. - * - * Maximum Deviation Found: 1.466e-18 - * At double precision, max error found: 2.452e-17 - */ - double r, g; - - g = x - psi_root1; - g -= psi_root2; - g -= psi_root3; - r = xsf::cephes::polevl(x - 1.0, psi_P, 5) / xsf::cephes::polevl(x - 1.0, psi_Q, 6); - - return g * psi_Y + g * r; - } - - XSF_HOST_DEVICE double psi_asy(double x) { - double y, z; - - if (x < 1.0e17) { - z = 1.0 / (x * x); - y = z * xsf::cephes::polevl(z, psi_A, 6); - } else { - y = 0.0; - } - - return std::log(x) - (0.5 / x) - y; - } - } // namespace detail - - XSF_HOST_DEVICE double psi(double x) { - double y = 0.0; - double q, r; - int i, n; - - if (std::isnan(x)) { - return x; - } else if (x == std::numeric_limits::infinity()) { - return x; - } else if (x == -std::numeric_limits::infinity()) { - return std::numeric_limits::quiet_NaN(); - } else if (x == 0) { - set_error("psi", SF_ERROR_SINGULAR, NULL); - return std::copysign(std::numeric_limits::infinity(), -x); - } else if (x < 0.0) { - /* argument reduction before evaluating tan(pi * x) */ - r = std::modf(x, &q); - if (r == 0.0) { - set_error("psi", SF_ERROR_SINGULAR, NULL); - return std::numeric_limits::quiet_NaN(); - } - y = -M_PI / std::tan(M_PI * r); - x = 1.0 - x; - } - - /* check for positive integer up to 10 */ - if ((x <= 10.0) && (x == std::floor(x))) { - n = static_cast(x); - for (i = 1; i < n; i++) { - y += 1.0 / i; - } - y -= detail::SCIPY_EULER; - return y; - } - - /* use the recurrence relation to move x into [1, 2] */ - if (x < 1.0) { - y -= 1.0 / x; - x += 1.0; - } else if (x < 10.0) { - while (x > 2.0) { - x -= 1.0; - y += 1.0 / x; - } - } - if ((1.0 <= x) && (x <= 2.0)) { - y += detail::digamma_imp_1_2(x); - return y; - } - - /* x is large, use the asymptotic series */ - y += detail::psi_asy(x); - return y; - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/rgamma.h b/scipy/special/xsf/cephes/rgamma.h deleted file mode 100644 index 97f29b33ab50..000000000000 --- a/scipy/special/xsf/cephes/rgamma.h +++ /dev/null @@ -1,111 +0,0 @@ -/* rgamma.c - * - * Reciprocal Gamma function - * - * - * - * SYNOPSIS: - * - * double x, y, rgamma(); - * - * y = rgamma( x ); - * - * - * - * DESCRIPTION: - * - * Returns one divided by the Gamma function of the argument. - * - * The function is approximated by a Chebyshev expansion in - * the interval [0,1]. Range reduction is by recurrence - * for arguments between -34.034 and +34.84425627277176174. - * 0 is returned for positive arguments outside this - * range. For arguments less than -34.034 the cosecant - * reflection formula is applied; lograrithms are employed - * to avoid unnecessary overflow. - * - * The reciprocal Gamma function has no singularities, - * but overflow and underflow may occur for large arguments. - * These conditions return either INFINITY or 0 with - * appropriate sign. - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE -30,+30 30000 1.1e-15 2.0e-16 - * For arguments less than -34.034 the peak error is on the - * order of 5e-15 (DEC), excepting overflow or underflow. - */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1985, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "../error.h" -#include "chbevl.h" -#include "const.h" -#include "gamma.h" -#include "trig.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* Chebyshev coefficients for reciprocal Gamma function - * in interval 0 to 1. Function is 1/(x Gamma(x)) - 1 - */ - - constexpr double rgamma_R[] = { - 3.13173458231230000000E-17, -6.70718606477908000000E-16, 2.20039078172259550000E-15, - 2.47691630348254132600E-13, -6.60074100411295197440E-12, 5.13850186324226978840E-11, - 1.08965386454418662084E-9, -3.33964630686836942556E-8, 2.68975996440595483619E-7, - 2.96001177518801696639E-6, -8.04814124978471142852E-5, 4.16609138709688864714E-4, - 5.06579864028608725080E-3, -6.41925436109158228810E-2, -4.98558728684003594785E-3, - 1.27546015610523951063E-1}; - - } // namespace detail - - XSF_HOST_DEVICE double rgamma(double x) { - double w, y, z; - - if (x == 0) { - // This case is separate from below to get correct sign for zero. - return x; - } - - if (x < 0 && x == std::floor(x)) { - // Gamma poles. - return 0.0; - } - - if (std::abs(x) > 4.0) { - return 1.0 / Gamma(x); - } - - z = 1.0; - w = x; - - while (w > 1.0) { /* Downward recurrence */ - w -= 1.0; - z *= w; - } - while (w < 0.0) { /* Upward recurrence */ - z /= w; - w += 1.0; - } - if (w == 0.0) /* Nonpositive integer */ - return (0.0); - if (w == 1.0) /* Other integer */ - return (1.0 / z); - - y = w * (1.0 + chbevl(4.0 * w - 2.0, detail::rgamma_R, 16)) / z; - return (y); - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/round.h b/scipy/special/xsf/cephes/round.h deleted file mode 100644 index 6e88c5a19747..000000000000 --- a/scipy/special/xsf/cephes/round.h +++ /dev/null @@ -1,74 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* round.c - * - * Round double to nearest or even integer valued double - * - * - * - * SYNOPSIS: - * - * double x, y, round(); - * - * y = round(x); - * - * - * - * DESCRIPTION: - * - * Returns the nearest integer to x as a double precision - * floating point result. If x ends in 0.5 exactly, the - * nearest even integer is chosen. - * - * - * - * ACCURACY: - * - * If x is greater than 1/(2*MACHEP), its closest machine - * representation is already an integer, so rounding does - * not change it. - */ - -/* - * Cephes Math Library Release 2.1: January, 1989 - * Copyright 1984, 1987, 1989 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" - -namespace xsf { -namespace cephes { - - double round(double x) { - double y, r; - - /* Largest integer <= x */ - y = std::floor(x); - - /* Fractional part */ - r = x - y; - - /* Round up to nearest. */ - if (r > 0.5) { - goto rndup; - } - - /* Round to even */ - if (r == 0.5) { - r = y - 2.0 * std::floor(0.5 * y); - if (r == 1.0) { - rndup: - y += 1.0; - } - } - - /* Else round down. */ - return (y); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/scipy_iv.h b/scipy/special/xsf/cephes/scipy_iv.h deleted file mode 100644 index fe0c631e3458..000000000000 --- a/scipy/special/xsf/cephes/scipy_iv.h +++ /dev/null @@ -1,811 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* iv.c - * - * Modified Bessel function of noninteger order - * - * - * - * SYNOPSIS: - * - * double v, x, y, iv(); - * - * y = iv( v, x ); - * - * - * - * DESCRIPTION: - * - * Returns modified Bessel function of order v of the - * argument. If x is negative, v must be integer valued. - * - */ -/* iv.c */ -/* Modified Bessel function of noninteger order */ -/* If x < 0, then v must be an integer. */ - -/* - * Parts of the code are copyright: - * - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 1988, 2000 by Stephen L. Moshier - * - * And other parts: - * - * Copyright (c) 2006 Xiaogang Zhang - * Use, modification and distribution are subject to the - * Boost Software License, Version 1.0. - * - * Boost Software License - Version 1.0 - August 17th, 2003 - * - * Permission is hereby granted, free of charge, to any person or - * organization obtaining a copy of the software and accompanying - * documentation covered by this license (the "Software") to use, reproduce, - * display, distribute, execute, and transmit the Software, and to prepare - * derivative works of the Software, and to permit third-parties to whom the - * Software is furnished to do so, all subject to the following: - * - * The copyright notices in the Software and this entire statement, - * including the above license grant, this restriction and the following - * disclaimer, must be included in all copies of the Software, in whole or - * in part, and all derivative works of the Software, unless such copies or - * derivative works are solely in the form of machine-executable object code - * generated by a source language processor. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND - * NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE - * DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, - * WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * And the rest are: - * - * Copyright (C) 2009 Pauli Virtanen - * Distributed under the same license as Scipy. - * - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "gamma.h" -#include "trig.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* - * Compute Iv from (AMS5 9.7.1), asymptotic expansion for large |z| - * Iv ~ exp(x)/sqrt(2 pi x) ( 1 + (4*v*v-1)/8x + (4*v*v-1)(4*v*v-9)/8x/2! + ...) - */ - XSF_HOST_DEVICE inline double iv_asymptotic(double v, double x) { - double mu; - double sum, term, prefactor, factor; - int k; - - prefactor = std::exp(x) / std::sqrt(2 * M_PI * x); - - if (prefactor == std::numeric_limits::infinity()) { - return prefactor; - } - - mu = 4 * v * v; - sum = 1.0; - term = 1.0; - k = 1; - - do { - factor = (mu - (2 * k - 1) * (2 * k - 1)) / (8 * x) / k; - if (k > 100) { - /* didn't converge */ - set_error("iv(iv_asymptotic)", SF_ERROR_NO_RESULT, NULL); - break; - } - term *= -factor; - sum += term; - ++k; - } while (std::abs(term) > MACHEP * std::abs(sum)); - return sum * prefactor; - } - - /* - * Uniform asymptotic expansion factors, (AMS5 9.3.9; AMS5 9.3.10) - * - * Computed with: - * -------------------- - import numpy as np - t = np.poly1d([1,0]) - def up1(p): - return .5*t*t*(1-t*t)*p.deriv() + 1/8. * ((1-5*t*t)*p).integ() - us = [np.poly1d([1])] - for k in range(10): - us.append(up1(us[-1])) - n = us[-1].order - for p in us: - print "{" + ", ".join(["0"]*(n-p.order) + map(repr, p)) + "}," - print "N_UFACTORS", len(us) - print "N_UFACTOR_TERMS", us[-1].order + 1 - * -------------------- - */ - constexpr int iv_N_UFACTORS = 11; - constexpr int iv_N_UFACTOR_TERMS = 31; - - constexpr double iv_asymptotic_ufactors[iv_N_UFACTORS][iv_N_UFACTOR_TERMS] = { - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0.20833333333333334, - 0.0, 0.125, 0.0}, - {0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.3342013888888889, - 0.0, - -0.40104166666666669, - 0.0, - 0.0703125, - 0.0, - 0.0}, - {0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, -1.0258125964506173, - 0.0, 1.8464626736111112, - 0.0, -0.89121093750000002, - 0.0, 0.0732421875, - 0.0, 0.0, - 0.0}, - {0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 4.6695844234262474, - 0.0, - -11.207002616222995, - 0.0, - 8.78912353515625, - 0.0, - -2.3640869140624998, - 0.0, - 0.112152099609375, - 0.0, - 0.0, - 0.0, - 0.0}, - {0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, -28.212072558200244, - 0.0, 84.636217674600744, - 0.0, -91.818241543240035, - 0.0, 42.534998745388457, - 0.0, -7.3687943594796312, - 0.0, 0.22710800170898438, - 0.0, 0.0, - 0.0, 0.0, - 0.0}, - {0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 212.5701300392171, - 0.0, - -765.25246814118157, - 0.0, - 1059.9904525279999, - 0.0, - -699.57962737613275, - 0.0, - 218.19051174421159, - 0.0, - -26.491430486951554, - 0.0, - 0.57250142097473145, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0}, - {0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, -1919.4576623184068, - 0.0, 8061.7221817373083, - 0.0, -13586.550006434136, - 0.0, 11655.393336864536, - 0.0, -5305.6469786134048, - 0.0, 1200.9029132163525, - 0.0, -108.09091978839464, - 0.0, 1.7277275025844574, - 0.0, 0.0, - 0.0, 0.0, - 0.0, 0.0, - 0.0}, - {0, - 0, - 0, - 0, - 0, - 0, - 20204.291330966149, - 0.0, - -96980.598388637503, - 0.0, - 192547.0012325315, - 0.0, - -203400.17728041555, - 0.0, - 122200.46498301747, - 0.0, - -41192.654968897557, - 0.0, - 7109.5143024893641, - 0.0, - -493.915304773088, - 0.0, - 6.074042001273483, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0}, - {0, 0, - 0, -242919.18790055133, - 0.0, 1311763.6146629769, - 0.0, -2998015.9185381061, - 0.0, 3763271.2976564039, - 0.0, -2813563.2265865342, - 0.0, 1268365.2733216248, - 0.0, -331645.17248456361, - 0.0, 45218.768981362737, - 0.0, -2499.8304818112092, - 0.0, 24.380529699556064, - 0.0, 0.0, - 0.0, 0.0, - 0.0, 0.0, - 0.0, 0.0, - 0.0}, - {3284469.8530720375, - 0.0, - -19706819.11843222, - 0.0, - 50952602.492664628, - 0.0, - -74105148.211532637, - 0.0, - 66344512.274729028, - 0.0, - -37567176.660763353, - 0.0, - 13288767.166421819, - 0.0, - -2785618.1280864552, - 0.0, - 308186.40461266245, - 0.0, - -13886.089753717039, - 0.0, - 110.01714026924674, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0}}; - - /* - * Compute Iv, Kv from (AMS5 9.7.7 + 9.7.8), asymptotic expansion for large v - */ - XSF_HOST_DEVICE inline void ikv_asymptotic_uniform(double v, double x, double *i_value, double *k_value) { - double i_prefactor, k_prefactor; - double t, t2, eta, z; - double i_sum, k_sum, term, divisor; - int k, n; - int sign = 1; - - if (v < 0) { - /* Negative v; compute I_{-v} and K_{-v} and use (AMS 9.6.2) */ - sign = -1; - v = -v; - } - - z = x / v; - t = 1 / std::sqrt(1 + z * z); - t2 = t * t; - eta = std::sqrt(1 + z * z) + std::log(z / (1 + 1 / t)); - - i_prefactor = std::sqrt(t / (2 * M_PI * v)) * std::exp(v * eta); - i_sum = 1.0; - - k_prefactor = std::sqrt(M_PI * t / (2 * v)) * std::exp(-v * eta); - k_sum = 1.0; - - divisor = v; - for (n = 1; n < iv_N_UFACTORS; ++n) { - /* - * Evaluate u_k(t) with Horner's scheme; - * (using the knowledge about which coefficients are zero) - */ - term = 0; - for (k = iv_N_UFACTOR_TERMS - 1 - 3 * n; k < iv_N_UFACTOR_TERMS - n; k += 2) { - term *= t2; - term += iv_asymptotic_ufactors[n][k]; - } - for (k = 1; k < n; k += 2) { - term *= t2; - } - if (n % 2 == 1) { - term *= t; - } - - /* Sum terms */ - term /= divisor; - i_sum += term; - k_sum += (n % 2 == 0) ? term : -term; - - /* Check convergence */ - if (std::abs(term) < MACHEP) { - break; - } - - divisor *= v; - } - - if (std::abs(term) > 1e-3 * std::abs(i_sum)) { - /* Didn't converge */ - set_error("ikv_asymptotic_uniform", SF_ERROR_NO_RESULT, NULL); - } - if (std::abs(term) > MACHEP * std::abs(i_sum)) { - /* Some precision lost */ - set_error("ikv_asymptotic_uniform", SF_ERROR_LOSS, NULL); - } - - if (k_value != NULL) { - /* symmetric in v */ - *k_value = k_prefactor * k_sum; - } - - if (i_value != NULL) { - if (sign == 1) { - *i_value = i_prefactor * i_sum; - } else { - /* (AMS 9.6.2) */ - *i_value = (i_prefactor * i_sum + (2 / M_PI) * xsf::cephes::sinpi(v) * k_prefactor * k_sum); - } - } - } - - /* - * The following code originates from the Boost C++ library, - * from file `boost/math/special_functions/detail/bessel_ik.hpp`, - * converted from C++ to C. - */ - - /* - * Modified Bessel functions of the first and second kind of fractional order - * - * Calculate K(v, x) and K(v+1, x) by method analogous to - * Temme, Journal of Computational Physics, vol 21, 343 (1976) - */ - XSF_HOST_DEVICE inline int temme_ik_series(double v, double x, double *K, double *K1) { - double f, h, p, q, coef, sum, sum1, tolerance; - double a, b, c, d, sigma, gamma1, gamma2; - std::uint64_t k; - double gp; - double gm; - - /* - * |x| <= 2, Temme series converge rapidly - * |x| > 2, the larger the |x|, the slower the convergence - */ - XSF_ASSERT(std::abs(x) <= 2); - XSF_ASSERT(std::abs(v) <= 0.5f); - - gp = xsf::cephes::Gamma(v + 1) - 1; - gm = xsf::cephes::Gamma(-v + 1) - 1; - - a = std::log(x / 2); - b = std::exp(v * a); - sigma = -a * v; - c = std::abs(v) < MACHEP ? 1 : xsf::cephes::sinpi(v) / (v * M_PI); - d = std::abs(sigma) < MACHEP ? 1 : std::sinh(sigma) / sigma; - gamma1 = std::abs(v) < MACHEP ? -SCIPY_EULER : (0.5 / v) * (gp - gm) * c; - gamma2 = (2 + gp + gm) * c / 2; - - /* initial values */ - p = (gp + 1) / (2 * b); - q = (1 + gm) * b / 2; - f = (std::cosh(sigma) * gamma1 + d * (-a) * gamma2) / c; - h = p; - coef = 1; - sum = coef * f; - sum1 = coef * h; - - /* series summation */ - tolerance = MACHEP; - for (k = 1; k < MAXITER; k++) { - f = (k * f + p + q) / (k * k - v * v); - p /= k - v; - q /= k + v; - h = p - k * f; - coef *= x * x / (4 * k); - sum += coef * f; - sum1 += coef * h; - if (std::abs(coef * f) < std::abs(sum) * tolerance) { - break; - } - } - if (k == MAXITER) { - set_error("ikv_temme(temme_ik_series)", SF_ERROR_NO_RESULT, NULL); - } - - *K = sum; - *K1 = 2 * sum1 / x; - - return 0; - } - - /* Evaluate continued fraction fv = I_(v+1) / I_v, derived from - * Abramowitz and Stegun, Handbook of Mathematical Functions, 1972, 9.1.73 */ - XSF_HOST_DEVICE inline int CF1_ik(double v, double x, double *fv) { - double C, D, f, a, b, delta, tiny, tolerance; - std::uint64_t k; - - /* - * |x| <= |v|, CF1_ik converges rapidly - * |x| > |v|, CF1_ik needs O(|x|) iterations to converge - */ - - /* - * modified Lentz's method, see - * Lentz, Applied Optics, vol 15, 668 (1976) - */ - tolerance = 2 * MACHEP; - tiny = 1 / std::sqrt(std::numeric_limits::max()); - C = f = tiny; /* b0 = 0, replace with tiny */ - D = 0; - for (k = 1; k < MAXITER; k++) { - a = 1; - b = 2 * (v + k) / x; - C = b + a / C; - D = b + a * D; - if (C == 0) { - C = tiny; - } - if (D == 0) { - D = tiny; - } - D = 1 / D; - delta = C * D; - f *= delta; - if (std::abs(delta - 1) <= tolerance) { - break; - } - } - if (k == MAXITER) { - set_error("ikv_temme(CF1_ik)", SF_ERROR_NO_RESULT, NULL); - } - - *fv = f; - - return 0; - } - - /* - * Calculate K(v, x) and K(v+1, x) by evaluating continued fraction - * z1 / z0 = U(v+1.5, 2v+1, 2x) / U(v+0.5, 2v+1, 2x), see - * Thompson and Barnett, Computer Physics Communications, vol 47, 245 (1987) - */ - XSF_HOST_DEVICE inline int CF2_ik(double v, double x, double *Kv, double *Kv1) { - - double S, C, Q, D, f, a, b, q, delta, tolerance, current, prev; - std::uint64_t k; - - /* - * |x| >= |v|, CF2_ik converges rapidly - * |x| -> 0, CF2_ik fails to converge - */ - - XSF_ASSERT(std::abs(x) > 1); - - /* - * Steed's algorithm, see Thompson and Barnett, - * Journal of Computational Physics, vol 64, 490 (1986) - */ - tolerance = MACHEP; - a = v * v - 0.25; - b = 2 * (x + 1); /* b1 */ - D = 1 / b; /* D1 = 1 / b1 */ - f = delta = D; /* f1 = delta1 = D1, coincidence */ - prev = 0; /* q0 */ - current = 1; /* q1 */ - Q = C = -a; /* Q1 = C1 because q1 = 1 */ - S = 1 + Q * delta; /* S1 */ - for (k = 2; k < MAXITER; k++) { /* starting from 2 */ - /* continued fraction f = z1 / z0 */ - a -= 2 * (k - 1); - b += 2; - D = 1 / (b + a * D); - delta *= b * D - 1; - f += delta; - - /* series summation S = 1 + \sum_{n=1}^{\infty} C_n * z_n / z_0 */ - q = (prev - (b - 2) * current) / a; - prev = current; - current = q; /* forward recurrence for q */ - C *= -a / k; - Q += C * q; - S += Q * delta; - - /* S converges slower than f */ - if (std::abs(Q * delta) < std::abs(S) * tolerance) { - break; - } - } - if (k == MAXITER) { - set_error("ikv_temme(CF2_ik)", SF_ERROR_NO_RESULT, NULL); - } - - *Kv = std::sqrt(M_PI / (2 * x)) * std::exp(-x) / S; - *Kv1 = *Kv * (0.5 + v + x + (v * v - 0.25) * f) / x; - - return 0; - } - - /* Flags for what to compute */ - enum { ikv_temme_need_i = 0x1, ikv_temme_need_k = 0x2 }; - - /* - * Compute I(v, x) and K(v, x) simultaneously by Temme's method, see - * Temme, Journal of Computational Physics, vol 19, 324 (1975) - */ - XSF_HOST_DEVICE inline void ikv_temme(double v, double x, double *Iv_p, double *Kv_p) { - /* Kv1 = K_(v+1), fv = I_(v+1) / I_v */ - /* Ku1 = K_(u+1), fu = I_(u+1) / I_u */ - double u, Iv, Kv, Kv1, Ku, Ku1, fv; - double W, current, prev, next; - int reflect = 0; - unsigned n, k; - int kind; - - kind = 0; - if (Iv_p != NULL) { - kind |= ikv_temme_need_i; - } - if (Kv_p != NULL) { - kind |= ikv_temme_need_k; - } - - if (v < 0) { - reflect = 1; - v = -v; /* v is non-negative from here */ - kind |= ikv_temme_need_k; - } - n = std::round(v); - u = v - n; /* -1/2 <= u < 1/2 */ - - if (x < 0) { - if (Iv_p != NULL) - *Iv_p = std::numeric_limits::quiet_NaN(); - if (Kv_p != NULL) - *Kv_p = std::numeric_limits::quiet_NaN(); - set_error("ikv_temme", SF_ERROR_DOMAIN, NULL); - return; - } - if (x == 0) { - Iv = (v == 0) ? 1 : 0; - if (kind & ikv_temme_need_k) { - set_error("ikv_temme", SF_ERROR_OVERFLOW, NULL); - Kv = std::numeric_limits::infinity(); - } else { - Kv = std::numeric_limits::quiet_NaN(); /* any value will do */ - } - - if (reflect && (kind & ikv_temme_need_i)) { - double z = (u + n % 2); - - Iv = xsf::cephes::sinpi(z) == 0 ? Iv : std::numeric_limits::infinity(); - if (std::isinf(Iv)) { - set_error("ikv_temme", SF_ERROR_OVERFLOW, NULL); - } - } - - if (Iv_p != NULL) { - *Iv_p = Iv; - } - if (Kv_p != NULL) { - *Kv_p = Kv; - } - return; - } - /* x is positive until reflection */ - W = 1 / x; /* Wronskian */ - if (x <= 2) { /* x in (0, 2] */ - temme_ik_series(u, x, &Ku, &Ku1); /* Temme series */ - } else { /* x in (2, \infty) */ - CF2_ik(u, x, &Ku, &Ku1); /* continued fraction CF2_ik */ - } - prev = Ku; - current = Ku1; - for (k = 1; k <= n; k++) { /* forward recurrence for K */ - next = 2 * (u + k) * current / x + prev; - prev = current; - current = next; - } - Kv = prev; - Kv1 = current; - if (kind & ikv_temme_need_i) { - double lim = (4 * v * v + 10) / (8 * x); - - lim *= lim; - lim *= lim; - lim /= 24; - if ((lim < MACHEP * 10) && (x > 100)) { - /* - * x is huge compared to v, CF1 may be very slow - * to converge so use asymptotic expansion for large - * x case instead. Note that the asymptotic expansion - * isn't very accurate - so it's deliberately very hard - * to get here - probably we're going to overflow: - */ - Iv = iv_asymptotic(v, x); - } else { - CF1_ik(v, x, &fv); /* continued fraction CF1_ik */ - Iv = W / (Kv * fv + Kv1); /* Wronskian relation */ - } - } else { - Iv = std::numeric_limits::quiet_NaN(); /* any value will do */ - } - - if (reflect) { - double z = (u + n % 2); - - if (Iv_p != NULL) { - *Iv_p = Iv + (2 / M_PI) * xsf::cephes::sinpi(z) * Kv; /* reflection formula */ - } - if (Kv_p != NULL) { - *Kv_p = Kv; - } - } else { - if (Iv_p != NULL) { - *Iv_p = Iv; - } - if (Kv_p != NULL) { - *Kv_p = Kv; - } - } - return; - } - - } // namespace detail - - XSF_HOST_DEVICE inline double iv(double v, double x) { - int sign; - double t, ax, res; - - if (std::isnan(v) || std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - - /* If v is a negative integer, invoke symmetry */ - t = std::floor(v); - if (v < 0.0) { - if (t == v) { - v = -v; /* symmetry */ - t = -t; - } - } - /* If x is negative, require v to be an integer */ - sign = 1; - if (x < 0.0) { - if (t != v) { - set_error("iv", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - if (v != 2.0 * std::floor(v / 2.0)) { - sign = -1; - } - } - - /* Avoid logarithm singularity */ - if (x == 0.0) { - if (v == 0.0) { - return 1.0; - } - if (v < 0.0) { - set_error("iv", SF_ERROR_OVERFLOW, NULL); - return std::numeric_limits::infinity(); - } else - return 0.0; - } - - ax = std::abs(x); - if (std::abs(v) > 50) { - /* - * Uniform asymptotic expansion for large orders. - * - * This appears to overflow slightly later than the Boost - * implementation of Temme's method. - */ - detail::ikv_asymptotic_uniform(v, ax, &res, NULL); - } else { - /* Otherwise: Temme's method */ - detail::ikv_temme(v, ax, &res, NULL); - } - res *= sign; - return res; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/shichi.h b/scipy/special/xsf/cephes/shichi.h deleted file mode 100644 index fcdd2d798646..000000000000 --- a/scipy/special/xsf/cephes/shichi.h +++ /dev/null @@ -1,248 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* shichi.c - * - * Hyperbolic sine and cosine integrals - * - * - * - * SYNOPSIS: - * - * double x, Chi, Shi, shichi(); - * - * shichi( x, &Chi, &Shi ); - * - * - * DESCRIPTION: - * - * Approximates the integrals - * - * x - * - - * | | cosh t - 1 - * Chi(x) = eul + ln x + | ----------- dt, - * | | t - * - - * 0 - * - * x - * - - * | | sinh t - * Shi(x) = | ------ dt - * | | t - * - - * 0 - * - * where eul = 0.57721566490153286061 is Euler's constant. - * The integrals are evaluated by power series for x < 8 - * and by Chebyshev expansions for x between 8 and 88. - * For large x, both functions approach exp(x)/2x. - * Arguments greater than 88 in magnitude return INFINITY. - * - * - * ACCURACY: - * - * Test interval 0 to 88. - * Relative error: - * arithmetic function # trials peak rms - * IEEE Shi 30000 6.9e-16 1.6e-16 - * Absolute error, except relative when |Chi| > 1: - * IEEE Chi 30000 8.4e-16 1.4e-16 - */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1984, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" - -#include "chbevl.h" -#include "const.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* x exp(-x) shi(x), inverted interval 8 to 18 */ - constexpr double shichi_S1[] = { - 1.83889230173399459482E-17, -9.55485532279655569575E-17, 2.04326105980879882648E-16, - 1.09896949074905343022E-15, -1.31313534344092599234E-14, 5.93976226264314278932E-14, - -3.47197010497749154755E-14, -1.40059764613117131000E-12, 9.49044626224223543299E-12, - -1.61596181145435454033E-11, -1.77899784436430310321E-10, 1.35455469767246947469E-9, - -1.03257121792819495123E-9, -3.56699611114982536845E-8, 1.44818877384267342057E-7, - 7.82018215184051295296E-7, -5.39919118403805073710E-6, -3.12458202168959833422E-5, - 8.90136741950727517826E-5, 2.02558474743846862168E-3, 2.96064440855633256972E-2, - 1.11847751047257036625E0}; - - /* x exp(-x) shi(x), inverted interval 18 to 88 */ - constexpr double shichi_S2[] = { - -1.05311574154850938805E-17, 2.62446095596355225821E-17, 8.82090135625368160657E-17, - -3.38459811878103047136E-16, -8.30608026366935789136E-16, 3.93397875437050071776E-15, - 1.01765565969729044505E-14, -4.21128170307640802703E-14, -1.60818204519802480035E-13, - 3.34714954175994481761E-13, 2.72600352129153073807E-12, 1.66894954752839083608E-12, - -3.49278141024730899554E-11, -1.58580661666482709598E-10, -1.79289437183355633342E-10, - 1.76281629144264523277E-9, 1.69050228879421288846E-8, 1.25391771228487041649E-7, - 1.16229947068677338732E-6, 1.61038260117376323993E-5, 3.49810375601053973070E-4, - 1.28478065259647610779E-2, 1.03665722588798326712E0}; - - /* x exp(-x) chin(x), inverted interval 8 to 18 */ - constexpr double shichi_C1[] = { - -8.12435385225864036372E-18, 2.17586413290339214377E-17, 5.22624394924072204667E-17, - -9.48812110591690559363E-16, 5.35546311647465209166E-15, -1.21009970113732918701E-14, - -6.00865178553447437951E-14, 7.16339649156028587775E-13, -2.93496072607599856104E-12, - -1.40359438136491256904E-12, 8.76302288609054966081E-11, -4.40092476213282340617E-10, - -1.87992075640569295479E-10, 1.31458150989474594064E-8, -4.75513930924765465590E-8, - -2.21775018801848880741E-7, 1.94635531373272490962E-6, 4.33505889257316408893E-6, - -6.13387001076494349496E-5, -3.13085477492997465138E-4, 4.97164789823116062801E-4, - 2.64347496031374526641E-2, 1.11446150876699213025E0}; - - /* x exp(-x) chin(x), inverted interval 18 to 88 */ - constexpr double shichi_C2[] = { - 8.06913408255155572081E-18, -2.08074168180148170312E-17, -5.98111329658272336816E-17, - 2.68533951085945765591E-16, 4.52313941698904694774E-16, -3.10734917335299464535E-15, - -4.42823207332531972288E-15, 3.49639695410806959872E-14, 6.63406731718911586609E-14, - -3.71902448093119218395E-13, -1.27135418132338309016E-12, 2.74851141935315395333E-12, - 2.33781843985453438400E-11, 2.71436006377612442764E-11, -2.56600180000355990529E-10, - -1.61021375163803438552E-9, -4.72543064876271773512E-9, -3.00095178028681682282E-9, - 7.79387474390914922337E-8, 1.06942765566401507066E-6, 1.59503164802313196374E-5, - 3.49592575153777996871E-4, 1.28475387530065247392E-2, 1.03665693917934275131E0}; - - /* - * Evaluate 3F0(a1, a2, a3; z) - * - * The series is only asymptotic, so this requires z large enough. - */ - XSF_HOST_DEVICE inline double hyp3f0(double a1, double a2, double a3, double z) { - int n, maxiter; - double err, sum, term, m; - - m = std::pow(z, -1.0 / 3); - if (m < 50) { - maxiter = m; - } else { - maxiter = 50; - } - - term = 1.0; - sum = term; - for (n = 0; n < maxiter; ++n) { - term *= (a1 + n) * (a2 + n) * (a3 + n) * z / (n + 1); - sum += term; - if (std::abs(term) < 1e-13 * std::abs(sum) || term == 0) { - break; - } - } - - err = std::abs(term); - - if (err > 1e-13 * std::abs(sum)) { - return std::numeric_limits::quiet_NaN(); - } - - return sum; - } - - } // namespace detail - - /* Sine and cosine integrals */ - XSF_HOST_DEVICE inline int shichi(double x, double *si, double *ci) { - double k, z, c, s, a, b; - short sign; - - if (x < 0.0) { - sign = -1; - x = -x; - } else { - sign = 0; - } - - if (x == 0.0) { - *si = 0.0; - *ci = -std::numeric_limits::infinity(); - return (0); - } - - if (x >= 8.0) { - goto chb; - } - - if (x >= 88.0) { - goto asymp; - } - - z = x * x; - - /* Direct power series expansion */ - a = 1.0; - s = 1.0; - c = 0.0; - k = 2.0; - - do { - a *= z / k; - c += a / k; - k += 1.0; - a /= k; - s += a / k; - k += 1.0; - } while (std::abs(a / s) > detail::MACHEP); - - s *= x; - goto done; - - chb: - /* Chebyshev series expansions */ - if (x < 18.0) { - a = (576.0 / x - 52.0) / 10.0; - k = std::exp(x) / x; - s = k * chbevl(a, detail::shichi_S1, 22); - c = k * chbevl(a, detail::shichi_C1, 23); - goto done; - } - - if (x <= 88.0) { - a = (6336.0 / x - 212.0) / 70.0; - k = std::exp(x) / x; - s = k * chbevl(a, detail::shichi_S2, 23); - c = k * chbevl(a, detail::shichi_C2, 24); - goto done; - } - - asymp: - if (x > 1000) { - *si = std::numeric_limits::infinity(); - *ci = std::numeric_limits::infinity(); - } else { - /* Asymptotic expansions - * http://functions.wolfram.com/GammaBetaErf/CoshIntegral/06/02/ - * http://functions.wolfram.com/GammaBetaErf/SinhIntegral/06/02/0001/ - */ - a = detail::hyp3f0(0.5, 1, 1, 4.0 / (x * x)); - b = detail::hyp3f0(1, 1, 1.5, 4.0 / (x * x)); - *si = std::cosh(x) / x * a + std::sinh(x) / (x * x) * b; - *ci = std::sinh(x) / x * a + std::cosh(x) / (x * x) * b; - } - if (sign) { - *si = -*si; - } - return 0; - - done: - if (sign) { - s = -s; - } - - *si = s; - - *ci = detail::SCIPY_EULER + std::log(x) + c; - return (0); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/sici.h b/scipy/special/xsf/cephes/sici.h deleted file mode 100644 index c22612ccc9ab..000000000000 --- a/scipy/special/xsf/cephes/sici.h +++ /dev/null @@ -1,224 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* sici.c - * - * Sine and cosine integrals - * - * - * - * SYNOPSIS: - * - * double x, Ci, Si, sici(); - * - * sici( x, &Si, &Ci ); - * - * - * DESCRIPTION: - * - * Evaluates the integrals - * - * x - * - - * | cos t - 1 - * Ci(x) = eul + ln x + | --------- dt, - * | t - * - - * 0 - * x - * - - * | sin t - * Si(x) = | ----- dt - * | t - * - - * 0 - * - * where eul = 0.57721566490153286061 is Euler's constant. - * The integrals are approximated by rational functions. - * For x > 8 auxiliary functions f(x) and g(x) are employed - * such that - * - * Ci(x) = f(x) sin(x) - g(x) cos(x) - * Si(x) = pi/2 - f(x) cos(x) - g(x) sin(x) - * - * - * ACCURACY: - * Test interval = [0,50]. - * Absolute error, except relative when > 1: - * arithmetic function # trials peak rms - * IEEE Si 30000 4.4e-16 7.3e-17 - * IEEE Ci 30000 6.9e-16 5.1e-17 - */ - -/* - * Cephes Math Library Release 2.1: January, 1989 - * Copyright 1984, 1987, 1989 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" - -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double sici_SN[] = { - -8.39167827910303881427E-11, 4.62591714427012837309E-8, -9.75759303843632795789E-6, - 9.76945438170435310816E-4, -4.13470316229406538752E-2, 1.00000000000000000302E0, - }; - - constexpr double sici_SD[] = { - 2.03269266195951942049E-12, 1.27997891179943299903E-9, 4.41827842801218905784E-7, - 9.96412122043875552487E-5, 1.42085239326149893930E-2, 9.99999999999999996984E-1, - }; - - constexpr double sici_CN[] = { - 2.02524002389102268789E-11, -1.35249504915790756375E-8, 3.59325051419993077021E-6, - -4.74007206873407909465E-4, 2.89159652607555242092E-2, -1.00000000000000000080E0, - }; - - constexpr double sici_CD[] = { - 4.07746040061880559506E-12, 3.06780997581887812692E-9, 1.23210355685883423679E-6, - 3.17442024775032769882E-4, 5.10028056236446052392E-2, 4.00000000000000000080E0, - }; - - constexpr double sici_FN4[] = { - 4.23612862892216586994E0, 5.45937717161812843388E0, 1.62083287701538329132E0, 1.67006611831323023771E-1, - 6.81020132472518137426E-3, 1.08936580650328664411E-4, 5.48900223421373614008E-7, - }; - - constexpr double sici_FD4[] = { - /* 1.00000000000000000000E0, */ - 8.16496634205391016773E0, 7.30828822505564552187E0, 1.86792257950184183883E0, 1.78792052963149907262E-1, - 7.01710668322789753610E-3, 1.10034357153915731354E-4, 5.48900252756255700982E-7, - }; - - constexpr double sici_FN8[] = { - 4.55880873470465315206E-1, 7.13715274100146711374E-1, 1.60300158222319456320E-1, - 1.16064229408124407915E-2, 3.49556442447859055605E-4, 4.86215430826454749482E-6, - 3.20092790091004902806E-8, 9.41779576128512936592E-11, 9.70507110881952024631E-14, - }; - - constexpr double sici_FD8[] = { - /* 1.00000000000000000000E0, */ - 9.17463611873684053703E-1, 1.78685545332074536321E-1, 1.22253594771971293032E-2, - 3.58696481881851580297E-4, 4.92435064317881464393E-6, 3.21956939101046018377E-8, - 9.43720590350276732376E-11, 9.70507110881952025725E-14, - }; - - constexpr double sici_GN4[] = { - 8.71001698973114191777E-2, 6.11379109952219284151E-1, 3.97180296392337498885E-1, 7.48527737628469092119E-2, - 5.38868681462177273157E-3, 1.61999794598934024525E-4, 1.97963874140963632189E-6, 7.82579040744090311069E-9, - }; - - constexpr double sici_GD4[] = { - /* 1.00000000000000000000E0, */ - 1.64402202413355338886E0, 6.66296701268987968381E-1, 9.88771761277688796203E-2, 6.22396345441768420760E-3, - 1.73221081474177119497E-4, 2.02659182086343991969E-6, 7.82579218933534490868E-9, - }; - - constexpr double sici_GN8[] = { - 6.97359953443276214934E-1, 3.30410979305632063225E-1, 3.84878767649974295920E-2, - 1.71718239052347903558E-3, 3.48941165502279436777E-5, 3.47131167084116673800E-7, - 1.70404452782044526189E-9, 3.85945925430276600453E-12, 3.14040098946363334640E-15, - }; - - constexpr double sici_GD8[] = { - /* 1.00000000000000000000E0, */ - 1.68548898811011640017E0, 4.87852258695304967486E-1, 4.67913194259625806320E-2, - 1.90284426674399523638E-3, 3.68475504442561108162E-5, 3.57043223443740838771E-7, - 1.72693748966316146736E-9, 3.87830166023954706752E-12, 3.14040098946363335242E-15, - }; - - } // namespace detail - - XSF_HOST_DEVICE inline int sici(double x, double *si, double *ci) { - double z, c, s, f, g; - short sign; - - if (x < 0.0) { - sign = -1; - x = -x; - } else { - sign = 0; - } - - if (x == 0.0) { - *si = 0.0; - *ci = -std::numeric_limits::infinity(); - return (0); - } - - if (x > 1.0e9) { - if (std::isinf(x)) { - if (sign == -1) { - *si = -M_PI_2; - *ci = std::numeric_limits::quiet_NaN(); - } else { - *si = M_PI_2; - *ci = 0; - } - return 0; - } - *si = M_PI_2 - std::cos(x) / x; - *ci = std::sin(x) / x; - } - - if (x > 4.0) { - goto asympt; - } - - z = x * x; - s = x * polevl(z, detail::sici_SN, 5) / polevl(z, detail::sici_SD, 5); - c = z * polevl(z, detail::sici_CN, 5) / polevl(z, detail::sici_CD, 5); - - if (sign) { - s = -s; - } - *si = s; - *ci = detail::SCIPY_EULER + std::log(x) + c; /* real part if x < 0 */ - return (0); - - /* The auxiliary functions are: - * - * - * *si = *si - M_PI_2; - * c = cos(x); - * s = sin(x); - * - * t = *ci * s - *si * c; - * a = *ci * c + *si * s; - * - * *si = t; - * *ci = -a; - */ - - asympt: - - s = std::sin(x); - c = std::cos(x); - z = 1.0 / (x * x); - if (x < 8.0) { - f = polevl(z, detail::sici_FN4, 6) / (x * p1evl(z, detail::sici_FD4, 7)); - g = z * polevl(z, detail::sici_GN4, 7) / p1evl(z, detail::sici_GD4, 7); - } else { - f = polevl(z, detail::sici_FN8, 8) / (x * p1evl(z, detail::sici_FD8, 8)); - g = z * polevl(z, detail::sici_GN8, 8) / p1evl(z, detail::sici_GD8, 9); - } - *si = M_PI_2 - f * c - g * s; - if (sign) { - *si = -(*si); - } - *ci = f * s - g * c; - - return (0); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/sindg.h b/scipy/special/xsf/cephes/sindg.h deleted file mode 100644 index 63adb1698f4c..000000000000 --- a/scipy/special/xsf/cephes/sindg.h +++ /dev/null @@ -1,221 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* sindg.c - * - * Circular sine of angle in degrees - * - * - * - * SYNOPSIS: - * - * double x, y, sindg(); - * - * y = sindg( x ); - * - * - * - * DESCRIPTION: - * - * Range reduction is into intervals of 45 degrees. - * - * Two polynomial approximating functions are employed. - * Between 0 and pi/4 the sine is approximated by - * x + x**3 P(x**2). - * Between pi/4 and pi/2 the cosine is represented as - * 1 - x**2 P(x**2). - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE +-1000 30000 2.3e-16 5.6e-17 - * - * ERROR MESSAGES: - * - * message condition value returned - * sindg total loss x > 1.0e14 (IEEE) 0.0 - * - */ -/* cosdg.c - * - * Circular cosine of angle in degrees - * - * - * - * SYNOPSIS: - * - * double x, y, cosdg(); - * - * y = cosdg( x ); - * - * - * - * DESCRIPTION: - * - * Range reduction is into intervals of 45 degrees. - * - * Two polynomial approximating functions are employed. - * Between 0 and pi/4 the cosine is approximated by - * 1 - x**2 P(x**2). - * Between pi/4 and pi/2 the sine is represented as - * x + x**3 P(x**2). - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE +-1000 30000 2.1e-16 5.7e-17 - * See also sin(). - * - */ - -/* Cephes Math Library Release 2.0: April, 1987 - * Copyright 1985, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double sincof[] = {1.58962301572218447952E-10, -2.50507477628503540135E-8, - 2.75573136213856773549E-6, -1.98412698295895384658E-4, - 8.33333333332211858862E-3, -1.66666666666666307295E-1}; - - constexpr double coscof[] = {1.13678171382044553091E-11, -2.08758833757683644217E-9, 2.75573155429816611547E-7, - -2.48015872936186303776E-5, 1.38888888888806666760E-3, -4.16666666666666348141E-2, - 4.99999999999999999798E-1}; - - constexpr double sindg_lossth = 1.0e14; - - } // namespace detail - - XSF_HOST_DEVICE inline double sindg(double x) { - double y, z, zz; - int j, sign; - - /* make argument positive but save the sign */ - sign = 1; - if (x < 0) { - x = -x; - sign = -1; - } - - if (x > detail::sindg_lossth) { - set_error("sindg", SF_ERROR_NO_RESULT, NULL); - return (0.0); - } - - y = std::floor(x / 45.0); /* integer part of x/M_PI_4 */ - - /* strip high bits of integer part to prevent integer overflow */ - z = std::ldexp(y, -4); - z = std::floor(z); /* integer part of y/8 */ - z = y - std::ldexp(z, 4); /* y - 16 * (y/16) */ - - j = z; /* convert to integer for tests on the phase angle */ - /* map zeros to origin */ - if (j & 1) { - j += 1; - y += 1.0; - } - j = j & 07; /* octant modulo 360 degrees */ - /* reflect in x axis */ - if (j > 3) { - sign = -sign; - j -= 4; - } - - z = x - y * 45.0; /* x mod 45 degrees */ - z *= detail::PI180; /* multiply by pi/180 to convert to radians */ - zz = z * z; - - if ((j == 1) || (j == 2)) { - y = 1.0 - zz * polevl(zz, detail::coscof, 6); - } else { - y = z + z * (zz * polevl(zz, detail::sincof, 5)); - } - - if (sign < 0) - y = -y; - - return (y); - } - - XSF_HOST_DEVICE inline double cosdg(double x) { - double y, z, zz; - int j, sign; - - /* make argument positive */ - sign = 1; - if (x < 0) - x = -x; - - if (x > detail::sindg_lossth) { - set_error("cosdg", SF_ERROR_NO_RESULT, NULL); - return (0.0); - } - - y = std::floor(x / 45.0); - z = std::ldexp(y, -4); - z = std::floor(z); /* integer part of y/8 */ - z = y - std::ldexp(z, 4); /* y - 16 * (y/16) */ - - /* integer and fractional part modulo one octant */ - j = z; - if (j & 1) { /* map zeros to origin */ - j += 1; - y += 1.0; - } - j = j & 07; - if (j > 3) { - j -= 4; - sign = -sign; - } - - if (j > 1) - sign = -sign; - - z = x - y * 45.0; /* x mod 45 degrees */ - z *= detail::PI180; /* multiply by pi/180 to convert to radians */ - - zz = z * z; - - if ((j == 1) || (j == 2)) { - y = z + z * (zz * polevl(zz, detail::sincof, 5)); - } else { - y = 1.0 - zz * polevl(zz, detail::coscof, 6); - } - - if (sign < 0) - y = -y; - - return (y); - } - - /* Degrees, minutes, seconds to radians: */ - - /* 1 arc second, in radians = 4.848136811095359935899141023579479759563533023727e-6 */ - - namespace detail { - constexpr double sindg_P64800 = 4.848136811095359935899141023579479759563533023727e-6; - } - - XSF_HOST_DEVICE inline double radian(double d, double m, double s) { - return (((d * 60.0 + m) * 60.0 + s) * detail::sindg_P64800); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/spence.h b/scipy/special/xsf/cephes/spence.h deleted file mode 100644 index 6d908ada9380..000000000000 --- a/scipy/special/xsf/cephes/spence.h +++ /dev/null @@ -1,127 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* spence.c - * - * Dilogarithm - * - * - * - * SYNOPSIS: - * - * double x, y, spence(); - * - * y = spence( x ); - * - * - * - * DESCRIPTION: - * - * Computes the integral - * - * x - * - - * | | log t - * spence(x) = - | ----- dt - * | | t - 1 - * - - * 1 - * - * for x >= 0. A rational approximation gives the integral in - * the interval (0.5, 1.5). Transformation formulas for 1/x - * and 1-x are employed outside the basic expansion range. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,4 30000 3.9e-15 5.4e-16 - * - * - */ - -/* spence.c */ - -/* - * Cephes Math Library Release 2.1: January, 1989 - * Copyright 1985, 1987, 1989 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double spence_A[8] = { - 4.65128586073990045278E-5, 7.31589045238094711071E-3, 1.33847639578309018650E-1, 8.79691311754530315341E-1, - 2.71149851196553469920E0, 4.25697156008121755724E0, 3.29771340985225106936E0, 1.00000000000000000126E0, - }; - - constexpr double spence_B[8] = { - 6.90990488912553276999E-4, 2.54043763932544379113E-2, 2.82974860602568089943E-1, 1.41172597751831069617E0, - 3.63800533345137075418E0, 5.03278880143316990390E0, 3.54771340985225096217E0, 9.99999999999999998740E-1, - }; - - } // namespace detail - - XSF_HOST_DEVICE inline double spence(double x) { - double w, y, z; - int flag; - - if (x < 0.0) { - set_error("spence", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - - if (x == 1.0) { - return (0.0); - } - - if (x == 0.0) { - return (M_PI * M_PI / 6.0); - } - - flag = 0; - - if (x > 2.0) { - x = 1.0 / x; - flag |= 2; - } - - if (x > 1.5) { - w = (1.0 / x) - 1.0; - flag |= 2; - } else if (x < 0.5) { - w = -x; - flag |= 1; - } else { - w = x - 1.0; - } - - y = -w * polevl(w, detail::spence_A, 7) / polevl(w, detail::spence_B, 7); - - if (flag & 1) { - y = (M_PI * M_PI) / 6.0 - std::log(x) * std::log(1.0 - x) - y; - } - - if (flag & 2) { - z = std::log(x); - y = -0.5 * z * z - y; - } - - return (y); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/struve.h b/scipy/special/xsf/cephes/struve.h deleted file mode 100644 index 3ed5bae25d51..000000000000 --- a/scipy/special/xsf/cephes/struve.h +++ /dev/null @@ -1,382 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* - * Compute the Struve function. - * - * Notes - * ----- - * - * We use three expansions for the Struve function discussed in [1]: - * - * - power series - * - expansion in Bessel functions - * - asymptotic large-z expansion - * - * Rounding errors are estimated based on the largest terms in the sums. - * - * ``struve_convergence.py`` plots the convergence regions of the different - * expansions. - * - * (i) - * - * Looking at the error in the asymptotic expansion, one finds that - * it's not worth trying if z ~> 0.7 * v + 12 for v > 0. - * - * (ii) - * - * The Bessel function expansion tends to fail for |z| >~ |v| and is not tried - * there. - * - * For Struve H it covers the quadrant v > z where the power series may fail to - * produce reasonable results. - * - * (iii) - * - * The three expansions together cover for Struve H the region z > 0, v real. - * - * They also cover Struve L, except that some loss of precision may occur around - * the transition region z ~ 0.7 |v|, v < 0, |v| >> 1 where the function changes - * rapidly. - * - * (iv) - * - * The power series is evaluated in double-double precision. This fixes accuracy - * issues in Struve H for |v| << |z| before the asymptotic expansion kicks in. - * Moreover, it improves the Struve L behavior for negative v. - * - * - * References - * ---------- - * [1] NIST Digital Library of Mathematical Functions - * https://dlmf.nist.gov/11 - */ - -/* - * Copyright (C) 2013 Pauli Virtanen - * - * 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 Enthought nor the names of the SciPy Developers - * 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 COPYRIGHT HOLDERS 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. - */ -#pragma once - -#include "../bessel.h" -#include "../config.h" -#include "../error.h" - -#include "dd_real.h" -#include "gamma.h" -#include "rgamma.h" -#include "scipy_iv.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr int STRUVE_MAXITER = 10000; - constexpr double STRUVE_SUM_EPS = 1e-16; /* be sure we are in the tail of the sum */ - constexpr double STRUVE_SUM_TINY = 1e-100; - constexpr double STRUVE_GOOD_EPS = 1e-12; - constexpr double STRUVE_ACCEPTABLE_EPS = 1e-7; - constexpr double STRUVE_ACCEPTABLE_ATOL = 1e-300; - - /* - * Large-z expansion for Struve H and L - * https://dlmf.nist.gov/11.6.1 - */ - XSF_HOST_DEVICE inline double struve_asymp_large_z(double v, double z, int is_h, double *err) { - int n, sgn, maxiter; - double term, sum, maxterm; - double m; - - if (is_h) { - sgn = -1; - } else { - sgn = 1; - } - - /* Asymptotic expansion divergenge point */ - m = z / 2; - if (m <= 0) { - maxiter = 0; - } else if (m > STRUVE_MAXITER) { - maxiter = STRUVE_MAXITER; - } else { - maxiter = (int) m; - } - if (maxiter == 0) { - *err = std::numeric_limits::infinity(); - return std::numeric_limits::quiet_NaN(); - } - - if (z < v) { - /* Exclude regions where our error estimation fails */ - *err = std::numeric_limits::infinity(); - return std::numeric_limits::quiet_NaN(); - } - - /* Evaluate sum */ - term = -sgn / std::sqrt(M_PI) * std::exp(-xsf::cephes::lgam(v + 0.5) + (v - 1) * std::log(z / 2)) * - xsf::cephes::gammasgn(v + 0.5); - sum = term; - maxterm = 0; - - for (n = 0; n < maxiter; ++n) { - term *= sgn * (1 + 2 * n) * (1 + 2 * n - 2 * v) / (z * z); - sum += term; - if (std::abs(term) > maxterm) { - maxterm = std::abs(term); - } - if (std::abs(term) < STRUVE_SUM_EPS * std::abs(sum) || term == 0 || !std::isfinite(sum)) { - break; - } - } - - if (is_h) { - sum += xsf::cyl_bessel_y(v, z); - } else { - sum += xsf::cephes::iv(v, z); - } - - /* - * This error estimate is strictly speaking valid only for - * n > v - 0.5, but numerical results indicate that it works - * reasonably. - */ - *err = std::abs(term) + std::abs(maxterm) * STRUVE_SUM_EPS; - - return sum; - } - - /* - * Power series for Struve H and L - * https://dlmf.nist.gov/11.2.1 - * - * Starts to converge roughly at |n| > |z| - */ - XSF_HOST_DEVICE inline double struve_power_series(double v, double z, int is_h, double *err) { - int n, sgn; - double term, sum, maxterm, scaleexp, tmp; - double_double cterm, csum, cdiv, z2, c2v, ctmp; - - if (is_h) { - sgn = -1; - } else { - sgn = 1; - } - - tmp = -xsf::cephes::lgam(v + 1.5) + (v + 1) * std::log(z / 2); - if (tmp < -600 || tmp > 600) { - /* Scale exponent to postpone underflow/overflow */ - scaleexp = tmp / 2; - tmp -= scaleexp; - } else { - scaleexp = 0; - } - - term = 2 / std::sqrt(M_PI) * std::exp(tmp) * xsf::cephes::gammasgn(v + 1.5); - sum = term; - maxterm = 0; - - cterm = double_double(term); - csum = double_double(sum); - z2 = double_double(sgn * z * z); - c2v = double_double(2 * v); - - for (n = 0; n < STRUVE_MAXITER; ++n) { - /* cdiv = (3 + 2*n) * (3 + 2*n + 2*v)) */ - cdiv = double_double(3 + 2 * n); - ctmp = double_double(3 + 2 * n); - ctmp = ctmp + c2v; - cdiv = cdiv * ctmp; - - /* cterm *= z2 / cdiv */ - cterm = cterm * z2; - cterm = cterm / cdiv; - - csum = csum + cterm; - - term = static_cast(cterm); - sum = static_cast(csum); - - if (std::abs(term) > maxterm) { - maxterm = std::abs(term); - } - if (std::abs(term) < STRUVE_SUM_TINY * std::abs(sum) || term == 0 || !std::isfinite(sum)) { - break; - } - } - - *err = std::abs(term) + std::abs(maxterm) * 1e-22; - - if (scaleexp != 0) { - sum *= std::exp(scaleexp); - *err *= std::exp(scaleexp); - } - - if (sum == 0 && term == 0 && v < 0 && !is_h) { - /* Spurious underflow */ - *err = std::numeric_limits::infinity(); - return std::numeric_limits::quiet_NaN(); - ; - } - - return sum; - } - - /* - * Bessel series - * https://dlmf.nist.gov/11.4.19 - */ - XSF_HOST_DEVICE inline double struve_bessel_series(double v, double z, int is_h, double *err) { - int n; - double term, cterm, sum, maxterm; - - if (is_h && v < 0) { - /* Works less reliably in this region */ - *err = std::numeric_limits::infinity(); - return std::numeric_limits::quiet_NaN(); - } - - sum = 0; - maxterm = 0; - - cterm = std::sqrt(z / (2 * M_PI)); - - for (n = 0; n < STRUVE_MAXITER; ++n) { - if (is_h) { - term = cterm * xsf::cyl_bessel_j(n + v + 0.5, z) / (n + 0.5); - cterm *= z / 2 / (n + 1); - } else { - term = cterm * xsf::cephes::iv(n + v + 0.5, z) / (n + 0.5); - cterm *= -z / 2 / (n + 1); - } - sum += term; - if (std::abs(term) > maxterm) { - maxterm = std::abs(term); - } - if (std::abs(term) < STRUVE_SUM_EPS * std::abs(sum) || term == 0 || !std::isfinite(sum)) { - break; - } - } - - *err = std::abs(term) + std::abs(maxterm) * 1e-16; - - /* Account for potential underflow of the Bessel functions */ - *err += 1e-300 * std::abs(cterm); - - return sum; - } - - XSF_HOST_DEVICE inline double struve_hl(double v, double z, int is_h) { - double value[4], err[4], tmp; - int n; - - if (z < 0) { - n = v; - if (v == n) { - tmp = (n % 2 == 0) ? -1 : 1; - return tmp * struve_hl(v, -z, is_h); - } else { - return std::numeric_limits::quiet_NaN(); - } - } else if (z == 0) { - if (v < -1) { - return xsf::cephes::gammasgn(v + 1.5) * std::numeric_limits::infinity(); - } else if (v == -1) { - return 2 / std::sqrt(M_PI) * xsf::cephes::rgamma(0.5); - } else { - return 0; - } - } - - n = -v - 0.5; - if (n == -v - 0.5 && n > 0) { - if (is_h) { - return (n % 2 == 0 ? 1 : -1) * xsf::cyl_bessel_j(n + 0.5, z); - } else { - return xsf::cephes::iv(n + 0.5, z); - } - } - - /* Try the asymptotic expansion */ - if (z >= 0.7 * v + 12) { - value[0] = struve_asymp_large_z(v, z, is_h, &err[0]); - if (err[0] < STRUVE_GOOD_EPS * std::abs(value[0])) { - return value[0]; - } - } else { - err[0] = std::numeric_limits::infinity(); - } - - /* Try power series */ - value[1] = struve_power_series(v, z, is_h, &err[1]); - if (err[1] < STRUVE_GOOD_EPS * std::abs(value[1])) { - return value[1]; - } - - /* Try bessel series */ - if (std::abs(z) < std::abs(v) + 20) { - value[2] = struve_bessel_series(v, z, is_h, &err[2]); - if (err[2] < STRUVE_GOOD_EPS * std::abs(value[2])) { - return value[2]; - } - } else { - err[2] = std::numeric_limits::infinity(); - } - - /* Return the best of the three, if it is acceptable */ - n = 0; - if (err[1] < err[n]) - n = 1; - if (err[2] < err[n]) - n = 2; - if (err[n] < STRUVE_ACCEPTABLE_EPS * std::abs(value[n]) || err[n] < STRUVE_ACCEPTABLE_ATOL) { - return value[n]; - } - - /* Maybe it really is an overflow? */ - tmp = -xsf::cephes::lgam(v + 1.5) + (v + 1) * std::log(z / 2); - if (!is_h) { - tmp = std::abs(tmp); - } - if (tmp > 700) { - set_error("struve", SF_ERROR_OVERFLOW, NULL); - return std::numeric_limits::infinity() * xsf::cephes::gammasgn(v + 1.5); - } - - /* Failure */ - set_error("struve", SF_ERROR_NO_RESULT, NULL); - return std::numeric_limits::quiet_NaN(); - } - } // namespace detail - - XSF_HOST_DEVICE inline double struve_h(double v, double z) { return detail::struve_hl(v, z, 1); } - - XSF_HOST_DEVICE inline double struve_l(double v, double z) { return detail::struve_hl(v, z, 0); } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/tandg.h b/scipy/special/xsf/cephes/tandg.h deleted file mode 100644 index 071b1a81d8bd..000000000000 --- a/scipy/special/xsf/cephes/tandg.h +++ /dev/null @@ -1,139 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* tandg.c - * - * Circular tangent of argument in degrees - * - * - * - * SYNOPSIS: - * - * double x, y, tandg(); - * - * y = tandg( x ); - * - * - * - * DESCRIPTION: - * - * Returns the circular tangent of the argument x in degrees. - * - * Range reduction is modulo pi/4. A rational function - * x + x**3 P(x**2)/Q(x**2) - * is employed in the basic interval [0, pi/4]. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,10 30000 3.2e-16 8.4e-17 - * - * ERROR MESSAGES: - * - * message condition value returned - * tandg total loss x > 1.0e14 (IEEE) 0.0 - * tandg singularity x = 180 k + 90 INFINITY - */ -/* cotdg.c - * - * Circular cotangent of argument in degrees - * - * - * - * SYNOPSIS: - * - * double x, y, cotdg(); - * - * y = cotdg( x ); - * - * - * - * DESCRIPTION: - * - * Returns the circular cotangent of the argument x in degrees. - * - * Range reduction is modulo pi/4. A rational function - * x + x**3 P(x**2)/Q(x**2) - * is employed in the basic interval [0, pi/4]. - * - * - * ERROR MESSAGES: - * - * message condition value returned - * cotdg total loss x > 1.0e14 (IEEE) 0.0 - * cotdg singularity x = 180 k INFINITY - */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1984, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -namespace xsf { -namespace cephes { - - namespace detail { - constexpr double tandg_lossth = 1.0e14; - - XSF_HOST_DEVICE inline double tancot(double xx, int cotflg) { - double x; - int sign; - - /* make argument positive but save the sign */ - if (xx < 0) { - x = -xx; - sign = -1; - } else { - x = xx; - sign = 1; - } - - if (x > detail::tandg_lossth) { - set_error("tandg", SF_ERROR_NO_RESULT, NULL); - return 0.0; - } - - /* modulo 180 */ - x = x - 180.0 * std::floor(x / 180.0); - if (cotflg) { - if (x <= 90.0) { - x = 90.0 - x; - } else { - x = x - 90.0; - sign *= -1; - } - } else { - if (x > 90.0) { - x = 180.0 - x; - sign *= -1; - } - } - if (x == 0.0) { - return 0.0; - } else if (x == 45.0) { - return sign * 1.0; - } else if (x == 90.0) { - set_error((cotflg ? "cotdg" : "tandg"), SF_ERROR_SINGULAR, NULL); - return std::numeric_limits::infinity(); - } - /* x is now transformed into [0, 90) */ - return sign * std::tan(x * detail::PI180); - } - - } // namespace detail - - XSF_HOST_DEVICE inline double tandg(double x) { return (detail::tancot(x, 0)); } - - XSF_HOST_DEVICE inline double cotdg(double x) { return (detail::tancot(x, 1)); } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/trig.h b/scipy/special/xsf/cephes/trig.h deleted file mode 100644 index 47dcdbe6ab3c..000000000000 --- a/scipy/special/xsf/cephes/trig.h +++ /dev/null @@ -1,58 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * - * Original author: Josh Wilson, 2020. - */ - -/* - * Implement sin(pi * x) and cos(pi * x) for real x. Since the periods - * of these functions are integral (and thus representable in double - * precision), it's possible to compute them with greater accuracy - * than sin(x) and cos(x). - */ -#pragma once - -#include "../config.h" - -namespace xsf { -namespace cephes { - - /* Compute sin(pi * x). */ - template - XSF_HOST_DEVICE T sinpi(T x) { - T s = 1.0; - - if (x < 0.0) { - x = -x; - s = -1.0; - } - - T r = std::fmod(x, 2.0); - if (r < 0.5) { - return s * std::sin(M_PI * r); - } else if (r > 1.5) { - return s * std::sin(M_PI * (r - 2.0)); - } else { - return -s * std::sin(M_PI * (r - 1.0)); - } - } - - /* Compute cos(pi * x) */ - template - XSF_HOST_DEVICE T cospi(T x) { - if (x < 0.0) { - x = -x; - } - - T r = std::fmod(x, 2.0); - if (r == 0.5) { - // We don't want to return -0.0 - return 0.0; - } - if (r < 1.0) { - return -std::sin(M_PI * (r - 0.5)); - } else { - return std::sin(M_PI * (r - 1.5)); - } - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/tukey.h b/scipy/special/xsf/cephes/tukey.h deleted file mode 100644 index 062966df316e..000000000000 --- a/scipy/special/xsf/cephes/tukey.h +++ /dev/null @@ -1,80 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* Compute the CDF of the Tukey-Lambda distribution - * using a bracketing search with special checks - * - * The PPF of the Tukey-lambda distribution is - * G(p) = (p**lam + (1-p)**lam) / lam - * - * Author: Travis Oliphant - */ - -#pragma once - -#include "../config.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double tukey_SMALLVAL = 1e-4; - constexpr double tukey_EPS = 1.0e-14; - constexpr int tukey_MAXCOUNT = 60; - - } // namespace detail - - XSF_HOST_DEVICE inline double tukeylambdacdf(double x, double lmbda) { - double pmin, pmid, pmax, plow, phigh, xeval; - int count; - - if (std::isnan(x) || std::isnan(lmbda)) { - return std::numeric_limits::quiet_NaN(); - } - - xeval = 1.0 / lmbda; - if (lmbda > 0.0) { - if (x <= (-xeval)) { - return 0.0; - } - if (x >= xeval) { - return 1.0; - } - } - - if ((-detail::tukey_SMALLVAL < lmbda) && (lmbda < detail::tukey_SMALLVAL)) { - if (x >= 0) { - return 1.0 / (1.0 + std::exp(-x)); - } else { - return exp(x) / (1.0 + exp(x)); - } - } - - pmin = 0.0; - pmid = 0.5; - pmax = 1.0; - plow = pmin; - phigh = pmax; - count = 0; - - while ((count < detail::tukey_MAXCOUNT) && (std::abs(pmid - plow) > detail::tukey_EPS)) { - xeval = (std::pow(pmid, lmbda) - std::pow(1.0 - pmid, lmbda)) / lmbda; - if (xeval == x) { - return pmid; - } - if (xeval > x) { - phigh = pmid; - pmid = (pmid + plow) / 2.0; - } else { - plow = pmid; - pmid = (pmid + phigh) / 2.0; - } - count++; - } - return pmid; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/unity.h b/scipy/special/xsf/cephes/unity.h deleted file mode 100644 index eb045edda212..000000000000 --- a/scipy/special/xsf/cephes/unity.h +++ /dev/null @@ -1,186 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. */ - -/* unity.c - * - * Relative error approximations for function arguments near - * unity. - * - * log1p(x) = log(1+x) - * expm1(x) = exp(x) - 1 - * cosm1(x) = cos(x) - 1 - * lgam1p(x) = lgam(1+x) - * - */ - -/* Scipy changes: - * - 06-10-2016: added lgam1p - */ -#pragma once - -#include "../config.h" - -#include "const.h" -#include "gamma.h" -#include "polevl.h" -#include "zeta.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* log1p(x) = log(1 + x) */ - - /* Coefficients for log(1+x) = x - x**2/2 + x**3 P(x)/Q(x) - * 1/sqrt(2) <= x < sqrt(2) - * Theoretical peak relative error = 2.32e-20 - */ - - constexpr double unity_LP[] = { - 4.5270000862445199635215E-5, 4.9854102823193375972212E-1, 6.5787325942061044846969E0, - 2.9911919328553073277375E1, 6.0949667980987787057556E1, 5.7112963590585538103336E1, - 2.0039553499201281259648E1, - }; - - constexpr double unity_LQ[] = { - /* 1.0000000000000000000000E0, */ - 1.5062909083469192043167E1, 8.3047565967967209469434E1, 2.2176239823732856465394E2, - 3.0909872225312059774938E2, 2.1642788614495947685003E2, 6.0118660497603843919306E1, - }; - - } // namespace detail - - XSF_HOST_DEVICE inline double log1p(double x) { - double z; - - z = 1.0 + x; - if ((z < M_SQRT1_2) || (z > M_SQRT2)) - return (std::log(z)); - z = x * x; - z = -0.5 * z + x * (z * polevl(x, detail::unity_LP, 6) / p1evl(x, detail::unity_LQ, 6)); - return (x + z); - } - - /* log(1 + x) - x */ - XSF_HOST_DEVICE inline double log1pmx(double x) { - if (std::abs(x) < 0.5) { - uint64_t n; - double xfac = x; - double term; - double res = 0; - - for (n = 2; n < detail::MAXITER; n++) { - xfac *= -x; - term = xfac / n; - res += term; - if (std::abs(term) < detail::MACHEP * std::abs(res)) { - break; - } - } - return res; - } else { - return log1p(x) - x; - } - } - - /* expm1(x) = exp(x) - 1 */ - - /* e^x = 1 + 2x P(x^2)/( Q(x^2) - P(x^2) ) - * -0.5 <= x <= 0.5 - */ - - namespace detail { - - constexpr double unity_EP[3] = { - 1.2617719307481059087798E-4, - 3.0299440770744196129956E-2, - 9.9999999999999999991025E-1, - }; - - constexpr double unity_EQ[4] = { - 3.0019850513866445504159E-6, - 2.5244834034968410419224E-3, - 2.2726554820815502876593E-1, - 2.0000000000000000000897E0, - }; - - } // namespace detail - - XSF_HOST_DEVICE inline double expm1(double x) { - double r, xx; - - if (!std::isfinite(x)) { - if (std::isnan(x)) { - return x; - } else if (x > 0) { - return x; - } else { - return -1.0; - } - } - if ((x < -0.5) || (x > 0.5)) - return (std::exp(x) - 1.0); - xx = x * x; - r = x * polevl(xx, detail::unity_EP, 2); - r = r / (polevl(xx, detail::unity_EQ, 3) - r); - return (r + r); - } - - /* cosm1(x) = cos(x) - 1 */ - - namespace detail { - constexpr double unity_coscof[7] = { - 4.7377507964246204691685E-14, -1.1470284843425359765671E-11, 2.0876754287081521758361E-9, - -2.7557319214999787979814E-7, 2.4801587301570552304991E-5, -1.3888888888888872993737E-3, - 4.1666666666666666609054E-2, - }; - - } - - XSF_HOST_DEVICE inline double cosm1(double x) { - double xx; - - if ((x < -M_PI_4) || (x > M_PI_4)) - return (std::cos(x) - 1.0); - xx = x * x; - xx = -0.5 * xx + xx * xx * polevl(xx, detail::unity_coscof, 6); - return xx; - } - - namespace detail { - /* Compute lgam(x + 1) around x = 0 using its Taylor series. */ - XSF_HOST_DEVICE inline double lgam1p_taylor(double x) { - int n; - double xfac, coeff, res; - - if (x == 0) { - return 0; - } - res = -SCIPY_EULER * x; - xfac = -x; - for (n = 2; n < 42; n++) { - xfac *= -x; - coeff = xsf::cephes::zeta(n, 1) * xfac / n; - res += coeff; - if (std::abs(coeff) < detail::MACHEP * std::abs(res)) { - break; - } - } - - return res; - } - } // namespace detail - - /* Compute lgam(x + 1). */ - XSF_HOST_DEVICE inline double lgam1p(double x) { - if (std::abs(x) <= 0.5) { - return detail::lgam1p_taylor(x); - } else if (std::abs(x - 1) < 0.5) { - return std::log(x) + detail::lgam1p_taylor(x - 1); - } else { - return lgam(x + 1); - } - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/yn.h b/scipy/special/xsf/cephes/yn.h deleted file mode 100644 index da942305266b..000000000000 --- a/scipy/special/xsf/cephes/yn.h +++ /dev/null @@ -1,118 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* yn.c - * - * Bessel function of second kind of integer order - * - * - * - * SYNOPSIS: - * - * double x, y, yn(); - * int n; - * - * y = yn( n, x ); - * - * - * - * DESCRIPTION: - * - * Returns Bessel function of order n, where n is a - * (possibly negative) integer. - * - * The function is evaluated by forward recurrence on - * n, starting with values computed by the routines - * y0() and y1(). - * - * If n = 0 or 1 the routine for y0 or y1 is called - * directly. - * - * - * - * ACCURACY: - * - * - * Absolute error, except relative - * when y > 1: - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 3.4e-15 4.3e-16 - * - * - * ERROR MESSAGES: - * - * message condition value returned - * yn singularity x = 0 INFINITY - * yn overflow INFINITY - * - * Spot checked against tables for x, n between 0 and 100. - * - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 2000 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "j0.h" -#include "j1.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE inline double yn(int n, double x) { - double an, anm1, anm2, r; - int k, sign; - - if (n < 0) { - n = -n; - if ((n & 1) == 0) { /* -1**n */ - sign = 1; - } else { - sign = -1; - } - } else { - sign = 1; - } - - if (n == 0) { - return (sign * y0(x)); - } - if (n == 1) { - return (sign * y1(x)); - } - - /* test for overflow */ - if (x == 0.0) { - set_error("yn", SF_ERROR_SINGULAR, NULL); - return -std::numeric_limits::infinity() * sign; - } else if (x < 0.0) { - set_error("yn", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - /* forward recurrence on n */ - - anm2 = y0(x); - anm1 = y1(x); - k = 1; - r = 2 * k; - do { - an = r * anm1 / x - anm2; - anm2 = anm1; - anm1 = an; - r += 2.0; - ++k; - } while (k < n && std::isfinite(an)); - - return (sign * an); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/yv.h b/scipy/special/xsf/cephes/yv.h deleted file mode 100644 index e97564506b22..000000000000 --- a/scipy/special/xsf/cephes/yv.h +++ /dev/null @@ -1,55 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 2000 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "jv.h" -#include "yn.h" - -namespace xsf { -namespace cephes { - - /* - * Bessel function of noninteger order - */ - XSF_HOST_DEVICE inline double yv(double v, double x) { - double y, t; - int n; - - n = v; - if (n == v) { - y = yn(n, x); - return (y); - } else if (v == std::floor(v)) { - /* Zero in denominator. */ - set_error("yv", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - t = M_PI * v; - y = (std::cos(t) * jv(v, x) - jv(-v, x)) / std::sin(t); - - if (std::isinf(y)) { - if (v > 0) { - set_error("yv", SF_ERROR_OVERFLOW, NULL); - return -std::numeric_limits::infinity(); - } else if (v < -1e10) { - /* Whether it's +inf or -inf is numerically ill-defined. */ - set_error("yv", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - } - - return (y); - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/zeta.h b/scipy/special/xsf/cephes/zeta.h deleted file mode 100644 index 6f9d68e0bdce..000000000000 --- a/scipy/special/xsf/cephes/zeta.h +++ /dev/null @@ -1,172 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* zeta.c - * - * Riemann zeta function of two arguments - * - * - * - * SYNOPSIS: - * - * double x, q, y, zeta(); - * - * y = zeta( x, q ); - * - * - * - * DESCRIPTION: - * - * - * - * inf. - * - -x - * zeta(x,q) = > (k+q) - * - - * k=0 - * - * where x > 1 and q is not a negative integer or zero. - * The Euler-Maclaurin summation formula is used to obtain - * the expansion - * - * n - * - -x - * zeta(x,q) = > (k+q) - * - - * k=1 - * - * 1-x inf. B x(x+1)...(x+2j) - * (n+q) 1 - 2j - * + --------- - ------- + > -------------------- - * x-1 x - x+2j+1 - * 2(n+q) j=1 (2j)! (n+q) - * - * where the B2j are Bernoulli numbers. Note that (see zetac.c) - * zeta(x,1) = zetac(x) + 1. - * - * - * - * ACCURACY: - * - * - * - * REFERENCE: - * - * Gradshteyn, I. S., and I. M. Ryzhik, Tables of Integrals, - * Series, and Products, p. 1073; Academic Press, 1980. - * - */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1984, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "../error.h" -#include "const.h" - -namespace xsf { -namespace cephes { - - namespace detail { - /* Expansion coefficients - * for Euler-Maclaurin summation formula - * (2k)! / B2k - * where B2k are Bernoulli numbers - */ - constexpr double zeta_A[] = { - 12.0, - -720.0, - 30240.0, - -1209600.0, - 47900160.0, - -1.8924375803183791606e9, /*1.307674368e12/691 */ - 7.47242496e10, - -2.950130727918164224e12, /*1.067062284288e16/3617 */ - 1.1646782814350067249e14, /*5.109094217170944e18/43867 */ - -4.5979787224074726105e15, /*8.028576626982912e20/174611 */ - 1.8152105401943546773e17, /*1.5511210043330985984e23/854513 */ - -7.1661652561756670113e18 /*1.6938241367317436694528e27/236364091 */ - }; - - /* 30 Nov 86 -- error in third coefficient fixed */ - } // namespace detail - - XSF_HOST_DEVICE double inline zeta(double x, double q) { - int i; - double a, b, k, s, t, w; - - if (x == 1.0) - goto retinf; - - if (x < 1.0) { - domerr: - set_error("zeta", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - - if (q <= 0.0) { - if (q == floor(q)) { - set_error("zeta", SF_ERROR_SINGULAR, NULL); - retinf: - return (std::numeric_limits::infinity()); - } - if (x != std::floor(x)) - goto domerr; /* because q^-x not defined */ - } - - /* Asymptotic expansion - * https://dlmf.nist.gov/25.11#E43 - */ - if (q > 1e8) { - return (1 / (x - 1) + 1 / (2 * q)) * std::pow(q, 1 - x); - } - - /* Euler-Maclaurin summation formula */ - - /* Permit negative q but continue sum until n+q > +9 . - * This case should be handled by a reflection formula. - * If q<0 and x is an integer, there is a relation to - * the polyGamma function. - */ - s = std::pow(q, -x); - a = q; - i = 0; - b = 0.0; - while ((i < 9) || (a <= 9.0)) { - i += 1; - a += 1.0; - b = std::pow(a, -x); - s += b; - if (std::abs(b / s) < detail::MACHEP) - goto done; - } - - w = a; - s += b * w / (x - 1.0); - s -= 0.5 * b; - a = 1.0; - k = 0.0; - for (i = 0; i < 12; i++) { - a *= x + k; - b /= w; - t = a * b / detail::zeta_A[i]; - s = s + t; - t = std::abs(t / s); - if (t < detail::MACHEP) - goto done; - k += 1.0; - a *= x + k; - b /= w; - k += 1.0; - } - done: - return (s); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/zetac.h b/scipy/special/xsf/cephes/zetac.h deleted file mode 100644 index c006681e7cc6..000000000000 --- a/scipy/special/xsf/cephes/zetac.h +++ /dev/null @@ -1,280 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* zetac.c - * - * Riemann zeta function - * - * - * - * SYNOPSIS: - * - * double x, y, zetac(); - * - * y = zetac( x ); - * - * - * - * DESCRIPTION: - * - * - * - * inf. - * - -x - * zetac(x) = > k , x > 1, - * - - * k=2 - * - * is related to the Riemann zeta function by - * - * Riemann zeta(x) = zetac(x) + 1. - * - * Extension of the function definition for x < 1 is implemented. - * Zero is returned for x > log2(INFINITY). - * - * ACCURACY: - * - * Tabulated values have full machine accuracy. - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 1,50 10000 9.8e-16 1.3e-16 - * - * - */ - -/* - * Cephes Math Library Release 2.1: January, 1989 - * Copyright 1984, 1987, 1989 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" - -#include "const.h" -#include "lanczos.h" -#include "polevl.h" -#include "zeta.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* Riemann zeta(x) - 1 - * for integer arguments between 0 and 30. - */ - constexpr double azetac[] = {-1.50000000000000000000E0, 0.0, /* Not used; zetac(1.0) is infinity. */ - 6.44934066848226436472E-1, 2.02056903159594285400E-1, - 8.23232337111381915160E-2, 3.69277551433699263314E-2, - 1.73430619844491397145E-2, 8.34927738192282683980E-3, - 4.07735619794433937869E-3, 2.00839282608221441785E-3, - 9.94575127818085337146E-4, 4.94188604119464558702E-4, - 2.46086553308048298638E-4, 1.22713347578489146752E-4, - 6.12481350587048292585E-5, 3.05882363070204935517E-5, - 1.52822594086518717326E-5, 7.63719763789976227360E-6, - 3.81729326499983985646E-6, 1.90821271655393892566E-6, - 9.53962033872796113152E-7, 4.76932986787806463117E-7, - 2.38450502727732990004E-7, 1.19219925965311073068E-7, - 5.96081890512594796124E-8, 2.98035035146522801861E-8, - 1.49015548283650412347E-8, 7.45071178983542949198E-9, - 3.72533402478845705482E-9, 1.86265972351304900640E-9, - 9.31327432419668182872E-10}; - - /* 2**x (1 - 1/x) (zeta(x) - 1) = P(1/x)/Q(1/x), 1 <= x <= 10 */ - constexpr double zetac_P[9] = { - 5.85746514569725319540E11, 2.57534127756102572888E11, 4.87781159567948256438E10, - 5.15399538023885770696E9, 3.41646073514754094281E8, 1.60837006880656492731E7, - 5.92785467342109522998E5, 1.51129169964938823117E4, 2.01822444485997955865E2, - }; - - constexpr double zetac_Q[8] = { - /* 1.00000000000000000000E0, */ - 3.90497676373371157516E11, 5.22858235368272161797E10, 5.64451517271280543351E9, 3.39006746015350418834E8, - 1.79410371500126453702E7, 5.66666825131384797029E5, 1.60382976810944131506E4, 1.96436237223387314144E2, - }; - - /* log(zeta(x) - 1 - 2**-x), 10 <= x <= 50 */ - constexpr double zetac_A[11] = { - 8.70728567484590192539E6, 1.76506865670346462757E8, 2.60889506707483264896E10, - 5.29806374009894791647E11, 2.26888156119238241487E13, 3.31884402932705083599E14, - 5.13778997975868230192E15, -1.98123688133907171455E15, -9.92763810039983572356E16, - 7.82905376180870586444E16, 9.26786275768927717187E16, - }; - - constexpr double zetac_B[10] = { - /* 1.00000000000000000000E0, */ - -7.92625410563741062861E6, -1.60529969932920229676E8, -2.37669260975543221788E10, - -4.80319584350455169857E11, -2.07820961754173320170E13, -2.96075404507272223680E14, - -4.86299103694609136686E15, 5.34589509675789930199E15, 5.71464111092297631292E16, - -1.79915597658676556828E16, - }; - - /* (1-x) (zeta(x) - 1), 0 <= x <= 1 */ - constexpr double zetac_R[6] = { - -3.28717474506562731748E-1, 1.55162528742623950834E1, -2.48762831680821954401E2, - 1.01050368053237678329E3, 1.26726061410235149405E4, -1.11578094770515181334E5, - }; - - constexpr double zetac_S[5] = { - /* 1.00000000000000000000E0, */ - 1.95107674914060531512E1, 3.17710311750646984099E2, 3.03835500874445748734E3, - 2.03665876435770579345E4, 7.43853965136767874343E4, - }; - - constexpr double zetac_TAYLOR0[10] = { - -1.0000000009110164892, -1.0000000057646759799, - -9.9999983138417361078e-1, -1.0000013011460139596, - -1.000001940896320456, -9.9987929950057116496e-1, - -1.000785194477042408, -1.0031782279542924256, - -9.1893853320467274178e-1, -1.5, - }; - - constexpr int zetac_MAXL2 = 127; - - /* - * Compute zetac for positive arguments - */ - XSF_HOST_DEVICE inline double zetac_positive(double x) { - int i; - double a, b, s, w; - - if (x == 1.0) { - return std::numeric_limits::infinity(); - } - - if (x >= detail::zetac_MAXL2) { - /* because first term is 2**-x */ - return 0.0; - } - - /* Tabulated values for integer argument */ - w = std::floor(x); - if (w == x) { - i = x; - if (i < 31) { - return (azetac[i]); - } - } - - if (x < 1.0) { - w = 1.0 - x; - a = xsf::cephes::polevl(x, zetac_R, 5) / (w * xsf::cephes::p1evl(x, zetac_S, 5)); - return a; - } - - if (x <= 10.0) { - b = std::pow(2.0, x) * (x - 1.0); - w = 1.0 / x; - s = (x * xsf::cephes::polevl(w, zetac_P, 8)) / (b * xsf::cephes::p1evl(w, zetac_Q, 8)); - return s; - } - - if (x <= 50.0) { - b = std::pow(2.0, -x); - w = xsf::cephes::polevl(x, zetac_A, 10) / xsf::cephes::p1evl(x, zetac_B, 10); - w = std::exp(w) + b; - return w; - } - - /* Basic sum of inverse powers */ - s = 0.0; - a = 1.0; - do { - a += 2.0; - b = std::pow(a, -x); - s += b; - } while (b / s > MACHEP); - - b = std::pow(2.0, -x); - s = (s + b) / (1.0 - b); - return s; - } - - /* - * Compute zetac for small negative x. We can't use the reflection - * formula because to double precision 1 - x = 1 and zetac(1) = inf. - */ - XSF_HOST_DEVICE inline double zetac_smallneg(double x) { - return xsf::cephes::polevl(x, zetac_TAYLOR0, 9); - } - - /* - * Compute zetac using the reflection formula (see DLMF 25.4.2) plus - * the Lanczos approximation for Gamma to avoid overflow. - */ - XSF_HOST_DEVICE inline double zeta_reflection(double x) { - double base, large_term, small_term, hx, x_shift; - - hx = x / 2; - if (hx == std::floor(hx)) { - /* Hit a zero of the sine factor */ - return 0; - } - - /* Reduce the argument to sine */ - x_shift = std::fmod(x, 4); - small_term = -SQRT2PI * sin(0.5 * M_PI * x_shift); - small_term *= xsf::cephes::lanczos_sum_expg_scaled(x + 1) * xsf::cephes::zeta(x + 1, 1); - - /* Group large terms together to prevent overflow */ - base = (x + xsf::cephes::lanczos_g + 0.5) / (2 * M_PI * M_E); - large_term = std::pow(base, x + 0.5); - if (std::isfinite(large_term)) { - return large_term * small_term; - } - /* - * We overflowed, but we might be able to stave off overflow by - * factoring in the small term earlier. To do this we compute - * - * (sqrt(large_term) * small_term) * sqrt(large_term) - * - * Since we only call this method for negative x bounded away from - * zero, the small term can only be as small sine on that region; - * i.e. about machine epsilon. This means that if the above still - * overflows, then there was truly no avoiding it. - */ - large_term = std::pow(base, 0.5 * x + 0.25); - return (large_term * small_term) * large_term; - } - - } // namespace detail - - /* - * Riemann zeta function, minus one - */ - XSF_HOST_DEVICE inline double zetac(double x) { - if (std::isnan(x)) { - return x; - } else if (x == -std::numeric_limits::infinity()) { - return std::numeric_limits::quiet_NaN(); - } else if (x < 0.0 && x > -0.01) { - return detail::zetac_smallneg(x); - } else if (x < 0.0) { - return detail::zeta_reflection(-x) - 1; - } else { - return detail::zetac_positive(x); - } - } - - /* - * Riemann zeta function - */ - XSF_HOST_DEVICE inline double riemann_zeta(double x) { - if (std::isnan(x)) { - return x; - } else if (x == -std::numeric_limits::infinity()) { - return std::numeric_limits::quiet_NaN(); - } else if (x < 0.0 && x > -0.01) { - return 1 + detail::zetac_smallneg(x); - } else if (x < 0.0) { - return detail::zeta_reflection(-x); - } else { - return 1 + detail::zetac_positive(x); - } - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/config.h b/scipy/special/xsf/config.h deleted file mode 100644 index 5cb40ed1e1e0..000000000000 --- a/scipy/special/xsf/config.h +++ /dev/null @@ -1,304 +0,0 @@ -#pragma once - -// Define math constants if they are not available -#ifndef M_E -#define M_E 2.71828182845904523536 -#endif - -#ifndef M_LOG2E -#define M_LOG2E 1.44269504088896340736 -#endif - -#ifndef M_LOG10E -#define M_LOG10E 0.434294481903251827651 -#endif - -#ifndef M_LN2 -#define M_LN2 0.693147180559945309417 -#endif - -#ifndef M_LN10 -#define M_LN10 2.30258509299404568402 -#endif - -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif - -#ifndef M_PI_2 -#define M_PI_2 1.57079632679489661923 -#endif - -#ifndef M_PI_4 -#define M_PI_4 0.785398163397448309616 -#endif - -#ifndef M_1_PI -#define M_1_PI 0.318309886183790671538 -#endif - -#ifndef M_2_PI -#define M_2_PI 0.636619772367581343076 -#endif - -#ifndef M_2_SQRTPI -#define M_2_SQRTPI 1.12837916709551257390 -#endif - -#ifndef M_SQRT2 -#define M_SQRT2 1.41421356237309504880 -#endif - -#ifndef M_SQRT1_2 -#define M_SQRT1_2 0.707106781186547524401 -#endif - -#ifdef __CUDACC__ -#define XSF_HOST_DEVICE __host__ __device__ - -#include -#include -#include -#include -#include -#include -#include - -// Fallback to global namespace for functions unsupported on NVRTC Jit -#ifdef _LIBCUDACXX_COMPILER_NVRTC -#include -#endif - -namespace std { - -XSF_HOST_DEVICE inline double abs(double num) { return cuda::std::abs(num); } - -XSF_HOST_DEVICE inline double exp(double num) { return cuda::std::exp(num); } - -XSF_HOST_DEVICE inline double log(double num) { return cuda::std::log(num); } - -XSF_HOST_DEVICE inline double sqrt(double num) { return cuda::std::sqrt(num); } - -XSF_HOST_DEVICE inline bool isinf(double num) { return cuda::std::isinf(num); } - -XSF_HOST_DEVICE inline bool isnan(double num) { return cuda::std::isnan(num); } - -XSF_HOST_DEVICE inline bool isfinite(double num) { return cuda::std::isfinite(num); } - -XSF_HOST_DEVICE inline double pow(double x, double y) { return cuda::std::pow(x, y); } - -XSF_HOST_DEVICE inline double sin(double x) { return cuda::std::sin(x); } - -XSF_HOST_DEVICE inline double cos(double x) { return cuda::std::cos(x); } - -XSF_HOST_DEVICE inline double tan(double x) { return cuda::std::tan(x); } - -XSF_HOST_DEVICE inline double atan(double x) { return cuda::std::atan(x); } - -XSF_HOST_DEVICE inline double acos(double x) { return cuda::std::acos(x); } - -XSF_HOST_DEVICE inline double sinh(double x) { return cuda::std::sinh(x); } - -XSF_HOST_DEVICE inline double cosh(double x) { return cuda::std::cosh(x); } - -XSF_HOST_DEVICE inline double asinh(double x) { return cuda::std::asinh(x); } - -XSF_HOST_DEVICE inline bool signbit(double x) { return cuda::std::signbit(x); } - -// Fallback to global namespace for functions unsupported on NVRTC -#ifndef _LIBCUDACXX_COMPILER_NVRTC -XSF_HOST_DEVICE inline double ceil(double x) { return cuda::std::ceil(x); } -XSF_HOST_DEVICE inline double floor(double x) { return cuda::std::floor(x); } -XSF_HOST_DEVICE inline double round(double x) { return cuda::std::round(x); } -XSF_HOST_DEVICE inline double trunc(double x) { return cuda::std::trunc(x); } -XSF_HOST_DEVICE inline double fma(double x, double y, double z) { return cuda::std::fma(x, y, z); } -XSF_HOST_DEVICE inline double copysign(double x, double y) { return cuda::std::copysign(x, y); } -XSF_HOST_DEVICE inline double modf(double value, double *iptr) { return cuda::std::modf(value, iptr); } -XSF_HOST_DEVICE inline double fmax(double x, double y) { return cuda::std::fmax(x, y); } -XSF_HOST_DEVICE inline double fmin(double x, double y) { return cuda::std::fmin(x, y); } -XSF_HOST_DEVICE inline double log10(double num) { return cuda::std::log10(num); } -XSF_HOST_DEVICE inline double log1p(double num) { return cuda::std::log1p(num); } -XSF_HOST_DEVICE inline double frexp(double num, int *exp) { return cuda::std::frexp(num, exp); } -XSF_HOST_DEVICE inline double ldexp(double num, int exp) { return cuda::std::ldexp(num, exp); } -XSF_HOST_DEVICE inline double fmod(double x, double y) { return cuda::std::fmod(x, y); } -XSF_HOST_DEVICE inline double nextafter(double from, double to) { return cuda::std::nextafter(from, to); } -#else -XSF_HOST_DEVICE inline double ceil(double x) { return ::ceil(x); } -XSF_HOST_DEVICE inline double floor(double x) { return ::floor(x); } -XSF_HOST_DEVICE inline double round(double x) { return ::round(x); } -XSF_HOST_DEVICE inline double trunc(double x) { return ::trunc(x); } -XSF_HOST_DEVICE inline double fma(double x, double y, double z) { return ::fma(x, y, z); } -XSF_HOST_DEVICE inline double copysign(double x, double y) { return ::copysign(x, y); } -XSF_HOST_DEVICE inline double modf(double value, double *iptr) { return ::modf(value, iptr); } -XSF_HOST_DEVICE inline double fmax(double x, double y) { return ::fmax(x, y); } -XSF_HOST_DEVICE inline double fmin(double x, double y) { return ::fmin(x, y); } -XSF_HOST_DEVICE inline double log10(double num) { return ::log10(num); } -XSF_HOST_DEVICE inline double log1p(double num) { return ::log1p(num); } -XSF_HOST_DEVICE inline double frexp(double num, int *exp) { return ::frexp(num, exp); } -XSF_HOST_DEVICE inline double ldexp(double num, int exp) { return ::ldexp(num, exp); } -XSF_HOST_DEVICE inline double fmod(double x, double y) { return ::fmod(x, y); } -XSF_HOST_DEVICE inline double nextafter(double from, double to) { return ::nextafter(from, to); } -#endif - -template -XSF_HOST_DEVICE void swap(T &a, T &b) { - cuda::std::swap(a, b); -} - -// Reimplement std::clamp until it's available in CuPy -template -XSF_HOST_DEVICE constexpr T clamp(T &v, T &lo, T &hi) { - return v < lo ? lo : (v > hi ? lo : v); -} - -template -using numeric_limits = cuda::std::numeric_limits; - -// Must use thrust for complex types in order to support CuPy -template -using complex = thrust::complex; - -template -XSF_HOST_DEVICE T abs(const complex &z) { - return thrust::abs(z); -} - -template -XSF_HOST_DEVICE complex exp(const complex &z) { - return thrust::exp(z); -} - -template -XSF_HOST_DEVICE complex log(const complex &z) { - return thrust::log(z); -} - -template -XSF_HOST_DEVICE T norm(const complex &z) { - return thrust::norm(z); -} - -template -XSF_HOST_DEVICE complex sqrt(const complex &z) { - return thrust::sqrt(z); -} - -template -XSF_HOST_DEVICE complex conj(const complex &z) { - return thrust::conj(z); -} - -template -XSF_HOST_DEVICE complex pow(const complex &x, const complex &y) { - return thrust::pow(x, y); -} - -template -XSF_HOST_DEVICE complex pow(const complex &x, const T &y) { - return thrust::pow(x, y); -} - -// Other types and utilities -template -using is_floating_point = cuda::std::is_floating_point; - -template -using enable_if = cuda::std::enable_if; - -template -using decay = cuda::std::decay; - -template -using invoke_result = cuda::std::invoke_result; - -template -using pair = cuda::std::pair; - -template -using tuple = cuda::std::tuple; - -using cuda::std::ptrdiff_t; -using cuda::std::size_t; -using cuda::std::uint64_t; - -#define XSF_ASSERT(a) - -} // namespace std - -#else -#define XSF_HOST_DEVICE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef DEBUG -#define XSF_ASSERT(a) assert(a) -#else -#define XSF_ASSERT(a) -#endif - -namespace xsf { - -// basic -using std::abs; - -// exponential -using std::exp; - -// power -using std::sqrt; - -// trigonometric -using std::cos; -using std::sin; - -// floating-point manipulation -using std::copysign; - -// classification and comparison -using std::isfinite; -using std::isinf; -using std::isnan; -using std::signbit; - -// complex -using std::imag; -using std::real; - -template -struct remove_complex { - using type = T; -}; - -template -struct remove_complex> { - using type = T; -}; - -template -using remove_complex_t = typename remove_complex::type; - -template -struct complex_type { - using type = std::complex; -}; - -template -using complex_type_t = typename complex_type::type; - -template -using complex = complex_type_t; - -} // namespace xsf - -#endif diff --git a/scipy/special/xsf/digamma.h b/scipy/special/xsf/digamma.h deleted file mode 100644 index db51362ce039..000000000000 --- a/scipy/special/xsf/digamma.h +++ /dev/null @@ -1,205 +0,0 @@ -/* Translated from Cython into C++ by SciPy developers in 2024. - * Original header comment appears below. - */ - -/* An implementation of the digamma function for complex arguments. - * - * Author: Josh Wilson - * - * Distributed under the same license as Scipy. - * - * Sources: - * [1] "The Digital Library of Mathematical Functions", dlmf.nist.gov - * - * [2] mpmath (version 0.19), http://mpmath.org - */ - -#pragma once - -#include "cephes/psi.h" -#include "cephes/zeta.h" -#include "config.h" -#include "error.h" -#include "trig.h" - -namespace xsf { -namespace detail { - // All of the following were computed with mpmath - // Location of the positive root - constexpr double digamma_posroot = 1.4616321449683623; - // Value of the positive root - constexpr double digamma_posrootval = -9.2412655217294275e-17; - // Location of the negative root - constexpr double digamma_negroot = -0.504083008264455409; - // Value of the negative root - constexpr double digamma_negrootval = 7.2897639029768949e-17; - - template - XSF_HOST_DEVICE T digamma_zeta_series(T z, double root, double rootval) { - T res = rootval; - T coeff = -1.0; - - z = z - root; - T term; - for (int n = 1; n < 100; n++) { - coeff *= -z; - term = coeff * cephes::zeta(n + 1, root); - res += term; - if (std::abs(term) < std::numeric_limits::epsilon() * std::abs(res)) { - break; - } - } - return res; - } - - XSF_HOST_DEVICE inline std::complex - digamma_forward_recurrence(std::complex z, std::complex psiz, int n) { - /* Compute digamma(z + n) using digamma(z) using the recurrence - * relation - * - * digamma(z + 1) = digamma(z) + 1/z. - * - * See https://dlmf.nist.gov/5.5#E2 */ - std::complex res = psiz; - - for (int k = 0; k < n; k++) { - res += 1.0 / (z + static_cast(k)); - } - return res; - } - - XSF_HOST_DEVICE inline std::complex - digamma_backward_recurrence(std::complex z, std::complex psiz, int n) { - /* Compute digamma(z - n) using digamma(z) and a recurrence relation. */ - std::complex res = psiz; - - for (int k = 1; k < n + 1; k++) { - res -= 1.0 / (z - static_cast(k)); - } - return res; - } - - XSF_HOST_DEVICE inline std::complex digamma_asymptotic_series(std::complex z) { - /* Evaluate digamma using an asymptotic series. See - * - * https://dlmf.nist.gov/5.11#E2 */ - double bernoulli2k[] = {0.166666666666666667, -0.0333333333333333333, 0.0238095238095238095, - -0.0333333333333333333, 0.0757575757575757576, -0.253113553113553114, - 1.16666666666666667, -7.09215686274509804, 54.9711779448621554, - -529.124242424242424, 6192.12318840579710, -86580.2531135531136, - 1425517.16666666667, -27298231.0678160920, 601580873.900642368, - -15116315767.0921569}; - std::complex rzz = 1.0 / z / z; - std::complex zfac = 1.0; - std::complex term; - std::complex res; - - if (!(std::isfinite(z.real()) && std::isfinite(z.imag()))) { - /* Check for infinity (or nan) and return early. - * Result of division by complex infinity is implementation dependent. - * and has been observed to vary between C++ stdlib and CUDA stdlib. - */ - return std::log(z); - } - - res = std::log(z) - 0.5 / z; - - for (int k = 1; k < 17; k++) { - zfac *= rzz; - term = -bernoulli2k[k - 1] * zfac / (2 * static_cast(k)); - res += term; - if (std::abs(term) < std::numeric_limits::epsilon() * std::abs(res)) { - break; - } - } - return res; - } - -} // namespace detail - -XSF_HOST_DEVICE inline double digamma(double z) { - /* Wrap Cephes' psi to take advantage of the series expansion around - * the smallest negative zero. - */ - if (std::abs(z - detail::digamma_negroot) < 0.3) { - return detail::digamma_zeta_series(z, detail::digamma_negroot, detail::digamma_negrootval); - } - return cephes::psi(z); -} - -XSF_HOST_DEVICE inline float digamma(float z) { return static_cast(digamma(static_cast(z))); } - -XSF_HOST_DEVICE inline std::complex digamma(std::complex z) { - /* - * Compute the digamma function for complex arguments. The strategy - * is: - * - * - Around the two zeros closest to the origin (posroot and negroot) - * use a Taylor series with precomputed zero order coefficient. - * - If close to the origin, use a recurrence relation to step away - * from the origin. - * - If close to the negative real axis, use the reflection formula - * to move to the right halfplane. - * - If |z| is large (> 16), use the asymptotic series. - * - If |z| is small, use a recurrence relation to make |z| large - * enough to use the asymptotic series. - */ - double absz = std::abs(z); - std::complex res = 0; - /* Use the asymptotic series for z away from the negative real axis - * with abs(z) > smallabsz. */ - int smallabsz = 16; - /* Use the reflection principle for z with z.real < 0 that are within - * smallimag of the negative real axis. - * int smallimag = 6 # unused below except in a comment */ - - if (z.real() <= 0.0 && std::ceil(z.real()) == z) { - // Poles - set_error("digamma", SF_ERROR_SINGULAR, NULL); - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - if (std::abs(z - detail::digamma_negroot) < 0.3) { - // First negative root. - return detail::digamma_zeta_series(z, detail::digamma_negroot, detail::digamma_negrootval); - } - - if (z.real() < 0 and std::abs(z.imag()) < smallabsz) { - /* Reflection formula for digamma. See - * - *https://dlmf.nist.gov/5.5#E4 - */ - res = -M_PI * cospi(z) / sinpi(z); - z = 1.0 - z; - absz = std::abs(z); - } - - if (absz < 0.5) { - /* Use one step of the recurrence relation to step away from - * the pole. */ - res = -1.0 / z; - z += 1.0; - absz = std::abs(z); - } - - if (std::abs(z - detail::digamma_posroot) < 0.5) { - res += detail::digamma_zeta_series(z, detail::digamma_posroot, detail::digamma_posrootval); - } else if (absz > smallabsz) { - res += detail::digamma_asymptotic_series(z); - } else if (z.real() >= 0.0) { - double n = std::trunc(smallabsz - absz) + 1; - std::complex init = detail::digamma_asymptotic_series(z + n); - res += detail::digamma_backward_recurrence(z + n, init, n); - } else { - // z.real() < 0, absz < smallabsz, and z.imag() > smallimag - double n = std::trunc(smallabsz - absz) - 1; - std::complex init = detail::digamma_asymptotic_series(z - n); - res += detail::digamma_forward_recurrence(z - n, init, n); - } - return res; -} - -XSF_HOST_DEVICE inline std::complex digamma(std::complex z) { - return static_cast>(digamma(static_cast>(z))); -} - -} // namespace xsf diff --git a/scipy/special/xsf/dual.h b/scipy/special/xsf/dual.h deleted file mode 100644 index f0f79de28902..000000000000 --- a/scipy/special/xsf/dual.h +++ /dev/null @@ -1,670 +0,0 @@ -#pragma once - -#include "binom.h" -#include "config.h" -#include "numbers.h" - -namespace xsf { - -namespace detail { - template - constexpr T small_binom_coefs[3][3] = { - {T(1.0), T(0.0), T(0.0)}, {T(1.0), T(1.0), T(0.0)}, {T(1.0), T(2.0), T(1.0)} - }; - - /* Since we only compute derivatives up to order 2, we only need - * Binomial coefficients with n <= 2 for use in the General - * Leibniz rule. Get these from a lookup table. */ - template - T fast_binom(size_t n, size_t k) { - if ((n <= 2) && (k <= 2)) { - return small_binom_coefs[n][k]; - } - return T(xsf::binom(static_cast(n), static_cast(k))); - } -} // namespace detail - -template -class dual; - -template -class dual { - public: - using value_type = T; - - private: - value_type data[Order + 1]; - - public: - dual() = default; - - dual(value_type value) { - data[0] = value; - for (size_t i = 1; i <= Order; ++i) { - data[i] = 0; - } - } - - dual(std::initializer_list data) { - for (auto it = data.begin(); it != data.end(); ++it) { - this->data[it - data.begin()] = *it; - } - for (size_t i = data.size(); i <= Order; ++i) { - this->data[i] = 0; - } - } - - template - dual(const dual &other) { - for (size_t i = 0; i <= Order; ++i) { - data[i] = other[i]; - } - } - - dual &operator=(const value_type &other) { - data[0] = other; - for (size_t i = 1; i <= Order; ++i) { - data[i] = 0; - } - - return *this; - } - - dual &operator+=(const dual &other) { - for (size_t i = 0; i <= Order; ++i) { - data[i] += other.data[i]; - } - - return *this; - } - - dual &operator+=(const value_type &other) { - data[0] += other; - - return *this; - } - - dual &operator-=(const dual &other) { - for (size_t i = 0; i <= Order; ++i) { - data[i] -= other.data[i]; - } - - return *this; - } - - dual &operator-=(const value_type &other) { - data[0] -= other; - - return *this; - } - - dual &operator*=(const dual &other) { - for (size_t i = Order + 1; i-- > 0;) { - data[i] *= other.data[0]; - // General Leibniz Rule - for (size_t j = 0; j < i; ++j) { - data[i] += detail::fast_binom(i, j) * data[j] * other.data[i - j]; - } - } - - return *this; - } - - dual &operator*=(const value_type &other) { - for (size_t i = 0; i <= Order; ++i) { - data[i] *= other; - } - - return *this; - } - - dual &operator/=(const dual &other) { - for (size_t i = 0; i <= Order; ++i) { - for (size_t j = 1; j <= i; ++j) { - data[i] -= detail::fast_binom(i - 1, j) * other.data[j] * data[i - j]; - } - - data[i] /= other.data[0]; - } - - return *this; - } - - dual &operator/=(const value_type &other) { - for (size_t i = 0; i <= Order; ++i) { - data[i] /= other; - } - - return *this; - } - - value_type &value() { return data[0]; } - - const value_type &value() const { return data[0]; } - - value_type &operator[](size_t i) { return data[i]; } - - const value_type &operator[](size_t i) const { return data[i]; } - - static constexpr size_t max_order() { return Order; } -}; - -template -class dual { - public: - using value_type = T; - - private: - dual data[Order0 + 1]; - - public: - dual() = default; - - dual(value_type value) { - data[0] = value; - for (size_t i = 1; i <= Order0; ++i) { - data[i] = 0; - } - } - - dual(dual value) { - data[0] = value; - for (size_t i = 1; i <= Order0; ++i) { - data[i] = 0; - } - } - - dual(std::initializer_list> data) { - for (auto it = data.begin(); it != data.end(); ++it) { - this->data[it - data.begin()] = *it; - } - for (size_t i = data.size(); i <= Order0; ++i) { - this->data[i] = 0; - } - } - - template - dual(const dual &other) { - for (size_t i = 0; i <= Order0; ++i) { - data[i] = other[i]; - } - } - - dual &operator=(const value_type &other) { - data[0] = other; - for (size_t i = 1; i <= Order0; ++i) { - data[i] = 0; - } - - return *this; - } - - dual &operator+=(const dual &other) { - for (size_t i = 0; i <= Order0; ++i) { - data[i] += other.data[i]; - } - - return *this; - } - - dual &operator+=(const value_type &other) { - data[0] += other; - - return *this; - } - - dual &operator-=(const dual &other) { - for (size_t i = 0; i <= Order0; ++i) { - data[i] -= other.data[i]; - } - - return *this; - } - - dual &operator-=(const value_type &other) { - data[0] -= other; - - return *this; - } - - dual &operator*=(const dual &other) { - for (size_t i = Order0 + 1; i-- > 0;) { - data[i] *= other.data[0]; - // General Leibniz Rule - for (size_t j = 0; j < i; ++j) { - data[i] += detail::fast_binom(i, j) * data[j] * other.data[i - j]; - } - } - - return *this; - } - - dual &operator*=(const value_type &other) { - for (size_t i = 0; i <= Order0; ++i) { - data[i] *= other; - } - - return *this; - } - - dual &operator/=(const dual &other) { - for (size_t i = 0; i <= Order0; ++i) { - for (size_t j = 1; j <= i; ++j) { - data[i] -= detail::fast_binom(i - 1, j) * other.data[j] * data[i - j]; - } - - data[i] /= other.data[0]; - } - - return *this; - } - - dual &operator/=(const value_type &other) { - for (size_t i = 0; i <= Order0; ++i) { - data[i] /= other; - } - - return *this; - } - - value_type &value() { - dual &data0 = data[0]; - - return data0.value(); - } - - const value_type &value() const { - const dual &data0 = data[0]; - - return data0.value(); - } - - dual &operator[](size_t i) { return data[i]; } - - const dual &operator[](size_t i) const { return data[i]; } - - static constexpr size_t max_order() { return std::max({Order0, Order1, Orders...}); } -}; - -template -dual operator+(const dual &x) { - dual res; - for (size_t i = 0; i <= Order0; ++i) { - res[i] = +x[i]; - } - - return res; -} - -template -dual operator-(const dual &x) { - dual res; - for (size_t i = 0; i <= Order0; ++i) { - res[i] = -x[i]; - } - - return res; -} - -template -dual operator+(const dual &lhs, const dual &rhs) { - dual res = lhs; - res += rhs; - - return res; -} - -template -dual operator+(const dual &lhs, const T &rhs) { - dual res = lhs; - res += rhs; - - return res; -} - -template -dual operator+(const T &lhs, const dual &rhs) { - dual res = lhs; - res += rhs; - - return res; -} - -template -dual operator-(const dual &lhs, const dual &rhs) { - dual res = lhs; - res -= rhs; - - return res; -} - -template -dual operator-(const dual &lhs, const T &rhs) { - dual res = lhs; - res -= rhs; - - return res; -} - -template -dual operator*(const dual &lhs, const dual &rhs) { - dual res = lhs; - res *= rhs; - - return res; -} - -template -dual, Orders...> operator*(const dual, Orders...> &lhs, const dual &rhs) { - dual, Orders...> res = lhs; - res *= rhs; - - return res; -} - -template -dual, Orders...> operator*(const dual &lhs, const dual, Orders...> &rhs) { - dual, Orders...> res = lhs; - res *= rhs; - - return res; -} - -template -dual operator*(const dual &lhs, const T &rhs) { - dual res = lhs; - res *= rhs; - - return res; -} - -template -dual, Orders...> operator*(const dual &lhs, const complex &rhs) { - dual, Orders...> res = lhs; - res *= rhs; - - return res; -} - -template -dual operator*(const T &lhs, const dual &rhs) { - dual res = rhs; - res *= lhs; - - return res; -} - -template -dual, Orders...> operator*(const complex &lhs, const dual &rhs) { - dual, Orders...> res = rhs; - res *= lhs; - - return res; -} - -template -dual operator/(const dual &lhs, const dual &rhs) { - dual res = lhs; - res /= rhs; - - return res; -} - -template -dual operator/(const dual &lhs, const T &rhs) { - dual res = lhs; - res /= rhs; - - return res; -} - -template -dual operator/(const T &lhs, const dual &rhs) { - dual res = lhs; - res /= rhs; - - return res; -} - -template -bool operator==(const dual &lhs, const dual &rhs) { - return lhs.value() == rhs.value(); -} - -template -bool operator==(const dual &lhs, const U &rhs) { - return lhs.value() == rhs; -} - -template -bool operator!=(const dual &lhs, const dual &rhs) { - return lhs.value() != rhs.value(); -} - -template -bool operator<(const dual &lhs, const dual &rhs) { - return lhs.value() < rhs.value(); -} - -template -bool operator>(const dual &lhs, const dual &rhs) { - return lhs.value() > rhs.value(); -} - -template -bool operator<=(const dual &lhs, const dual &rhs) { - return lhs.value() <= rhs.value(); -} - -template -bool operator>=(const dual &lhs, const dual &rhs) { - return lhs.value() >= rhs.value(); -} - -template -dual dual_var(T value, size_t dim = 0) { - // dim must be zero - - if constexpr (Order >= 1) { - return {value, 1}; - } - - return value; -} - -template -dual dual_var(T value, size_t dim = 0) { - if (dim == 0) { - if constexpr (Orders0 >= 1) { - return {value, 1}; - } - - return value; - } - - return dual_var(value, dim - 1); -} - -template -dual dual_taylor_series(const T (&coef)[N], const dual &x, T a) { - dual res = coef[0]; - if constexpr (N >= 2) { - dual y = x - a; - T denom = 1; // factorial - - res += y * coef[1]; - - for (size_t i = 2; i < N; ++i) { - y *= x - a; - denom *= T(i); - - res += y * coef[i] / denom; - } - } - - return res; -} - -template -dual abs(dual z) { - if (z.value() < 0) { - return dual_taylor_series({abs(z.value()), T(-1)}, z, z.value()); - } - - return dual_taylor_series({abs(z.value()), T(1)}, z, z.value()); -} - -template -dual abs(dual, Orders...> z) { - return dual_taylor_series({abs(z.value()), real(z.value()) / abs(z.value())}, z, z.value()); -} - -template -dual exp(const dual &x) { - static constexpr size_t MaxOrder = dual::max_order(); - - T coef[MaxOrder + 1] = {exp(x.value())}; - for (size_t i = 1; i <= MaxOrder; ++i) { - coef[i] = coef[0]; - } - - return dual_taylor_series(coef, x, x.value()); -} - -template -dual sqrt(const dual &z) { - static constexpr size_t MaxOrder = dual::max_order(); - - T coef[MaxOrder + 1] = {sqrt(z.value())}; - if constexpr (MaxOrder >= 1) { - coef[1] = T(1) / (T(2) * coef[0]); - - if constexpr (MaxOrder >= 2) { - coef[2] = -T(1) / (T(4) * coef[0] * z.value()); - } - } - - return dual_taylor_series(coef, z, z.value()); -} - -template -dual sin(const dual &x) { - static constexpr size_t MaxOrder = dual::max_order(); - - T coef[MaxOrder + 1] = {sin(x.value())}; - if constexpr (MaxOrder >= 1) { - coef[1] = cos(x.value()); - - for (size_t i = 2; i <= MaxOrder; ++i) { - coef[i] = -coef[i - 2]; - } - } - - return dual_taylor_series(coef, x, x.value()); -} - -template -dual cos(const dual &x) { - static constexpr size_t MaxOrder = dual::max_order(); - - T coef[MaxOrder + 1] = {cos(x.value())}; - if constexpr (MaxOrder >= 1) { - coef[1] = -sin(x.value()); - - for (size_t i = 2; i <= MaxOrder; ++i) { - coef[i] = -coef[i - 2]; - } - } - - return dual_taylor_series(coef, x, x.value()); -} - -template -dual copysign(const dual &x, const dual &y) { - if (signbit(x.value()) == signbit(y.value())) { - return x; - } - - return -x; -} - -template -bool isfinite(const dual &x) { - return isfinite(x.value()); -} - -template -bool isinf(const dual &x) { - return isinf(x.value()); -} - -template -bool isnan(const dual &x) { - return isnan(x.value()); -} - -template -dual real(dual, Order0, Orders...> z) { - dual res; - for (size_t i = 0; i <= Order0; ++i) { - res[i] = real(z[i]); - } - - return res; -} - -template -dual real(dual x) { - dual res; - for (size_t i = 0; i <= Order0; ++i) { - res[i] = real(x[i]); - } - - return res; -} - -template -dual imag(dual, Order0, Orders...> z) { - dual res; - for (size_t i = 0; i <= Order0; ++i) { - res[i] = imag(z[i]); - } - - return res; -} - -template -dual imag(dual x) { - dual res; - for (size_t i = 0; i <= Order0; ++i) { - res[i] = imag(x[i]); - } - - return res; -} - -template -struct complex_type> { - using type = dual::type, Orders...>; -}; - -template -struct remove_dual { - using type = T; -}; - -template -struct remove_dual> { - using type = T; -}; - -template -using remove_dual_t = typename remove_dual::type; - -namespace numbers { - - template - dual, Orders...> i_v> = i_v; - -} // namespace numbers -} // namespace xsf diff --git a/scipy/special/xsf/ellip.h b/scipy/special/xsf/ellip.h deleted file mode 100644 index 814f95d8b12e..000000000000 --- a/scipy/special/xsf/ellip.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include "cephes/ellie.h" -#include "cephes/ellik.h" -#include "cephes/ellpe.h" -#include "cephes/ellpj.h" -#include "cephes/ellpk.h" -#include "config.h" - -namespace xsf { - -inline double ellipe(double m) { return cephes::ellpe(m); } - -inline float ellipe(float m) { return ellipe(static_cast(m)); } - -inline double ellipeinc(double phi, double m) { return cephes::ellie(phi, m); } - -inline float ellipeinc(float phi, float m) { return ellipeinc(static_cast(phi), static_cast(m)); } - -inline void ellipj(double u, double m, double &sn, double &cn, double &dn, double &ph) { - cephes::ellpj(u, m, &sn, &cn, &dn, &ph); -} - -inline void ellipj(float u, float m, float &sn, float &cn, float &dn, float &ph) { - double sn_double; - double cn_double; - double dn_double; - double ph_double; - ellipj(static_cast(u), static_cast(m), sn_double, cn_double, dn_double, ph_double); - - sn = sn_double; - cn = cn_double; - dn = dn_double; - ph = ph_double; -} - -inline double ellipkinc(double phi, double m) { return cephes::ellik(phi, m); } - -inline float ellipkinc(float phi, float m) { return ellipkinc(static_cast(phi), static_cast(m)); } - -XSF_HOST_DEVICE inline double ellipk(double m) { return cephes::ellpk(1.0 - m); } - -XSF_HOST_DEVICE inline float ellipk(float m) { return ellipk(static_cast(m)); } - -inline double ellipkm1(double p) { return cephes::ellpk(p); } - -inline float ellipkm1(float p) { return ellipkm1(static_cast(p)); } - -} // namespace xsf diff --git a/scipy/special/xsf/erf.h b/scipy/special/xsf/erf.h deleted file mode 100644 index 0ec390dde3d6..000000000000 --- a/scipy/special/xsf/erf.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -#include "faddeeva.h" -#include "cephes/ndtr.h" -#include "config.h" - -namespace xsf { - -inline double erf(double x) { return cephes::erf(x); } - -inline float erf(float x) { return erf(static_cast(x)); } - -inline std::complex erf(std::complex z) { return Faddeeva::erf(z); } - -inline std::complex erf(std::complex x) { - return static_cast>(erf(static_cast>(x))); -} - -inline double erfc(double x) { return cephes::erfc(x); } - -inline float erfc(float x) { return erfc(static_cast(x)); } - -inline std::complex erfc(std::complex z) { return Faddeeva::erfc(z); } - -inline std::complex erfc(std::complex x) { - return static_cast>(erfc(static_cast>(x))); -} - -inline double erfcx(double x) { return Faddeeva::erfcx(x); } - -inline float erfcx(float x) { return erfcx(static_cast(x)); } - -inline std::complex erfcx(std::complex z) { return Faddeeva::erfcx(z); } - -inline std::complex erfcx(std::complex x) { - return static_cast>(erfcx(static_cast>(x))); -} - -inline double erfi(double x) { return Faddeeva::erfi(x); } - -inline float erfi(float x) { return erfi(static_cast(x)); } - -inline std::complex erfi(std::complex z) { return Faddeeva::erfi(z); } - -inline std::complex erfi(std::complex z) { - return static_cast>(erfi(static_cast>(z))); -} - -inline double voigt_profile(double x, double sigma, double gamma) { - const double INV_SQRT_2 = 0.707106781186547524401; - const double SQRT_2PI = 2.5066282746310002416123552393401042; - - if (sigma == 0) { - if (gamma == 0) { - if (std::isnan(x)) - return x; - if (x == 0) - return INFINITY; - return 0; - } - return gamma / M_PI / (x * x + gamma * gamma); - } - if (gamma == 0) { - return 1 / SQRT_2PI / sigma * exp(-(x / sigma) * (x / sigma) / 2); - } - - double zreal = x / sigma * INV_SQRT_2; - double zimag = gamma / sigma * INV_SQRT_2; - std::complex z(zreal, zimag); - std::complex w = Faddeeva::w(z); - return w.real() / sigma / SQRT_2PI; -} - -inline float voigt_profile(float x, float sigma, float gamma) { - return voigt_profile(static_cast(x), static_cast(sigma), static_cast(gamma)); -} - -inline std::complex wofz(std::complex z) { return Faddeeva::w(z); } - -inline std::complex wofz(std::complex x) { - return static_cast>(wofz(static_cast>(x))); -} - -inline double dawsn(double x) { return Faddeeva::Dawson(x); } - -inline float dawsn(float x) { return dawsn(static_cast(x)); } - -inline std::complex dawsn(std::complex z) { return Faddeeva::Dawson(z); } - -inline std::complex dawsn(std::complex x) { - return static_cast>(dawsn(static_cast>(x))); -} - -} // namespace xsf diff --git a/scipy/special/xsf/error.h b/scipy/special/xsf/error.h deleted file mode 100644 index 7221b5e6c405..000000000000 --- a/scipy/special/xsf/error.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -typedef enum { - SF_ERROR_OK = 0, /* no error */ - SF_ERROR_SINGULAR, /* singularity encountered */ - SF_ERROR_UNDERFLOW, /* floating point underflow */ - SF_ERROR_OVERFLOW, /* floating point overflow */ - SF_ERROR_SLOW, /* too many iterations required */ - SF_ERROR_LOSS, /* loss of precision */ - SF_ERROR_NO_RESULT, /* no result obtained */ - SF_ERROR_DOMAIN, /* out of domain */ - SF_ERROR_ARG, /* invalid input parameter */ - SF_ERROR_OTHER, /* unclassified error */ - SF_ERROR_MEMORY, /* memory allocation failed */ - SF_ERROR__LAST -} sf_error_t; - -#ifdef __cplusplus - -#include "config.h" - -namespace xsf { - -#ifndef SP_SPECFUN_ERROR -XSF_HOST_DEVICE inline void set_error(const char *func_name, sf_error_t code, const char *fmt, ...) { - // nothing -} -#else -void set_error(const char *func_name, sf_error_t code, const char *fmt, ...); -#endif - -template -XSF_HOST_DEVICE void set_error_and_nan(const char *name, sf_error_t code, T &value) { - if (code != SF_ERROR_OK) { - set_error(name, code, nullptr); - - if (code == SF_ERROR_DOMAIN || code == SF_ERROR_OVERFLOW || code == SF_ERROR_NO_RESULT) { - value = std::numeric_limits::quiet_NaN(); - } - } -} - -template -XSF_HOST_DEVICE void set_error_and_nan(const char *name, sf_error_t code, std::complex &value) { - if (code != SF_ERROR_OK) { - set_error(name, code, nullptr); - - if (code == SF_ERROR_DOMAIN || code == SF_ERROR_OVERFLOW || code == SF_ERROR_NO_RESULT) { - value.real(std::numeric_limits::quiet_NaN()); - value.imag(std::numeric_limits::quiet_NaN()); - } - } -} - -} // namespace xsf - -#endif diff --git a/scipy/special/xsf/evalpoly.h b/scipy/special/xsf/evalpoly.h deleted file mode 100644 index b126fb608fae..000000000000 --- a/scipy/special/xsf/evalpoly.h +++ /dev/null @@ -1,47 +0,0 @@ -/* Translated from Cython into C++ by SciPy developers in 2024. - * - * Original author: Josh Wilson, 2016. - */ - -/* Evaluate polynomials. - * - * All of the coefficients are stored in reverse order, i.e. if the - * polynomial is - * - * u_n x^n + u_{n - 1} x^{n - 1} + ... + u_0, - * - * then coeffs[0] = u_n, coeffs[1] = u_{n - 1}, ..., coeffs[n] = u_0. - * - * References - * ---------- - * [1] Knuth, "The Art of Computer Programming, Volume II" - */ - -#pragma once - -#include "config.h" - -namespace xsf { - -XSF_HOST_DEVICE inline std::complex cevalpoly(const double *coeffs, int degree, std::complex z) { - /* Evaluate a polynomial with real coefficients at a complex point. - * - * Uses equation (3) in section 4.6.4 of [1]. Note that it is more - * efficient than Horner's method. - */ - double a = coeffs[0]; - double b = coeffs[1]; - double r = 2 * z.real(); - double s = std::norm(z); - double tmp; - - for (int j = 2; j < degree + 1; j++) { - tmp = b; - b = std::fma(-s, a, coeffs[j]); - a = std::fma(r, a, tmp); - } - - return z * a + b; -} - -} // namespace xsf diff --git a/scipy/special/xsf/exp.h b/scipy/special/xsf/exp.h deleted file mode 100644 index 0d8551109084..000000000000 --- a/scipy/special/xsf/exp.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include "xsf/cephes/exp10.h" -#include "xsf/cephes/exp2.h" - -namespace xsf { - -inline double expm1(double x) { return cephes::expm1(x); } - -inline float expm1(float x) { return expm1(static_cast(x)); } - -// cexpm1(z) = cexp(z) - 1 -// -// The imaginary part of this is easily computed via exp(z.real)*sin(z.imag) -// The real part is difficult to compute when there is cancellation e.g. when -// z.real = -log(cos(z.imag)). There isn't a way around this problem that -// doesn't involve computing exp(z.real) and/or cos(z.imag) to higher -// precision. -inline std::complex expm1(std::complex z) { - if (!std::isfinite(std::real(z)) || !std::isfinite(std::imag(z))) { - return std::exp(z) - 1.0; - } - - double x; - double ezr = 0; - if (std::real(z) <= -40) { - x = -1.0; - } else { - ezr = expm1(std::real(z)); - x = ezr * std::cos(std::imag(z)) + cosm1(std::imag(z)); - } - - // don't compute exp(zr) too, unless necessary - double y; - if (std::real(z) > -1.0) { - y = (ezr + 1.0) * sin(std::imag(z)); - } else { - y = exp(std::real(z)) * sin(std::imag(z)); - } - - return std::complex{x, y}; -} - -inline std::complex expm1(std::complex z) { - return static_cast>(expm1(static_cast>(z))); -} - -double exp2(double x) { return cephes::exp2(x); } - -float exp2(float x) { return exp2(static_cast(x)); } - -double exp10(double x) { return cephes::exp10(x); } - -float exp10(float x) { return exp10(static_cast(x)); } - -} // namespace xsf diff --git a/scipy/special/xsf/expint.h b/scipy/special/xsf/expint.h deleted file mode 100644 index 600448aeb7f7..000000000000 --- a/scipy/special/xsf/expint.h +++ /dev/null @@ -1,266 +0,0 @@ -/* The functions exp1, expi below are based on translations of the Fortran code - * by Shanjie Zhang and Jianming Jin from the book - * - * Shanjie Zhang, Jianming Jin, - * Computation of Special Functions, - * Wiley, 1996, - * ISBN: 0-471-11963-6, - * LC: QA351.C45. - */ - -#pragma once - -#include "config.h" -#include "error.h" - -#include "cephes/const.h" - - -namespace xsf { - - -XSF_HOST_DEVICE inline double exp1(double x) { - // ============================================ - // Purpose: Compute exponential integral E1(x) - // Input : x --- Argument of E1(x) - // Output: E1 --- E1(x) ( x > 0 ) - // ============================================ - int k, m; - double e1, r, t, t0; - constexpr double ga = cephes::detail::SCIPY_EULER; - - if (x == 0.0) { - return std::numeric_limits::infinity(); - } - if (x <= 1.0) { - e1 = 1.0; - r = 1.0; - for (k = 1; k < 26; k++) { - r = -r*k*x/std::pow(k+1.0, 2); - e1 += r; - if (std::abs(r) <= std::abs(e1)*1e-15) { break; } - } - return -ga - std::log(x) + x*e1; - } - m = 20 + (int)(80.0/x); - t0 = 0.0; - for (k = m; k > 0; k--) { - t0 = k / (1.0 + k / (x+t0)); - } - t = 1.0 / (x + t0); - return std::exp(-x)*t; -} - -XSF_HOST_DEVICE inline float exp1(float x) { return exp1(static_cast(x)); } - -XSF_HOST_DEVICE inline std::complex exp1(std::complex z) { - // ==================================================== - // Purpose: Compute complex exponential integral E1(z) - // Input : z --- Argument of E1(z) - // Output: CE1 --- E1(z) - // ==================================================== - constexpr double el = cephes::detail::SCIPY_EULER; - int k; - std::complex ce1, cr, zc, zd, zdc; - double x = z.real(); - double a0 = std::abs(z); - // Continued fraction converges slowly near negative real axis, - // so use power series in a wedge around it until radius 40.0 - double xt = -2.0*std::abs(z.imag()); - - if (a0 == 0.0) { return std::numeric_limits::infinity(); } - if ((a0 < 5.0) || ((x < xt) && (a0 < 40.0))) { - // Power series - ce1 = 1.0; - cr = 1.0; - for (k = 1; k < 501; k++) { - cr = -cr*z*static_cast(k / std::pow(k + 1, 2)); - ce1 += cr; - if (std::abs(cr) < std::abs(ce1)*1e-15) { break; } - } - if ((x <= 0.0) && (z.imag() == 0.0)) { - //Careful on the branch cut -- use the sign of the imaginary part - // to get the right sign on the factor if pi. - ce1 = -el - std::log(-z) + z*ce1 - std::copysign(M_PI, z.imag())*std::complex(0.0, 1.0); - } else { - ce1 = -el - std::log(z) + z*ce1; - } - } else { - // Continued fraction https://dlmf.nist.gov/6.9 - // 1 1 1 2 2 3 3 - // E1 = exp(-z) * ----- ----- ----- ----- ----- ----- ----- ... - // Z + 1 + Z + 1 + Z + 1 + Z + - zc = 0.0; - zd = static_cast(1) / z; - zdc = zd; - zc += zdc; - for (k = 1; k < 501; k++) { - zd = static_cast(1) / (zd*static_cast(k) + static_cast(1)); - zdc *= (zd - static_cast(1)); - zc += zdc; - - zd = static_cast(1) / (zd*static_cast(k) + z); - zdc *= (z*zd - static_cast(1)); - zc += zdc; - if ((std::abs(zdc) <= std::abs(zc)*1e-15) && (k > 20)) { break; } - } - ce1 = std::exp(-z)*zc; - if ((x <= 0.0) && (z.imag() == 0.0)) { - ce1 -= M_PI*std::complex(0.0, 1.0); - } - } - return ce1; -} - -XSF_HOST_DEVICE inline std::complex exp1(std::complex z) { - return static_cast>(exp1(static_cast>(z))); -} - -XSF_HOST_DEVICE inline double expi(double x) { - // ============================================ - // Purpose: Compute exponential integral Ei(x) - // Input : x --- Argument of Ei(x) - // Output: EI --- Ei(x) - // ============================================ - - constexpr double ga = cephes::detail::SCIPY_EULER; - double ei, r; - - if (x == 0.0) { - ei = -std::numeric_limits::infinity(); - } else if (x < 0) { - ei = -exp1(-x); - } else if (std::abs(x) <= 40.0) { - // Power series around x=0 - ei = 1.0; - r = 1.0; - - for (int k = 1; k <= 100; k++) { - r = r * k * x / ((k + 1.0) * (k + 1.0)); - ei += r; - if (std::abs(r / ei) <= 1.0e-15) { break; } - } - ei = ga + std::log(x) + x * ei; - } else { - // Asymptotic expansion (the series is not convergent) - ei = 1.0; - r = 1.0; - for (int k = 1; k <= 20; k++) { - r = r * k / x; - ei += r; - } - ei = std::exp(x) / x * ei; - } - return ei; -} - -XSF_HOST_DEVICE inline float expi(float x) { return expi(static_cast(x)); } - -std::complex expi(std::complex z) { - // ============================================ - // Purpose: Compute exponential integral Ei(x) - // Input : x --- Complex argument of Ei(x) - // Output: EI --- Ei(x) - // ============================================ - - std::complex cei; - cei = - exp1(-z); - if (z.imag() > 0.0) { - cei += std::complex(0.0, M_PI); - } else if (z.imag() < 0.0 ) { - cei -= std::complex(0.0, M_PI); - } else { - if (z.real() > 0.0) { - cei += std::complex(0.0, copysign(M_PI, z.imag())); - } - } - return cei; -} - - -XSF_HOST_DEVICE inline std::complex expi(std::complex z) { - return static_cast>(expi(static_cast>(z))); -} - -namespace detail { - - // - // Compute a factor of the exponential integral E1. - // This is used in scaled_exp1(x) for moderate values of x. - // - // The function uses the continued fraction expansion given in equation 5.1.22 - // of Abramowitz & Stegun, "Handbook of Mathematical Functions". - // For n=1, this is - // - // E1(x) = exp(-x)*C(x) - // - // where C(x), expressed in the notation used in A&S, is the continued fraction - // - // 1 1 1 2 2 3 3 - // C(x) = --- --- --- --- --- --- --- ... - // x + 1 + x + 1 + x + 1 + x + - // - // Here, we pull a factor of 1/z out of C(x), so - // - // E1(x) = (exp(-x)/x)*F(x) - // - // and a bit of algebra gives the continued fraction expansion of F(x) to be - // - // 1 1 1 2 2 3 3 - // F(x) = --- --- --- --- --- --- --- ... - // 1 + x + 1 + x + 1 + x + 1 + - // - XSF_HOST_DEVICE inline double expint1_factor_cont_frac(double x) { - // The number of terms to use in the truncated continued fraction - // depends on x. Larger values of x require fewer terms. - int m = 20 + (int) (80.0 / x); - double t0 = 0.0; - for (int k = m; k > 0; --k) { - t0 = k / (x + k / (1 + t0)); - } - return 1 / (1 + t0); - } - -} // namespace detail - -// -// Scaled version of the exponential integral E_1(x). -// -// Factor E_1(x) as -// -// E_1(x) = exp(-x)/x * F(x) -// -// This function computes F(x). -// -// F(x) has the properties: -// * F(0) = 0 -// * F is increasing on [0, inf) -// * lim_{x->inf} F(x) = 1. -// -XSF_HOST_DEVICE inline double scaled_exp1(double x) { - if (x < 0) { - return std::numeric_limits::quiet_NaN(); - } - - if (x == 0) { - return 0.0; - } - - if (x <= 1) { - // For small x, the naive implementation is sufficiently accurate. - return x * std::exp(x) * exp1(x); - } - - if (x <= 1250) { - // For moderate x, use the continued fraction expansion. - return detail::expint1_factor_cont_frac(x); - } - - // For large x, use the asymptotic expansion. This is equation 5.1.51 - // from Abramowitz & Stegun, "Handbook of Mathematical Functions". - return 1 + (-1 + (2 + (-6 + (24 - 120 / x) / x) / x) / x) / x; -} - -XSF_HOST_DEVICE inline float scaled_exp1(float x) { return scaled_exp1(static_cast(x)); } - -} // namespace xsf diff --git a/scipy/special/xsf/faddeeva.h b/scipy/special/xsf/faddeeva.h deleted file mode 100644 index 78b51dc84de0..000000000000 --- a/scipy/special/xsf/faddeeva.h +++ /dev/null @@ -1,1758 +0,0 @@ -/* Copyright (c) 2012 Massachusetts Institute of Technology - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* Available at: http://ab-initio.mit.edu/Faddeeva - - Computes various error functions (erf, erfc, erfi, erfcx), - including the Dawson integral, in the complex plane, based - on algorithms for the computation of the Faddeeva function - w(z) = exp(-z^2) * erfc(-i*z). - Given w(z), the error functions are mostly straightforward - to compute, except for certain regions where we have to - switch to Taylor expansions to avoid cancellation errors - [e.g. near the origin for erf(z)]. - - To compute the Faddeeva function, we use a combination of two - algorithms: - - For sufficiently large |z|, we use a continued-fraction expansion - for w(z) similar to those described in: - - Walter Gautschi, "Efficient computation of the complex error - function," SIAM J. Numer. Anal. 7(1), pp. 187-198 (1970) - - G. P. M. Poppe and C. M. J. Wijers, "More efficient computation - of the complex error function," ACM Trans. Math. Soft. 16(1), - pp. 38-46 (1990). - - Unlike those papers, however, we switch to a completely different - algorithm for smaller |z|: - - Mofreh R. Zaghloul and Ahmed N. Ali, "Algorithm 916: Computing the - Faddeyeva and Voigt Functions," ACM Trans. Math. Soft. 38(2), 15 - (2011). - - (I initially used this algorithm for all z, but it turned out to be - significantly slower than the continued-fraction expansion for - larger |z|. On the other hand, it is competitive for smaller |z|, - and is significantly more accurate than the Poppe & Wijers code - in some regions, e.g. in the vicinity of z=1+1i.) - - Note that this is an INDEPENDENT RE-IMPLEMENTATION of these algorithms, - based on the description in the papers ONLY. In particular, I did - not refer to the authors' Fortran or Matlab implementations, respectively, - (which are under restrictive ACM copyright terms and therefore unusable - in free/open-source software). - - Steven G. Johnson, Massachusetts Institute of Technology - http://math.mit.edu/~stevenj - October 2012. - - -- Note that Algorithm 916 assumes that the erfc(x) function, - or rather the scaled function erfcx(x) = exp(x*x)*erfc(x), - is supplied for REAL arguments x. I originally used an - erfcx routine derived from DERFC in SLATEC, but I have - since replaced it with a much faster routine written by - me which uses a combination of continued-fraction expansions - and a lookup table of Chebyshev polynomials. For speed, - I implemented a similar algorithm for Im[w(x)] of real x, - since this comes up frequently in the other error functions. - - A small test program is included the end, which checks - the w(z) etc. results against several known values. To compile - the test function, compile with -DTEST_FADDEEVA (that is, - #define TEST_FADDEEVA). - - REVISION HISTORY: - 4 October 2012: Initial public release (SGJ) - 5 October 2012: Revised (SGJ) to fix spelling error, - start summation for large x at round(x/a) (> 1) - rather than ceil(x/a) as in the original - paper, which should slightly improve performance - (and, apparently, slightly improves accuracy) - 19 October 2012: Revised (SGJ) to fix bugs for large x, large -y, - and 15 1e154. - Set relerr argument to min(relerr,0.1). - 27 October 2012: Enhance accuracy in Re[w(z)] taken by itself, - by switching to Alg. 916 in a region near - the real-z axis where continued fractions - have poor relative accuracy in Re[w(z)]. Thanks - to M. Zaghloul for the tip. - 29 October 2012: Replace SLATEC-derived erfcx routine with - completely rewritten code by me, using a very - different algorithm which is much faster. - 30 October 2012: Implemented special-case code for real z - (where real part is exp(-x^2) and imag part is - Dawson integral), using algorithm similar to erfx. - Export ImFaddeeva_w function to make Dawson's - integral directly accessible. - 3 November 2012: Provide implementations of erf, erfc, erfcx, - and Dawson functions in Faddeeva:: namespace, - in addition to Faddeeva::w. Provide header - file Faddeeva.hh. -*/ - -#pragma once - -#include -#include - -namespace Faddeeva { - -// compute w(z) = exp(-z^2) erfc(-iz) [ Faddeeva / scaled complex error func ] -std::complex w(std::complex z,double relerr=0); -double w_im(double x); // special-case code for Im[w(x)] of real x - -// Various functions that we can compute with the help of w(z) - -// compute erfcx(z) = exp(z^2) erfz(z) -std::complex erfcx(std::complex z, double relerr=0); -double erfcx(double x); // special case for real x - -// compute erf(z), the error function of complex arguments -std::complex erf(std::complex z, double relerr=0); -double erf(double x); // special case for real x - -// compute erfi(z) = -i erf(iz), the imaginary error function -std::complex erfi(std::complex z, double relerr=0); -double erfi(double x); // special case for real x - -// compute erfc(z) = 1 - erf(z), the complementary error function -std::complex erfc(std::complex z, double relerr=0); -double erfc(double x); // special case for real x - -// compute Dawson(z) = sqrt(pi)/2 * exp(-z^2) * erfi(z) -std::complex Dawson(std::complex z, double relerr=0); -double Dawson(double x); // special case for real x - -// compute erfcx(z) = exp(z^2) erfz(z) -std::complex erfcx(std::complex z, double relerr) -{ - return w(std::complex(-imag(z), real(z))); -} - -// compute the error function erf(x) -double erf(double x) -{ - double mx2 = -x*x; - if (mx2 < -750) // underflow - return (x >= 0 ? 1.0 : -1.0); - - if (x >= 0) { - if (x < 5e-3) goto taylor; - return 1.0 - exp(mx2) * erfcx(x); - } - else { // x < 0 - if (x > -5e-3) goto taylor; - return exp(mx2) * erfcx(-x) - 1.0; - } - - // Use Taylor series for small |x|, to avoid cancellation inaccuracy - // erf(x) = 2/sqrt(pi) * x * (1 - x^2/3 + x^4/10 - ...) - taylor: - return x * (1.1283791670955125739 - + mx2 * (0.37612638903183752464 - + mx2 * 0.11283791670955125739)); -} - - -// compute the error function erf(z) -std::complex erf(std::complex z, double relerr) -{ - double x = real(z), y = imag(z); - - if (x == 0) // handle separately for speed & handling of y = Inf or NaN - return std::complex(x, // preserve sign of 0 - /* handle y -> Inf limit manually, since - exp(y^2) -> Inf but Im[w(y)] -> 0, so - IEEE will give us a NaN when it should be Inf */ - y*y > 720 ? (y > 0 ? std::numeric_limits::infinity() : -std::numeric_limits::infinity()) - : exp(y*y) * w_im(y)); - - double mRe_z2 = (y - x) * (x + y); // Re(-z^2), being careful of overflow - double mIm_z2 = -2*x*y; // Im(-z^2) - if (mRe_z2 < -750) // underflow - return (x >= 0 ? 1.0 : -1.0); - - /* Handle positive and negative x via different formulas, - using the mirror symmetries of w, to avoid overflow/underflow - problems from multiplying exponentially large and small quantities. */ - if (x >= 0) { - if (x < 5e-3) { - if (fabs(y) < 5e-3) - goto taylor; - else if (fabs(mIm_z2) < 5e-3) - goto taylor_erfi; - } - /* don't use complex exp function, since that will produce spurious NaN - values when multiplying w in an overflow situation. */ - return 1.0 - exp(mRe_z2) * - (std::complex(cos(mIm_z2), sin(mIm_z2)) - * w(std::complex(-y,x))); - } - else { // x < 0 - if (x > -5e-3) { // duplicate from above to avoid fabs(x) call - if (fabs(y) < 5e-3) - goto taylor; - else if (fabs(mIm_z2) < 5e-3) - goto taylor_erfi; - } - else if (std::isnan(x)) - return std::complex(std::numeric_limits::quiet_NaN(), y == 0 ? 0 : std::numeric_limits::quiet_NaN()); - /* don't use complex exp function, since that will produce spurious NaN - values when multiplying w in an overflow situation. */ - return exp(mRe_z2) * - (std::complex(cos(mIm_z2), sin(mIm_z2)) - * w(std::complex(y,-x))) - 1.0; - } - - // Use Taylor series for small |z|, to avoid cancellation inaccuracy - // erf(z) = 2/sqrt(pi) * z * (1 - z^2/3 + z^4/10 - ...) - taylor: - { - std::complex mz2(mRe_z2, mIm_z2); // -z^2 - return z * (1.1283791670955125739 - + mz2 * (0.37612638903183752464 - + mz2 * 0.11283791670955125739)); - } - - /* for small |x| and small |xy|, - use Taylor series to avoid cancellation inaccuracy: - erf(x+iy) = erf(iy) - + 2*exp(y^2)/sqrt(pi) * - [ x * (1 - x^2 * (1+2y^2)/3 + x^4 * (3+12y^2+4y^4)/30 + ... - - i * x^2 * y * (1 - x^2 * (3+2y^2)/6 + ...) ] - where: - erf(iy) = exp(y^2) * Im[w(y)] - */ - taylor_erfi: - { - double x2 = x*x, y2 = y*y; - double expy2 = exp(y2); - return std::complex - (expy2 * x * (1.1283791670955125739 - - x2 * (0.37612638903183752464 - + 0.75225277806367504925*y2) - + x2*x2 * (0.11283791670955125739 - + y2 * (0.45135166683820502956 - + 0.15045055561273500986*y2))), - expy2 * (w_im(y) - - x2*y * (1.1283791670955125739 - - x2 * (0.56418958354775628695 - + 0.37612638903183752464*y2)))); - } -} - - -// erfi(z) = -i erf(iz) -std::complex erfi(std::complex z, double relerr) -{ - std::complex e = erf(std::complex(-imag(z),real(z)), relerr); - return std::complex(imag(e), -real(e)); -} - -// erfi(x) = -i erf(ix) -double erfi(double x) -{ - return x*x > 720 ? (x > 0 ? std::numeric_limits::infinity() : -std::numeric_limits::infinity()) - : exp(x*x) * w_im(x); -} - -// erfc(x) = 1 - erf(x) -double erfc(double x) -{ - if (x*x > 750) // underflow - return (x >= 0 ? 0.0 : 2.0); - return x >= 0 ? exp(-x*x) * erfcx(x) - : 2. - exp(-x*x) * erfcx(-x); -} - -// erfc(z) = 1 - erf(z) -std::complex erfc(std::complex z, double relerr) -{ - double x = real(z), y = imag(z); - - if (x == 0.) - return std::complex(1, - /* handle y -> Inf limit manually, since - exp(y^2) -> Inf but Im[w(y)] -> 0, so - IEEE will give us a NaN when it should be Inf */ - y*y > 720 ? (y > 0 ? -std::numeric_limits::infinity() : std::numeric_limits::infinity()) - : -exp(y*y) * w_im(y)); - if (y == 0.) { - if (x*x > 750) // underflow - return (x >= 0 ? 0.0 : 2.0); - return x >= 0 ? exp(-x*x) * erfcx(x) - : 2. - exp(-x*x) * erfcx(-x); - } - - double mRe_z2 = (y - x) * (x + y); // Re(-z^2), being careful of overflow - double mIm_z2 = -2*x*y; // Im(-z^2) - if (mRe_z2 < -750) // underflow - return (x >= 0 ? 0.0 : 2.0); - - if (x >= 0) - return exp(std::complex(mRe_z2, mIm_z2)) - * w(std::complex(-y,x), relerr); - else - return 2.0 - exp(std::complex(mRe_z2, mIm_z2)) - * w(std::complex(y,-x), relerr); -} - -// compute Dawson(x) = sqrt(pi)/2 * exp(-x^2) * erfi(x) -double Dawson(double x) -{ - const double spi2 = 0.8862269254527580136490837416705725913990; // sqrt(pi)/2 - return spi2 * w_im(x); -} - -// compute Dawson(z) = sqrt(pi)/2 * exp(-z^2) * erfi(z) -std::complex Dawson(std::complex z, double relerr) -{ - const double spi2 = 0.8862269254527580136490837416705725913990; // sqrt(pi)/2 - double x = real(z), y = imag(z); - - // handle axes separately for speed & proper handling of x or y = Inf or NaN - if (y == 0) - return std::complex(spi2 * w_im(x), - -y); // preserve sign of 0 - if (x == 0) { - double y2 = y*y; - if (y2 < 2.5e-5) { // Taylor expansion - return std::complex(x, // preserve sign of 0 - y * (1. - + y2 * (0.6666666666666666666666666666666666666667 - + y2 * 0.2666666666666666666666666666666666666667))); - } - return std::complex(x, // preserve sign of 0 - spi2 * (y >= 0 - ? exp(y2) - erfcx(y) - : erfcx(-y) - exp(y2))); - } - - double mRe_z2 = (y - x) * (x + y); // Re(-z^2), being careful of overflow - double mIm_z2 = -2*x*y; // Im(-z^2) - std::complex mz2(mRe_z2, mIm_z2); // -z^2 - - /* Handle positive and negative x via different formulas, - using the mirror symmetries of w, to avoid overflow/underflow - problems from multiplying exponentially large and small quantities. */ - if (y >= 0) { - if (y < 5e-3) { - if (fabs(x) < 5e-3) - goto taylor; - else if (fabs(mIm_z2) < 5e-3) - goto taylor_realaxis; - } - std::complex res = exp(mz2) - w(z); - return spi2 * std::complex(-imag(res), real(res)); - } - else { // y < 0 - if (y > -5e-3) { // duplicate from above to avoid fabs(x) call - if (fabs(x) < 5e-3) - goto taylor; - else if (fabs(mIm_z2) < 5e-3) - goto taylor_realaxis; - } - else if (std::isnan(y)) - return std::complex(x == 0 ? 0 : std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()); - std::complex res = w(-z) - exp(mz2); - return spi2 * std::complex(-imag(res), real(res)); - } - - // Use Taylor series for small |z|, to avoid cancellation inaccuracy - // dawson(z) = z - 2/3 z^3 + 4/15 z^5 + ... - taylor: - return z * (1. - + mz2 * (0.6666666666666666666666666666666666666667 - + mz2 * 0.2666666666666666666666666666666666666667)); - - /* for small |y| and small |xy|, - use Taylor series to avoid cancellation inaccuracy: - dawson(x + iy) - = D + y^2 (D + x - 2Dx^2) - + y^4 (D/2 + 5x/6 - 2Dx^2 - x^3/3 + 2Dx^4/3) - + iy [ (1-2Dx) + 2/3 y^2 (1 - 3Dx - x^2 + 2Dx^3) - + y^4/15 (4 - 15Dx - 9x^2 + 20Dx^3 + 2x^4 - 4Dx^5) ] + ... - where D = dawson(x) - - However, for large |x|, 2Dx -> 1 which gives cancellation problems in - this series (many of the leading terms cancel). So, for large |x|, - we need to substitute a continued-fraction expansion for D. - - dawson(x) = 0.5 / (x-0.5/(x-1/(x-1.5/(x-2/(x-2.5/(x...)))))) - - The 6 terms shown here seems to be the minimum needed to be - accurate as soon as the simpler Taylor expansion above starts - breaking down. Using this 6-term expansion, factoring out the - denominator, and simplifying with Maple, we obtain: - - Re dawson(x + iy) * (-15 + 90x^2 - 60x^4 + 8x^6) / x - = 33 - 28x^2 + 4x^4 + y^2 (18 - 4x^2) + 4 y^4 - Im dawson(x + iy) * (-15 + 90x^2 - 60x^4 + 8x^6) / y - = -15 + 24x^2 - 4x^4 + 2/3 y^2 (6x^2 - 15) - 4 y^4 - - Finally, for |x| > 5e7, we can use a simpler 1-term continued-fraction - expansion for the real part, and a 2-term expansion for the imaginary - part. (This avoids overflow problems for huge |x|.) This yields: - - Re dawson(x + iy) = [1 + y^2 (1 + y^2/2 - (xy)^2/3)] / (2x) - Im dawson(x + iy) = y [ -1 - 2/3 y^2 + y^4/15 (2x^2 - 4) ] / (2x^2 - 1) - - */ - taylor_realaxis: - { - double x2 = x*x; - if (x2 > 1600) { // |x| > 40 - double y2 = y*y; - if (x2 > 25e14) {// |x| > 5e7 - double xy2 = (x*y)*(x*y); - return std::complex((0.5 + y2 * (0.5 + 0.25*y2 - - 0.16666666666666666667*xy2)) / x, - y * (-1 + y2 * (-0.66666666666666666667 - + 0.13333333333333333333*xy2 - - 0.26666666666666666667*y2)) - / (2*x2 - 1)); - } - return (1. / (-15 + x2*(90 + x2*(-60 + 8*x2)))) * - std::complex(x * (33 + x2 * (-28 + 4*x2) - + y2 * (18 - 4*x2 + 4*y2)), - y * (-15 + x2 * (24 - 4*x2) - + y2 * (4*x2 - 10 - 4*y2))); - } - else { - double D = spi2 * w_im(x); - double x2 = x*x, y2 = y*y; - return std::complex - (D + y2 * (D + x - 2*D*x2) - + y2*y2 * (D * (0.5 - x2 * (2 - 0.66666666666666666667*x2)) - + x * (0.83333333333333333333 - - 0.33333333333333333333 * x2)), - y * (1 - 2*D*x - + y2 * 0.66666666666666666667 * (1 - x2 - D*x * (3 - 2*x2)) - + y2*y2 * (0.26666666666666666667 - - x2 * (0.6 - 0.13333333333333333333 * x2) - - D*x * (1 - x2 * (1.3333333333333333333 - - 0.26666666666666666667 * x2))))); - } - } -} - -// return sinc(x) = sin(x)/x, given both x and sin(x) -// [since we only use this in cases where sin(x) has already been computed] -inline double sinc(double x, double sinx) { - return fabs(x) < 1e-4 ? 1 - (0.1666666666666666666667)*x*x : sinx / x; -} - -// sinh(x) via Taylor series, accurate to machine precision for |x| < 1e-2 -inline double sinh_taylor(double x) { - return x * (1 + (x*x) * (0.1666666666666666666667 - + 0.00833333333333333333333 * (x*x))); -} - -inline double sqr(double x) { return x*x; } - -///////////////////////////////////////////////////////////////////////// - -// precomputed table of expa2n2[n-1] = exp(-a2*n*n) -// for double-precision a2 = 0.26865... in Faddeeva::w, below. -static const double expa2n2[] = { - 7.64405281671221563e-01, - 3.41424527166548425e-01, - 8.91072646929412548e-02, - 1.35887299055460086e-02, - 1.21085455253437481e-03, - 6.30452613933449404e-05, - 1.91805156577114683e-06, - 3.40969447714832381e-08, - 3.54175089099469393e-10, - 2.14965079583260682e-12, - 7.62368911833724354e-15, - 1.57982797110681093e-17, - 1.91294189103582677e-20, - 1.35344656764205340e-23, - 5.59535712428588720e-27, - 1.35164257972401769e-30, - 1.90784582843501167e-34, - 1.57351920291442930e-38, - 7.58312432328032845e-43, - 2.13536275438697082e-47, - 3.51352063787195769e-52, - 3.37800830266396920e-57, - 1.89769439468301000e-62, - 6.22929926072668851e-68, - 1.19481172006938722e-73, - 1.33908181133005953e-79, - 8.76924303483223939e-86, - 3.35555576166254986e-92, - 7.50264110688173024e-99, - 9.80192200745410268e-106, - 7.48265412822268959e-113, - 3.33770122566809425e-120, - 8.69934598159861140e-128, - 1.32486951484088852e-135, - 1.17898144201315253e-143, - 6.13039120236180012e-152, - 1.86258785950822098e-160, - 3.30668408201432783e-169, - 3.43017280887946235e-178, - 2.07915397775808219e-187, - 7.36384545323984966e-197, - 1.52394760394085741e-206, - 1.84281935046532100e-216, - 1.30209553802992923e-226, - 5.37588903521080531e-237, - 1.29689584599763145e-247, - 1.82813078022866562e-258, - 1.50576355348684241e-269, - 7.24692320799294194e-281, - 2.03797051314726829e-292, - 3.34880215927873807e-304, - 0.0 // underflow (also prevents reads past array end, below) -}; - - - -///////////////////////////////////////////////////////////////////////// - -std::complex w(std::complex z, double relerr) -{ - if (real(z) == 0.0) - return std::complex(erfcx(imag(z)), - real(z)); // give correct sign of 0 in imag(w) - else if (imag(z) == 0) - return std::complex(exp(-sqr(real(z))), - w_im(real(z))); - - double a, a2, c; - if (relerr <= DBL_EPSILON) { - relerr = DBL_EPSILON; - a = 0.518321480430085929872; // pi / sqrt(-log(eps*0.5)) - c = 0.329973702884629072537; // (2/pi) * a; - a2 = 0.268657157075235951582; // a^2 - } - else { - const double pi = 3.14159265358979323846264338327950288419716939937510582; - if (relerr > 0.1) relerr = 0.1; // not sensible to compute < 1 digit - a = pi / sqrt(-log(relerr*0.5)); - c = (2/pi)*a; - a2 = a*a; - } - const double x = fabs(real(z)); - const double y = imag(z), ya = fabs(y); - - std::complex ret(0.,0.); // return value - - double sum1 = 0, sum2 = 0, sum3 = 0, sum4 = 0, sum5 = 0; - -#define USE_CONTINUED_FRACTION 1 // 1 to use continued fraction for large |z| - -#if USE_CONTINUED_FRACTION - if (ya > 7 || (x > 6 // continued fraction is faster - /* As pointed out by M. Zaghloul, the continued - fraction seems to give a large relative error in - Re w(z) for |x| ~ 6 and small |y|, so use - algorithm 816 in this region: */ - && (ya > 0.1 || (x > 8 && ya > 1e-10) || x > 28))) { - - /* Poppe & Wijers suggest using a number of terms - nu = 3 + 1442 / (26*rho + 77) - where rho = sqrt((x/x0)^2 + (y/y0)^2) where x0=6.3, y0=4.4. - (They only use this expansion for rho >= 1, but rho a little less - than 1 seems okay too.) - Instead, I did my own fit to a slightly different function - that avoids the hypotenuse calculation, using NLopt to minimize - the sum of the squares of the errors in nu with the constraint - that the estimated nu be >= minimum nu to attain machine precision. - I also separate the regions where nu == 2 and nu == 1. */ - const double ispi = 0.56418958354775628694807945156; // 1 / sqrt(pi) - double xs = y < 0 ? -real(z) : real(z); // compute for -z if y < 0 - if (x + ya > 4000) { // nu <= 2 - if (x + ya > 1e7) { // nu == 1, w(z) = i/sqrt(pi) / z - // scale to avoid overflow - if (x > ya) { - double yax = ya / xs; - double denom = ispi / (xs + yax*ya); - ret = std::complex(denom*yax, denom); - } - else if (std::isinf(ya)) - return ((std::isnan(x) || y < 0) - ? std::complex(std::numeric_limits::quiet_NaN(),std::numeric_limits::quiet_NaN()) : std::complex(0,0)); - else { - double xya = xs / ya; - double denom = ispi / (xya*xs + ya); - ret = std::complex(denom, denom*xya); - } - } - else { // nu == 2, w(z) = i/sqrt(pi) * z / (z*z - 0.5) - double dr = xs*xs - ya*ya - 0.5, di = 2*xs*ya; - double denom = ispi / (dr*dr + di*di); - ret = std::complex(denom * (xs*di-ya*dr), denom * (xs*dr+ya*di)); - } - } - else { // compute nu(z) estimate and do general continued fraction - const double c0=3.9, c1=11.398, c2=0.08254, c3=0.1421, c4=0.2023; // fit - double nu = floor(c0 + c1 / (c2*x + c3*ya + c4)); - double wr = xs, wi = ya; - for (nu = 0.5 * (nu - 1); nu > 0.4; nu -= 0.5) { - // w <- z - nu/w: - double denom = nu / (wr*wr + wi*wi); - wr = xs - wr * denom; - wi = ya + wi * denom; - } - { // w(z) = i/sqrt(pi) / w: - double denom = ispi / (wr*wr + wi*wi); - ret = std::complex(denom*wi, denom*wr); - } - } - if (y < 0) { - // use w(z) = 2.0*exp(-z*z) - w(-z), - // but be careful of overflow in exp(-z*z) - // = exp(-(xs*xs-ya*ya) -2*i*xs*ya) - return 2.0*exp(std::complex((ya-xs)*(xs+ya), 2*xs*y)) - ret; - } - else - return ret; - } -#else // !USE_CONTINUED_FRACTION - if (x + ya > 1e7) { // w(z) = i/sqrt(pi) / z, to machine precision - const double ispi = 0.56418958354775628694807945156; // 1 / sqrt(pi) - double xs = y < 0 ? -real(z) : real(z); // compute for -z if y < 0 - // scale to avoid overflow - if (x > ya) { - double yax = ya / xs; - double denom = ispi / (xs + yax*ya); - ret = std::complex(denom*yax, denom); - } - else { - double xya = xs / ya; - double denom = ispi / (xya*xs + ya); - ret = std::complex(denom, denom*xya); - } - if (y < 0) { - // use w(z) = 2.0*exp(-z*z) - w(-z), - // but be careful of overflow in exp(-z*z) - // = exp(-(xs*xs-ya*ya) -2*i*xs*ya) - return 2.0*exp(std::complex((ya-xs)*(xs+ya), 2*xs*y)) - ret; - } - else - return ret; - } -#endif // !USE_CONTINUED_FRACTION - - /* Note: The test that seems to be suggested in the paper is x < - sqrt(-log(DBL_MIN)), about 26.6, since otherwise exp(-x^2) - underflows to zero and sum1,sum2,sum4 are zero. However, long - before this occurs, the sum1,sum2,sum4 contributions are - negligible in double precision; I find that this happens for x > - about 6, for all y. On the other hand, I find that the case - where we compute all of the sums is faster (at least with the - precomputed expa2n2 table) until about x=10. Furthermore, if we - try to compute all of the sums for x > 20, I find that we - sometimes run into numerical problems because underflow/overflow - problems start to appear in the various coefficients of the sums, - below. Therefore, we use x < 10 here. */ - else if (x < 10) { - double prod2ax = 1, prodm2ax = 1; - double expx2; - - if (std::isnan(y)) - return std::complex(y,y); - - /* Somewhat ugly copy-and-paste duplication here, but I see significant - speedups from using the special-case code with the precomputed - exponential, and the x < 5e-4 special case is needed for accuracy. */ - - if (relerr == DBL_EPSILON) { // use precomputed exp(-a2*(n*n)) table - if (x < 5e-4) { // compute sum4 and sum5 together as sum5-sum4 - const double x2 = x*x; - expx2 = 1 - x2 * (1 - 0.5*x2); // exp(-x*x) via Taylor - // compute exp(2*a*x) and exp(-2*a*x) via Taylor, to double precision - const double ax2 = 1.036642960860171859744*x; // 2*a*x - const double exp2ax = - 1 + ax2 * (1 + ax2 * (0.5 + 0.166666666666666666667*ax2)); - const double expm2ax = - 1 - ax2 * (1 - ax2 * (0.5 - 0.166666666666666666667*ax2)); - for (int n = 1; 1; ++n) { - const double coef = expa2n2[n-1] * expx2 / (a2*(n*n) + y*y); - prod2ax *= exp2ax; - prodm2ax *= expm2ax; - sum1 += coef; - sum2 += coef * prodm2ax; - sum3 += coef * prod2ax; - - // really = sum5 - sum4 - sum5 += coef * (2*a) * n * sinh_taylor((2*a)*n*x); - - // test convergence via sum3 - if (coef * prod2ax < relerr * sum3) break; - } - } - else { // x > 5e-4, compute sum4 and sum5 separately - expx2 = exp(-x*x); - const double exp2ax = exp((2*a)*x), expm2ax = 1 / exp2ax; - for (int n = 1; 1; ++n) { - const double coef = expa2n2[n-1] * expx2 / (a2*(n*n) + y*y); - prod2ax *= exp2ax; - prodm2ax *= expm2ax; - sum1 += coef; - sum2 += coef * prodm2ax; - sum4 += (coef * prodm2ax) * (a*n); - sum3 += coef * prod2ax; - sum5 += (coef * prod2ax) * (a*n); - // test convergence via sum5, since this sum has the slowest decay - if ((coef * prod2ax) * (a*n) < relerr * sum5) break; - } - } - } - else { // relerr != DBL_EPSILON, compute exp(-a2*(n*n)) on the fly - const double exp2ax = exp((2*a)*x), expm2ax = 1 / exp2ax; - if (x < 5e-4) { // compute sum4 and sum5 together as sum5-sum4 - const double x2 = x*x; - expx2 = 1 - x2 * (1 - 0.5*x2); // exp(-x*x) via Taylor - for (int n = 1; 1; ++n) { - const double coef = exp(-a2*(n*n)) * expx2 / (a2*(n*n) + y*y); - prod2ax *= exp2ax; - prodm2ax *= expm2ax; - sum1 += coef; - sum2 += coef * prodm2ax; - sum3 += coef * prod2ax; - - // really = sum5 - sum4 - sum5 += coef * (2*a) * n * sinh_taylor((2*a)*n*x); - - // test convergence via sum3 - if (coef * prod2ax < relerr * sum3) break; - } - } - else { // x > 5e-4, compute sum4 and sum5 separately - expx2 = exp(-x*x); - for (int n = 1; 1; ++n) { - const double coef = exp(-a2*(n*n)) * expx2 / (a2*(n*n) + y*y); - prod2ax *= exp2ax; - prodm2ax *= expm2ax; - sum1 += coef; - sum2 += coef * prodm2ax; - sum4 += (coef * prodm2ax) * (a*n); - sum3 += coef * prod2ax; - sum5 += (coef * prod2ax) * (a*n); - // test convergence via sum5, since this sum has the slowest decay - if ((coef * prod2ax) * (a*n) < relerr * sum5) break; - } - } - } - const double expx2erfcxy = // avoid spurious overflow for large negative y - y > -6 // for y < -6, erfcx(y) = 2*exp(y*y) to double precision - ? expx2*erfcx(y) : 2*exp(y*y-x*x); - if (y > 5) { // imaginary terms cancel - const double sinxy = sin(x*y); - ret = (expx2erfcxy - c*y*sum1) * cos(2*x*y) - + (c*x*expx2) * sinxy * sinc(x*y, sinxy); - } - else { - double xs = real(z); - const double sinxy = sin(xs*y); - const double sin2xy = sin(2*xs*y), cos2xy = cos(2*xs*y); - const double coef1 = expx2erfcxy - c*y*sum1; - const double coef2 = c*xs*expx2; - ret = std::complex(coef1 * cos2xy + coef2 * sinxy * sinc(xs*y, sinxy), - coef2 * sinc(2*xs*y, sin2xy) - coef1 * sin2xy); - } - } - else { // x large: only sum3 & sum5 contribute (see above note) - if (std::isnan(x)) - return std::complex(x,x); - if (std::isnan(y)) - return std::complex(y,y); - -#if USE_CONTINUED_FRACTION - ret = exp(-x*x); // |y| < 1e-10, so we only need exp(-x*x) term -#else - if (y < 0) { - /* erfcx(y) ~ 2*exp(y*y) + (< 1) if y < 0, so - erfcx(y)*exp(-x*x) ~ 2*exp(y*y-x*x) term may not be negligible - if y*y - x*x > -36 or so. So, compute this term just in case. - We also need the -exp(-x*x) term to compute Re[w] accurately - in the case where y is very small. */ - ret = polar(2*exp(y*y-x*x) - exp(-x*x), -2*real(z)*y); - } - else - ret = exp(-x*x); // not negligible in real part if y very small -#endif - // (round instead of ceil as in original paper; note that x/a > 1 here) - double n0 = floor(x/a + 0.5); // sum in both directions, starting at n0 - double dx = a*n0 - x; - sum3 = exp(-dx*dx) / (a2*(n0*n0) + y*y); - sum5 = a*n0 * sum3; - double exp1 = exp(4*a*dx), exp1dn = 1; - int dn; - for (dn = 1; n0 - dn > 0; ++dn) { // loop over n0-dn and n0+dn terms - double np = n0 + dn, nm = n0 - dn; - double tp = exp(-sqr(a*dn+dx)); - double tm = tp * (exp1dn *= exp1); // trick to get tm from tp - tp /= (a2*(np*np) + y*y); - tm /= (a2*(nm*nm) + y*y); - sum3 += tp + tm; - sum5 += a * (np * tp + nm * tm); - if (a * (np * tp + nm * tm) < relerr * sum5) goto finish; - } - while (1) { // loop over n0+dn terms only (since n0-dn <= 0) - double np = n0 + dn++; - double tp = exp(-sqr(a*dn+dx)) / (a2*(np*np) + y*y); - sum3 += tp; - sum5 += a * np * tp; - if (a * np * tp < relerr * sum5) goto finish; - } - } - finish: - return ret + std::complex((0.5*c)*y*(sum2+sum3), - (0.5*c)*std::copysign(sum5-sum4, real(z))); -} - - -///////////////////////////////////////////////////////////////////////// - -/* erfcx(x) = exp(x^2) erfc(x) function, for real x, written by - Steven G. Johnson, October 2012. - - This function combines a few different ideas. - - First, for x > 50, it uses a continued-fraction expansion (same as - for the Faddeeva function, but with algebraic simplifications for z=i*x). - - Second, for 0 <= x <= 50, it uses Chebyshev polynomial approximations, - but with two twists: - - a) It maps x to y = 4 / (4+x) in [0,1]. This simple transformation, - inspired by a similar transformation in the octave-forge/specfun - erfcx by Soren Hauberg, results in much faster Chebyshev convergence - than other simple transformations I have examined. - - b) Instead of using a single Chebyshev polynomial for the entire - [0,1] y interval, we break the interval up into 100 equal - subintervals, with a switch/lookup table, and use much lower - degree Chebyshev polynomials in each subinterval. This greatly - improves performance in my tests. - - For x < 0, we use the relationship erfcx(-x) = 2 exp(x^2) - erfc(x), - with the usual checks for overflow etcetera. - - Performance-wise, it seems to be substantially faster than either - the SLATEC DERFC function [or an erfcx function derived therefrom] - or Cody's CALERF function (from netlib.org/specfun), while - retaining near machine precision in accuracy. */ - -/* Given y100=100*y, where y = 4/(4+x) for x >= 0, compute erfc(x). - - Uses a look-up table of 100 different Chebyshev polynomials - for y intervals [0,0.01], [0.01,0.02], ...., [0.99,1], generated - with the help of Maple and a little shell script. This allows - the Chebyshev polynomials to be of significantly lower degree (about 1/4) - compared to fitting the whole [0,1] interval with a single polynomial. */ -double erfcx_y100(double y100) -{ - switch ((int) y100) { -case 0: { -double t = 2*y100 - 1; -return 0.70878032454106438663e-3 + (0.71234091047026302958e-3 + (0.35779077297597742384e-5 + (0.17403143962587937815e-7 + (0.81710660047307788845e-10 + (0.36885022360434957634e-12 + 0.15917038551111111111e-14 * t) * t) * t) * t) * t) * t; -} -case 1: { -double t = 2*y100 - 3; -return 0.21479143208285144230e-2 + (0.72686402367379996033e-3 + (0.36843175430938995552e-5 + (0.18071841272149201685e-7 + (0.85496449296040325555e-10 + (0.38852037518534291510e-12 + 0.16868473576888888889e-14 * t) * t) * t) * t) * t) * t; -} -case 2: { -double t = 2*y100 - 5; -return 0.36165255935630175090e-2 + (0.74182092323555510862e-3 + (0.37948319957528242260e-5 + (0.18771627021793087350e-7 + (0.89484715122415089123e-10 + (0.40935858517772440862e-12 + 0.17872061464888888889e-14 * t) * t) * t) * t) * t) * t; -} -case 3: { -double t = 2*y100 - 7; -return 0.51154983860031979264e-2 + (0.75722840734791660540e-3 + (0.39096425726735703941e-5 + (0.19504168704300468210e-7 + (0.93687503063178993915e-10 + (0.43143925959079664747e-12 + 0.18939926435555555556e-14 * t) * t) * t) * t) * t) * t; -} -case 4: { -double t = 2*y100 - 9; -return 0.66457513172673049824e-2 + (0.77310406054447454920e-3 + (0.40289510589399439385e-5 + (0.20271233238288381092e-7 + (0.98117631321709100264e-10 + (0.45484207406017752971e-12 + 0.20076352213333333333e-14 * t) * t) * t) * t) * t) * t; -} -case 5: { -double t = 2*y100 - 11; -return 0.82082389970241207883e-2 + (0.78946629611881710721e-3 + (0.41529701552622656574e-5 + (0.21074693344544655714e-7 + (0.10278874108587317989e-9 + (0.47965201390613339638e-12 + 0.21285907413333333333e-14 * t) * t) * t) * t) * t) * t; -} -case 6: { -double t = 2*y100 - 13; -return 0.98039537275352193165e-2 + (0.80633440108342840956e-3 + (0.42819241329736982942e-5 + (0.21916534346907168612e-7 + (0.10771535136565470914e-9 + (0.50595972623692822410e-12 + 0.22573462684444444444e-14 * t) * t) * t) * t) * t) * t; -} -case 7: { -double t = 2*y100 - 15; -return 0.11433927298290302370e-1 + (0.82372858383196561209e-3 + (0.44160495311765438816e-5 + (0.22798861426211986056e-7 + (0.11291291745879239736e-9 + (0.53386189365816880454e-12 + 0.23944209546666666667e-14 * t) * t) * t) * t) * t) * t; -} -case 8: { -double t = 2*y100 - 17; -return 0.13099232878814653979e-1 + (0.84167002467906968214e-3 + (0.45555958988457506002e-5 + (0.23723907357214175198e-7 + (0.11839789326602695603e-9 + (0.56346163067550237877e-12 + 0.25403679644444444444e-14 * t) * t) * t) * t) * t) * t; -} -case 9: { -double t = 2*y100 - 19; -return 0.14800987015587535621e-1 + (0.86018092946345943214e-3 + (0.47008265848816866105e-5 + (0.24694040760197315333e-7 + (0.12418779768752299093e-9 + (0.59486890370320261949e-12 + 0.26957764568888888889e-14 * t) * t) * t) * t) * t) * t; -} -case 10: { -double t = 2*y100 - 21; -return 0.16540351739394069380e-1 + (0.87928458641241463952e-3 + (0.48520195793001753903e-5 + (0.25711774900881709176e-7 + (0.13030128534230822419e-9 + (0.62820097586874779402e-12 + 0.28612737351111111111e-14 * t) * t) * t) * t) * t) * t; -} -case 11: { -double t = 2*y100 - 23; -return 0.18318536789842392647e-1 + (0.89900542647891721692e-3 + (0.50094684089553365810e-5 + (0.26779777074218070482e-7 + (0.13675822186304615566e-9 + (0.66358287745352705725e-12 + 0.30375273884444444444e-14 * t) * t) * t) * t) * t) * t; -} -case 12: { -double t = 2*y100 - 25; -return 0.20136801964214276775e-1 + (0.91936908737673676012e-3 + (0.51734830914104276820e-5 + (0.27900878609710432673e-7 + (0.14357976402809042257e-9 + (0.70114790311043728387e-12 + 0.32252476000000000000e-14 * t) * t) * t) * t) * t) * t; -} -case 13: { -double t = 2*y100 - 27; -return 0.21996459598282740954e-1 + (0.94040248155366777784e-3 + (0.53443911508041164739e-5 + (0.29078085538049374673e-7 + (0.15078844500329731137e-9 + (0.74103813647499204269e-12 + 0.34251892320000000000e-14 * t) * t) * t) * t) * t) * t; -} -case 14: { -double t = 2*y100 - 29; -return 0.23898877187226319502e-1 + (0.96213386835900177540e-3 + (0.55225386998049012752e-5 + (0.30314589961047687059e-7 + (0.15840826497296335264e-9 + (0.78340500472414454395e-12 + 0.36381553564444444445e-14 * t) * t) * t) * t) * t) * t; -} -case 15: { -double t = 2*y100 - 31; -return 0.25845480155298518485e-1 + (0.98459293067820123389e-3 + (0.57082915920051843672e-5 + (0.31613782169164830118e-7 + (0.16646478745529630813e-9 + (0.82840985928785407942e-12 + 0.38649975768888888890e-14 * t) * t) * t) * t) * t) * t; -} -case 16: { -double t = 2*y100 - 33; -return 0.27837754783474696598e-1 + (0.10078108563256892757e-2 + (0.59020366493792212221e-5 + (0.32979263553246520417e-7 + (0.17498524159268458073e-9 + (0.87622459124842525110e-12 + 0.41066206488888888890e-14 * t) * t) * t) * t) * t) * t; -} -case 17: { -double t = 2*y100 - 35; -return 0.29877251304899307550e-1 + (0.10318204245057349310e-2 + (0.61041829697162055093e-5 + (0.34414860359542720579e-7 + (0.18399863072934089607e-9 + (0.92703227366365046533e-12 + 0.43639844053333333334e-14 * t) * t) * t) * t) * t) * t; -} -case 18: { -double t = 2*y100 - 37; -return 0.31965587178596443475e-1 + (0.10566560976716574401e-2 + (0.63151633192414586770e-5 + (0.35924638339521924242e-7 + (0.19353584758781174038e-9 + (0.98102783859889264382e-12 + 0.46381060817777777779e-14 * t) * t) * t) * t) * t) * t; -} -case 19: { -double t = 2*y100 - 39; -return 0.34104450552588334840e-1 + (0.10823541191350532574e-2 + (0.65354356159553934436e-5 + (0.37512918348533521149e-7 + (0.20362979635817883229e-9 + (0.10384187833037282363e-11 + 0.49300625262222222221e-14 * t) * t) * t) * t) * t) * t; -} -case 20: { -double t = 2*y100 - 41; -return 0.36295603928292425716e-1 + (0.11089526167995268200e-2 + (0.67654845095518363577e-5 + (0.39184292949913591646e-7 + (0.21431552202133775150e-9 + (0.10994259106646731797e-11 + 0.52409949102222222221e-14 * t) * t) * t) * t) * t) * t; -} -case 21: { -double t = 2*y100 - 43; -return 0.38540888038840509795e-1 + (0.11364917134175420009e-2 + (0.70058230641246312003e-5 + (0.40943644083718586939e-7 + (0.22563034723692881631e-9 + (0.11642841011361992885e-11 + 0.55721092871111111110e-14 * t) * t) * t) * t) * t) * t; -} -case 22: { -double t = 2*y100 - 45; -return 0.40842225954785960651e-1 + (0.11650136437945673891e-2 + (0.72569945502343006619e-5 + (0.42796161861855042273e-7 + (0.23761401711005024162e-9 + (0.12332431172381557035e-11 + 0.59246802364444444445e-14 * t) * t) * t) * t) * t) * t; -} -case 23: { -double t = 2*y100 - 47; -return 0.43201627431540222422e-1 + (0.11945628793917272199e-2 + (0.75195743532849206263e-5 + (0.44747364553960993492e-7 + (0.25030885216472953674e-9 + (0.13065684400300476484e-11 + 0.63000532853333333334e-14 * t) * t) * t) * t) * t) * t; -} -case 24: { -double t = 2*y100 - 49; -return 0.45621193513810471438e-1 + (0.12251862608067529503e-2 + (0.77941720055551920319e-5 + (0.46803119830954460212e-7 + (0.26375990983978426273e-9 + (0.13845421370977119765e-11 + 0.66996477404444444445e-14 * t) * t) * t) * t) * t) * t; -} -case 25: { -double t = 2*y100 - 51; -return 0.48103121413299865517e-1 + (0.12569331386432195113e-2 + (0.80814333496367673980e-5 + (0.48969667335682018324e-7 + (0.27801515481905748484e-9 + (0.14674637611609884208e-11 + 0.71249589351111111110e-14 * t) * t) * t) * t) * t) * t; -} -case 26: { -double t = 2*y100 - 53; -return 0.50649709676983338501e-1 + (0.12898555233099055810e-2 + (0.83820428414568799654e-5 + (0.51253642652551838659e-7 + (0.29312563849675507232e-9 + (0.15556512782814827846e-11 + 0.75775607822222222221e-14 * t) * t) * t) * t) * t) * t; -} -case 27: { -double t = 2*y100 - 55; -return 0.53263363664388864181e-1 + (0.13240082443256975769e-2 + (0.86967260015007658418e-5 + (0.53662102750396795566e-7 + (0.30914568786634796807e-9 + (0.16494420240828493176e-11 + 0.80591079644444444445e-14 * t) * t) * t) * t) * t) * t; -} -case 28: { -double t = 2*y100 - 57; -return 0.55946601353500013794e-1 + (0.13594491197408190706e-2 + (0.90262520233016380987e-5 + (0.56202552975056695376e-7 + (0.32613310410503135996e-9 + (0.17491936862246367398e-11 + 0.85713381688888888890e-14 * t) * t) * t) * t) * t) * t; -} -case 29: { -double t = 2*y100 - 59; -return 0.58702059496154081813e-1 + (0.13962391363223647892e-2 + (0.93714365487312784270e-5 + (0.58882975670265286526e-7 + (0.34414937110591753387e-9 + (0.18552853109751857859e-11 + 0.91160736711111111110e-14 * t) * t) * t) * t) * t) * t; -} -case 30: { -double t = 2*y100 - 61; -return 0.61532500145144778048e-1 + (0.14344426411912015247e-2 + (0.97331446201016809696e-5 + (0.61711860507347175097e-7 + (0.36325987418295300221e-9 + (0.19681183310134518232e-11 + 0.96952238400000000000e-14 * t) * t) * t) * t) * t) * t; -} -case 31: { -double t = 2*y100 - 63; -return 0.64440817576653297993e-1 + (0.14741275456383131151e-2 + (0.10112293819576437838e-4 + (0.64698236605933246196e-7 + (0.38353412915303665586e-9 + (0.20881176114385120186e-11 + 0.10310784480000000000e-13 * t) * t) * t) * t) * t) * t; -} -case 32: { -double t = 2*y100 - 65; -return 0.67430045633130393282e-1 + (0.15153655418916540370e-2 + (0.10509857606888328667e-4 + (0.67851706529363332855e-7 + (0.40504602194811140006e-9 + (0.22157325110542534469e-11 + 0.10964842115555555556e-13 * t) * t) * t) * t) * t) * t; -} -case 33: { -double t = 2*y100 - 67; -return 0.70503365513338850709e-1 + (0.15582323336495709827e-2 + (0.10926868866865231089e-4 + (0.71182482239613507542e-7 + (0.42787405890153386710e-9 + (0.23514379522274416437e-11 + 0.11659571751111111111e-13 * t) * t) * t) * t) * t) * t; -} -case 34: { -double t = 2*y100 - 69; -return 0.73664114037944596353e-1 + (0.16028078812438820413e-2 + (0.11364423678778207991e-4 + (0.74701423097423182009e-7 + (0.45210162777476488324e-9 + (0.24957355004088569134e-11 + 0.12397238257777777778e-13 * t) * t) * t) * t) * t) * t; -} -case 35: { -double t = 2*y100 - 71; -return 0.76915792420819562379e-1 + (0.16491766623447889354e-2 + (0.11823685320041302169e-4 + (0.78420075993781544386e-7 + (0.47781726956916478925e-9 + (0.26491544403815724749e-11 + 0.13180196462222222222e-13 * t) * t) * t) * t) * t) * t; -} -case 36: { -double t = 2*y100 - 73; -return 0.80262075578094612819e-1 + (0.16974279491709504117e-2 + (0.12305888517309891674e-4 + (0.82350717698979042290e-7 + (0.50511496109857113929e-9 + (0.28122528497626897696e-11 + 0.14010889635555555556e-13 * t) * t) * t) * t) * t) * t; -} -case 37: { -double t = 2*y100 - 75; -return 0.83706822008980357446e-1 + (0.17476561032212656962e-2 + (0.12812343958540763368e-4 + (0.86506399515036435592e-7 + (0.53409440823869467453e-9 + (0.29856186620887555043e-11 + 0.14891851591111111111e-13 * t) * t) * t) * t) * t) * t; -} -case 38: { -double t = 2*y100 - 77; -return 0.87254084284461718231e-1 + (0.17999608886001962327e-2 + (0.13344443080089492218e-4 + (0.90900994316429008631e-7 + (0.56486134972616465316e-9 + (0.31698707080033956934e-11 + 0.15825697795555555556e-13 * t) * t) * t) * t) * t) * t; -} -case 39: { -double t = 2*y100 - 79; -return 0.90908120182172748487e-1 + (0.18544478050657699758e-2 + (0.13903663143426120077e-4 + (0.95549246062549906177e-7 + (0.59752787125242054315e-9 + (0.33656597366099099413e-11 + 0.16815130613333333333e-13 * t) * t) * t) * t) * t) * t; -} -case 40: { -double t = 2*y100 - 81; -return 0.94673404508075481121e-1 + (0.19112284419887303347e-2 + (0.14491572616545004930e-4 + (0.10046682186333613697e-6 + (0.63221272959791000515e-9 + (0.35736693975589130818e-11 + 0.17862931591111111111e-13 * t) * t) * t) * t) * t) * t; -} -case 41: { -double t = 2*y100 - 83; -return 0.98554641648004456555e-1 + (0.19704208544725622126e-2 + (0.15109836875625443935e-4 + (0.10567036667675984067e-6 + (0.66904168640019354565e-9 + (0.37946171850824333014e-11 + 0.18971959040000000000e-13 * t) * t) * t) * t) * t) * t; -} -case 42: { -double t = 2*y100 - 85; -return 0.10255677889470089531e0 + (0.20321499629472857418e-2 + (0.15760224242962179564e-4 + (0.11117756071353507391e-6 + (0.70814785110097658502e-9 + (0.40292553276632563925e-11 + 0.20145143075555555556e-13 * t) * t) * t) * t) * t) * t; -} -case 43: { -double t = 2*y100 - 87; -return 0.10668502059865093318e0 + (0.20965479776148731610e-2 + (0.16444612377624983565e-4 + (0.11700717962026152749e-6 + (0.74967203250938418991e-9 + (0.42783716186085922176e-11 + 0.21385479360000000000e-13 * t) * t) * t) * t) * t) * t; -} -case 44: { -double t = 2*y100 - 89; -return 0.11094484319386444474e0 + (0.21637548491908170841e-2 + (0.17164995035719657111e-4 + (0.12317915750735938089e-6 + (0.79376309831499633734e-9 + (0.45427901763106353914e-11 + 0.22696025653333333333e-13 * t) * t) * t) * t) * t) * t; -} -case 45: { -double t = 2*y100 - 91; -return 0.11534201115268804714e0 + (0.22339187474546420375e-2 + (0.17923489217504226813e-4 + (0.12971465288245997681e-6 + (0.84057834180389073587e-9 + (0.48233721206418027227e-11 + 0.24079890062222222222e-13 * t) * t) * t) * t) * t) * t; -} -case 46: { -double t = 2*y100 - 93; -return 0.11988259392684094740e0 + (0.23071965691918689601e-2 + (0.18722342718958935446e-4 + (0.13663611754337957520e-6 + (0.89028385488493287005e-9 + (0.51210161569225846701e-11 + 0.25540227111111111111e-13 * t) * t) * t) * t) * t) * t; -} -case 47: { -double t = 2*y100 - 95; -return 0.12457298393509812907e0 + (0.23837544771809575380e-2 + (0.19563942105711612475e-4 + (0.14396736847739470782e-6 + (0.94305490646459247016e-9 + (0.54366590583134218096e-11 + 0.27080225920000000000e-13 * t) * t) * t) * t) * t) * t; -} -case 48: { -double t = 2*y100 - 97; -return 0.12941991566142438816e0 + (0.24637684719508859484e-2 + (0.20450821127475879816e-4 + (0.15173366280523906622e-6 + (0.99907632506389027739e-9 + (0.57712760311351625221e-11 + 0.28703099555555555556e-13 * t) * t) * t) * t) * t) * t; -} -case 49: { -double t = 2*y100 - 99; -return 0.13443048593088696613e0 + (0.25474249981080823877e-2 + (0.21385669591362915223e-4 + (0.15996177579900443030e-6 + (0.10585428844575134013e-8 + (0.61258809536787882989e-11 + 0.30412080142222222222e-13 * t) * t) * t) * t) * t) * t; -} -case 50: { -double t = 2*y100 - 101; -return 0.13961217543434561353e0 + (0.26349215871051761416e-2 + (0.22371342712572567744e-4 + (0.16868008199296822247e-6 + (0.11216596910444996246e-8 + (0.65015264753090890662e-11 + 0.32210394506666666666e-13 * t) * t) * t) * t) * t) * t; -} -case 51: { -double t = 2*y100 - 103; -return 0.14497287157673800690e0 + (0.27264675383982439814e-2 + (0.23410870961050950197e-4 + (0.17791863939526376477e-6 + (0.11886425714330958106e-8 + (0.68993039665054288034e-11 + 0.34101266222222222221e-13 * t) * t) * t) * t) * t) * t; -} -case 52: { -double t = 2*y100 - 105; -return 0.15052089272774618151e0 + (0.28222846410136238008e-2 + (0.24507470422713397006e-4 + (0.18770927679626136909e-6 + (0.12597184587583370712e-8 + (0.73203433049229821618e-11 + 0.36087889048888888890e-13 * t) * t) * t) * t) * t) * t; -} -case 53: { -double t = 2*y100 - 107; -return 0.15626501395774612325e0 + (0.29226079376196624949e-2 + (0.25664553693768450545e-4 + (0.19808568415654461964e-6 + (0.13351257759815557897e-8 + (0.77658124891046760667e-11 + 0.38173420035555555555e-13 * t) * t) * t) * t) * t) * t; -} -case 54: { -double t = 2*y100 - 109; -return 0.16221449434620737567e0 + (0.30276865332726475672e-2 + (0.26885741326534564336e-4 + (0.20908350604346384143e-6 + (0.14151148144240728728e-8 + (0.82369170665974313027e-11 + 0.40360957457777777779e-13 * t) * t) * t) * t) * t) * t; -} -case 55: { -double t = 2*y100 - 111; -return 0.16837910595412130659e0 + (0.31377844510793082301e-2 + (0.28174873844911175026e-4 + (0.22074043807045782387e-6 + (0.14999481055996090039e-8 + (0.87348993661930809254e-11 + 0.42653528977777777779e-13 * t) * t) * t) * t) * t) * t; -} -case 56: { -double t = 2*y100 - 113; -return 0.17476916455659369953e0 + (0.32531815370903068316e-2 + (0.29536024347344364074e-4 + (0.23309632627767074202e-6 + (0.15899007843582444846e-8 + (0.92610375235427359475e-11 + 0.45054073102222222221e-13 * t) * t) * t) * t) * t) * t; -} -case 57: { -double t = 2*y100 - 115; -return 0.18139556223643701364e0 + (0.33741744168096996041e-2 + (0.30973511714709500836e-4 + (0.24619326937592290996e-6 + (0.16852609412267750744e-8 + (0.98166442942854895573e-11 + 0.47565418097777777779e-13 * t) * t) * t) * t) * t) * t; -} -case 58: { -double t = 2*y100 - 117; -return 0.18826980194443664549e0 + (0.35010775057740317997e-2 + (0.32491914440014267480e-4 + (0.26007572375886319028e-6 + (0.17863299617388376116e-8 + (0.10403065638343878679e-10 + 0.50190265831111111110e-13 * t) * t) * t) * t) * t) * t; -} -case 59: { -double t = 2*y100 - 119; -return 0.19540403413693967350e0 + (0.36342240767211326315e-2 + (0.34096085096200907289e-4 + (0.27479061117017637474e-6 + (0.18934228504790032826e-8 + (0.11021679075323598664e-10 + 0.52931171733333333334e-13 * t) * t) * t) * t) * t) * t; -} -case 60: { -double t = 2*y100 - 121; -return 0.20281109560651886959e0 + (0.37739673859323597060e-2 + (0.35791165457592409054e-4 + (0.29038742889416172404e-6 + (0.20068685374849001770e-8 + (0.11673891799578381999e-10 + 0.55790523093333333334e-13 * t) * t) * t) * t) * t) * t; -} -case 61: { -double t = 2*y100 - 123; -return 0.21050455062669334978e0 + (0.39206818613925652425e-2 + (0.37582602289680101704e-4 + (0.30691836231886877385e-6 + (0.21270101645763677824e-8 + (0.12361138551062899455e-10 + 0.58770520160000000000e-13 * t) * t) * t) * t) * t) * t; -} -case 62: { -double t = 2*y100 - 125; -return 0.21849873453703332479e0 + (0.40747643554689586041e-2 + (0.39476163820986711501e-4 + (0.32443839970139918836e-6 + (0.22542053491518680200e-8 + (0.13084879235290858490e-10 + 0.61873153262222222221e-13 * t) * t) * t) * t) * t) * t; -} -case 63: { -double t = 2*y100 - 127; -return 0.22680879990043229327e0 + (0.42366354648628516935e-2 + (0.41477956909656896779e-4 + (0.34300544894502810002e-6 + (0.23888264229264067658e-8 + (0.13846596292818514601e-10 + 0.65100183751111111110e-13 * t) * t) * t) * t) * t) * t; -} -case 64: { -double t = 2*y100 - 129; -return 0.23545076536988703937e0 + (0.44067409206365170888e-2 + (0.43594444916224700881e-4 + (0.36268045617760415178e-6 + (0.25312606430853202748e-8 + (0.14647791812837903061e-10 + 0.68453122631111111110e-13 * t) * t) * t) * t) * t) * t; -} -case 65: { -double t = 2*y100 - 131; -return 0.24444156740777432838e0 + (0.45855530511605787178e-2 + (0.45832466292683085475e-4 + (0.38352752590033030472e-6 + (0.26819103733055603460e-8 + (0.15489984390884756993e-10 + 0.71933206364444444445e-13 * t) * t) * t) * t) * t) * t; -} -case 66: { -double t = 2*y100 - 133; -return 0.25379911500634264643e0 + (0.47735723208650032167e-2 + (0.48199253896534185372e-4 + (0.40561404245564732314e-6 + (0.28411932320871165585e-8 + (0.16374705736458320149e-10 + 0.75541379822222222221e-13 * t) * t) * t) * t) * t) * t; -} -case 67: { -double t = 2*y100 - 135; -return 0.26354234756393613032e0 + (0.49713289477083781266e-2 + (0.50702455036930367504e-4 + (0.42901079254268185722e-6 + (0.30095422058900481753e-8 + (0.17303497025347342498e-10 + 0.79278273368888888890e-13 * t) * t) * t) * t) * t) * t; -} -case 68: { -double t = 2*y100 - 137; -return 0.27369129607732343398e0 + (0.51793846023052643767e-2 + (0.53350152258326602629e-4 + (0.45379208848865015485e-6 + (0.31874057245814381257e-8 + (0.18277905010245111046e-10 + 0.83144182364444444445e-13 * t) * t) * t) * t) * t) * t; -} -case 69: { -double t = 2*y100 - 139; -return 0.28426714781640316172e0 + (0.53983341916695141966e-2 + (0.56150884865255810638e-4 + (0.48003589196494734238e-6 + (0.33752476967570796349e-8 + (0.19299477888083469086e-10 + 0.87139049137777777779e-13 * t) * t) * t) * t) * t) * t; -} -case 70: { -double t = 2*y100 - 141; -return 0.29529231465348519920e0 + (0.56288077305420795663e-2 + (0.59113671189913307427e-4 + (0.50782393781744840482e-6 + (0.35735475025851713168e-8 + (0.20369760937017070382e-10 + 0.91262442613333333334e-13 * t) * t) * t) * t) * t) * t; -} -case 71: { -double t = 2*y100 - 143; -return 0.30679050522528838613e0 + (0.58714723032745403331e-2 + (0.62248031602197686791e-4 + (0.53724185766200945789e-6 + (0.37827999418960232678e-8 + (0.21490291930444538307e-10 + 0.95513539182222222221e-13 * t) * t) * t) * t) * t) * t; -} -case 72: { -double t = 2*y100 - 145; -return 0.31878680111173319425e0 + (0.61270341192339103514e-2 + (0.65564012259707640976e-4 + (0.56837930287837738996e-6 + (0.40035151353392378882e-8 + (0.22662596341239294792e-10 + 0.99891109760000000000e-13 * t) * t) * t) * t) * t) * t; -} -case 73: { -double t = 2*y100 - 147; -return 0.33130773722152622027e0 + (0.63962406646798080903e-2 + (0.69072209592942396666e-4 + (0.60133006661885941812e-6 + (0.42362183765883466691e-8 + (0.23888182347073698382e-10 + 0.10439349811555555556e-12 * t) * t) * t) * t) * t) * t; -} -case 74: { -double t = 2*y100 - 149; -return 0.34438138658041336523e0 + (0.66798829540414007258e-2 + (0.72783795518603561144e-4 + (0.63619220443228800680e-6 + (0.44814499336514453364e-8 + (0.25168535651285475274e-10 + 0.10901861383111111111e-12 * t) * t) * t) * t) * t) * t; -} -case 75: { -double t = 2*y100 - 151; -return 0.35803744972380175583e0 + (0.69787978834882685031e-2 + (0.76710543371454822497e-4 + (0.67306815308917386747e-6 + (0.47397647975845228205e-8 + (0.26505114141143050509e-10 + 0.11376390933333333333e-12 * t) * t) * t) * t) * t) * t; -} -case 76: { -double t = 2*y100 - 153; -return 0.37230734890119724188e0 + (0.72938706896461381003e-2 + (0.80864854542670714092e-4 + (0.71206484718062688779e-6 + (0.50117323769745883805e-8 + (0.27899342394100074165e-10 + 0.11862637614222222222e-12 * t) * t) * t) * t) * t) * t; -} -case 77: { -double t = 2*y100 - 155; -return 0.38722432730555448223e0 + (0.76260375162549802745e-2 + (0.85259785810004603848e-4 + (0.75329383305171327677e-6 + (0.52979361368388119355e-8 + (0.29352606054164086709e-10 + 0.12360253370666666667e-12 * t) * t) * t) * t) * t) * t; -} -case 78: { -double t = 2*y100 - 157; -return 0.40282355354616940667e0 + (0.79762880915029728079e-2 + (0.89909077342438246452e-4 + (0.79687137961956194579e-6 + (0.55989731807360403195e-8 + (0.30866246101464869050e-10 + 0.12868841946666666667e-12 * t) * t) * t) * t) * t) * t; -} -case 79: { -double t = 2*y100 - 159; -return 0.41914223158913787649e0 + (0.83456685186950463538e-2 + (0.94827181359250161335e-4 + (0.84291858561783141014e-6 + (0.59154537751083485684e-8 + (0.32441553034347469291e-10 + 0.13387957943111111111e-12 * t) * t) * t) * t) * t) * t; -} -case 80: { -double t = 2*y100 - 161; -return 0.43621971639463786896e0 + (0.87352841828289495773e-2 + (0.10002929142066799966e-3 + (0.89156148280219880024e-6 + (0.62480008150788597147e-8 + (0.34079760983458878910e-10 + 0.13917107176888888889e-12 * t) * t) * t) * t) * t) * t; -} -case 81: { -double t = 2*y100 - 163; -return 0.45409763548534330981e0 + (0.91463027755548240654e-2 + (0.10553137232446167258e-3 + (0.94293113464638623798e-6 + (0.65972492312219959885e-8 + (0.35782041795476563662e-10 + 0.14455745872000000000e-12 * t) * t) * t) * t) * t) * t; -} -case 82: { -double t = 2*y100 - 165; -return 0.47282001668512331468e0 + (0.95799574408860463394e-2 + (0.11135019058000067469e-3 + (0.99716373005509038080e-6 + (0.69638453369956970347e-8 + (0.37549499088161345850e-10 + 0.15003280712888888889e-12 * t) * t) * t) * t) * t) * t; -} -case 83: { -double t = 2*y100 - 167; -return 0.49243342227179841649e0 + (0.10037550043909497071e-1 + (0.11750334542845234952e-3 + (0.10544006716188967172e-5 + (0.73484461168242224872e-8 + (0.39383162326435752965e-10 + 0.15559069118222222222e-12 * t) * t) * t) * t) * t) * t; -} -case 84: { -double t = 2*y100 - 169; -return 0.51298708979209258326e0 + (0.10520454564612427224e-1 + (0.12400930037494996655e-3 + (0.11147886579371265246e-5 + (0.77517184550568711454e-8 + (0.41283980931872622611e-10 + 0.16122419680000000000e-12 * t) * t) * t) * t) * t) * t; -} -case 85: { -double t = 2*y100 - 171; -return 0.53453307979101369843e0 + (0.11030120618800726938e-1 + (0.13088741519572269581e-3 + (0.11784797595374515432e-5 + (0.81743383063044825400e-8 + (0.43252818449517081051e-10 + 0.16692592640000000000e-12 * t) * t) * t) * t) * t) * t; -} -case 86: { -double t = 2*y100 - 173; -return 0.55712643071169299478e0 + (0.11568077107929735233e-1 + (0.13815797838036651289e-3 + (0.12456314879260904558e-5 + (0.86169898078969313597e-8 + (0.45290446811539652525e-10 + 0.17268801084444444444e-12 * t) * t) * t) * t) * t) * t; -} -case 87: { -double t = 2*y100 - 175; -return 0.58082532122519320968e0 + (0.12135935999503877077e-1 + (0.14584223996665838559e-3 + (0.13164068573095710742e-5 + (0.90803643355106020163e-8 + (0.47397540713124619155e-10 + 0.17850211608888888889e-12 * t) * t) * t) * t) * t) * t; -} -case 88: { -double t = 2*y100 - 177; -return 0.60569124025293375554e0 + (0.12735396239525550361e-1 + (0.15396244472258863344e-3 + (0.13909744385382818253e-5 + (0.95651595032306228245e-8 + (0.49574672127669041550e-10 + 0.18435945564444444444e-12 * t) * t) * t) * t) * t) * t; -} -case 89: { -double t = 2*y100 - 179; -return 0.63178916494715716894e0 + (0.13368247798287030927e-1 + (0.16254186562762076141e-3 + (0.14695084048334056083e-5 + (0.10072078109604152350e-7 + (0.51822304995680707483e-10 + 0.19025081422222222222e-12 * t) * t) * t) * t) * t) * t; -} -case 90: { -double t = 2*y100 - 181; -return 0.65918774689725319200e0 + (0.14036375850601992063e-1 + (0.17160483760259706354e-3 + (0.15521885688723188371e-5 + (0.10601827031535280590e-7 + (0.54140790105837520499e-10 + 0.19616655146666666667e-12 * t) * t) * t) * t) * t) * t; -} -case 91: { -double t = 2*y100 - 183; -return 0.68795950683174433822e0 + (0.14741765091365869084e-1 + (0.18117679143520433835e-3 + (0.16392004108230585213e-5 + (0.11155116068018043001e-7 + (0.56530360194925690374e-10 + 0.20209663662222222222e-12 * t) * t) * t) * t) * t) * t; -} -case 92: { -double t = 2*y100 - 185; -return 0.71818103808729967036e0 + (0.15486504187117112279e-1 + (0.19128428784550923217e-3 + (0.17307350969359975848e-5 + (0.11732656736113607751e-7 + (0.58991125287563833603e-10 + 0.20803065333333333333e-12 * t) * t) * t) * t) * t) * t; -} -case 93: { -double t = 2*y100 - 187; -return 0.74993321911726254661e0 + (0.16272790364044783382e-1 + (0.20195505163377912645e-3 + (0.18269894883203346953e-5 + (0.12335161021630225535e-7 + (0.61523068312169087227e-10 + 0.21395783431111111111e-12 * t) * t) * t) * t) * t) * t; -} -case 94: { -double t = 2*y100 - 189; -return 0.78330143531283492729e0 + (0.17102934132652429240e-1 + (0.21321800585063327041e-3 + (0.19281661395543913713e-5 + (0.12963340087354341574e-7 + (0.64126040998066348872e-10 + 0.21986708942222222222e-12 * t) * t) * t) * t) * t) * t; -} -case 95: { -double t = 2*y100 - 191; -return 0.81837581041023811832e0 + (0.17979364149044223802e-1 + (0.22510330592753129006e-3 + (0.20344732868018175389e-5 + (0.13617902941839949718e-7 + (0.66799760083972474642e-10 + 0.22574701262222222222e-12 * t) * t) * t) * t) * t) * t; -} -case 96: { -double t = 2*y100 - 193; -return 0.85525144775685126237e0 + (0.18904632212547561026e-1 + (0.23764237370371255638e-3 + (0.21461248251306387979e-5 + (0.14299555071870523786e-7 + (0.69543803864694171934e-10 + 0.23158593688888888889e-12 * t) * t) * t) * t) * t) * t; -} -case 97: { -double t = 2*y100 - 195; -return 0.89402868170849933734e0 + (0.19881418399127202569e-1 + (0.25086793128395995798e-3 + (0.22633402747585233180e-5 + (0.15008997042116532283e-7 + (0.72357609075043941261e-10 + 0.23737194737777777778e-12 * t) * t) * t) * t) * t) * t; -} -case 98: { -double t = 2*y100 - 197; -return 0.93481333942870796363e0 + (0.20912536329780368893e-1 + (0.26481403465998477969e-3 + (0.23863447359754921676e-5 + (0.15746923065472184451e-7 + (0.75240468141720143653e-10 + 0.24309291271111111111e-12 * t) * t) * t) * t) * t) * t; -} -case 99: { -double t = 2*y100 - 199; -return 0.97771701335885035464e0 + (0.22000938572830479551e-1 + (0.27951610702682383001e-3 + (0.25153688325245314530e-5 + (0.16514019547822821453e-7 + (0.78191526829368231251e-10 + 0.24873652355555555556e-12 * t) * t) * t) * t) * t) * t; -} - } - // we only get here if y = 1, i.e. |x| < 4*eps, in which case - // erfcx is within 1e-15 of 1.. - return 1.0; -} - -double erfcx(double x) -{ - if (x >= 0) { - if (x > 50) { // continued-fraction expansion is faster - const double ispi = 0.56418958354775628694807945156; // 1 / sqrt(pi) - if (x > 5e7) // 1-term expansion, important to avoid overflow - return ispi / x; - /* 5-term expansion (rely on compiler for CSE), simplified from: - ispi / (x+0.5/(x+1/(x+1.5/(x+2/x)))) */ - return ispi*((x*x) * (x*x+4.5) + 2) / (x * ((x*x) * (x*x+5) + 3.75)); - } - return erfcx_y100(400/(4+x)); - } - else - return x < -26.7 ? HUGE_VAL : (x < -6.1 ? 2*exp(x*x) - : 2*exp(x*x) - erfcx_y100(400/(4-x))); -} - -///////////////////////////////////////////////////////////////////////// -/* Compute a scaled Dawson integral - Faddeeva::w_im(x) = 2*Dawson(x)/sqrt(pi) - equivalent to the imaginary part w(x) for real x. - - Uses methods similar to the erfcx calculation above: continued fractions - for large |x|, a lookup table of Chebyshev polynomials for smaller |x|, - and finally a Taylor expansion for |x|<0.01. - - Steven G. Johnson, October 2012. */ - -/* Given y100=100*y, where y = 1/(1+x) for x >= 0, compute w_im(x). - - Uses a look-up table of 100 different Chebyshev polynomials - for y intervals [0,0.01], [0.01,0.02], ...., [0.99,1], generated - with the help of Maple and a little shell script. This allows - the Chebyshev polynomials to be of significantly lower degree (about 1/30) - compared to fitting the whole [0,1] interval with a single polynomial. */ -double w_im_y100(double y100, double x) { - switch ((int) y100) { - case 0: { - double t = 2*y100 - 1; - return 0.28351593328822191546e-2 + (0.28494783221378400759e-2 + (0.14427470563276734183e-4 + (0.10939723080231588129e-6 + (0.92474307943275042045e-9 + (0.89128907666450075245e-11 + 0.92974121935111111110e-13 * t) * t) * t) * t) * t) * t; - } - case 1: { - double t = 2*y100 - 3; - return 0.85927161243940350562e-2 + (0.29085312941641339862e-2 + (0.15106783707725582090e-4 + (0.11716709978531327367e-6 + (0.10197387816021040024e-8 + (0.10122678863073360769e-10 + 0.10917479678400000000e-12 * t) * t) * t) * t) * t) * t; - } - case 2: { - double t = 2*y100 - 5; - return 0.14471159831187703054e-1 + (0.29703978970263836210e-2 + (0.15835096760173030976e-4 + (0.12574803383199211596e-6 + (0.11278672159518415848e-8 + (0.11547462300333495797e-10 + 0.12894535335111111111e-12 * t) * t) * t) * t) * t) * t; - } - case 3: { - double t = 2*y100 - 7; - return 0.20476320420324610618e-1 + (0.30352843012898665856e-2 + (0.16617609387003727409e-4 + (0.13525429711163116103e-6 + (0.12515095552507169013e-8 + (0.13235687543603382345e-10 + 0.15326595042666666667e-12 * t) * t) * t) * t) * t) * t; - } - case 4: { - double t = 2*y100 - 9; - return 0.26614461952489004566e-1 + (0.31034189276234947088e-2 + (0.17460268109986214274e-4 + (0.14582130824485709573e-6 + (0.13935959083809746345e-8 + (0.15249438072998932900e-10 + 0.18344741882133333333e-12 * t) * t) * t) * t) * t) * t; - } - case 5: { - double t = 2*y100 - 11; - return 0.32892330248093586215e-1 + (0.31750557067975068584e-2 + (0.18369907582308672632e-4 + (0.15761063702089457882e-6 + (0.15577638230480894382e-8 + (0.17663868462699097951e-10 + (0.22126732680711111111e-12 + 0.30273474177737853668e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 6: { - double t = 2*y100 - 13; - return 0.39317207681134336024e-1 + (0.32504779701937539333e-2 + (0.19354426046513400534e-4 + (0.17081646971321290539e-6 + (0.17485733959327106250e-8 + (0.20593687304921961410e-10 + (0.26917401949155555556e-12 + 0.38562123837725712270e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 7: { - double t = 2*y100 - 15; - return 0.45896976511367738235e-1 + (0.33300031273110976165e-2 + (0.20423005398039037313e-4 + (0.18567412470376467303e-6 + (0.19718038363586588213e-8 + (0.24175006536781219807e-10 + (0.33059982791466666666e-12 + 0.49756574284439426165e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 8: { - double t = 2*y100 - 17; - return 0.52640192524848962855e-1 + (0.34139883358846720806e-2 + (0.21586390240603337337e-4 + (0.20247136501568904646e-6 + (0.22348696948197102935e-8 + (0.28597516301950162548e-10 + (0.41045502119111111110e-12 + 0.65151614515238361946e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 9: { - double t = 2*y100 - 19; - return 0.59556171228656770456e-1 + (0.35028374386648914444e-2 + (0.22857246150998562824e-4 + (0.22156372146525190679e-6 + (0.25474171590893813583e-8 + (0.34122390890697400584e-10 + (0.51593189879111111110e-12 + 0.86775076853908006938e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 10: { - double t = 2*y100 - 21; - return 0.66655089485108212551e-1 + (0.35970095381271285568e-2 + (0.24250626164318672928e-4 + (0.24339561521785040536e-6 + (0.29221990406518411415e-8 + (0.41117013527967776467e-10 + (0.65786450716444444445e-12 + 0.11791885745450623331e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 11: { - double t = 2*y100 - 23; - return 0.73948106345519174661e-1 + (0.36970297216569341748e-2 + (0.25784588137312868792e-4 + (0.26853012002366752770e-6 + (0.33763958861206729592e-8 + (0.50111549981376976397e-10 + (0.85313857496888888890e-12 + 0.16417079927706899860e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 12: { - double t = 2*y100 - 25; - return 0.81447508065002963203e-1 + (0.38035026606492705117e-2 + (0.27481027572231851896e-4 + (0.29769200731832331364e-6 + (0.39336816287457655076e-8 + (0.61895471132038157624e-10 + (0.11292303213511111111e-11 + 0.23558532213703884304e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 13: { - double t = 2*y100 - 27; - return 0.89166884027582716628e-1 + (0.39171301322438946014e-2 + (0.29366827260422311668e-4 + (0.33183204390350724895e-6 + (0.46276006281647330524e-8 + (0.77692631378169813324e-10 + (0.15335153258844444444e-11 + 0.35183103415916026911e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 14: { - double t = 2*y100 - 29; - return 0.97121342888032322019e-1 + (0.40387340353207909514e-2 + (0.31475490395950776930e-4 + (0.37222714227125135042e-6 + (0.55074373178613809996e-8 + (0.99509175283990337944e-10 + (0.21552645758222222222e-11 + 0.55728651431872687605e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 15: { - double t = 2*y100 - 31; - return 0.10532778218603311137e0 + (0.41692873614065380607e-2 + (0.33849549774889456984e-4 + (0.42064596193692630143e-6 + (0.66494579697622432987e-8 + (0.13094103581931802337e-9 + (0.31896187409777777778e-11 + 0.97271974184476560742e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 16: { - double t = 2*y100 - 33; - return 0.11380523107427108222e0 + (0.43099572287871821013e-2 + (0.36544324341565929930e-4 + (0.47965044028581857764e-6 + (0.81819034238463698796e-8 + (0.17934133239549647357e-9 + (0.50956666166186293627e-11 + (0.18850487318190638010e-12 + 0.79697813173519853340e-14 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 17: { - double t = 2*y100 - 35; - return 0.12257529703447467345e0 + (0.44621675710026986366e-2 + (0.39634304721292440285e-4 + (0.55321553769873381819e-6 + (0.10343619428848520870e-7 + (0.26033830170470368088e-9 + (0.87743837749108025357e-11 + (0.34427092430230063401e-12 + 0.10205506615709843189e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 18: { - double t = 2*y100 - 37; - return 0.13166276955656699478e0 + (0.46276970481783001803e-2 + (0.43225026380496399310e-4 + (0.64799164020016902656e-6 + (0.13580082794704641782e-7 + (0.39839800853954313927e-9 + (0.14431142411840000000e-10 + 0.42193457308830027541e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 19: { - double t = 2*y100 - 39; - return 0.14109647869803356475e0 + (0.48088424418545347758e-2 + (0.47474504753352150205e-4 + (0.77509866468724360352e-6 + (0.18536851570794291724e-7 + (0.60146623257887570439e-9 + (0.18533978397305276318e-10 + (0.41033845938901048380e-13 - 0.46160680279304825485e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 20: { - double t = 2*y100 - 41; - return 0.15091057940548936603e0 + (0.50086864672004685703e-2 + (0.52622482832192230762e-4 + (0.95034664722040355212e-6 + (0.25614261331144718769e-7 + (0.80183196716888606252e-9 + (0.12282524750534352272e-10 + (-0.10531774117332273617e-11 - 0.86157181395039646412e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 21: { - double t = 2*y100 - 43; - return 0.16114648116017010770e0 + (0.52314661581655369795e-2 + (0.59005534545908331315e-4 + (0.11885518333915387760e-5 + (0.33975801443239949256e-7 + (0.82111547144080388610e-9 + (-0.12357674017312854138e-10 + (-0.24355112256914479176e-11 - 0.75155506863572930844e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 22: { - double t = 2*y100 - 45; - return 0.17185551279680451144e0 + (0.54829002967599420860e-2 + (0.67013226658738082118e-4 + (0.14897400671425088807e-5 + (0.40690283917126153701e-7 + (0.44060872913473778318e-9 + (-0.52641873433280000000e-10 - 0.30940587864543343124e-11 * t) * t) * t) * t) * t) * t) * t; - } - case 23: { - double t = 2*y100 - 47; - return 0.18310194559815257381e0 + (0.57701559375966953174e-2 + (0.76948789401735193483e-4 + (0.18227569842290822512e-5 + (0.41092208344387212276e-7 + (-0.44009499965694442143e-9 + (-0.92195414685628803451e-10 + (-0.22657389705721753299e-11 + 0.10004784908106839254e-12 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 24: { - double t = 2*y100 - 49; - return 0.19496527191546630345e0 + (0.61010853144364724856e-2 + (0.88812881056342004864e-4 + (0.21180686746360261031e-5 + (0.30652145555130049203e-7 + (-0.16841328574105890409e-8 + (-0.11008129460612823934e-9 + (-0.12180794204544515779e-12 + 0.15703325634590334097e-12 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 25: { - double t = 2*y100 - 51; - return 0.20754006813966575720e0 + (0.64825787724922073908e-2 + (0.10209599627522311893e-3 + (0.22785233392557600468e-5 + (0.73495224449907568402e-8 + (-0.29442705974150112783e-8 + (-0.94082603434315016546e-10 + (0.23609990400179321267e-11 + 0.14141908654269023788e-12 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 26: { - double t = 2*y100 - 53; - return 0.22093185554845172146e0 + (0.69182878150187964499e-2 + (0.11568723331156335712e-3 + (0.22060577946323627739e-5 + (-0.26929730679360840096e-7 + (-0.38176506152362058013e-8 + (-0.47399503861054459243e-10 + (0.40953700187172127264e-11 + 0.69157730376118511127e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 27: { - double t = 2*y100 - 55; - return 0.23524827304057813918e0 + (0.74063350762008734520e-2 + (0.12796333874615790348e-3 + (0.18327267316171054273e-5 + (-0.66742910737957100098e-7 + (-0.40204740975496797870e-8 + (0.14515984139495745330e-10 + (0.44921608954536047975e-11 - 0.18583341338983776219e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 28: { - double t = 2*y100 - 57; - return 0.25058626331812744775e0 + (0.79377285151602061328e-2 + (0.13704268650417478346e-3 + (0.11427511739544695861e-5 + (-0.10485442447768377485e-6 + (-0.34850364756499369763e-8 + (0.72656453829502179208e-10 + (0.36195460197779299406e-11 - 0.84882136022200714710e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 29: { - double t = 2*y100 - 59; - return 0.26701724900280689785e0 + (0.84959936119625864274e-2 + (0.14112359443938883232e-3 + (0.17800427288596909634e-6 + (-0.13443492107643109071e-6 + (-0.23512456315677680293e-8 + (0.11245846264695936769e-9 + (0.19850501334649565404e-11 - 0.11284666134635050832e-12 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 30: { - double t = 2*y100 - 61; - return 0.28457293586253654144e0 + (0.90581563892650431899e-2 + (0.13880520331140646738e-3 + (-0.97262302362522896157e-6 + (-0.15077100040254187366e-6 + (-0.88574317464577116689e-9 + (0.12760311125637474581e-9 + (0.20155151018282695055e-12 - 0.10514169375181734921e-12 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 31: { - double t = 2*y100 - 63; - return 0.30323425595617385705e0 + (0.95968346790597422934e-2 + (0.12931067776725883939e-3 + (-0.21938741702795543986e-5 + (-0.15202888584907373963e-6 + (0.61788350541116331411e-9 + (0.11957835742791248256e-9 + (-0.12598179834007710908e-11 - 0.75151817129574614194e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 32: { - double t = 2*y100 - 65; - return 0.32292521181517384379e0 + (0.10082957727001199408e-1 + (0.11257589426154962226e-3 + (-0.33670890319327881129e-5 + (-0.13910529040004008158e-6 + (0.19170714373047512945e-8 + (0.94840222377720494290e-10 + (-0.21650018351795353201e-11 - 0.37875211678024922689e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 33: { - double t = 2*y100 - 67; - return 0.34351233557911753862e0 + (0.10488575435572745309e-1 + (0.89209444197248726614e-4 + (-0.43893459576483345364e-5 + (-0.11488595830450424419e-6 + (0.28599494117122464806e-8 + (0.61537542799857777779e-10 - 0.24935749227658002212e-11 * t) * t) * t) * t) * t) * t) * t; - } - case 34: { - double t = 2*y100 - 69; - return 0.36480946642143669093e0 + (0.10789304203431861366e-1 + (0.60357993745283076834e-4 + (-0.51855862174130669389e-5 + (-0.83291664087289801313e-7 + (0.33898011178582671546e-8 + (0.27082948188277716482e-10 + (-0.23603379397408694974e-11 + 0.19328087692252869842e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 35: { - double t = 2*y100 - 71; - return 0.38658679935694939199e0 + (0.10966119158288804999e-1 + (0.27521612041849561426e-4 + (-0.57132774537670953638e-5 + (-0.48404772799207914899e-7 + (0.35268354132474570493e-8 + (-0.32383477652514618094e-11 + (-0.19334202915190442501e-11 + 0.32333189861286460270e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 36: { - double t = 2*y100 - 73; - return 0.40858275583808707870e0 + (0.11006378016848466550e-1 + (-0.76396376685213286033e-5 + (-0.59609835484245791439e-5 + (-0.13834610033859313213e-7 + (0.33406952974861448790e-8 + (-0.26474915974296612559e-10 + (-0.13750229270354351983e-11 + 0.36169366979417390637e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 37: { - double t = 2*y100 - 75; - return 0.43051714914006682977e0 + (0.10904106549500816155e-1 + (-0.43477527256787216909e-4 + (-0.59429739547798343948e-5 + (0.17639200194091885949e-7 + (0.29235991689639918688e-8 + (-0.41718791216277812879e-10 + (-0.81023337739508049606e-12 + 0.33618915934461994428e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 38: { - double t = 2*y100 - 77; - return 0.45210428135559607406e0 + (0.10659670756384400554e-1 + (-0.78488639913256978087e-4 + (-0.56919860886214735936e-5 + (0.44181850467477733407e-7 + (0.23694306174312688151e-8 + (-0.49492621596685443247e-10 + (-0.31827275712126287222e-12 + 0.27494438742721623654e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 39: { - double t = 2*y100 - 79; - return 0.47306491195005224077e0 + (0.10279006119745977570e-1 + (-0.11140268171830478306e-3 + (-0.52518035247451432069e-5 + (0.64846898158889479518e-7 + (0.17603624837787337662e-8 + (-0.51129481592926104316e-10 + (0.62674584974141049511e-13 + 0.20055478560829935356e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 40: { - double t = 2*y100 - 81; - return 0.49313638965719857647e0 + (0.97725799114772017662e-2 + (-0.14122854267291533334e-3 + (-0.46707252568834951907e-5 + (0.79421347979319449524e-7 + (0.11603027184324708643e-8 + (-0.48269605844397175946e-10 + (0.32477251431748571219e-12 + 0.12831052634143527985e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 41: { - double t = 2*y100 - 83; - return 0.51208057433416004042e0 + (0.91542422354009224951e-2 + (-0.16726530230228647275e-3 + (-0.39964621752527649409e-5 + (0.88232252903213171454e-7 + (0.61343113364949928501e-9 + (-0.42516755603130443051e-10 + (0.47910437172240209262e-12 + 0.66784341874437478953e-14 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 42: { - double t = 2*y100 - 85; - return 0.52968945458607484524e0 + (0.84400880445116786088e-2 + (-0.18908729783854258774e-3 + (-0.32725905467782951931e-5 + (0.91956190588652090659e-7 + (0.14593989152420122909e-9 + (-0.35239490687644444445e-10 + 0.54613829888448694898e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 43: { - double t = 2*y100 - 87; - return 0.54578857454330070965e0 + (0.76474155195880295311e-2 + (-0.20651230590808213884e-3 + (-0.25364339140543131706e-5 + (0.91455367999510681979e-7 + (-0.23061359005297528898e-9 + (-0.27512928625244444444e-10 + 0.54895806008493285579e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 44: { - double t = 2*y100 - 89; - return 0.56023851910298493910e0 + (0.67938321739997196804e-2 + (-0.21956066613331411760e-3 + (-0.18181127670443266395e-5 + (0.87650335075416845987e-7 + (-0.51548062050366615977e-9 + (-0.20068462174044444444e-10 + 0.50912654909758187264e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 45: { - double t = 2*y100 - 91; - return 0.57293478057455721150e0 + (0.58965321010394044087e-2 + (-0.22841145229276575597e-3 + (-0.11404605562013443659e-5 + (0.81430290992322326296e-7 + (-0.71512447242755357629e-9 + (-0.13372664928000000000e-10 + 0.44461498336689298148e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 46: { - double t = 2*y100 - 93; - return 0.58380635448407827360e0 + (0.49717469530842831182e-2 + (-0.23336001540009645365e-3 + (-0.51952064448608850822e-6 + (0.73596577815411080511e-7 + (-0.84020916763091566035e-9 + (-0.76700972702222222221e-11 + 0.36914462807972467044e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 47: { - double t = 2*y100 - 95; - return 0.59281340237769489597e0 + (0.40343592069379730568e-2 + (-0.23477963738658326185e-3 + (0.34615944987790224234e-7 + (0.64832803248395814574e-7 + (-0.90329163587627007971e-9 + (-0.30421940400000000000e-11 + 0.29237386653743536669e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 48: { - double t = 2*y100 - 97; - return 0.59994428743114271918e0 + (0.30976579788271744329e-2 + (-0.23308875765700082835e-3 + (0.51681681023846925160e-6 + (0.55694594264948268169e-7 + (-0.91719117313243464652e-9 + (0.53982743680000000000e-12 + 0.22050829296187771142e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 49: { - double t = 2*y100 - 99; - return 0.60521224471819875444e0 + (0.21732138012345456060e-2 + (-0.22872428969625997456e-3 + (0.92588959922653404233e-6 + (0.46612665806531930684e-7 + (-0.89393722514414153351e-9 + (0.31718550353777777778e-11 + 0.15705458816080549117e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 50: { - double t = 2*y100 - 101; - return 0.60865189969791123620e0 + (0.12708480848877451719e-2 + (-0.22212090111534847166e-3 + (0.12636236031532793467e-5 + (0.37904037100232937574e-7 + (-0.84417089968101223519e-9 + (0.49843180828444444445e-11 + 0.10355439441049048273e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 51: { - double t = 2*y100 - 103; - return 0.61031580103499200191e0 + (0.39867436055861038223e-3 + (-0.21369573439579869291e-3 + (0.15339402129026183670e-5 + (0.29787479206646594442e-7 + (-0.77687792914228632974e-9 + (0.61192452741333333334e-11 + 0.60216691829459295780e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 52: { - double t = 2*y100 - 105; - return 0.61027109047879835868e0 + (-0.43680904508059878254e-3 + (-0.20383783788303894442e-3 + (0.17421743090883439959e-5 + (0.22400425572175715576e-7 + (-0.69934719320045128997e-9 + (0.67152759655111111110e-11 + 0.26419960042578359995e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 53: { - double t = 2*y100 - 107; - return 0.60859639489217430521e0 + (-0.12305921390962936873e-2 + (-0.19290150253894682629e-3 + (0.18944904654478310128e-5 + (0.15815530398618149110e-7 + (-0.61726850580964876070e-9 + 0.68987888999111111110e-11 * t) * t) * t) * t) * t) * t; - } - case 54: { - double t = 2*y100 - 109; - return 0.60537899426486075181e0 + (-0.19790062241395705751e-2 + (-0.18120271393047062253e-3 + (0.19974264162313241405e-5 + (0.10055795094298172492e-7 + (-0.53491997919318263593e-9 + (0.67794550295111111110e-11 - 0.17059208095741511603e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 55: { - double t = 2*y100 - 111; - return 0.60071229457904110537e0 + (-0.26795676776166354354e-2 + (-0.16901799553627508781e-3 + (0.20575498324332621581e-5 + (0.51077165074461745053e-8 + (-0.45536079828057221858e-9 + (0.64488005516444444445e-11 - 0.29311677573152766338e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 56: { - double t = 2*y100 - 113; - return 0.59469361520112714738e0 + (-0.33308208190600993470e-2 + (-0.15658501295912405679e-3 + (0.20812116912895417272e-5 + (0.93227468760614182021e-9 + (-0.38066673740116080415e-9 + (0.59806790359111111110e-11 - 0.36887077278950440597e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 57: { - double t = 2*y100 - 115; - return 0.58742228631775388268e0 + (-0.39321858196059227251e-2 + (-0.14410441141450122535e-3 + (0.20743790018404020716e-5 + (-0.25261903811221913762e-8 + (-0.31212416519526924318e-9 + (0.54328422462222222221e-11 - 0.40864152484979815972e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 58: { - double t = 2*y100 - 117; - return 0.57899804200033018447e0 + (-0.44838157005618913447e-2 + (-0.13174245966501437965e-3 + (0.20425306888294362674e-5 + (-0.53330296023875447782e-8 + (-0.25041289435539821014e-9 + (0.48490437205333333334e-11 - 0.42162206939169045177e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 59: { - double t = 2*y100 - 119; - return 0.56951968796931245974e0 + (-0.49864649488074868952e-2 + (-0.11963416583477567125e-3 + (0.19906021780991036425e-5 + (-0.75580140299436494248e-8 + (-0.19576060961919820491e-9 + (0.42613011928888888890e-11 - 0.41539443304115604377e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 60: { - double t = 2*y100 - 121; - return 0.55908401930063918964e0 + (-0.54413711036826877753e-2 + (-0.10788661102511914628e-3 + (0.19229663322982839331e-5 + (-0.92714731195118129616e-8 + (-0.14807038677197394186e-9 + (0.36920870298666666666e-11 - 0.39603726688419162617e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 61: { - double t = 2*y100 - 123; - return 0.54778496152925675315e0 + (-0.58501497933213396670e-2 + (-0.96582314317855227421e-4 + (0.18434405235069270228e-5 + (-0.10541580254317078711e-7 + (-0.10702303407788943498e-9 + (0.31563175582222222222e-11 - 0.36829748079110481422e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 62: { - double t = 2*y100 - 125; - return 0.53571290831682823999e0 + (-0.62147030670760791791e-2 + (-0.85782497917111760790e-4 + (0.17553116363443470478e-5 + (-0.11432547349815541084e-7 + (-0.72157091369041330520e-10 + (0.26630811607111111111e-11 - 0.33578660425893164084e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 63: { - double t = 2*y100 - 127; - return 0.52295422962048434978e0 + (-0.65371404367776320720e-2 + (-0.75530164941473343780e-4 + (0.16613725797181276790e-5 + (-0.12003521296598910761e-7 + (-0.42929753689181106171e-10 + (0.22170894940444444444e-11 - 0.30117697501065110505e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 64: { - double t = 2*y100 - 129; - return 0.50959092577577886140e0 + (-0.68197117603118591766e-2 + (-0.65852936198953623307e-4 + (0.15639654113906716939e-5 + (-0.12308007991056524902e-7 + (-0.18761997536910939570e-10 + (0.18198628922666666667e-11 - 0.26638355362285200932e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 65: { - double t = 2*y100 - 131; - return 0.49570040481823167970e0 + (-0.70647509397614398066e-2 + (-0.56765617728962588218e-4 + (0.14650274449141448497e-5 + (-0.12393681471984051132e-7 + (0.92904351801168955424e-12 + (0.14706755960177777778e-11 - 0.23272455351266325318e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 66: { - double t = 2*y100 - 133; - return 0.48135536250935238066e0 + (-0.72746293327402359783e-2 + (-0.48272489495730030780e-4 + (0.13661377309113939689e-5 + (-0.12302464447599382189e-7 + (0.16707760028737074907e-10 + (0.11672928324444444444e-11 - 0.20105801424709924499e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 67: { - double t = 2*y100 - 135; - return 0.46662374675511439448e0 + (-0.74517177649528487002e-2 + (-0.40369318744279128718e-4 + (0.12685621118898535407e-5 + (-0.12070791463315156250e-7 + (0.29105507892605823871e-10 + (0.90653314645333333334e-12 - 0.17189503312102982646e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 68: { - double t = 2*y100 - 137; - return 0.45156879030168268778e0 + (-0.75983560650033817497e-2 + (-0.33045110380705139759e-4 + (0.11732956732035040896e-5 + (-0.11729986947158201869e-7 + (0.38611905704166441308e-10 + (0.68468768305777777779e-12 - 0.14549134330396754575e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 69: { - double t = 2*y100 - 139; - return 0.43624909769330896904e0 + (-0.77168291040309554679e-2 + (-0.26283612321339907756e-4 + (0.10811018836893550820e-5 + (-0.11306707563739851552e-7 + (0.45670446788529607380e-10 + (0.49782492549333333334e-12 - 0.12191983967561779442e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 70: { - double t = 2*y100 - 141; - return 0.42071877443548481181e0 + (-0.78093484015052730097e-2 + (-0.20064596897224934705e-4 + (0.99254806680671890766e-6 + (-0.10823412088884741451e-7 + (0.50677203326904716247e-10 + (0.34200547594666666666e-12 - 0.10112698698356194618e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 71: { - double t = 2*y100 - 143; - return 0.40502758809710844280e0 + (-0.78780384460872937555e-2 + (-0.14364940764532853112e-4 + (0.90803709228265217384e-6 + (-0.10298832847014466907e-7 + (0.53981671221969478551e-10 + (0.21342751381333333333e-12 - 0.82975901848387729274e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 72: { - double t = 2*y100 - 145; - return 0.38922115269731446690e0 + (-0.79249269708242064120e-2 + (-0.91595258799106970453e-5 + (0.82783535102217576495e-6 + (-0.97484311059617744437e-8 + (0.55889029041660225629e-10 + (0.10851981336888888889e-12 - 0.67278553237853459757e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 73: { - double t = 2*y100 - 147; - return 0.37334112915460307335e0 + (-0.79519385109223148791e-2 + (-0.44219833548840469752e-5 + (0.75209719038240314732e-6 + (-0.91848251458553190451e-8 + (0.56663266668051433844e-10 + (0.23995894257777777778e-13 - 0.53819475285389344313e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 74: { - double t = 2*y100 - 149; - return 0.35742543583374223085e0 + (-0.79608906571527956177e-2 + (-0.12530071050975781198e-6 + (0.68088605744900552505e-6 + (-0.86181844090844164075e-8 + (0.56530784203816176153e-10 + (-0.43120012248888888890e-13 - 0.42372603392496813810e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 75: { - double t = 2*y100 - 151; - return 0.34150846431979618536e0 + (-0.79534924968773806029e-2 + (0.37576885610891515813e-5 + (0.61419263633090524326e-6 + (-0.80565865409945960125e-8 + (0.55684175248749269411e-10 + (-0.95486860764444444445e-13 - 0.32712946432984510595e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 76: { - double t = 2*y100 - 153; - return 0.32562129649136346824e0 + (-0.79313448067948884309e-2 + (0.72539159933545300034e-5 + (0.55195028297415503083e-6 + (-0.75063365335570475258e-8 + (0.54281686749699595941e-10 - 0.13545424295111111111e-12 * t) * t) * t) * t) * t) * t; - } - case 77: { - double t = 2*y100 - 155; - return 0.30979191977078391864e0 + (-0.78959416264207333695e-2 + (0.10389774377677210794e-4 + (0.49404804463196316464e-6 + (-0.69722488229411164685e-8 + (0.52469254655951393842e-10 - 0.16507860650666666667e-12 * t) * t) * t) * t) * t) * t; - } - case 78: { - double t = 2*y100 - 157; - return 0.29404543811214459904e0 + (-0.78486728990364155356e-2 + (0.13190885683106990459e-4 + (0.44034158861387909694e-6 + (-0.64578942561562616481e-8 + (0.50354306498006928984e-10 - 0.18614473550222222222e-12 * t) * t) * t) * t) * t) * t; - } - case 79: { - double t = 2*y100 - 159; - return 0.27840427686253660515e0 + (-0.77908279176252742013e-2 + (0.15681928798708548349e-4 + (0.39066226205099807573e-6 + (-0.59658144820660420814e-8 + (0.48030086420373141763e-10 - 0.20018995173333333333e-12 * t) * t) * t) * t) * t) * t; - } - case 80: { - double t = 2*y100 - 161; - return 0.26288838011163800908e0 + (-0.77235993576119469018e-2 + (0.17886516796198660969e-4 + (0.34482457073472497720e-6 + (-0.54977066551955420066e-8 + (0.45572749379147269213e-10 - 0.20852924954666666667e-12 * t) * t) * t) * t) * t) * t; - } - case 81: { - double t = 2*y100 - 163; - return 0.24751539954181029717e0 + (-0.76480877165290370975e-2 + (0.19827114835033977049e-4 + (0.30263228619976332110e-6 + (-0.50545814570120129947e-8 + (0.43043879374212005966e-10 - 0.21228012028444444444e-12 * t) * t) * t) * t) * t) * t; - } - case 82: { - double t = 2*y100 - 165; - return 0.23230087411688914593e0 + (-0.75653060136384041587e-2 + (0.21524991113020016415e-4 + (0.26388338542539382413e-6 + (-0.46368974069671446622e-8 + (0.40492715758206515307e-10 - 0.21238627815111111111e-12 * t) * t) * t) * t) * t) * t; - } - case 83: { - double t = 2*y100 - 167; - return 0.21725840021297341931e0 + (-0.74761846305979730439e-2 + (0.23000194404129495243e-4 + (0.22837400135642906796e-6 + (-0.42446743058417541277e-8 + (0.37958104071765923728e-10 - 0.20963978568888888889e-12 * t) * t) * t) * t) * t) * t; - } - case 84: { - double t = 2*y100 - 169; - return 0.20239979200788191491e0 + (-0.73815761980493466516e-2 + (0.24271552727631854013e-4 + (0.19590154043390012843e-6 + (-0.38775884642456551753e-8 + (0.35470192372162901168e-10 - 0.20470131678222222222e-12 * t) * t) * t) * t) * t) * t; - } - case 85: { - double t = 2*y100 - 171; - return 0.18773523211558098962e0 + (-0.72822604530339834448e-2 + (0.25356688567841293697e-4 + (0.16626710297744290016e-6 + (-0.35350521468015310830e-8 + (0.33051896213898864306e-10 - 0.19811844544000000000e-12 * t) * t) * t) * t) * t) * t; - } - case 86: { - double t = 2*y100 - 173; - return 0.17327341258479649442e0 + (-0.71789490089142761950e-2 + (0.26272046822383820476e-4 + (0.13927732375657362345e-6 + (-0.32162794266956859603e-8 + (0.30720156036105652035e-10 - 0.19034196304000000000e-12 * t) * t) * t) * t) * t) * t; - } - case 87: { - double t = 2*y100 - 175; - return 0.15902166648328672043e0 + (-0.70722899934245504034e-2 + (0.27032932310132226025e-4 + (0.11474573347816568279e-6 + (-0.29203404091754665063e-8 + (0.28487010262547971859e-10 - 0.18174029063111111111e-12 * t) * t) * t) * t) * t) * t; - } - case 88: { - double t = 2*y100 - 177; - return 0.14498609036610283865e0 + (-0.69628725220045029273e-2 + (0.27653554229160596221e-4 + (0.92493727167393036470e-7 + (-0.26462055548683583849e-8 + (0.26360506250989943739e-10 - 0.17261211260444444444e-12 * t) * t) * t) * t) * t) * t; - } - case 89: { - double t = 2*y100 - 179; - return 0.13117165798208050667e0 + (-0.68512309830281084723e-2 + (0.28147075431133863774e-4 + (0.72351212437979583441e-7 + (-0.23927816200314358570e-8 + (0.24345469651209833155e-10 - 0.16319736960000000000e-12 * t) * t) * t) * t) * t) * t; - } - case 90: { - double t = 2*y100 - 181; - return 0.11758232561160626306e0 + (-0.67378491192463392927e-2 + (0.28525664781722907847e-4 + (0.54156999310046790024e-7 + (-0.21589405340123827823e-8 + (0.22444150951727334619e-10 - 0.15368675584000000000e-12 * t) * t) * t) * t) * t) * t; - } - case 91: { - double t = 2*y100 - 183; - return 0.10422112945361673560e0 + (-0.66231638959845581564e-2 + (0.28800551216363918088e-4 + (0.37758983397952149613e-7 + (-0.19435423557038933431e-8 + (0.20656766125421362458e-10 - 0.14422990012444444444e-12 * t) * t) * t) * t) * t) * t; - } - case 92: { - double t = 2*y100 - 185; - return 0.91090275493541084785e-1 + (-0.65075691516115160062e-2 + (0.28982078385527224867e-4 + (0.23014165807643012781e-7 + (-0.17454532910249875958e-8 + (0.18981946442680092373e-10 - 0.13494234691555555556e-12 * t) * t) * t) * t) * t) * t; - } - case 93: { - double t = 2*y100 - 187; - return 0.78191222288771379358e-1 + (-0.63914190297303976434e-2 + (0.29079759021299682675e-4 + (0.97885458059415717014e-8 + (-0.15635596116134296819e-8 + (0.17417110744051331974e-10 - 0.12591151763555555556e-12 * t) * t) * t) * t) * t) * t; - } - case 94: { - double t = 2*y100 - 189; - return 0.65524757106147402224e-1 + (-0.62750311956082444159e-2 + (0.29102328354323449795e-4 + (-0.20430838882727954582e-8 + (-0.13967781903855367270e-8 + (0.15958771833747057569e-10 - 0.11720175765333333333e-12 * t) * t) * t) * t) * t) * t; - } - case 95: { - double t = 2*y100 - 191; - return 0.53091065838453612773e-1 + (-0.61586898417077043662e-2 + (0.29057796072960100710e-4 + (-0.12597414620517987536e-7 + (-0.12440642607426861943e-8 + (0.14602787128447932137e-10 - 0.10885859114666666667e-12 * t) * t) * t) * t) * t) * t; - } - case 96: { - double t = 2*y100 - 193; - return 0.40889797115352738582e-1 + (-0.60426484889413678200e-2 + (0.28953496450191694606e-4 + (-0.21982952021823718400e-7 + (-0.11044169117553026211e-8 + (0.13344562332430552171e-10 - 0.10091231402844444444e-12 * t) * t) * t) * t) * t) * t; - } - case 97: { - double t = 2*y100 - 195; - return 0.28920121009594899986e-1 + (-0.59271325915413781788e-2 + (0.28796136372768177423e-4 + (-0.30300382596279568642e-7 + (-0.97688275022802329749e-9 + (0.12179215701512592356e-10 - 0.93380988481777777779e-13 * t) * t) * t) * t) * t) * t; - } - case 98: { - double t = 2*y100 - 197; - return 0.17180782722617876655e-1 + (-0.58123419543161127769e-2 + (0.28591841095380959666e-4 + (-0.37642963496443667043e-7 + (-0.86055809047367300024e-9 + (0.11101709356762665578e-10 - 0.86272947493333333334e-13 * t) * t) * t) * t) * t) * t; - } - case 99: case 100: { // use Taylor expansion for small x (|x| <= 0.010101...) - // (2/sqrt(pi)) * (x - 2/3 x^3 + 4/15 x^5 - 8/105 x^7) - double x2 = x*x; - return x * (1.1283791670955125739 - - x2 * (0.75225277806367504925 - - x2 * (0.30090111122547001970 - - x2 * 0.085971746064420005629))); - } - } - /* Since 0 <= y100 < 101, this is only reached if x is NaN, - in which case we should return NaN. */ - return std::numeric_limits::quiet_NaN(); -} - -double w_im(double x) -{ - if (x >= 0) { - if (x > 45) { // continued-fraction expansion is faster - const double ispi = 0.56418958354775628694807945156; // 1 / sqrt(pi) - if (x > 5e7) // 1-term expansion, important to avoid overflow - return ispi / x; - /* 5-term expansion (rely on compiler for CSE), simplified from: - ispi / (x-0.5/(x-1/(x-1.5/(x-2/x)))) */ - return ispi*((x*x) * (x*x-4.5) + 2) / (x * ((x*x) * (x*x-5) + 3.75)); - } - return w_im_y100(100/(1+x), x); - } - else { // = -Faddeeva::w_im(-x) - if (x < -45) { // continued-fraction expansion is faster - const double ispi = 0.56418958354775628694807945156; // 1 / sqrt(pi) - if (x < -5e7) // 1-term expansion, important to avoid overflow - return ispi / x; - /* 5-term expansion (rely on compiler for CSE), simplified from: - ispi / (x+0.5/(x+1/(x+1.5/(x+2/x)))) */ - return ispi*((x*x) * (x*x-4.5) + 2) / (x * ((x*x) * (x*x-5) + 3.75)); - } - return -w_im_y100(100/(1-x), -x); - } -} - -} \ No newline at end of file diff --git a/scipy/special/xsf/fresnel.h b/scipy/special/xsf/fresnel.h deleted file mode 100644 index 094ba031710c..000000000000 --- a/scipy/special/xsf/fresnel.h +++ /dev/null @@ -1,424 +0,0 @@ -#pragma once - -#include "cephes/fresnl.h" -#include "config.h" - -namespace xsf { -namespace detail { - - inline void cfc(std::complex z, std::complex *zf, std::complex *zd) { - - // ========================================================= - // Purpose: Compute complex Fresnel integral C(z) and C'(z) - // Input : z --- Argument of C(z) - // Output: ZF --- C(z) - // ZD --- C'(z) - // ========================================================= - - int k, m; - double wa0, wa; - std::complex c, cr, cf, cf0, cf1, cg, d; - const double eps = 1.0e-14; - const double pi = 3.141592653589793; - - double w0 = std::abs(z); - std::complex zp = 0.5 * pi * z * z; - std::complex zp2 = zp * zp; - std::complex z0 = 0.0; - - if (z == z0) { - c = z0; - } else if (w0 <= 2.5) { - cr = z; - c = cr; - wa0 = 0.0; - for (k = 1; k <= 80; k++) { - cr = -0.5 * cr * (4.0 * k - 3.0) / static_cast(k) / (2.0 * k - 1.0) / (4.0 * k + 1.0) * zp2; - c += cr; - wa = std::abs(c); - if ((fabs((wa - wa0) / wa) < eps) && (k > 10)) { - *zf = c; - *zd = std::cos(0.5 * pi * z * z); - return; - } - wa0 = wa; - } - } else if ((w0 > 2.5) && (w0 < 4.5)) { - m = 85; - c = z0; - cf1 = z0; - cf0 = 1.0e-100; - for (k = m; k >= 0; k--) { - cf = (2.0 * k + 3.0) * cf0 / zp - cf1; - if (k % 2 == 0) { - c += cf; - } - cf1 = cf0; - cf0 = cf; - } - c *= 2.0 / (pi * z) * std::sin(zp) / cf; - } else { - // See comment at CFS(), use C(z) = iC(-iz) - if ((z.imag() > -z.real()) && (z.imag() <= z.real())) { - // right quadrant - d = 0.5; - } else if ((z.imag() > z.real()) && (z.imag() >= -z.real())) { - // upper quadrant - d = std::complex(0, 0.5); - } else if ((z.imag() < -z.real()) && (z.imag() >= z.real())) { - // left quadrant - d = -0.5; - } else { - d = std::complex(0, -0.5); - } - cr = 1.0; - cf = 1.0; - for (k = 1; k <= 20; k++) { - cr = -0.25 * cr * (4.0 * k - 1.0) * (4.0 * k - 3.0) / zp2; - cf += cr; - } - cr = 1.0 / (pi * z * z); - cg = cr; - for (k = 1; k <= 12; k++) { - cr = -0.25 * cr * (4.0 * k + 1.0) * (4.0 * k - 1.0) / zp2; - cg += cr; - } - c = d + (cf * std::sin(zp) - cg * std::cos(zp)) / (pi * z); - } - *zf = c; - *zd = std::cos(0.5 * pi * z * z); - return; - } - - inline void cfs(std::complex z, std::complex *zf, std::complex *zd) { - - // ========================================================= - // Purpose: Compute complex Fresnel Integral S(z) and S'(z) - // Input : z --- Argument of S(z) - // Output: ZF --- S(z) - // ZD --- S'(z) - // ========================================================= - - int k, m; - double wb0, wb; - std::complex s, cr, cf, cf0, cf1, cg, d; - const double eps = 1.0e-14; - const double pi = 3.141592653589793; - - double w0 = std::abs(z); - std::complex zp = 0.5 * pi * z * z; - std::complex zp2 = zp * zp; - std::complex z0 = 0.0; - - if (z == z0) { - s = z0; - } else if (w0 <= 2.5) { - s = z * zp / 3.0; - cr = s; - wb0 = 0.0; - for (k = 1; k <= 80; k++) { - cr = -0.5 * cr * (4.0 * k - 1.0) / static_cast(k) / (2.0 * k + 1.0) / (4.0 * k + 3.0) * zp2; - s += cr; - wb = std::abs(s); - if ((fabs(wb - wb0) < eps) && (k > 10)) { - *zf = s; - *zd = std::sin(0.5 * pi * z * z); - return; - } - wb0 = wb; - } - } else if ((w0 > 2.5) && (w0 < 4.5)) { - m = 85; - s = z0; - cf1 = z0; - cf0 = 1.0e-100; - for (k = m; k >= 0; k--) { - cf = (2.0 * k + 3.0) * cf0 / zp - cf1; - if (k % 2 == 1) { - s += cf; - } - cf1 = cf0; - cf0 = cf; - } - s = 2.0 / (pi * z) * std::sin(zp) / cf * s; - } else { - // Auxiliary functions f(z) and g(z) can be computed using an - // asymptotic expansion in the right quadrant |arg(z)| <= pi/4, not pi/2 - // as sometimes suggested. Use the symmetry S(z) = -iS(-iz). - // Interestingly, most of the expansion code is the same across - // the quadrants. (The forth power in Z is the equalizer here.) - // Only one constant has to be adapted. - if ((z.imag() > -z.real()) && (z.imag() <= z.real())) { - // right quadrant - d = 0.5; - } else if ((z.imag() > z.real()) && (z.imag() >= -z.real())) { - // upper quadrant - d = std::complex(0, -0.5); - } else if ((z.imag() < -z.real()) && (z.imag() >= z.real())) { - // left quadrant - d = -0.5; - } else { - d = std::complex(0, 0.5); - } - cr = 1.0; - cf = 1.0; - for (k = 1; k <= 20; k++) { - cr = -0.25 * cr * (4.0 * k - 1.0) * (4.0 * k - 3.0) / zp2; - cf += cr; - } - cr = 1.0; - cg = 1.0; - for (k = 1; k <= 12; k++) { - cr = -0.25 * cr * (4.0 * k + 1.0) * (4.0 * k - 1.0) / zp2; - cg += cr; - } - cg = cg / (pi * z * z); - s = d - (cf * std::cos(zp) + cg * std::sin(zp)) / (pi * z); - } - *zf = s; - *zd = std::sin(0.5 * pi * z * z); - return; - } - - template - void ffk(int ks, T x, std::complex &f, std::complex &g) { - - // ======================================================= - // Purpose: Compute modified Fresnel integrals F±(x) - // and K±(x) - // Input : x --- Argument of F±(x) and K±(x) - // KS --- Sign code - // KS=0 for calculating F+(x) and K+(x) - // KS=1 for calculating F_(x) and K_(x) - // Output: FR --- Re[F±(x)] - // FI --- Im[F±(x)] - // GR --- Re[K±(x)] - // GI --- Im[K±(x)] - // ====================================================== - - const T eps = 1.0e-15; - const T pi = 3.141592653589793; - const T pp2 = 1.2533141373155; - const T p2p = 0.7978845608028654; - - T fi0, c1, s1, cs, ss, xa, x2, x4, xc, xf, xf0, xf1, xg, xp, xq, xq2, xr, xs, xsu, xw; - - xa = fabs(x); - x2 = x * x; - x4 = x2 * x2; - - if (x == 0.0) { - f.real(0.5 * sqrt(0.5 * pi)); - f.imag(pow(-1, ks) * f.real()); - g = 0.5; - } else { - if (xa <= 2.5) { - xr = p2p * xa; - c1 = xr; - - for (int k = 1; k <= 50; ++k) { - xr = -0.5 * xr * (4.0 * k - 3.0) / k / (2.0 * k - 1.0) / (4.0 * k + 1.0) * x4; - c1 += xr; - if (fabs(xr / c1) < eps) - break; - } - - s1 = p2p * xa * xa * xa / 3.0; - xr = s1; - - for (int k = 1; k <= 50; ++k) { - xr = -0.5 * xr * (4.0 * k - 1.0) / k / (2.0 * k + 1.0) / (4.0 * k + 3.0) * x4; - s1 += xr; - if (fabs(xr / s1) < eps) - break; - } - - f.real(pp2 * (0.5 - c1)); - fi0 = pp2 * (0.5 - s1); - f.imag(pow(-1, ks) * fi0); - } else if (xa < 5.5) { - int m = (int) (42 + 1.75 * x2); - xsu = 0.0; - xc = 0.0; - xs = 0.0; - xf1 = 0.0; - xf0 = 1.0e-100; - - for (int k = m; k >= 0; --k) { - xf = (2.0 * k + 3.0) * xf0 / x2 - xf1; - if (k % 2 == 0) { - xc += xf; - } else { - xs += xf; - } - xsu += (2.0 * k + 1.0) * xf * xf; - xf1 = xf0; - xf0 = xf; - } - - xq = sqrt(xsu); - xw = p2p * xa / xq; - c1 = xc * xw; - s1 = xs * xw; - } else { - xr = 1.0; - xf = 1.0; - - for (int k = 1; k <= 12; ++k) { - xr = -0.25 * xr * (4.0 * k - 1.0) * (4.0 * k - 3.0) / x4; - xf += xr; - } - - xr = 1.0 / (2.0 * xa * xa); - xg = xr; - - for (int k = 1; k <= 12; ++k) { - xr = -0.25 * xr * (4.0 * k + 1.0) * (4.0 * k - 1.0) / x4; - xg += xr; - } - - c1 = 0.5 + (xf * sin(x2) - xg * cos(x2)) / sqrt(2.0 * pi) / xa; - s1 = 0.5 - (xf * cos(x2) + xg * sin(x2)) / sqrt(2.0 * pi) / xa; - } - - f.real(pp2 * (0.5 - c1)); - fi0 = pp2 * (0.5 - s1); - f.imag(pow(-1, ks) * fi0); - - xp = x2 + pi / 4.0; - cs = cos(xp); - ss = sin(xp); - xq2 = 1.0 / sqrt(pi); - - g.real(xq2 * (f.real() * cs + fi0 * ss)); - g.imag(pow(-1, ks) * xq2 * (fi0 * cs - f.real() * ss)); - - if (x < 0.0) { - f.real(pp2 - f.real()); - f.imag(pow(-1, ks) * pp2 - f.real()); - g.real(cos(x2) - g.real()); - g.imag(-pow(-1, ks) * sin(x2) - g.imag()); - } - } - } - -} // namespace detail - -/* Fresnel integrals of complex numbers */ - -inline void fresnel(double z, double &fs, double &fc) { cephes::fresnl(z, &fs, &fc); } - -inline void fresnel(float z, float &fs, float &fc) { - double fs_double; - double fc_double; - fresnel(static_cast(z), fs_double, fc_double); - - fs = fs_double; - fc = fc_double; -} - -inline void fresnel(std::complex z, std::complex &fs, std::complex &fc) { - std::complex fd; - detail::cfs(z, &fs, &fd); - detail::cfc(z, &fc, &fd); -} - -inline void fresnel(std::complex z, std::complex &fs, std::complex &fc) { - std::complex fs_cdouble; - std::complex fc_cdouble; - fresnel(static_cast>(z), fs_cdouble, fc_cdouble); - - fs = fs_cdouble; - fc = fc_cdouble; -} - -template -void modified_fresnel_plus(T x, std::complex &Fplus, std::complex &Kplus) { - detail::ffk(0, x, Fplus, Kplus); -} - -template -void modified_fresnel_minus(T x, std::complex &Fminus, std::complex &Kminus) { - detail::ffk(1, x, Fminus, Kminus); -} - -inline void fcszo(int kf, int nt, std::complex *zo) { - - // =============================================================== - // Purpose: Compute the complex zeros of Fresnel integral C(z) - // or S(z) using modified Newton's iteration method - // Input : KF --- Function code - // KF=1 for C(z) or KF=2 for S(z) - // NT --- Total number of zeros - // Output: ZO(L) --- L-th zero of C(z) or S(z) - // Routines called: - // (1) CFC for computing Fresnel integral C(z) - // (2) CFS for computing Fresnel integral S(z) - // ============================================================== - - int it; - double psq, px, py, w, w0; - std::complex z, zp, zf, zd, zfd, zgd, zq, zw; - const double pi = 3.141592653589793; - psq = 0.0; - w = 0.0; - - for (int nr = 1; nr <= nt; ++nr) { - if (kf == 1) - psq = sqrt(4.0 * nr - 1.0); - if (kf == 2) - psq = 2.0 * sqrt(nr); - - px = psq - log(pi * psq) / (pi * pi * psq * psq * psq); - py = log(pi * psq) / (pi * psq); - z = std::complex(px, py); - - if (kf == 2) { - if (nr == 2) { - z = std::complex(2.8334, 0.2443); - } - if (nr == 3) { - z = std::complex(3.4674, 0.2185); - } - if (nr == 4) { - z = std::complex(4.0025, 0.2008); - } - } - - it = 0; - do { - it++; - if (kf == 1) { - detail::cfc(z, &zf, &zd); - } - if (kf == 2) { - detail::cfs(z, &zf, &zd); - } - - zp = 1.0; - for (int i = 1; i < nr; i++) - zp *= (z - zo[i - 1]); - - zfd = zf / zp; - zq = 0.0; - for (int i = 1; i < nr; i++) { - zw = 1.0; - for (int j = 1; j < nr; j++) { - if (j == i) { - continue; - } - zw *= (z - zo[j - 1]); - } - zq += zw; - } - zgd = (zd - zq * zfd) / zp; - z -= zfd / zgd; - w0 = w; - w = std::abs(z); - } while ((it <= 50) && (fabs((w - w0) / w) > 1.0e-12)); - zo[nr - 1] = z; - } - return; -} - -} // namespace xsf diff --git a/scipy/special/xsf/gamma.h b/scipy/special/xsf/gamma.h deleted file mode 100644 index 5d94a6111084..000000000000 --- a/scipy/special/xsf/gamma.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include "cephes/gamma.h" -#include "cephes/igam.h" -#include "cephes/igami.h" -#include "loggamma.h" - -namespace xsf { - -template -XSF_HOST_DEVICE T gamma(T x) { - return cephes::Gamma(x); -} - -inline double gammainc(double a, double x) { return cephes::igam(a, x); } - -inline float gammainc(float a, float x) { return gammainc(static_cast(a), static_cast(x)); } - -inline double gammaincinv(double a, double p) { return cephes::igami(a, p); } - -inline float gammaincinv(float a, float p) { return gammaincinv(static_cast(a), static_cast(p)); } - -inline double gammaincc(double a, double x) { return cephes::igamc(a, x); } - -inline float gammaincc(float a, float x) { return gammaincc(static_cast(a), static_cast(x)); } - -inline double gammainccinv(double a, double p) { return cephes::igamci(a, p); } - -inline float gammainccinv(float a, float p) { return gammainccinv(static_cast(a), static_cast(p)); } - -XSF_HOST_DEVICE inline double gammaln(double x) { return cephes::lgam(x); } - -XSF_HOST_DEVICE inline float gammaln(float x) { return gammaln(static_cast(x)); } - -XSF_HOST_DEVICE inline double gammasgn(double x) { return cephes::gammasgn(x); } - -XSF_HOST_DEVICE inline float gammasgn(float x) { return gammasgn(static_cast(x)); } - -XSF_HOST_DEVICE inline std::complex gamma(std::complex z) { - // Compute Gamma(z) using loggamma. - if (z.real() <= 0 && z == std::floor(z.real())) { - // Poles - set_error("gamma", SF_ERROR_SINGULAR, NULL); - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - return std::exp(loggamma(z)); -} - -XSF_HOST_DEVICE inline std::complex gamma(std::complex z) { - return static_cast>(gamma(static_cast>(z))); -} - -template -T gamma_ratio(T a, T b) { - return std::tgamma(a) / std::tgamma(b); -} - -} // namespace xsf diff --git a/scipy/special/xsf/hyp2f1.h b/scipy/special/xsf/hyp2f1.h deleted file mode 100644 index 9d4eff753220..000000000000 --- a/scipy/special/xsf/hyp2f1.h +++ /dev/null @@ -1,694 +0,0 @@ -/* Implementation of Gauss's hypergeometric function for complex values. - * - * This implementation is based on the Fortran implementation by Shanjie Zhang and - * Jianming Jin included in specfun.f [1]_. Computation of Gauss's hypergeometric - * function involves handling a patchwork of special cases. By default the Zhang and - * Jin implementation has been followed as closely as possible except for situations where - * an improvement was obvious. We've attempted to document the reasons behind decisions - * made by Zhang and Jin and to document the reasons for deviating from their implementation - * when this has been done. References to the NIST Digital Library of Mathematical - * Functions [2]_ have been added where they are appropriate. The review paper by - * Pearson et al [3]_ is an excellent resource for best practices for numerical - * computation of hypergeometric functions. We have followed this review paper - * when making improvements to and correcting defects in Zhang and Jin's - * implementation. When Pearson et al propose several competing alternatives for a - * given case, we've used our best judgment to decide on the method to use. - * - * Author: Albert Steppi - * - * Distributed under the same license as Scipy. - * - * References - * ---------- - * .. [1] S. Zhang and J.M. Jin, "Computation of Special Functions", Wiley 1996 - * .. [2] NIST Digital Library of Mathematical Functions. http://dlmf.nist.gov/, - * Release 1.1.1 of 2021-03-15. F. W. J. Olver, A. B. Olde Daalhuis, - * D. W. Lozier, B. I. Schneider, R. F. Boisvert, C. W. Clark, B. R. Miller, - * B. V. Saunders, H. S. Cohl, and M. A. McClain, eds. - * .. [3] Pearson, J.W., Olver, S. & Porter, M.A. - * "Numerical methods for the computation of the confluent and Gauss - * hypergeometric functions." - * Numer Algor 74, 821-866 (2017). https://doi.org/10.1007/s11075-016-0173-0 - * .. [4] Raimundas Vidunas, "Degenerate Gauss Hypergeometric Functions", - * Kyushu Journal of Mathematics, 2007, Volume 61, Issue 1, Pages 109-135, - * .. [5] López, J.L., Temme, N.M. New series expansions of the Gauss hypergeometric - * function. Adv Comput Math 39, 349-365 (2013). - * https://doi.org/10.1007/s10444-012-9283-y - * """ - */ - -#pragma once - -#include "config.h" -#include "error.h" -#include "tools.h" - -#include "binom.h" -#include "cephes/gamma.h" -#include "cephes/lanczos.h" -#include "cephes/poch.h" -#include "cephes/hyp2f1.h" -#include "digamma.h" - -namespace xsf { -namespace detail { - constexpr double hyp2f1_EPS = 1e-15; - /* The original implementation in SciPy from Zhang and Jin used 1500 for the - * maximum number of series iterations in some cases and 500 in others. - * Through the empirical results on the test cases in - * scipy/special/_precompute/hyp2f1_data.py, it was determined that these values - * can lead to early termination of series which would have eventually converged - * at a reasonable level of accuracy. We've bumped the iteration limit to 3000, - * and may adjust it again based on further analysis. */ - constexpr std::uint64_t hyp2f1_MAXITER = 3000; - - XSF_HOST_DEVICE inline double four_gammas_lanczos(double u, double v, double w, double x) { - /* Compute ratio of gamma functions using lanczos approximation. - * - * Computes gamma(u)*gamma(v)/(gamma(w)*gamma(x)) - * - * It is assumed that x = u + v - w, but it is left to the user to - * ensure this. - * - * The lanczos approximation takes the form - * - * gamma(x) = factor(x) * lanczos_sum_expg_scaled(x) - * - * where factor(x) = ((x + lanczos_g - 0.5)/e)**(x - 0.5). - * - * The formula above is only valid for x >= 0.5, but can be extended to - * x < 0.5 with the reflection principle. - * - * Using the lanczos approximation when computing this ratio of gamma functions - * allows factors to be combined analytically to avoid underflow and overflow - * and produce a more accurate result. The condition x = u + v - w makes it - * possible to cancel the factors in the expression - * - * factor(u) * factor(v) / (factor(w) * factor(x)) - * - * by taking one factor and absorbing it into the others. Currently, this - * implementation takes the factor corresponding to the argument with largest - * absolute value and absorbs it into the others. - * - * Since this is only called internally by four_gammas. It is assumed that - * |u| >= |v| and |w| >= |x|. - */ - - /* The below implementation may incorrectly return finite results - * at poles of the gamma function. Handle these cases explicitly. */ - if ((u == std::trunc(u) && u <= 0) || (v == std::trunc(v) && v <= 0)) { - /* Return nan if numerator has pole. Diverges to +- infinity - * depending on direction so value is undefined. */ - return std::numeric_limits::quiet_NaN(); - } - if ((w == std::trunc(w) && w <= 0) || (x == std::trunc(x) && x <= 0)) { - // Return 0 if denominator has pole but not numerator. - return 0.0; - } - - double result = 1.0; - double ugh, vgh, wgh, xgh, u_prime, v_prime, w_prime, x_prime; - - if (u >= 0.5) { - result *= cephes::lanczos_sum_expg_scaled(u); - ugh = u + cephes::lanczos_g - 0.5; - u_prime = u; - } else { - result /= cephes::lanczos_sum_expg_scaled(1 - u) * std::sin(M_PI * u) * M_1_PI; - ugh = 0.5 - u + cephes::lanczos_g; - u_prime = 1 - u; - } - - if (v >= 0.5) { - result *= cephes::lanczos_sum_expg_scaled(v); - vgh = v + cephes::lanczos_g - 0.5; - v_prime = v; - } else { - result /= cephes::lanczos_sum_expg_scaled(1 - v) * std::sin(M_PI * v) * M_1_PI; - vgh = 0.5 - v + cephes::lanczos_g; - v_prime = 1 - v; - } - - if (w >= 0.5) { - result /= cephes::lanczos_sum_expg_scaled(w); - wgh = w + cephes::lanczos_g - 0.5; - w_prime = w; - } else { - result *= cephes::lanczos_sum_expg_scaled(1 - w) * std::sin(M_PI * w) * M_1_PI; - wgh = 0.5 - w + cephes::lanczos_g; - w_prime = 1 - w; - } - - if (x >= 0.5) { - result /= cephes::lanczos_sum_expg_scaled(x); - xgh = x + cephes::lanczos_g - 0.5; - x_prime = x; - } else { - result *= cephes::lanczos_sum_expg_scaled(1 - x) * std::sin(M_PI * x) * M_1_PI; - xgh = 0.5 - x + cephes::lanczos_g; - x_prime = 1 - x; - } - - if (std::abs(u) >= std::abs(w)) { - // u has greatest absolute value. Absorb ugh into the others. - if (std::abs((v_prime - u_prime) * (v - 0.5)) < 100 * ugh and v > 100) { - /* Special case where base is close to 1. Condition taken from - * Boost's beta function implementation. */ - result *= std::exp((v - 0.5) * std::log1p((v_prime - u_prime) / ugh)); - } else { - result *= std::pow(vgh / ugh, v - 0.5); - } - - if (std::abs((u_prime - w_prime) * (w - 0.5)) < 100 * wgh and u > 100) { - result *= std::exp((w - 0.5) * std::log1p((u_prime - w_prime) / wgh)); - } else { - result *= std::pow(ugh / wgh, w - 0.5); - } - - if (std::abs((u_prime - x_prime) * (x - 0.5)) < 100 * xgh and u > 100) { - result *= std::exp((x - 0.5) * std::log1p((u_prime - x_prime) / xgh)); - } else { - result *= std::pow(ugh / xgh, x - 0.5); - } - } else { - // w has greatest absolute value. Absorb wgh into the others. - if (std::abs((u_prime - w_prime) * (u - 0.5)) < 100 * wgh and u > 100) { - result *= std::exp((u - 0.5) * std::log1p((u_prime - w_prime) / wgh)); - } else { - result *= pow(ugh / wgh, u - 0.5); - } - if (std::abs((v_prime - w_prime) * (v - 0.5)) < 100 * wgh and v > 100) { - result *= std::exp((v - 0.5) * std::log1p((v_prime - w_prime) / wgh)); - } else { - result *= std::pow(vgh / wgh, v - 0.5); - } - if (std::abs((w_prime - x_prime) * (x - 0.5)) < 100 * xgh and x > 100) { - result *= std::exp((x - 0.5) * std::log1p((w_prime - x_prime) / xgh)); - } else { - result *= std::pow(wgh / xgh, x - 0.5); - } - } - // This exhausts all cases because we assume |u| >= |v| and |w| >= |x|. - - return result; - } - - XSF_HOST_DEVICE inline double four_gammas(double u, double v, double w, double x) { - double result; - - // Without loss of generality, ensure |u| >= |v| and |w| >= |x|. - if (std::abs(v) > std::abs(u)) { - std::swap(u, v); - } - if (std::abs(x) > std::abs(w)) { - std::swap(x, w); - } - /* Direct ratio tends to be more accurate for arguments in this range. Range - * chosen empirically based on the relevant benchmarks in - * scipy/special/_precompute/hyp2f1_data.py */ - if (std::abs(u) <= 100 && std::abs(v) <= 100 && std::abs(w) <= 100 && std::abs(x) <= 100) { - result = cephes::Gamma(u) * cephes::Gamma(v) * (cephes::rgamma(w) * cephes::rgamma(x)); - if (std::isfinite(result) && result != 0.0) { - return result; - } - } - result = four_gammas_lanczos(u, v, w, x); - if (std::isfinite(result) && result != 0.0) { - return result; - } - // If overflow or underflow, try again with logs. - result = std::exp(cephes::lgam(v) - cephes::lgam(x) + cephes::lgam(u) - cephes::lgam(w)); - result *= cephes::gammasgn(u) * cephes::gammasgn(w) * cephes::gammasgn(v) * cephes::gammasgn(x); - return result; - } - - class HypergeometricSeriesGenerator { - /* Maclaurin series for hyp2f1. - * - * Series is convergent for |z| < 1 but is only practical for numerical - * computation when |z| < 0.9. - */ - public: - XSF_HOST_DEVICE HypergeometricSeriesGenerator(double a, double b, double c, std::complex z) - : a_(a), b_(b), c_(c), z_(z), term_(1.0), k_(0) {} - - XSF_HOST_DEVICE std::complex operator()() { - std::complex output = term_; - term_ = term_ * (a_ + k_) * (b_ + k_) / ((k_ + 1) * (c_ + k_)) * z_; - ++k_; - return output; - } - - private: - double a_, b_, c_; - std::complex z_, term_; - std::uint64_t k_; - }; - - class Hyp2f1Transform1Generator { - /* 1 -z transformation of standard series.*/ - public: - XSF_HOST_DEVICE Hyp2f1Transform1Generator(double a, double b, double c, std::complex z) - : factor1_(four_gammas(c, c - a - b, c - a, c - b)), - factor2_(four_gammas(c, a + b - c, a, b) * std::pow(1.0 - z, c - a - b)), - generator1_(HypergeometricSeriesGenerator(a, b, a + b - c + 1, 1.0 - z)), - generator2_(HypergeometricSeriesGenerator(c - a, c - b, c - a - b + 1, 1.0 - z)) {} - - XSF_HOST_DEVICE std::complex operator()() { - return factor1_ * generator1_() + factor2_ * generator2_(); - } - - private: - std::complex factor1_, factor2_; - HypergeometricSeriesGenerator generator1_, generator2_; - }; - - class Hyp2f1Transform1LimitSeriesGenerator { - /* 1 - z transform in limit as c - a - b approaches an integer m. */ - public: - XSF_HOST_DEVICE Hyp2f1Transform1LimitSeriesGenerator(double a, double b, double m, std::complex z) - : d1_(xsf::digamma(a)), d2_(xsf::digamma(b)), d3_(xsf::digamma(1 + m)), - d4_(xsf::digamma(1.0)), a_(a), b_(b), m_(m), z_(z), log_1_z_(std::log(1.0 - z)), - factor_(cephes::rgamma(m + 1)), k_(0) {} - - XSF_HOST_DEVICE std::complex operator()() { - std::complex term_ = (d1_ + d2_ - d3_ - d4_ + log_1_z_) * factor_; - // Use digamma(x + 1) = digamma(x) + 1/x - d1_ += 1 / (a_ + k_); // d1 = digamma(a + k) - d2_ += 1 / (b_ + k_); // d2 = digamma(b + k) - d3_ += 1 / (1.0 + m_ + k_); // d3 = digamma(1 + m + k) - d4_ += 1 / (1.0 + k_); // d4 = digamma(1 + k) - factor_ *= (a_ + k_) * (b_ + k_) / ((k_ + 1.0) * (m_ + k_ + 1)) * (1.0 - z_); - ++k_; - return term_; - } - - private: - double d1_, d2_, d3_, d4_, a_, b_, m_; - std::complex z_, log_1_z_, factor_; - int k_; - }; - - class Hyp2f1Transform2Generator { - /* 1/z transformation of standard series.*/ - public: - XSF_HOST_DEVICE Hyp2f1Transform2Generator(double a, double b, double c, std::complex z) - : factor1_(four_gammas(c, b - a, b, c - a) * std::pow(-z, -a)), - factor2_(four_gammas(c, a - b, a, c - b) * std::pow(-z, -b)), - generator1_(HypergeometricSeriesGenerator(a, a - c + 1, a - b + 1, 1.0 / z)), - generator2_(HypergeometricSeriesGenerator(b, b - c + 1, b - a + 1, 1.0 / z)) {} - - XSF_HOST_DEVICE std::complex operator()() { - return factor1_ * generator1_() + factor2_ * generator2_(); - } - - private: - std::complex factor1_, factor2_; - HypergeometricSeriesGenerator generator1_, generator2_; - }; - - class Hyp2f1Transform2LimitSeriesGenerator { - /* 1/z transform in limit as a - b approaches a non-negative integer m. (Can swap a and b to - * handle the m a negative integer case. */ - public: - XSF_HOST_DEVICE Hyp2f1Transform2LimitSeriesGenerator(double a, double b, double c, double m, - std::complex z) - : d1_(xsf::digamma(1.0)), d2_(xsf::digamma(1 + m)), d3_(xsf::digamma(a)), - d4_(xsf::digamma(c - a)), a_(a), b_(b), c_(c), m_(m), z_(z), log_neg_z_(std::log(-z)), - factor_(xsf::cephes::poch(b, m) * xsf::cephes::poch(1 - c + b, m) * - xsf::cephes::rgamma(m + 1)), - k_(0) {} - - XSF_HOST_DEVICE std::complex operator()() { - std::complex term = (d1_ + d2_ - d3_ - d4_ + log_neg_z_) * factor_; - // Use digamma(x + 1) = digamma(x) + 1/x - d1_ += 1 / (1.0 + k_); // d1 = digamma(1 + k) - d2_ += 1 / (1.0 + m_ + k_); // d2 = digamma(1 + m + k) - d3_ += 1 / (a_ + k_); // d3 = digamma(a + k) - d4_ -= 1 / (c_ - a_ - k_ - 1); // d4 = digamma(c - a - k) - factor_ *= (b_ + m_ + k_) * (1 - c_ + b_ + m_ + k_) / ((k_ + 1) * (m_ + k_ + 1)) / z_; - ++k_; - return term; - } - - private: - double d1_, d2_, d3_, d4_, a_, b_, c_, m_; - std::complex z_, log_neg_z_, factor_; - std::uint64_t k_; - }; - - class Hyp2f1Transform2LimitSeriesCminusAIntGenerator { - /* 1/z transform in limit as a - b approaches a non-negative integer m, and c - a approaches - * a positive integer n. */ - public: - XSF_HOST_DEVICE Hyp2f1Transform2LimitSeriesCminusAIntGenerator(double a, double b, double c, double m, - double n, std::complex z) - : d1_(xsf::digamma(1.0)), d2_(xsf::digamma(1 + m)), d3_(xsf::digamma(a)), - d4_(xsf::digamma(n)), a_(a), b_(b), c_(c), m_(m), n_(n), z_(z), log_neg_z_(std::log(-z)), - factor_(xsf::cephes::poch(b, m) * xsf::cephes::poch(1 - c + b, m) * - xsf::cephes::rgamma(m + 1)), - k_(0) {} - - XSF_HOST_DEVICE std::complex operator()() { - std::complex term; - if (k_ < n_) { - term = (d1_ + d2_ - d3_ - d4_ + log_neg_z_) * factor_; - // Use digamma(x + 1) = digamma(x) + 1/x - d1_ += 1 / (1.0 + k_); // d1 = digamma(1 + k) - d2_ += 1 / (1 + m_ + k_); // d2 = digamma(1 + m + k) - d3_ += 1 / (a_ + k_); // d3 = digamma(a + k) - d4_ -= 1 / (n_ - k_ - 1); // d4 = digamma(c - a - k) - factor_ *= (b_ + m_ + k_) * (1 - c_ + b_ + m_ + k_) / ((k_ + 1) * (m_ + k_ + 1)) / z_; - ++k_; - return term; - } - if (k_ == n_) { - /* When c - a approaches a positive integer and k_ >= c - a = n then - * poch(1 - c + b + m + k) = poch(1 - c + a + k) = approaches zero and - * digamma(c - a - k) approaches a pole. However we can use the limit - * digamma(-n + epsilon) / gamma(-n + epsilon) -> (-1)**(n + 1) * (n+1)! as epsilon -> 0 - * to continue the series. - * - * poch(1 - c + b, m + k) = gamma(1 - c + b + m + k)/gamma(1 - c + b) - * - * If a - b is an integer and c - a is an integer, then a and b must both be integers, so assume - * a and b are integers and take the limit as c approaches an integer. - * - * gamma(1 - c + epsilon + a + k)/gamma(1 - c - epsilon + b) = - * (gamma(c + epsilon - b) / gamma(c + epsilon - a - k)) * - * (sin(pi * (c + epsilon - b)) / sin(pi * (c + epsilon - a - k))) (reflection principle) - * - * In the limit as epsilon goes to zero, the ratio of sines will approach - * (-1)**(a - b + k) = (-1)**(m + k) - * - * We may then replace - * - * poch(1 - c - epsilon + b, m + k)*digamma(c + epsilon - a - k) - * - * with - * - * (-1)**(a - b + k)*gamma(c + epsilon - b) * digamma(c + epsilon - a - k) / gamma(c + epsilon - a - k) - * - * and taking the limit epsilon -> 0 gives - * - * (-1)**(a - b + k) * gamma(c - b) * (-1)**(k + a - c + 1)(k + a - c)! - * = (-1)**(c - b - 1)*Gamma(k + a - c + 1) - */ - factor_ = std::pow(-1, m_ + n_) * xsf::binom(c_ - 1, b_ - 1) * - xsf::cephes::poch(c_ - a_ + 1, m_ - 1) / std::pow(z_, static_cast(k_)); - } - term = factor_; - factor_ *= (b_ + m_ + k_) * (k_ + a_ - c_ + 1) / ((k_ + 1) * (m_ + k_ + 1)) / z_; - ++k_; - return term; - } - - private: - double d1_, d2_, d3_, d4_, a_, b_, c_, m_, n_; - std::complex z_, log_neg_z_, factor_; - std::uint64_t k_; - }; - - class Hyp2f1Transform2LimitFinitePartGenerator { - /* Initial finite sum in limit as a - b approaches a non-negative integer m. The limiting series - * for the 1 - z transform also has an initial finite sum, but it is a standard hypergeometric - * series. */ - public: - XSF_HOST_DEVICE Hyp2f1Transform2LimitFinitePartGenerator(double b, double c, double m, - std::complex z) - : b_(b), c_(c), m_(m), z_(z), term_(cephes::Gamma(m) * cephes::rgamma(c - b)), k_(0) {} - - XSF_HOST_DEVICE std::complex operator()() { - std::complex output = term_; - term_ = term_ * (b_ + k_) * (c_ - b_ - k_ - 1) / ((k_ + 1) * (m_ - k_ - 1)) / z_; - ++k_; - return output; - } - - private: - double b_, c_, m_; - std::complex z_, term_; - std::uint64_t k_; - }; - - class LopezTemmeSeriesGenerator { - /* Lopez-Temme Series for Gaussian hypergeometric function [4]. - * - * Converges for all z with real(z) < 1, including in the regions surrounding - * the points exp(+- i*pi/3) that are not covered by any of the standard - * transformations. - */ - public: - XSF_HOST_DEVICE LopezTemmeSeriesGenerator(double a, double b, double c, std::complex z) - : n_(0), a_(a), b_(b), c_(c), phi_previous_(1.0), phi_(1 - 2 * b / c), z_(z), Z_(a * z / (z - 2.0)) {} - - XSF_HOST_DEVICE std::complex operator()() { - if (n_ == 0) { - ++n_; - return 1.0; - } - if (n_ > 1) { // Update phi and Z for n>=2 - double new_phi = ((n_ - 1) * phi_previous_ - (2.0 * b_ - c_) * phi_) / (c_ + (n_ - 1)); - phi_previous_ = phi_; - phi_ = new_phi; - Z_ = Z_ * z_ / (z_ - 2.0) * ((a_ + (n_ - 1)) / n_); - } - ++n_; - return Z_ * phi_; - } - - private: - std::uint64_t n_; - double a_, b_, c_, phi_previous_, phi_; - std::complex z_, Z_; - }; - - XSF_HOST_DEVICE std::complex hyp2f1_transform1_limiting_case(double a, double b, double c, double m, - std::complex z) { - /* 1 - z transform in limiting case where c - a - b approaches an integer m. */ - std::complex result = 0.0; - if (m >= 0) { - if (m != 0) { - auto series_generator = HypergeometricSeriesGenerator(a, b, 1 - m, 1.0 - z); - result += four_gammas(m, c, a + m, b + m) * series_eval_fixed_length(series_generator, - std::complex{0.0, 0.0}, - static_cast(m)); - } - std::complex prefactor = std::pow(-1.0, m + 1) * xsf::cephes::Gamma(c) / - (xsf::cephes::Gamma(a) * xsf::cephes::Gamma(b)) * - std::pow(1.0 - z, m); - auto series_generator = Hyp2f1Transform1LimitSeriesGenerator(a + m, b + m, m, z); - result += prefactor * series_eval(series_generator, std::complex{0.0, 0.0}, hyp2f1_EPS, - hyp2f1_MAXITER, "hyp2f1"); - return result; - } else { - result = four_gammas(-m, c, a, b) * std::pow(1.0 - z, m); - auto series_generator1 = HypergeometricSeriesGenerator(a + m, b + m, 1 + m, 1.0 - z); - result *= series_eval_fixed_length(series_generator1, std::complex{0.0, 0.0}, - static_cast(-m)); - double prefactor = std::pow(-1.0, m + 1) * xsf::cephes::Gamma(c) * - (xsf::cephes::rgamma(a + m) * xsf::cephes::rgamma(b + m)); - auto series_generator2 = Hyp2f1Transform1LimitSeriesGenerator(a, b, -m, z); - result += prefactor * series_eval(series_generator2, std::complex{0.0, 0.0}, hyp2f1_EPS, - hyp2f1_MAXITER, "hyp2f1"); - return result; - } - } - - XSF_HOST_DEVICE std::complex hyp2f1_transform2_limiting_case(double a, double b, double c, double m, - std::complex z) { - /* 1 / z transform in limiting case where a - b approaches a non-negative integer m. Negative integer case - * can be handled by swapping a and b. */ - auto series_generator1 = Hyp2f1Transform2LimitFinitePartGenerator(b, c, m, z); - std::complex result = cephes::Gamma(c) * cephes::rgamma(a) * std::pow(-z, -b); - result *= - series_eval_fixed_length(series_generator1, std::complex{0.0, 0.0}, static_cast(m)); - std::complex prefactor = cephes::Gamma(c) * (cephes::rgamma(a) * cephes::rgamma(c - b) * std::pow(-z, -a)); - double n = c - a; - if (abs(n - std::round(n)) < hyp2f1_EPS) { - auto series_generator2 = Hyp2f1Transform2LimitSeriesCminusAIntGenerator(a, b, c, m, n, z); - result += prefactor * series_eval(series_generator2, std::complex{0.0, 0.0}, hyp2f1_EPS, - hyp2f1_MAXITER, "hyp2f1"); - return result; - } - auto series_generator2 = Hyp2f1Transform2LimitSeriesGenerator(a, b, c, m, z); - result += prefactor * - series_eval(series_generator2, std::complex{0.0, 0.0}, hyp2f1_EPS, hyp2f1_MAXITER, "hyp2f1"); - return result; - } - -} // namespace detail - -XSF_HOST_DEVICE inline std::complex hyp2f1(double a, double b, double c, std::complex z) { - /* Special Cases - * ----------------------------------------------------------------------- - * Takes constant value 1 when a = 0 or b = 0, even if c is a non-positive - * integer. This follows mpmath. */ - if (a == 0 || b == 0) { - return 1.0; - } - double z_abs = std::abs(z); - // Equals 1 when z i 0, unless c is 0. - if (z_abs == 0) { - if (c != 0) { - return 1.0; - } else { - // Returning real part NAN and imaginary part 0 follows mpmath. - return std::complex{std::numeric_limits::quiet_NaN(), 0}; - } - } - bool a_neg_int = a == std::trunc(a) && a < 0; - bool b_neg_int = b == std::trunc(b) && b < 0; - bool c_non_pos_int = c == std::trunc(c) and c <= 0; - /* Diverges when c is a non-positive integer unless a is an integer with - * c <= a <= 0 or b is an integer with c <= b <= 0, (or z equals 0 with - * c != 0) Cases z = 0, a = 0, or b = 0 have already been handled. We follow - * mpmath in handling the degenerate cases where any of a, b, c are - * non-positive integers. See [3] for a treatment of degenerate cases. */ - if (c_non_pos_int && !((a_neg_int && c <= a && a < 0) || (b_neg_int && c <= b && b < 0))) { - return std::complex{std::numeric_limits::infinity(), 0}; - } - /* Reduces to a polynomial when a or b is a negative integer. - * If a and b are both negative integers, we take care to terminate - * the series at a or b of smaller magnitude. This is to ensure proper - * handling of situations like a < c < b <= 0, a, b, c all non-positive - * integers, where terminating at a would lead to a term of the form 0 / 0. */ - double max_degree; - if (a_neg_int || b_neg_int) { - if (a_neg_int && b_neg_int) { - max_degree = a > b ? std::abs(a) : std::abs(b); - } else if (a_neg_int) { - max_degree = std::abs(a); - } else { - max_degree = std::abs(b); - } - if (max_degree <= (double) UINT64_MAX) { - auto series_generator = detail::HypergeometricSeriesGenerator(a, b, c, z); - return detail::series_eval_fixed_length(series_generator, std::complex{0.0, 0.0}, max_degree + 1); - } else { - set_error("hyp2f1", SF_ERROR_NO_RESULT, NULL); - return std::complex{std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN()}; - } - } - // Kummer's Theorem for z = -1; c = 1 + a - b (DLMF 15.4.26) - if (std::abs(z + 1.0) < detail::hyp2f1_EPS && std::abs(1 + a - b - c) < detail::hyp2f1_EPS && !c_non_pos_int) { - return detail::four_gammas(a - b + 1, 0.5 * a + 1, a + 1, 0.5 * a - b + 1); - } - std::complex result; - bool c_minus_a_neg_int = c - a == std::trunc(c - a) && c - a < 0; - bool c_minus_b_neg_int = c - b == std::trunc(c - b) && c - b < 0; - /* If one of c - a or c - b is a negative integer, reduces to evaluating - * a polynomial through an Euler hypergeometric transformation. - * (DLMF 15.8.1) */ - if (c_minus_a_neg_int || c_minus_b_neg_int) { - max_degree = c_minus_b_neg_int ? std::abs(c - b) : std::abs(c - a); - if (max_degree <= (double) UINT64_MAX) { - result = std::pow(1.0 - z, c - a - b); - auto series_generator = detail::HypergeometricSeriesGenerator(c - a, c - b, c, z); - result *= - detail::series_eval_fixed_length(series_generator, std::complex{0.0, 0.0}, max_degree + 2); - return result; - } else { - set_error("hyp2f1", SF_ERROR_NO_RESULT, NULL); - return std::complex{std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN()}; - } - } - /* Diverges as real(z) -> 1 when c <= a + b. - * Todo: Actually check for overflow instead of using a fixed tolerance for - * all parameter combinations like in the Fortran original. */ - if (std::abs(1 - z.real()) < detail::hyp2f1_EPS && z.imag() == 0 && c - a - b <= 0 && !c_non_pos_int) { - return std::complex{std::numeric_limits::infinity(), 0}; - } - // Gauss's Summation Theorem for z = 1; c - a - b > 0 (DLMF 15.4.20). - if (z == 1.0 && c - a - b > 0 && !c_non_pos_int) { - return detail::four_gammas(c, c - a - b, c - a, c - b); - } - /* |z| < 0, z.real() >= 0. Use the Maclaurin Series. - * ----------------------------------------------------------------------- - * Apply Euler Hypergeometric Transformation (DLMF 15.8.1) to reduce - * size of a and b if possible. We follow Zhang and Jin's - * implementation [1] although there is very likely a better heuristic - * to determine when this transformation should be applied. As it - * stands, this hurts precision in some cases. */ - if (z_abs < 0.9 && z.real() >= 0) { - if (c - a < a && c - b < b) { - result = std::pow(1.0 - z, c - a - b); - auto series_generator = detail::HypergeometricSeriesGenerator(c - a, c - b, c, z); - result *= detail::series_eval(series_generator, std::complex{0.0, 0.0}, detail::hyp2f1_EPS, - detail::hyp2f1_MAXITER, "hyp2f1"); - return result; - } - auto series_generator = detail::HypergeometricSeriesGenerator(a, b, c, z); - return detail::series_eval(series_generator, std::complex{0.0, 0.0}, detail::hyp2f1_EPS, - detail::hyp2f1_MAXITER, "hyp2f1"); - } - /* Points near exp(iπ/3), exp(-iπ/3) not handled by any of the standard - * transformations. Use series of López and Temme [5]. These regions - * were not correctly handled by Zhang and Jin's implementation. - * -------------------------------------------------------------------------*/ - if (0.9 <= z_abs && z_abs < 1.1 && std::abs(1.0 - z) >= 0.9 && z.real() >= 0) { - /* This condition for applying Euler Transformation (DLMF 15.8.1) - * was determined empirically to work better for this case than that - * used in Zhang and Jin's implementation for |z| < 0.9, - * real(z) >= 0. */ - if ((c - a <= a && c - b < b) || (c - a < a && c - b <= b)) { - auto series_generator = detail::LopezTemmeSeriesGenerator(c - a, c - b, c, z); - result = std::pow(1.0 - 0.5 * z, a - c); // Lopez-Temme prefactor - result *= detail::series_eval(series_generator, std::complex{0.0, 0.0}, detail::hyp2f1_EPS, - detail::hyp2f1_MAXITER, "hyp2f1"); - return std::pow(1.0 - z, c - a - b) * result; // Euler transform prefactor. - } - auto series_generator = detail::LopezTemmeSeriesGenerator(a, b, c, z); - result = detail::series_eval(series_generator, std::complex{0.0, 0.0}, detail::hyp2f1_EPS, - detail::hyp2f1_MAXITER, "hyp2f1"); - return std::pow(1.0 - 0.5 * z, -a) * result; // Lopez-Temme prefactor. - } - /* z/(z - 1) transformation (DLMF 15.8.1). Avoids cancellation issues that - * occur with Maclaurin series for real(z) < 0. - * -------------------------------------------------------------------------*/ - if (z_abs < 1.1 && z.real() < 0) { - if (0 < b && b < a && a < c) { - std::swap(a, b); - } - auto series_generator = detail::HypergeometricSeriesGenerator(a, c - b, c, z / (z - 1.0)); - return std::pow(1.0 - z, -a) * detail::series_eval(series_generator, std::complex{0.0, 0.0}, - detail::hyp2f1_EPS, detail::hyp2f1_MAXITER, "hyp2f1"); - } - /* 1 - z transformation (DLMF 15.8.4). */ - if (0.9 <= z_abs && z_abs < 1.1) { - if (std::abs(c - a - b - std::round(c - a - b)) < detail::hyp2f1_EPS) { - // Removable singularity when c - a - b is an integer. Need to use limiting formula. - double m = std::round(c - a - b); - return detail::hyp2f1_transform1_limiting_case(a, b, c, m, z); - } - auto series_generator = detail::Hyp2f1Transform1Generator(a, b, c, z); - return detail::series_eval(series_generator, std::complex{0.0, 0.0}, detail::hyp2f1_EPS, - detail::hyp2f1_MAXITER, "hyp2f1"); - } - /* 1/z transformation (DLMF 15.8.2). */ - if (std::abs(a - b - std::round(a - b)) < detail::hyp2f1_EPS) { - if (b > a) { - std::swap(a, b); - } - double m = std::round(a - b); - return detail::hyp2f1_transform2_limiting_case(a, b, c, m, z); - } - auto series_generator = detail::Hyp2f1Transform2Generator(a, b, c, z); - return detail::series_eval(series_generator, std::complex{0.0, 0.0}, detail::hyp2f1_EPS, - detail::hyp2f1_MAXITER, "hyp2f1"); -} - -XSF_HOST_DEVICE inline std::complex hyp2f1(float a, float b, float c, std::complex x) { - return static_cast>(hyp2f1(static_cast(a), static_cast(b), - static_cast(c), static_cast>(x))); -} - -XSF_HOST_DEVICE inline double hyp2f1(double a, double b, double c, double x) { return cephes::hyp2f1(a, b, c, x); } - -XSF_HOST_DEVICE inline float hyp2f1(float a, float b, float c, float x) { - return hyp2f1(static_cast(a), static_cast(b), static_cast(c), static_cast(x)); -} - -} // namespace xsf diff --git a/scipy/special/xsf/iv_ratio.h b/scipy/special/xsf/iv_ratio.h deleted file mode 100644 index e5dc871bd003..000000000000 --- a/scipy/special/xsf/iv_ratio.h +++ /dev/null @@ -1,173 +0,0 @@ -// Numerically stable computation of iv(v+1, x) / iv(v, x) - -#pragma once - -#include "config.h" -#include "tools.h" -#include "error.h" -#include "cephes/dd_real.h" - -namespace xsf { - -/* Generates the "tail" of Perron's continued fraction for iv(v,x)/iv(v-1,x). - * - * The Perron continued fraction is studied in [1]. It is given by - * - * iv(v, x) x -(2v+1)x -(2v+3)x -(2v+5)x - * R := --------- = ------ ---------- ---------- ---------- ... - * iv(v-1,x) x+2v + 2(v+x)+1 + 2(v+x)+2 + 2(v+x)+3 + - * - * Given a suitable constant c, the continued fraction may be rearranged - * into the following form to avoid premature floating point overflow: - * - * xc -(2vc+c)(xc) -(2vc+3c)(xc) -(2vc+5c)(xc) - * R = -----, fc = 2vc + ------------ ------------- ------------- ... - * xc+fc 2(vc+xc)+c + 2(vc+xc)+2c + 2(vc+xc)+3c + - * - * This class generates the fractions of fc after 2vc. - * - * [1] Gautschi, W. and Slavik, J. (1978). "On the computation of modified - * Bessel function ratios." Mathematics of Computation, 32(143):865-875. - */ -template -struct IvRatioCFTailGenerator { - - XSF_HOST_DEVICE IvRatioCFTailGenerator(T vc, T xc, T c) noexcept { - a0_ = -(2*vc-c)*xc; - as_ = -2*c*xc; - b0_ = 2*(vc+xc); - bs_ = c; - k_ = 0; - } - - XSF_HOST_DEVICE std::pair operator()() noexcept { - using std::fma; - ++k_; - return {fma(static_cast(k_), as_, a0_), - fma(static_cast(k_), bs_, b0_)}; - } - -private: - T a0_, as_; // a[k] == a0 + as*k, k >= 1 - T b0_, bs_; // b[k] == b0 + bs*k, k >= 1 - std::uint64_t k_; // current index -}; - -// Computes f(v, x) using Perron's continued fraction. -// -// T specifies the working type. This allows the function to perform -// calculations in a higher precision, such as double-double, even if -// the return type is hardcoded to be double. -template -XSF_HOST_DEVICE inline std::pair -_iv_ratio_cf(double v, double x, bool complement) { - - int e; - std::frexp(std::fmax(v, x), &e); - T c = T(std::ldexp(1, 2-e)); // rescaling multiplier - T vc = v * c; - T xc = x * c; - - IvRatioCFTailGenerator cf(vc, xc, c); - auto [fc, terms] = detail::series_eval_kahan( - detail::continued_fraction_series(cf), - T(std::numeric_limits::epsilon()), - 1000, - 2*vc); - - T ret = (complement ? fc : xc) / (xc + fc); - return {static_cast(ret), terms}; -} - -XSF_HOST_DEVICE inline double iv_ratio(double v, double x) { - - if (std::isnan(v) || std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - if (v < 0.5 || x < 0) { - set_error("iv_ratio", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - if (std::isinf(v) && std::isinf(x)) { - // There is not a unique limit as both v and x tends to infinity. - set_error("iv_ratio", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - if (x == 0.0) { - return x; // keep sign of x because iv_ratio is an odd function - } - if (std::isinf(v)) { - return 0.0; - } - if (std::isinf(x)) { - return 1.0; - } - - auto [ret, terms] = _iv_ratio_cf(v, x, false); - if (terms == 0) { // failed to converge; should not happen - set_error("iv_ratio", SF_ERROR_NO_RESULT, NULL); - return std::numeric_limits::quiet_NaN(); - } - return ret; -} - -XSF_HOST_DEVICE inline float iv_ratio(float v, float x) { - return iv_ratio(static_cast(v), static_cast(x)); -} - -XSF_HOST_DEVICE inline double iv_ratio_c(double v, double x) { - - if (std::isnan(v) || std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - if (v < 0.5 || x < 0) { - set_error("iv_ratio_c", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - if (std::isinf(v) && std::isinf(x)) { - // There is not a unique limit as both v and x tends to infinity. - set_error("iv_ratio_c", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - if (x == 0.0) { - return 1.0; - } - if (std::isinf(v)) { - return 1.0; - } - if (std::isinf(x)) { - return 0.0; - } - - if (v >= 1) { - // Numerical experiments show that evaluating the Perron c.f. - // in double precision is sufficiently accurate if v >= 1. - auto [ret, terms] = _iv_ratio_cf(v, x, true); - if (terms == 0) { // failed to converge; should not happen - set_error("iv_ratio_c", SF_ERROR_NO_RESULT, NULL); - return std::numeric_limits::quiet_NaN(); - } - return ret; - } else if (v > 0.5) { - // double-double arithmetic is needed for 0.5 < v < 1 to - // achieve relative error on the scale of machine precision. - using cephes::detail::double_double; - auto [ret, terms] = _iv_ratio_cf(v, x, true); - if (terms == 0) { // failed to converge; should not happen - set_error("iv_ratio_c", SF_ERROR_NO_RESULT, NULL); - return std::numeric_limits::quiet_NaN(); - } - return ret; - } else { - // The previous branch (v > 0.5) also works for v == 0.5, but - // the closed-form formula "1 - tanh(x)" is more efficient. - double t = std::exp(-2*x); - return (2 * t) / (1 + t); - } -} - -XSF_HOST_DEVICE inline float iv_ratio_c(float v, float x) { - return iv_ratio_c(static_cast(v), static_cast(x)); -} - -} // namespace xsf diff --git a/scipy/special/xsf/kelvin.h b/scipy/special/xsf/kelvin.h deleted file mode 100644 index 7da98d0f3624..000000000000 --- a/scipy/special/xsf/kelvin.h +++ /dev/null @@ -1,424 +0,0 @@ -#pragma once - -#include "specfun.h" - -namespace xsf { - -namespace detail { - - template - void klvna(T x, T *ber, T *bei, T *ger, T *gei, T *der, T *dei, T *her, T *hei) { - - // ====================================================== - // Purpose: Compute Kelvin functions ber x, bei x, ker x - // and kei x, and their derivatives ( x > 0 ) - // Input : x --- Argument of Kelvin functions - // Output: BER --- ber x - // BEI --- bei x - // GER --- ker x - // GEI --- kei x - // DER --- ber'x - // DEI --- bei'x - // HER --- ker'x - // HEI --- kei'x - // ================================================ - - int k, km, m; - T gs, r, x2, x4, pp1, pn1, qp1, qn1, r1, pp0, pn0, qp0, qn0, r0, fac, xt, cs, ss, xd, xe1, xe2, xc1, xc2, cp0, - cn0, sp0, sn0, rc, rs; - const T pi = 3.141592653589793; - const T el = 0.5772156649015329; - const T eps = 1.0e-15; - - if (x == 0.0) { - *ber = 1.0; - *bei = 0.0; - *ger = 1.0e+300; - *gei = -0.25 * pi; - *der = 0.0; - *dei = 0.0; - *her = -1.0e+300; - *hei = 0.0; - return; - } - - x2 = 0.25 * x * x; - x4 = x2 * x2; - - if (fabs(x) < 10.0) { - *ber = 1.0; - r = 1.0; - for (m = 1; m <= 60; m++) { - r = -0.25 * r / (m * m) / pow((2.0 * m - 1.0), 2) * x4; - *ber += r; - if (fabs(r) < fabs(*ber) * eps) { - break; - } - } - - *bei = x2; - r = x2; - for (m = 1; m <= 60; m++) { - r = -0.25 * r / (m * m) / pow((2.0 * m + 1.0), 2) * x4; - *bei += r; - if (fabs(r) < fabs(*bei) * eps) { - break; - } - } - - *ger = -(log(x / 2.0) + el) * (*ber) + 0.25 * pi * (*bei); - - r = 1.0; - gs = 0.0; - for (m = 1; m <= 60; m++) { - r = -0.25 * r / (m * m) / pow((2.0 * m - 1.0), 2) * x4; - gs = gs + 1.0 / (2.0 * m - 1.0) + 1.0 / (2.0 * m); - *ger += r * gs; - if (fabs(r * gs) < fabs(*ger) * eps) { - break; - } - } - - *gei = x2 - (log(x / 2.0) + el) * (*bei) - 0.25 * pi * (*ber); - - r = x2; - gs = 1.0; - for (m = 1; m <= 60; m++) { - r = -0.25 * r / (m * m) / pow((2.0 * m + 1.0), 2) * x4; - gs = gs + 1.0 / (2.0 * m) + 1.0 / (2.0 * m + 1.0); - *gei += r * gs; - if (fabs(r * gs) < fabs(*gei) * eps) { - break; - } - } - - *der = -0.25 * x * x2; - r = *der; - for (m = 1; m <= 60; m++) { - r = -0.25 * r / m / (m + 1.0) / pow((2.0 * m + 1.0), 2) * x4; - *der += r; - if (fabs(r) < fabs(*der) * eps) { - break; - } - } - - *dei = 0.5 * x; - r = *dei; - for (m = 1; m <= 60; m++) { - r = -0.25 * r / (m * m) / (2.0 * m - 1.0) / (2.0 * m + 1.0) * x4; - *dei += r; - if (fabs(r) < fabs(*dei) * eps) { - break; - } - } - - r = -0.25 * x * x2; - gs = 1.5; - *her = 1.5 * r - (*ber) / x - (log(x / 2.0) + el) * (*der) + 0.25 * pi * (*dei); - for (m = 1; m <= 60; m++) { - r = -0.25 * r / m / (m + 1.0) / pow((2.0 * m + 1.0), 2) * x4; - gs = gs + 1.0 / (2.0 * m + 1.0) + 1.0 / (2 * m + 2.0); - *her += r * gs; - if (fabs(r * gs) < fabs(*her) * eps) { - break; - } - } - - r = 0.5 * x; - gs = 1.0; - *hei = 0.5 * x - (*bei) / x - (log(x / 2.0) + el) * (*dei) - 0.25 * pi * (*der); - for (m = 1; m <= 60; m++) { - r = -0.25 * r / (m * m) / (2 * m - 1.0) / (2 * m + 1.0) * x4; - gs = gs + 1.0 / (2.0 * m) + 1.0 / (2 * m + 1.0); - *hei += r * gs; - if (fabs(r * gs) < fabs(*hei) * eps) { - return; - } - } - } else { - pp0 = 1.0; - pn0 = 1.0; - qp0 = 0.0; - qn0 = 0.0; - r0 = 1.0; - km = 18; - if (fabs(x) >= 40.0) - km = 10; - fac = 1.0; - for (k = 1; k <= km; k++) { - fac = -fac; - xt = 0.25 * k * pi - trunc(0.125 * k) * 2.0 * pi; - cs = cos(xt); - ss = sin(xt); - r0 = 0.125 * r0 * pow((2.0 * k - 1.0), 2) / k / x; - rc = r0 * cs; - rs = r0 * ss; - pp0 += rc; - pn0 += fac * rc; - qp0 += rs; - qn0 += fac * rs; - } - - xd = x / sqrt(2.0); - xe1 = exp(xd); - xe2 = exp(-xd); - xc1 = 1.0 / sqrt(2.0 * pi * x); - xc2 = sqrt(0.5 * pi / x); - cp0 = cos(xd + 0.125 * pi); - cn0 = cos(xd - 0.125 * pi); - sp0 = sin(xd + 0.125 * pi); - sn0 = sin(xd - 0.125 * pi); - - *ger = xc2 * xe2 * (pn0 * cp0 - qn0 * sp0); - *gei = xc2 * xe2 * (-pn0 * sp0 - qn0 * cp0); - *ber = xc1 * xe1 * (pp0 * cn0 + qp0 * sn0) - (*gei) / pi; - *bei = xc1 * xe1 * (pp0 * sn0 - qp0 * cn0) + (*ger) / pi; - - pp1 = 1.0; - pn1 = 1.0; - qp1 = 0.0; - qn1 = 0.0; - r1 = 1.0; - fac = 1.0; - for (int k = 1; k <= km; k++) { - fac = -fac; - xt = 0.25 * k * pi - (int) (0.125 * k) * 2.0 * pi; - cs = cos(xt); - ss = sin(xt); - r1 = 0.125 * r1 * (4.0 - pow(2.0 * k - 1.0, 2)) / (k * x); - rc = r1 * cs; - rs = r1 * ss; - pp1 += fac * rc; - pn1 += rc; - qp1 += fac * rs; - qn1 += rs; - } - *her = xc2 * xe2 * (-pn1 * cn0 + qn1 * sn0); - *hei = xc2 * xe2 * (pn1 * sn0 + qn1 * cn0); - *der = xc1 * xe1 * (pp1 * cp0 + qp1 * sp0) - (*hei) / pi; - *dei = xc1 * xe1 * (pp1 * sp0 - qp1 * cp0) + (*her) / pi; - } - return; - } - -} // namespace detail - -template -T ber(T x) { - std::complex Be; - T ber, bei, ger, gei, der, dei, her, hei; - - if (x < 0) { - x = -x; - } - - detail::klvna(x, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - Be.real(ber); - Be.imag(bei); - SPECFUN_ZCONVINF("ber", Be); - return Be.real(); -} - -template -T bei(T x) { - std::complex Be; - T ber, bei, ger, gei, der, dei, her, hei; - - if (x < 0) { - x = -x; - } - - detail::klvna(x, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - Be.real(ber); - Be.imag(bei); - SPECFUN_ZCONVINF("bei", Be); - return Be.imag(); -} - -template -T ker(T x) { - std::complex Ke; - T ber, bei, ger, gei, der, dei, her, hei; - if (x < 0) { - return std::numeric_limits::quiet_NaN(); - } - - detail::klvna(x, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - Ke.real(ger); - Ke.imag(gei); - SPECFUN_ZCONVINF("ker", Ke); - return Ke.real(); -} - -template -T kei(T x) { - std::complex Ke; - T ber, bei, ger, gei, der, dei, her, hei; - if (x < 0) { - return std::numeric_limits::quiet_NaN(); - } - - detail::klvna(x, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - Ke.real(ger); - Ke.imag(gei); - SPECFUN_ZCONVINF("kei", Ke); - return Ke.imag(); -} - -template -T berp(T x) { - std::complex Bep; - T ber, bei, ger, gei, der, dei, her, hei; - int flag = 0; - - if (x < 0) { - x = -x; - flag = 1; - } - - detail::klvna(x, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - Bep.real(der); - Bep.imag(dei); - SPECFUN_ZCONVINF("berp", Bep); - if (flag) { - return -Bep.real(); - } - return Bep.real(); -} - -template -T beip(T x) { - std::complex Bep; - T ber, bei, ger, gei, der, dei, her, hei; - int flag = 0; - - if (x < 0) { - x = -x; - flag = 1; - } - detail::klvna(x, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - Bep.real(der); - Bep.imag(dei); - SPECFUN_ZCONVINF("beip", Bep); - if (flag) { - return -Bep.imag(); - } - return Bep.imag(); -} - -template -T kerp(T x) { - std::complex Kep; - T ber, bei, ger, gei, der, dei, her, hei; - - if (x < 0) { - return std::numeric_limits::quiet_NaN(); - } - detail::klvna(x, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - Kep.real(her); - Kep.imag(hei); - SPECFUN_ZCONVINF("kerp", Kep); - return Kep.real(); -} - -template -T keip(T x) { - std::complex Kep; - T ber, bei, ger, gei, der, dei, her, hei; - - if (x < 0) { - return std::numeric_limits::quiet_NaN(); - } - detail::klvna(x, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - Kep.real(her); - Kep.imag(hei); - SPECFUN_ZCONVINF("keip", Kep); - return Kep.imag(); -} - -template -void kelvin(T x, std::complex &Be, std::complex &Ke, std::complex &Bep, std::complex &Kep) { - int flag = 0; - T ber, bei, ger, gei, der, dei, her, hei; - if (x < 0) { - x = -x; - flag = 1; - } - - detail::klvna(x, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - Be.real(ber); - Be.imag(bei); - Ke.real(ger); - Ke.imag(gei); - Bep.real(der); - Bep.imag(dei); - Kep.real(her); - Kep.imag(hei); - - SPECFUN_ZCONVINF("klvna", Be); - SPECFUN_ZCONVINF("klvna", Ke); - SPECFUN_ZCONVINF("klvna", Bep); - SPECFUN_ZCONVINF("klvna", Kep); - if (flag) { - Bep.real(-Bep.real()); - Bep.imag(-Bep.imag()); - Ke.real(std::numeric_limits::quiet_NaN()); - Ke.imag(std::numeric_limits::quiet_NaN()); - Kep.real(std::numeric_limits::quiet_NaN()); - Kep.imag(std::numeric_limits::quiet_NaN()); - } -} - -inline void klvnzo(int nt, int kd, double *zo) { - - // ==================================================== - // Purpose: Compute the zeros of Kelvin functions - // Input : NT --- Total number of zeros - // KD --- Function code - // KD=1 to 8 for ber x, bei x, ker x, kei x, - // ber'x, bei'x, ker'x and kei'x, - // respectively. - // Output: ZO(M) --- the M-th zero of Kelvin function - // for code KD - // Routine called: - // KLVNA for computing Kelvin functions and - // their derivatives - // ==================================================== - - double ber, bei, ger, gei, der, dei, her, hei; - double rt0[9] = {0.0, 2.84891, 5.02622, 1.71854, 3.91467, 6.03871, 3.77268, 2.66584, 4.93181}; - double rt = rt0[kd]; - - for (int m = 1; m <= nt; m++) { - while (1) { - detail::klvna(rt, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - if (kd == 1) { - rt -= ber / der; - } else if (kd == 2) { - rt -= bei / dei; - } else if (kd == 3) { - rt -= ger / her; - } else if (kd == 4) { - rt -= gei / hei; - } else if (kd == 5) { - rt -= der / (-bei - der / rt); - } else if (kd == 6) { - rt -= dei / (ber - dei / rt); - } else if (kd == 7) { - rt -= her / (-gei - her / rt); - } else { - rt -= hei / (ger - hei / rt); - } - - if (fabs(rt - rt0[kd]) <= 5e-10) { - break; - } else { - rt0[kd] = rt; - } - } - zo[m - 1] = rt; - rt += 4.44; - } -} - -} // namespace xsf diff --git a/scipy/special/xsf/lambertw.h b/scipy/special/xsf/lambertw.h deleted file mode 100644 index 9eb1882eaec4..000000000000 --- a/scipy/special/xsf/lambertw.h +++ /dev/null @@ -1,150 +0,0 @@ -/* Translated from Cython into C++ by SciPy developers in 2023. - * Original header with Copyright information appears below. - */ - -/* Implementation of the Lambert W function [1]. Based on MPMath - * Implementation [2], and documentation [3]. - * - * Copyright: Yosef Meller, 2009 - * Author email: mellerf@netvision.net.il - * - * Distributed under the same license as SciPy - * - * - * References: - * [1] On the Lambert W function, Adv. Comp. Math. 5 (1996) 329-359, - * available online: https://web.archive.org/web/20230123211413/https://cs.uwaterloo.ca/research/tr/1993/03/W.pdf - * [2] mpmath source code, - https://github.com/mpmath/mpmath/blob/c5939823669e1bcce151d89261b802fe0d8978b4/mpmath/functions/functions.py#L435-L461 - * [3] - https://web.archive.org/web/20230504171447/https://mpmath.org/doc/current/functions/powers.html#lambert-w-function - * - - * TODO: use a series expansion when extremely close to the branch point - * at `-1/e` and make sure that the proper branch is chosen there. - */ - -#pragma once - -#include "config.h" -#include "error.h" -#include "evalpoly.h" - -namespace xsf { -constexpr double EXPN1 = 0.36787944117144232159553; // exp(-1) -constexpr double OMEGA = 0.56714329040978387299997; // W(1, 0) - -namespace detail { - XSF_HOST_DEVICE inline std::complex lambertw_branchpt(std::complex z) { - // Series for W(z, 0) around the branch point; see 4.22 in [1]. - double coeffs[] = {-1.0 / 3.0, 1.0, -1.0}; - std::complex p = std::sqrt(2.0 * (M_E * z + 1.0)); - - return cevalpoly(coeffs, 2, p); - } - - XSF_HOST_DEVICE inline std::complex lambertw_pade0(std::complex z) { - // (3, 2) Pade approximation for W(z, 0) around 0. - double num[] = {12.85106382978723404255, 12.34042553191489361902, 1.0}; - double denom[] = {32.53191489361702127660, 14.34042553191489361702, 1.0}; - - /* This only gets evaluated close to 0, so we don't need a more - * careful algorithm that avoids overflow in the numerator for - * large z. */ - return z * cevalpoly(num, 2, z) / cevalpoly(denom, 2, z); - } - - XSF_HOST_DEVICE inline std::complex lambertw_asy(std::complex z, long k) { - /* Compute the W function using the first two terms of the - * asymptotic series. See 4.20 in [1]. - */ - std::complex w = std::log(z) + 2.0 * M_PI * k * std::complex(0, 1); - return w - std::log(w); - } - -} // namespace detail - -XSF_HOST_DEVICE inline std::complex lambertw(std::complex z, long k, double tol) { - double absz; - std::complex w; - std::complex ew, wew, wewz, wn; - - if (std::isnan(z.real()) || std::isnan(z.imag())) { - return z; - } - if (z.real() == std::numeric_limits::infinity()) { - return z + 2.0 * M_PI * k * std::complex(0, 1); - } - if (z.real() == -std::numeric_limits::infinity()) { - return -z + (2.0 * M_PI * k + M_PI) * std::complex(0, 1); - } - if (z == 0.0) { - if (k == 0) { - return z; - } - set_error("lambertw", SF_ERROR_SINGULAR, NULL); - return -std::numeric_limits::infinity(); - } - if (z == 1.0 && k == 0) { - // Split out this case because the asymptotic series blows up - return OMEGA; - } - - absz = std::abs(z); - // Get an initial guess for Halley's method - if (k == 0) { - if (std::abs(z + EXPN1) < 0.3) { - w = detail::lambertw_branchpt(z); - } else if (-1.0 < z.real() && z.real() < 1.5 && std::abs(z.imag()) < 1.0 && - -2.5 * std::abs(z.imag()) - 0.2 < z.real()) { - /* Empirically determined decision boundary where the Pade - * approximation is more accurate. */ - w = detail::lambertw_pade0(z); - } else { - w = detail::lambertw_asy(z, k); - } - } else if (k == -1) { - if (absz <= EXPN1 && z.imag() == 0.0 && z.real() < 0.0) { - w = std::log(-z.real()); - } else { - w = detail::lambertw_asy(z, k); - } - } else { - w = detail::lambertw_asy(z, k); - } - - // Halley's method; see 5.9 in [1] - if (w.real() >= 0) { - // Rearrange the formula to avoid overflow in exp - for (int i = 0; i < 100; i++) { - ew = std::exp(-w); - wewz = w - z * ew; - wn = w - wewz / (w + 1.0 - (w + 2.0) * wewz / (2.0 * w + 2.0)); - if (std::abs(wn - w) <= tol * std::abs(wn)) { - return wn; - } - w = wn; - } - } else { - for (int i = 0; i < 100; i++) { - ew = std::exp(w); - wew = w * ew; - wewz = wew - z; - wn = w - wewz / (wew + ew - (w + 2.0) * wewz / (2.0 * w + 2.0)); - if (std::abs(wn - w) <= tol * std::abs(wn)) { - return wn; - } - w = wn; - } - } - - set_error("lambertw", SF_ERROR_SLOW, "iteration failed to converge: %g + %gj", z.real(), z.imag()); - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; -} - -XSF_HOST_DEVICE inline std::complex lambertw(std::complex z, long k, float tol) { - return static_cast>( - lambertw(static_cast>(z), k, static_cast(tol))); -} - -} // namespace xsf diff --git a/scipy/special/xsf/legendre.h b/scipy/special/xsf/legendre.h deleted file mode 100644 index 6565ed52c368..000000000000 --- a/scipy/special/xsf/legendre.h +++ /dev/null @@ -1,1064 +0,0 @@ -#pragma once - -#include "dual.h" -#include "error.h" -#include "recur.h" - -namespace xsf { - -template -struct legendre_p_initializer_n { - T z; - - void operator()(T (&res)[2]) const { - res[0] = T(1); - res[1] = z; - } -}; - -template -struct legendre_p_recurrence_n { - T z; - - void operator()(int n, T (&res)[2]) const { - using value_type = remove_dual_t; - value_type fac0 = -value_type(n - 1) / value_type(n); - value_type fac1 = value_type(2 * n - 1) / value_type(n); - - res[0] = fac0; - res[1] = fac1 * z; - } -}; - -/** - * Compute the Legendre polynomial of degree n. - * - * @param n degree of the polynomial - * @param z argument of the polynomial, either real or complex - * @param f a function to be called as callback(j, res) for 0 <= j <= n - * @param res value and derivatives of the polynomial - */ -template -void legendre_p_for_each_n(int n, T z, T (&res)[2], Func f) { - legendre_p_initializer_n init_n{z}; - init_n(res); - - legendre_p_recurrence_n re_n{z}; - forward_recur(0, n + 1, re_n, res, f); -} - -/* - * Compute the Legendre polynomial of degree n. - * - * @param n degree of the polynomial - * @param z argument of the polynomial, either real or complex - * - * @return value of the polynomial - */ -template -T legendre_p(int n, T z) { - T res_n[2]; - legendre_p_for_each_n(n, z, res_n, [](int n, const T(&res_n)[2]) {}); - - return res_n[1]; -} - -/** - * Compute all Legendre polynomials of degree j, where 0 <= j <= n. - * - * @param z argument of the polynomials, either real or complex - * @param res a view into a multidimensional array with element type T and size n + 1 to store the value of each - * polynomial - */ -template -void legendre_p_all(T z, OutputVec res) { - int n = res.extent(0) - 1; - - T res_n[2]; - legendre_p_for_each_n(n, z, res_n, [&res](int n, const T(&res_n)[2]) { res(n) = res_n[1]; }); -} - -struct assoc_legendre_unnorm_policy {}; - -struct assoc_legendre_norm_policy {}; - -constexpr assoc_legendre_unnorm_policy assoc_legendre_unnorm; - -constexpr assoc_legendre_norm_policy assoc_legendre_norm; - -template -struct assoc_legendre_p_initializer_m_abs_m; - -template -struct assoc_legendre_p_initializer_m_abs_m { - bool m_signbit; - T z; - int branch_cut; - T w; - - assoc_legendre_p_initializer_m_abs_m(bool m_signbit, T z, int branch_cut) - : m_signbit(m_signbit), z(z), branch_cut(branch_cut) { - if (branch_cut == 3) { - w = sqrt(z - T(1)) * sqrt(z + T(1)); // form for this branch cut - } else { - w = -sqrt(T(1) - z * z); // form for this branch cut - if (m_signbit) { - w = -w; - } - } - } - - void operator()(T (&res)[2]) const { - res[0] = T(1); - res[1] = w; - - if (m_signbit) { - res[1] /= 2; - } - } -}; - -template -struct assoc_legendre_p_initializer_m_abs_m { - bool m_signbit; - T z; - int branch_cut; - T w; - - assoc_legendre_p_initializer_m_abs_m(bool m_signbit, T z, int branch_cut) - : m_signbit(m_signbit), z(z), branch_cut(branch_cut) { - if (branch_cut == 3) { - - w = sqrt(z - T(1)) * sqrt(z + T(1)); // form for this branch cut - } else { - w = -sqrt(T(1) - z * z); // form for this branch cut - if (m_signbit) { - w = -w; - } - } - } - - void operator()(T (&res)[2]) const { - res[0] = T(1) / sqrt(T(2)); - res[1] = sqrt(T(3)) * w / T(2); - } -}; - -template -struct assoc_legendre_p_recurrence_m_abs_m; - -template -struct assoc_legendre_p_recurrence_m_abs_m { - T z; - int branch_cut; - T branch_cut_sign; - - assoc_legendre_p_recurrence_m_abs_m(T z, int branch_cut) : z(z), branch_cut(branch_cut) { - if (branch_cut == 3) { - branch_cut_sign = T(-1); - } else { - branch_cut_sign = T(1); - } - } - - // other square roots can be avoided if each iteration increments by 2 - void operator()(int m, T (&res)[2]) const { - int m_abs = abs(m); - - T fac; - if (m < 0) { - fac = branch_cut_sign / T((2 * m_abs) * (2 * m_abs - 2)); - } else { - fac = branch_cut_sign * T((2 * m_abs - 1) * (2 * m_abs - 3)); - } - - res[0] = fac * (T(1) - z * z); - res[1] = T(0); - } -}; - -template -struct assoc_legendre_p_recurrence_m_abs_m { - T z; - int branch_cut; - T branch_cut_sign; - - assoc_legendre_p_recurrence_m_abs_m(T z, int branch_cut) : z(z), branch_cut(branch_cut) { - if (branch_cut == 3) { - branch_cut_sign = T(-1); - } else { - branch_cut_sign = T(1); - } - } - - void operator()(int m, T (&res)[2]) const { - int m_abs = abs(m); - - T fac = branch_cut_sign * sqrt(T((2 * m_abs + 1) * (2 * m_abs - 1)) / T(4 * m_abs * (m_abs - 1))); - - res[0] = fac * (T(1) - z * z); - res[1] = T(0); - } -}; - -template -void assoc_legendre_p_for_each_m_abs_m(NormPolicy norm, int m, T z, int branch_cut, T (&res)[2], Func f) { - bool m_signbit; - if (m < 0) { - m_signbit = true; - } else { - m_signbit = false; - } - - assoc_legendre_p_initializer_m_abs_m init_m_abs_m{m_signbit, z, branch_cut}; - init_m_abs_m(res); - - assoc_legendre_p_recurrence_m_abs_m re_m_abs_m{z, branch_cut}; - if (m >= 0) { - forward_recur(0, m + 1, re_m_abs_m, res, f); - } else { - backward_recur(0, m - 1, re_m_abs_m, res, f); - } -} - -/** - * Compute the associated Legendre polynomial of degree n and order n. - * - * We need to be careful with complex arithmetic, in particular the square roots - * should not be modified. This is because the sign bit of a real or imaginary part, - * even if it is equal to zero, can affect the branch cut. - */ - -template -struct assoc_legendre_p_initializer_n; - -template -struct assoc_legendre_p_initializer_n { - int m; - T z; - int type; - - void operator()(const T &res_m_abs_m, T (&res)[2]) const { - int m_abs = abs(m); - T fac = T(2 * (m_abs + 1) - 1) / T(m_abs + 1 - m); - - res[0] = res_m_abs_m; - res[1] = fac * z * res_m_abs_m; - } -}; - -template -struct assoc_legendre_p_initializer_n { - int m; - T z; - int type; - - void operator()(const T &res_m_abs_m, T (&res)[2]) const { - T fac = sqrt(T(2 * abs(m) + 3)); - - res[0] = res_m_abs_m; - res[1] = fac * z * res_m_abs_m; - } -}; - -template -struct assoc_legendre_p_recurrence_n; - -template -struct assoc_legendre_p_recurrence_n { - int m; - T z; - int type; - - void operator()(int n, T (&res)[2]) const { - using value_type = remove_dual_t; - value_type fac0 = -value_type(n + m - 1) / value_type(n - m); - value_type fac1 = value_type(2 * n - 1) / value_type(n - m); - - res[0] = fac0; - res[1] = fac1 * z; - } -}; - -template -struct assoc_legendre_p_recurrence_n { - int m; - T z; - int type; - - void operator()(int n, T (&res)[2]) const { - using value_type = remove_dual_t; - value_type fac0 = - -sqrt(value_type((2 * n + 1) * ((n - 1) * (n - 1) - m * m)) / value_type((2 * n - 3) * (n * n - m * m))); - value_type fac1 = - sqrt(value_type((2 * n + 1) * (4 * (n - 1) * (n - 1) - 1)) / value_type((2 * n - 3) * (n * n - m * m))); - - res[0] = fac0; - res[1] = fac1 * z; - } -}; - -template -void assoc_legendre_p_pm1(NormPolicy norm, int n, int m, T z, int branch_cut, T &res) { - if (m == 0) { - res = T(1); - } else { - res = T(0); - } -} - -template -void assoc_legendre_p_pm1(NormPolicy norm, int n, int m, dual z, int branch_cut, dual &res) { - if (m == 0) { - res[0] = T(1); - } else { - res[0] = T(0); - } - - T branch_cut_sign; - if (branch_cut == 3) { - branch_cut_sign = -1; - } else { - branch_cut_sign = 1; - } - - if (Order >= 1) { - if (abs(m) > n) { - res[1] = 0; - } else if (m == 0) { - res[1] = T(n) * T(n + 1) * std::pow(z[0], T(n + 1)) / T(2); - } else if (m == 1) { - res[1] = std::pow(z[0], T(n)) * std::numeric_limits>::infinity(); - } else if (m == 2) { - res[1] = -branch_cut_sign * T(n + 2) * T(n + 1) * T(n) * T(n - 1) * std::pow(z[0], T(n + 1)) / T(4); - } else if (m == -2) { - res[1] = -branch_cut_sign * std::pow(z[0], T(n + 1)) / T(4); - } else if (m == -1) { - res[1] = -std::pow(z[0], T(n)) * std::numeric_limits>::infinity(); - } else { - res[1] = 0; - } - - if (Order >= 2) { - if (abs(m) > n) { - res[2] = 0; - } else if (m == 0) { - res[2] = T(n + 2) * T(n + 1) * T(n) * T(n - 1) / T(8); - } else if (m == 1) { - res[2] = std::numeric_limits>::infinity(); - } else if (m == 2) { - res[2] = -T((n + 1) * n - 3) * T(n + 2) * T(n + 1) * T(n) * T(n - 1) / T(12); - } else if (m == 3) { - res[2] = std::numeric_limits>::infinity(); - } else if (m == 4) { - res[2] = T(n + 4) * T(n + 3) * T(n + 2) * T(n + 1) * T(n) * T(n - 1) * T(n - 2) * T(n - 3) / T(48); - } else if (m == -4) { - res[2] = 0; - } else if (m == -3) { - res[2] = -std::numeric_limits>::infinity(); - } else if (m == -2) { - res[2] = -T(1) / T(4); - } else if (m == -1) { - res[2] = -std::numeric_limits>::infinity(); - } else { - res[2] = 0; - } - } - } -} - -/** - * Compute the associated Legendre polynomial of degree n and order m. - * - * @param n degree of the polynomial - * @param m order of the polynomial - * @param type specifies the branch cut of the polynomial, either 1, 2, or 3 - * @param z argument of the polynomial, either real or complex - * @param callback a function to be called as callback(j, m, type, z, p, p_prev, args...) for 0 <= j <= n - * @param args arguments to forward to the callback - * - * @return value of the polynomial - */ -template -void assoc_legendre_p_for_each_n( - NormPolicy norm, int n, int m, T z, int branch_cut, const T &res_m_abs_m, T (&res)[2], Func f -) { - res[0] = 0; - res[1] = 0; - - int m_abs = abs(m); - if (m_abs > n) { - for (int j = 0; j <= n; ++j) { - f(j, res); - } - } else { - for (int j = 0; j < m_abs; ++j) { - f(j, res); - } - - if (abs(real(z)) == 1 && imag(z) == 0) { - for (int j = m_abs; j <= n; ++j) { - forward_recur_shift_left(res); - assoc_legendre_p_pm1(norm, j, m, z, branch_cut, res[1]); - - f(j, res); - } - } else { - assoc_legendre_p_initializer_n init_n{m, z, branch_cut}; - init_n(res_m_abs_m, res); - - assoc_legendre_p_recurrence_n re_n{m, z, branch_cut}; - forward_recur(m_abs, n + 1, re_n, res, f); - } - } -} - -template -void assoc_legendre_p_for_each_n(NormPolicy norm, int n, int m, T z, int branch_cut, T (&res)[2], Func f) { - assoc_legendre_p_for_each_m_abs_m(norm, m, z, branch_cut, res, [](int m, const T(&res)[2]) {}); - - T res_m_abs_m = res[1]; - assoc_legendre_p_for_each_n(norm, n, m, z, branch_cut, res_m_abs_m, res, f); -} - -template -void assoc_legendre_p_for_each_n_m(NormPolicy norm, int n, int m, T z, int branch_cut, T (&res)[2], Func f) { - T res_m_abs_m[2]; - assoc_legendre_p_for_each_m_abs_m( - norm, m, z, branch_cut, res_m_abs_m, - [norm, n, z, branch_cut, &res, f](int m, const T(&res_m_abs_m)[2]) { - res[0] = res_m_abs_m[1]; - - assoc_legendre_p_for_each_n( - norm, n, m, z, branch_cut, res_m_abs_m[1], res, [f, m](int n, const T(&res_n)[2]) { f(n, m, res_n); } - ); - } - ); - assoc_legendre_p_for_each_m_abs_m( - norm, -m, z, branch_cut, res_m_abs_m, - [norm, n, z, branch_cut, &res, f](int m, const T(&res_m_abs_m)[2]) { - res[0] = res_m_abs_m[1]; - - assoc_legendre_p_for_each_n( - norm, n, m, z, branch_cut, res_m_abs_m[1], res, [f, m](int n, const T(&res_n)[2]) { f(n, m, res_n); } - ); - } - ); -} - -/** - * Compute the associated Legendre polynomial of degree n and order m. - * - * @param n degree of the polynomial - * @param m order of the polynomial - * @param type specifies the branch cut of the polynomial, either 1, 2, or 3 - * @param z argument of the polynomial, either real or complex - * - * @return value of the polynomial - */ -template -T assoc_legendre_p(NormPolicy norm, int n, int m, T z, int branch_cut) { - T res_n[2]; - assoc_legendre_p_for_each_n(norm, n, m, z, branch_cut, res_n, [](int n, const T(&res_n)[2]) {}); - - return res_n[1]; -} - -/** - * Compute all associated Legendre polynomials of degree j and order i, where 0 <= j <= n and -m <= i <= m. - * - * @param type specifies the branch cut of the polynomial, either 1, 2, or 3 - * @param z argument of the polynomial, either real or complex - * @param res a view into the output with element type T and extents (2 * m + 1, n + 1) - * - * @return value of the polynomial - */ -template -void assoc_legendre_p_all(NormPolicy norm, T z, int branch_cut, OutputMat res) { - int n = res.extent(0) - 1; - int m = (res.extent(1) - 1) / 2; - - T p[2]; - assoc_legendre_p_for_each_n_m(norm, n, m, z, branch_cut, p, [&res](int n, int m, const T(&res_n_m)[2]) { - if (m >= 0) { - res(n, m) = res_n_m[1]; - } else { - res(n, m + res.extent(1)) = res_n_m[1]; - } - }); -} - -template -struct sph_legendre_p_initializer_m_abs_m { - bool m_signbit; - T theta; - T theta_sin; - - sph_legendre_p_initializer_m_abs_m(bool m_signbit, T theta) - : m_signbit(m_signbit), theta(theta), theta_sin(sin(theta)) {} - - void operator()(T (&res)[2]) const { - T fac0 = T(1) / (T(2) * sqrt(T(M_PI))); - T fac1 = -sqrt(T(3)) / (T(2) * sqrt(T(2) * T(M_PI))); - if (m_signbit) { - fac1 = -fac1; - } - - res[0] = fac0; - res[1] = fac1 * abs(theta_sin); - } -}; - -template -struct sph_legendre_p_recurrence_m_abs_m { - T theta; - T theta_sin; - - sph_legendre_p_recurrence_m_abs_m(T theta) : theta(theta), theta_sin(sin(theta)) {} - - void operator()(int m, T (&res)[2]) const { - int m_abs = abs(m); - - T fac = sqrt(T((2 * m_abs + 1) * (2 * m_abs - 1)) / T(4 * m_abs * (m_abs - 1))); - - res[0] = fac * theta_sin * theta_sin; - res[1] = 0; - } -}; - -template -void sph_legendre_p_for_each_m_abs_m(int m, T theta, T (&res)[2], Func f) { - bool m_signbit; - if (m < 0) { - m_signbit = true; - } else { - m_signbit = false; - } - - sph_legendre_p_initializer_m_abs_m init_m_abs_m{m_signbit, theta}; - init_m_abs_m(res); - - sph_legendre_p_recurrence_m_abs_m re_m_abs_m{theta}; - if (m >= 0) { - forward_recur(0, m + 1, re_m_abs_m, res, f); - } else { - backward_recur(0, m - 1, re_m_abs_m, res, f); - } -} - -template -struct sph_legendre_p_initializer_n { - int m; - T theta; - T theta_cos; - - sph_legendre_p_initializer_n(int m, T theta) : m(m), theta(theta), theta_cos(cos(theta)) {} - - void operator()(const T &res_m_abs_m, T (&res)[2]) const { - T fac = sqrt(T(2 * abs(m) + 3)); - - res[0] = res_m_abs_m; - res[1] = fac * theta_cos * res_m_abs_m; - } -}; - -template -struct sph_legendre_p_recurrence_n { - int m; - T theta; - T theta_cos; - - sph_legendre_p_recurrence_n(int m, T theta) : m(m), theta(theta), theta_cos(cos(theta)) {} - - void operator()(int n, T (&res)[2]) const { - using value_type = remove_dual_t; - value_type fac0 = - -sqrt(value_type((2 * n + 1) * ((n - 1) * (n - 1) - m * m)) / value_type((2 * n - 3) * (n * n - m * m))); - value_type fac1 = - sqrt(value_type((2 * n + 1) * (4 * (n - 1) * (n - 1) - 1)) / value_type((2 * n - 3) * (n * n - m * m))); - - res[0] = fac0; - res[1] = fac1 * theta_cos; - } -}; - -/** - * Compute the spherical Legendre polynomial of degree n and order m. - * - * @param n degree of the polynomial - * @param m order of the polynomial - * @param theta z = cos(theta) argument of the polynomial, either real or complex - * @param callback a function to be called as callback(j, m, type, z, p, p_prev, args...) for 0 <= j <= n - * @param args arguments to forward to the callback - * - * @return value of the polynomial - */ -template -void sph_legendre_p_for_each_n(int n, int m, T theta, const T &res_m_abs_m, T (&res)[2], Func f) { - res[0] = 0; - res[1] = 0; - - int m_abs = abs(m); - if (m_abs > n) { - for (int j = 0; j <= n; ++j) { - f(j, res); - } - } else { - for (int j = 0; j < m_abs; ++j) { - f(j, res); - } - - sph_legendre_p_initializer_n init_n{m, theta}; - init_n(res_m_abs_m, res); - - sph_legendre_p_recurrence_n re_n{m, theta}; - forward_recur(m_abs, n + 1, re_n, res, f); - } -} - -template -void sph_legendre_p_for_each_n(int n, int m, T theta, T (&res)[2], Func f) { - sph_legendre_p_for_each_m_abs_m(m, theta, res, [](int m, auto) {}); - - T res_m_abs_m = res[1]; - sph_legendre_p_for_each_n(n, m, theta, res_m_abs_m, res, f); -} - -template -void sph_legendre_p_for_each_n_m(int n, int m, T theta, T (&res)[2], Func f) { - T res_m_abs_m[2]; - sph_legendre_p_for_each_m_abs_m(m, theta, res_m_abs_m, [n, theta, &res, f](int m, const T(&res_m_abs_m)[2]) { - res[0] = res_m_abs_m[1]; - - sph_legendre_p_for_each_n(n, m, theta, res_m_abs_m[1], res, [f, m](int n, const T(&res_n)[2]) { - f(n, m, res_n); - }); - }); - sph_legendre_p_for_each_m_abs_m(-m, theta, res_m_abs_m, [n, theta, &res, f](int m, const T(&res_m_abs_m)[2]) { - res[0] = res_m_abs_m[1]; - - sph_legendre_p_for_each_n(n, m, theta, res_m_abs_m[1], res, [f, m](int n, const T(&res_n)[2]) { - f(n, m, res_n); - }); - }); -} - -template -T sph_legendre_p(int n, int m, T theta) { - T res_n[2]; - sph_legendre_p_for_each_n(n, m, theta, res_n, [](int n, const T(&res_n)[2]) {}); - - return res_n[1]; -} - -template -void sph_legendre_p_all(T theta, OutputMat res) { - int n_max = res.extent(0) - 1; - int m_max = (res.extent(1) - 1) / 2; - - T res_n_m[2]; - sph_legendre_p_for_each_n_m(n_max, m_max, theta, res_n_m, [m_max, &res](int n, int m, const T(&res_n_m)[2]) { - if (m >= 0) { - res(n, m) = res_n_m[1]; - } else { - res(n, m + 2 * m_max + 1) = res_n_m[1]; - } - }); -} - -// ==================================================== -// Purpose: Compute Legendre functions Qn(x) & Qn'(x) -// Input : x --- Argument of Qn(x) -// n --- Degree of Qn(x) ( n = 0,1,2,…) -// Output: QN(n) --- Qn(x) -// QD(n) --- Qn'(x) -// ==================================================== - -template -void lqn(T x, OutputVec1 qn, OutputVec2 qd) { - int n = qn.size() - 1; - - T x2, q0, q1, qf, qc1, qc2, qr, qf0, qf1, qf2; - const T eps = 1.0e-14; - - if (fabs(x) == 1.0) { - for (int k = 0; k <= n; k++) { - qn[k] = 1.0e300; - qd[k] = 1.0e300; - } - return; - } - - if (x <= 1.021) { - x2 = fabs((1.0 + x) / (1.0 - x)); - q0 = 0.5 * log(x2); - q1 = x * q0 - 1.0; - qn[0] = q0; - qn[1] = q1; - qd[0] = 1.0 / (1.0 - x * x); - qd[1] = qn[0] + x * qd[0]; - - for (int k = 2; k <= n; k++) { - qf = ((2.0 * k - 1.0) * x * q1 - (k - 1.0) * q0) / k; - qn[k] = qf; - qd[k] = (qn[k - 1] - x * qf) * k / (1.0 - x * x); - q0 = q1; - q1 = qf; - } - } else { - qc1 = 0.0; - qc2 = 1.0 / x; - - for (int j = 1; j <= n; j++) { - qc2 *= j / ((2.0 * j + 1.0) * x); - if (j == n - 1) - qc1 = qc2; - } - - for (int l = 0; l <= 1; l++) { - int nl = n + l; - qf = 1.0; - qr = 1.0; - - for (int k = 1; k <= 500; k++) { - qr = qr * (0.5 * nl + k - 1.0) * (0.5 * (nl - 1) + k) / ((nl + k - 0.5) * k * x * x); - qf += qr; - if (fabs(qr / qf) < eps) - break; - } - - if (l == 0) { - qn[n - 1] = qf * qc1; - } else { - qn[n] = qf * qc2; - } - } - - qf2 = qn[n]; - qf1 = qn[n - 1]; - - for (int k = n; k >= 2; k--) { - qf0 = ((2 * k - 1.0) * x * qf1 - k * qf2) / (k - 1.0); - qn[k - 2] = qf0; - qf2 = qf1; - qf1 = qf0; - } - - qd[0] = 1.0 / (1.0 - x * x); - - for (int k = 1; k <= n; k++) { - qd[k] = k * (qn[k - 1] - x * qn[k]) / (1.0 - x * x); - } - } -} - -// ================================================== -// Purpose: Compute the Legendre functions Qn(z) and -// their derivatives Qn'(z) for a complex -// argument -// Input : x --- Real part of z -// y --- Imaginary part of z -// n --- Degree of Qn(z), n = 0,1,2,... -// Output: CQN(n) --- Qn(z) -// CQD(n) --- Qn'(z) -// ================================================== - -template -void lqn(std::complex z, OutputVec1 cqn, OutputVec2 cqd) { - int n = cqn.size() - 1; - - std::complex cq0, cq1, cqf0 = 0.0, cqf1, cqf2; - - if (real(z) == 1) { - for (int k = 0; k <= n; ++k) { - cqn(k) = 1e300; - cqd(k) = 1e300; - } - return; - } - int ls = ((abs(z) > 1.0) ? -1 : 1); - - cq0 = std::log(static_cast(ls) * (static_cast(1) + z) / (static_cast(1) - z)) / static_cast(2); - cq1 = z * cq0 - static_cast(1); - - cqn(0) = cq0; - cqn(1) = cq1; - - if (abs(z) < 1.0001) { - cqf0 = cq0; - cqf1 = cq1; - for (int k = 2; k <= n; k++) { - cqf2 = (static_cast(2 * k - 1) * z * cqf1 - static_cast(k - 1) * cqf0) / static_cast(k); - cqn(k) = cqf2; - cqf0 = cqf1; - cqf1 = cqf2; - } - } else { - int km; - if (abs(z) > 1.1) { - km = 40 + n; - } else { - km = (int) ((40 + n) * floor(-1.0 - 1.8 * log(abs(z - static_cast(1))))); - } - - cqf2 = 0.0; - cqf1 = 1.0; - for (int k = km; k >= 0; k--) { - cqf0 = (static_cast(2 * k + 3) * z * cqf1 - static_cast(k + 2) * cqf2) / static_cast(k + 1); - if (k <= n) { - cqn[k] = cqf0; - } - cqf2 = cqf1; - cqf1 = cqf0; - } - for (int k = 0; k <= n; ++k) { - cqn[k] *= cq0 / cqf0; - } - } - cqd(0) = (cqn(1) - z * cqn(0)) / (z * z - static_cast(1)); - - for (int k = 1; k <= n; ++k) { - cqd(k) = (static_cast(k) * z * cqn(k) - static_cast(k) * cqn(k - 1)) / (z * z - static_cast(1)); - } -} - -// ========================================================== -// Purpose: Compute the associated Legendre functions of the -// second kind, Qmn(x) and Qmn'(x) -// Input : x --- Argument of Qmn(x) -// m --- Order of Qmn(x) ( m = 0,1,2,… ) -// n --- Degree of Qmn(x) ( n = 0,1,2,… ) -// mm --- Physical dimension of QM and QD -// Output: QM(m,n) --- Qmn(x) -// QD(m,n) --- Qmn'(x) -// ========================================================== - -template -void lqmn(T x, OutputMat1 qm, OutputMat2 qd) { - int m = qm.extent(0) - 1; - int n = qm.extent(1) - 1; - - double q0, q1, q10, qf, qf0, qf1, qf2, xs, xq; - int i, j, k, km, ls; - - if (fabs(x) == 1.0) { - for (i = 0; i < (m + 1); i++) { - for (j = 0; j < (n + 1); j++) { - qm(i, j) = 1e300; - qd(i, j) = 1e300; - } - } - return; - } - ls = 1; - if (fabs(x) > 1.0) { - ls = -1; - } - xs = ls * (1.0 - x * x); - xq = sqrt(xs); - q0 = 0.5 * log(fabs((x + 1.0) / (x - 1.0))); - if (fabs(x) < 1.0001) { - qm(0, 0) = q0; - qm(0, 1) = x * q0 - 1.0; - qm(1, 0) = -1.0 / xq; - qm(1, 1) = -ls * xq * (q0 + x / (1. - x * x)); - for (i = 0; i <= 1; i++) { - for (j = 2; j <= n; j++) { - qm(i, j) = ((2.0 * j - 1.) * x * qm(i, j - 1) - (j + i - 1) * qm(i, j - 2)) / (j - i); - } - } - /* 15 */ - for (i = 2; i <= m; i++) { - for (j = 0; j <= n; j++) { - qm(i, j) = -2.0 * (i - 1.0) * x / xq * qm(i - 1, j) - ls * (j + i - 1.0) * (j - i + 2.0) * qm(i - 2, j); - } - } - } else { - if (fabs(x) > 1.1) { - km = 40 + m + n; - } else { - km = (40 + m + n) * ((int) (-1. - 1.8 * log(x - 1.))); - } - qf2 = 0.0; - qf1 = 1.0; - qf0 = 0.0; - for (k = km; k >= 0; k--) { - qf0 = ((2.0 * k + 3.0) * x * qf1 - (k + 2.0) * qf2) / (k + 1.0); - if (k <= n) { - qm(0, k) = qf0; - } - qf2 = qf1; - qf1 = qf0; - } - - for (k = 0; k <= n; k++) { - qm(0, k) *= q0 / qf0; - } - - qf2 = 0.0; - qf1 = 1.0; - for (k = km; k >= 0; k--) { - qf0 = ((2.0 * k + 3.0) * x * qf1 - (k + 1.0) * qf2) / (k + 2.0); - if (k <= n) { - qm(1, k) = qf0; - } - qf2 = qf1; - qf1 = qf0; - } - - q10 = -1.0 / xq; - for (k = 0; k <= n; k++) { - qm(1, k) *= q10 / qf0; - } - - for (j = 0; j <= n; j++) { - q0 = qm(0, j); - q1 = qm(1, j); - for (i = 0; i <= (m - 2); i++) { - qf = -2. * (i + 1.) * x / xq * q1 + (j - i) * (j + i + 1.) * q0; - qm(i + 2, j) = qf; - q0 = q1; - q1 = qf; - } - } - } - - qd(0, 0) = ls / xs; - for (j = 1; j <= n; j++) { - qd(0, j) = ls * j * (qm(0, j - 1) - x * qm(0, j)) / xs; - } - - for (i = 1; i <= m; i++) { - for (j = 0; j <= n; j++) { - qd(i, j) = ls * i * x / xs * qm(i, j) + (i + j) * (j - i + 1.) / xq * qm(i - 1, j); - } - } -} - -// ======================================================= -// Purpose: Compute the associated Legendre functions of -// the second kind, Qmn(z) and Qmn'(z), for a -// complex argument -// Input : x --- Real part of z -// y --- Imaginary part of z -// m --- Order of Qmn(z) ( m = 0,1,2,… ) -// n --- Degree of Qmn(z) ( n = 0,1,2,… ) -// mm --- Physical dimension of CQM and CQD -// Output: CQM(m,n) --- Qmn(z) -// CQD(m,n) --- Qmn'(z) -// ======================================================= - -template -void lqmn(std::complex z, OutputMat1 cqm, OutputMat2 cqd) { - int m = cqm.extent(0) - 1; - int n = cqm.extent(1) - 1; - - int i, j, k, km, ls; - std::complex cq0, cq1, cq10, cqf0 = 0, cqf, cqf1, cqf2, zq, zs; - - if ((abs(real(z)) == 1) && (imag(z) == 0)) { - for (i = 0; i < (m + 1); i++) { - for (j = 0; j < (n + 1); j++) { - cqm(i, j) = 1e300; - cqd(i, j) = 1e300; - } - } - - return; - } - - T xc = abs(z); - ls = 0; - if ((imag(z) == 0) || (xc < 1)) { - ls = 1; - } - if (xc > 1) { - ls = -1; - } - zs = static_cast(ls) * (static_cast(1) - z * z); - zq = sqrt(zs); - - cq0 = std::log(static_cast(ls) * (static_cast(1) + z) / (static_cast(1) - z)) / static_cast(2); - if (xc < 1.0001) { - cqm(0, 0) = cq0; - cqm(1, 0) = -static_cast(1) / zq; - cqm(0, 1) = z * cq0 - static_cast(1); - cqm(1, 1) = -zq * (cq0 + z / (static_cast(1) - z * z)); - - for (i = 0; i <= 1; i++) { - for (j = 2; j <= n; j++) { - cqm(i, j) = - (static_cast(2 * j - 1) * z * cqm(i, j - 1) - static_cast(j + i - 1) * cqm(i, j - 2)) / - static_cast(j - i); - } - } - - for (i = 2; i <= m; i++) { - for (j = 0; j <= n; j++) { - cqm(i, j) = -2 * static_cast(i - 1) * z / zq * cqm(i - 1, j) - - static_cast(ls * (j + i - 1) * (j - i + 2)) * cqm(i - 2, j); - } - } - } else { - if (xc > 1.1) { - km = 40 + m + n; - } else { - km = (40 + m + n) * ((int) (-1.0 - 1.8 * log(xc - 1.))); - } - cqf2 = 0.0; - cqf1 = 1.0; - for (k = km; k >= 0; k--) { - cqf0 = (static_cast(2 * k + 3) * z * cqf1 - static_cast(k + 2) * cqf2) / static_cast(k + 1); - if (k <= n) { - cqm(0, k) = cqf0; - } - cqf2 = cqf1; - cqf1 = cqf0; - } - - for (k = 0; k <= n; k++) { - cqm(0, k) *= cq0 / cqf0; - } - - cqf2 = 0.0; - cqf1 = 1.0; - for (k = km; k >= 0; k--) { - cqf0 = (static_cast(2 * k + 3) * z * cqf1 - static_cast(k + 1) * cqf2) / static_cast(k + 2); - if (k <= n) { - cqm(1, k) = cqf0; - } - cqf2 = cqf1; - cqf1 = cqf0; - } - - cq10 = -static_cast(1) / zq; - for (k = 0; k <= n; k++) { - cqm(1, k) *= cq10 / cqf0; - } - - for (j = 0; j <= n; j++) { - cq0 = cqm(0, j); - cq1 = cqm(1, j); - for (i = 0; i <= (m - 2); i++) { - cqf = -static_cast(2 * (i + 1)) * z / zq * cq1 + static_cast((j - i) * (j + i + 1)) * cq0; - cqm(i + 2, j) = cqf; - cq0 = cq1; - cq1 = cqf; - } - } - - cqd(0, 0) = static_cast(ls) / zs; - for (j = 1; j <= n; j++) { - cqd(0, j) = ls * static_cast(j) * (cqm(0, j - 1) - z * cqm(0, j)) / zs; - } - - for (i = 1; i <= m; i++) { - for (j = 0; j <= n; j++) { - cqd(i, j) = static_cast(ls * i) * z / zs * cqm(i, j) + - static_cast((i + j) * (j - i + 1)) / zq * cqm(i - 1, j); - } - } - } -} - -} // namespace xsf diff --git a/scipy/special/xsf/log.h b/scipy/special/xsf/log.h deleted file mode 100644 index 23681cb2ccb0..000000000000 --- a/scipy/special/xsf/log.h +++ /dev/null @@ -1,119 +0,0 @@ -#pragma once - -#include "cephes/dd_real.h" -#include "trig.h" - -namespace xsf { - -inline double log1p(double x) { return cephes::log1p(x); } - -inline float log1p(float x) { return log1p(static_cast(x)); } - -inline std::complex clog1p_ddouble(double zr, double zi) { - double x, y; - - cephes::detail::double_double r(zr); - cephes::detail::double_double i(zi); - cephes::detail::double_double two(2.0); - - cephes::detail::double_double rsqr = r * r; - cephes::detail::double_double isqr = i * i; - cephes::detail::double_double rtwo = two * r; - cephes::detail::double_double absm1 = rsqr + isqr; - absm1 = absm1 + rtwo; - - x = 0.5 * log1p(static_cast(absm1)); - y = atan2(zi, zr + 1.0); - return std::complex{x, y}; -} - -// log(z + 1) = log(x + 1 + 1j*y) -// = log(sqrt((x+1)**2 + y**2)) + 1j*atan2(y, x+1) -// -// Using atan2(y, x+1) for the imaginary part is always okay. The real part -// needs to be calculated more carefully. For |z| large, the naive formula -// log(z + 1) can be used. When |z| is small, rewrite as -// -// log(sqrt((x+1)**2 + y**2)) = 0.5*log(x**2 + 2*x +1 + y**2) -// = 0.5 * log1p(x**2 + y**2 + 2*x) -// = 0.5 * log1p(hypot(x,y) * (hypot(x, y) + 2*x/hypot(x,y))) -// -// This expression suffers from cancellation when x < 0 and -// y = +/-sqrt(2*fabs(x)). To get around this cancellation problem, we use -// double-double precision when necessary. -inline std::complex log1p(std::complex z) { - double x, y, az, azi; - - if (!std::isfinite(std::real(z)) || !std::isfinite(std::imag(z))) { - z = z + 1.0; - return std::log(z); - } - - double zr = z.real(); - double zi = z.imag(); - - if (zi == 0.0 && zr >= -1.0) { - return log1p(zr); - } - - az = std::abs(z); - if (az < 0.707) { - azi = std::fabs(zi); - if (zr < 0 && std::abs(-zr - azi * azi / 2) / (-zr) < 0.5) { - return clog1p_ddouble(zr, zi); - } else { - x = 0.5 * log1p(az * (az + 2 * zr / az)); - y = atan2(zi, zr + 1.0); - return std::complex(x, y); - } - } - - z = z + 1.0; - return std::log(z); -} - -inline std::complex log1p(std::complex z) { - return static_cast>(log1p(static_cast>(z))); -} - -inline double log1pmx(double x) { return cephes::log1pmx(x); } - -inline float log1pmx(float x) { return log1pmx(static_cast(x)); } - -template -T xlogy(T x, T y) { - if (x == 0 && !std::isnan(y)) { - return 0; - } - - return x * std::log(y); -} - -template -std::complex xlogy(std::complex x, std::complex y) { - if (x == T(0) && !std::isnan(std::real(y)) && !std::isnan(std::imag(y))) { - return 0; - } - - return x * std::log(y); -} - -template -T xlog1py(T x, T y) { - if (x == 0 && !std::isnan(y)) { - return 0; - } - - return x * log1p(y); -} - -template -std::complex xlog1py(std::complex x, std::complex y) { - if (x == T(0) && !std::isnan(std::real(y)) && !std::isnan(std::imag(y))) { - return 0; - } - - return x * log1p(y); -} - -} // namespace xsf diff --git a/scipy/special/xsf/log_exp.h b/scipy/special/xsf/log_exp.h deleted file mode 100644 index 2f9e901e9157..000000000000 --- a/scipy/special/xsf/log_exp.h +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -#include - -#include "config.h" -#include "error.h" - -namespace xsf { - -template -T expit(T x) { - return 1 / (1 + std::exp(-x)); -}; - -inline double exprel(double x) { - if (std::abs(x) < std::numeric_limits::epsilon()) { - return 1; - } - - if (x > 717) { // near log(DBL_MAX) - return std::numeric_limits::infinity(); - } - - return std::expm1(x) / x; -} - -inline float exprel(float x) { return exprel(static_cast(x)); } - -template -T logit(T x) { - // The standard formula is log(x/(1 - x)), but this expression - // loses precision near x=0.5, as does log(x) - log1p(-x). - // We use the standard formula away from p=0.5, and use - // log1p(2*(x - 0.5)) - log1p(-2*(x - 0.5)) around p=0.5, which - // provides very good precision in this interval. - if (x < 0.3 || x > 0.65) { - return std::log(x/(1 - x)); - } - else { - T s = 2*(x - 0.5); - return std::log1p(s) - std::log1p(-s); - } -}; - -// -// The logistic sigmoid function 'expit' is -// -// S(x) = 1/(1 + exp(-x)) = exp(x)/(exp(x) + 1) -// -// so -// -// log S(x) = -log(1 + exp(-x)) = x - log(exp(x) + 1) -// = -log1p(exp(-x)) = x - log1p(exp(x)) -// -// By using -log1p(exp(-x)) for x >= 0 and x - log1p(exp(x)) -// for x < 0, we extend the range of x values for which we -// obtain accurate results (compared to the naive implementation -// log(expit(x))). -// -template -T log_expit(T x) { - if (x < 0) { - return x - std::log1p(std::exp(x)); - } - - return -std::log1p(std::exp(-x)); -}; - - -/* Compute log(1 - exp(x)). */ -template -T log1mexp(T x) { - if (x > 0) { - set_error("_log1mexp", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - if (x == 0) { - set_error("_log1mexp", SF_ERROR_SINGULAR, NULL); - return -std::numeric_limits::infinity(); - } - if (x < -1) { - return std::log1p(-std::exp(x)); - } - return std::log(-std::expm1(x)); -} - -} // namespace xsf diff --git a/scipy/special/xsf/loggamma.h b/scipy/special/xsf/loggamma.h deleted file mode 100644 index eaae479b2054..000000000000 --- a/scipy/special/xsf/loggamma.h +++ /dev/null @@ -1,163 +0,0 @@ -/* Translated from Cython into C++ by SciPy developers in 2024. - * Original header comment appears below. - */ - -/* An implementation of the principal branch of the logarithm of - * Gamma. Also contains implementations of Gamma and 1/Gamma which are - * easily computed from log-Gamma. - * - * Author: Josh Wilson - * - * Distributed under the same license as Scipy. - * - * References - * ---------- - * [1] Hare, "Computing the Principal Branch of log-Gamma", - * Journal of Algorithms, 1997. - * - * [2] Julia, - * https://github.com/JuliaLang/julia/blob/master/base/special/gamma.jl - */ - -#pragma once - -#include "cephes/gamma.h" -#include "cephes/rgamma.h" -#include "config.h" -#include "error.h" -#include "evalpoly.h" -#include "trig.h" -#include "zlog1.h" - -namespace xsf { - -namespace detail { - constexpr double loggamma_SMALLX = 7; - constexpr double loggamma_SMALLY = 7; - constexpr double loggamma_HLOG2PI = 0.918938533204672742; // log(2*pi)/2 - constexpr double loggamma_LOGPI = 1.1447298858494001741434262; // log(pi) - constexpr double loggamma_TAYLOR_RADIUS = 0.2; - - XSF_HOST_DEVICE std::complex loggamma_stirling(std::complex z) { - /* Stirling series for log-Gamma - * - * The coefficients are B[2*n]/(2*n*(2*n - 1)) where B[2*n] is the - * (2*n)th Bernoulli number. See (1.1) in [1]. - */ - double coeffs[] = {-2.955065359477124183E-2, 6.4102564102564102564E-3, -1.9175269175269175269E-3, - 8.4175084175084175084E-4, -5.952380952380952381E-4, 7.9365079365079365079E-4, - -2.7777777777777777778E-3, 8.3333333333333333333E-2}; - std::complex rz = 1.0 / z; - std::complex rzz = rz / z; - - return (z - 0.5) * std::log(z) - z + loggamma_HLOG2PI + rz * cevalpoly(coeffs, 7, rzz); - } - - XSF_HOST_DEVICE std::complex loggamma_recurrence(std::complex z) { - /* Backward recurrence relation. - * - * See Proposition 2.2 in [1] and the Julia implementation [2]. - * - */ - int signflips = 0; - int sb = 0; - std::complex shiftprod = z; - - z += 1.0; - int nsb; - while (z.real() <= loggamma_SMALLX) { - shiftprod *= z; - nsb = std::signbit(shiftprod.imag()); - signflips += nsb != 0 && sb == 0 ? 1 : 0; - sb = nsb; - z += 1.0; - } - return loggamma_stirling(z) - std::log(shiftprod) - signflips * 2 * M_PI * std::complex(0, 1); - } - - XSF_HOST_DEVICE std::complex loggamma_taylor(std::complex z) { - /* Taylor series for log-Gamma around z = 1. - * - * It is - * - * loggamma(z + 1) = -gamma*z + zeta(2)*z**2/2 - zeta(3)*z**3/3 ... - * - * where gamma is the Euler-Mascheroni constant. - */ - - double coeffs[] = { - -4.3478266053040259361E-2, 4.5454556293204669442E-2, -4.7619070330142227991E-2, 5.000004769810169364E-2, - -5.2631679379616660734E-2, 5.5555767627403611102E-2, -5.8823978658684582339E-2, 6.2500955141213040742E-2, - -6.6668705882420468033E-2, 7.1432946295361336059E-2, -7.6932516411352191473E-2, 8.3353840546109004025E-2, - -9.0954017145829042233E-2, 1.0009945751278180853E-1, -1.1133426586956469049E-1, 1.2550966952474304242E-1, - -1.4404989676884611812E-1, 1.6955717699740818995E-1, -2.0738555102867398527E-1, 2.7058080842778454788E-1, - -4.0068563438653142847E-1, 8.2246703342411321824E-1, -5.7721566490153286061E-1}; - - z -= 1.0; - return z * cevalpoly(coeffs, 22, z); - } -} // namespace detail - -XSF_HOST_DEVICE inline double loggamma(double x) { - if (x < 0.0) { - return std::numeric_limits::quiet_NaN(); - } - return cephes::lgam(x); -} - -XSF_HOST_DEVICE inline float loggamma(float x) { return loggamma(static_cast(x)); } - -XSF_HOST_DEVICE inline std::complex loggamma(std::complex z) { - // Compute the principal branch of log-Gamma - - if (std::isnan(z.real()) || std::isnan(z.imag())) { - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - if (z.real() <= 0 and z == std::floor(z.real())) { - set_error("loggamma", SF_ERROR_SINGULAR, NULL); - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - if (z.real() > detail::loggamma_SMALLX || std::abs(z.imag()) > detail::loggamma_SMALLY) { - return detail::loggamma_stirling(z); - } - if (std::abs(z - 1.0) < detail::loggamma_TAYLOR_RADIUS) { - return detail::loggamma_taylor(z); - } - if (std::abs(z - 2.0) < detail::loggamma_TAYLOR_RADIUS) { - // Recurrence relation and the Taylor series around 1. - return detail::zlog1(z - 1.0) + detail::loggamma_taylor(z - 1.0); - } - if (z.real() < 0.1) { - // Reflection formula; see Proposition 3.1 in [1] - double tmp = std::copysign(2 * M_PI, z.imag()) * std::floor(0.5 * z.real() + 0.25); - return std::complex(detail::loggamma_LOGPI, tmp) - std::log(sinpi(z)) - loggamma(1.0 - z); - } - if (std::signbit(z.imag()) == 0) { - // z.imag() >= 0 but is not -0.0 - return detail::loggamma_recurrence(z); - } - return std::conj(detail::loggamma_recurrence(std::conj(z))); -} - -XSF_HOST_DEVICE inline std::complex loggamma(std::complex z) { - return static_cast>(loggamma(static_cast>(z))); -} - -XSF_HOST_DEVICE inline double rgamma(double z) { return cephes::rgamma(z); } - -XSF_HOST_DEVICE inline float rgamma(float z) { return rgamma(static_cast(z)); } - -XSF_HOST_DEVICE inline std::complex rgamma(std::complex z) { - // Compute 1/Gamma(z) using loggamma. - if (z.real() <= 0 && z == std::floor(z.real())) { - // Zeros at 0, -1, -2, ... - return 0.0; - } - return std::exp(-loggamma(z)); -} - -XSF_HOST_DEVICE inline std::complex rgamma(std::complex z) { - return static_cast>(rgamma(static_cast>(z))); -} - -} // namespace xsf diff --git a/scipy/special/xsf/mathieu.h b/scipy/special/xsf/mathieu.h deleted file mode 100644 index 7fe82a5ec2d2..000000000000 --- a/scipy/special/xsf/mathieu.h +++ /dev/null @@ -1,233 +0,0 @@ -#pragma once - -#include "specfun/specfun.h" - -namespace xsf { - -template -T sem_cva(T m, T q); - -template -void sem(T m, T q, T x, T &csf, T &csd); - -/* Mathieu functions */ -/* Characteristic values */ -template -T cem_cva(T m, T q) { - int int_m, kd = 1; - - if ((m < 0) || (m != floor(m))) { - set_error("mathieu_a", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - int_m = (int) m; - if (q < 0) { - /* https://dlmf.nist.gov/28.2#E26 */ - if (int_m % 2 == 0) { - return cem_cva(m, -q); - } else { - return sem_cva(m, -q); - } - } - - if (int_m % 2) { - kd = 2; - } - return specfun::cva2(kd, int_m, q); -} - -template -T sem_cva(T m, T q) { - int int_m, kd = 4; - - if ((m <= 0) || (m != floor(m))) { - set_error("mathieu_b", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - int_m = (int) m; - if (q < 0) { - /* https://dlmf.nist.gov/28.2#E26 */ - if (int_m % 2 == 0) { - return sem_cva(m, -q); - } else { - return cem_cva(m, -q); - } - } - if (int_m % 2) { - kd = 3; - } - return specfun::cva2(kd, int_m, q); -} - -/* Mathieu functions */ -template -void cem(T m, T q, T x, T &csf, T &csd) { - int int_m, kf = 1, sgn; - T f = 0.0, d = 0.0; - if ((m < 0) || (m != floor(m))) { - csf = std::numeric_limits::quiet_NaN(); - csd = std::numeric_limits::quiet_NaN(); - set_error("mathieu_cem", SF_ERROR_DOMAIN, NULL); - } else { - int_m = (int) m; - if (q < 0) { - /* https://dlmf.nist.gov/28.2#E34 */ - if (int_m % 2 == 0) { - sgn = ((int_m / 2) % 2 == 0) ? 1 : -1; - cem(m, -q, 90 - x, f, d); - csf = sgn * f; - csd = -sgn * d; - - } else { - sgn = ((int_m / 2) % 2 == 0) ? 1 : -1; - sem(m, -q, 90 - x, f, d); - csf = sgn * f; - csd = -sgn * d; - } - } else { - using specfun::Status; - Status status = specfun::mtu0(kf, int_m, q, x, &csf, &csd); - if (status != Status::OK) { - csf = std::numeric_limits::quiet_NaN(); - csd = std::numeric_limits::quiet_NaN(); - sf_error_t sf_error = status == Status::NoMemory ? SF_ERROR_MEMORY - : SF_ERROR_OTHER; - set_error("mathieu_cem", sf_error, NULL); - } - } - } -} - -template -void sem(T m, T q, T x, T &csf, T &csd) { - int int_m, kf = 2, sgn; - T f = 0.0, d = 0.0; - if ((m < 0) || (m != floor(m))) { - csf = std::numeric_limits::quiet_NaN(); - csd = std::numeric_limits::quiet_NaN(); - set_error("mathieu_sem", SF_ERROR_DOMAIN, NULL); - } else { - int_m = (int) m; - if (int_m == 0) { - csf = 0; - csd = 0; - } else if (q < 0) { - /* https://dlmf.nist.gov/28.2#E34 */ - if (int_m % 2 == 0) { - sgn = ((int_m / 2) % 2 == 0) ? -1 : 1; - sem(m, -q, 90 - x, f, d); - csf = sgn * f; - csd = -sgn * d; - } else { - sgn = ((int_m / 2) % 2 == 0) ? 1 : -1; - cem(m, -q, 90 - x, f, d); - csf = sgn * f; - csd = -sgn * d; - } - } else { - using specfun::Status; - Status status = specfun::mtu0(kf, int_m, q, x, &csf, &csd); - if (status != Status::OK) { - csf = std::numeric_limits::quiet_NaN(); - csd = std::numeric_limits::quiet_NaN(); - sf_error_t sf_error = status == Status::NoMemory ? SF_ERROR_MEMORY - : SF_ERROR_OTHER; - set_error("mathieu_sem", sf_error, NULL); - } - } - } -} - -template -void mcm1(T m, T q, T x, T &f1r, T &d1r) { - int int_m, kf = 1, kc = 1; - T f2r = 0.0, d2r = 0.0; - - if ((m < 0) || (m != floor(m)) || (q < 0)) { - f1r = std::numeric_limits::quiet_NaN(); - d1r = std::numeric_limits::quiet_NaN(); - set_error("mathieu_modcem1", SF_ERROR_DOMAIN, NULL); - } else { - using specfun::Status; - int_m = (int) m; - Status status = specfun::mtu12(kf, kc, int_m, q, x, &f1r, &d1r, &f2r, &d2r); - if (status != Status::OK) { - f1r = std::numeric_limits::quiet_NaN(); - d1r = std::numeric_limits::quiet_NaN(); - sf_error_t sf_error = status == Status::NoMemory ? SF_ERROR_MEMORY - : SF_ERROR_OTHER; - set_error("mathieu_modcem1", sf_error, NULL); - } - } -} - -template -void msm1(T m, T q, T x, T &f1r, T &d1r) { - int int_m, kf = 2, kc = 1; - T f2r = 0.0, d2r = 0.0; - - if ((m < 1) || (m != floor(m)) || (q < 0)) { - f1r = std::numeric_limits::quiet_NaN(); - d1r = std::numeric_limits::quiet_NaN(); - set_error("mathieu_modsem1", SF_ERROR_DOMAIN, NULL); - } else { - using specfun::Status; - int_m = (int) m; - Status status = specfun::mtu12(kf, kc, int_m, q, x, &f1r, &d1r, &f2r, &d2r); - if (status != Status::OK) { - f1r = std::numeric_limits::quiet_NaN(); - d1r = std::numeric_limits::quiet_NaN(); - sf_error_t sf_error = status == Status::NoMemory ? SF_ERROR_MEMORY - : SF_ERROR_OTHER; - set_error("mathieu_modsem1", sf_error, NULL); - } - } -} - -template -void mcm2(T m, T q, T x, T &f2r, T &d2r) { - int int_m, kf = 1, kc = 2; - T f1r = 0.0, d1r = 0.0; - - if ((m < 0) || (m != floor(m)) || (q < 0)) { - f2r = std::numeric_limits::quiet_NaN(); - d2r = std::numeric_limits::quiet_NaN(); - set_error("mathieu_modcem2", SF_ERROR_DOMAIN, NULL); - } else { - using specfun::Status; - int_m = (int) m; - Status status = specfun::mtu12(kf, kc, int_m, q, x, &f1r, &d1r, &f2r, &d2r); - if (status != Status::OK) { - f2r = std::numeric_limits::quiet_NaN(); - d2r = std::numeric_limits::quiet_NaN(); - sf_error_t sf_error = status == Status::NoMemory ? SF_ERROR_MEMORY - : SF_ERROR_OTHER; - set_error("mathieu_modcem2", sf_error, NULL); - } - } -} - -template -void msm2(T m, T q, T x, T &f2r, T &d2r) { - int int_m, kf = 2, kc = 2; - T f1r = 0.0, d1r = 0.0; - - if ((m < 1) || (m != floor(m)) || (q < 0)) { - f2r = std::numeric_limits::quiet_NaN(); - d2r = std::numeric_limits::quiet_NaN(); - set_error("mathieu_modsem2", SF_ERROR_DOMAIN, NULL); - } else { - using specfun::Status; - int_m = (int) m; - Status status = specfun::mtu12(kf, kc, int_m, q, x, &f1r, &d1r, &f2r, &d2r); - if (status != Status::OK) { - f2r = std::numeric_limits::quiet_NaN(); - d2r = std::numeric_limits::quiet_NaN(); - sf_error_t sf_error = status == Status::NoMemory ? SF_ERROR_MEMORY - : SF_ERROR_OTHER; - set_error("mathieu_modsem2", sf_error, NULL); - } - } -} - -} // namespace xsf diff --git a/scipy/special/xsf/numbers.h b/scipy/special/xsf/numbers.h deleted file mode 100644 index da4e241ad542..000000000000 --- a/scipy/special/xsf/numbers.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "config.h" - -namespace xsf { -namespace numbers { - - template - std::complex i_v; - - template <> - std::complex i_v = std::literals::complex_literals::operator""if(1.0L); - - template <> - std::complex i_v = std::literals::complex_literals::operator""i(1.0L); - -} // namespace numbers -} // namespace xsf diff --git a/scipy/special/xsf/numpy.h b/scipy/special/xsf/numpy.h deleted file mode 100644 index 1e37caf81418..000000000000 --- a/scipy/special/xsf/numpy.h +++ /dev/null @@ -1,1081 +0,0 @@ -#pragma once - -#define PY_SSIZE_T_CLEAN -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "dual.h" -#include "error.h" -// Force defining the parenthesis operator even when compiling with a compiler -// defaulting to C++ >= 23. -#define MDSPAN_USE_PAREN_OPERATOR 1 -#include "third_party/kokkos/mdspan.hpp" - -/* PyUFunc_getfperr gets bits for current floating point error (fpe) status codes so we - * can check for floating point errors and make proper calls to set_error in ufunc loops. - * Define a wrapper so it can be given C linkage within this C++ header. */ -extern "C" int wrap_PyUFunc_getfperr() { return PyUFunc_getfperr(); } - -namespace xsf { -namespace numpy { - - void set_error_check_fpe(const char *func_name) { - int status = wrap_PyUFunc_getfperr(); - if (status & NPY_FPE_DIVIDEBYZERO) { - xsf::set_error(func_name, SF_ERROR_SINGULAR, "floating point division by zero"); - } - if (status & NPY_FPE_OVERFLOW) { - xsf::set_error(func_name, SF_ERROR_UNDERFLOW, "floating point underflow"); - } - if (status & NPY_FPE_UNDERFLOW) { - xsf::set_error(func_name, SF_ERROR_OVERFLOW, "floating point overflow"); - } - if (status & NPY_FPE_INVALID) { - xsf::set_error(func_name, SF_ERROR_DOMAIN, "floating point invalid value"); - } - } - - namespace detail { - - // This is std::accumulate, but that is not constexpr until C++20 - template - constexpr T initializer_accumulate(InputIt first, InputIt last, T init) { - for (InputIt it = first; it != last; ++it) { - init = std::move(init) + *it; - } - - return init; - } - - } // namespace detail - - using cfloat = std::complex; - using cdouble = std::complex; - - using float_1d = std::mdspan, std::layout_stride>; - using float_2d = std::mdspan, std::layout_stride>; - using float_3d = std::mdspan, std::layout_stride>; - using float_4d = std::mdspan, std::layout_stride>; - using double_1d = std::mdspan, std::layout_stride>; - using double_2d = std::mdspan, std::layout_stride>; - using double_3d = std::mdspan, std::layout_stride>; - using double_4d = std::mdspan, std::layout_stride>; - using cfloat_1d = std::mdspan, std::layout_stride>; - using cfloat_2d = std::mdspan, std::layout_stride>; - using cfloat_3d = std::mdspan, std::layout_stride>; - using cfloat_4d = std::mdspan, std::layout_stride>; - using cdouble_1d = std::mdspan, std::layout_stride>; - using cdouble_2d = std::mdspan, std::layout_stride>; - using cdouble_3d = std::mdspan, std::layout_stride>; - using cdouble_4d = std::mdspan, std::layout_stride>; - - using autodiff0_float = dual; - using autodiff0_double = dual; - using autodiff0_cfloat = dual; - using autodiff0_cdouble = dual; - using autodiff0_float_1d = std::mdspan, std::layout_stride>; - using autodiff0_double_1d = std::mdspan, std::layout_stride>; - using autodiff0_cfloat_1d = std::mdspan, std::layout_stride>; - using autodiff0_cdouble_1d = std::mdspan, std::layout_stride>; - using autodiff0_float_2d = std::mdspan, std::layout_stride>; - using autodiff0_double_2d = std::mdspan, std::layout_stride>; - using autodiff0_cfloat_2d = std::mdspan, std::layout_stride>; - using autodiff0_cdouble_2d = std::mdspan, std::layout_stride>; - using autodiff1_float = dual; - using autodiff1_double = dual; - using autodiff1_cfloat = dual; - using autodiff1_cdouble = dual; - using autodiff1_float_1d = std::mdspan, std::layout_stride>; - using autodiff1_double_1d = std::mdspan, std::layout_stride>; - using autodiff1_cfloat_1d = std::mdspan, std::layout_stride>; - using autodiff1_cdouble_1d = std::mdspan, std::layout_stride>; - using autodiff1_float_2d = std::mdspan, std::layout_stride>; - using autodiff1_double_2d = std::mdspan, std::layout_stride>; - using autodiff1_cfloat_2d = std::mdspan, std::layout_stride>; - using autodiff1_cdouble_2d = std::mdspan, std::layout_stride>; - using autodiff2_float = dual; - using autodiff2_double = dual; - using autodiff2_cfloat = dual; - using autodiff2_cdouble = dual; - using autodiff2_float_1d = std::mdspan, std::layout_stride>; - using autodiff2_double_1d = std::mdspan, std::layout_stride>; - using autodiff2_cfloat_1d = std::mdspan, std::layout_stride>; - using autodiff2_cdouble_1d = std::mdspan, std::layout_stride>; - using autodiff2_float_2d = std::mdspan, std::layout_stride>; - using autodiff2_double_2d = std::mdspan, std::layout_stride>; - using autodiff2_cfloat_2d = std::mdspan, std::layout_stride>; - using autodiff2_cdouble_2d = std::mdspan, std::layout_stride>; - - using autodiff00_float = dual; - using autodiff00_double = dual; - using autodiff00_cfloat = dual; - using autodiff00_cdouble = dual; - using autodiff00_cfloat_2d = std::mdspan, std::layout_stride>; - using autodiff00_cdouble_2d = std::mdspan, std::layout_stride>; - using autodiff11_float = dual; - using autodiff11_double = dual; - using autodiff11_cfloat = dual; - using autodiff11_cdouble = dual; - using autodiff11_cfloat_2d = std::mdspan, std::layout_stride>; - using autodiff11_cdouble_2d = std::mdspan, std::layout_stride>; - using autodiff22_float = dual; - using autodiff22_double = dual; - using autodiff22_cfloat = dual; - using autodiff22_cdouble = dual; - using autodiff22_cfloat_2d = std::mdspan, std::layout_stride>; - using autodiff22_cdouble_2d = std::mdspan, std::layout_stride>; - - // The following are based off NumPy's dtype type codes and functions like PyUFunc_dd_d - - // 1 input, 1 output - using f_f = float (*)(float); - using d_d = double (*)(double); - using F_F = cfloat (*)(cfloat); - using D_D = cdouble (*)(cdouble); - - // autodiff, 1 input, 1 output - using autodiff0_f_f1 = void (*)(autodiff0_float, autodiff0_float_1d); - using autodiff0_d_d1 = void (*)(autodiff0_double, autodiff0_double_1d); - using autodiff0_F_F1 = void (*)(autodiff0_cfloat, autodiff0_cfloat_1d); - using autodiff0_D_D1 = void (*)(autodiff0_cdouble, autodiff0_cdouble_1d); - using autodiff1_f_f1 = void (*)(autodiff1_float, autodiff1_float_1d); - using autodiff1_d_d1 = void (*)(autodiff1_double, autodiff1_double_1d); - using autodiff1_F_F1 = void (*)(autodiff1_cfloat, autodiff1_cfloat_1d); - using autodiff1_D_D1 = void (*)(autodiff1_cdouble, autodiff1_cdouble_1d); - using autodiff2_f_f1 = void (*)(autodiff2_float, autodiff2_float_1d); - using autodiff2_d_d1 = void (*)(autodiff2_double, autodiff2_double_1d); - using autodiff2_F_F1 = void (*)(autodiff2_cfloat, autodiff2_cfloat_1d); - using autodiff2_D_D1 = void (*)(autodiff2_cdouble, autodiff2_cdouble_1d); - - using autodiff0_f_f2 = void (*)(autodiff0_float, autodiff0_float_2d); - using autodiff0_d_d2 = void (*)(autodiff0_double, autodiff0_double_2d); - using autodiff0_F_F2 = void (*)(autodiff0_cfloat, autodiff0_cfloat_2d); - using autodiff0_D_D2 = void (*)(autodiff0_cdouble, autodiff0_cdouble_2d); - using autodiff1_f_f2 = void (*)(autodiff1_float, autodiff1_float_2d); - using autodiff1_d_d2 = void (*)(autodiff1_double, autodiff1_double_2d); - using autodiff1_F_F2 = void (*)(autodiff1_cfloat, autodiff1_cfloat_2d); - using autodiff1_D_D2 = void (*)(autodiff1_cdouble, autodiff1_cdouble_2d); - using autodiff2_f_f2 = void (*)(autodiff2_float, autodiff2_float_2d); - using autodiff2_d_d2 = void (*)(autodiff2_double, autodiff2_double_2d); - using autodiff2_F_F2 = void (*)(autodiff2_cfloat, autodiff2_cfloat_2d); - using autodiff2_D_D2 = void (*)(autodiff2_cdouble, autodiff2_cdouble_2d); - - // 1 input, 2 outputs - using f_ff = void (*)(float, float &, float &); - using d_dd = void (*)(double, double &, double &); - using f_FF = void (*)(float, cfloat &, cfloat &); - using d_DD = void (*)(double, cdouble &, cdouble &); - using F_FF = void (*)(cfloat, cfloat &, cfloat &); - using D_DD = void (*)(cdouble, cdouble &, cdouble &); - - // 1 input, 4 outputs - using f_ffff = void (*)(float, float &, float &, float &, float &); - using d_dddd = void (*)(double, double &, double &, double &, double &); - using f_FFFF = void (*)(float, cfloat &, cfloat &, cfloat &, cfloat &); - using d_DDDD = void (*)(double, cdouble &, cdouble &, cdouble &, cdouble &); - using F_FFFF = void (*)(cfloat, cfloat &, cfloat &, cfloat &, cfloat &); - using D_DDDD = void (*)(cdouble, cdouble &, cdouble &, cdouble &, cdouble &); - - // 2 inputs, 1 output - using qf_f = float (*)(long long int, float); - using qd_d = double (*)(long long int, double); - using ff_f = float (*)(float, float); - using dd_d = double (*)(double, double); - using FF_F = cfloat (*)(cfloat, cfloat); - using DD_D = cdouble (*)(cdouble, cdouble); - using fF_F = cfloat (*)(float, cfloat); - using dD_D = cdouble (*)(double, cdouble); - using lf_f = float (*)(long int, float); - using ld_d = double (*)(long int, double); - using lF_F = cfloat (*)(long int, cfloat); - using lD_D = cdouble (*)(long int, cdouble); - using Dd_D = cdouble (*) (cdouble, double); - using Ff_F = cfloat (*) (cfloat, float); - - // autodiff, 2 inputs, 1 output - using autodiff0_if_f = autodiff0_float (*)(int, autodiff0_float); - using autodiff0_id_d = autodiff0_double (*)(int, autodiff0_double); - using autodiff1_if_f = autodiff1_float (*)(int, autodiff1_float); - using autodiff1_id_d = autodiff1_double (*)(int, autodiff1_double); - using autodiff2_if_f = autodiff2_float (*)(int, autodiff2_float); - using autodiff2_id_d = autodiff2_double (*)(int, autodiff2_double); - - // 2 inputs, 2 outputs - using qf_ff = void (*)(long long int, float, float &, float &); - using qd_dd = void (*)(long long int, double, double &, double &); - - // 2 inputs, 3 outputs - using qf_fff = void (*)(long long int, float, float &, float &, float &); - using qd_ddd = void (*)(long long int, double, double &, double &, double &); - - // 2 inputs, 2 outputs - using ff_ff = void (*)(float, float, float &, float &); - using dd_dd = void (*)(double, double, double &, double &); - using lf_ff = void (*)(long int, float, float &, float &); - using ld_dd = void (*)(long int, double, double &, double &); - - // 2 inputs, 3 outputs - using lf_fff = void (*)(long int, float, float &, float &, float &); - using ld_ddd = void (*)(long int, double, double &, double &, double &); - - // 2 inputs, 4 outputs - using ff_ffff = void (*)(float, float, float &, float &, float &, float &); - using dd_dddd = void (*)(double, double, double &, double &, double &, double &); - - // 3 inputs, 1 output - using fff_f = float (*)(float, float, float); - using ddd_d = double (*)(double, double, double); - using Flf_F = cfloat (*)(cfloat, long int, float); - using Dld_D = cdouble (*)(cdouble, long int, double); - - // 3 inputs, 2 outputs - using fff_ff = void (*)(float, float, float, float &, float &); - using ddd_dd = void (*)(double, double, double, double &, double &); - - // 3 inputs, 1 output - using qqf_f = float (*)(long long int, long long int, float); - using qqd_d = double (*)(long long int, long long int, double); - - // autodiff, 3 inputs, 1 ouput - using autodiff0_iif_f = autodiff0_float (*)(int, int, autodiff0_float); - using autodiff0_iid_d = autodiff0_double (*)(int, int, autodiff0_double); - using autodiff1_iif_f = autodiff1_float (*)(int, int, autodiff1_float); - using autodiff1_iid_d = autodiff1_double (*)(int, int, autodiff1_double); - using autodiff2_iif_f = autodiff2_float (*)(int, int, autodiff2_float); - using autodiff2_iid_d = autodiff2_double (*)(int, int, autodiff2_double); - - // 3 inputs, 2 outputs - using qqf_ff = void (*)(long long int, long long int, float, float &, float &); - using qqd_dd = void (*)(long long int, long long int, double, double &, double &); - - // 3 inputs, 3 outputs - using qqf_fff = void (*)(long long int, long long int, float, float &, float &, float &); - using qqd_ddd = void (*)(long long int, long long int, double, double &, double &, double &); - - // 4 inputs, 1 output - using qqqF_F = cfloat (*)(long long int, long long int, long long int, cfloat); - using qqqD_D = cdouble (*)(long long int, long long int, long long int, cdouble); - using qqff_F = cfloat (*)(long long int, long long int, float, float); - using qqdd_D = cdouble (*)(long long int, long long int, double, double); - using ffff_f = float (*)(float, float, float, float); - using dddd_d = double (*)(double, double, double, double); - using fffF_F = cfloat (*)(float, float, float, cfloat); - using dddD_D = cdouble (*)(double, double, double, cdouble); - using ffff_F = cfloat (*)(float, float, float, float); - using dddd_D = cdouble (*)(double, double, double, double); - - // autodiff, 4 inputs, 1 output - using autodiff00_iiff_F = autodiff00_cfloat (*)(int, int, autodiff00_float, autodiff00_float); - using autodiff00_iidd_D = autodiff00_cdouble (*)(int, int, autodiff00_double, autodiff00_double); - using autodiff11_iiff_F = autodiff11_cfloat (*)(int, int, autodiff11_float, autodiff11_float); - using autodiff11_iidd_D = autodiff11_cdouble (*)(int, int, autodiff11_double, autodiff11_double); - using autodiff22_iiff_F = autodiff22_cfloat (*)(int, int, autodiff22_float, autodiff22_float); - using autodiff22_iidd_D = autodiff22_cdouble (*)(int, int, autodiff22_double, autodiff22_double); - - // 4 inputs, 2 outputs - using qqqf_ff = void (*)(long long int, long long int, long long int, float, float &, float &); - using ffff_ff = void (*)(float, float, float, float, float &, float &); - using qqqd_dd = void (*)(long long int, long long int, long long int, double, double &, double &); - using dddd_dd = void (*)(double, double, double, double, double &, double &); - using qqqF_FF = void (*)(long long int, long long int, long long int, cfloat, cfloat &, cfloat &); - using qqqD_DD = void (*)(long long int, long long int, long long int, cdouble, cdouble &, cdouble &); - using qqff_FF = void (*)(long long int, long long int, float, float, cfloat &, cfloat &); - using qqdd_DD = void (*)(long long int, long long int, double, double, cdouble &, cdouble &); - - // 4 inputs, 3 outputs - using qqqf_fff = void (*)(long long int, long long int, long long int, float, float &, float &, float &); - using qqqd_ddd = void (*)(long long int, long long int, long long int, double, double &, double &, double &); - using qqqF_FFF = void (*)(long long int, long long int, long long int, cfloat, cfloat &, cfloat &, cfloat &); - using qqqD_DDD = void (*)(long long int, long long int, long long int, cdouble, cdouble &, cdouble &, cdouble &); - using qqff_FFF = void (*)(long long int, long long int, float, float, cfloat &, cfloat &, cfloat &); - using qqdd_DDD = void (*)(long long int, long long int, double, double, cdouble &, cdouble &, cdouble &); - - // 5 inputs, 2 outputs - using fffff_ff = void (*)(float, float, float, float, float, float &, float &); - using ddddd_dd = void (*)(double, double, double, double, double, double &, double &); - -#if (NPY_SIZEOF_LONGDOUBLE == NPY_SIZEOF_DOUBLE) - using g_g = double (*)(double); - using gg_g = double (*)(double); -#else - using g_g = long double (*)(long double); - using gg_g = long double (*)(long double); -#endif - - // 1 input, 1 output - using f_f1 = void (*)(float, float_1d); - using f_f2 = void (*)(float, float_2d); - using d_d1 = void (*)(double, double_1d); - using d_d2 = void (*)(double, double_2d); - using F_F1 = void (*)(cfloat, cfloat_1d); - using D_D1 = void (*)(cdouble, cdouble_1d); - - // 1 input, 2 outputs - using f_f1f1 = void (*)(float, float_1d, float_1d); - using f_f2f2 = void (*)(float, float_2d, float_2d); - using d_d1d1 = void (*)(double, double_1d, double_1d); - using d_d2d2 = void (*)(double, double_2d, double_2d); - using F_F1F1 = void (*)(cfloat, cfloat_1d, cfloat_1d); - using F_F2F2 = void (*)(cfloat, cfloat_2d, cfloat_2d); - using D_D1D1 = void (*)(cdouble, cdouble_1d, cdouble_1d); - using D_D2D2 = void (*)(cdouble, cdouble_2d, cdouble_2d); - - // 1 input, 3 outputs - using f_f1f1f1 = void (*)(float, float_1d, float_1d, float_1d); - using f_f2f2f2 = void (*)(float, float_2d, float_2d, float_2d); - using d_d1d1d1 = void (*)(double, double_1d, double_1d, double_1d); - using d_d2d2d2 = void (*)(double, double_2d, double_2d, double_2d); - using F_F1F1F1 = void (*)(cfloat, cfloat_1d, cfloat_1d, cfloat_1d); - using D_D1D1D1 = void (*)(cdouble, cdouble_1d, cdouble_1d, cdouble_1d); - - // 2 inputs, 1 output - using ff_F2 = void (*)(float, float, cfloat_2d); - using dd_D2 = void (*)(double, double, cdouble_2d); - using qF_F2 = void (*)(long long int, cfloat, cfloat_2d); - using qD_D2 = void (*)(long long int, cdouble, cdouble_2d); - - using autodiff00_ff_F2 = void (*)(autodiff00_float, autodiff00_float, autodiff00_cfloat_2d); - using autodiff00_dd_D2 = void (*)(autodiff00_double, autodiff00_double, autodiff00_cdouble_2d); - using autodiff11_ff_F2 = void (*)(autodiff11_float, autodiff11_float, autodiff11_cfloat_2d); - using autodiff11_dd_D2 = void (*)(autodiff11_double, autodiff11_double, autodiff11_cdouble_2d); - using autodiff22_ff_F2 = void (*)(autodiff22_float, autodiff22_float, autodiff22_cfloat_2d); - using autodiff22_dd_D2 = void (*)(autodiff22_double, autodiff22_double, autodiff22_cdouble_2d); - - // 2 inputs, 2 outputs - using qF_F2F2 = void (*)(long long int, cfloat, cfloat_2d, cfloat_2d); - using qD_D2D2 = void (*)(long long int, cdouble, cdouble_2d, cdouble_2d); - using ff_F2F3 = void (*)(float, float, cfloat_2d, cfloat_3d); - using dd_D2D3 = void (*)(double, double, cdouble_2d, cdouble_3d); - - // 2 inputs, 3 outputs - using qF_F2F2F2 = void (*)(long long int, cfloat, cfloat_2d, cfloat_2d, cfloat_2d); - using qD_D2D2D2 = void (*)(long long int, cdouble, cdouble_2d, cdouble_2d, cdouble_2d); - - // 2 inputs, 4 outputs - using ff_F2F3F4 = void (*)(float, float, cfloat_2d, cfloat_3d, cfloat_4d); - using dd_D2D3D4 = void (*)(double, double, cdouble_2d, cdouble_3d, cdouble_4d); - - template - struct signature_of { - using type = typename signature_of::type; - }; - - template - struct signature_of { - using type = Res(Args...); - }; - - template - struct signature_of { - using type = Res(Args...); - }; - - template - struct signature_of { - using type = Res(Args...); - }; - - template - struct signature_of { - using type = Res(Args...); - }; - - template - using signature_of_t = typename signature_of::type; - - // Deduces the number of arguments of a callable F. - template - struct arity_of { - static constexpr size_t value = arity_of>::value; - }; - - template - struct arity_of { - static constexpr size_t value = sizeof...(Args); - }; - - template - constexpr size_t arity_of_v = arity_of::value; - - template - struct has_return { - static constexpr bool value = has_return>::value; - }; - - template - struct has_return { - static constexpr bool value = true; - }; - - template - struct has_return { - static constexpr bool value = false; - }; - - template - constexpr size_t has_return_v = has_return::value; - - template - struct rank_of { - static constexpr size_t value = 0; - }; - - template - struct rank_of> { - static constexpr size_t value = Extents::rank() + rank_of::value; - }; - - template - struct rank_of> { - static constexpr size_t value = sizeof...(Orders); - }; - - template - inline constexpr size_t rank_of_v = rank_of::value; - - // Maps a C++ type to a NumPy type - template - struct npy_type; - - template <> - struct npy_type { - using type = npy_bool; - }; - - template <> - struct npy_type { - using type = npy_byte; - }; - - template <> - struct npy_type { - using type = npy_short; - }; - - template <> - struct npy_type { - using type = npy_int; - }; - - template <> - struct npy_type { - using type = npy_long; - }; - - template <> - struct npy_type { - using type = npy_longlong; - }; - - template <> - struct npy_type { - using type = npy_ubyte; - }; - - template <> - struct npy_type { - using type = npy_ushort; - }; - - template <> - struct npy_type { - using type = npy_uint; - }; - - template <> - struct npy_type { - using type = npy_ulong; - }; - - template <> - struct npy_type { - using type = npy_ulonglong; - }; - - template <> - struct npy_type { - using type = npy_float; - }; - - template <> - struct npy_type { - using type = npy_double; - }; - - template <> - struct npy_type { - using type = npy_longdouble; - }; - - template <> - struct npy_type> { - using type = npy_cfloat; - }; - - template <> - struct npy_type> { - using type = npy_cdouble; - }; - - template <> - struct npy_type> { - using type = npy_clongdouble; - }; - - template - using npy_type_t = typename npy_type::type; - - // Maps a C++ type to a NumPy type number - template - struct npy_typenum { - static constexpr NPY_TYPES value = npy_typenum>::value; - }; - - // We need to specialise for bool as npy_bool is defined as npy_ubyte - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_BOOL; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_BYTE; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_SHORT; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_INT; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_LONG; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_LONGLONG; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_UBYTE; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_USHORT; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_UINT; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_ULONG; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_ULONGLONG; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_FLOAT; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_DOUBLE; - }; - -// When NPY_SIZEOF_LONGDOUBLE == NPY_SIZEOF_DOUBLE, npy_longdouble is defined as npy_double -// See https://github.com/numpy/numpy/blob/main/numpy/_core/include/numpy/npy_common.h -#if (NPY_SIZEOF_LONGDOUBLE != NPY_SIZEOF_DOUBLE) - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_LONGDOUBLE; - }; -#endif - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_CFLOAT; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_CDOUBLE; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_CLONGDOUBLE; - }; - - template - struct npy_typenum { - static constexpr NPY_TYPES value = npy_typenum::value; - }; - - template - struct npy_typenum { - static constexpr NPY_TYPES value = npy_typenum::value; - }; - - template - struct npy_typenum { - static constexpr NPY_TYPES value = npy_typenum::value; - }; - - template - struct npy_typenum> { - static constexpr NPY_TYPES value = npy_typenum::value; - }; - - template - struct npy_typenum> { - static constexpr NPY_TYPES value = npy_typenum::value; - }; - - template - inline constexpr NPY_TYPES npy_typenum_v = npy_typenum::value; - - template - struct npy_traits { - static T get(char *src, const npy_intp *dimensions, const npy_intp *steps) { - return *reinterpret_cast(src); - } - - static void set(char *dst, const T &src) { *reinterpret_cast *>(dst) = src; } - }; - - template - struct npy_traits> { - static std::complex get(char *src, const npy_intp *dimensions, const npy_intp *steps) { - return *reinterpret_cast *>(src); - } - - static void set(char *dst, const std::complex &src) { - *reinterpret_cast *>(dst) = std::real(src); - *reinterpret_cast *>(dst + sizeof(T)) = std::imag(src); - } - }; - - template - struct npy_traits { - static T *get(char *src, const npy_intp *dimensions, const npy_intp *steps) { - static_assert(sizeof(T) == sizeof(npy_type_t), "NumPy type has different size than argument type"); - - return reinterpret_cast(src); - } - }; - - template - struct npy_traits { - static T &get(char *src, const npy_intp *dimensions, const npy_intp *steps) { - static_assert(sizeof(T) == sizeof(npy_type_t), "NumPy type has different size than argument type"); - - return *reinterpret_cast(src); - } - }; - - template - struct npy_traits { - using dst_type = T (&)[N]; - - static dst_type get(char *src, const npy_intp *dimensions, const npy_intp *steps) { - static_assert(sizeof(T) == sizeof(npy_type_t), "NumPy type has different size than argument type"); - - return *reinterpret_cast(src); - } - }; - - template - struct npy_traits { - using dst_type = T (&)[N][N]; - - static dst_type get(char *src, const npy_intp *dimensions, const npy_intp *steps) { - static_assert(sizeof(T) == sizeof(npy_type_t), "NumPy type has different size than argument type"); - - return *reinterpret_cast(src); - } - }; - - template - struct npy_traits> { - static std::mdspan - get(char *src, const npy_intp *dimensions, const npy_intp *steps) { - // static_assert(sizeof(T) == sizeof(npy_type_t), "NumPy type has different size than argument - // type"); - - std::array strides; - for (npy_uintp i = 0; i < strides.size(); ++i) { - strides[i] = steps[i] / sizeof(T); - } - - std::array exts; - for (npy_uintp i = 0; i < exts.size(); ++i) { - exts[i] = dimensions[i]; - } - - return {reinterpret_cast(src), {exts, strides}}; - } - }; - - template - struct npy_traits> { - static dual get(char *src, const npy_intp *dimensions, const npy_intp *steps) { - return *reinterpret_cast *>(src); - } - - static void set(char *dst, const dual &src) { - *reinterpret_cast *>(dst) = src; - } - }; - - using map_dims_type = void (*)(const npy_intp *, npy_intp *); - - struct base_ufunc_data { - const char *name; - map_dims_type map_dims; - int flags; - }; - - template - struct ufunc_data : base_ufunc_data { - Func func; - }; - - template < - typename Func, typename Signature = signature_of_t, - typename Indices = std::make_index_sequence>> - struct ufunc_traits; - - template - struct ufunc_traits> { - static constexpr char types[sizeof...(Args) + 1] = {npy_typenum_v..., npy_typenum_v}; - - static constexpr size_t ranks[sizeof...(Args) + 1] = {rank_of_v..., rank_of_v}; - - static constexpr size_t ranks_scan[sizeof...(Args) + 2] = { - detail::initializer_accumulate(ranks, ranks + I, 0)..., - detail::initializer_accumulate(ranks, ranks + sizeof...(Args), 0), - detail::initializer_accumulate(ranks, ranks + sizeof...(Args) + 1, 0) - }; - - static void loop(char **args, const npy_intp *dims, const npy_intp *steps, void *data) { - std::array new_dims; - - map_dims_type map_dims = static_cast *>(data)->map_dims; - map_dims(dims + 1, new_dims.data()); - - Func func = static_cast *>(data)->func; - for (npy_intp i = 0; i < dims[0]; ++i) { - Res res = func(npy_traits::get( - args[I], new_dims.data() + ranks_scan[I], steps + ranks_scan[I] + sizeof...(Args) + 1 - )...); - npy_traits::set(args[sizeof...(Args)], res); // assign to the output pointer - - for (npy_uintp j = 0; j <= sizeof...(Args); ++j) { - args[j] += steps[j]; - } - } - - const char *name = static_cast *>(data)->name; - set_error_check_fpe(name); - } - }; - - template - struct ufunc_traits> { - static constexpr char types[sizeof...(Args)] = {npy_typenum_v...}; - - static constexpr size_t ranks[sizeof...(Args)] = {rank_of_v...}; - - static constexpr size_t ranks_scan[sizeof...(Args) + 1] = { - detail::initializer_accumulate(ranks, ranks + I, 0)..., - detail::initializer_accumulate(ranks, ranks + sizeof...(Args), 0) - }; - - static void loop(char **args, const npy_intp *dims, const npy_intp *steps, void *data) { - std::array new_dims; - - map_dims_type map_dims = static_cast *>(data)->map_dims; - map_dims(dims + 1, new_dims.data()); - - Func func = static_cast *>(data)->func; - for (npy_intp i = 0; i < dims[0]; ++i) { - func(npy_traits::get( - args[I], new_dims.data() + ranks_scan[I], steps + ranks_scan[I] + sizeof...(Args) - )...); - - for (npy_uintp j = 0; j < sizeof...(Args); ++j) { - args[j] += steps[j]; - } - } - - const char *name = static_cast *>(data)->name; - set_error_check_fpe(name); - } - }; - - namespace detail { - - template - decltype(auto) compose(Func func, Tr tr) { - return tr(func); - } - - template - decltype(auto) compose(Func func, Tr0 tr0, Tr1 tr1, Trs... trs) { - return compose(tr0(func), tr1, trs...); - } - - } // namespace detail - - template - class compose { - std::tuple m_trs; - - public: - compose(Trs... trs) : m_trs(trs...) {} - - template - decltype(auto) operator()(Func func) const { - return std::apply([func](auto... trs) { return detail::compose(func, trs...); }, m_trs); - } - }; - - struct ufunc_wraps { - bool has_return; - int nin_and_nout; - PyUFuncGenericFunction func; - void *data; - void (*data_deleter)(void *); - const char *types; - - template - ufunc_wraps(Func func) - : has_return(has_return_v), nin_and_nout(arity_of_v + has_return), - func(ufunc_traits::loop), data(new ufunc_data{{nullptr}, func}), - data_deleter([](void *ptr) { delete static_cast *>(ptr); }), - types(ufunc_traits::types) {} - }; - - class ufunc_overloads { - public: - using data_handle_type = void *; - using data_deleter_type = void (*)(void *); - - private: - int m_ntypes; - bool m_has_return; - int m_nin_and_nout; - std::unique_ptr m_func; - std::unique_ptr m_data; - std::unique_ptr m_data_deleters; - std::unique_ptr m_types; - - public: - template - ufunc_overloads(Func0 func0, Funcs... funcs) - : m_ntypes(sizeof...(Funcs) + 1), m_has_return(has_return_v), - m_nin_and_nout(arity_of_v + m_has_return), m_func(new PyUFuncGenericFunction[m_ntypes]), - m_data(new data_handle_type[m_ntypes]), m_data_deleters(new data_deleter_type[m_ntypes]), - m_types(new char[m_ntypes * m_nin_and_nout]) { - ufunc_wraps func[sizeof...(Funcs) + 1] = {func0, funcs...}; - for (auto it = std::begin(func); it != std::end(func); ++it) { - if (it->nin_and_nout != m_nin_and_nout) { - PyErr_SetString(PyExc_RuntimeError, "all functions must have the same number of arguments"); - } - if (it->has_return != m_has_return) { - PyErr_SetString(PyExc_RuntimeError, "all functions must be void if any function is"); - } - - size_t i = it - std::begin(func); - m_func[i] = it->func; - m_data[i] = it->data; - m_data_deleters[i] = it->data_deleter; - std::memcpy(m_types.get() + i * m_nin_and_nout, it->types, m_nin_and_nout); - } - } - - template - ufunc_overloads(compose trs, Funcs... funcs) : ufunc_overloads(trs(funcs)...) {} - - ufunc_overloads(ufunc_overloads &&other) = default; - - ~ufunc_overloads() { - if (m_data) { - for (int i = 0; i < m_ntypes; ++i) { - data_deleter_type data_deleter = m_data_deleters[i]; - data_deleter(m_data[i]); - } - } - } - - int ntypes() const { return m_ntypes; } - - bool has_return() const { return m_has_return; } - - int nin_and_nout() const { return m_nin_and_nout; } - - PyUFuncGenericFunction *func() const { return m_func.get(); } - - data_handle_type *data() const { return m_data.get(); } - - char *types() const { return m_types.get(); } - - void set_name(const char *name) { - for (int i = 0; i < m_ntypes; ++i) { - static_cast(m_data[i])->name = name; - } - } - - void set_map_dims(map_dims_type map_dims) { - for (int i = 0; i < m_ntypes; ++i) { - static_cast(m_data[i])->map_dims = map_dims; - } - } - }; - - PyObject *ufunc(ufunc_overloads func, int nout, const char *name, const char *doc) { - static std::vector ufuncs; - - if (PyErr_Occurred()) { - return nullptr; - } - - ufunc_overloads &ufunc = ufuncs.emplace_back(std::move(func)); - ufunc.set_name(name); - ufunc.set_map_dims([](const npy_intp *dims, npy_intp *new_dims) {}); - - return PyUFunc_FromFuncAndData( - ufunc.func(), ufunc.data(), ufunc.types(), ufunc.ntypes(), ufunc.nin_and_nout() - nout, nout, PyUFunc_None, - name, doc, 0 - ); - } - - PyObject *ufunc(ufunc_overloads overloads, const char *name, const char *doc) { - int nout = overloads.has_return(); - - return ufunc(std::move(overloads), nout, name, doc); - } - - PyObject *gufunc( - ufunc_overloads overloads, int nout, const char *name, const char *doc, const char *signature, - map_dims_type map_dims - ) { - static std::vector ufuncs; - - if (PyErr_Occurred()) { - return nullptr; - } - - ufunc_overloads &ufunc = ufuncs.emplace_back(std::move(overloads)); - ufunc.set_name(name); - ufunc.set_map_dims(map_dims); - - return PyUFunc_FromFuncAndDataAndSignature( - ufunc.func(), ufunc.data(), ufunc.types(), ufunc.ntypes(), ufunc.nin_and_nout() - nout, nout, PyUFunc_None, - name, doc, 0, signature - ); - } - - PyObject *gufunc( - ufunc_overloads overloads, const char *name, const char *doc, const char *signature, map_dims_type map_dims - ) { - int nout = overloads.has_return(); - - return gufunc(std::move(overloads), nout, name, doc, signature, map_dims); - } - - // rename to autodiff_var? - template - struct autodiff_traits { - static T to_var(T arg, size_t i) { return arg; } - }; - - template - struct autodiff_traits> { - static dual to_var(T arg, size_t i) { return dual_var(arg, i); } - }; - - template < - typename Func, typename Signature = signature_of_t, - typename Indices = std::make_index_sequence>> - struct autodiff_wrapper; - - template - struct autodiff_wrapper> { - Func func; - - Res operator()(remove_dual_t... args) { - return func(autodiff_traits::to_var(args, I - i_scan[I])...); - } - - static constexpr size_t is_autodiff[sizeof...(Args)] = {std::is_same_v>...}; - - static constexpr size_t i_scan[sizeof...(Args)] = { - detail::initializer_accumulate(is_autodiff, is_autodiff + I, 0)..., - }; - }; - - template - autodiff_wrapper(Func func) -> autodiff_wrapper; - - struct autodiff { - template - decltype(auto) operator()(Func f) { - return autodiff_wrapper{f}; - } - }; - - template > - struct use_long_long_int_wrapper; - - template - struct use_long_long_int_wrapper { - Func func; - - Res operator()( - std::conditional_t && !std::is_same_v, long long int, Args>... args - ) { - return func(args...); - } - }; - - template - use_long_long_int_wrapper(Func func) -> use_long_long_int_wrapper; - - struct use_long_long_int { - template - decltype(auto) operator()(Func f) { - return use_long_long_int_wrapper{f}; - } - }; - -} // namespace numpy -} // namespace xsf diff --git a/scipy/special/xsf/par_cyl.h b/scipy/special/xsf/par_cyl.h deleted file mode 100644 index 6092ab2dee43..000000000000 --- a/scipy/special/xsf/par_cyl.h +++ /dev/null @@ -1,667 +0,0 @@ -#pragma once - -#include "specfun/specfun.h" - -namespace xsf { -namespace detail { - - template - T vvla(T x, T va); - - template - T dvsa(T x, T va) { - - // =================================================== - // Purpose: Compute parabolic cylinder function Dv(x) - // for small argument - // Input: x --- Argument - // va --- Order - // Output: PD --- Dv(x) - // Routine called: GAMMA2 for computing Г(x) - // =================================================== - - int m; - T ep, a0, va0, pd, ga0, g1, g0, r, vm, gm, r1, vt; - - const T pi = 3.141592653589793; - const T eps = 1.0e-15; - const T sq2 = sqrt(2); - - ep = exp(-0.25 * x * x); - va0 = 0.5 * (1.0 - va); - if (va == 0.0) { - pd = ep; - } else { - if (x == 0.0) { - if ((va0 <= 0.0) && (va0 == (int) va0)) { - pd = 0.0; - } else { - ga0 = specfun::gamma2(va0); - pd = sqrt(pi) / (pow(2.0, -0.5 * va) * ga0); - } - } else { - g1 = specfun::gamma2(-va); - a0 = pow(2.0, -0.5 * va - 1.0) * ep / g1; - vt = -0.5 * va; - g0 = specfun::gamma2(vt); - pd = g0; - r = 1.0; - for (m = 1; m <= 250; m++) { - vm = 0.5 * (m - va); - gm = specfun::gamma2(vm); - r = -r * sq2 * x / m; - r1 = gm * r; - pd += r1; - if (fabs(r1) < fabs(pd) * eps) { - break; - } - } - pd *= a0; - } - } - return pd; - } - - template - T dvla(T x, T va) { - - // ==================================================== - // Purpose: Compute parabolic cylinder functions Dv(x) - // for large argument - // Input: x --- Argument - // va --- Order - // Output: PD --- Dv(x) - // Routines called: - // (1) VVLA for computing Vv(x) for large |x| - // (2) GAMMA2 for computing Г(x) - // ==================================================== - - int k; - T ep, a0, gl, pd, r, vl, x1; - - const T pi = 3.141592653589793; - const T eps = 1.0e-12; - ep = exp(-.25 * x * x); - a0 = pow(fabs(x), va) * ep; - r = 1.0; - pd = 1.0; - for (k = 1; k <= 16; k++) { - r = -0.5 * r * (2.0 * k - va - 1.0) * (2.0 * k - va - 2.0) / (k * x * x); - pd += r; - if (fabs(r / pd) < eps) { - break; - } - } - pd *= a0; - if (x < 0.0) { - x1 = -x; - vl = vvla(x1, va); - gl = specfun::gamma2(-va); - pd = pi * vl / gl + cos(pi * va) * pd; - } - return pd; - } - - template - T vvla(T x, T va) { - - // =================================================== - // Purpose: Compute parabolic cylinder function Vv(x) - // for large argument - // Input: x --- Argument - // va --- Order - // Output: PV --- Vv(x) - // Routines called: - // (1) DVLA for computing Dv(x) for large |x| - // (2) GAMMA2 for computing Г(x) - // =================================================== - - int k; - T pv, qe, a0, r, x1, gl, dsl, pdl; - - const T pi = 3.141592653589793; - const T eps = 1e-12; - - qe = exp(0.25 * x * x); - a0 = pow(fabs(x), -va - 1) * sqrt(2.0 / pi) * qe; - r = 1.0; - pv = 1.0; - for (k = 1; k <= 18; k++) { - r = 0.5 * r * (2.0 * k + va - 1.0) * (2.0 * k + va) / (k * x * x); - pv += r; - if (fabs(r / pv) < eps) { - break; - } - } - pv *= a0; - if (x < 0.0) { - x1 = -x; - pdl = dvla(x1, va); - gl = specfun::gamma2(-va); - dsl = sin(pi * va) * sin(pi * va); - pv = dsl * gl / pi * pdl - cos(pi * va) * pv; - } - return pv; - } - - template - T vvsa(T x, T va) { - - // =================================================== - // Purpose: Compute parabolic cylinder function Vv(x) - // for small argument - // Input: x --- Argument - // va --- Order - // Output: PV --- Vv(x) - // Routine called : GAMMA2 for computing Г(x) - // =================================================== - - T a0, fac, g1, ga0, gm, gw, r, r1, sq2, sv, sv0, v1, vb0, vm, pv; - - const T eps = 1.0e-15; - const T pi = 3.141592653589793; - const T ep = exp(-0.25 * x * x); - T va0 = 1.0 + 0.5 * va; - - if (x == 0.0) { - if (((va0 <= 0.0) && (va0 == (int) va0)) || va == 0.0) { - pv = 0.0; - } else { - vb0 = -0.5 * va; - sv0 = sin(va0 * pi); - ga0 = specfun::gamma2(va0); - pv = pow(2.0, vb0) * sv0 / ga0; - } - } else { - sq2 = sqrt(2.0); - a0 = pow(2.0, -0.5 * va) * ep / (2.0 * pi); - sv = sin(-(va + 0.5) * pi); - v1 = -0.5 * va; - g1 = specfun::gamma2(v1); - pv = (sv + 1.0) * g1; - r = 1.0; - fac = 1.0; - - for (int m = 1; m <= 250; m++) { - vm = 0.5 * (m - va); - gm = specfun::gamma2(vm); - r = r * sq2 * x / m; - fac = -fac; - gw = fac * sv + 1.0; - r1 = gw * r * gm; - pv += r1; - if ((fabs(r1 / pv) < eps) && (gw != 0.0)) { - break; - } - } - pv *= a0; - } - return pv; - } - - template - void pbdv(T x, T v, T *dv, T *dp, T *pdf, T *pdd) { - - // ==================================================== - // Purpose: Compute parabolic cylinder functions Dv(x) - // and their derivatives - // Input: x --- Argument of Dv(x) - // v --- Order of Dv(x) - // Output: DV(na) --- Dn+v0(x) - // DP(na) --- Dn+v0'(x) - // ( na = |n|, v0 = v-n, |v0| < 1, - // n = 0,±1,±2,… ) - // PDF --- Dv(x) - // PDD --- Dv'(x) - // Routines called: - // (1) DVSA for computing Dv(x) for small |x| - // (2) DVLA for computing Dv(x) for large |x| - // ==================================================== - - int ja, k, l, m, nk, nv, na; - T xa, vh, ep, f, f0, f1, v0, v1, v2, pd, pd0, pd1, s0; - - xa = fabs(x); - vh = v; - v += copysign(1.0, v); - nv = (int) v; - v0 = v - nv; - na = abs(nv); - ep = exp(-0.25 * x * x); - ja = 0; - if (na >= 1) { - ja = 1; - } - if (v >= 0.0) { - if (v0 == 0.0) { - pd0 = ep; - pd1 = x * ep; - } else { - for (l = 0; l <= ja; l++) { - v1 = v0 + l; - if (xa <= 5.8) { - pd1 = dvsa(x, v1); - } else { - pd1 = dvla(x, v1); - } - if (l == 0) { - pd0 = pd1; - } - } - } - dv[0] = pd0; - dv[1] = pd1; - for (k = 2; k <= na; k++) { - *pdf = x * pd1 - (k + v0 - 1.0) * pd0; - dv[k] = *pdf; - pd0 = pd1; - pd1 = *pdf; - } - } else { - if (x <= 0.0) { - if (xa <= 5.8) { - pd0 = dvsa(x, v0); - v1 = v0 - 1.0; - pd1 = dvsa(x, v1); - } else { - pd0 = dvla(x, v0); - v1 = v0 - 1.0; - pd1 = dvla(x, v1); - } - dv[0] = pd0; - dv[1] = pd1; - for (k = 2; k <= na; k++) { - pd = (-x * pd1 + pd0) / (k - 1.0 - v0); - dv[k] = pd; - pd0 = pd1; - pd1 = pd; - } - } else if (x <= 2.0) { - v2 = nv + v0; - if (nv == 0) { - v2 -= 1.0; - } - nk = (int) (-v2); - f1 = dvsa(x, v2); - v1 = v2 + 1.0; - f0 = dvsa(x, v1); - dv[nk] = f1; - dv[nk - 1] = f0; - for (k = nk - 2; k >= 0; k--) { - f = x * f0 + (k - v0 + 1.0) * f1; - dv[k] = f; - f1 = f0; - f0 = f; - } - } else { - if (xa <= 5.8) { - pd0 = dvsa(x, v0); - } else { - pd0 = dvla(x, v0); - } - dv[0] = pd0; - m = 100 + na; - f1 = 0.0; - f0 = 1e-30; - f = 0.0; - for (k = m; k >= 0; k--) { - f = x * f0 + (k - v0 + 1.0) * f1; - if (k <= na) { - dv[k] = f; - } - f1 = f0; - f0 = f; - } - s0 = pd0 / f; - for (k = 0; k <= na; k++) { - dv[k] *= s0; - } - } - } - for (k = 0; k < na; k++) { - v1 = fabs(v0) + k; - if (v >= 0.0) { - dp[k] = 0.5 * x * dv[k] - dv[k + 1]; - } else { - dp[k] = -0.5 * x * dv[k] - v1 * dv[k + 1]; - } - } - *pdf = dv[na - 1]; - *pdd = dp[na - 1]; - v = vh; - return; - } - - template - void pbvv(T x, T v, T *vv, T *vp, T *pvf, T *pvd) { - - // =================================================== - // Purpose: Compute parabolic cylinder functions Vv(x) - // and their derivatives - // Input: x --- Argument of Vv(x) - // v --- Order of Vv(x) - // Output: VV(na) --- Vv(x) - // VP(na) --- Vv'(x) - // ( na = |n|, v = n+v0, |v0| < 1 - // n = 0,±1,±2,… ) - // PVF --- Vv(x) - // PVD --- Vv'(x) - // Routines called: - // (1) VVSA for computing Vv(x) for small |x| - // (2) VVLA for computing Vv(x) for large |x| - // =================================================== - - int ja, k, kv, l, m, na, nv; - T f, f0, f1, pv0, q2p, qe, s0, v0, v1, v2, vh, xa; - - const T pi = 3.141592653589793; - - xa = fabs(x); - vh = v; - v += copysign(1.0, v); - nv = (int) v; - v0 = v - nv; - na = abs(nv); - qe = exp(0.25 * x * x); - q2p = sqrt(2.0 / pi); - ja = 0; - if (na >= 1) { - ja = 1; - } - f = 0.0; - if (v <= 0.0) { - if (v0 == 0.0) { - if (xa <= 7.5) { - pv0 = vvsa(x, v0); - } else { - pv0 = vvla(x, v0); - } - f0 = q2p * qe; - f1 = x * f0; - vv[0] = pv0; - vv[1] = f0; - vv[2] = f1; - } else { - for (l = 0; l <= ja; l++) { - v1 = v0 - l; - if (xa <= 7.5) { - f1 = vvsa(x, v1); - } else { - f1 = vvla(x, v1); - } - if (l == 0) { - f0 = f1; - } - } - vv[0] = f0; - vv[1] = f1; - } - kv = 2; - if (v0 == 0.0) { - kv = 3; - } - for (k = kv; k <= na; k++) { - f = x * f1 + (k - v0 - 2.0) * f0; - vv[k] = f; - f0 = f1; - f1 = f; - } - } else { - if ((x >= 0.0) && (x <= 7.5)) { - v2 = v; - if (v2 < 1.0) { - v2 = v2 + 1.0; - } - f1 = vvsa(x, v2); - v1 = v2 - 1.0; - kv = (int) v2; - f0 = vvsa(x, v1); - vv[kv] = f1; - vv[kv - 1] = f0; - for (k = kv - 2; k >= 0; k--) { - f = x * f0 - (k + v0 + 2.0) * f1; - if (k <= na) { - vv[k] = f; - } - f1 = f0; - f0 = f; - } - } else if (x > 7.5) { - pv0 = vvla(x, v0); - m = 100 + abs(na); - vv[1] = pv0; - f1 = 0.0; - f0 = 1.0e-40; - for (k = m; k >= 0; k--) { - f = x * f0 - (k + v0 + 2.0) * f1; - if (k <= na) { - vv[k] = f; - } - f1 = f0; - f0 = f; - } - s0 = pv0 / f; - for (k = 0; k <= na; k++) { - vv[k] *= s0; - } - } else { - if (xa <= 7.5) { - f0 = vvsa(x, v0); - v1 = v0 + 1.0; - f1 = vvsa(x, v1); - } else { - f0 = vvla(x, v0); - v1 = v0 + 1.0; - f1 = vvla(x, v1); - } - vv[0] = f0; - vv[1] = f1; - for (k = 2; k <= na; k++) { - f = (x * f1 - f0) / (k + v0); - vv[k] = f; - f0 = f1; - f1 = f; - } - } - } - for (k = 0; k < na; k++) { - v1 = v0 + k; - if (v >= 0.0) { - vp[k] = 0.5 * x * vv[k] - (v1 + 1.0) * vv[k + 1]; - } else { - vp[k] = -0.5 * x * vv[k] + vv[k + 1]; - } - } - *pvf = vv[na - 1]; - *pvd = vp[na - 1]; - v = vh; - return; - } - - template - void pbwa(T a, T x, T *w1f, T *w1d, T *w2f, T *w2d) { - - // ====================================================== - // Purpose: Compute parabolic cylinder functions W(a,±x) - // and their derivatives - // Input : a --- Parameter ( 0 ≤ |a| ≤ 5 ) - // x --- Argument of W(a,±x) ( 0 ≤ |x| ≤ 5 ) - // Output : W1F --- W(a,x) - // W1D --- W'(a,x) - // W2F --- W(a,-x) - // W2D --- W'(a,-x) - // Routine called: - // CGAMA for computing complex gamma function - // ====================================================== - - int k, L1, L2; - T d[80], d1, d2, dl, f1, f2, g1, g2, h[100], h0, h1, hl, r, r1, y1d, y2d, y1f, y2f; - std::complex ug, vg; - const T eps = 1e-15; - const T p0 = 0.59460355750136; - - if (a == 0.0) { - g1 = 3.625609908222; - g2 = 1.225416702465; - } else { - ug = specfun::cgama(std::complex(0.25, 0.5 * a), 1); - g1 = std::abs(ug); - vg = specfun::cgama(std::complex(0.75, 0.5 * a), 1); - g2 = std::abs(vg); - } - f1 = sqrt(g1 / g2); - f2 = sqrt(2.0 * g2 / g1); - h0 = 1.0; - h1 = a; - h[0] = a; - for (L1 = 2; L1 <= 100; L1++) { - hl = a * h1 - 0.25 * (2 * L1 - 2.0) * (2 * L1 - 3.0) * h0; - h[L1 - 1] = hl; - h0 = h1; - h1 = hl; - } - y1f = 1.0; - r = 1.0; - for (k = 1; k <= 100; k++) { - r = 0.5 * r * x * x / (k * (2.0 * k - 1.0)); - r1 = h[k - 1] * r; - y1f += r1; - if ((fabs(r1) <= eps * fabs(y1f)) && (k > 30)) { - break; - } - } - y1d = a; - r = 1.0; - for (k = 1; k < 100; k++) { - r = 0.5 * r * x * x / (k * (2.0 * k + 1.0)); - r1 = h[k] * r; - y1d += r1; - if ((fabs(r1) <= eps * fabs(y1d)) && (k > 30)) { - break; - } - } - y1d *= x; - d1 = 1.0; - d2 = a; - d[0] = 1.0; - d[1] = a; - for (L2 = 3; L2 <= 80; L2++) { - dl = a * d2 - 0.25 * ((2 * L2 - 1) - 2.0) * ((2 * L2 - 1) - 3.0) * d1; - d[L2 - 1] = dl; - d1 = d2; - d2 = dl; - } - y2f = 1.0; - r = 1.0; - for (k = 1; k < 80; k++) { - r = 0.5 * r * x * x / (k * (2.0 * k + 1.0)); - r1 = d[k] * r; - y2f += r1; - if ((fabs(r1) <= eps * fabs(y2f)) && (k > 30)) { - break; - } - } - y2f *= x; - y2d = 1.0; - r = 1.0; - for (k = 1; k < 80; k++) { - r = 0.5 * r * x * x / (k * (2.0 * k - 1.0)); - r1 = d[k] * r; - y2d += r1; - if ((fabs(r1) <= eps * fabs(y2d)) && (k > 30)) { - break; - } - } - *w1f = p0 * (f1 * y1f - f2 * y2f); - *w2f = p0 * (f1 * y1f + f2 * y2f); - *w1d = p0 * (f1 * y1d - f2 * y2d); - *w2d = p0 * (f1 * y1d + f2 * y2d); - return; - } - -} // namespace detail - -/* - * If x > 0 return w1f and w1d. Otherwise set x = abs(x) and return - * w2f and -w2d. - */ -template -void pbwa(T a, T x, T &wf, T &wd) { - int flag = 0; - T w1f = 0.0, w1d = 0.0, w2f = 0.0, w2d = 0.0; - - if (x < -5 || x > 5 || a < -5 || a > 5) { - /* - * The Zhang and Jin implementation only uses Taylor series; - * return NaN outside of the range which they are accurate. - */ - wf = std::numeric_limits::quiet_NaN(); - wd = std::numeric_limits::quiet_NaN(); - set_error("pbwa", SF_ERROR_LOSS, NULL); - } else { - if (x < 0) { - x = -x; - flag = 1; - } - detail::pbwa(a, x, &w1f, &w1d, &w2f, &w2d); - if (flag) { - wf = w2f; - wd = -w2d; - } else { - wf = w1f; - wd = w1d; - } - } -} - -template -void pbdv(T v, T x, T &pdf, T &pdd) { - T *dv; - T *dp; - int num; - - if (isnan(v) || isnan(x)) { - pdf = std::numeric_limits::quiet_NaN(); - pdd = std::numeric_limits::quiet_NaN(); - } else { - /* NB. Indexing of DV/DP in specfun.f:PBDV starts from 0, hence +2 */ - num = std::abs((int) v) + 2; - dv = (T *) malloc(sizeof(T) * 2 * num); - if (dv == NULL) { - set_error("pbdv", SF_ERROR_MEMORY, "memory allocation error"); - pdf = std::numeric_limits::quiet_NaN(); - pdd = std::numeric_limits::quiet_NaN(); - } else { - dp = dv + num; - detail::pbdv(x, v, dv, dp, &pdf, &pdd); - free(dv); - } - } -} - -template -void pbvv(T v, T x, T &pvf, T &pvd) { - T *vv; - T *vp; - int num; - - if (isnan(v) || isnan(x)) { - pvf = std::numeric_limits::quiet_NaN(); - pvd = std::numeric_limits::quiet_NaN(); - } else { - /* NB. Indexing of DV/DP in specfun.f:PBVV starts from 0, hence +2 */ - num = std::abs((int) v) + 2; - vv = (T *) malloc(sizeof(T) * 2 * num); - if (vv == NULL) { - set_error("pbvv", SF_ERROR_MEMORY, "memory allocation error"); - pvf = std::numeric_limits::quiet_NaN(); - pvd = std::numeric_limits::quiet_NaN(); - } else { - vp = vv + num; - detail::pbvv(x, v, vv, vp, &pvf, &pvd); - free(vv); - } - } -} - -} // namespace xsf diff --git a/scipy/special/xsf/recur.h b/scipy/special/xsf/recur.h deleted file mode 100644 index 9e9828949d7a..000000000000 --- a/scipy/special/xsf/recur.h +++ /dev/null @@ -1,90 +0,0 @@ -#pragma once - -#include "config.h" - -namespace xsf { - -template -T dot(const T (&x)[N], const T (&y)[N]) { - T res = T(0); - for (size_t i = 0; i < N; ++i) { - res += x[i] * y[i]; - } - - return res; -} - -template -void forward_recur_shift_left(T (&res)[K]) { - for (size_t k = 1; k < K; ++k) { - res[k - 1] = res[k]; - } -} - -template -void forward_recur_rotate_left(T (&res)[K]) { - T tmp = res[0]; - forward_recur_shift_left(res); - res[K - 1] = tmp; -} - -/** - * Compute a forward recurrence that depends on K previous values. - * - * @param first begin iterator - * @param last end iterator - * @param r recurrence - * @param res values, initialised to the leading K values - * @param f a function to be called as f(it, res) - */ -template -void forward_recur(InputIt first, InputIt last, Recurrence r, T (&res)[K], Func f) { - InputIt it = first; - while (it - first != K && it != last) { - forward_recur_rotate_left(res); - - f(it, res); - ++it; - } - - if (last - first > K) { - while (it != last) { - T coef[K]; - r(it, coef); - - T tmp = dot(coef, res); - forward_recur_shift_left(res); - res[K - 1] = tmp; - - f(it, res); - ++it; - } - } -} - -template -void backward_recur(InputIt first, InputIt last, Recurrence r, T (&res)[K], Func f) { - InputIt it = first; - while (std::abs(it - first) != K && it != last) { - forward_recur_rotate_left(res); - - f(it, res); - --it; - } - - if (std::abs(last - first) > K) { - while (it != last) { - T coef[K]; - r(it, coef); - - T tmp = dot(coef, res); - forward_recur_shift_left(res); - res[K - 1] = tmp; - - f(it, res); - --it; - } - } -} - -} // namespace xsf diff --git a/scipy/special/xsf/sici.h b/scipy/special/xsf/sici.h deleted file mode 100644 index 4d26b64e02aa..000000000000 --- a/scipy/special/xsf/sici.h +++ /dev/null @@ -1,200 +0,0 @@ -/* Translated from Cython into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* Implementation of sin/cos/sinh/cosh integrals for complex arguments - * - * Sources - * [1] Fredrik Johansson and others. mpmath: a Python library for - * arbitrary-precision floating-point arithmetic (version 0.19), - * December 2013. http://mpmath.org/. - * [2] NIST, "Digital Library of Mathematical Functions", - * https://dlmf.nist.gov/ - */ - -#pragma once - -#include "config.h" -#include "error.h" - -#include "expint.h" -#include "cephes/const.h" -#include "cephes/sici.h" -#include "cephes/shichi.h" - -namespace xsf { -namespace detail { - - XSF_HOST_DEVICE inline void sici_power_series(int sgn, std::complex z, - std::complex *s, std::complex *c) { - /* DLMF 6.6.5 and 6.6.6. If sgn = -1 computes si/ci, and if sgn = 1 - * computes shi/chi. - */ - std::complex fac = z; - *s = fac; - *c = 0; - std::complex term1, term2; - for (int n = 1; n < 100; n++) { - fac *= static_cast(sgn)*z/(2.0*n); - term2 = fac/(2.0*n); - *c += term2; - fac *= z/(2.0*n + 1.0); - term1 = fac/(2.0*n + 1.0); - *s += term1; - constexpr double tol = std::numeric_limits::epsilon(); - if (std::abs(term1) < tol*std::abs(*s) && std::abs(term2) < tol*std::abs(*c)) { - break; - } - } - } - -} - - -XSF_HOST_DEVICE inline int sici(std::complex z, - std::complex *si, std::complex *ci) { - /* Compute sin/cos integrals at complex arguments. The algorithm - * largely follows that of [1]. - */ - - constexpr double EULER = xsf::cephes::detail::SCIPY_EULER; - - if (z == std::numeric_limits::infinity()) { - *si = M_PI_2; - *ci = 0; - return 0; - } - if (z == -std::numeric_limits::infinity()) { - *si = -M_PI_2; - *ci = {0.0, M_PI}; - return 0; - } - - if (std::abs(z) < 0.8) { - // Use the series to avoid cancellation in si - detail::sici_power_series(-1, z, si, ci); - - if (z == 0.0) { - set_error("sici", SF_ERROR_DOMAIN, NULL); - *ci = {-std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN()}; - } else { - *ci += EULER + std::log(z); - } - return 0; - } - - // DLMF 6.5.5/6.5.6 plus DLMF 6.4.4/6.4.6/6.4.7 - std::complex jz = std::complex(0.0, 1.0) * z; - std::complex term1 = expi(jz); - std::complex term2 = expi(-jz); - *si = std::complex(0.0, -0.5)*(term1 - term2); - *ci = 0.5*(term1 + term2); - if (z.real() == 0) { - if (z.imag() > 0) { - *ci += std::complex(0.0, M_PI_2); - } else if (z.imag() < 0) { - *ci -= std::complex(0.0, M_PI_2); - } - } else if (z.real() > 0) { - *si -= M_PI_2; - } else { - *si += M_PI_2; - if (z.imag() >= 0) { - *ci += std::complex(0.0, M_PI); - } else { - *ci -= std::complex(0.0, M_PI); - } - } - return 0; -} - -XSF_HOST_DEVICE inline int sici(std::complex z, - std::complex *si_f, std::complex *ci_f) { - std::complex si; - std::complex ci; - int res = sici(z, &si, &ci); - *si_f = si; - *ci_f = ci; - return res; -} - -XSF_HOST_DEVICE inline int shichi(std::complex z, - std::complex *shi, std::complex *chi) { - /* Compute sinh/cosh integrals at complex arguments. The algorithm - * largely follows that of [1]. - */ - constexpr double EULER = xsf::cephes::detail::SCIPY_EULER; - if (z == std::numeric_limits::infinity()) { - *shi = std::numeric_limits::infinity(); - *chi = std::numeric_limits::infinity(); - return 0; - } - if (z == -std::numeric_limits::infinity()) { - *shi = -std::numeric_limits::infinity(); - *chi = std::numeric_limits::infinity(); - return 0; - } - if (std::abs(z) < 0.8) { - // Use the series to avoid cancellation in shi - detail::sici_power_series(1, z, shi, chi); - if (z == 0.0) { - set_error("shichi", SF_ERROR_DOMAIN, NULL); - *chi = {-std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN()}; - } else { - *chi += EULER + std::log(z); - } - return 0; - } - - std::complex term1 = expi(z); - std::complex term2 = expi(-z); - *shi = 0.5*(term1 - term2); - *chi = 0.5*(term1 + term2); - if (z.imag() > 0) { - *shi -= std::complex(0.0, 0.5*M_PI); - *chi += std::complex(0.0, 0.5*M_PI); - } else if (z.imag() < 0) { - *shi += std::complex(0.0, 0.5*M_PI); - *chi -= std::complex(0.0, 0.5*M_PI); - } else if (z.real() < 0) { - *chi += std::complex(0.0, M_PI); - } - return 0; -} - -XSF_HOST_DEVICE inline int shichi(std::complex z, - std::complex *shi_f, std::complex *chi_f) { - std::complex shi; - std::complex chi; - int res = shichi(z, &shi, &chi); - *shi_f = shi; - *chi_f = chi; - return res; -} - -XSF_HOST_DEVICE inline int sici(double x, double *si, double *ci) { - return cephes::sici(x, si, ci); -} - -XSF_HOST_DEVICE inline int shichi(double x, double *shi, double *chi) { - return cephes::shichi(x, shi, chi); -} - -XSF_HOST_DEVICE inline int sici(float x, float *si_f, float *ci_f) { - double si; - double ci; - int res = cephes::sici(x, &si, &ci); - *si_f = si; - *ci_f = ci; - return res; -} - -XSF_HOST_DEVICE inline int shichi(float x, float *shi_f, float *chi_f) { - double shi; - double chi; - int res = cephes::shichi(x, &shi, &chi); - *shi_f = shi; - *chi_f = chi; - return res; -} -} diff --git a/scipy/special/xsf/specfun.h b/scipy/special/xsf/specfun.h deleted file mode 100644 index 305416f3e9b4..000000000000 --- a/scipy/special/xsf/specfun.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -#include "error.h" -#include "specfun/specfun.h" - -#define SPECFUN_ZCONVINF(func, z) \ - do { \ - if ((double) (z).real() == (double) 1.0e300) { \ - set_error(func, SF_ERROR_OVERFLOW, NULL); \ - (z).real(std::numeric_limits::infinity()); \ - } \ - if ((double) (z).real() == (double) -1.0e300) { \ - set_error(func, SF_ERROR_OVERFLOW, NULL); \ - (z).real(-std::numeric_limits::infinity()); \ - } \ - } while (0) -#define SPECFUN_CONVINF(func, x) \ - do { \ - if ((double) (x) == (double) 1.0e300) { \ - set_error(func, SF_ERROR_OVERFLOW, NULL); \ - (x) = std::numeric_limits::infinity(); \ - } \ - if ((double) (x) == (double) -1.0e300) { \ - set_error(func, SF_ERROR_OVERFLOW, NULL); \ - (x) = -std::numeric_limits::infinity(); \ - } \ - } while (0) - -namespace xsf { - -inline std::complex chyp2f1(double a, double b, double c, std::complex z) { - int l0 = ((c == floor(c)) && (c < 0)); - int l1 = ((fabs(1 - z.real()) < 1e-15) && (z.imag() == 0) && (c - a - b <= 0)); - if (l0 || l1) { - set_error("chyp2f1", SF_ERROR_OVERFLOW, NULL); - return std::numeric_limits::infinity(); - } - - int isfer = 0; - std::complex outz = specfun::hygfz(a, b, c, z, &isfer); - if (isfer == 3) { - set_error("chyp2f1", SF_ERROR_OVERFLOW, NULL); - outz.real(std::numeric_limits::infinity()); - outz.imag(0.0); - } else if (isfer == 5) { - set_error("chyp2f1", SF_ERROR_LOSS, NULL); - } else if (isfer != 0) { - set_error("chyp2f1", static_cast(isfer), NULL); - outz.real(std::numeric_limits::quiet_NaN()); - outz.imag(std::numeric_limits::quiet_NaN()); - } - return outz; -} - -inline std::complex chyp1f1(double a, double b, std::complex z) { - std::complex outz = specfun::cchg(a, b, z); - if (outz.real() == 1e300) { - set_error("chyp1f1", SF_ERROR_OVERFLOW, NULL); - outz.real(std::numeric_limits::infinity()); - } - - return outz; -} - -inline double hypu(double a, double b, double x) { - double out; - int md; /* method code --- not returned */ - int isfer = 0; - - out = specfun::chgu(x, a, b, &md, &isfer); - if (out == 1e300) { - set_error("hypU", SF_ERROR_OVERFLOW, NULL); - out = std::numeric_limits::infinity(); - } - if (isfer == 6) { - set_error("hypU", SF_ERROR_NO_RESULT, NULL); - out = std::numeric_limits::quiet_NaN(); - } else if (isfer != 0) { - set_error("hypU", static_cast(isfer), NULL); - out = std::numeric_limits::quiet_NaN(); - } - return out; -} - -inline double hyp1f1(double a, double b, double x) { - double outy; - - outy = specfun::chgm(x, a, b); - return outy; -} - -inline std::complex cerf(std::complex z) { return specfun::cerror(z); } - -inline double pmv(double m, double v, double x) { - int int_m; - double out; - - if (m != floor(m)) { - return std::numeric_limits::quiet_NaN(); - } - int_m = (int) m; - out = specfun::lpmv(x, int_m, v); - SPECFUN_CONVINF("pmv", out); - return out; -} - -} // namespace special diff --git a/scipy/special/xsf/specfun/specfun.h b/scipy/special/xsf/specfun/specfun.h deleted file mode 100644 index dd90091082a7..000000000000 --- a/scipy/special/xsf/specfun/specfun.h +++ /dev/null @@ -1,5894 +0,0 @@ -/* - * - * This file accompanied with the header file specfun.h is a partial - * C translation of the Fortran code by Zhang and Jin following - * original description: - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * COMPUTATION OF SPECIAL FUNCTIONS - * - * Shanjie Zhang and Jianming Jin - * - * Copyrighted but permission granted to use code in programs. - * Buy their book: - * - * Shanjie Zhang, Jianming Jin, - * Computation of Special Functions, - * Wiley, 1996, - * ISBN: 0-471-11963-6, - * LC: QA351.C45. - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * - * Scipy changes: - * - Compiled into a single source file and changed REAL To DBLE throughout. - * - Changed according to ERRATA. - * - Changed GAMMA to GAMMA2 and PSI to PSI_SPEC to avoid potential conflicts. - * - Made functions return sf_error codes in ISFER variables instead - * of printing warnings. The codes are - * - SF_ERROR_OK = 0: no error - * - SF_ERROR_SINGULAR = 1: singularity encountered - * - SF_ERROR_UNDERFLOW = 2: floating point underflow - * - SF_ERROR_OVERFLOW = 3: floating point overflow - * - SF_ERROR_SLOW = 4: too many iterations required - * - SF_ERROR_LOSS = 5: loss of precision - * - SF_ERROR_NO_RESULT = 6: no result obtained - * - SF_ERROR_DOMAIN = 7: out of domain - * - SF_ERROR_ARG = 8: invalid input parameter - * - SF_ERROR_OTHER = 9: unclassified error - * - Improved initial guesses for roots in JYZO. - * - * - */ - -/* - * Copyright (C) 2024 SciPy developers - * - * 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. Names of the SciPy Developers may not 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 COPYRIGHT HOLDERS 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. - */ - -#pragma once - -#include -#include "../config.h" - -namespace xsf { -namespace specfun { - -// The Status enum is the return type of a few private, low-level functions -// defined here. Currently the only use is by functions that allocate -// memory internally. If the allocation fails, the function returns -// Status::NoMemory. - -enum class Status { - OK = 0, - NoMemory, - Other -}; - -void airyb(double, double*, double*, double*, double*); -void bjndd(double, int, double *, double *, double *); - -void cerzo(int, std::complex *); - -void cyzo(int, int, int, std::complex*, std::complex *); - -void cerf(std::complex, std::complex *, std::complex *); -std::complex cgama(std::complex, int); -double chgubi(double, double, double, int *); -double chguit(double, double, double, int *); -double chgul(double, double, double, int *); -double chgus(double, double, double, int *); -void cpbdn(int, std::complex, std::complex *, std::complex *); -std::complex cpdla(int, std::complex); -std::complex cpdsa(int, std::complex); -double cv0(double, double, double); -double cvf(int, int, double, double, int); -double cvql(int, int, double); -double cvqm(int, double); -double gaih(double); -double gam0(double); -double gamma2(double); - -template -void jynbh(int, int, T, int *, T *, T *); - -void jyndd(int, double, double *, double *, double *, double *, double *, double *); - -double lpmv0(double, int, double); -int msta1(double, int); -int msta2(double, int, int); -double psi_spec(double); -double refine(int, int, double, double); - -template -void sckb(int, int, T, T *, T *); - -template -Status sdmn(int, int, T, T, int, T *); - -template -void sphj(T, int, int *, T *, T *); - -template -void sphy(T, int, int *, T *, T *); - -template -Status aswfa(T x, int m, int n, T c, int kd, T cv, T *s1f, T *s1d) { - - // =========================================================== - // Purpose: Compute the prolate and oblate spheroidal angular - // functions of the first kind and their derivatives - // Input : m --- Mode parameter, m = 0,1,2,... - // n --- Mode parameter, n = m,m+1,... - // c --- Spheroidal parameter - // x --- Argument of angular function, |x| < 1.0 - // KD --- Function code - // KD=1 for prolate; KD=-1 for oblate - // cv --- Characteristic value - // Output: S1F --- Angular function of the first kind - // S1D --- Derivative of the angular function of - // the first kind - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // - // Routine called: - // SDMN for computing expansion coefficients df - // SCKB for computing expansion coefficients ck - // =========================================================== - - int ip, k, nm, nm2; - T a0, d0, d1, r, su1, su2, x0, x1; - auto ck = std::unique_ptr{new (std::nothrow) T[200]()}; - auto df = std::unique_ptr{new (std::nothrow) T[200]()}; - if (ck == nullptr || df == nullptr) { - return Status::NoMemory; - } - const T eps = 1e-14; - x0 = x; - x = fabs(x); - ip = ((n-m) % 2 == 0 ? 0 : 1); - nm = 40 + (int)((n-m)/2 + c); - nm2 = nm/2 - 2; - if (sdmn(m, n, c, cv, kd, df.get()) == Status::NoMemory) { - return Status::NoMemory; - } - sckb(m, n, c, df.get(), ck.get()); - x1 = 1.0 - x*x; - if ((m == 0) && (x1 == 0.0)) { - a0 = 1.0; - } else { - a0 = pow(x1, 0.5*m); - } - su1 = ck[0]; - for (k = 1; k <= nm2; k++) { - r = ck[k]*pow(x1, k); - su1 += r; - if ((k >= 10) && (fabs(r/su1) < eps)) { break; } - } - *s1f = a0*pow(x, ip)*su1; - if (x == 1.0) { - if (m == 0) { - *s1d = ip*ck[0] - 2.0*ck[1]; - } else if (m == 1) { - *s1d = -1e100; - } else if (m == 2) { - *s1d = -2.0*ck[0]; - } else if (m >= 3) { - *s1d = 0.0; - } - } else { - d0 = ip - m/x1*pow(x, ip+1.0); - d1 = -2.0*a0*pow(x, ip+1.0); - su2 = ck[1]; - for (k = 2; k <= nm2; k++) { - r = k*ck[k]*pow(x1, (k-1.0)); - su2 += r; - if ((k >= 10) && (fabs(r/su2) < eps)) { break; } - } - *s1d = d0*a0*su1 + d1*su2; - } - if ((x0 < 0.0) && (ip == 0)) { *s1d = -*s1d; } - if ((x0 < 0.0) && (ip == 1)) { *s1f = -*s1f; } - x = x0; - return Status::OK; -} - - -inline void bernob(int n, double *bn) { - - // ====================================== - // Purpose: Compute Bernoulli number Bn - // Input : n >= 3 --- Serial number - // Output: BN(n) --- Bn - // ====================================== - - int k, m; - double r1, r2, s; - const double tpi = 6.283185307179586; - - bn[0] = 1.0; - bn[1] = -0.5; - bn[2] = 1.0 / 6.0; - r1 = pow(2.0 / tpi, 2); - for ( m = 4; m < (n+1); m += 2) { - r1 = -r1 * (m-1)*m/(tpi*tpi); - r2 = 1.0; - for (k = 2; k < 10001; k++) { - s = pow(1.0/k, m); - r2 += s; - if (s < 1e-15) { break; } - } - bn[m] = r1*r2; - } - return; -} - - -inline void bjndd(double x, int n, double *bj, double *dj, double *fj) { - - // ===================================================== - // Purpose: Compute Bessel functions Jn(x) and their - // first and second derivatives ( 0 <= n <= 100) - // Input: x --- Argument of Jn(x) ( x ≥ 0 ) - // n --- Order of Jn(x) - // Output: BJ(n+1) --- Jn(x) - // DJ(n+1) --- Jn'(x) - // FJ(n+1) --- Jn"(x) - // ===================================================== - - int k, m, mt; - double bs = 0.0, f = 0.0, f0 = 0.0, f1 = 1e-35; - - for (m = 1; m < 901; m++) { - mt = (int)(0.5*log10(6.28*m)-m*log10(1.36*fabs(x)/m)); - if (mt > 20) { break; } - } - if (m == 901) { m -= 1; } - for (k = m; k > -1; k--) { - f = 2.0*(k+1.0)*f1/x - f0; - if (k <= n) { bj[k] = f; } - if (k % 2 == 0) { bs += 2.0*f; } - f0 = f1; - f1 = f; - } - for (k = 0; k < (n+1); k++) { - bj[k] /= (bs - f); - } - dj[0] = -bj[1]; - fj[0] = -bj[0] - dj[0]/x; - for (k = 1; k < (n+1); k++) { - dj[k] = bj[k-1] - k*bj[k]/x; - fj[k] = (k*k/(x*x)-1.0)*bj[k] - dj[k]/x; - } - return; -} - - -template -Status cbk(int m, int n, T c, T cv, T qt, T *ck, T *bk) { - - // ========================================================== - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // ========================================================== - - const T eps = 1.0e-14; - - int i, i1, ip, j, k, n2, nm; - T r1, s1, sw, t; - - ip = ((n - m) % 2 == 0 ? 0 : 1); - nm = 25 + (int)(0.5 * (n - m) + c); - - auto u = std::unique_ptr{new (std::nothrow) T[200]()}; - auto v = std::unique_ptr{new (std::nothrow) T[200]()}; - auto w = std::unique_ptr{new (std::nothrow) T[200]()}; - if (u.get() == nullptr || v.get() == nullptr || w.get() == nullptr) { - return Status::NoMemory; - } - - u[0] = 0.0; - n2 = nm - 2; - - for (j = 1; j < n2 + 1; j++) { - u[j] = c * c; - } - for (j = 1; j < n2 + 1; j++) { - v[j - 1] = (2.0 * j - 1.0 - ip) * (2.0 * (j - m) - ip) + m * (m - 1.0) - cv; - } - for (j = 1; j < nm; j++) - w[j - 1] = (2.0 * j - ip) * (2.0 * j + 1.0 - ip); - - if (ip == 0) { - sw = 0.0; - for (k = 0; k < n2; k++) { - s1 = 0.0; - i1 = k - m + 1; - - for (i = i1; i < nm + 1; i++) { - if (i < 0) { continue; } - r1 = 1.0; - for (j = 1; j <= k; j++) { - r1 = r1*(i + m - j) / (1.0*j); - } - s1 += ck[i] * (2.0 * i + m) * r1; - if (fabs(s1 - sw) < fabs(s1) * eps) { break; } - sw = s1; - } - bk[k] = qt * s1; - } - } else if (ip == 1) { - sw = 0.0; - for (k = 0; k < n2; k++) { - s1 = 0.0; - i1 = k - m + 1; - - for (int i = i1; i < nm + 1; i++) { - if (i < 0) { continue; } - r1 = 1.0; - for (j = 1; j <= k; j++) { - r1 = r1* (i + m - j) / (1.0*j); - } - if (i > 0) { - s1 += ck[i - 1] * (2.0 * i + m - 1) * r1; - } - s1 -= ck[i] * (2.0 * i + m) * r1; - - if (fabs(s1 - sw) < fabs(s1) * eps) { break; } - sw = s1; - } - bk[k] = qt * s1; - } - } - - w[0] /= v[0]; - bk[0] /= v[0]; - - for (k = 2; k <= n2; k++) { - t = v[k - 1] - w[k - 2] * u[k - 1]; - w[k - 1] /= t; - bk[k - 1] = (bk[k - 1] - bk[k - 2] * u[k - 1]) / t; - } - - for (k = n2 - 1; k >= 1; k--) { - bk[k - 1] -= w[k - 1] * bk[k]; - } - return Status::OK; -} - - -inline void cerf(std::complex z, std::complex *cer, std::complex *cder) { - - // ========================================================== - // Purpose: Compute complex Error function erf(z) & erf'(z) - // Input: z --- Complex argument of erf(z) - // x --- Real part of z - // y --- Imaginary part of z - // Output: CER --- erf(z) - // CDER --- erf'(z) - // ========================================================== - - int k; - double c0, cs, er0, er, er1, ei1, er2, ei2, err, eri, r, ss, w, w1, w2; - const double eps = 1.0e-12; - const double pi = 3.141592653589793; - - double x = z.real(); - double y = z.imag(); - double x2 = x * x; - - if (x <= 3.5) { - er = 1.0; - r = 1.0; - w = 0.0; - - for (k = 1; k <= 100; k++) { - r = r * x2 / (k + 0.5); - er += r; - if (fabs(er - w) <= eps * fabs(er)) - break; - w = er; - } - - c0 = 2.0 / sqrt(pi) * x * exp(-x2); - er0 = c0 * er; - *cer = er0; - } else { - er = 1.0; - r = 1.0; - - for (k = 1; k <= 12; k++) { - r = -r * (k - 0.5) / x2; - er += r; - } - - c0 = exp(-x2) / (x * sqrt(pi)); - er0 = 1.0 - c0 * er; - *cer = er0; - } - - if (y == 0.0) { - err = cer->real(); - eri = 0.0; - *cer = std::complex(err, eri); - } else { - cs = cos(2.0 * x * y); - ss = sin(2.0 * x * y); - er1 = exp(-x2) * (1.0 - cs) / (2.0 * pi * x); - ei1 = exp(-x2) * ss / (2.0 * pi * x); - er2 = 0.0; - w1 = 0.0; - - for (int n = 1; n <= 100; n++) { - er2 += exp(-0.25 * n * n) / (n * n + 4.0 * x2) * (2.0 * x - 2.0 * x * cosh(n * y) * cs + n * sinh(n * y) * ss); - if (fabs((er2 - w1) / er2) < eps) - break; - w1 = er2; - } - - c0 = 2.0 * exp(-x2) / pi; - err = cer->real() + er1 + c0 * er2; - ei2 = 0.0; - w2 = 0.0; - - for (int n = 1; n <= 100; n++) { - ei2 += exp(-0.25 * n * n) / (n * n + 4.0 * x2) * (2.0 * x * cosh(n * y) * ss + n * sinh(n * y) * cs); - if (fabs((ei2 - w2) / ei2) < eps) - break; - w2 = ei2; - } - *cer = std::complex(err, ei1 + c0 * ei2); - } - *cder = 2.0 / sqrt(pi) * std::exp(-z*z); - -} - - -inline std::complex cerror(std::complex z) { - - // ==================================================== - // Purpose: Compute error function erf(z) for a complex - // argument (z=x+iy) - // Input : z --- Complex argument - // Output: CER --- erf(z) - // ==================================================== - - int k; - std::complex cer, cl, cr, cs, z1; - std::complex c0 = std::exp(-z*z); - const double sqpi = 1.7724538509055160273; - z1 = z; - if (z.real() < 0.0) { z1 = -z; } - // Cutoff radius R = 4.36; determined by balancing rounding error - // and asymptotic expansion error, see below. - // - // The resulting maximum global accuracy expected is around 1e-8 - // - if (std::abs(z) <= 4.36) { - // Rounding error in the Taylor expansion is roughly - // ~ R*R * EPSILON * R**(2 R**2) / (2 R**2 Gamma(R**2 + 1/2)) - cs = z1; - cr = z1; - for (k = 1; k < 121; k++) { - cr = cr*(z1*z1) / (k+0.5); - cs += cr; - if (std::abs(cr/cs) < 1e-15) { break; } - } - cer = 2.0*c0*cs/sqpi; - } else { - cl = 1.0 / z1; - cr = cl; - // Asymptotic series; maximum K must be at most ~ R^2. - // - // The maximum accuracy obtainable from this expansion is roughly - // - // ~ Gamma(2R**2 + 2) / ( - // (2 R**2)**(R**2 + 1/2) Gamma(R**2 + 3/2) 2**(R**2 + 1/2)) - for (k = 1; k < 21; k++) { - cr = -cr*(k-0.5) / (z1*z1); - cl += cr; - if (std::abs(cr/cl) < 1e-15) { break; } - } - cer = 1.0 - c0*cl/sqpi; - } - if (z.real() < 0.0) { cer = -cer; } - return cer; -} - - -inline void cerzo(int nt, std::complex *zo) { - - // =============================================================== - // Purpose : Evaluate the complex zeros of error function erf(z) - // using the modified Newton's iteration method - // Input : NT --- Total number of zeros - // Output: ZO(L) --- L-th zero of erf(z), L=1,2,...,NT - // Routine called: CERF for computing erf(z) and erf'(z) - // =============================================================== - - int i, j, nr, it = 0; - double pu, pv, px, py, w0; - std::complex z, zf, zd, zp, zw, zq, zfd, zgd; - double w = 0.0; - const double pi = 3.141592653589793; - - for (nr = 1; nr <= nt; nr++) { - pu = sqrt(pi * (4.0 * nr - 0.5)); - pv = pi * sqrt(2.0 * nr - 0.25); - px = 0.5 * pu - 0.5 * log(pv) / pu; - py = 0.5 * pu + 0.5 * log(pv) / pu; - z = std::complex(px, py); - it = 0; - - do { - it++; - cerf(z, &zf, &zd); - zp = 1.0; - - for (i = 1; i < nr; i++) { - zp *= (z - zo[i - 1]); - } - zfd = zf / zp; - zq = 0.0; - for (i = 1; i < nr; i++) { - zw = 1.0; - for (j = 1; j < nr; j++) { - if (j == i) continue; - zw *= (z - zo[j - 1]); - } - zq += zw; - } - zgd = (zd - zq * zfd) / zp; - z -= zfd / zgd; - w0 = w; - w = std::abs(z); - } while ((it <= 50) && (fabs((w - w0) / w) > 1.0e-11)); - zo[nr - 1] = z; - } - return; -} - -inline std::complex cchg(double a, double b, std::complex z) { - - // =================================================== - // Purpose: Compute confluent hypergeometric function - // M(a,b,z) with real parameters a, b and a - // complex argument z - // Input : a --- Parameter - // b --- Parameter - // z --- Complex argument - // Output: CHG --- M(a,b,z) - // Routine called: CGAMA for computing complex ln[Г(x)] - // =================================================== - - int i, j, k, la, m, n, nl, ns; - double a0, a1, phi, x0, x, y; - std::complex cfac, cg1, cg2, cg3, chg, chg1, chg2, chw, cr, cr1, cr2, cs1, - cs2, crg, cy0, cy1, z0; - const double pi = 3.141592653589793; - const std::complex ci(0.0, 1.0); - a0 = a; - a1 = a; - z0 = z; - cy0 = 0.0; - cy1 = 0.0; - if ((b == 0.0) || (b == -(int)fabs(b))) { return 1e300; } - if ((a == 0.0) || (z == 0.0)) { return 1.0; } - if (a == -1.0) { return 1.0 - z/b; } - if (a == b) { return std::exp(z); } - if (a - b == 1.0) { return (1.0 + z/b)*std::exp(z); } - if ((a == 1.0) && (b == 2.0)) { return (std::exp(z)-1.0) / z; } - if ((a == (int)a) && (a < 0.0)) { - m = (int)(-a); - cr = 1.0; - chg = 1.0; - for (k = 1; k < (m+1); k++) { - cr = cr * (a+k-1.0)/static_cast(k)/(b+k-1.0)*z; - chg += cr; - } - } else { - x0 = z.real(); - if (x0 < 0.0) { - a = b-a; - a0 = a; - z = -z; - } - nl = 0; - la = 0; - if (a >= 2.0) { - nl = 1; - la = (int)a; - a -= la + 1; - } - ns = 0; - for (n = 0; n < (nl+1); n++) { - if (a0 >= 2.0) { a += 1.0; } - if ((std::abs(z) < 20.0+fabs(b)) || (a < 0.0)) { - chg = 1.0; - chw = 0.0; - crg = 1.0; - for (j = 1; j < 501; j++) { - crg = crg * (a+j-1.0)/(j*(b+j-1.0))*z; - chg += crg; - if (std::abs((chg-chw)/chg) < 1e-15) { break; } - chw = chg; - } - } else { - y = 0.0; - cg1 = cgama(a, 0); - cg2 = cgama(b, 0); - cg3 = cgama(b-a, 0); - cs1 = 1.0; - cs2 = 1.0; - cr1 = 1.0; - cr2 = 1.0; - for (i = 1; i <= 8; i++) { - cr1 = -cr1 * (a+i-1.0)*(a-b+i)/(z*static_cast(i)); - cr2 = cr2 * (b-a+i-1.0)*(i-a)/(z*static_cast(i)); - cs1 += cr1; - cs2 += cr2; - } - x = z.real(); - y = z.imag(); - if ((x == 0.0) && (y >= 0.0)) { - phi = 0.5*pi; - } else if ((x == 0.0) && (y <= 0.0)) { - phi = -0.5*pi; - } else { - phi = atan(y/x); - } - if ((phi > -0.5*pi) && (phi < 1.5*pi)) { ns = 1; } - if ((phi > -1.5*pi) && (phi <= -0.5*pi)) { ns = -1; } - cfac = std::exp(static_cast(ns)*ci*pi*a); - if (y == 0.0) { cfac = cos(pi*a); } - chg1 = std::exp(cg2-cg3)*std::pow(z, -a)*cfac*cs1; - chg2 = std::exp(cg2-cg1+z)*std::pow(z, a-b)*cs2; - chg = chg1 + chg2; - } - if (n == 0) { cy0 = chg; } - if (n == 1) { cy1 = chg; } - } - if (a0 >= 2.0) { - for (i = 1; i < la; i++) { - chg = ((2.0*a-b+z)*cy1 + (b-a)*cy0)/a; - cy0 = cy1; - cy1 = chg; - a += 1.0; - } - } - if (x0 < 0.0) { chg *= std::exp(-z); } - } - a = a1; - z = z0; - return chg; -} - - -inline std::complex cgama(std::complex z, int kf) { - - // ========================================================= - // Purpose: Compute the gamma function Г(z) or ln[Г(z)] - // for a complex argument - // Input : z --- Complex argument - // kf --- Function code - // kf=0 for ln[Г(z)] - // kf=1 for Г(z) - // Output: g --- ln[Г(z)] or Г(z) - // ======================================================== - - std::complex g, z1; - double az0, az1, gi, gi1, gr, gr1, t, th, th1, th2, sr, si, x0, xx, yy; - int j, k, na; - const double pi = 3.141592653589793; - static const double a[10] = { - 8.333333333333333e-02, -2.777777777777778e-03, - 7.936507936507937e-04, -5.952380952380952e-04, - 8.417508417508418e-04, -1.917526917526918e-03, - 6.410256410256410e-03, -2.955065359477124e-02, - 1.796443723688307e-01, -1.392432216905900e+00 - }; - xx = z.real(); - yy = z.imag(); - if ((yy == 0.0) && (xx <= 0.0) && (xx == (int)xx)) { - return 1e300; - } else if (xx < 0.0) { - z1 = z; - z = -z; - xx = -xx; - yy = -yy; - } else { - z1 = std::complex(xx, 0.0); - } - x0 = xx; - na = 0; - if (xx <= 7.0) { - na = (int)(7 - xx); - x0 = xx + na; - } - az0 = std::abs(std::complex(x0, yy)); - th = atan(yy / x0); - gr = (x0 - 0.5)*log(az0) - th*yy - x0 + 0.5*log(2.0*pi); - gi = th*(x0 - 0.5) + yy*log(az0) - yy; - for (k = 1; k < 11; k++) { - t = pow(az0, 1-2*k); - gr += a[k - 1]*t*cos((2.0*k - 1.0)*th); - gi += -a[k - 1]*t*sin((2.0*k - 1.0)*th); - } - if (xx <= 7.0) { - gr1 = 0.0; - gi1 = 0.0; - for (j = 0; j < na; j++) { - gr1 += 0.5*log(pow(xx + j, 2) + yy*yy); - gi1 += atan(yy/(xx + j)); - } - gr -= gr1; - gi -= gi1; - } - if (z1.real() < 0.0) { - az0 = std::abs(z); - th1 = atan(yy/xx); - sr = -sin(pi*xx)*cosh(pi*yy); - si = -cos(pi*xx)*sinh(pi*yy); - az1 = std::abs(std::complex(sr, si)); - th2 = atan(si/sr); - if (sr < 0.0) { - th2 += pi; - } - gr = log(pi/(az0*az1)) - gr; - gi = - th1 - th2 - gi; - z = z1; - } - if (kf == 1) { - g = exp(gr)*std::complex(cos(gi), sin(gi)); - } else { - g = std::complex(gr, gi); - } - return g; -} - - -inline double chgm(double x, double a, double b) { - - // =================================================== - // Purpose: Compute confluent hypergeometric function - // M(a,b,x) - // Input : a --- Parameter - // b --- Parameter ( b <> 0,-1,-2,... ) - // x --- Argument - // Output: HG --- M(a,b,x) - // Routine called: CGAMA for computing complex ln[Г(x)] - // =================================================== - - int i, j, la, n, nl; - double a0 = a, a1 = a, x0 = x, y0, y1, hg1, hg2, r1, r2, rg, xg, sum1, sum2; - std::complex cta, ctb, ctba; - const double pi = 3.141592653589793; - double hg = 0.0; - - // DLMF 13.2.39 - if (x < 0.0) { - a = b - a; - a0 = a; - x = fabs(x); - } - nl = 0; - la = 0; - if (a >= 2.0) { - // preparing terms for DLMF 13.3.1 - nl = 1; - la = (int)a; - a -= la+1; - } - y0 = 0.0; - y1 = 0.0; - for (n = 0; n < (nl + 1); n++) { - if (a0 >= 2.0) { a += 1.0; } - if ((x <= 30.0 + fabs(b)) || (a < 0.0)) { - hg = 1.0; - rg = 1.0; - for (j = 1; j < 501; j++) { - rg = rg * (a + j - 1.0) / (j*(b + j - 1.0))*x; - hg += rg; - if ((hg != 0.0) && (fabs(rg/hg) < 1e-15)) { - // DLMF 13.2.39 (cf. above) - if (x0 < 0.0) { hg *= exp(x0); } - break; - } - } - } else { - // DLMF 13.7.2 & 13.2.4, SUM2 corresponds to first sum - cta = cgama(a, 0); - ctb = cgama(b, 0); - xg = b-a; - ctba = cgama(xg, 0); - sum1 = 1.0; - sum2 = 1.0; - r1 = 1.0; - r2 = 1.0; - for (i = 1; i < 9; i++) { - r1 = -r1*(a+i-1.0)*(a-b+i)/(x*i); - r2 = -r2*(b-a+i-1.0)*(a-i)/(x*i); - sum1 += r1; - sum2 += r2; - } - if (x0 >= 0.0) { - hg1 = (std::exp(ctb-ctba)).real()*pow(x, -a)*cos(pi*a)*sum1; - hg2 = (std::exp(ctb-cta+x)).real()*pow(x, a-b)*sum2; - } else { - // DLMF 13.2.39 (cf. above) - hg1 = (std::exp(ctb-ctba+x0)).real()*pow(x, -a)*cos(pi*a)*sum1; - hg2 = (std::exp(ctb-cta)).real()*pow(x, a-b)*sum2; - } - hg = hg1 + hg2; - } - /* 25 */ - if (n == 0) { y0 = hg; } - if (n == 1) { y1 = hg; } - } - if (a0 >= 2.0) { - // DLMF 13.3.1 - for (i = 1; i < la; i++) { - hg = ((2.0*a - b + x)*y1 + (b - a)*y0) / a; - y0 = y1; - y1 = hg; - a += 1.0; - } - } - a = a1; - x = x0; - return hg; -} - - -inline double chgu(double x, double a, double b, int *md, int *isfer) { - - // ======================================================= - // Purpose: Compute the confluent hypergeometric function - // U(a,b,x) - // Input : a --- Parameter - // b --- Parameter - // x --- Argument ( x > 0 ) - // Output: HU --- U(a,b,x) - // MD --- Method code - // ISFER --- Error flag - // Routines called: - // (1) CHGUS for small x ( MD=1 ) - // (2) CHGUL for large x ( MD=2 ) - // (3) CHGUBI for integer b ( MD=3 ) - // (4) CHGUIT for numerical integration ( MD=4 ) - // ======================================================= - - int il1, il2, il3, bl1, bl2, bl3, bn, id1 = 0, id; - double aa, hu = 0.0, hu1, b00; - - aa = a - b + 1.0; - *isfer = 0; - il1 = (a == (int)a) && (a <= 0.0); - il2 = (aa == (int)aa) && (aa <= 0.0); - il3 = fabs(a*(a-b+1.0))/x <= 2.0; - bl1 = (x <= 5.0) || (x <= 10.0 && a <= 2.0); - bl2 = (x > 5.0) && (x <= 12.5) && ((a >= 1.0) && (b >= a+4.0)); - bl3 = (x > 12.5) && (a >= 5.0) && (b >= a + 5.0); - bn = (b == (int)b) && (b != 0.0); - - id = -100; - hu1 = 0.0; - if (b != (int)b) { - hu = chgus(x, a, b, &id1); - *md = 1; - if (id1 >= 9) { return hu; } - hu1 = hu; - } - if (il1 || il2 || il3) { - hu = chgul(x, a, b, &id); - *md = 2; - if (id >= 9) { return hu; } - if (id1 > id) { - *md = 1; - id = id1; - hu = hu1; - } - } - if (a >= 1.0) { - if (bn && (bl1 || bl2 || bl3)) { - hu = chgubi(x, a, b, &id); - *md = 3; - } else { - hu = chguit(x, a, b, &id); - *md = 4; - } - } else { - if (b <= a) { - b00 = b; - a -= b - 1.0; - b = 2.0 - b; - hu = chguit(x, a, b, &id); - hu *= pow(x, 1.0 - b00); - *md = 4; - } else if (bn && (~il1)) { - hu = chgubi(x, a, b, &id); - *md = 3; - } - } - if (id < 6) { *isfer = 6; } - return hu; -} - - -inline double chgubi(double x, double a, double b, int *id) { - - // ====================================================== - // Purpose: Compute confluent hypergeometric function - // U(a,b,x) with integer b ( b = ±1,±2,... ) - // Input : a --- Parameter - // b --- Parameter - // x --- Argument - // Output: HU --- U(a,b,x) - // ID --- Estimated number of significant digits - // Routines called: - // (1) GAMMA2 for computing gamma function Г(x) - // (2) PSI_SPEC for computing psi function - // ====================================================== - - int id1, id2, j, k, m, n; - double a0, a1, a2, da1, da2, db1, db2, ga, ga1, h0, hm1, hm2, hm3,\ - hmax, hmin, hu, hu1, hu2, hw, ps, r, rn, rn1, s0, s1, s2,\ - sa, sb, ua, ub; - const double el = 0.5772156649015329; - - *id = -100; - n = (int)fabs(b-1); - rn1 = 1.0; - rn = 1.0; - for (j = 1; j <= n; j++) { - rn *= j; - if (j == n-1) { - rn1 = rn; - } - } - ps = psi_spec(a); - ga = gamma2(a); - if (b > 0.0) { - a0 = a; - a1 = a - n; - a2 = a1; - ga1 = gamma2(a1); - ua = pow(-1, n-1) / (rn * ga1); - ub = rn1 / ga * pow(x, -n); - } else { - a0 = a + n; - a1 = a0; - a2 = a; - ga1 = gamma2(a1); - ua = pow(-1, n-1) / (rn * ga) * pow(x, n); - ub = rn1 / ga1; - } - hm1 = 1.0; - r = 1.0; - hmax = 0.0; - hmin = 1e300; - h0 = 0.0; - for (k = 1; k <= 150; k++) { - r = r * (a0 + k - 1) * x / ((n + k) * k); - hm1 += r; - hu1 = fabs(hm1); - - if (hu1 > hmax) { - hmax = hu1; - } - if (hu1 < hmin) { - hmin = hu1; - } - if (fabs(hm1 - h0) < fabs(hm1) * 1.0e-15) { break; } - h0 = hm1; - } - - da1 = log10(hmax); - da2 = 0; - - if (hmin != 0) { - da2 = log10(hmin); - } - - *id = 15 - (int)fabs(da1 - da2); - hm1 *= log(x); - s0 = 0; - - for (m = 1; m <= n; m++) { - if (b >= 0) { - s0 -= 1.0 / m; - } - if (b < 0) { - s0 += (1.0 - a) / (m * (a + m - 1)); - } - } - - hm2 = ps + 2 * el + s0; - r = 1; - hmax = 0; - hmin = 1.0e+300; - - for (k = 1; k <= 150; k++) { - s1 = 0; - s2 = 0; - - if (b > 0) { - for (m = 1; m <= k; m++) { - s1 -= (m + 2 * a - 2) / (m * (m + a - 1)); - } - for (m = 1; m <= n; m++) { - s2 += 1.0 / (k + m); - } - } else { - for (m = 1; m <= k + n; m++) { - s1 += (1.0 - a) / (m * (m + a - 1)); - } - for (m = 1; m <= k; m++) { - s2 += 1.0 / m; - } - } - - hw = 2 * el + ps + s1 - s2; - r = r * (a0 + k - 1) * x / ((n + k) * k); - hm2 += r * hw; - hu2 = fabs(hm2); - - if (hu2 > hmax) { - hmax = hu2; - } - - if (hu2 < hmin) { - hmin = hu2; - } - - if (fabs((hm2 - h0) / hm2) < 1.0e-15) { - break; - } - - h0 = hm2; - } - - db1 = log10(hmax); - db2 = 0.0; - if (hmin != 0.0) { db2 = log10(hmin); } - id1 = 15 - (int)fabs(db1 - db2); - if (id1 < *id) { *id = id1; } - hm3 = 1.0; - if (n == 0) { hm3 = 0.0; } - r = 1.0; - for (k = 1; k < n; k++) { - r = r * (a2 + k - 1.0) / ((k - n)*k)*x; - hm3 += r; - } - sa = ua*(hm1 + hm2); - sb = ub*hm3; - hu = sa + sb; - id2 = 0; - if (sa != 0.0) { id1 = (int)(log10(fabs(sa))); } - if (hu != 0.0) { id2 = (int)(log10(fabs(hu))); } - if (sa*sb < 0.0) { *id -= abs(id1-id2); } - return hu; -} - - -inline double chguit(double x, double a, double b, int *id) { - - // ====================================================== - // Purpose: Compute hypergeometric function U(a,b,x) by - // using Gaussian-Legendre integration (n=60) - // Input : a --- Parameter ( a > 0 ) - // b --- Parameter - // x --- Argument ( x > 0 ) - // Output: HU --- U(a,b,z) - // ID --- Estimated number of significant digits - // Routine called: GAMMA2 for computing Г(x) - // ====================================================== - - int k, j, m; - double a1, b1, c, d, f1, f2, g, ga, hu, hu0, hu1, hu2, s, t1, t2, t3, t4; - static const double t[30] = { - 0.259597723012478e-01, 0.778093339495366e-01, 0.129449135396945e+00, 0.180739964873425e+00, - 0.231543551376029e+00, 0.281722937423262e+00, 0.331142848268448e+00, 0.379670056576798e+00, - 0.427173741583078e+00, 0.473525841761707e+00, 0.518601400058570e+00, 0.562278900753945e+00, - 0.604440597048510e+00, 0.644972828489477e+00, 0.683766327381356e+00, 0.720716513355730e+00, - 0.755723775306586e+00, 0.788693739932264e+00, 0.819537526162146e+00, 0.848171984785930e+00, - 0.874519922646898e+00, 0.898510310810046e+00, 0.920078476177628e+00, 0.939166276116423e+00, - 0.955722255839996e+00, 0.969701788765053e+00, 0.981067201752598e+00, 0.989787895222222e+00, - 0.995840525118838e+00, 0.999210123227436e+00 - }; - static const double w[30] = { - 0.519078776312206e-01, 0.517679431749102e-01, 0.514884515009810e-01, 0.510701560698557e-01, - 0.505141845325094e-01, 0.498220356905502e-01, 0.489955754557568e-01, 0.480370318199712e-01, - 0.469489888489122e-01, 0.457343797161145e-01, 0.443964787957872e-01, 0.429388928359356e-01, - 0.413655512355848e-01, 0.396806954523808e-01, 0.378888675692434e-01, 0.359948980510845e-01, - 0.340038927249464e-01, 0.319212190192963e-01, 0.297524915007890e-01, 0.275035567499248e-01, - 0.251804776215213e-01, 0.227895169439978e-01, 0.203371207294572e-01, 0.178299010142074e-01, - 0.152746185967848e-01, 0.126781664768159e-01, 0.100475571822880e-01, 0.738993116334531e-02, - 0.471272992695363e-02, 0.202681196887362e-02 - }; - *id = 9; - // DLMF 13.4.4, integration up to C=12/X - a1 = a - 1.0; - b1 = b - a - 1.0; - c = 12.0 / x; - hu0 = 0.0; - for (m = 10; m <= 100; m += 5) { - hu1 = 0.0; - g=0.5 * c / m; - d=g; - for (j = 1; j < (m + 1); j++) { - s = 0.0; - for (k = 1; k <= 30; k++) { - t1 = d + g * t[k-1]; - t2 = d - g * t[k-1]; - f1 = exp(-x*t1) * pow(t1, a1) * pow(1.0 + t1, b1); - f2 = exp(-x*t2) * pow(t2, a1) * pow(1.0 + t2, b1); - s += w[k-1]*(f1 + f2); - } - hu1 += s * g; - d += 2.0 * g; - } - if (fabs(1.0 - hu0/hu1) < 1.0e-9) { break; } - hu0 = hu1; - } - ga = gamma2(a); - hu1 /= ga; - // DLMF 13.4.4 with substitution t=C/(1-u) - // integration u from 0 to 1, i.e. t from C=12/X to infinity - for (m = 2; m <= 10; m += 2) { - hu2 = 0.0; - g = 0.5 / m; - d = g; - for (j = 1; j <= m; j++) { - s = 0.0; - for (k = 1; k <= 30; k++) { - t1 = d + g * t[k-1]; - t2 = d - g * t[k-1]; - t3 = c / (1.0 - t1); - t4 = c / (1.0 - t2); - f1 = t3*t3 / c * exp(-x*t3)*pow(t3, a1)*pow(1.0 + t3, b1); - f2 = t4*t4 / c * exp(-x*t4)*pow(t4, a1)*pow(1.0 + t4, b1); - s += w[k-1]*(f1 + f2); - } - hu2 += s*g; - d += 2.0*g; - } - if (fabs(1.0 - hu0/hu2) < 1.0e-9) { break; } - hu0 = hu2; - } - ga = gamma2(a); - hu2 /= ga; - hu = hu1 + hu2; - return hu; -} - - -inline double chgul(double x, double a, double b, int *id) { - - // ======================================================= - // Purpose: Compute the confluent hypergeometric function - // U(a,b,x) for large argument x - // Input : a --- Parameter - // b --- Parameter - // x --- Argument - // Output: HU --- U(a,b,x) - // ID --- Estimated number of significant digits - // ======================================================= - - int il1, il2, k, nm; - double aa, hu, r, r0 = 0.0, ra = 0.0; - - *id = -100; - aa = a - b + 1.0; - il1 = (a == (int)a) && (a <= 0.0); - il2 = (aa == (int)aa) && (aa <= 0.0); - nm = 0; - if (il1) { nm = (int)fabs(a); } - if (il2) { nm = (int)fabs(aa); } - // IL1: DLMF 13.2.7 with k=-s-a - // IL2: DLMF 13.2.8 - if (il1 || il2) { - hu = 1.0; - r = 1.0; - for (k = 1; k <= nm; k++) { - r = -r*(a + k - 1.0)*(a - b + k) / (k*x); - hu += r; - } - hu *= pow(x, -a); - *id = 10; - } else { - // DLMF 13.7.3 - hu = 1.0; - r = 1.0; - for (k = 1; k <= 25; k++) { - r = -r*(a + k - 1.0)*(a - b + k) / (k*x); - ra = fabs(r); - if (((k > 5) && (ra >= r0)) || (ra < 1e-15)) { break; } - r0 = ra; - hu += r; - } - *id = (int)fabs(log10(ra)); - hu *= pow(x, -a); - } - return hu; -} - - -inline double chgus(double x, double a, double b, int *id) { - - // ====================================================== - // Purpose: Compute confluent hypergeometric function - // U(a,b,x) for small argument x - // Input : a --- Parameter - // b --- Parameter ( b <> 0,-1,-2,...) - // x --- Argument - // Output: HU --- U(a,b,x) - // ID --- Estimated number of significant digits - // Routine called: GAMMA2 for computing gamma function - // ====================================================== - - // DLMF 13.2.42 with prefactors rewritten according to - // DLMF 5.5.3, M(a, b, x) with DLMF 13.2.2 - int j; - double d1, d2, ga, gb, gab, gb2, h0, hmax, hmin, hu, hu0, hua, r1, r2; - const double pi = 3.141592653589793; - - *id = 100; - ga = gamma2(a); - gb = gamma2(b); - gab = gamma2(1.0 + a - b); - gb2 = gamma2(2.0 - b); - hu0 = pi / sin(pi*b); - r1 = hu0 / (gab*gb); - r2 = hu0*pow(x, 1.0 - b) / (ga*gb2); - hu = r1 - r2; - hmax = 0.0; - hmin = 1e300; - h0 = 0.0; - for (j = 1; j < 151; j++) { - r1 = r1*(a + j - 1.0) / (j*(b + j - 1.0))*x; - r2 = r2*(a - b + j) / (j*(1.0 - b + j))*x; - hu += r1 - r2; - hua = fabs(hu); - if (hua > hmax) { hmax = hua; } - if (hua < hmin) { hmin = hua; } - if (fabs(hu - h0) < fabs(hu)*1e-15) { break; } - h0 = hu; - } - d1 = log10(hmax); - d2 = 0.0; - if (hmin != 0.0) { d2 = log10(hmin); } - *id = 15 - (d1 - d2 < 0 ? d2 - d1 : d1 - d2); - return hu; -} - - -inline void cpbdn(int n, std::complex z, std::complex *cpb, std::complex *cpd) { - - // ================================================== - // Purpose: Compute the parabolic cylinder functions - // Dn(z) and Dn'(z) for a complex argument - // Input: z --- Complex argument of Dn(z) - // n --- Order of Dn(z) ( n=0,±1,±2,… ) - // Output: CPB(|n|) --- Dn(z) - // CPD(|n|) --- Dn'(z) - // Routines called: - // (1) CPDSA for computing Dn(z) for a small |z| - // (2) CPDLA for computing Dn(z) for a large |z| - // ================================================== - - int n0, n1, nm1; - double a0, x; - std::complex ca0, cf, cf0, cf1, cfa, cfb, cs0, z1; - const double pi = 3.141592653589793; - - x = z.real(); - a0 = std::abs(z); - ca0 = std::exp(-0.25 * z * conj(z)); - n0 = 0; - - if (n >= 0) { - cf0 = ca0; - cf1 = z * ca0; - - cpb[0] = cf0; - cpb[1] = cf1; - - for (int k = 2; k <= n; ++k) { - cf = z * cf1 - (k - 1.0) * cf0; - cpb[k] = cf; - cf0 = cf1; - cf1 = cf; - } - } else { - n0 = -n; - - if (x <= 0.0 || a0 == 0.0) { - cf0 = ca0; - cpb[0] = cf0; - - z1 = -z; - - if (a0 <= 7.0) { - cpb[1] = cpdsa(-1, z1); - } else { - cpb[1] = cpdla(-1, z1); - } - - cf1 = std::sqrt(2.0 * pi) / ca0 - cpb[1]; - cpb[1] = cf1; - - for (int k = 2; k < n0; ++k) { - cf = (-z * cf1 + cf0) / (k - 1.0); - cpb[k] = cf; - cf0 = cf1; - cf1 = cf; - } - } else if (a0 <= 3.0) { - cpb[n0] = cpdsa(-n0, z); - n1 = n0 + 1; - cpb[n1] = cpdsa(-n1, z); - - nm1 = n0 - 1; - for (int k = nm1; k >= 0; --k) { - cf = z * cpb[n0] + (k + 1.0) * cpb[n1]; - cpb[k] = cf; - cpb[n1] = cpb[n0]; - cpb[n0] = cf; - } - } else { - int m = 100 + abs(n); - cfa = 0.0; - cfb = 1.0e-30; - - for (int k = m; k >= 0; --k) { - cf = z * cfb + (k + 1.0) * cfa; - - if (k <= n0) { - cpb[k] = cf; - } - - cfa = cfb; - cfb = cf; - } - - cs0 = ca0 / cfb; - - for (int k = 0; k <= n0; ++k) { - cpb[k] = cs0 * cpb[k]; - } - } - } - - cpd[0] = -0.5 * z * cpb[0]; - - if (n >= 0) { - for (int k = 1; k <= n; ++k) { - cpd[k] = -0.5 * z * cpb[k] + static_cast(k) * cpb[k - 1]; - } - } else { - for (int k = 1; k < n0; ++k) { - cpd[k] = 0.5 * z * cpb[k] - cpb[k - 1]; - } - } -} - - -inline std::complex cpdla(int n, std::complex z) { - - // =========================================================== - // Purpose: Compute complex parabolic cylinder function Dn(z) - // for large argument - // Input: z --- Complex argument of Dn(z) - // n --- Order of Dn(z) (n = 0,±1,±2,…) - // Output: CDN --- Dn(z) - // =========================================================== - - int k; - std::complex cb0, cr, cdn; - - cb0 = std::pow(z, n)*std::exp(-0.25*z*z); - cr = 1.0; - cdn = 1.0; - for (k = 1; k <= 16; k++) { - cr = - 0.5 * cr * (2.0 * k - n - 1.0) * (2.0 * k - n - 2.0) / (static_cast(k) * z * z); - cdn += cr; - if (std::abs(cr) < std::abs(cdn) * 1e-12) { break; } - } - return cdn * cb0; -} - - -inline std::complex cpdsa(int n, std::complex z) { - - // =========================================================== - // Purpose: Compute complex parabolic cylinder function Dn(z) - // for small argument - // Input: z --- Complex argument of D(z) - // n --- Order of D(z) (n = 0,-1,-2,...) - // Output: CDN --- Dn(z) - // Routine called: GAIH for computing Г(x), x=n/2 (n=1,2,...) - // =========================================================== - - int m; - double va0, pd, vm, vt, xn; - std::complex ca0, cb0, cdn, cr, cdw, g0, g1, ga0, gm; - const double eps = 1.0e-15; - const double pi = 3.141592653589793; - const double sq2 = sqrt(2.0); - - ca0 = std::exp(-0.25 * z * z); - va0 = 0.5 * (1.0 - n); - if (n == 0.0) { - cdn = ca0; - } else { - if (std::abs(z) == 0.0) { - if ((va0 <= 0.0) && (va0 == (int)va0)) { - cdn = 0.0; - } else { - ga0 = gaih(va0); - pd = sqrt(pi) / (pow(2.0, -0.5 * n) * ga0.real()); - cdn = pd; - } - } else { - xn = -n; - g1 = gaih(xn); - cb0 = pow(2.0, -0.5 * n - 1.0) * ca0 / g1; - vt = -0.5 * n; - g0 = gaih(vt); - cdn = g0; - cr = std::complex(1.0, 0.0); - - for (m = 1; m <= 250; m++) { - vm = 0.5 * (m - n); - gm = gaih(vm); - cr = -cr*sq2 * z / static_cast(m); - cdw = gm * cr; - cdn += cdw; - if (std::abs(cdw) < std::abs(cdn) * eps) { - break; - } - } - cdn *= cb0; - } - } - return cdn; -} - - -inline double cv0(double kd, double m, double q) { - - // ===================================================== - // Purpose: Compute the initial characteristic value of - // Mathieu functions for m ≤ 12 or q ≤ 300 or - // q ≥ m*m - // Input : m --- Order of Mathieu functions - // q --- Parameter of Mathieu functions - // Output: A0 --- Characteristic value - // Routines called: - // (1) CVQM for computing initial characteristic - // value for q ≤ 3*m - // (2) CVQL for computing initial characteristic - // value for q ≥ m*m - // ==================================================== - - double a0 = 0.0, q2 = q*q; - - if (m == 0) { - if (q <= 1.0) { - a0 = (((0.0036392 * q2 - 0.0125868) * q2 + 0.0546875) * q2 - 0.5) * q2; - } else if (q <= 10.0) { - a0 = ((3.999267e-3 * q - 9.638957e-2) * q - 0.88297) * q + 0.5542818; - } else { - a0 = cvql(kd, m, q); - } - } else if (m == 1) { - if ((q <= 1.0) && (kd == 2)) { - a0 = (((-6.51e-4 * q - 0.015625) * q - 0.125) * q + 1.0) * q + 1.0; - } else if (q <= 1.0 && kd == 3) { - a0 = (((-6.51e-4 * q + 0.015625) * q - 0.125) * q - 1.0) * q + 1.0; - } else if (q <= 10.0 && kd == 2) { - a0 = (((-4.94603e-4 * q + 1.92917e-2) * q - 0.3089229) * q + 1.33372) * q + 0.811752; - } else if (q <= 10.0 && kd == 3) { - a0 = ((1.971096e-3 * q - 5.482465e-2) * q - 1.152218) * q + 1.10427; - } else { - a0 = cvql(kd, m, q); - } - } else if (m == 2) { - if (q <= 1.0 && kd == 1) { - a0 = (((-0.0036391 * q2 + 0.0125888) * q2 - 0.0551939) * q2 + 0.416667) * q2 + 4.0; - } else if (q <= 1.0 && kd == 4) { - a0 = (0.0003617 * q2 - 0.0833333) * q2 + 4.0; - } else if (q <= 15.0 && kd == 1) { - a0 = (((3.200972e-4 * q - 8.667445e-3) * q - 1.829032e-4) * q + 0.9919999) * q + 3.3290504; - } else if (q <= 10.0 && kd == 4) { - a0 = ((2.38446e-3 * q - 0.08725329) * q - 4.732542e-3) * q + 4.00909; - } else { - a0 = cvql(kd, m, q); - } - } else if (m == 3) { - if (q <= 1.0 && kd == 2) { - a0 = (((6.348e-4 * q + 0.015625) * q + 0.0625) * q2 + 9.0); - } else if (q <= 1.0 && kd == 3) { - a0 = (((6.348e-4 * q - 0.015625) * q + 0.0625) * q2 + 9.0); - } else if (q <= 20.0 && kd == 2) { - a0 = (((3.035731e-4 * q - 1.453021e-2) * q + 0.19069602) * q - 0.1039356) * q + 8.9449274; - } else if (q <= 15.0 && kd == 3) { - a0 = ((9.369364e-5 * q - 0.03569325) * q + 0.2689874) * q + 8.771735; - } else { - a0 = cvql(kd, m, q); - } - } else if (m == 4) { - if (q <= 1.0 && kd == 1) { - a0 = ((-2.1e-6 * q2 + 5.012e-4) * q2 + 0.0333333) * q2 + 16.0; - } else if (q <= 1.0 && kd == 4) { - a0 = ((3.7e-6 * q2 - 3.669e-4) * q2 + 0.0333333) * q2 + 16.0; - } else if (q <= 25.0 && kd == 1) { - a0 = (((1.076676e-4 * q - 7.9684875e-3) * q + 0.17344854) * q - 0.5924058) * q + 16.620847; - } else if (q <= 20.0 && kd == 4) { - a0 = ((-7.08719e-4 * q + 3.8216144e-3) * q + 0.1907493) * q + 15.744; - } else { - a0 = cvql(kd, m, q); - } - } else if (m == 5) { - if (q <= 1.0 && kd == 2) { - a0 = ((6.8e-6 * q + 1.42e-5) * q2 + 0.0208333) * q2 + 25.0; - } else if (q <= 1.0 && kd == 3) { - a0 = ((-6.8e-6 * q + 1.42e-5) * q2 + 0.0208333) * q2 + 25.0; - } else if (q <= 35.0 && kd == 2) { - a0 = (((2.238231e-5 * q - 2.983416e-3) * q + 0.10706975) * q - 0.600205) * q + 25.93515; - } else if (q <= 25.0 && kd == 3) { - a0 = ((-7.425364e-4 * q + 2.18225e-2) * q + 4.16399e-2) * q + 24.897; - } else { - a0 = cvql(kd, m, q); - } - } else if (m == 6) { - if (q <= 1.0) { - a0 = (4e-6 * q2 + 0.0142857) * q2 + 36.0; - } else if (q <= 40.0 && kd == 1) { - a0 = (((-1.66846e-5 * q + 4.80263e-4) * q + 2.53998e-2) * q - 0.181233) * q + 36.423; - } else if (q <= 35.0 && kd == 4) { - a0 = ((-4.57146e-4 * q + 2.16609e-2) * q - 2.349616e-2) * q + 35.99251; - } else { - a0 = cvql(kd, m, q); - } - } else if (m == 7) { - if (q <= 10.0) { - a0 = cvqm(m, q); - } else if (q <= 50.0 && kd == 2) { - a0 = (((-1.411114e-5 * q + 9.730514e-4) * q - 3.097887e-3) * q + 3.533597e-2) * q + 49.0547; - } else if (q <= 40.0 && kd == 3) { - a0 = ((-3.043872e-4 * q + 2.05511e-2) * q - 9.16292e-2) * q + 49.19035; - } else { - a0 = cvql(kd, m, q); - } - } else if (m >= 8) { - if (q <= 3*m) { - a0 = cvqm(m, q); - } else if (q > m * m) { - a0 = cvql(kd, m, q); - } else { - if (m == 8 && kd == 1) { - a0 = (((8.634308e-6 * q - 2.100289e-3) * q + 0.169072) * q - 4.64336) * q + 109.4211; - } else if (m == 8 && kd == 4) { - a0 = ((-6.7842e-5 * q + 2.2057e-3) * q + 0.48296) * q + 56.59; - } else if (m == 9 && kd == 2) { - a0 = (((2.906435e-6 * q - 1.019893e-3) * q + 0.1101965) * q - 3.821851) * q + 127.6098; - } else if (m == 9 && kd == 3) { - a0 = ((-9.577289e-5 * q + 0.01043839) * q + 0.06588934) * q + 78.0198; - } else if (m == 10 && kd == 1) { - a0 = (((5.44927e-7 * q - 3.926119e-4) * q + 0.0612099) * q - 2.600805) * q + 138.1923; - } else if (m == 10 && kd == 4) { - a0 = ((-7.660143e-5 * q + 0.01132506) * q - 0.09746023) * q + 99.29494; - } else if (m == 11 && kd == 2) { - a0 = (((-5.67615e-7 * q + 7.152722e-6) * q + 0.01920291) * q - 1.081583) * q + 140.88; - } else if (m == 11 && kd == 3) { - a0 = ((-6.310551e-5 * q + 0.0119247) * q - 0.2681195) * q + 123.667; - } else if (m == 12 && kd == 1) { - a0 = (((-2.38351e-7 * q - 2.90139e-5) * q + 0.02023088) * q - 1.289) * q + 171.2723; - } else if (m == 12 && kd == 4) { - a0 = (((3.08902e-7 * q - 1.577869e-4) * q + 0.0247911) * q - 1.05454) * q + 161.471; - } - } - } - return a0; -} - - -inline double cva2(int kd, int m, double q) { - - // ====================================================== - // Purpose: Calculate a specific characteristic value of - // Mathieu functions - // Input : m --- Order of Mathieu functions - // q --- Parameter of Mathieu functions - // KD --- Case code - // KD=1 for cem(x,q) ( m = 0,2,4,...) - // KD=2 for cem(x,q) ( m = 1,3,5,...) - // KD=3 for sem(x,q) ( m = 1,3,5,...) - // KD=4 for sem(x,q) ( m = 2,4,6,...) - // Output: A --- Characteristic value - // Routines called: - // (1) REFINE for finding accurate characteristic - // value using an iteration method - // (2) CV0 for finding initial characteristic - // values using polynomial approximation - // (3) CVQM for computing initial characteristic - // values for q ≤ 3*m - // (3) CVQL for computing initial characteristic - // values for q ≥ m*m - // ====================================================== - - int ndiv, nn, i; - double a = 0.0, delta, q1, q2, qq, a1, a2; - - if ((m <= 12) || (q <= 3.0 * m) || (q > m * m)) { - a = cv0(kd, m, q); - if ((q != 0.0) && (m != 2)) { a = refine(kd, m, q, a); } - if ((q > 2.0e-3) && (m == 2)) { a = refine(kd, m, q, a); } - } else { - ndiv = 10; - delta = (m - 3.0) * m / ndiv; - - if ((q - 3.0 * m) <= (m * m - q)) { - nn = (int)((q - 3.0 * m) / delta) + 1; - delta = (q - 3.0 * m) / nn; - q1 = 2.0 * m; - a1 = cvqm(m, q1); - q2 = 3.0 * m; - a2 = cvqm(m, q2); - qq = 3.0 * m; - for (i = 1; i <= nn; i++) { - qq = qq + delta; - a = (a1 * q2 - a2 * q1 + (a2 - a1) * qq) / (q2 - q1); - a = refine(kd, m, qq, a); - q1 = q2; - q2 = qq; - a1 = a2; - a2 = a; - } - } else { - nn = (int)((m * m - q) / delta) + 1; - delta = (m * m - q) / nn; - q1 = m * (m - 1.0); - a1 = cvql(kd, m, q1); - q2 = m * m; - a2 = cvql(kd, m, q2); - qq = m * m; - for (i = 1; i <= nn; ++i) { - qq = qq - delta; - a = (a1 * q2 - a2 * q1 + (a2 - a1) * qq) / (q2 - q1); - a = refine(kd, m, qq, a); - q1 = q2; - q2 = qq; - a1 = a2; - a2 = a; - } - } - } - return a; -} - - -inline double cvf(int kd, int m, double q, double a, int mj) { - - // ====================================================== - // Purpose: Compute the value of F for characteristic - // equation of Mathieu functions - // Input : m --- Order of Mathieu functions - // q --- Parameter of Mathieu functions - // A --- Characteristic value - // Output: F --- Value of F for characteristic equation - // ====================================================== - - int j, ic = m / 2, l = 0, l0 = 0, j0 = 2; - int jf = ic; - double t0 = 0.0, t1 = 0.0, t2 = 0.0, b = a, f; - - if (kd == 1) { - j0 = 3; - l0 = 2; - } - if ((kd == 2) || (kd == 3)) { l = 1; } - if (kd == 4) { jf = ic-1; } - - for (j = mj; j >= ic+1; j--) { - t1 = -q*q/(pow(2.0*j + l, 2) - b + t1); - } - - if (m <= 2) { - if ((kd == 1) && (m == 0)) { t1 += t1; } - if ((kd == 1) && (m == 2)) { t1 = -2.0*q*q/(4.0-b+t1) - 4.0; } - if ((kd == 2) && (m == 1)) { t1 += q; } - if ((kd == 3) && (m == 1)) { t1 -= q; } - } else { - if (kd == 1) { t0 = 4.0 - b + 2.0*q*q / b; } - if (kd == 2) { t0 = 1.0 - b + q; } - if (kd == 3) { t0 = 1.0 - b - q; } - if (kd == 4) { t0 = 4.0 - b; } - t2 = -q*q / t0; - for (j = j0; j <= jf; j++) { - t2 = -q*q/(pow(2.0*j -l-l0, 2.0) - b + t2); - } - } - f = pow(2.0*ic+l, 2) + t1 + t2 - b; - return f; -} - - -inline double cvql(int kd, int m, double q) { - - // ======================================================== - // Purpose: Compute the characteristic value of Mathieu - // functions for q ≥ 3m - // Input : m --- Order of Mathieu functions - // q --- Parameter of Mathieu functions - // Output: A0 --- Initial characteristic value - // ======================================================== - - double a0, w, w2, w3, w4, w6, d1, d2, d3, d4, c1, p1, p2, cv1, cv2; - - w = 0.0; - if ((kd == 1) || (kd == 2)) { w=2.0*m + 1.0; } - if ((kd == 3) || (kd == 4)) { w=2.0*m - 1.0; } - w2 = w*w; - w3 = w*w2; - w4 = w2*w2; - w6 = w2*w4; - d1 = 5.0+34.0/w2+9.0/w4; - d2 = (33.0 + 410.0/w2 + 405.0/w4)/w; - d3 = (63.0 + 1260.0/w2 + 2943.0/w4 + 486.0/w6)/w2; - d4 = (527.0 + 15617.0/w2 + 69001.0/w4 + 41607.0/w6)/w3; - c1 = 128.0; - p2 = q/w4; - p1 = sqrt(p2); - cv1 = -2.0*q+2.0*w*sqrt(q) - (w2+1.0)/8.0; - cv2 = (w+3.0/w) + d1/(32.0*p1) + d2/(8.0*c1*p2); - cv2 = cv2 + d3/(64.0*c1*p1*p2)+d4/(16.0*c1*c1*p2*p2); - a0 = cv1 - cv2/(c1*p1); - return a0; -} - - -inline double cvqm(int m, double q) { - - // ===================================================== - // Purpose: Compute the characteristic value of Mathieu - // functions for q ≤ m*m - // Input : m --- Order of Mathieu functions - // q --- Parameter of Mathieu functions - // Output: A0 --- Initial characteristic value - // ===================================================== - - double hm1, hm3, hm5, a0; - - hm1= 0.5*q/(m*m-1.0); - hm3=.25*pow(hm1, 3)/(m*m - 4.0); - hm5 = hm1*hm3*q/((m*m - 1.0)*(m*m - 9.0)); - a0 = m*m + q*(hm1+(5.0*m*m + 7.0)*hm3 + (9.0*pow(m, 4) + 58.0*m*m + 29.0)*hm5); - return a0; -} - - -inline void cy01(int kf, std::complex z, std::complex *zf, std::complex *zd) { - - // =========================================================== - // Purpose: Compute complex Bessel functions Y0(z), Y1(z) - // and their derivatives - // Input : z --- Complex argument of Yn(z) ( n=0,1 ) - // KF --- Function choice code - // KF=0 for ZF=Y0(z) and ZD=Y0'(z) - // KF=1 for ZF=Y1(z) and ZD=Y1'(z) - // KF=2 for ZF=Y1'(z) and ZD=Y1''(z) - // Output: ZF --- Y0(z) or Y1(z) or Y1'(z) - // ZD --- Y0'(z) or Y1'(z) or Y1''(z) - // =========================================================== - - int k, k0; - double a0, w0, w1; - std::complex cr, cp, cp0, cq0, cu, cp1, cq1, cbj0, cbj1,\ - cby0, cby1, cdy0, cdy1, cs, ct1, ct2, z1, z2; - - const double pi = 3.141592653589793; - const double el = 0.5772156649015329; - const double rp2 = 2.0 / pi; - const std::complex ci(0.0, 1.0); - - static const double a[12] = {-0.703125e-01, 0.112152099609375, -0.5725014209747314, - 0.6074042001273483, -0.1100171402692467, 0.3038090510922384, - -0.1188384262567832, 0.6252951493434797, -0.4259392165047669, - 0.3646840080706556, -0.3833534661393944, 0.4854014686852901}; - - static const double b[12] = { 0.732421875e-01, -0.2271080017089844, 0.1727727502584457, - -0.2438052969955606, 0.5513358961220206, -0.1825775547429318, - 0.8328593040162893, -0.5006958953198893, 0.3836255180230433, - -0.3649010818849833, 0.4218971570284096, -0.5827244631566907}; - - static const double a1[12] = { 0.1171875, -0.144195556640625, 0.6765925884246826, - -0.6883914268109947, 0.1215978918765359, -0.3302272294480852, - 0.1276412726461746, -0.6656367718817688, 0.4502786003050393, - -0.3833857520742790, 0.4011838599133198, -0.5060568503314727}; - - static const double b1[12] = {-0.1025390625, 0.2775764465332031, -0.1993531733751297, - 0.2724882731126854, -0.6038440767050702, 0.1971837591223663, - -0.8902978767070678, 0.5310411010968522, -0.4043620325107754, - 0.3827011346598605, -0.4406481417852278, 0.6065091351222699}; - - a0 = std::abs(z); - z1 = z; - z2 = z * z; - if (a0 == 0.0) { - cbj0 = std::complex(1.0, 0.0); - cbj1 = std::complex(0.0, 0.0); - cby0 = std::complex(-1e300, 0.0); - cby1 = std::complex(-1e300, 0.0); - cdy0 = std::complex( 1e300, 0.0); - cdy1 = std::complex( 1e300, 0.0); - if (kf == 0) { - *zf = cby0; - *zd = cdy0; - } else if (kf == 1) { - *zf = cby1; - *zd = cdy1; - } else if (kf == 2) { - *zf = cdy1; - *zd = -cdy1 / z - (1.0 - 1.0 / (z * z)) * cby1; - } - return; - } - - if (z.real() < 0.0) { - z1 = -z; - } - - if (a0 <= 12.0) { - cbj0 = std::complex(1.0, 0.0); - cr = std::complex(1.0, 0.0); - for (k = 1; k <= 40; k++) { - cr = -0.25 * cr * z2 / static_cast(k * k); - cbj0 += cr; - if (std::abs(cr) < std::abs(cbj0) * 1.0e-15) break; - } - - cbj1 = std::complex(1.0, 0.0); - cr = std::complex(1.0, 0.0); - for (k = 1; k <= 40; k++) { - cr = -0.25 * cr * z2 / (k * (k + 1.0)); - cbj1 += cr; - if (std::abs(cr) < std::abs(cbj1) * 1.0e-15) break; - } - - cbj1 *= 0.5 * z1; - w0 = 0.0; - cr = std::complex(1.0, 0.0); - cs = std::complex(0.0, 0.0); - for (k = 1; k <= 40; k++) { - w0 += 1.0 / k; - cr = -0.25 * cr / static_cast(k * k) * z2; - cp = cr * w0; - cs += cp; - if (std::abs(cp) < std::abs(cs) * 1.0e-15) break; - } - - cby0 = rp2 * (std::log(z1 / 2.0) + el) * cbj0 - rp2 * cs; - w1 = 0.0; - cr = 1.0; - cs = 1.0; - for (k = 1; k <= 40; k++) { - w1 += 1.0 / k; - cr = -0.25 * cr / static_cast(k * (k + 1)) * z2; - cp = cr * (2.0 * w1 + 1.0 / (k + 1.0)); - cs += cp; - if (std::abs(cp) < std::abs(cs) * 1.0e-15) break; - } - cby1 = rp2 * ((std::log(z1 / 2.0) + el) * cbj1 - 1.0 / z1 - 0.25 * z1 * cs); - } else { - k0 = 12; - if (a0 >= 35.0) k0 = 10; - if (a0 >= 50.0) k0 = 8; - - ct1 = z1 - 0.25 * pi; - cp0 = 1.0; - for (k = 1; k <= k0; k++) { - cp0 += a[k - 1] * pow(z1, -2 * k); - } - cq0 = -0.125 / z1; - for (k = 1; k <= k0; k++) - cq0 += b[k - 1] * pow(z1, -2 * k - 1); - - cu = std::sqrt(rp2 / z1); - cbj0 = cu * (cp0 * cos(ct1) - cq0 * sin(ct1)); - cby0 = cu * (cp0 * sin(ct1) + cq0 * cos(ct1)); - - ct2 = z1 - 0.75 * pi; - cp1 = 1.0; - for (k = 1; k <= k0; k++) - cp1 += a1[k - 1] * pow(z1, -2 * k); - - cq1 = 0.375 / z1; - for (k = 1; k <= k0; k++) { - cq1 = cq1 + b1[k - 1] * pow(z1, -2 * k - 1); - } - cbj1 = cu * (cp1 * cos(ct2) - cq1 * sin(ct2)); - cby1 = cu * (cp1 * sin(ct2) + cq1 * cos(ct2)); - } - - if (z.real() < 0.0) { - if (z.imag() < 0.0) cby0 = cby0 - 2.0 * ci * cbj0; - if (z.imag() > 0.0) cby0 = cby0 + 2.0 * ci * cbj0; - if (z.imag() < 0.0) cby1 = -(cby1 - 2.0 * ci * cbj1); - if (z.imag() > 0.0) cby1 = -(cby1 + 2.0 * ci * cbj1); - cbj1 = -cbj1; - } - - cdy0 = -cby1; - cdy1 = cby0 - 1.0 / z * cby1; - - if (kf == 0) { - *zf = cby0; - *zd = cdy0; - } else if (kf == 1) { - *zf = cby1; - *zd = cdy1; - } else if (kf == 2) { - *zf = cdy1; - *zd = -cdy1 / z - (1.0 - 1.0 / (z * z)) * cby1; - } - return; -} - - -inline void cyzo(int nt, int kf, int kc, std::complex *zo, std::complex *zv) { - - // =========================================================== - // Purpose : Compute the complex zeros of Y0(z), Y1(z) and - // Y1'(z), and their associated values at the zeros - // using the modified Newton's iteration method - // Input: NT --- Total number of zeros/roots - // KF --- Function choice code - // KF=0 for Y0(z) & Y1(z0) - // KF=1 for Y1(z) & Y0(z1) - // KF=2 for Y1'(z) & Y1(z1') - // KC --- Choice code - // KC=0 for complex roots - // KC=1 for real roots - // Output: ZO(L) --- L-th zero of Y0(z) or Y1(z) or Y1'(z) - // ZV(L) --- Value of Y0'(z) or Y1'(z) or Y1(z) - // at the L-th zero - // Routine called: CY01 for computing Y0(z) and Y1(z), and - // their derivatives - // =========================================================== - - int i, it, j, nr; - double x, h, w, y, w0; - std::complex z, zf, zd, zfd, zgd, zp, zq, zw; - - x = 0.0; - y = 0.0; - h = 0.0; - - if (kc == 0) { - x = -2.4; - y = 0.54; - h = 3.14; - } else if (kc == 1) { - x = 0.89; - y = 0.0; - h = -3.14; - } - - if (kf == 1) { - x = -0.503; - } - - if (kf == 2) { - x = 0.577; - } - z = std::complex(x, y); - w = 0.0; - for (nr = 1; nr <= nt; nr++) { - if (nr > 1) { - z = zo[nr - 2] - h; - } - it = 0; - do { - it += 1; - cy01(kf, z, &zf, &zd); - zp = 1.0; - for (i = 1; i < nr; i++) { - zp *= (z - zo[i - 1]); - } - zfd = zf / zp; - zq = 0.0; - for (i = 1; i < nr; i++) { - zw = 1.0; - for (j = 1; j < nr; j++) { - if (j == i) { continue; } - zw *= (z - zo[j - 1]); - } - zq += zw; - } - zgd = (zd - zq * zfd) / zp; - z -= zfd / zgd; - w0 = w; - w = std::abs(z); - } while ((it <= 50) && (fabs((w - w0) / w) > 1.0e-12)); - - zo[nr - 1] = z; - } - - for (i = 1; i <= nt; i++) { - z = zo[i - 1]; - if ((kf == 0) || (kf == 2)) { - cy01(1, z, &zf, &zd); - zv[i - 1] = zf; - } else if (kf == 1) { - cy01(0, z, &zf, &zd); - zv[i - 1] = zf; - } - } - return; -} - - -template -T e1xb(T x) { - - // ============================================ - // Purpose: Compute exponential integral E1(x) - // Input : x --- Argument of E1(x) - // Output: E1 --- E1(x) ( x > 0 ) - // ============================================ - - int k, m; - T e1, r, t, t0; - const T ga = 0.5772156649015328; - - if (x == 0.0) { - e1 = 1e300; - } else if (x <= 1.0) { - e1 = 1.0; - r = 1.0; - for (k = 1; k < 26; k++) { - r = -r*k*x/pow(k+1.0, 2); - e1 += r; - if (fabs(r) <= fabs(e1)*1e-15) { break; } - } - e1 = -ga - log(x) + x*e1; - } else { - m = 20 + (int)(80.0/x); - t0 = 0.0; - for (k = m; k > 0; k--) { - t0 = k / (1.0 + k / (x+t0)); - } - t = 1.0 / (x + t0); - e1 = exp(-x)*t; - } - return e1; -} - - -template -std::complex e1z(std::complex z) { - - // ==================================================== - // Purpose: Compute complex exponential integral E1(z) - // Input : z --- Argument of E1(z) - // Output: CE1 --- E1(z) - // ==================================================== - - const T pi = 3.141592653589793; - const T el = 0.5772156649015328; - int k; - std::complex ce1, cr, zc, zd, zdc; - T x = z.real(); - T a0 = std::abs(z); - // Continued fraction converges slowly near negative real axis, - // so use power series in a wedge around it until radius 40.0 - T xt = -2.0*fabs(z.imag()); - - if (a0 == 0.0) { return 1e300; } - if ((a0 < 5.0) || ((x < xt) && (a0 < 40.0))) { - // Power series - ce1 = 1.0; - cr = 1.0; - for (k = 1; k < 501; k++) { - cr = -cr*z*static_cast(k / std::pow(k + 1, 2)); - ce1 += cr; - if (std::abs(cr) < std::abs(ce1)*1e-15) { break; } - } - if ((x <= 0.0) && (z.imag() == 0.0)) { - //Careful on the branch cut -- use the sign of the imaginary part - // to get the right sign on the factor if pi. - ce1 = -el - std::log(-z) + z*ce1 - copysign(pi, z.imag())*std::complex(0.0, 1.0); - } else { - ce1 = -el - std::log(z) + z*ce1; - } - } else { - // Continued fraction https://dlmf.nist.gov/6.9 - // 1 1 1 2 2 3 3 - // E1 = exp(-z) * ----- ----- ----- ----- ----- ----- ----- ... - // Z + 1 + Z + 1 + Z + 1 + Z + - zc = 0.0; - zd = static_cast(1) / z; - zdc = zd; - zc += zdc; - for (k = 1; k < 501; k++) { - zd = static_cast(1) / (zd*static_cast(k) + static_cast(1)); - zdc *= (zd - static_cast(1)); - zc += zdc; - - zd = static_cast(1) / (zd*static_cast(k) + z); - zdc *= (z*zd - static_cast(1)); - zc += zdc; - if ((std::abs(zdc) <= std::abs(zc)*1e-15) && (k > 20)) { break; } - } - ce1 = std::exp(-z)*zc; - if ((x <= 0.0) && (z.imag() == 0.0)) { - ce1 -= pi*std::complex(0.0, 1.0); - } - } - return ce1; -} - - -template -T eix(T x) { - - // ============================================ - // Purpose: Compute exponential integral Ei(x) - // Input : x --- Argument of Ei(x) - // Output: EI --- Ei(x) - // ============================================ - - const T ga = 0.5772156649015328; - T ei, r; - - if (x == 0.0) { - ei = -1.0e+300; - } else if (x < 0) { - ei = -e1xb(-x); - } else if (fabs(x) <= 40.0) { - // Power series around x=0 - ei = 1.0; - r = 1.0; - - for (int k = 1; k <= 100; k++) { - r = r * k * x / ((k + 1.0) * (k + 1.0)); - ei += r; - if (fabs(r / ei) <= 1.0e-15) { break; } - } - ei = ga + log(x) + x * ei; - } else { - // Asymptotic expansion (the series is not convergent) - ei = 1.0; - r = 1.0; - for (int k = 1; k <= 20; k++) { - r = r * k / x; - ei += r; - } - ei = exp(x) / x * ei; - } - return ei; -} - - -template -std::complex eixz(std::complex z) { - - // ============================================ - // Purpose: Compute exponential integral Ei(x) - // Input : x --- Complex argument of Ei(x) - // Output: EI --- Ei(x) - // ============================================ - - std::complex cei; - const T pi = 3.141592653589793; - cei = - e1z(-z); - if (z.imag() > 0.0) { - cei += std::complex(0.0, pi); - } else if (z.imag() < 0.0 ) { - cei -= std::complex(0.0, pi); - } else { - if (z.real() > 0.0) { - cei += std::complex(0.0, copysign(pi, z.imag())); - } - } - return cei; -} - - -inline void eulerb(int n, double *en) { - - // ====================================== - // Purpose: Compute Euler number En - // Input : n --- Serial number - // Output: EN(n) --- En - // ====================================== - - int k, m, isgn; - double r1, r2, s; - const double hpi = 2.0 / 3.141592653589793; - en[0] = 1.0; - en[2] = -1.0; - r1 = -4.0*pow(hpi, 3); - for (m = 4; m <= n; m += 2) { - r1 = -r1 * (m-1) * m * hpi * hpi; - r2 = 1.0; - isgn = 1; - for (k = 3; k <= 1000; k += 2) { - isgn = -isgn; - s = pow(1.0 / k, m + 1); - r2 += isgn * s; - if (s < 1e-15) { break; } - } - en[m] = r1*r2; - } - return; -} - - -template -void fcoef(int kd, int m, T q, T a, T *fc) { - - // ===================================================== - // Purpose: Compute expansion coefficients for Mathieu - // functions and modified Mathieu functions - // Input : m --- Order of Mathieu functions - // q --- Parameter of Mathieu functions - // KD --- Case code - // KD=1 for cem(x,q) ( m = 0,2,4,...) - // KD=2 for cem(x,q) ( m = 1,3,5,...) - // KD=3 for sem(x,q) ( m = 1,3,5,...) - // KD=4 for sem(x,q) ( m = 2,4,6,...) - // A --- Characteristic value of Mathieu - // functions for given m and q - // Output: FC(k) --- Expansion coefficients of Mathieu - // functions ( k= 1,2,...,KM ) - // FC(1),FC(2),FC(3),... correspond to - // A0,A2,A4,... for KD=1 case, A1,A3, - // A5,... for KD=2 case, B1,B3,B5,... - // for KD=3 case and B2,B4,B6,... for - // KD=4 case - // ===================================================== - - int i, k, j, jm = 0, km, kb; - T f1, fnan, qm, s, f, u, v, f2, f3, sp, ss, s0; - - for (i = 0; i < 251; ++i) { fc[i] = 0.0; } - - if (fabs(q) <= 1.0e-7) { - // Expansion up to order Q^1 (Abramowitz & Stegun 20.2.27-28) - if (kd == 1) { - jm = m / 2 + 1; - } else if ((kd == 2) || (kd == 3)) { - jm = (m - 1) / 2 + 1; - } else if (kd == 4) { - jm = m / 2; - } - - if (jm + 1 > 251) { - fnan = NAN; - for (i = 0; i < 251; ++i) { - fc[i] = fnan; - } - return; - } - // Proceed using the simplest expansion - if (kd == 1 || kd == 2) { - if (m == 0) { - fc[0] = 1.0 / sqrt(2.0); - fc[1] = -q / (2.0 * sqrt(2.0)); - } else if (m == 1) { - fc[0] = 1.0; - fc[1] = -q / 8.0; - } else if (m == 2) { - fc[0] = q / 4.0; - fc[1] = 1.0; - fc[2] = -q / 12.0; - } else { - fc[jm - 1] = 1.0; - fc[jm] = -q / (4.0 * (m + 1)); - fc[jm - 2] = q / (4.0 * (m - 1)); - } - } else if (kd == 3 || kd == 4) { - if (m == 1) { - fc[0] = 1.0; - fc[1] = -q / 8.0; - } else if (m == 2) { - fc[0] = 1.0; - fc[1] = -q / 12.0; - } else { - fc[jm - 1] = 1.0; - fc[jm] = -q / (4.0 * (m + 1)); - fc[jm - 2] = q / (4.0 * (m - 1)); - } - } - return; - } else if (q <= 1.0) { - qm = 7.5 + 56.1 * sqrt(q) - 134.7 * q + 90.7 * sqrt(q) * q; - } else { - qm = 17.0 + 3.1 * sqrt(q) - 0.126 * q + 0.0037 * sqrt(q) * q; - } - - km = (int)(qm + 0.5 * m); - - if (km > 251) { - // Overflow, generate NaNs - for (i = 0; i < 251; ++i) { - fc[i] = NAN; - } - return; - } - - kb = 0; - s = 0.0; - f = 1.0e-100; - u = 0.0; - fc[km - 1] = 0.0; - f2 = 0.0; - - if (kd == 1) { - for (k = km; k >= 3; k--) { - v = u; - u = f; - f = (a - 4.0 * k * k) * u / q - v; - - if (fabs(f) < fabs(fc[k])) { - kb = k; - fc[0] = 1.0e-100; - sp = 0.0; - f3 = fc[k]; - fc[1] = a / q * fc[0]; - fc[2] = (a - 4.0) * fc[1] / q - 2.0 * fc[0]; - u = fc[1]; - f1 = fc[2]; - - for (i = 3; i <= kb; i++) { - v = u; - u = f1; - f1 = (a - 4.0 * (i - 1.0) * (i - 1.0)) * u / q - v; - fc[i] = f1; - - if (i == kb) { f2 = f1; } - if (i != kb) { sp += f1*f1; } - } - - sp += 2.0*fc[0]*fc[0] + fc[1]*fc[1] + fc[2]*fc[2]; - ss = s + sp * (f3 / f2) * (f3 / f2); - s0 = sqrt(1.0 / ss); - - for (j = 1; j <= km; j++) { - if (j <= kb + 1) { - fc[j - 1] = s0 * fc[j-1] * f3 / f2; - } else { - fc[j - 1] *= s0; - } - } - if (fc[0] < 0.0) { for (j = 0; j < km; j++) { fc[j] = -fc[j]; } } - return; - } else { - fc[k - 1] = f; - s += f*f; - } - } - - fc[1] = q * fc[2] / (a - 4.0 - 2.0 * q * q / a); - fc[0] = q / a * fc[1]; - s += 2.0 * fc[0] * fc[0] + fc[1] * fc[1]; - s0 = sqrt(1.0 / s); - - for (k = 1; k <= km; k++) { - fc[k - 1] *= s0; - } - } else if ((kd == 2) || (kd == 3)) { - for (k = km; k >= 3; k--) { - v = u; - u = f; - f = (a - (2.0 * k - 1) * (2.0 * k - 1)) * u / q - v; - - if (fabs(f) >= fabs(fc[k - 1])) { - fc[k - 2] = f; - s += f * f; - } else { - kb = k; - f3 = fc[k - 1]; - goto L45; - } - } - - fc[0] = q / (a - 1.0 - pow(-1, kd) * q) * fc[1]; - s += fc[0] * fc[0]; - s0 = sqrt(1.0 / s); - - for (k = 1; k <= km; k++) { - fc[k - 1] *= s0; - } - if (fc[0] < 0.0) { for (j = 0; j < km; j++) { fc[j] = -fc[j]; } } - return; -L45: - fc[0] = 1.0e-100; - fc[1] = (a - 1.0 - pow(-1, kd) * q) / q * fc[0]; - sp = 0.0; - u = fc[0]; - f1 = fc[1]; - - for (i = 2; i <= kb - 1; i++) { - v = u; - u = f1; - f1 = (a - (2.0 * i - 1) * (2.0 * i - 1)) * u / q - v; - - if (i != kb - 1) { - fc[i] = f1; - sp += f1 * f1; - } else { - f2 = f1; - } - } - - sp += fc[0] * fc[0] + fc[1] * fc[1]; - ss = s + sp * (f3 / f2) * (f3 / f2); - s0 = sqrt(1.0 / ss); - - for (j = 1; j <= km; j++) { - if (j < kb) { - fc[j - 1] *= s0 * f3 / f2; - } - - if (j >= kb) { - fc[j - 1] *= s0; - } - } - - } else if (kd == 4) { - for (k = km; k >= 3; k--) { - v = u; - u = f; - f = (a - 4.0 * k * k) * u / q - v; - - if (fabs(f) >= fabs(fc[k])) { - fc[k - 2] = f; - s += f*f; - } else { - kb = k; - f3 = fc[k - 1]; - goto L70; - } - } - - fc[0] = q / (a - 4.0) * fc[1]; - s += fc[0] * fc[0]; - s0 = sqrt(1.0 / s); - - for (k = 1; k <= km; k++) { - fc[k - 1] *= s0; - } - if (fc[0] < 0.0) { for (j = 0; j < km; j++) { fc[j] = -fc[j]; } } - return; -L70: - fc[0] = 1.0e-100; - fc[1] = (a - 4.0) / q * fc[0]; - sp = 0.0; - u = fc[0]; - f1 = fc[1]; - - for (i = 2; i <= kb - 1; i++) { - v = u; - u = f1; - f1 = (a - 4.0 * i * i) * u / q - v; - - if (i != kb - 1) { - fc[i] = f1; - sp = sp + f1 * f1; - } else { - f2 = f1; - } - } - - sp += fc[0] * fc[0] + fc[1] * fc[1]; - ss = s + sp * (f3 / f2) * (f3 / f2); - s0 = sqrt(1.0 / ss); - - for (j = 1; j <= km; j++) { - if (j < kb) { - fc[j - 1] *= s0 * f3 / f2; - } else { - fc[j - 1] *= s0; - } - } - } - if (fc[0] < 0.0) { for (j = 0; j < km; j++) { fc[j] = -fc[j]; } } - return; -} - - -inline double gaih(double x) { - - // ===================================================== - // Purpose: Compute gamma function Г(x) - // Input : x --- Argument of Г(x), x = n/2, n=1,2,… - // Output: GA --- Г(x) - // ===================================================== - - int k, m; - const double pi = 3.141592653589793; - double ga = 0.0; - - if ((x == (int)x) && (x > 0.0)) { - ga = 1.0; - m = (int)(x - 1.0); - for (k = 2; k < (m+1); k++) { - ga *= k; - } - } else if (((x+0.5) == (int)(x+0.5)) && (x > 0.0)) { - m = (int)x; - ga = sqrt(pi); - for (k = 1; k < (m+1); k++) { - ga *= 0.5*(2.0*k - 1.0); - } - } else { - ga = NAN; - } - return ga; -} - - -inline double gam0(double x) { - - // ================================================ - // Purpose: Compute gamma function Г(x) - // Input : x --- Argument of Г(x) ( |x| ≤ 1 ) - // Output: GA --- Г(x) - // ================================================ - double gr; - static const double g[25] = { - 1.0e0, - 0.5772156649015329e0, -0.6558780715202538e0, -0.420026350340952e-1, 0.1665386113822915e0, - -0.421977345555443e-1, -0.96219715278770e-2, 0.72189432466630e-2, -0.11651675918591e-2, - -0.2152416741149e-3, 0.1280502823882e-3, -0.201348547807e-4, -0.12504934821e-5, - 0.11330272320e-5, -0.2056338417e-6, 0.61160950e-8, 0.50020075e-8, -0.11812746e-8, - 0.1043427e-9, 0.77823e-11, -0.36968e-11, 0.51e-12, -0.206e-13, -0.54e-14, 0.14e-14 - }; - gr = g[24]; - for (int k = 23; k >= 0; k--) { - gr = gr*x + g[k]; - } - return 1.0 / (gr * x); -} - - -inline double gamma2(double x) { - - // ================================================== - // Purpose: Compute gamma function Г(x) - // Input : x --- Argument of Г(x) - // ( x is not equal to 0,-1,-2,…) - // Output: GA --- Г(x) - // ================================================== - - double ga, gr, r, z; - int k, m; - const double pi = 3.141592653589793; - static const double g[26] = { - 1.0000000000000000e+00, 0.5772156649015329e+00, -0.6558780715202538e+00, -0.4200263503409520e-01, - 0.1665386113822915e+00, -0.4219773455554430e-01, -0.9621971527877000e-02, 0.7218943246663000e-02, - -0.1165167591859100e-02, -0.2152416741149000e-03, 0.1280502823882000e-03, -0.2013485478070000e-04, - -0.1250493482100000e-05, 0.1133027232000000e-05, -0.2056338417000000e-06, 0.6116095000000000e-08, - 0.5002007500000000e-08, -0.1181274600000000e-08, 0.1043427000000000e-09, 0.7782300000000000e-11, - -0.3696800000000000e-11, 0.5100000000000000e-12, -0.2060000000000000e-13, -0.5400000000000000e-14, - 0.1400000000000000e-14, 0.1000000000000000e-15 - }; - if (x == (int)x) { - if (x > 0.0) { - ga = 1.0; - m = (int)(x - 1); - for (k = 2; k < (m+1); k++) { - ga *= k; - } - } else { - ga = 1e300; - } - } else { - r = 1.0; - if (fabs(x) > 1.0) { - z = fabs(x); - m = (int)z; - for (k = 1; k < (m+1); k++) { - r *= (z-k); - } - z -= m; - } else { - z = x; - } - gr = g[25]; - for ( k = 25; k > 0; k--) { - gr *= z; - gr += g[k-1]; - } - ga = 1.0 / (gr*z); - if (fabs(x) > 1.0) { - ga *= r; - if (x < 0.0) { - ga = -pi / (x*ga*sin(pi*x)); - } - } - } - return ga; -} - - -template -inline void gmn(int m, int n, T c, T x, T *bk, T *gf, T *gd) { - - // =========================================================== - // Purpose: Compute gmn(-ic,ix) and its derivative for oblate - // radial functions with a small argument - // =========================================================== - - int ip, k, nm; - T xm, gf0, gw, gd0, gd1; - const T eps = 1.0e-14; - ip = ((n - m) % 2 == 0 ? 0 : 1); - nm = 25 + (int)(0.5 * (n - m) + c); - xm = pow(1.0 + x * x, -0.5 * m); - gf0 = 0.0; - gw = 0.0; - - for (k = 1; k <= nm; k++) { - gf0 += bk[k - 1] * pow(x, 2.0 * k - 2.0); - if ((fabs((gf0 - gw) / gf0) < eps) && (k >= 10)) { break; } - gw = gf0; - } - - *gf = xm * gf0 * pow(x, 1 - ip); - gd1 = -m * x / (1.0 + x * x) * (*gf); - gd0 = 0.0; - - for (k = 1; k < nm; ++k) { - if (ip == 0) { - gd0 += (2.0 * k - 1.0) * bk[k - 1] * pow(x, 2.0 * k - 2.0); - } else { - gd0 += 2.0 * k * bk[k - 1] * pow(x, 2.0 * k - 1.0); - } - if ((fabs((gd0 - gw) / gd0) < eps) && (k >= 10)) { break; } - gw = gd0; - } - *gd = gd1 + xm * gd0; -} - - -inline std::complex hygfz(double a, double b, double c, std::complex z, int *isfer) { - - // ====================================================== - // Purpose: Compute the hypergeometric function for a - // complex argument, F(a,b,c,z) - // Input : a --- Parameter - // b --- Parameter - // c --- Parameter, c <> 0,-1,-2,... - // z --- Complex argument - // Output: ZHF --- F(a,b,c,z) - // ISFER --- Error flag - // Routines called: - // (1) GAMMA2 for computing gamma function - // (2) PSI_SPEC for computing psi function - // ====================================================== - - int L0 = 0, L1 = 0, L2 = 0, L3 = 0, L4 = 0, L5 = 0, L6 = 0; - int j, k=1, m, mab, mcab, nca, ncb, nm; - double a0, aa, bb, ca, cb, g0, g1, g2, g3, ga, gab, gam, gabc, gb, gba, gbm, gc, gcab,\ - gca, gcb, gm, pa, pac, pb, pca, rk1, rk2, rm, sp0, sm, sp, sq, sj1, sj2, w0, ws; - std::complex z00, z1, zc0, zc1, zf0, zf1, zhf = 0.0, zp, zr, zp0, zr0, zr1, zw = 0.0; - double x = z.real(); - double y = z.imag(); - double eps = 1e-15; - double pi = 3.141592653589793; - double el = 0.5772156649015329; - *isfer = 0; - - if ((c == (int)c) && (c < 0.0)) { L0 = 1; } - if ((fabs(1 - x) < eps) && (y == 0.0) && (c-a-b <= 0.0)) { L1 = 1; } - if ((std::abs(z+1.0) < eps) && (fabs(c-a+b - 1.0) < eps)) { L2 = 1; } - if ((a == (int)a) && (a < 0.0)) { L3 = 1; } - if ((b == (int)b) && (b < 0.0)) { L4 = 1; } - if (((c-a) == (int)(c-a)) && (c-a <= 0.0)) { L5 = 1; } - if (((c-b) == (int)(c-b)) && (c-b <= 0.0)) { L6 = 1; } - aa = a; - bb = b; - a0 = std::abs(z); - if (a0 > 0.95) { eps = 1e-8; } - if (L0 || L1) { - *isfer = 3; - return 0.0; - } - - if ((a0 == 0.0) || (a == 0.0) || (b == 0.0)) { - zhf = 1.0; - } else if ((z == 1.0) && (c-a-b > 0.0)) { - gc = gamma2(c); - gcab = gamma2(c-a-b); - gca = gamma2(c-a); - gcb = gamma2(c-b); - zhf = gc*gcab/(gca*gcb); - } else if (L2) { - g0 = sqrt(pi)*pow(2.0, -a); - g1 = gamma2(c); - g2 = gamma2(1.0 + 0.5*a - b); - g3 = gamma2(0.5 + 0.5*a); - zhf = g0*g1/(g2*g3); - } else if (L3 || L4) { - if (L3) { nm = (int)fabs(a); } - if (L4) { nm = (int)fabs(b); } - zhf = 1.0; - zr = 1.0; - for (k = 1; k < (nm+1); k++) { - zr = zr*(c-a+k-1.0)*(c-b+k-1.0)/(k*(c+k-1.0))*z; - zhf += zr; - } - } else if (L5 || L6) { - if (L5) { nm = (int)fabs(c-a); } - if (L6) { nm = (int)fabs(c-b); } - zhf = 1.0; - zr = 1.0; - for (k = 1; k < (nm+1); k++) { - zr = zr*(c-a+k-1.0)*(c-b+k-1.0)/(k*(c+k-1.0))*z; - zhf += zr; - } - zhf *= std::pow(1.0-z, c-a-b); - } else if (a0 <= 1.0) { - if (x < 0.0) { - z1 = z / (z - 1.0); - if ((c > a) && (b < a) && (b > 0.0)) { - a = aa; - b = bb; - } - zc0 = 1.0 / std::pow(1.0 - z, a); - zhf = 1.0; - zr0 = 1.0; - zw = 0.0; - for (k = 1; k <501; k++) { - zr0 = zr0*(a+k-1.0)*(c-b+k-1.0)/(k*(c+k-1.0))*z1; - zhf += zr0; - if (std::abs(zhf-zw) < std::abs(zhf)*eps) { break; } - zw = zhf; - } - zhf *= zc0; - } else if (a0 >= 0.9) { - gm = 0.0; - mcab = (int)(c-a-b + eps*copysign(1.0, c-a-b)); - if (fabs(c-a-b-mcab) < eps) { - m = (int)(c-a-b); - ga = gamma2(a); - gb = gamma2(b); - gc = gamma2(c); - gam = gamma2(a+m); - gbm = gamma2(b+m); - pa = psi_spec(a); - pb = psi_spec(b); - if (m != 0) { gm = 1.0; } - for (j = 1; j < abs(m); j++) { - gm *= j; - } - rm = 1.0; - for (j = 1; j < abs(m)+1; j++) { - rm *= j; - } - zf0 = 1.0; - zr0 = 1.0; - zr1 = 1.0; - sp0 = 0.0; - sp = 0.0; - if (m >= 0) { - zc0 = gm*gc/(gam*gbm); - zc1 = -gc*std::pow(z-1.0, m)/(ga*gb*rm); - for (k = 1; k < m; k++) { - zr0 = zr0*(a+k-1.0)*(b+k-1.0)/static_cast(k*(k-m))*(1.0-z); - zf0 += zr0; - } - for (k = 1; k < (m+1); k++) { - sp0 += 1.0/(a+k-1.0) + 1.0/(b+k-1.0) - 1.0/k; - } - zf1 = pa + pb + sp0 + 2.0*el + std::log(1.0 - z); - zw = 0.0; - for (k = 1; k <501; k++) { - sp += (1.0-a)/(k*(a+k-1.0)) + (1.0-b)/(k*(b+k-1.0)); - sm = 0.0; - for (j = 1; j < (m+1); j++) { - sm += (1.0-a)/((j+k)*(a+j+k-1.0)) + 1.0/(b+j+k-1.0); - } - zp = pa + pb + 2.0*el + sp + sm + std::log(1.0 - z); - zr1 = zr1*(a+m+k-1.0)*(b+m+k-1.0) / static_cast(k*(m+k))*(1.0-z); - zf1 += zr1*zp; - if (std::abs(zf1-zw) < std::abs(zf1)*eps) { break; } - zw = zf1; - } - zhf = zf0*zc0 + zf1*zc1; - } else if (m < 0) { - m = -m; - zc0 = gm*gc/(ga*gb*std::pow(1.0 - z, m)); - zc1 = -(pow(-1.0, m))*gc/(gam*gbm*rm); - for (k = 1; k < m; k++) { - zr0 = zr0*(a-m+k-1.0)*(b-m+k-1.0)/static_cast(k*(k-m))*(1.0-z); - zf0 += zr0; - } - for (k = 1; k < (m+1); k++) { - sp0 += 1.0 / k; - } - zf1 = pa + pb -sp0 + 2.0*el + std::log(1.0 - z); - zw = 0.0; - for (k = 1; k <501; k++) { - sp += (1.0-a)/(k*(a+k-1.0)) + (1.0-b)/(k*(b+k-1.0)); - sm = 0.0; - for (j = 1; j < (m+1); j++) { - sm += 1.0/(j+k); - } - zp = pa + pb+2.0*el + sp - sm + std::log(1.0 -z ); - zr1 = zr1*(a+k-1.0)*(b+k-1.0)/static_cast(k*(m+k))*(1.0-z); - zf1 += zr1*zp; - if (std::abs(zf1-zw) < std::abs(zf1)*eps) { break; } - zw = zf1; - } - zhf = zf0*zc0 + zf1*zc1; - } - } else { - ga = gamma2(a); - gb = gamma2(b); - gc = gamma2(c); - gca = gamma2(c-a); - gcb = gamma2(c-b); - gcab = gamma2(c-a-b); - gabc = gamma2(a+b-c); - zc0 = gc*gcab/(gca*gcb); - zc1 = gc*gabc/(ga*gb)*std::pow(1.0-z, c-a-b); - zhf = 0.0; - zr0 = zc0; - zr1 = zc1; - zw = 0.0; - for (k = 1; k < 501; k++) { - zr0 = zr0*(a+k-1.0)*(b+k-1.0)/(k*(a+b-c+k))*(1.0-z); - zr1 = zr1*(c-a+k-1.0)*(c-b+k-1.0)/(k*(c-a-b+k))*(1.0-z); - zhf += zr0+zr1; - if (std::abs(zhf-zw) < std::abs(zhf)*eps) { break; } - zw = zhf; - } - zhf += zc0 + zc1; - } - } else { - z00 = 1.0; - if ((c-a < a) && (c-b < b)) { - z00 = std::pow(1.0 - z, c-a-b); - a = c-a; - b = c-b; - } - zhf = 1.0; - zr = 1.0; - zw = 0.0; - for (k = 1; k < 1501; k++) { - zr = zr*(a+k-1.0)*(b+k-1.0)/(k*(c+k-1.0))*z; - zhf += zr; - if (std::abs(zhf-zw) < std::abs(zhf)*eps) { break; } - zw = zhf; - } - zhf *= z00; - } - } else if (a0 > 1.0) { - mab = (int)(a - b + eps*copysign(1.0, a - b)); - if ((fabs(a-b-mab) < eps) && (a0 <= 1.1)) { b += eps; } - if (fabs(a-b-mab) > eps) { - ga = gamma2(a); - gb = gamma2(b); - gc = gamma2(c); - gab = gamma2(a-b); - gba = gamma2(b-a); - gca = gamma2(c-a); - gcb = gamma2(c-b); - zc0 = gc*gba/(gca*gb*std::pow(-z, a)); - zc1 = gc*gab/(gcb*ga*std::pow(-z, b)); - zr0 = zc0; - zr1 = zc1; - zhf = 0.0; - for (k = 1; k < 501; k++) { - zr0 = zr0*(a+k-1.0)*(a-c+k)/((a-b+k)*k*z); - zr1 = zr1*(b+k-1.0)*(b-c+k)/((b-a+k)*k*z); - zhf += zr0+zr1; - if (std::abs(zhf-zw) < std::abs(zhf)*eps) { break; } - zw = zhf; - } - zhf += zc0 + zc1; - } else { - if (a-b < 0.0) { - a = bb; - b = aa; - } - ca = c - a; - cb = c - b; - nca = (int)(ca + eps*copysign(1.0, ca)); - ncb = (int)(cb + eps*copysign(1.0, cb)); - if ((fabs(ca-nca) < eps) || (fabs(cb-ncb) < eps)) { c += eps; } - ga = gamma2(a); - gc = gamma2(c); - gcb = gamma2(c-b); - pa = psi_spec(a); - pca = psi_spec(c-a); - pac = psi_spec(a-c); - mab = (int)(a-b+eps); - zc0 = gc / (ga*std::pow(-z, b)); - gm = gamma2(a-b); - zf0 = gm/gcb*zc0; - zr = zc0; - for (k = 1; k < mab; k++) { - zr = zr*(b+k-1.0)/(static_cast(k)*z); - g0 = gamma2(a-b-k); - zf0 += zr*g0/gamma2(c-b-k); - } - if (mab == 0) { zf0 = 0.0; } - zc1 = gc/(ga*gcb*std::pow(-z, a)); - sp = -2.0*el - pa- pca; - for (j = 1; j < (mab+1); j++) { - sp += 1.0 / j; - } - zp0 = sp + std::log(-z); - sq = 1.0; - for (j = 1; j < (mab+1); j++) { - sq = sq * (b+j-1.0)*(b-c+j)/j; - } - zf1 = (sq*zp0)*zc1; - zr = zc1; - rk1 = 1.0; - sj1 = 0.0; - w0 = 0.0; - for (k = 1; k < 10001; k++) { - zr /= z; - rk1 = rk1*(b+k-1.0)*(b-c+k)/(k*k); - rk2 = rk1; - for (j = k+1; j <= (k+mab); j++) { - rk2 = rk2 * (b+j-1.0)*(b-c+j)/j; - } - sj1 += (a-1.0)/(k*(a+k-1.0)) + (a-c-1.0)/(k*(a-c+k-1.0)); - sj2 = sj1; - for (j = k+1; j <= (k+mab); j++) { - sj2 += 1.0 / j; - } - zp= -2.0*el -pa - pac + sj2 - 1.0/(k+a-c) - pi/tan(pi*(k+a-c)) + std::log(-z); - zf1 += rk2*zr*zp; - ws = std::abs(zf1); - if (fabs((ws-w0)/ws) < eps) { break; } - w0 = ws; - } - zhf = zf0 + zf1; - } - } - a = aa; - b = bb; - if (k > 150) { *isfer = 5; } - return zhf; -} - -inline Status jdzo(int nt, double *zo, int *n, int *m, int *p) { - - // =========================================================== - // Purpose: Compute the zeros of Bessel functions Jn(x) and - // Jn'(x), and arrange them in the order of their - // magnitudes - // Input : NT --- Number of total zeros ( NT ≤ 1200 ) - // Output: ZO(L) --- Value of the L-th zero of Jn(x) - // and Jn'(x) - // N(L) --- n, order of Jn(x) or Jn'(x) associated - // with the L-th zero - // M(L) --- m, serial number of the zeros of Jn(x) - // or Jn'(x) associated with the L-th zero - // ( L is the serial number of all the - // zeros of Jn(x) and Jn'(x) ) - // P(L) --- 0 (TM) or 1 (TE), a code for designating the - // zeros of Jn(x) or Jn'(x). - // In the waveguide applications, the zeros - // of Jn(x) correspond to TM modes and - // those of Jn'(x) correspond to TE modes - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // - // Routine called: BJNDD for computing Jn(x), Jn'(x) and - // Jn''(x) - // ============================================================= - - int i, j, k, L, L0, L1, L2, mm, nm; - double x, x0, x1, x2, xm; - - auto p1 = std::unique_ptr{new (std::nothrow) int[70]()}; - - // Compared to specfun.f we use a single array instead of separate - // three arrays and use pointer arithmetic to access. Their usage - // is pretty much one-shot hence does not complicate the code. - - // Note: ZO and ZOC arrays are 0-indexed in specfun.f - - // m1, n1, zoc -> 70 + 70 + 71 - auto mnzoc = std::unique_ptr{new (std::nothrow) double[211]()}; - - // bj, dj, fj -> 101 + 101 + 101 - auto bdfj = std::unique_ptr{new (std::nothrow) double[303]()}; - - if (p1.get() == nullptr || mnzoc.get() == nullptr || bdfj.get() == nullptr) { - return Status::NoMemory; - } - - x = 0; - - if (nt < 600) { - xm = -1.0 + 2.248485*sqrt(nt) - 0.0159382*nt + 3.208775e-4*pow(nt, 1.5); - nm = (int)(14.5 + 0.05875*nt); - mm = (int)(0.02*nt) + 6; - } else { - xm = 5.0 + 1.445389*sqrt(nt) + 0.01889876*nt - 2.147763e-4*pow(nt, 1.5); - nm = (int)(27.8 + 0.0327*nt); - mm = (int)(0.01088*nt) + 10; - } - - L0 = 0; - /* 45 LOOP */ - for (i = 1; i < (nm+1); i++) { - x1 = 0.407658 + 0.4795504*sqrt(i-1.0) + 0.983618*(i-1); - x2 = 1.99535 + 0.8333883*sqrt(i-1.0) + 0.984584*(i-1); - L1 = 0; - - /* 30 LOOP */ - for (j = 1; j < (mm+1); j++) { - if ((i != 1) || (j != 1)) { - x = x1; - do - { - bjndd(x, i, &bdfj[0], &bdfj[101], &bdfj[202]); - x0 = x; - x -= bdfj[100+i]/bdfj[201+i]; - if (x1 > xm) { goto L20; } - } while (fabs(x-x0) > 1e-10); - } - /* 15 */ - L1 += 1; - mnzoc[69 + L1] = i-1; /* N[L1] */ - mnzoc[L1-1] = j; /* M[L1] */ - if (i == 1) { mnzoc[L1 - 1] = j-1; } - p1[L1-1] = 1; - mnzoc[140+L1] = x; /* ZOC[L1] */ - if (i <= 15) { - x1 = x + 3.057 + 0.0122*(i-1) + (1.555 + 0.41575*(i-1))/pow(j+1, 2.0); - } else { - x1 = x + 2.918 + 0.01924*(i-1) + (6.26 + 0.13205*(i-1))/pow(j+1, 2.0); - } -L20: - x = x2; - do { - bjndd(x, i, &bdfj[0], &bdfj[101], &bdfj[202]); - x0 = x; - x -= bdfj[i-1]/bdfj[100+i]; - if (x > xm) { goto L30; } /* Need to "continue;" twice hence goto is simpler */ - } while (fabs(x-x0) > 1e-10); - L1 += 1; - mnzoc[69 + L1] = i-1; - mnzoc[L1-1] = j; - p1[L1-1] = 0; - mnzoc[140+L1] = x; - if (i <= 15) { - x2 = x + 3.11 + 0.0138*(i-1) + (0.04832 + 0.2804*(i-1))/pow(j+1, 2); - } else { - x2 = x + 3.001 + 0.0105*(i-1) + (11.52 + 0.48525*(i-1))/pow(j+3, 2); - } -L30: - ; /* Do nothing line to silence compiler */ - } - L = L0 + L1; - L2 = L; - do { - if (L0 == 0) { - for (k = 1; k < (L+1); k++) { - p[k-1] = p1[k-1]; - m[k-1] = mnzoc[k-1]; /* m[k-1] = mnzoc[k-1] */ - n[k-1] = mnzoc[69+k]; /* n[k-1] = mnzoc[70 + (k-1)] */ - zo[k] = mnzoc[140+k]; - } - L1 = 0; - } else if (L0 != 0) { - if (zo[L0] >= mnzoc[140+L1]) { - p[L0+L1-1] = p[L0-1]; - m[L0+L1-1] = m[L0-1]; - n[L0+L1-1] = n[L0-1]; - zo[L0+L1] = zo[L0]; - L0 -= 1; - } else { - p[L0+L1-1] = p1[L1-1]; - m[L0+L1-1] = mnzoc[L1-1]; - n[L0+L1-1] = mnzoc[69+L1]; - zo[L0+L1] = mnzoc[140+L1]; - L1 -= 1; - } - } - } while (L1 != 0); - /* 45 */ - L0 = L2; - } - return Status::OK; -} - - -template -void jynb(int n, T x, int *nm, T *bj, T *dj, T *by, T *dy) { - - // ===================================================== - // Purpose: Compute Bessel functions Jn(x), Yn(x) and - // their derivatives - // Input : x --- Argument of Jn(x) and Yn(x) ( x ≥ 0 ) - // n --- Order of Jn(x) and Yn(x) - // Output: BJ(n) --- Jn(x) - // DJ(n) --- Jn'(x) - // BY(n) --- Yn(x) - // DY(n) --- Yn'(x) - // NM --- Highest order computed - // Routines called: - // JYNBH to calculate the Jn and Yn - // ===================================================== - - int k; - jynbh(n, 0, x, nm, bj, by); - // Compute derivatives by differentiation formulas - if (x < 1.0e-100) { - for (k = 0; k <= n; k++) { - dj[k] = 0.0; - dy[k] = 1.0e+300; - } - dj[1] = 0.5; - } else { - dj[0] = -bj[1]; - for (k = 1; k <= *nm; k++) { - dj[k] = bj[k - 1] - k / x * bj[k]; - } - - dy[0] = -by[1]; - for (k = 1; k <= *nm; k++) { - dy[k] = by[k - 1] - k * by[k] / x; - } - } - return; -} - - -template -void jynbh(int n, int nmin, T x, int *nm, T *bj, T *by) { - - // ===================================================== - // Purpose: Compute Bessel functions Jn(x), Yn(x) - // Input : x --- Argument of Jn(x) and Yn(x) ( x ≥ 0 ) - // n --- Highest order of Jn(x) and Yn(x) computed ( n ≥ 0 ) - // nmin -- Lowest order computed ( nmin ≥ 0 ) - // Output: BJ(n-NMIN) --- Jn(x) ; if indexing starts at 0 - // BY(n-NMIN) --- Yn(x) ; if indexing starts at 0 - // NM --- Highest order computed - // Routines called: - // MSTA1 and MSTA2 to calculate the starting - // point for backward recurrence - // ===================================================== - - int k, m, ky; - T pi = 3.141592653589793; - T r2p = 0.63661977236758; - T bs, s0, su, sv, f2, f1, f; - T bj0, bj1, ec, by0, by1, bjk, byk; - T p0, q0, cu, t1, p1, q1, t2; - - T a[4] = { -0.0703125, 0.112152099609375, -0.5725014209747314, 0.6074042001273483e+01 }; - T b[4] = { 0.0732421875, -0.2271080017089844, 0.1727727502584457e+01, -0.2438052969955606e+02 }; - T a1[4] = { 0.1171875, -0.144195556640625, 0.6765925884246826, -0.6883914268109947e+01 }; - T b1[4] = { -0.1025390625, 0.2775764465332031, -0.1993531733751297e+01, 0.2724882731126854e+02 }; - - *nm = n; - if (x < 1.0e-100) { - for (k = nmin; k <= n; k++) { - bj[k - nmin] = 0.0; - by[k - nmin] = -1.0e+300; - } - - if (nmin == 0) { bj[0] = 1.0; } - return; - } - - if ((x <= 300.0) || (n > (int)(0.9 * x))) { - // Backward recurrence for Jn - if (n == 0) { - *nm = 1; - } - m = msta1(x, 200); - if (m < *nm) { - *nm = m; - } else { - m = msta2(x, *nm, 15); - } - bs = 0.0; - su = 0.0; - sv = 0.0; - f2 = 0.0; - f1 = 1.0e-100; - f = 0.0; - - for (k = m; k >= 0; k--) { - f = 2.0*(k + 1.0)/x*f1 - f2; - if ((k <= *nm) && (k >= nmin)) { - bj[k - nmin] = f; - } - if (k == 2 * (int)(k / 2) && k != 0) { - bs += 2.0 * f; - su += pow(-1, k / 2) * f / k; - } else if (k > 1) { - sv += pow(-1, k / 2) * k / (k * k - 1.0) * f; - } - f2 = f1; - f1 = f; - } - s0 = bs + f; - - for (k = nmin; k <= *nm; k++) { - bj[k - nmin] /= s0; - } - // Estimates for Yn at start of recurrence - bj0 = f1 / s0; - bj1 = f2 / s0; - ec = log(x / 2.0) + 0.5772156649015329; - by0 = r2p * (ec * bj0 - 4.0*su/s0); - by1 = r2p * ((ec - 1.0)*bj1 - bj0/x - 4.0*sv/s0); - - if (0 >= nmin) { by[0 - nmin] = by0; } - if (1 >= nmin) { by[1 - nmin] = by1; } - ky = 2; - } else { - // Hankel expansion - t1 = x - 0.25*pi; - p0 = 1.0; - q0 = -0.125/x; - - for (k = 1; k <= 4; k++) { - p0 += a[k - 1] * pow(x,-2*k); - q0 += b[k - 1] * pow(x, -2*k - 1); - } - - cu = sqrt(r2p / x); - bj0 = cu * (p0*cos(t1) - q0*sin(t1)); - by0 = cu * (p0*sin(t1) + q0*cos(t1)); - - if (0 >= nmin) { - bj[0 - nmin] = bj0; - by[0 - nmin] = by0; - } - - t2 = x - 0.75*pi; - p1 = 1.0; - q1 = 0.375/x; - - for (k = 1; k <= 4; k++) { - p1 += a1[k - 1] * pow(x, -2*k); - q1 += b1[k - 1] * pow(x, -2*k - 1); - } - - bj1 = cu * (p1*cos(t2) - q1*sin(t2)); - by1 = cu * (p1*sin(t2) + q1*cos(t2)); - - if (1 >= nmin) { - bj[1 - nmin] = bj1; - by[1 - nmin] = by1; - } - - for (k = 2; k <= *nm; k++) { - bjk = 2.0*(k - 1.0)/x*bj1 - bj0; - if (k >= nmin) { bj[k - nmin] = bjk; } - bj0 = bj1; - bj1 = bjk; - } - ky = 2; - } - // Forward recurrence for Yn - for (k = ky; k <= *nm; k++) { - byk = 2.0 * (k - 1.0) * by1 / x - by0; - - if (k >= nmin) - by[k - nmin] = byk; - - by0 = by1; - by1 = byk; - } -} - - -inline void jyndd(int n, double x, double *bjn, double *djn, double *fjn, double *byn, double *dyn, double *fyn) { - - // =========================================================== - // purpose: compute bessel functions jn(x) and yn(x), and - // their first and second derivatives - // input: x --- argument of jn(x) and yn(x) ( x > 0 ) - // n --- order of jn(x) and yn(x) - // output: bjn --- jn(x) - // djn --- jn'(x) - // fjn --- jn"(x) - // byn --- yn(x) - // dyn --- yn'(x) - // fyn --- yn"(x) - // routines called: - // jynbh to compute jn and yn - // =========================================================== - - int nm = 0; - double bj[2], by[2]; - - jynbh(n+1, n, x, &nm, bj, by); - // compute derivatives by differentiation formulas - *bjn = bj[0]; - *byn = by[0]; - *djn = -bj[1] + n*bj[0]/x; - *dyn = -by[1] + n*by[0]/x; - *fjn = (n*n/(x*x) - 1.0)*(*bjn) - (*djn)/x; - *fyn = (n*n/(x*x) - 1.0)*(*byn) - (*dyn)/x; - return; -} - - -inline void jyzo(int n, int nt, double *rj0, double *rj1, double *ry0, double *ry1) { - - // ====================================================== - // Purpose: Compute the zeros of Bessel functions Jn(x), - // Yn(x), and their derivatives - // Input : n --- Order of Bessel functions (n >= 0) - // NT --- Number of zeros (roots) - // Output: RJ0(L) --- L-th zero of Jn(x), L=1,2,...,NT - // RJ1(L) --- L-th zero of Jn'(x), L=1,2,...,NT - // RY0(L) --- L-th zero of Yn(x), L=1,2,...,NT - // RY1(L) --- L-th zero of Yn'(x), L=1,2,...,NT - // Routine called: JYNDD for computing Jn(x), Yn(x), and - // their first and second derivatives - // ====================================================== - - /* - * SciPy Note: - * See GH-18859 for additional changes done by SciPy for - * better initial condition selection in Newton iteration - */ - - int L; - double b, h, x, x0, bjn, djn, fjn, byn, dyn, fyn; - const double pi = 3.141592653589793; - // -- Newton method for j_{N,L} - // initial guess for j_{N,1} - if (n == 0) { - x = 2.4; - } else { - // https://dlmf.nist.gov/10.21#E40 - x = n + 1.85576*pow(n, 0.33333) + 1.03315/ pow(n, 0.33333); - } - // iterate - L = 0; -L10: - x0 = x; - jyndd(n, x, &bjn, &djn, &fjn, &byn, &dyn, &fyn); - x -= bjn/djn; - if (fabs(x - x0) > 1e-11) { goto L10; } - - L += 1; - rj0[L - 1] = x; - // initial guess for j_{N,L+1} - if (L == 1) { - if (n == 0) { - x = 5.52; - } else { - // Expansion from https://dlmf.nist.gov/10.21#E32 and - // coefficients from Olver 1951 - x= n + 3.24460 * pow(n, 0.33333) + 3.15824 / pow(n, 0.33333); - } - } else { - // growth of roots is approximately linear (https://dlmf.nist.gov/10.21#E19) - x = rj0[L - 1] + (rj0[L - 1] - rj0[L - 2]); - } - if (L <= (n + 10)) { - jyndd(n, x, &bjn, &djn, &fjn, &byn, &dyn, &fyn); - h = atan(fabs(djn) / sqrt(fabs(fjn * bjn))); - b = -djn / (bjn * atan(h)); - x -= (h - pi/2) / b; - } - - if (L < nt) { goto L10; } - - // -- Newton method for j_{N,L+1}' - if (n == 0) { - x = 3.8317; - } else { - // https://dlmf.nist.gov/10.21#E40 - x = n + 0.80861 * pow(n, 0.33333) + 0.07249 / pow(n, 0.33333); - } - // iterate - L=0; -L15: - x0 = x; - jyndd(n, x, &bjn, &djn, &fjn, &byn, &dyn, &fyn); - x -= djn/fjn; - if (fabs(x-x0) > 1e-11) goto L15; - L += 1; - rj1[L - 1] = x; - if (L < nt) { - // https://dlmf.nist.gov/10.21#E20 - x = rj1[L - 1] + (rj0[L] - rj0[L - 1]); - goto L15; - } - - // -- Newton method for y_{N,L} - // initial guess for y_{N,1} - if (n == 0) { - x = 0.89357697; - } else { - // https://dlmf.nist.gov/10.21#E40 - x = n + 0.93158 * pow(n, 0.33333) + 0.26035 / pow(n, 0.33333); - } - // iterate - L=0; -L20: - x0 = x; - jyndd(n, x, &bjn, &djn, &fjn, &byn, &dyn, &fyn); - x -= byn/dyn; - if (fabs(x - x0) > 1.0e-11) goto L20; - L += 1; - ry0[L - 1] = x; - // initial guess for y_{N,L+1} - if (L == 1) { - if (n == 0) { - x = 3.957678419314858; - } else { - // Expansion from https://dlmf.nist.gov/10.21#E33 and - // coefficients from Olver 1951 - x = n + 2.59626 * pow(n, 0.33333) + 2.022183 / pow(n, 0.33333); - } - } else { - // growth of roots is approximately linear (https://dlmf.nist.gov/10.21#E19) - x = ry0[L - 1] + (ry0[L - 1] - ry0[L - 2]); - } - if (L <= n+10) { - jyndd(n, x, &bjn, &djn, &fjn, &byn, &dyn, &fyn); - h = atan(fabs(dyn) / sqrt(fabs(fyn * byn))); - b = -dyn / (byn * tan(h)); - x -= (h - pi/2) / b; - } - - if (L < nt) goto L20; - - // -- Newton method for y_{N,L+1}' - if (n == 0) { - x = 2.67257; - } else { - // https://dlmf.nist.gov/10.21#E40 - x = n + 1.8211 * pow(n, 0.33333) + 0.94001 / pow(n, 0.33333); - } - // iterate - L=0; -L25: - x0 = x; - jyndd(n, x, &bjn, &djn, &fjn, &byn, &dyn, &fyn); - x -= dyn/fyn; - if (fabs(x-x0) > 1.0e-11) goto L25; - L += 1; - ry1[L - 1] = x; - if (L < nt) { - // https://dlmf.nist.gov/10.21#E20 - x=ry1[L - 1] + (ry0[L] - ry0[L - 1]); - goto L25; - } - return; -} - - -template -inline Status kmn(int m, int n, T c, T cv, int kd, T *df, T *dn, T *ck1, T *ck2) { - - // =================================================== - // Purpose: Compute the expansion coefficients of the - // prolate and oblate spheroidal functions - // and joining factors - // - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // =================================================== - - int nm, nn, ip, k, i, l, j; - T cs, gk0, gk1, gk2, gk3, t, r, dnp, su0, sw, r1, r2, r3, sa0, r4, r5, g0, sb0; - nm = 25 + (int)(0.5 * (n - m) + c); - nn = nm + m; - auto u = std::unique_ptr{new (std::nothrow) T[nn + 4]}; - auto v = std::unique_ptr{new (std::nothrow) T[nn + 4]}; - auto w = std::unique_ptr{new (std::nothrow) T[nn + 4]}; - auto tp = std::unique_ptr{new (std::nothrow) T[nn + 4]}; - auto rk = std::unique_ptr{new (std::nothrow) T[nn + 4]}; - if (u.get() == nullptr || v.get() == nullptr || w.get() == nullptr - || tp.get() == nullptr || rk.get() == nullptr) { - return Status::NoMemory; - } - - const T eps = 1.0e-14; - - cs = c * c * kd; - *ck1 = 0.0; - *ck2 = 0.0; - - ip = ((n - m) % 2 == 0 ? 0 : 1); - k = 0; - - for (i = 1; i <= nn + 3; i++) { - k = (ip == 0 ? -2 * (i - 1) : -(2 * i - 3)); - gk0 = 2.0 * m + k; - gk1 = (m + k) * (m + k + 1.0); - gk2 = 2.0 * (m + k) - 1.0; - gk3 = 2.0 * (m + k) + 3.0; - - u[i - 1] = gk0 * (gk0 - 1.0) * cs / (gk2 * (gk2 + 2.0)); - v[i - 1] = gk1 - cv + (2.0 * (gk1 - m * m) - 1.0) * cs / (gk2 * gk3); - w[i - 1] = (k + 1.0) * (k + 2.0) * cs / ((gk2 + 2.0) * gk3); - } - - for (k = 1; k <= m; k++) { - t = v[m]; - for (l = 0; l <= m - k - 1; l++) - t = v[m - l - 1] - w[m - l] * u[m - l - 1] / t; - - rk[k - 1] = -u[k - 1] / t; - } - - r = 1.0; - for (k = 1; k <= m; k++) { - r = r * rk[k - 1]; - dn[k - 1] = df[0] * r; - } - - tp[nn - 1] = v[nn]; - - for (k = nn - 1; k >= m + 1; k--) { - tp[k - 1] = v[k] - w[k + 1] * u[k] / tp[k]; - - if (k > m + 1) - rk[k - 1] = -u[k - 1] / tp[k - 1]; - } - - dnp = (m == 0 ? df[0] : dn[m - 1]); - dn[m] = pow(-1, ip) * dnp * cs / ((2.0 * m - 1.0) * (2.0 * m + 1.0 - 4.0 * ip) * tp[m]); - - for (k = m + 2; k <= nn; k++) - dn[k - 1] = rk[k - 1] * dn[k - 2]; - - r1 = 1.0; - for (j = 1; j <= (n + m + ip) / 2; j++) { - r1 = r1*(j + 0.5 * (n + m + ip)); - } - r = 1.0; - for (j = 1; j <= 2 * m + ip; ++j){ - r *= j; - } - su0 = r * df[0]; - sw = 0.0; - - for (k = 2; k <= nm; ++k) { - r = r * (m + k - 1.0) * (m + k + ip - 1.5) / (k - 1.0) / (k + ip - 1.5); - su0 = su0 + r * df[k - 1]; - if (k > (n - m) / 2 && fabs((su0 - sw) / su0) < eps) { break; } - sw = su0; - } - - if (kd != 1) { - r2 = 1.0; - - for (j = 1; j <= m; ++j) - r2 = 2.0 * c * r2 * j; - - r3 = 1.0; - - for (j = 1; j <= (n - m - ip) / 2; ++j) - r3 = r3 * j; - - sa0 = (2.0 * (m + ip) + 1.0) * r1 / (pow(2.0, n) * pow(c, ip) * r2 * r3 * df[0]); - *ck1 = sa0 * su0; - - if (kd == -1) { - return Status::OK; - } - } - - r4 = 1.0; - for (j = 1; j <= (n - m - ip) / 2; ++j) { - r4 *= 4.0 * j; - } - r5 = 1.0; - for (j = 1; j <= m; ++j) - r5 = r5 * (j + m) / c; - - if (m == 0) - g0 = df[0]; - else - g0 = dn[m - 1]; - - sb0 = (ip + 1.0) * pow(c, ip + 1) / (2.0 * ip * (m - 2.0) + 1.0) / (2.0 * m - 1.0); - *ck2 = pow(-1, ip) * sb0 * r4 * r5 * g0 / r1 * su0; - - return Status::OK; -} - - -inline void lamn(int n, double x, int *nm, double *bl, double *dl) { - - // ========================================================= - // Purpose: Compute lambda functions and their derivatives - // Input: x --- Argument of lambda function - // n --- Order of lambda function - // Output: BL(n) --- Lambda function of order n - // DL(n) --- Derivative of lambda function - // NM --- Highest order computed - // Routines called: - // MSTA1 and MSTA2 for computing the start - // point for backward recurrence - // ========================================================= - - int i, k, m; - double bk, r, uk, bs, f, f0, f1, bg, r0, x2; - - *nm = n; - if (fabs(x) < 1e-100) { - for (k = 0; k <= n; k++) { - bl[k] = 0.0; - dl[k] = 0.0; - } - bl[0] = 1.0; - dl[1] = 0.5; - return; - } - if (x <= 12.0) { - x2 = x * x; - for (k = 0; k <= n; k++) { - bk = 1.0; - r = 1.0; - for (i = 1; i <= 50; i++) { - r = -0.25 * r * x2 / (i * (i + k)); - bk += r; - - if (fabs(r) < fabs(bk) * 1.0e-15) { break; } - } - bl[k] = bk; - if (k >= 1) { - dl[k - 1] = -0.5 * x / k * bk; - } - } - uk = 1.0; - r = 1.0; - for (i = 1; i <= 50; i++) { - r = -0.25 * r * x2 / (i * (i + n + 1.0)); - uk += r; - - if (fabs(r) < fabs(uk) * 1.0e-15) { break; } - } - dl[n] = -0.5 * x / (n + 1.0) * uk; - return; - } - if (n == 0) { - *nm = 1; - } - m = msta1(x, 200); - if (m < *nm) { - *nm = m; - } else { - m = msta2(x, *nm, 15); - } - bs = 0.0; - f = 0.0; - f0 = 0.0; - f1 = 1e-100; - for (k = m; k >= 0; k--) { - f = 2.0 * (k + 1.0) * f1 / x - f0; - if (k <= *nm) { - bl[k] = f; - } - if (k % 2 == 0) { - bs += 2.0 * f; - } - f0 = f1; - f1 = f; - } - bg = bs - f; - for (k = 0; k <= *nm; k++) { - bl[k] /= bg; - } - r0 = 1.0; - for (k = 1; k <= *nm; k++) { - r0 = 2.0 * r0 * k / x; - bl[k] *= r0; - } - dl[0] = -0.5 * x * bl[1]; - for (k = 1; k <= *nm; k++) { - dl[k] = 2.0 * k / x * (bl[k - 1] - bl[k]); - } - return; -} - - -inline void lamv(double v, double x, double *vm, double *vl, double *dl) { - - // ========================================================= - // Purpose: Compute lambda function with arbitrary order v, - // and their derivative - // Input : x --- Argument of lambda function - // v --- Order of lambda function - // Output: VL(n) --- Lambda function of order n+v0 - // DL(n) --- Derivative of lambda function - // VM --- Highest order computed - // Routines called: - // (1) MSTA1 and MSTA2 for computing the starting - // point for backward recurrence - // (2) GAM0 for computing gamma function (|x| ≤ 1) - // ========================================================= - - int i, n, k, j, k0, m; - double cs, ga, fac, r0, f0, f1, f2, f, xk, vv; - double x2, v0, vk, bk, r, uk, qx, px, rp, a0, ck, sk, bjv0, bjv1; - const double pi = 3.141592653589793; - const double rp2 = 0.63661977236758; - - x = fabs(x); - x2 = x * x; - n = (int)v; - v0 = v - n; - *vm = v; - - if (x <= 12.0) { - for (k = 0; k <= n; k++) { - vk = v0 + k; - bk = 1.0; - r = 1.0; - - for (i = 1; i <= 50; i++) { - r = -0.25 * r * x2 / (i * (i + vk)); - bk = bk + r; - - if (fabs(r) < fabs(bk) * 1.0e-15) - break; - } - vl[k] = bk; - - uk = 1.0; - r = 1.0; - - for (i = 1; i <= 50; i++) { - r = -0.25 * r * x2 / (i * (i + vk + 1.0)); - uk = uk + r; - - if (fabs(r) < fabs(uk) * 1.0e-15) - break; - } - dl[k] = -0.5 * x / (vk + 1.0) * uk; - } - return; - } - - k0 = (x >= 50.0) ? 8 : ((x >= 35.0) ? 10 : 11); - bjv0 = 0.0; - bjv1 = 0.0; - - for (j = 0; j <= 1; j++) { - vv = 4.0 * (j + v0) * (j + v0); - px = 1.0; - rp = 1.0; - for (k = 1; k <= k0; k++) { - rp = -0.78125e-2 * rp * (vv - pow((4.0 * k - 3.0), 2.0)) * (vv - pow((4.0 * k - 1.0), 2.0)) / (k * (2.0 * k - 1.0) * x2); - px += rp; - } - - qx = 1.0; - rp = 1.0; - for (k = 1; k <= k0; k++) { - rp = -0.78125e-2 * rp * (vv - pow((4.0 * k - 1.0), 2.0)) * (vv - pow((4.0 * k + 1.0), 2.0)) / (k * (2.0 * k + 1.0) * x2); - qx += rp; - } - - qx = 0.125 * (vv - 1.0) * qx / x; - xk = x - (0.5 * (j + v0) + 0.25) * pi; - a0 = sqrt(rp2 / x); - ck = cos(xk); - sk = sin(xk); - - if (j == 0) bjv0 = a0 * (px * ck - qx * sk); - if (j == 1) bjv1 = a0 * (px * ck - qx * sk); - } - - if (v0 == 0.0) { - ga = 1.0; - } else { - ga = gam0(v0); - ga *= v0; - } - - fac = pow(2.0 / x, v0) * ga; - vl[0] = bjv0; - dl[0] = -bjv1 + v0 / x * bjv0; - vl[1] = bjv1; - dl[1] = bjv0 - (1.0 + v0) / x * bjv1; - r0 = 2.0 * (1.0 + v0) / x; - - if (n <= 1) { - vl[0] *= fac; - dl[0] = fac * dl[0] - v0 / x * vl[0]; - vl[1] *= fac * r0; - dl[1] = fac * r0 * dl[1] - (1.0 + v0) / x * vl[1]; - return; - } - - if (n >= 2 && n <= (int)(0.9 * x)) { - f0 = bjv0; - f1 = bjv1; - - for (k = 2; k <= n; k++) { - f = 2.0 * (k + v0 - 1.0) / x * f1 - f0; - f0 = f1; - f1 = f; - vl[k] = f; - } - } else if (n >= 2) { - m = msta1(x, 200); - if (m < n) { - n = m; - } else { - m = msta2(x, n, 15); - } - - f = 0.0; - f2 = 0.0; - f1 = 1.0e-100; - - for (k = m; k >= 0; k--) { - f = 2.0 * (v0 + k + 1.0) / x * f1 - f2; - if (k <= n) vl[k] = f; - f2 = f1; - f1 = f; - } - - cs = 0.0; - if (fabs(bjv0) > fabs(bjv1)) { - cs = bjv0 / f; - } else { - cs = bjv1 / f2; - } - - for (k = 0; k <= n; k++) { - vl[k] *= cs; - } - } - - vl[0] *= fac; - for (j = 1; j <= n; j++) { - vl[j] *= fac * r0; - dl[j - 1] = -0.5 * x / (j + v0) * vl[j]; - r0 = 2.0 * (j + v0 + 1) / x * r0; - } - - dl[n] = 2.0 * (v0 + n) * (vl[n - 1] - vl[n]) / x; - *vm = n + v0; - return; -} - - -template -void lpmns(int m, int n, T x, T* pm, T* pd) { - - // ======================================================== - // Purpose: Compute associated Legendre functions Pmn(x) - // and Pmn'(x) for a given order - // Input : x --- Argument of Pmn(x) - // m --- Order of Pmn(x), m = 0,1,2,...,n - // n --- Degree of Pmn(x), n = 0,1,2,...,N - // Output: PM(n) --- Pmn(x) - // PD(n) --- Pmn'(x) - // ======================================================== - - int k; - T coef, x0, pm0, pm1, pm2, pmk; - for (k = 0; k <= n; k++) { - pm[k] = 0.0; - pd[k] = 0.0; - } - - if (fabs(x) == 1.0) { - for (k = 0; k <= n; k++) { - if (m == 0) { - pm[k] = 1.0; - pd[k] = 0.5 * k * (k + 1.0); - if (x < 0.0) { - pm[k] *= ((k % 2) == 0 ? 1 : -1 ); - pd[k] *= (((k + 1) % 2) == 0 ? 1 : -1 ); - } - } else if (m == 1) { - pd[k] = 1e300; - } else if (m == 2) { - pd[k] = -0.25 * (k + 2.0) * (k + 1.0) * k * (k - 1.0); - if (x < 0.0) - pd[k] *= (((k + 1) % 2) == 0 ? 1 : -1 ); - } - } - return; - } - - x0 = fabs(1.0 - x * x); - pm0 = 1.0; - pmk = pm0; - for (k = 1; k <= m; k++) { - pmk = (2.0 * k - 1.0) * sqrt(x0) * pm0; - pm0 = pmk; - } - pm1 = (2.0 * m + 1.0) * x * pm0; - pm[m] = pmk; - pm[m + 1] = pm1; - for (k = m + 2; k <= n; k++) { - pm2 = ((2.0 * k - 1.0) * x * pm1 - (k + m - 1.0) * pmk) / (k - m); - pm[k] = pm2; - pmk = pm1; - pm1 = pm2; - } - - pd[0] = ((1.0 - m) * pm[1] - x * pm[0]) / (x * x - 1.0); - for (k = 1; k <= n; k++) { - pd[k] = (k * x * pm[k] - (k + m) * pm[k - 1]) / (x * x - 1.0); - } - coef = ((m % 2) == 0 ? 1 : -1 ); - for (k = 1; k <= n; k++) { - pm[k] *= coef; - pd[k] *= coef; - } - return; -} - - -inline double lpmv(double x, int m, double v) { - - // ======================================================= - // Purpose: Compute the associated Legendre function - // Pmv(x) with an integer order and an arbitrary - // degree v, using recursion for large degrees - // Input : x --- Argument of Pm(x) ( -1 ≤ x ≤ 1 ) - // m --- Order of Pmv(x) - // v --- Degree of Pmv(x) - // Output: PMV --- Pmv(x) - // Routine called: LPMV0 - // ======================================================= - - int mx, neg_m, nv, j; - double vx, pmv, v0, p0, p1, g1, g2; - if ((x == -1.0) && (v != (int)v)) { - if (m == 0) { - pmv = -1e300; - } else { - pmv = 1e300; - } - return pmv; - } - vx = v; - mx = m; - // DLMF 14.9.5 - if (v < 0) { vx = -vx -1.0; } - neg_m = 0; - if (m < 0) { - if (((vx+m+1) > 0) || (vx != (int)vx)) { - neg_m = 1; - mx = -m; - } else { - // We don't handle cases where DLMF 14.9.3 doesn't help - return NAN; - } - } - nv = (int)vx; - v0 = vx - nv; - if ((nv > 2) && (nv > mx)) { - // Up-recursion on degree, AMS 8.5.3 / DLMF 14.10.3 - p0 = lpmv0(v0+mx, mx, x); - p1 = lpmv0(v0+mx+1, mx, x); - pmv = p1; - for (j = mx+2; j <= nv; j++) { - pmv = ((2*(v0+j)-1)*x*p1 - (v0+j-1+mx)*p0) / (v0+j-mx); - p0 = p1; - p1 = pmv; - } - } else { - pmv = lpmv0(vx, mx, x); - } - if ((neg_m != 0) && (fabs(pmv) < 1.e300)) { - // DLMF 14.9.3 - g1 = gamma2(vx-mx+1); - g2 = gamma2(vx+mx+1); - pmv = pmv*g1/g2 * pow(-1, mx); - } - return pmv; -} - - -inline double lpmv0(double v, int m, double x) { - - // ======================================================= - // Purpose: Compute the associated Legendre function - // Pmv(x) with an integer order and an arbitrary - // nonnegative degree v - // Input : x --- Argument of Pm(x) ( -1 ≤ x ≤ 1 ) - // m --- Order of Pmv(x) - // v --- Degree of Pmv(x) - // Output: PMV --- Pmv(x) - // Routine called: PSI_SPEC for computing Psi function - // ======================================================= - - int j, k, nv; - double c0, v0, vs, pa, pss, pv0, pmv, r, r0, r1, r2, s, s0, s1, s2, qr, rg, xq; - - const double pi = 3.141592653589793; - const double el = 0.5772156649015329; - const double eps = 1e-14; - - nv = (int)v; - v0 = v - nv; - - if (x == -1.0 && v != nv) { - if (m == 0) - return -1.0e+300; - if (m != 0) - return 1.0e+300; - } - - c0 = 1.0; - if (m != 0) { - rg = v * (v + m); - for (j = 1; j <= m - 1; j++) { - rg *= (v * v - j * j); - } - xq = sqrt(1.0 - x*x); - r0 = 1.0; - for (j = 1; j <= m; j++) { - r0 = 0.5*r0*xq/j; - } - c0 = r0*rg; - } - - if (v0 == 0.0) { - // DLMF 14.3.4, 14.7.17, 15.2.4 - pmv = 1.0; - r = 1.0; - for (k = 1; k <= nv - m; k++) { - r = 0.5 * r * (-nv + m + k - 1.0) * (nv + m + k) / (k * (k + m)) * (1.0 + x); - pmv += r; - } - return pow(-1, nv)*c0*pmv; - } else { - if (x >= -0.35) { - // DLMF 14.3.4, 15.2.1 - pmv = 1.0; - r = 1.0; - for (k = 1; k <= 100; k++) { - r = 0.5 * r * (-v + m + k - 1.0) * (v + m + k) / (k * (m + k)) * (1.0 - x); - pmv += r; - if (k > 12 && fabs(r / pmv) < eps) { break; } - } - return pow(-1, m)*c0*pmv; - } else { - // DLMF 14.3.5, 15.8.10 - vs = sin(v * pi) / pi; - pv0 = 0.0; - if (m != 0) { - qr = sqrt((1.0 - x) / (1.0 + x)); - r2 = 1.0; - for (j = 1; j <= m; j++) { - r2 *= qr * j; - } - s0 = 1.0; - r1 = 1.0; - for (k = 1; k <= m - 1; k++) { - r1 = 0.5 * r1 * (-v + k - 1) * (v + k) / (k * (k - m)) * (1.0 + x); - s0 += r1; - } - pv0 = -vs * r2 / m * s0; - } - - pa = 2.0 * (psi_spec(v) + el) + pi / tan(pi * v) + 1.0 / v; - s1 = 0.0; - for (j = 1; j <= m; j++) { - s1 += (j * j + v * v) / (j * (j * j - v * v)); - } - pmv = pa + s1 - 1.0 / (m - v) + log(0.5 * (1.0 + x)); - r = 1.0; - for (k = 1; k <= 100; k++) { - r = 0.5 * r * (-v + m + k - 1.0) * (v + m + k) / (k * (k + m)) * (1.0 + x); - s = 0.0; - for (j = 1; j <= m; j++) - s += ((k + j) * (k + j) + v * v) / ((k + j) * ((k + j) * (k + j) - v * v)); - - s2 = 0.0; - for (j = 1; j <= k; j++) - s2 = s2 + 1.0 / (j * (j * j - v * v)); - - pss = pa + s + 2.0 * v * v * s2 - 1.0 / (m + k - v) + log(0.5 * (1.0 + x)); - r2 = pss * r; - pmv += r2; - if (fabs(r2 / pmv) < eps) { break; } - } - return pv0 + pmv * vs * c0; - } - } -} - - -template -inline void lqmns(int m, int n, T x, T *qm, T *qd) { - - // ======================================================== - // Purpose: Compute associated Legendre functions Qmn(x) - // and Qmn'(x) for a given order - // Input : x --- Argument of Qmn(x) - // m --- Order of Qmn(x), m = 0,1,2,... - // n --- Degree of Qmn(x), n = 0,1,2,... - // Output: QM(n) --- Qmn(x) - // QD(n) --- Qmn'(x) - // ======================================================== - - int l, ls, k, km; - T xq, q0, q00, q10, q01, q11, qf0, qf1, qm0, qm1, qg0, qg1, qh0, qh1,\ - qh2, qmk, q0l, q1l, qf2, val; - - val = 0.0; - if (fabs(x) == 1.0) { val = 1e300; } - for (k = 0; k <= n; k++) { - qm[k] = val; - qd[k] = val; - } - - if (fabs(x) == 1.0) { - return; - } - ls = (fabs(x) > 1.0 ? -1 : 1); - - xq = sqrt(ls*(1.0 - x*x)); - q0 = 0.5 * log(fabs((x + 1.0) / (x - 1.0))); - q00 = q0; - q10 = -1.0 / xq; - q01 = x*q0 - 1.0; - q11 = -ls*xq*(q0 + x / (1.0 - x*x)); - qf0 = q00; - qf1 = q10; - qm0 = 0.0; - qm1 = 0.0; - - for (k = 2; k <= m; k++) { - qm0 = -2.0 * (k-1.0) / xq * x * qf1 - ls * (k-1.0) * (2.0 - k) * qf0; - qf0 = qf1; - qf1 = qm0; - } - - if (m == 0) { - qm0 = q00; - } - if (m == 1) { - qm0 = q10; - } - - qm[0] = qm0; - - if (fabs(x) < 1.0001) { - if ((m == 0) && (n > 0)) { - qf0 = q00; - qf1 = q01; - for (k = 2; k <= n; k++) { - qf2 = ((2.0 * k - 1.0) * x * qf1 - (k - 1.0) * qf0) / k; - qm[k] = qf2; - qf0 = qf1; - qf1 = qf2; - } - } - - qg0 = q01; - qg1 = q11; - for (k = 2; k <= m; k++) { - qm1 = -2.0 * (k - 1.0) / xq * x * qg1 - ls * k * (3.0 - k) * qg0; - qg0 = qg1; - qg1 = qm1; - } - - if (m == 0) { - qm1 = q01; - } - if (m == 1) { - qm1 = q11; - } - - qm[1] = qm1; - - if ((m == 1) && (n > 1)) { - qh0 = q10; - qh1 = q11; - for (k = 2; k <= n; k++) { - qh2 = ((2.0 * k - 1.0) * x * qh1 - k * qh0) / (k - 1.0); - qm[k] = qh2; - qh0 = qh1; - qh1 = qh2; - } - } else if (m >= 2) { - qg0 = q00; - qg1 = q01; - qh0 = q10; - qh1 = q11; - qmk = 0.0; - for (l = 2; l <= n; l++) { - q0l = ((2.0 * l - 1.0) * x * qg1 - (l - 1.0) * qg0) / l; - q1l = ((2.0 * l - 1.0) * x * qh1 - l * qh0) / (l - 1.0); - qf0 = q0l; - qf1 = q1l; - for (k = 2; k <= m; k++) { - qmk = -2.0 * (k - 1.0) / xq * x * qf1 - ls * (k + l - 1.0) * (l + 2.0 - k) * qf0; - qf0 = qf1; - qf1 = qmk; - } - qm[l] = qmk; - qg0 = qg1; - qg1 = q0l; - qh0 = qh1; - qh1 = q1l; - } - } - } else { - if (fabs(x) > 1.1) { - km = 40 + m + n; - } - else { - km = (40 + m + n) * (int)(-1.0 - 1.8 * log(x - 1.0)); - } - qf2 = 0.0; - qf1 = 1.0; - for (k = km; k >= 0; k--) { - qf0 = ((2.0 * k + 3.0) * x * qf1 - (k + 2.0 - m) * qf2) / (k + m + 1.0); - if (k <= n) { - qm[k] = qf0; - } - qf2 = qf1; - qf1 = qf0; - } - for (k = 0; k <= n; k++) { - qm[k] = qm[k] * qm0 / qf0; - } - } - - if (fabs(x) < 1.0) { - for (k = 0; k <= n; k++) { - qm[k] = pow(-1, m) * qm[k]; - } - } - - qd[0] = ((1.0 - m) * qm[1] - x * qm[0]) / (x*x - 1.0); - for (k = 1; k <= n; k++) { - qd[k] = (k * x * qm[k] - (k + m) * qm[k-1]) / (x*x - 1.0); - } - return; -} - - -inline int msta1(double x, int mp) { - - // =================================================== - // Purpose: Determine the starting point for backward - // recurrence such that the magnitude of - // Jn(x) at that point is about 10^(-MP) - // Input : x --- Argument of Jn(x) - // MP --- Value of magnitude - // Output: MSTA1 --- Starting point - // =================================================== - - int it, nn, n0, n1; - double a0, f, f0, f1; - - a0 = fabs(x); - n0 = (int)(1.1*a0) + 1; - f0 = 0.5*log10(6.28*n0) - n0*log10(1.36*a0/n0)- mp; - n1 = n0 + 5; - f1 = 0.5*log10(6.28*n1) - n1*log10(1.36*a0/n1) - mp; - for (it = 1; it <= 20; it++) { - nn = n1 - (n1 - n0) / (1.0 - f0/f1); - f = 0.5*log10(6.28*nn) - nn*log10(1.36*a0/nn) - mp; - if (abs(nn-n1) < 1) { break; } - n0 = n1; - f0 = f1; - n1 = nn; - f1 = f; - } - return nn; -} - - -inline int msta2(double x, int n, int mp) { - - // =================================================== - // Purpose: Determine the starting point for backward - // recurrence such that all Jn(x) has MP - // significant digits - // Input : x --- Argument of Jn(x) - // n --- Order of Jn(x) - // MP --- Significant digit - // Output: MSTA2 --- Starting point - // =================================================== - - int it, n0, n1, nn; - double a0, hmp, ejn, obj, f, f0, f1; - - a0 = fabs(x); - hmp = 0.5*mp; - ejn = 0.5*log10(6.28*n) - n*log10(1.36*a0/n); - if (ejn <= hmp ) { - obj = mp; - n0 = (int)(1.1*a0) + 1; - } else { - obj = hmp + ejn; - n0 = n; - } - f0 = 0.5*log10(6.28*n0) - n0*log10(1.36*a0/n0) - obj; - n1 = n0 + 5; - f1 = 0.5*log10(6.28*n1) - n1*log10(1.36*a0/n1) - obj; - for (it = 1; it <= 20; it++) { - nn = n1 - (n1 - n0) / (1.0 - f0/f1); - f = 0.5*log10(6.28*nn) - nn*log10(1.36*a0/nn) - obj; - if (abs(nn-n1) < 1) { break; } - n0 = n1; - f0 = f1; - n1 = nn; - f1 = f; - } - return nn + 10; -} - - -template -Status mtu0(int kf, int m, T q, T x, T *csf, T *csd) { - - // =============================================================== - // Purpose: Compute Mathieu functions cem(x,q) and sem(x,q) - // and their derivatives ( q ≥ 0 ) - // Input : KF --- Function code - // KF=1 for computing cem(x,q) and cem'(x,q) - // KF=2 for computing sem(x,q) and sem'(x,q) - // m --- Order of Mathieu functions - // q --- Parameter of Mathieu functions - // x --- Argument of Mathieu functions (in degrees) - // Output: CSF --- cem(x,q) or sem(x,q) - // CSD --- cem'x,q) or sem'x,q) - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. The output - // values will be set to nan. - // Status::Other - // An internal check failed. For mtu0, this occurs - // when km (see the code) is too big. km is a - // function of q and m. The output values will be - // set to nan. - // - // Routines called: - // (1) CVA2 for computing the characteristic values - // (2) FCOEF for computing the expansion coefficients - // =============================================================== - - int kd = 0, km = 0, ic, k; - T a, qm, xr; - const T eps = 1.0e-14; - const T rd = 1.74532925199433e-2; - - if (kf == 1 && m == 2 * (int)(m / 2)) { kd = 1; } - if (kf == 1 && m != 2 * (int)(m / 2)) { kd = 2; } - if (kf == 2 && m != 2 * (int)(m / 2)) { kd = 3; } - if (kf == 2 && m == 2 * (int)(m / 2)) { kd = 4; } - - a = cva2(kd, m, q); - - if (q <= 1.0) { - qm = 7.5 + 56.1 * sqrt(q) - 134.7 * q + 90.7 * sqrt(q) * q; - } else { - qm = 17.0 + 3.1 * sqrt(q) - 0.126 * q + 0.0037 * sqrt(q) * q; - } - - km = (int)(qm + 0.5 * m); - - if (km > 251) { - *csf = NAN; - *csd = NAN; - return Status::Other; - } - - auto fg = std::unique_ptr{new (std::nothrow) T[251]()}; - if (fg.get() == nullptr) { - *csf = NAN; - *csd = NAN; - return Status::NoMemory; - } - fcoef(kd, m, q, a, fg.get()); - - ic = (int)(m / 2) + 1; - xr = x * rd; - *csf = 0.0; - for (k = 1; k <= km; k++) { - if (kd == 1) { - *csf += fg[k - 1] * cos((2*k - 2) * xr); - } else if (kd == 2) { - *csf += fg[k - 1] * cos((2*k - 1) * xr); - } else if (kd == 3) { - *csf += fg[k - 1] * sin((2*k - 1) * xr); - } else if (kd == 4) { - *csf += fg[k - 1] * sin(2*k*xr); - } - if ((k >= ic) && (fabs(fg[k]) < fabs(*csf) * eps)) { - break; - } - } - - *csd = 0.0; - for (k = 1; k <= km; k++) { - if (kd == 1) { - *csd -= (2*k - 2) * fg[k - 1] * sin((2*k - 2) * xr); - } else if (kd == 2) { - *csd -= (2*k - 1) * fg[k - 1] * sin((2*k - 1) * xr); - } else if (kd == 3) { - *csd += (2*k - 1) * fg[k - 1] * cos((2*k - 1) * xr); - } else if (kd == 4) { - *csd += 2.0 * k * fg[k - 1] * cos(2*k*xr); - } - if ((k >= ic) && (fabs(fg[k - 1]) < fabs(*csd) * eps)) { - break; - } - } - return Status::OK; -} - - -template -Status mtu12(int kf, int kc, int m, T q, T x, T *f1r, T *d1r, T *f2r, T *d2r) { - - // ============================================================== - // Purpose: Compute modified Mathieu functions of the first and - // second kinds, Mcm(1)(2)(x,q) and Msm(1)(2)(x,q), - // and their derivatives - // Input: KF --- Function code - // KF=1 for computing Mcm(x,q) - // KF=2 for computing Msm(x,q) - // KC --- Function Code - // KC=1 for computing the first kind - // KC=2 for computing the second kind - // or Msm(2)(x,q) and Msm(2)'(x,q) - // KC=3 for computing both the first - // and second kinds - // m --- Order of Mathieu functions - // q --- Parameter of Mathieu functions ( q ≥ 0 ) - // x --- Argument of Mathieu functions - // Output: F1R --- Mcm(1)(x,q) or Msm(1)(x,q) - // D1R --- Derivative of Mcm(1)(x,q) or Msm(1)(x,q) - // F2R --- Mcm(2)(x,q) or Msm(2)(x,q) - // D2R --- Derivative of Mcm(2)(x,q) or Msm(2)(x,q) - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. The output - // values will be set to nan. - // Status::Other - // An internal check failed. For mtu12, this occurs - // when km (see the code) is too big. km is a - // function of q and m. The output values will be - // set to nan. - // Routines called: - // (1) CVA2 for computing the characteristic values - // (2) FCOEF for computing expansion coefficients - // (3) JYNB for computing Jn(x), Yn(x) and their - // derivatives - // ============================================================== - - T eps = 1.0e-14; - T a, qm, c1, c2, u1, u2, w1, w2; - int kd, km, ic, k, nm = 0; - - if ((kf == 1) && (m % 2 == 0)) { kd = 1; } - if ((kf == 1) && (m % 2 != 0)) { kd = 2; } - if ((kf == 2) && (m % 2 != 0)) { kd = 3; } - if ((kf == 2) && (m % 2 == 0)) { kd = 4; } - - a = cva2(kd, m, q); - - if (q <= 1.0) { - qm = 7.5 + 56.1 * sqrt(q) - 134.7 * q + 90.7 * sqrt(q) * q; - } else { - qm = 17.0 + 3.1 * sqrt(q) - 0.126 * q + 0.0037 * sqrt(q) * q; - } - - km = (int)(qm + 0.5 * m); - if (km >= 251) { - *f1r = NAN; - *d1r = NAN; - *f2r = NAN; - *d2r = NAN; - return Status::Other; - } - - // allocate memory after a possible NAN return - auto fg = std::unique_ptr{new (std::nothrow) T[251]()}; - auto bj1 = std::unique_ptr{new (std::nothrow) T[252]()}; - auto dj1 = std::unique_ptr{new (std::nothrow) T[252]()}; - auto bj2 = std::unique_ptr{new (std::nothrow) T[252]()}; - auto dj2 = std::unique_ptr{new (std::nothrow) T[252]()}; - auto by1 = std::unique_ptr{new (std::nothrow) T[252]()}; - auto dy1 = std::unique_ptr{new (std::nothrow) T[252]()}; - auto by2 = std::unique_ptr{new (std::nothrow) T[252]()}; - auto dy2 = std::unique_ptr{new (std::nothrow) T[252]()}; - - if (fg.get() == nullptr || bj1.get() == nullptr || dj1.get() == nullptr - || bj2.get() == nullptr || dj2.get() == nullptr - || by1.get() == nullptr || dy1.get() == nullptr - || by2.get() == nullptr || dy2.get() == nullptr) { - *f1r = NAN; - *d1r = NAN; - *f2r = NAN; - *d2r = NAN; - return Status::NoMemory; - } - - fcoef(kd, m, q, a, fg.get()); - ic = (int)(m / 2) + 1; - if (kd == 4) { ic = m / 2; } - - c1 = exp(-x); - c2 = exp(x); - u1 = sqrt(q) * c1; - u2 = sqrt(q) * c2; - jynb(km+1, u1, &nm, bj1.get(), dj1.get(), by1.get(), dy1.get()); - jynb(km+1, u2, &nm, bj2.get(), dj2.get(), by2.get(), dy2.get()); - w1 = 0.0; - w2 = 0.0; - - if (kc != 2) { - *f1r = 0.0; - for (k = 1; k <= km; k++) { - if (kd == 1) { - *f1r += pow(-1, ic + k) * fg[k - 1] * bj1[k - 1] * bj2[k - 1]; - } else if (kd == 2 || kd == 3) { - *f1r += pow(-1, ic + k) * fg[k - 1] * (bj1[k - 1] * bj2[k] + pow(-1, kd) * bj1[k] * bj2[k - 1]); - } else { - *f1r += pow(-1, ic + k) * fg[k - 1] * (bj1[k - 1] * bj2[k + 1] - bj1[k + 1] * bj2[k - 1]); - } - - if (k >= 5 && fabs(*f1r - w1) < fabs(*f1r) * eps) { break; } - w1 = *f1r; - } - - *f1r /= fg[0]; - - *d1r = 0.0; - for (k = 1; k <= km; k++) { - if (kd == 1) { - *d1r += pow(-1, ic + k) * fg[k - 1] * (c2 * bj1[k - 1] * dj2[k - 1] - c1 * dj1[k - 1] * bj2[k - 1]); - } else if (kd == 2 || kd == 3) { - *d1r += pow(-1, ic + k) * fg[k - 1] * (c2 * (bj1[k - 1] * dj2[k] + pow(-1, kd) * bj1[k] * dj2[k - 1]) - - c1 * (dj1[k - 1] * bj2[k] + pow(-1, kd) * dj1[k] * bj2[k - 1])); - } else { - *d1r += pow(-1, ic + k) * fg[k - 1] * (c2 * (bj1[k - 1] * dj2[k + 1] - bj1[k + 1] * dj2[k - 1]) - - c1 * (dj1[k - 1] * bj2[k + 1] - dj1[k + 1] * bj2[k - 1])); - } - - if (k >= 5 && fabs(*d1r - w2) < fabs(*d1r) * eps) { break; } - w2 = *d1r; - } - *d1r *= sqrt(q) / fg[0]; - if (kc == 1) { - return Status::OK; - } - } - - *f2r = 0.0; - for (k = 1; k <= km; k++) { - if (kd == 1) { - *f2r += pow(-1, ic + k) * fg[k - 1] * bj1[k - 1] * by2[k - 1]; - } else if (kd == 2 || kd == 3) { - *f2r += pow(-1, ic + k) * fg[k - 1] * (bj1[k - 1] * by2[k] + pow(-1, kd) * bj1[k] * by2[k - 1]); - } else { - *f2r += pow(-1, ic + k) * fg[k - 1] * (bj1[k - 1] * by2[k + 1] - bj1[k + 1] * by2[k - 1]); - } - - if (k >= 5 && fabs(*f2r - w1) < fabs(*f2r) * eps) { break; } - w1 = *f2r; - } - *f2r /= fg[0]; - - *d2r = 0.0; - for (k = 1; k <= km; k++) { - if (kd == 1) { - *d2r += pow(-1, ic + k) * fg[k - 1] * (c2 * bj1[k - 1] * dy2[k - 1] - c1 * dj1[k - 1] * by2[k - 1]); - } else if (kd == 2 || kd == 3) { - *d2r += pow(-1, ic + k) * fg[k - 1] * (c2 * (bj1[k - 1] * dy2[k] + pow(-1, kd) * bj1[k] * dy2[k - 1]) - - c1 * (dj1[k - 1] * by2[k] + pow(-1, kd) * dj1[k] * by2[k - 1])); - } else { - *d2r += pow(-1, ic + k) * fg[k - 1] * (c2 * (bj1[k - 1] * dy2[k + 1 ] - bj1[k + 1] * dy2[k - 1]) - - c1 * (dj1[k - 1] * by2[k + 1] - dj1[k + 1] * by2[k - 1])); - } - - if (k >= 5 && fabs(*d2r - w2) < fabs(*d2r) * eps) { break; } - w2 = *d2r; - } - *d2r = *d2r * sqrt(q) / fg[0]; - return Status::OK; -} - - -inline double psi_spec(double x) { - - // ====================================== - // Purpose: Compute Psi function - // Input : x --- Argument of psi(x) - // Output: PS --- psi(x) - // ====================================== - - int k, n; - double ps, s = 0.0, x2, xa = fabs(x); - const double pi = 3.141592653589793; - const double el = 0.5772156649015329; - static const double a[8] = { - -0.8333333333333e-01, - 0.83333333333333333e-02, - -0.39682539682539683e-02, - 0.41666666666666667e-02, - -0.75757575757575758e-02, - 0.21092796092796093e-01, - -0.83333333333333333e-01, - 0.4432598039215686 - }; - - if ((x == (int)x) && (x <= 0.0)) { - return 1e300; - } else if (xa == (int)xa) { - n = (int)xa; - for (k = 1; k < n; k++) { - s += 1.0 / k; - } - ps = -el + s; - } else if ((xa + 0.5) == (int)(xa + 0.5)) { - n = (int)(xa - 0.5); - for (k = 1; k < (n+1); k++) { - s += 1.0 / (2.0*k - 1.0); - } - ps = -el + 2.0*s - 1.386294361119891; /* 2*log(2) */ - } else { - if (xa < 10.0) { - n = (10.0 - (int)xa); - for (k = 0; k < n; k++) { - s += 1.0 / (xa + k); - } - xa += n; - } - x2 = 1.0 / (xa*xa); - ps = log(xa) - 0.5 / xa; - ps += x2*(((((((a[7]*x2+a[6])*x2+a[5])*x2+a[4])*x2+a[3])*x2+a[2])*x2+a[1])*x2+a[0]); - ps -= s; - } - if (x < 0.0) { - ps -= pi*cos(pi*x)/sin(pi*x) + 1.0 / x; - } - return ps; -} - - -template -Status qstar(int m, int n, T c, T ck1, T *ck, T *qs, T *qt) { - - // ========================================================== - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // ========================================================== - - int ip, i, l, k; - T r, s, sk, qs0; - - auto ap = std::unique_ptr{new (std::nothrow) T[200]}; - if (ap.get() == nullptr) { - return Status::NoMemory; - } - ip = ((n - m) == 2 * ((n - m) / 2) ? 0 : 1); - r = 1.0 / pow(ck[0], 2); - ap[0] = r; - - for (i = 1; i <= m; i++) { - s = 0.0; - for (l = 1; l <= i; l++) { - sk = 0.0; - for (k = 0; k <= l; k++) - sk += ck[k] * ck[l - k]; - s += sk * ap[i - l]; - } - ap[i] = -r * s; - } - qs0 = ap[m - 1]; - - for (l = 1; l < m; l++) { - r = 1.0; - for (k = 1; k <= l; ++k) { - r = r * (2.0 * k + ip) * (2.0 * k - 1.0 + ip) / pow(2.0 * k, 2); - } - qs0 += ap[m - l] * r; - } - *qs = pow(-1, ip) * (ck1) * (ck1 * qs0) / c; - *qt = -2.0 / (ck1) * (*qs); - return Status::OK; -} - - -inline double refine(int kd, int m, double q, double a) { - - // ===================================================== - // Purpose: calculate the accurate characteristic value - // by the secant method - // Input : m --- Order of Mathieu functions - // q --- Parameter of Mathieu functions - // A --- Initial characteristic value - // Output: A --- Refineed characteristic value - // Routine called: CVF for computing the value of F for - // characteristic equation - // ======================================================== - - int it, mj; - double x1, x0, x, f, f0, f1; - const double eps = 1e-14; - - mj = 10 + m; - x0 = a; - f0 = cvf(kd, m, q, x0, mj); - x1 = 1.002*a; - f1 = cvf(kd, m, q, x1, mj); - for (it = 1; it <= 100; it++) { - mj += 1; - x = x1 - (x1-x0)/(1.0 - f0/f1); - f = cvf(kd, m, q, x, mj); - if ((fabs(1.0 - x1/x) < eps) || (f == 0.0)) { break; } - x0 = x1; - f0 = f1; - x1 = x; - f1 = f; - } - return x; -} - - -template -inline Status rmn1(int m, int n, T c, T x, int kd, T *df, T *r1f, T *r1d) { - - // ======================================================= - // Purpose: Compute prolate and oblate spheroidal radial - // functions of the first kind for given m, n, - // c and x - // - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // - // Routines called: - // (1) SCKB for computing expansion coefficients c2k - // (2) SPHJ for computing the spherical Bessel - // functions of the first kind - // ======================================================= - - T a0, b0, cx, r, r0, r1, r2, r3, reg, sa0, suc, sud, sum, sw, sw1; - int ip, j, k, l, lg, nm, nm1, nm2, np; - - auto ck = std::unique_ptr{new (std::nothrow) T[200]()}; - auto dj = std::unique_ptr{new (std::nothrow) T[252]()}; - auto sj = std::unique_ptr{new (std::nothrow) T[252]()}; - if (ck.get() == nullptr || dj.get() == nullptr || sj.get() == nullptr) { - return Status::NoMemory; - } - const T eps = 1.0e-14; - - nm1 = (int)((n - m) / 2); - ip = (n - m == 2 * nm1 ? 0 : 1); - nm = 25 + nm1 + (int)c; - reg = (m + nm > 80 ? 1.0e-200 : 1.0); - r0 = reg; - - for (j = 1; j <= 2 * m + ip; ++j) { - r0 *= j; - } - r = r0; - suc = r * df[0]; - sw = 0.0; - - for (k = 2; k <= nm; k++) { - r = r * (m + k - 1.0) * (m + k + ip - 1.5) / (k - 1.0) / (k + ip - 1.5); - suc += r * df[k - 1]; - if ((k > nm1) && (fabs(suc - sw) < fabs(suc) * eps)) { break; } - sw = suc; - } - - if (x == 0.0) { - sckb(m, n, c, df, ck.get()); - - sum = 0.0; - sw1 = 0.0; - for (j = 1; j <= nm; ++j) { - sum += ck[j - 1]; - if (fabs(sum - sw1) < fabs(sum) * eps) { break; } - sw1 = sum; - } - - r1 = 1.0; - for (j = 1; j <= (n + m + ip) / 2; j++) { - r1 = r1 * (j + 0.5 * (n + m + ip)); - } - - r2 = 1.0; - for (j = 1; j <= m; j++) { - r2 *= 2.0 * c * j; - } - - r3 = 1.0; - for (j = 1; j <= (n - m - ip) / 2; j++) { - r3 *= j; - } - sa0 = (2.0 * (m + ip) + 1.0) * r1 / (pow(2.0, n) * pow(c, ip) * r2 * r3); - - if (ip == 0) { - *r1f = sum / (sa0 * suc) * df[0] * reg; - *r1d = 0.0; - } else if (ip == 1) { - *r1f = 0.0; - *r1d = sum / (sa0 * suc) * df[0] * reg; - } - return Status::OK; - } - - cx = c * x; - nm2 = 2 * nm + m; - sphj(cx, nm2, &nm2, sj.get(), dj.get()); - - a0 = pow(1.0 - kd / (x * x), 0.5 * m) / suc; - *r1f = 0.0; - sw = 0.0; - lg = 0; - - for (k = 1; k <= nm; ++k) { - l = 2 * k + m - n - 2 + ip; - lg = (l % 4 == 0 ? 1 : -1); - - if (k == 1) { - r = r0; - } else { - r = r * (m + k - 1.0) * (m + k + ip - 1.5) / (k - 1.0) / (k + ip - 1.5); - } - - np = m + 2 * k - 2 + ip; - *r1f += lg * r * df[k - 1] * sj[np]; - - if ((k > nm1) && (fabs(*r1f - sw) < fabs(*r1f) * eps)) { break; } - sw = *r1f; - } - - *r1f *= a0; - b0 = kd * m / pow(x, 3.0) / (1.0 - kd / (x * x)) * (*r1f); - - sud = 0.0; - sw = 0.0; - - for (k = 1; k <= nm; k++) { - l = 2 * k + m - n - 2 + ip; - lg = (l % 4 == 0 ? 1 : -1); - - if (k == 1) { - r = r0; - } else { - r = r * (m + k - 1.0) * (m + k + ip - 1.5) / (k - 1.0) / (k + ip - 1.5); - } - - np = m + 2 * k - 2 + ip; - sud = sud + lg * r * df[k - 1] * dj[np]; - - if ((k > nm1) && (fabs(sud - sw) < fabs(sud) * eps)) { break; } - sw = sud; - } - *r1d = b0 + a0 * c * sud; - return Status::OK; -} - - -template -inline Status rmn2l(int m, int n, T c, T x, int Kd, T *Df, T *R2f, T *R2d, int *Id) { - - // ======================================================== - // Purpose: Compute prolate and oblate spheroidal radial - // functions of the second kind for given m, n, - // c and a large cx - // - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // Status::Other - // An internal convergence check failed. When - // this happens, *Id is set to 10 on return. - // - // Routine called: - // SPHY for computing the spherical Bessel - // functions of the second kind - // ======================================================== - - - int ip, nm1, nm, nm2, np, j, k, l, lg, id1, id2; - T a0, b0, cx, reg, r0, r, suc, sud, sw, eps1, eps2; - const T eps = 1.0e-14; - - auto sy = std::unique_ptr{new (std::nothrow) T[252]()}; - auto dy = std::unique_ptr{new (std::nothrow) T[252]()}; - if (sy.get() == nullptr || dy.get() == nullptr) { - return Status::NoMemory; - } - - ip = 1; - nm1 = (int)((n - m) / 2); - if (n - m == 2 * nm1) { - ip = 0; - } - nm = 25 + nm1 + (int)c; - reg = 1.0; - if (m + nm > 80) { - reg = 1.0e-200; - } - nm2 = 2 * nm + m; - cx = c * x; - sphy(cx, nm2, &nm2, sy.get(), dy.get()); - r0 = reg; - - for (j = 1; j <= 2 * m + ip; ++j) { - r0 *= j; - } - - r = r0; - suc = r * Df[0]; - sw = 0.0; - - for (k = 2; k <= nm; k++) { - r = r * (m + k - 1.0) * (m + k + ip - 1.5) / (k - 1.0) / (k + ip - 1.5); - suc += r * Df[k - 1]; - if ((k > nm1) && (fabs(suc - sw) < fabs(suc) * eps)) { break; } - sw = suc; - } - - a0 = pow(1.0 - Kd / (x * x), 0.5 * m) / suc; - *R2f = 0.0; - eps1 = 0.0; - np = 0; - - for (k = 1; k <= nm; k++) { - l = 2 * k + m - n - 2 + ip; - lg = (l % 4 == 0 ? 1 : -1); - - if (k == 1) { - r = r0; - } else { - r = r * (m + k - 1.0) * (m + k + ip - 1.5) / (k - 1.0) / (k + ip - 1.5); - } - - np = m + 2 * k - 2 + ip; - *R2f += lg * r * (Df[k - 1] * sy[np]); - eps1 = fabs(*R2f - sw); - if (k > nm1 && eps1 < fabs(*R2f) * eps) { break; } - sw = *R2f; - } - - id1 = (int)(log10(eps1 / fabs(*R2f) + eps)); - *R2f *= a0; - - if (np >= nm2) { - // On page 584 of "Computation of Special functions" by Shanjie Zhang - // and Jian-Ming Jin, there is a comment next to this code that says - // this condition indicates that convergence is not achieved, so we - // return Status::Other in addition to setting *Id = 10. But the - // functions that call rmn2l (namely rswfp and rswfo) will actually - // look at the value of Id to check for convergence; the returned - // Status value is only checked for the occurrence of Status::NoMemory. - *Id = 10; - return Status::Other; - } - - b0 = Kd * m / pow(x, 3) / (1.0 - Kd / (x * x)) * (*R2f); - sud = 0.0; - eps2 = 0.0; - - for (k = 1; k <= nm; k++) { - l = 2 * k + m - n - 2 + ip; - lg = (l % 4 == 0 ? 1 : -1); - - if (k == 1) { - r = r0; - } else { - r = r * (m + k - 1.0) * (m + k + ip - 1.5) / (k - 1.0) / (k + ip - 1.5); - } - - np = m + 2 * k - 2 + ip; - sud += lg * r * (Df[k - 1] * dy[np]); - eps2 = fabs(sud - sw); - if ((k > nm1) && (eps2 < fabs(sud) * eps)) { break; } - sw = sud; - } - - *R2d = b0 + a0 * c * sud; - id2 = (int)log10(eps2 / fabs(sud) + eps); - *Id = (id1 > id2) ? id1 : id2; - return Status::OK; -} - - -template -inline Status rmn2so(int m, int n, T c, T x, T cv, int kd, T *df, T *r2f, T *r2d) { - - // ============================================================= - // Purpose: Compute oblate radial functions of the second kind - // with a small argument, Rmn(-ic,ix) & Rmn'(-ic,ix) - // - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // - // Routines called: - // (1) SCKB for computing the expansion coefficients c2k - // (2) KMN for computing the joining factors - // (3) QSTAR for computing the factor defined in (15.7.3) - // (4) CBK for computing the expansion coefficient - // defined in (15.7.6) - // (5) GMN for computing the function defined in (15.7.4) - // (6) RMN1 for computing the radial function of the first - // kind - // ============================================================= - - int nm, ip, j; - T ck1, ck2, r1f, r1d, qs, qt, sum, sw, gf, gd, h0; - const T eps = 1.0e-14; - const T pi = 3.141592653589793; - - if (fabs(df[0]) <= 1.0e-280) { - *r2f = 1.0e+300; - *r2d = 1.0e+300; - return Status::OK; - } - - auto bk = std::unique_ptr{new (std::nothrow) T[200]()}; - auto ck = std::unique_ptr{new (std::nothrow) T[200]()}; - auto dn = std::unique_ptr{new (std::nothrow) T[200]()}; - if (bk.get() == nullptr || ck.get() == nullptr || dn.get() == nullptr) { - return Status::NoMemory; - } - - nm = 25 + (int)((n - m) / 2 + c); - ip = (n - m) % 2; - sckb(m, n, c, df, ck.get()); - if (kmn(m, n, c, cv, kd, df, dn.get(), &ck1, &ck2) == Status::NoMemory) { - return Status::NoMemory; - } - if (qstar(m, n, c, ck1, ck.get(), &qs, &qt) == Status::NoMemory) { - return Status::NoMemory; - } - if (cbk(m, n, c, cv, qt, ck.get(), bk.get()) == Status::NoMemory) { - return Status::NoMemory; - } - - if (x == 0.0) { - sum = 0.0; - sw = 0.0; - - for (j = 0; j < nm; ++j) { - sum += ck[j]; - if (fabs(sum - sw) < fabs(sum) * eps) { break; } - sw = sum; - } - - if (ip == 0) { - r1f = sum / ck1; - *r2f = -0.5 * pi * qs * r1f; - *r2d = qs * r1f + bk[0]; - } else { - r1d = sum / ck1; - *r2f = bk[0]; - *r2d = -0.5 * pi * qs * r1d; - } - } else { - gmn(m, n, c, x, bk.get(), &gf, &gd); - if (rmn1(m, n, c, x, kd, df, &r1f, &r1d) == Status::NoMemory) { - return Status::NoMemory; - } - h0 = atan(x) - 0.5 * pi; - *r2f = qs * r1f * h0 + gf; - *r2d = qs * (r1d * h0 + r1f / (1.0 + x * x)) + gd; - } - return Status::OK; -} - - -template -Status rmn2sp(int m, int n, T c, T x, T cv, int kd, T *df, T *r2f, T *r2d) { - - // ====================================================== - // Purpose: Compute prolate spheroidal radial function - // of the second kind with a small argument - // - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // - // Routines called: - // (1) LPMNS for computing the associated Legendre - // functions of the first kind - // (2) LQMNS for computing the associated Legendre - // functions of the second kind - // (3) KMN for computing expansion coefficients - // and joining factors - // ====================================================== - - int k, j, j1, j2, l1, ki, nm3; - T ip, nm1, nm, nm2, su0, sw, sd0, su1, sd1, sd2, ga, r1, r2, r3, - sf, gb, spl, gc, sd, r4, spd1, spd2, su2, ck1, ck2, sum, sdm; - - // fortran index start from 0 - auto pm = std::unique_ptr{new (std::nothrow) T[252]}; - auto pd = std::unique_ptr{new (std::nothrow) T[252]}; - auto qm = std::unique_ptr{new (std::nothrow) T[252]}; - auto qd = std::unique_ptr{new (std::nothrow) T[252]}; - // fortran index start from 1 - auto dn = std::unique_ptr{new (std::nothrow) T[201]}; - - if (pm.get() == nullptr || pd.get() == nullptr || qm.get() == nullptr - || qd.get() == nullptr || dn.get() == nullptr) { - return Status::NoMemory; - } - - const T eps = 1.0e-14; - - nm1 = (n - m) / 2; - nm = 25.0 + nm1 + c; - nm2 = 2 * nm + m; - ip = (n - m) % 2; - - if (kmn(m, n, c, cv, kd, df, dn.get(), &ck1, &ck2) == Status::NoMemory) { - return Status::NoMemory; - } - lpmns(m, nm2, x, pm.get(), pd.get()); - lqmns(m, nm2, x, qm.get(), qd.get()); - - su0 = 0.0; - sw = 0.0; - for (k = 1; k <= nm; k++) { - j = 2 * k - 2 + m + ip; - su0 += df[k - 1] * qm[j]; - if ((k > nm1) && (fabs(su0 - sw) < fabs(su0) * eps)) { break; } - sw = su0; - } - - sd0 = 0.0; - for (k = 1; k <= nm; k++) { - j = 2 * k - 2 + m + ip; - sd0 += df[k - 1] * qd[j]; - if (k > nm1 && fabs(sd0 - sw) < fabs(sd0) * eps) { break; } - sw = sd0; - } - - su1 = 0.0; - sd1 = 0.0; - for (k = 1; k <= m; k++) { - j = m - 2 * k + ip; - if (j < 0) { - j = -j - 1; - } - su1 += dn[k - 1] * qm[j]; - sd1 += dn[k - 1] * qd[j]; - } - - ga = pow((x - 1.0) / (x + 1.0), 0.5 * m); - for (k = 1; k <= m; k++) { - j = m - 2 * k + ip; - if (j >= 0) { continue; } - if (j < 0) { j = -j - 1; } - r1 = 1.0; - for (j1 = 0; j1 < j; j1++) { - r1 *= (m + j1); - } - r2 = 1.0; - for (j2 = 1; j2 <= (m - j - 2); j2++) { - r2 *= j2; - } - r3 = 1.0; - sf = 1.0; - for (l1 = 1; l1 <= j; l1++) { - r3 = 0.5 * r3 * (-j + l1 - 1.0) * (j + l1) / ((m + l1) * l1) * (1.0 - x); - sf += r3; - } - if (m - j >= 2) { - gb = (m - j - 1.0) * r2; - } - if (m - j <= 1) { - gb = 1.0; - } - spl = r1 * ga * gb * sf; - su1 += pow(-1, (j + m)) * dn[k-1] * spl; - spd1 = m / (x * x - 1.0) * spl; - gc = 0.5 * j * (j + 1.0) / (m + 1.0); - sd = 1.0; - r4 = 1.0; - for (l1 = 1; l1 <= j - 1; l1++) { - r4 = 0.5 * r4 * (-j + l1) * (j + l1 + 1.0) / ((m + l1 + 1.0) * l1) * (1.0 - x); - sd += r4; - } - spd2 = r1 * ga * gb * gc * sd; - sd1 += pow(-1, (j + m)) * dn[k - 1] * (spd1 + spd2); - } - su2 = 0.0; - ki = (2 * m + 1 + ip) / 2; - ki = std::max(1, ki); - assert((ki-1) >= 0); - nm3 = nm + ki; - for (k = ki; k <= nm3; k++) { - j = 2 * k - 1 - m - ip; - su2 += dn[k - 1] * pm[j]; - if ((j > m) && (fabs(su2 - sw) < fabs(su2) * eps)) { break; } - sw = su2; - } - sd2 = 0.0; - for (k = ki; k < nm3; k++) { - j = 2 * k - 1 - m - ip; - sd2 += dn[k - 1] * pd[j]; - if (j > m && fabs(sd2 - sw) < fabs(sd2) * eps) { break; } - sw = sd2; - } - sum = su0 + su1 + su2; - sdm = sd0 + sd1 + sd2; - *r2f = sum / ck2; - *r2d = sdm / ck2; - return Status::OK; -} - - -template -inline Status rswfp(int m, int n, T c, T x, T cv, int kf, T *r1f, T *r1d, T *r2f, T *r2d) { - - // ============================================================== - // Purpose: Compute prolate spheriodal radial functions of the - // first and second kinds, and their derivatives - // Input : m --- Mode parameter, m = 0,1,2,... - // n --- Mode parameter, n = m,m+1,m+2,... - // c --- Spheroidal parameter - // x --- Argument of radial function ( x > 1.0 ) - // cv --- Characteristic value - // KF --- Function code - // KF=1 for the first kind - // KF=2 for the second kind - // KF=3 for both the first and second kinds - // Output: R1F --- Radial function of the first kind - // R1D --- Derivative of the radial function of - // the first kind - // R2F --- Radial function of the second kind - // R2D --- Derivative of the radial function of - // the second kind - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // - // Routines called: - // (1) SDMN for computing expansion coefficients dk - // (2) RMN1 for computing prolate and oblate radial - // functions of the first kind - // (3) RMN2L for computing prolate and oblate radial - // functions of the second kind for a large argument - // (4) RMN2SP for computing the prolate radial function - // of the second kind for a small argument - // ============================================================== - - auto df = std::unique_ptr{new (std::nothrow) T[200]}; - if (df.get() == nullptr) { - return Status::NoMemory; - } - int id, kd = 1; - - if (sdmn(m, n, c, cv, kd, df.get()) == Status::NoMemory) { - return Status::NoMemory; - } - - if (kf != 2) { - if (rmn1(m, n, c, x, kd, df.get(), r1f, r1d) == Status::NoMemory) { - return Status::NoMemory; - } - } - if (kf > 1) { - if (rmn2l(m, n, c, x, kd, df.get(), r2f, r2d, &id) == Status::NoMemory) { - return Status::NoMemory; - } - if (id > -8) { - if (rmn2sp(m, n, c, x, cv, kd, df.get(), r2f, r2d) == Status::NoMemory) { - return Status::NoMemory; - } - } - } - return Status::OK; -} - - -template -Status rswfo(int m, int n, T c, T x, T cv, int kf, T *r1f, T *r1d, T *r2f, T *r2d) { - - // ========================================================== - // Purpose: Compute oblate radial functions of the first - // and second kinds, and their derivatives - // Input : m --- Mode parameter, m = 0,1,2,... - // n --- Mode parameter, n = m,m+1,m+2,... - // c --- Spheroidal parameter - // x --- Argument (x ≥ 0) - // cv --- Characteristic value - // KF --- Function code - // KF=1 for the first kind - // KF=2 for the second kind - // KF=3 for both the first and second kinds - // Output: R1F --- Radial function of the first kind - // R1D --- Derivative of the radial function of - // the first kind - // R2F --- Radial function of the second kind - // R2D --- Derivative of the radial function of - // the second kind - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // - // Routines called: - // (1) SDMN for computing expansion coefficients dk - // (2) RMN1 for computing prolate or oblate radial - // function of the first kind - // (3) RMN2L for computing prolate or oblate radial - // function of the second kind for a large argument - // (4) RMN2SO for computing oblate radial functions of - // the second kind for a small argument - // ========================================================== - - auto df = std::unique_ptr{new (std::nothrow) T[200]}; - if (df.get() == nullptr) { - return Status::NoMemory; - } - int id, kd = -1; - - if (sdmn(m, n, c, cv, kd, df.get()) == Status::NoMemory) { - return Status::NoMemory; - } - - if (kf != 2) { - if (rmn1(m, n, c, x, kd, df.get(), r1f, r1d) == Status::NoMemory) { - return Status::NoMemory; - } - } - if (kf > 1) { - id = 10; - if (x > 1e-8) { - if (rmn2l(m, n, c, x, kd, df.get(), r2f, r2d, &id) == Status::NoMemory) { - return Status::NoMemory; - } - } - if (id > -1) { - if (rmn2so(m, n, c, x, cv, kd, df.get(), r2f, r2d) == Status::NoMemory) { - return Status::NoMemory; - } - } - } - return Status::OK; -} - - -template -void sckb(int m, int n, T c, T *df, T *ck) { - - // ====================================================== - // Purpose: Compute the expansion coefficients of the - // prolate and oblate spheroidal functions - // Input : m --- Mode parameter - // n --- Mode parameter - // c --- Spheroidal parameter - // DF(k) --- Expansion coefficients dk - // Output: CK(k) --- Expansion coefficients ck; - // CK(1), CK(2), ... correspond to - // c0, c2, ... - // ====================================================== - - int i, ip, i1, i2, k, nm; - T reg, fac, sw, r, d1, d2, d3, sum, r1; - - if (c <= 1.0e-10) { - c = 1.0e-10; - } - nm = 25 + (int)(0.5 * (n - m) + c); - ip = (n - m) % 2; - reg = ((m + nm) > 80 ? 1.0e-200 : 1.0); - fac = -pow(0.5, m); - sw = 0.0; - - for (k = 0; k < nm; k++) { - fac = -fac; - i1 = 2 * k + ip + 1; - r = reg; - - for (i = i1; i <= i1 + 2 * m - 1; i++) { - r *= i; - } - i2 = k + m + ip; - for (i = i2; i <= i2 + k - 1; i++) { - r *= (i + 0.5); - } - sum = r * df[k]; - for (i = k + 1; i <= nm; i++) { - d1 = 2.0 * i + ip; - d2 = 2.0 * m + d1; - d3 = i + m + ip - 0.5; - r = r * d2 * (d2 - 1.0) * i * (d3 + k) / (d1 * (d1 - 1.0) * (i - k) * d3); - sum += r * df[i]; - if (fabs(sw - sum) < fabs(sum) * 1.0e-14) { break; } - sw = sum; - } - r1 = reg; - for (i = 2; i <= m + k; i++) { r1 *= i; } - ck[k] = fac * sum / r1; - } -} - - -template -Status sdmn(int m, int n, T c, T cv, int kd, T *df) { - - // ===================================================== - // Purpose: Compute the expansion coefficients of the - // prolate and oblate spheroidal functions, dk - // Input : m --- Mode parameter - // n --- Mode parameter - // c --- Spheroidal parameter - // cv --- Characteristic value - // KD --- Function code - // KD=1 for prolate; KD=-1 for oblate - // Output: DF(k) --- Expansion coefficients dk; - // DF(1), DF(2), ... correspond to - // d0, d2, ... for even n-m and d1, - // d3, ... for odd n-m - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // ===================================================== - - int nm, ip, k, kb; - T cs, dk0, dk1, dk2, d2k, f, fs, f1, f0, fl, f2, su1,\ - su2, sw, r1, r3, r4, s0; - - nm = 25 + (int)(0.5 * (n - m) + c); - - if (c < 1e-10) { - for (int i = 1; i <= nm; ++i) { - df[i-1] = 0.0; - } - df[(n - m) / 2] = 1.0; - return Status::OK; - } - - auto a = std::unique_ptr{new (std::nothrow) T[nm + 2]()}; - auto d = std::unique_ptr{new (std::nothrow) T[nm + 2]()}; - auto g = std::unique_ptr{new (std::nothrow) T[nm + 2]()}; - if (a.get() == nullptr || d.get() == nullptr || g.get() == nullptr) { - return Status::NoMemory; - } - cs = c*c*kd; - ip = (n - m) % 2; - - for (int i = 1; i <= nm + 2; ++i) { - k = (ip == 0 ? 2 * (i - 1) : 2 * i - 1); - - dk0 = m + k; - dk1 = m + k + 1; - dk2 = 2 * (m + k); - d2k = 2 * m + k; - - a[i - 1] = (d2k + 2.0) * (d2k + 1.0) / ((dk2 + 3.0) * (dk2 + 5.0)) * cs; - d[i - 1] = dk0 * dk1 + (2.0 * dk0 * dk1 - 2.0 * m * m - 1.0) / ((dk2 - 1.0) * (dk2 + 3.0)) * cs; - g[i - 1] = k * (k - 1.0) / ((dk2 - 3.0) * (dk2 - 1.0)) * cs; - } - - fs = 1.0; - f1 = 0.0; - f0 = 1.0e-100; - kb = 0; - df[nm] = 0.0; - fl = 0.0; - - for (int k = nm; k >= 1; k--) { - f = -((d[k] - cv) * f0 + a[k] * f1) / g[k]; - - if (fabs(f) > fabs(df[k])) { - df[k-1] = f; - f1 = f0; - f0 = f; - - if (fabs(f) > 1.0e+100) { - for (int k1 = k; k1 <= nm; k1++) - df[k1 - 1] *= 1.0e-100; - f1 *= 1.0e-100; - f0 *= 1.0e-100; - } - } else { - kb = k; - fl = df[k]; - f1 = 1.0e-100; - f2 = -((d[0] - cv) / a[0]) * f1; - df[0] = f1; - - if (kb == 1) { - fs = f2; - } else if (kb == 2) { - df[1] = f2; - fs = -((d[1] - cv) * f2 + g[1] * f1) / a[1]; - } else { - df[1] = f2; - - for (int j = 3; j <= kb + 1; j++) { - f = -((d[j - 2] - cv) * f2 + g[j - 2] * f1) / a[j - 2]; - if (j <= kb) { - df[j-1] = f; - } - if (fabs(f) > 1.0e+100) { - for (int k1 = 1; k1 <= j; k1++) { - df[k1 - 1] *= 1.0e-100; - } - f *= 1.0e-100; - f2 *= 1.0e-100; - } - f1 = f2; - f2 = f; - } - fs = f; - } - break; - } - } - - su1 = 0.0; - r1 = 1.0; - - for (int j = m + ip + 1; j <= 2 * (m + ip); j++) { - r1 *= j; - } - su1 = df[0] * r1; - - for (int k = 2; k <= kb; k++) { - r1 = -r1 * (k + m + ip - 1.5) / (k - 1.0); - su1 += r1 * df[k - 1]; - } - - su2 = 0.0; - sw = 0.0; - - for (int k = kb + 1; k <= nm; k++) { - if (k != 1) { - r1 = -r1 * (k + m + ip - 1.5) / (k - 1.0); - } - su2 += r1 * df[k - 1]; - - if (fabs(sw - su2) < fabs(su2) * 1.0e-14) { break; } - sw = su2; - } - r3 = 1.0; - - for (int j = 1; j <= (m + n + ip) / 2; j++) { - r3 *= (j + 0.5 * (n + m + ip)); - } - r4 = 1.0; - - for (int j = 1; j <= (n - m - ip) / 2; j++) { - r4 *= -4.0 * j; - } - s0 = r3 / (fl * (su1 / fs) + su2) / r4; - - for (int k = 1; k <= kb; ++k) { - df[k - 1] *= fl / fs * s0; - } - for (int k = kb + 1; k <= nm; ++k) { - df[k - 1] *= s0; - } - return Status::OK; -} - - -template -Status segv(int m, int n, T c, int kd, T *cv, T *eg) { - - // ========================================================= - // Purpose: Compute the characteristic values of spheroidal - // wave functions - // Input : m --- Mode parameter - // n --- Mode parameter - // c --- Spheroidal parameter - // KD --- Function code - // KD=1 for Prolate; KD=-1 for Oblate - // Output: CV --- Characteristic value for given m, n and c - // EG(L) --- Characteristic value for mode m and n' - // ( L = n' - m + 1 ) - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // ========================================================= - - - int i, icm, j, k, k1, l, nm, nm1; - T cs, dk0, dk1, dk2, d2k, s, t, t1, x1, xa, xb; - // eg[<=200] is supplied by the caller - - if (c < 1e-10) { - for (i = 1; i <= (n-m+1); i++) { - eg[i-1] = (i+m) * (i + m -1); - } - *cv = eg[n-m]; - return Status::OK; - } - - // TODO: Following array sizes should be decided dynamically - auto a = std::unique_ptr{new (std::nothrow) T[300]()}; - auto b = std::unique_ptr{new (std::nothrow) T[100]()}; - auto cv0 = std::unique_ptr{new (std::nothrow) T[100]()}; - auto d = std::unique_ptr{new (std::nothrow) T[300]()}; - auto e = std::unique_ptr{new (std::nothrow) T[300]()}; - auto f = std::unique_ptr{new (std::nothrow) T[300]()}; - auto g = std::unique_ptr{new (std::nothrow) T[300]()}; - auto h = std::unique_ptr{new (std::nothrow) T[100]()}; - - if (a.get() == nullptr || b.get() == nullptr || cv0.get() == nullptr - || d.get() == nullptr || e.get() == nullptr || f.get() == nullptr - || g.get() == nullptr || h.get() == nullptr) { - return Status::NoMemory; - } - icm = (n-m+2)/2; - nm = 10 + (int)(0.5*(n-m)+c); - cs = c*c*kd; - k = 0; - for (l = 0; l <= 1; l++) { - for (i = 1; i <= nm; i++) { - k = (l == 0 ? 2*(i - 1) : 2*i - 1); - dk0 = m + k; - dk1 = m + k + 1; - dk2 = 2*(m + k); - d2k = 2*m + k; - a[i-1] = (d2k+2.0)*(d2k+1.0)/((dk2+3.0)*(dk2+5.0))*cs; - d[i-1] = dk0*dk1+(2.0*dk0*dk1-2.0*m*m-1.0)/((dk2-1.0)*(dk2+3.0))*cs; - g[i-1] = k*(k-1.0)/((dk2-3.0)*(dk2-1.0))*cs; - } - for (k = 2; k <= nm; k++) { - e[k-1] = sqrt(a[k-2]*g[k-1]); - f[k-1] = e[k-1]*e[k-1]; - } - f[0] = 0.0; - e[0] = 0.0; - xa = d[nm-1] + fabs(e[nm-1]); - xb = d[nm-1] - fabs(e[nm-1]); - nm1 = nm-1; - for (i = 1; i <= nm1; i++) { - t = fabs(e[i-1])+fabs(e[i]); - t1 = d[i-1] + t; - if (xa < t1) { xa = t1; } - t1 = d[i-1] - t; - if (t1 < xb) { xb = t1; } - } - for (i = 1; i <= icm; i++) { - b[i-1] = xa; - h[i-1] = xb; - } - for (k = 1; k <= icm; k++) { - for (k1 = k; k1 <= icm; k1++) { - if (b[k1-1] < b[k-1]) { - b[k-1] = b[k1-1]; - break; - } - } - if (k != 1) { - if(h[k-1] < h[k-2]) { h[k-1] = h[k-2]; } - } - while (1) { - x1 = (b[k-1]+h[k-1])/2.0; - cv0[k-1] = x1; - if (fabs((b[k-1] - h[k-1])/x1) < 1e-14) { break; } - j = 0; - s = 1.0; - for (i = 1; i <= nm; i++) { - if (s == 0.0) { s += 1e-30; } - t = f[i-1]/s; - s = d[i-1] - t - x1; - if (s < 0.0) { j += 1; } - } - if (j < k) { - h[k-1] = x1; - } else { - b[k-1] = x1; - if (j >= icm) { - b[icm - 1] = x1; - } else { - if (h[j] < x1) { h[j] = x1; } - if (x1 < b[j-1]) { b[j-1] = x1; } - } - } - } - cv0[k-1] = x1; - if (l == 0) eg[2*k-2] = cv0[k-1]; - if (l == 1) eg[2*k-1] = cv0[k-1]; - } - } - *cv = eg[n-m]; - return Status::OK; -} - - -template -void sphj(T x, int n, int *nm, T *sj, T *dj) { - - // MODIFIED to ALLOW N=0 CASE (ALSO IN SPHY) - // - // ======================================================= - // Purpose: Compute spherical Bessel functions jn(x) and - // their derivatives - // Input : x --- Argument of jn(x) - // n --- Order of jn(x) ( n = 0,1,… ) - // Output: SJ(n) --- jn(x) - // DJ(n) --- jn'(x) - // NM --- Highest order computed - // Routines called: - // MSTA1 and MSTA2 for computing the starting - // point for backward recurrence - // ======================================================= - - int k, m; - T cs, f, f0, f1, sa, sb; - - *nm = n; - if (fabs(x) < 1e-100) { - for (k = 0; k <= n; k++) { - sj[k] = 0.0; - dj[k] = 0.0; - } - sj[0] = 1.0; - if (n > 0) { - dj[1] = 1.0 / 3.0; - } - return; - } - sj[0] = sin(x)/x; - dj[0] = (cos(x) - sin(x)/x)/x; - if (n < 1) { - return; - } - sj[1] = (sj[0] - cos(x))/x; - if (n >= 2) { - sa = sj[0]; - sb = sj[1]; - m = msta1(x, 200); - if (m < n) { - *nm = m; - } else { - m = msta2(x, n, 15); - } - f = 0.0; - f0 = 0.0; - f1 = 1e-100; - for (k = m; k >= 0; k--) { - f = (2.0*k + 3.0)*f1/x - f0; - if (k <= *nm) { sj[k] = f; } - f0 = f1; - f1 = f; - } - cs = (fabs(sa) > fabs(sb) ? sa/f : sb/f0); - for (k = 0; k <= *nm; k++) { - sj[k] *= cs; - } - } - for (k = 1; k <= *nm; k++) { - dj[k] = sj[k - 1] - (k + 1.0)*sj[k]/x; - } - return; -} - - -template -inline void sphy(T x, int n, int *nm, T *sy, T *dy) { - - // ====================================================== - // Purpose: Compute spherical Bessel functions yn(x) and - // their derivatives - // Input : x --- Argument of yn(x) ( x ≥ 0 ) - // n --- Order of yn(x) ( n = 0,1,… ) - // Output: SY(n) --- yn(x) - // DY(n) --- yn'(x) - // NM --- Highest order computed - // ====================================================== - - T f, f0, f1; - - if (x < 1.0e-60) { - for (int k = 0; k <= n; ++k) { - sy[k] = -1.0e300; - dy[k] = 1.0e300; - } - *nm = n; - return; - } - sy[0] = -cos(x) / x; - f0 = sy[0]; - dy[0] = (sin(x) + cos(x) / x) / x; - - if (n < 1) { - *nm = n; - return; - } - - sy[1] = (sy[0] - sin(x)) / x; - f1 = sy[1]; - - for (int k = 2; k <= n; k++) { - f = ((2.0 * k - 1.0) * f1 / x) - f0; - sy[k] = f; - if (fabs(f) >= 1.0e300) { - *nm = k - 1; - return; - } - f0 = f1; - f1 = f; - } - *nm = n - 1; - for (int k = 1; k <= *nm; k++) { - dy[k] = sy[k - 1] - (k + 1.0) * sy[k] / x; - } - return; -} - -} -} diff --git a/scipy/special/xsf/sph_bessel.h b/scipy/special/xsf/sph_bessel.h deleted file mode 100644 index 3791de80944d..000000000000 --- a/scipy/special/xsf/sph_bessel.h +++ /dev/null @@ -1,382 +0,0 @@ -/* - -Implementation of spherical Bessel functions and modified spherical Bessel -functions of the first and second kinds, as well as their derivatives. - -Author: Tadeusz Pudlik - -Distributed under the same license as SciPy. - -I attempt to correctly handle the edge cases (0 and infinity), but this is -tricky: the values of the functions often depend on the direction in which -the limit is taken. At zero, I follow the convention of numpy (1.9.2), -which treats zero differently depending on its type: - - >>> np.cos(0)/0 - inf - >>> np.cos(0+0j)/(0+0j) - inf + nan*j - -So, real zero is assumed to be "positive zero", while complex zero has an -unspecified sign and produces nans. Similarly, complex infinity is taken to -represent the "point at infinity", an ambiguity which for some functions -makes `nan` the correct return value. - -Translated to C++ by SciPy developers in 2024. - -*/ - -#pragma once - -#include "amos.h" -#include "error.h" - -namespace xsf { - -template -T sph_bessel_j(long n, T x) { - if (std::isnan(x)) { - return x; - } - - if (n < 0) { - set_error("spherical_jn", SF_ERROR_DOMAIN, nullptr); - return std::numeric_limits::quiet_NaN(); - } - - if ((x == std::numeric_limits::infinity()) || (x == -std::numeric_limits::infinity())) { - return 0; - } - - if (x == 0) { - if (n == 0) { - return 1; - } - - return 0; - } - - if ((n > 0) && (n >= x)) { - return std::sqrt(M_PI_2 / x) * cyl_bessel_j(n + 1 / static_cast(2), x); - } - - T s0 = std::sin(x) / x; - if (n == 0) { - return s0; - } - - T s1 = (s0 - std::cos(x)) / x; - if (n == 1) { - return s1; - } - - T sn; - for (int i = 0; i < n - 1; ++i) { - sn = (2 * i + 3) * s1 / x - s0; - s0 = s1; - s1 = sn; - if (std::isinf(sn)) { - // Overflow occurred already : terminate recurrence. - return sn; - } - } - - return sn; -} - -template -std::complex sph_bessel_j(long n, std::complex z) { - if (std::isnan(std::real(z)) || std::isnan(std::imag(z))) { - return z; - } - - if (n < 0) { - set_error("spherical_jn", SF_ERROR_DOMAIN, nullptr); - return std::numeric_limits::quiet_NaN(); - } - - if (std::real(z) == std::numeric_limits::infinity() || std::real(z) == -std::numeric_limits::infinity()) { - // https://dlmf.nist.gov/10.52.E3 - if (std::imag(z) == 0) { - return 0; - } - - return std::complex(1, 1) * std::numeric_limits::infinity(); - } - - if ((std::real(z) == 0) && (std::imag(z) == 0)) { - if (n == 0) { - return 1; - } - - return 0; - } - - std::complex out = std::sqrt(static_cast(M_PI_2) / z) * cyl_bessel_j(n + 1 / static_cast(2), z); - if (std::imag(z) == 0) { - return std::real(out); // Small imaginary part is spurious - } - - return out; -} - -template -T sph_bessel_j_jac(long n, T z) { - if (n == 0) { - return -sph_bessel_j(1, z); - } - - if (z == static_cast(0)) { - // DLMF 10.51.2 doesn't work, so use 10.51.1 to get the exact value - if (n == 1) { - return static_cast(1) / static_cast(3); - } - - return 0; - } - - // DLMF 10.51.2 - return sph_bessel_j(n - 1, z) - static_cast(n + 1) * sph_bessel_j(n, z) / z; -} - -template -T sph_bessel_y(long n, T x) { - T s0, s1, sn; - int idx; - - if (std::isnan(x)) { - return x; - } - - if (n < 0) { - set_error("spherical_yn", SF_ERROR_DOMAIN, nullptr); - return std::numeric_limits::quiet_NaN(); - } - - if (x < 0) { - return std::pow(-1, n + 1) * sph_bessel_y(n, -x); - } - - if (x == std::numeric_limits::infinity() || x == -std::numeric_limits::infinity()) { - return 0; - } - - if (x == 0) { - return -std::numeric_limits::infinity(); - } - - s0 = -cos(x) / x; - if (n == 0) { - return s0; - } - - s1 = (s0 - sin(x)) / x; - if (n == 1) { - return s1; - } - - for (idx = 0; idx < n - 1; ++idx) { - sn = (2 * idx + 3) * s1 / x - s0; - s0 = s1; - s1 = sn; - if (std::isinf(sn)) { - // Overflow occurred already: terminate recurrence. - return sn; - } - } - - return sn; -} - -inline float sph_bessel_y(long n, float x) { return sph_bessel_y(n, static_cast(x)); } - -template -std::complex sph_bessel_y(long n, std::complex z) { - if (std::isnan(std::real(z)) || std::isnan(std::imag(z))) { - return z; - } - - if (n < 0) { - set_error("spherical_yn", SF_ERROR_DOMAIN, nullptr); - return std::numeric_limits::quiet_NaN(); - } - - if (std::real(z) == 0 && std::imag(z) == 0) { - // https://dlmf.nist.gov/10.52.E2 - return std::numeric_limits::quiet_NaN(); - } - - if (std::real(z) == std::numeric_limits::infinity() || std::real(z) == -std::numeric_limits::infinity()) { - // https://dlmf.nist.gov/10.52.E3 - if (std::imag(z) == 0) { - return 0; - } - - return std::complex(1, 1) * std::numeric_limits::infinity(); - } - - return std::sqrt(static_cast(M_PI_2) / z) * cyl_bessel_y(n + 1 / static_cast(2), z); -} - -template -T sph_bessel_y_jac(long n, T x) { - if (n == 0) { - return -sph_bessel_y(1, x); - } - - return sph_bessel_y(n - 1, x) - static_cast(n + 1) * sph_bessel_y(n, x) / x; -} - -template -T sph_bessel_i(long n, T x) { - if (std::isnan(x)) { - return x; - } - - if (n < 0) { - set_error("spherical_in", SF_ERROR_DOMAIN, nullptr); - return std::numeric_limits::quiet_NaN(); - } - - if (x == 0) { - // https://dlmf.nist.gov/10.52.E1 - if (n == 0) { - return 1; - } - return 0; - } - - if (std::isinf(x)) { - // https://dlmf.nist.gov/10.49.E8 - if (x == -std::numeric_limits::infinity()) { - return std::pow(-1, n) * std::numeric_limits::infinity(); - } - - return std::numeric_limits::infinity(); - } - - return sqrt(static_cast(M_PI_2) / x) * cyl_bessel_i(n + 1 / static_cast(2), x); -} - -template -std::complex sph_bessel_i(long n, std::complex z) { - if (std::isnan(std::real(z)) || std::isnan(std::imag(z))) { - return z; - } - - if (n < 0) { - set_error("spherical_in", SF_ERROR_DOMAIN, nullptr); - return std::numeric_limits::quiet_NaN(); - } - - if (std::abs(z) == 0) { - // https://dlmf.nist.gov/10.52.E1 - if (n == 0) { - return 1; - } - - return 0; - } - - if (std::isinf(std::real(z)) || std::isinf(std::imag(z))) { - // https://dlmf.nist.gov/10.52.E5 - if (std::imag(z) == 0) { - if (std::real(z) == -std::numeric_limits::infinity()) { - return std::pow(-1, n) * std::numeric_limits::infinity(); - } - - return std::numeric_limits::infinity(); - } - - return std::numeric_limits::quiet_NaN(); - } - - return std::sqrt(static_cast(M_PI_2) / z) * cyl_bessel_i(n + 1 / static_cast(2), z); -} - -template -T sph_bessel_i_jac(long n, T z) { - if (n == 0) { - return sph_bessel_i(1, z); - } - - if (z == static_cast(0)) { - if (n == 1) { - return 1./3.; - } - else { - return 0; - } - } - - return sph_bessel_i(n - 1, z) - static_cast(n + 1) * sph_bessel_i(n, z) / z; -} - -template -T sph_bessel_k(long n, T z) { - if (std::isnan(z)) { - return z; - } - - if (n < 0) { - set_error("spherical_kn", SF_ERROR_DOMAIN, nullptr); - return std::numeric_limits::quiet_NaN(); - } - - if (z == 0) { - return std::numeric_limits::infinity(); - } - - if (std::isinf(z)) { - // https://dlmf.nist.gov/10.52.E6 - if (z == std::numeric_limits::infinity()) { - return 0; - } - - return -std::numeric_limits::infinity(); - } - - return std::sqrt(M_PI_2 / z) * cyl_bessel_k(n + 1 / static_cast(2), z); -} - -template -std::complex sph_bessel_k(long n, std::complex z) { - if (std::isnan(std::real(z)) || std::isnan(std::imag(z))) { - return z; - } - - if (n < 0) { - set_error("spherical_kn", SF_ERROR_DOMAIN, nullptr); - return std::numeric_limits::quiet_NaN(); - } - - if (std::abs(z) == 0) { - return std::numeric_limits::quiet_NaN(); - } - - if (std::isinf(std::real(z)) || std::isinf(std::imag(z))) { - // https://dlmf.nist.gov/10.52.E6 - if (std::imag(z) == 0) { - if (std::real(z) == std::numeric_limits::infinity()) { - return 0; - } - - return -std::numeric_limits::infinity(); - } - - return std::numeric_limits::quiet_NaN(); - } - - return std::sqrt(static_cast(M_PI_2) / z) * cyl_bessel_k(n + 1 / static_cast(2), z); -} - -template -T sph_bessel_k_jac(long n, T x) { - if (n == 0) { - return -sph_bessel_k(1, x); - } - - return -sph_bessel_k(n - 1, x) - static_cast(n + 1) * sph_bessel_k(n, x) / x; -} - -} // namespace xsf diff --git a/scipy/special/xsf/sph_harm.h b/scipy/special/xsf/sph_harm.h deleted file mode 100644 index 30bfb2fa08dd..000000000000 --- a/scipy/special/xsf/sph_harm.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include "legendre.h" -#include "numbers.h" - -namespace xsf { -namespace detail { - - template - void sph_harm_y_next(int m, T phi, T p, complex &res) { - res = p * exp(numbers::i_v * T(m) * phi); - } - -} // namespace detail - -template -void sph_harm_y_for_each_n(int n, int m, T theta, T phi, complex &res, Func f) { - T p[2]; - sph_legendre_p_for_each_n(n, m, theta, p, [m, phi, &res, &f](int n, const T(&p)[2]) { - detail::sph_harm_y_next(m, phi, p[1], res); - - f(n, m, res); - }); -} - -template -void sph_harm_y_for_each_n_m(int n, int m, T theta, T phi, complex &res, Func f) { - T p[2]; - sph_legendre_p_for_each_n_m(n, m, theta, p, [phi, &res, &f](int n, int m, const T(&p)[2]) { - detail::sph_harm_y_next(m, phi, p[1], res); - - f(n, m, res); - }); -} - -template -complex sph_harm_y(int n, int m, T theta, T phi) { - complex res_n; - sph_harm_y_for_each_n(n, m, theta, phi, res_n, [](int n, int m, const complex &res_n) {}); - - return res_n; -} - -template -void sph_harm_y_all(T theta, T phi, OutputMat res) { - int n_max = res.extent(0) - 1; - int m_max = (res.extent(1) - 1) / 2; - - complex res_n_m; - sph_harm_y_for_each_n_m(n_max, m_max, theta, phi, res_n_m, [m_max, &res](int n, int m, complex &res_n_m) { - if (m >= 0) { - res(n, m) = res_n_m; - } else { - res(n, m + 2 * m_max + 1) = res_n_m; - } - }); -} - -} // namespace xsf diff --git a/scipy/special/xsf/sphd_wave.h b/scipy/special/xsf/sphd_wave.h deleted file mode 100644 index 2f2c2259768e..000000000000 --- a/scipy/special/xsf/sphd_wave.h +++ /dev/null @@ -1,407 +0,0 @@ -#pragma once - -#include "specfun.h" - -namespace xsf { - -template -T prolate_segv(T m, T n, T c) { - int kd = 1; - int int_m, int_n; - T cv = 0.0, *eg; - - if ((m < 0) || (n < m) || (m != floor(m)) || (n != floor(n)) || ((n - m) > 198)) { - return std::numeric_limits::quiet_NaN(); - } - int_m = (int) m; - int_n = (int) n; - eg = (T *) malloc(sizeof(T) * (n - m + 2)); - if (eg == NULL) { - set_error("pro_cv", SF_ERROR_MEMORY, "memory allocation error"); - return std::numeric_limits::quiet_NaN(); - } - specfun::Status status = specfun::segv(int_m, int_n, c, kd, &cv, eg); - free(eg); - if (status == specfun::Status::NoMemory) { - set_error("pro_cv", SF_ERROR_MEMORY, "memory allocation error"); - return std::numeric_limits::quiet_NaN(); - } - return cv; -} - -template -T oblate_segv(T m, T n, T c) { - int kd = -1; - int int_m, int_n; - T cv = 0.0, *eg; - - if ((m < 0) || (n < m) || (m != floor(m)) || (n != floor(n)) || ((n - m) > 198)) { - return std::numeric_limits::quiet_NaN(); - } - int_m = (int) m; - int_n = (int) n; - eg = (T *) malloc(sizeof(T) * (n - m + 2)); - if (eg == NULL) { - set_error("obl_cv", SF_ERROR_MEMORY, "memory allocation error"); - return std::numeric_limits::quiet_NaN(); - } - specfun::Status status = specfun::segv(int_m, int_n, c, kd, &cv, eg); - free(eg); - if (status == specfun::Status::NoMemory) { - set_error("obl_cv", SF_ERROR_MEMORY, "memory allocation error"); - return std::numeric_limits::quiet_NaN(); - } - return cv; -} - -template -void prolate_aswfa_nocv(T m, T n, T c, T x, T &s1f, T &s1d) { - int kd = 1; - int int_m, int_n; - T cv = 0.0, *eg; - - if ((x >= 1) || (x <= -1) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n)) || ((n - m) > 198)) { - set_error("pro_ang1", SF_ERROR_DOMAIN, NULL); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } - int_m = (int) m; - int_n = (int) n; - eg = (T *) malloc(sizeof(T) * (n - m + 2)); - if (eg == NULL) { - set_error("pro_ang1", SF_ERROR_MEMORY, "memory allocation error"); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } - specfun::Status status = specfun::segv(int_m, int_n, c, kd, &cv, eg); - free(eg); - if (status == specfun::Status::NoMemory) { - set_error("pro_ang1", SF_ERROR_MEMORY, "memory allocation error"); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } - if (specfun::aswfa(x, int_m, int_n, c, kd, cv, &s1f, &s1d) - == specfun::Status::NoMemory) { - set_error("prol_ang1", SF_ERROR_MEMORY, "memory allocation error"); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } -} - -template -void oblate_aswfa_nocv(T m, T n, T c, T x, T &s1f, T &s1d) { - int kd = -1; - int int_m, int_n; - T cv = 0.0, *eg; - - if ((x >= 1) || (x <= -1) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n)) || ((n - m) > 198)) { - set_error("obl_ang1", SF_ERROR_DOMAIN, NULL); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } - int_m = (int) m; - int_n = (int) n; - eg = (T *) malloc(sizeof(T) * (n - m + 2)); - if (eg == NULL) { - set_error("obl_ang1", SF_ERROR_MEMORY, "memory allocation error"); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } - specfun::Status status = specfun::segv(int_m, int_n, c, kd, &cv, eg); - free(eg); - if (status == specfun::Status::NoMemory) { - set_error("obl_ang1", SF_ERROR_MEMORY, "memory allocation error"); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } - if (specfun::aswfa(x, int_m, int_n, c, kd, cv, &s1f, &s1d) - == specfun::Status::NoMemory) { - set_error("obl_ang1", SF_ERROR_MEMORY, "memory allocation error"); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } -} - -template -void prolate_aswfa(T m, T n, T c, T cv, T x, T &s1f, T &s1d) { - if ((x >= 1) || (x <= -1) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n))) { - set_error("pro_ang1_cv", SF_ERROR_DOMAIN, NULL); - s1f = std::numeric_limits::quiet_NaN(); - s1d = std::numeric_limits::quiet_NaN(); - } else { - specfun::Status status = specfun::aswfa(x, static_cast(m), - static_cast(n), c, 1, cv, - &s1f, &s1d); - if (status == specfun::Status::NoMemory) { - set_error("pro_ang1_cv", SF_ERROR_MEMORY, "memory allocation error"); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } - } -} - -template -void oblate_aswfa(T m, T n, T c, T cv, T x, T &s1f, T &s1d) { - if ((x >= 1) || (x <= -1) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n))) { - set_error("obl_ang1_cv", SF_ERROR_DOMAIN, NULL); - s1f = std::numeric_limits::quiet_NaN(); - s1d = std::numeric_limits::quiet_NaN(); - } else { - specfun::Status status = specfun::aswfa(x, static_cast(m), - static_cast(n), c, -1, cv, - &s1f, &s1d); - if (status == specfun::Status::NoMemory) { - set_error("obl_ang1_cv", SF_ERROR_MEMORY, "memory allocation error"); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } - } -} - -template -void prolate_radial1_nocv(T m, T n, T c, T x, T &r1f, T &r1d) { - int kf = 1, kd = 1; - T r2f = 0.0, r2d = 0.0, cv = 0.0, *eg; - int int_m, int_n; - - if ((x <= 1.0) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n)) || ((n - m) > 198)) { - set_error("pro_rad1", SF_ERROR_DOMAIN, NULL); - r1d = std::numeric_limits::quiet_NaN(); - r1f = std::numeric_limits::quiet_NaN(); - return; - } - int_m = (int) m; - int_n = (int) n; - eg = (T *) malloc(sizeof(T) * (n - m + 2)); - if (eg == NULL) { - set_error("pro_rad1", SF_ERROR_MEMORY, "memory allocation error"); - r1d = std::numeric_limits::quiet_NaN(); - r1f = std::numeric_limits::quiet_NaN(); - return; - } - specfun::Status status = specfun::segv(int_m, int_n, c, kd, &cv, eg); - free(eg); - if (status == specfun::Status::NoMemory) { - set_error("pro_rad1", SF_ERROR_MEMORY, "memory allocation error"); - r1d = std::numeric_limits::quiet_NaN(); - r1f = std::numeric_limits::quiet_NaN(); - return; - } - if (specfun::rswfp(int_m, int_n, c, x, cv, kf, &r1f, &r1d, &r2f, &r2d) - == specfun::Status::NoMemory) { - set_error("pro_rad1", SF_ERROR_MEMORY, "memory allocation error"); - r1d = std::numeric_limits::quiet_NaN(); - r1f = std::numeric_limits::quiet_NaN(); - } -} - -template -void prolate_radial2_nocv(T m, T n, T c, T x, T &r2f, T &r2d) { - int kf = 2, kd = 1; - T r1f = 0.0, r1d = 0.0, cv = 0.0, *eg; - int int_m, int_n; - - if ((x <= 1.0) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n)) || ((n - m) > 198)) { - set_error("pro_rad2", SF_ERROR_DOMAIN, NULL); - r2d = std::numeric_limits::quiet_NaN(); - r2f = std::numeric_limits::quiet_NaN(); - return; - } - int_m = (int) m; - int_n = (int) n; - eg = (T *) malloc(sizeof(T) * (n - m + 2)); - if (eg == NULL) { - set_error("pro_rad2", SF_ERROR_MEMORY, "memory allocation error"); - r2d = std::numeric_limits::quiet_NaN(); - r2f = std::numeric_limits::quiet_NaN(); - return; - } - specfun::Status status = specfun::segv(int_m, int_n, c, kd, &cv, eg); - free(eg); - if (status == specfun::Status::NoMemory) { - set_error("pro_rad2", SF_ERROR_MEMORY, "memory allocation error"); - r2d = std::numeric_limits::quiet_NaN(); - r2f = std::numeric_limits::quiet_NaN(); - return; - } - if (specfun::rswfp(int_m, int_n, c, x, cv, kf, &r1f, &r1d, &r2f, &r2d) - == specfun::Status::NoMemory) { - set_error("pro_rad2", SF_ERROR_MEMORY, "memory allocation error"); - r2d = std::numeric_limits::quiet_NaN(); - r2f = std::numeric_limits::quiet_NaN(); - } -} - -template -void prolate_radial1(T m, T n, T c, T cv, T x, T &r1f, T &r1d) { - int kf = 1; - T r2f = 0.0, r2d = 0.0; - int int_m, int_n; - - if ((x <= 1.0) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n))) { - set_error("pro_rad1_cv", SF_ERROR_DOMAIN, NULL); - r1f = std::numeric_limits::quiet_NaN(); - r1d = std::numeric_limits::quiet_NaN(); - } else { - int_m = (int) m; - int_n = (int) n; - if (specfun::rswfp(int_m, int_n, c, x, cv, kf, &r1f, &r1d, &r2f, &r2d) - == specfun::Status::NoMemory) { - set_error("pro_rad1_cv", SF_ERROR_MEMORY, NULL); - r1f = std::numeric_limits::quiet_NaN(); - r1d = std::numeric_limits::quiet_NaN(); - } - } -} - -template -void prolate_radial2(T m, T n, T c, T cv, T x, T &r2f, T &r2d) { - int kf = 2; - T r1f = 0.0, r1d = 0.0; - int int_m, int_n; - - if ((x <= 1.0) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n))) { - set_error("pro_rad2_cv", SF_ERROR_DOMAIN, NULL); - r2f = std::numeric_limits::quiet_NaN(); - r2d = std::numeric_limits::quiet_NaN(); - } else { - int_m = (int) m; - int_n = (int) n; - if (specfun::rswfp(int_m, int_n, c, x, cv, kf, &r1f, &r1d, &r2f, &r2d) - == specfun::Status::NoMemory) { - set_error("pro_rad2_cv", SF_ERROR_MEMORY, NULL); - r2f = std::numeric_limits::quiet_NaN(); - r2d = std::numeric_limits::quiet_NaN(); - } - } -} - -template -void oblate_radial1_nocv(T m, T n, T c, T x, T &r1f, T &r1d) { - int kf = 1, kd = -1; - T r2f = 0.0, r2d = 0.0, cv = 0.0, *eg; - int int_m, int_n; - - if ((x < 0.0) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n)) || ((n - m) > 198)) { - set_error("obl_rad1", SF_ERROR_DOMAIN, NULL); - r1d = std::numeric_limits::quiet_NaN(); - r1f = std::numeric_limits::quiet_NaN(); - return; - } - int_m = (int) m; - int_n = (int) n; - eg = (T *) malloc(sizeof(T) * (n - m + 2)); - if (eg == NULL) { - set_error("obl_rad1", SF_ERROR_MEMORY, "memory allocation error"); - r1d = std::numeric_limits::quiet_NaN(); - r1f = std::numeric_limits::quiet_NaN(); - return; - } - specfun::Status status = specfun::segv(int_m, int_n, c, kd, &cv, eg); - free(eg); - if (status == specfun::Status::NoMemory) { - set_error("obl_rad1", SF_ERROR_MEMORY, "memory allocation error"); - r1d = std::numeric_limits::quiet_NaN(); - r1f = std::numeric_limits::quiet_NaN(); - return; - } - if (specfun::rswfo(int_m, int_n, c, x, cv, kf, &r1f, &r1d, &r2f, &r2d) - == specfun::Status::NoMemory) { - set_error("obl_rad1", SF_ERROR_MEMORY, "memory allocation error"); - r1d = std::numeric_limits::quiet_NaN(); - r1f = std::numeric_limits::quiet_NaN(); - return; - } -} - -template -void oblate_radial2_nocv(T m, T n, T c, T x, T &r2f, T &r2d) { - int kf = 2, kd = -1; - T r1f = 0.0, r1d = 0.0, cv = 0.0, *eg; - int int_m, int_n; - - if ((x < 0.0) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n)) || ((n - m) > 198)) { - set_error("obl_rad2", SF_ERROR_DOMAIN, NULL); - r2d = std::numeric_limits::quiet_NaN(); - r2f = std::numeric_limits::quiet_NaN(); - return; - } - int_m = (int) m; - int_n = (int) n; - eg = (T *) malloc(sizeof(T) * (n - m + 2)); - if (eg == NULL) { - set_error("obl_rad2", SF_ERROR_MEMORY, "memory allocation error"); - r2d = std::numeric_limits::quiet_NaN(); - r2f = std::numeric_limits::quiet_NaN(); - return; - } - specfun::Status status = specfun::segv(int_m, int_n, c, kd, &cv, eg); - free(eg); - if (status == specfun::Status::NoMemory) { - set_error("obl_rad2", SF_ERROR_MEMORY, "memory allocation error"); - r2d = std::numeric_limits::quiet_NaN(); - r2f = std::numeric_limits::quiet_NaN(); - return; - } - if (specfun::rswfo(int_m, int_n, c, x, cv, kf, &r1f, &r1d, &r2f, &r2d) - == specfun::Status::NoMemory){ - set_error("obl_rad2", SF_ERROR_MEMORY, "memory allocation error"); - r2d = std::numeric_limits::quiet_NaN(); - r2f = std::numeric_limits::quiet_NaN(); - return; - } -} - -template -void oblate_radial1(T m, T n, T c, T cv, T x, T &r1f, T &r1d) { - int kf = 1; - T r2f = 0.0, r2d = 0.0; - - if ((x < 0.0) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n))) { - set_error("obl_rad1_cv", SF_ERROR_DOMAIN, NULL); - r1f = std::numeric_limits::quiet_NaN(); - r1d = std::numeric_limits::quiet_NaN(); - } else { - if (specfun::rswfo(static_cast(m), static_cast(n), - c, x, cv, kf, &r1f, &r1d, &r2f, &r2d) - == specfun::Status::NoMemory) { - set_error("obl_rad1_cv", SF_ERROR_MEMORY, "memory allocation error"); - r1d = std::numeric_limits::quiet_NaN(); - r1f = std::numeric_limits::quiet_NaN(); - return; - } - } -} - -template -void oblate_radial2(T m, T n, T c, T cv, T x, T &r2f, T &r2d) { - int kf = 2; - T r1f = 0.0, r1d = 0.0; - - if ((x < 0.0) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n))) { - set_error("obl_rad2_cv", SF_ERROR_DOMAIN, NULL); - r2f = std::numeric_limits::quiet_NaN(); - r2d = std::numeric_limits::quiet_NaN(); - } else { - if (specfun::rswfo(static_cast(m), static_cast(n), - c, x, cv, kf, &r1f, &r1d, &r2f, &r2d) - == specfun::Status::NoMemory) { - set_error("obl_rad2_cv", SF_ERROR_MEMORY, "memory allocation error"); - r2d = std::numeric_limits::quiet_NaN(); - r2f = std::numeric_limits::quiet_NaN(); - return; - } - } -} - -} // namespace xsf diff --git a/scipy/special/xsf/stats.h b/scipy/special/xsf/stats.h deleted file mode 100644 index 267bf3c6d00d..000000000000 --- a/scipy/special/xsf/stats.h +++ /dev/null @@ -1,182 +0,0 @@ -#pragma once - -#include "xsf/cephes/bdtr.h" -#include "xsf/cephes/chdtr.h" -#include "xsf/cephes/fdtr.h" -#include "xsf/cephes/gdtr.h" -#include "xsf/cephes/incbet.h" -#include "xsf/cephes/incbi.h" -#include "xsf/cephes/kolmogorov.h" -#include "xsf/cephes/nbdtr.h" -#include "xsf/cephes/ndtr.h" -#include "xsf/cephes/ndtri.h" -#include "xsf/cephes/owens_t.h" -#include "xsf/cephes/pdtr.h" -#include "xsf/cephes/tukey.h" -#include "xsf/erf.h" - -namespace xsf { - -inline double bdtr(double k, int n, double p) { return cephes::bdtr(k, n, p); } - -inline double bdtri(double k, int n, double y) { return cephes::bdtri(k, n, y); } - -inline double bdtrc(double k, int n, double p) { return cephes::bdtrc(k, n, p); } - -inline double chdtr(double df, double x) { return cephes::chdtr(df, x); } - -inline double chdtrc(double df, double x) { return cephes::chdtrc(df, x); } - -inline double chdtri(double df, double y) { return cephes::chdtri(df, y); } - -inline double fdtr(double a, double b, double x) { return cephes::fdtr(a, b, x); } - -inline double fdtrc(double a, double b, double x) { return cephes::fdtrc(a, b, x); } - -inline double fdtri(double a, double b, double y) { return cephes::fdtri(a, b, y); } - -inline double gdtr(double a, double b, double x) { return cephes::gdtr(a, b, x); } - -inline double gdtrc(double a, double b, double x) { return cephes::gdtrc(a, b, x); } - -inline double kolmogorov(double x) { return cephes::kolmogorov(x); } - -inline double kolmogc(double x) { return cephes::kolmogc(x); } - -inline double kolmogi(double x) { return cephes::kolmogi(x); } - -inline double kolmogci(double x) { return cephes::kolmogci(x); } - -inline double kolmogp(double x) { return cephes::kolmogp(x); } - -inline double ndtr(double x) { return cephes::ndtr(x); } - -inline float ndtr(float x) { return ndtr(static_cast(x)); } - -inline std::complex ndtr(std::complex z) { return 0.5 * erfc(-z * M_SQRT1_2); } - -inline std::complex ndtr(std::complex z) { - return static_cast>(ndtr(static_cast>(z))); -} - -/* - * Log of the CDF of the normal distribution for double x. - * - * Let F(x) be the CDF of the standard normal distribution. - * This implementation of log(F(x)) is based on the identities - * - * F(x) = erfc(-x/√2)/2 - * = 1 - erfc(x/√2)/2 - * - * We use the first formula for x < -1, with erfc(z) replaced - * by erfcx(z)*exp(-z**2) to ensure high precision for large - * negative values when we take the logarithm: - * - * log F(x) = log(erfc(-x/√2)/2) - * = log(erfcx(-x/√2)/2)*exp(-x**2/2)) - * = log(erfcx(-x/√2)/2) - x**2/2 - * - * For x >= -1, we use the second formula for F(x): - * - * log F(x) = log(1 - erfc(x/√2)/2) - * = log1p(-erfc(x/√2)/2) - */ -inline double log_ndtr(double x) { - double t = x * M_SQRT1_2; - if (x < -1.0) { - return log(erfcx(-t) / 2) - t * t; - } else { - return log1p(-erfc(t) / 2); - } -} - -inline float log_ndtr(float x) { return log_ndtr(static_cast(x)); } - -/* - * Log of the normal CDF for complex arguments. - * - * This is equivalent to log(ndtr(z)), but is more robust to overflow at $z\to\infty$. - * This implementation uses $\erfc(z) = \exp(-z^2) w(iz)$ taking special care to select - * the principal branch of the log function log( exp(-z^2) w(i z) ) - */ -inline std::complex log_ndtr(std::complex z) { - if (z.real() > 6) { - // Underflow. Close to the real axis, expand the log in log(1 - ndtr(-z)). - std::complex w = -0.5 * erfc(z * M_SQRT1_2); - if (std::abs(w) < 1e-8) { - return w; - } - } - - z *= -M_SQRT1_2; - double x = std::real(z); - double y = std::imag(z); - - /* Compute the principal branch of $log(exp(-z^2))$, using the fact that - * $log(e^t) = log|e^t| + i Arg(e^t)$, and that if $t = r + is$, then - * $e^t = e^r (\cos(s) + i \sin(s))$. - */ - double mRe_z2 = (y - x) * (x + y); // Re(-z^2), being careful of overflow - double mIm_z2 = -2 * x * y; // Im(-z^2) - - double im = fmod(mIm_z2, 2.0 * M_PI); - if (im > M_PI) { - im -= 2.0 * M_PI; - } - - std::complex val1 = std::complex(mRe_z2, im); - - std::complex val2 = log(xsf::wofz(complex(-y, x))); - std::complex result = val1 + val2 - NPY_LOGE2; - - /* Again, select the principal branch: log(z) = log|z| + i arg(z), thus - * the imaginary part of the result should belong to [-pi, pi]. - */ - im = imag(result); - if (im >= M_PI) { - im -= 2 * M_PI; - } - if (im < -M_PI) { - im += 2 * M_PI; - } - - return {result.real(), im}; -} - -inline std::complex log_ndtr(std::complex z) { - return static_cast>(log_ndtr(static_cast>(z))); -} - -inline double nbdtr(int k, int n, double p) { return cephes::nbdtr(k, n, p); } - -inline double nbdtrc(int k, int n, double p) { return cephes::nbdtrc(k, n, p); } - -inline double nbdtri(int k, int n, double p) { return cephes::nbdtri(k, n, p); } - -inline double ndtri(double x) { return cephes::ndtri(x); } - -inline double owens_t(double h, double a) { return cephes::owens_t(h, a); } - -inline double pdtr(double k, double m) { return cephes::pdtr(k, m); } - -inline double pdtrc(double k, double m) { return cephes::pdtrc(k, m); } - -inline double pdtri(int k, double y) { return cephes::pdtri(k, y); } - -inline double smirnov(int n, double x) { return cephes::smirnov(n, x); } - -inline double smirnovc(int n, double x) { return cephes::smirnovc(n, x); } - -inline double smirnovi(int n, double x) { return cephes::smirnovi(n, x); } - -inline double smirnovci(int n, double x) { return cephes::smirnovci(n, x); } - -inline double smirnovp(int n, double x) { return cephes::smirnovp(n, x); } - -inline double tukeylambdacdf(double x, double lmbda) { return cephes::tukeylambdacdf(x, lmbda); } - -inline float tukeylambdacdf(float x, double lmbda) { - return tukeylambdacdf(static_cast(x), static_cast(lmbda)); -} - -} // namespace xsf diff --git a/scipy/special/xsf/struve.h b/scipy/special/xsf/struve.h deleted file mode 100644 index d9541aa22a35..000000000000 --- a/scipy/special/xsf/struve.h +++ /dev/null @@ -1,228 +0,0 @@ -#pragma once - -#include "cephes/struve.h" - -namespace xsf { -namespace detail { - - inline double itth0(double x) { - - // =========================================================== - // Purpose: Evaluate the integral H0(t)/t with respect to t - // from x to infinity - // Input : x --- Lower limit ( x ≥ 0 ) - // Output: TTH --- Integration of H0(t)/t from x to infinity - // =========================================================== - - int k; - double f0, g0, r, s, t, tth, tty, xt; - const double pi = 3.141592653589793; - s = 1.0; - r = 1.0; - if (x < 24.5) { - for (k = 1; k < 61; k++) { - r = -r * x * x * (2.0 * k - 1.0) / pow(2.0 * k + 1.0, 3); - s += r; - if (fabs(r) < fabs(s) * 1.0e-12) { - break; - } - } - tth = pi / 2.0 - 2.0 / pi * x * s; - } else { - for (k = 1; k < 11; k++) { - r = -r * pow(2.0 * k - 1.0, 3) / ((2.0 * k + 1.0) * x * x); - s += r; - if (fabs(r) < fabs(s) * 1.0e-12) { - break; - } - } - tth = 2.0 / (pi * x) * s; - t = 8.0 / x; - xt = x + 0.25 * pi; - f0 = (((((0.18118e-2 * t - 0.91909e-2) * t + 0.017033) * t - 0.9394e-3) * t - 0.051445) * t - 0.11e-5) * t + - 0.7978846; - g0 = - (((((-0.23731e-2 * t + 0.59842e-2) * t + 0.24437e-2) * t - 0.0233178) * t + 0.595e-4) * t + 0.1620695) * - t; - tty = (f0 * sin(xt) - g0 * cos(xt)) / (sqrt(x) * x); - tth = tth + tty; - } - return tth; - } - - inline double itsh0(double x) { - - // =================================================== - // Purpose: Evaluate the integral of Struve function - // H0(t) with respect to t from 0 and x - // Input : x --- Upper limit ( x ≥ 0 ) - // Output: TH0 --- Integration of H0(t) from 0 and x - // =================================================== - - int k; - double a[25], a0, a1, af, bf, bg, r, rd, s, s0, th0, ty, xp; - const double pi = 3.141592653589793; - const double el = 0.57721566490153; - - r = 1.0; - if (x <= 30.0) { - s = 0.5; - for (k = 1; k < 101; k++) { - rd = 1.0; - if (k == 1) { - rd = 0.5; - } - r = -r * rd * k / (k + 1.0) * pow(x / (2.0 * k + 1.0), 2); - s += r; - if (fabs(r) < fabs(s) * 1.0e-12) { - break; - } - } - th0 = 2.0 / pi * x * x * s; - } else { - s = 1.0; - for (k = 1; k < 13; k++) { - r = -r * k / (k + 1.0) * pow((2.0 * k + 1.0) / x, 2); - s += r; - if (fabs(r) < fabs(s) * 1.0e-12) { - break; - } - } - s0 = s / (pi * x * x) + 2.0 / pi * (log(2.0 * x) + el); - a0 = 1.0; - a1 = 5.0 / 8.0; - a[0] = a1; - for (k = 1; k < 21; k++) { - af = ((1.5 * (k + 0.5) * (k + 5.0 / 6.0) * a1 - 0.5 * (k + 0.5) * (k + 0.5) * (k - 0.5) * a0)) / - (k + 1.0); - a[k] = af; - a0 = a1; - a1 = af; - } - bf = 1.0; - r = 1.0; - for (k = 1; k < 11; k++) { - r = -r / (x * x); - bf += a[2 * k - 1] * r; - } - bg = a[0] * x; - r = 1.0 / x; - for (k = 1; k < 10; k++) { - r = -r / (x * x); - bg += a[2 * k] * r; - } - xp = x + 0.25 * pi; - ty = sqrt(2.0 / (pi * x)) * (bg * cos(xp) - bf * sin(xp)); - th0 = ty + s0; - } - return th0; - } - - inline double itsl0(double x) { - - // =========================================================== - // Purpose: Evaluate the integral of modified Struve function - // L0(t) with respect to t from 0 to x - // Input : x --- Upper limit ( x ≥ 0 ) - // Output: TL0 --- Integration of L0(t) from 0 to x - // =========================================================== - - int k; - double a[18], a0, a1, af, r, rd, s, s0, ti, tl0; - const double pi = 3.141592653589793; - const double el = 0.57721566490153; - r = 1.0; - if (x <= 20.0) { - s = 0.5; - for (k = 1; k < 101; k++) { - rd = 1.0; - if (k == 1) { - rd = 0.5; - } - r = r * rd * k / (k + 1.0) * pow(x / (2.0 * k + 1.0), 2); - s += r; - if (fabs(r / s) < 1.0e-12) { - break; - } - } - tl0 = 2.0 / pi * x * x * s; - } else { - s = 1.0; - for (k = 1; k < 11; k++) { - r = r * k / (k + 1.0) * pow((2.0 * k + 1.0) / x, 2); - s += r; - if (fabs(r / s) < 1.0e-12) { - break; - } - } - s0 = -s / (pi * x * x) + 2.0 / pi * (log(2.0 * x) + el); - a0 = 1.0; - a1 = 5.0 / 8.0; - a[0] = a1; - for (k = 1; k < 11; k++) { - af = ((1.5 * (k + .50) * (k + 5.0 / 6.0) * a1 - 0.5 * pow(k + 0.5, 2) * (k - 0.5) * a0)) / (k + 1.0); - a[k] = af; - a0 = a1; - a1 = af; - } - ti = 1.0; - r = 1.0; - for (k = 1; k < 11; k++) { - r = r / x; - ti += a[k - 1] * r; - } - tl0 = ti / sqrt(2 * pi * x) * exp(x) + s0; - } - return tl0; - } - -} // namespace detail - -template -T itstruve0(T x) { - if (x < 0) { - x = -x; - } - - T out = detail::itsh0(x); - SPECFUN_CONVINF("itstruve0", out); - return out; -} - -template -T it2struve0(T x) { - int flag = 0; - - if (x < 0) { - x = -x; - flag = 1; - } - - T out = detail::itth0(x); - SPECFUN_CONVINF("it2struve0", out); - if (flag) { - out = M_PI - out; - } - return out; -} - -template -T itmodstruve0(T x) { - if (x < 0) { - x = -x; - } - - T out = detail::itsl0(x); - SPECFUN_CONVINF("itmodstruve0", out); - return out; -} - -double struve_h(double v, double z) { return cephes::struve_h(v, z); } - -float struve_h(float v, float z) { return struve_h(static_cast(v), static_cast(z)); } - -double struve_l(double v, double z) { return cephes::struve_l(v, z); } - -float struve_l(float v, float z) { return struve_l(static_cast(v), static_cast(z)); } - -} // namespace xsf diff --git a/scipy/special/xsf/third_party/kokkos/mdspan.hpp b/scipy/special/xsf/third_party/kokkos/mdspan.hpp deleted file mode 100644 index ecfa332cf010..000000000000 --- a/scipy/special/xsf/third_party/kokkos/mdspan.hpp +++ /dev/null @@ -1,5674 +0,0 @@ -#ifndef _MDSPAN_SINGLE_HEADER_INCLUDE_GUARD_ -#define _MDSPAN_SINGLE_HEADER_INCLUDE_GUARD_ - -//BEGIN_FILE_INCLUDE: mdspan/include/mdspan/mdspan.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - -#ifndef MDSPAN_HPP_ -#define MDSPAN_HPP_ - -#ifndef MDSPAN_IMPL_STANDARD_NAMESPACE - #define MDSPAN_IMPL_STANDARD_NAMESPACE std -#endif - -#ifndef MDSPAN_IMPL_PROPOSED_NAMESPACE - #define MDSPAN_IMPL_PROPOSED_NAMESPACE experimental -#endif - -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/default_accessor.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/macros.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/config.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - -#ifndef __has_include -# define __has_include(x) 0 -#endif - -#if __has_include() -# include -#else -# include -# include -#endif - -#ifdef _MSVC_LANG -#define _MDSPAN_CPLUSPLUS _MSVC_LANG -#else -#define _MDSPAN_CPLUSPLUS __cplusplus -#endif - -#define MDSPAN_CXX_STD_14 201402L -#define MDSPAN_CXX_STD_17 201703L -#define MDSPAN_CXX_STD_20 202002L -// Note GCC has not updated this in version 13 -#ifdef __clang__ -#define MDSPAN_CXX_STD_23 202302L -#else -#define MDSPAN_CXX_STD_23 202100L -#endif - -#define MDSPAN_HAS_CXX_14 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_14) -#define MDSPAN_HAS_CXX_17 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_17) -#define MDSPAN_HAS_CXX_20 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_20) -#define MDSPAN_HAS_CXX_23 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_23) - -static_assert(_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_14, "mdspan requires C++14 or later."); - -#ifndef _MDSPAN_COMPILER_CLANG -# if defined(__clang__) -# define _MDSPAN_COMPILER_CLANG __clang__ -# endif -#endif - -#if !defined(_MDSPAN_COMPILER_MSVC) && !defined(_MDSPAN_COMPILER_MSVC_CLANG) -# if defined(_MSC_VER) -# if !defined(_MDSPAN_COMPILER_CLANG) -# define _MDSPAN_COMPILER_MSVC _MSC_VER -# else -# define _MDSPAN_COMPILER_MSVC_CLANG _MSC_VER -# endif -# endif -#endif - -#ifndef _MDSPAN_COMPILER_INTEL -# ifdef __INTEL_COMPILER -# define _MDSPAN_COMPILER_INTEL __INTEL_COMPILER -# endif -#endif - -#ifndef _MDSPAN_COMPILER_APPLECLANG -# ifdef __apple_build_version__ -# define _MDSPAN_COMPILER_APPLECLANG __apple_build_version__ -# endif -#endif - -#ifndef _MDSPAN_HAS_CUDA -# if defined(__CUDACC__) -# define _MDSPAN_HAS_CUDA __CUDACC__ -# endif -#endif - -#ifndef _MDSPAN_HAS_HIP -# if defined(__HIPCC__) -# define _MDSPAN_HAS_HIP __HIPCC__ -# endif -#endif - -#ifndef _MDSPAN_HAS_SYCL -# if defined(SYCL_LANGUAGE_VERSION) -# define _MDSPAN_HAS_SYCL SYCL_LANGUAGE_VERSION -# endif -#endif - -#ifndef __has_cpp_attribute -# define __has_cpp_attribute(x) 0 -#endif - -#ifndef _MDSPAN_PRESERVE_STANDARD_LAYOUT -// Preserve standard layout by default, but we're not removing the old version -// that turns this off until we're sure this doesn't have an unreasonable cost -// to the compiler or optimizer. -# define _MDSPAN_PRESERVE_STANDARD_LAYOUT 1 -#endif - -#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) -# if ((__has_cpp_attribute(no_unique_address) >= 201803L) && \ - (!defined(__NVCC__) || MDSPAN_HAS_CXX_20) && \ - (!defined(_MDSPAN_COMPILER_MSVC) || MDSPAN_HAS_CXX_20)) -# define _MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS 1 -# define _MDSPAN_NO_UNIQUE_ADDRESS [[no_unique_address]] -# else -# define _MDSPAN_NO_UNIQUE_ADDRESS -# endif -#endif - -// NVCC older than 11.6 chokes on the no-unique-address-emulation -// so just pretend to use it (to avoid the full blown EBO workaround -// which NVCC also doesn't like ...), and leave the macro empty -#ifndef _MDSPAN_NO_UNIQUE_ADDRESS -# if defined(__NVCC__) -# define _MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS 1 -# define _MDSPAN_USE_FAKE_ATTRIBUTE_NO_UNIQUE_ADDRESS -# endif -# define _MDSPAN_NO_UNIQUE_ADDRESS -#endif - -// AMDs HIP compiler seems to have issues with concepts -// it pretends concepts exist, but doesn't ship -#ifndef __HIPCC__ -#ifndef _MDSPAN_USE_CONCEPTS -# if defined(__cpp_concepts) && __cpp_concepts >= 201507L -# define _MDSPAN_USE_CONCEPTS 1 -# endif -#endif -#endif - -#ifndef _MDSPAN_USE_FOLD_EXPRESSIONS -# if (defined(__cpp_fold_expressions) && __cpp_fold_expressions >= 201603L) \ - || (!defined(__cpp_fold_expressions) && MDSPAN_HAS_CXX_17) -# define _MDSPAN_USE_FOLD_EXPRESSIONS 1 -# endif -#endif - -#ifndef _MDSPAN_USE_INLINE_VARIABLES -# if defined(__cpp_inline_variables) && __cpp_inline_variables >= 201606L \ - || (!defined(__cpp_inline_variables) && MDSPAN_HAS_CXX_17) -# define _MDSPAN_USE_INLINE_VARIABLES 1 -# endif -#endif - -#ifndef _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS -# if (!(defined(__cpp_lib_type_trait_variable_templates) && __cpp_lib_type_trait_variable_templates >= 201510L) \ - || !MDSPAN_HAS_CXX_17) -# if !(defined(_MDSPAN_COMPILER_APPLECLANG) && MDSPAN_HAS_CXX_17) -# define _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS 1 -# endif -# endif -#endif - -#ifndef _MDSPAN_USE_VARIABLE_TEMPLATES -# if (defined(__cpp_variable_templates) && __cpp_variable_templates >= 201304 && MDSPAN_HAS_CXX_17) \ - || (!defined(__cpp_variable_templates) && MDSPAN_HAS_CXX_17) -# define _MDSPAN_USE_VARIABLE_TEMPLATES 1 -# endif -#endif // _MDSPAN_USE_VARIABLE_TEMPLATES - -#ifndef _MDSPAN_USE_CONSTEXPR_14 -# if (defined(__cpp_constexpr) && __cpp_constexpr >= 201304) \ - || (!defined(__cpp_constexpr) && MDSPAN_HAS_CXX_14) \ - && (!(defined(__INTEL_COMPILER) && __INTEL_COMPILER <= 1700)) -# define _MDSPAN_USE_CONSTEXPR_14 1 -# endif -#endif - -#ifndef _MDSPAN_USE_INTEGER_SEQUENCE -# if defined(_MDSPAN_COMPILER_MSVC) -# if (defined(__cpp_lib_integer_sequence) && __cpp_lib_integer_sequence >= 201304) -# define _MDSPAN_USE_INTEGER_SEQUENCE 1 -# endif -# endif -#endif -#ifndef _MDSPAN_USE_INTEGER_SEQUENCE -# if (defined(__cpp_lib_integer_sequence) && __cpp_lib_integer_sequence >= 201304) \ - || (!defined(__cpp_lib_integer_sequence) && MDSPAN_HAS_CXX_14) \ - /* as far as I can tell, libc++ seems to think this is a C++11 feature... */ \ - || (defined(__GLIBCXX__) && __GLIBCXX__ > 20150422 && __GNUC__ < 5 && !defined(__INTEL_CXX11_MODE__)) - // several compilers lie about integer_sequence working properly unless the C++14 standard is used -# define _MDSPAN_USE_INTEGER_SEQUENCE 1 -# elif defined(_MDSPAN_COMPILER_APPLECLANG) && MDSPAN_HAS_CXX_14 - // appleclang seems to be missing the __cpp_lib_... macros, but doesn't seem to lie about C++14 making - // integer_sequence work -# define _MDSPAN_USE_INTEGER_SEQUENCE 1 -# endif -#endif - -#ifndef _MDSPAN_USE_RETURN_TYPE_DEDUCTION -# if (defined(__cpp_return_type_deduction) && __cpp_return_type_deduction >= 201304) \ - || (!defined(__cpp_return_type_deduction) && MDSPAN_HAS_CXX_14) -# define _MDSPAN_USE_RETURN_TYPE_DEDUCTION 1 -# endif -#endif - -#ifndef _MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION -# if (!defined(__NVCC__) || (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__ * 10 >= 1170)) && \ - ((defined(__cpp_deduction_guides) && __cpp_deduction_guides >= 201703) || \ - (!defined(__cpp_deduction_guides) && MDSPAN_HAS_CXX_17)) -# define _MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION 1 -# endif -#endif - -#ifndef _MDSPAN_USE_STANDARD_TRAIT_ALIASES -# if (defined(__cpp_lib_transformation_trait_aliases) && __cpp_lib_transformation_trait_aliases >= 201304) \ - || (!defined(__cpp_lib_transformation_trait_aliases) && MDSPAN_HAS_CXX_14) -# define _MDSPAN_USE_STANDARD_TRAIT_ALIASES 1 -# elif defined(_MDSPAN_COMPILER_APPLECLANG) && MDSPAN_HAS_CXX_14 - // appleclang seems to be missing the __cpp_lib_... macros, but doesn't seem to lie about C++14 -# define _MDSPAN_USE_STANDARD_TRAIT_ALIASES 1 -# endif -#endif - -#ifndef _MDSPAN_DEFAULTED_CONSTRUCTORS_INHERITANCE_WORKAROUND -# ifdef __GNUC__ -# if __GNUC__ < 9 -# define _MDSPAN_DEFAULTED_CONSTRUCTORS_INHERITANCE_WORKAROUND 1 -# endif -# endif -#endif - -#ifndef MDSPAN_CONDITIONAL_EXPLICIT -# if MDSPAN_HAS_CXX_20 -# define MDSPAN_CONDITIONAL_EXPLICIT(COND) explicit(COND) -# else -# define MDSPAN_CONDITIONAL_EXPLICIT(COND) -# endif -#endif - -#ifndef MDSPAN_USE_BRACKET_OPERATOR -# if defined(__cpp_multidimensional_subscript) -# define MDSPAN_USE_BRACKET_OPERATOR 1 -# else -# define MDSPAN_USE_BRACKET_OPERATOR 0 -# endif -#endif - -#ifndef MDSPAN_USE_PAREN_OPERATOR -# if !MDSPAN_USE_BRACKET_OPERATOR -# define MDSPAN_USE_PAREN_OPERATOR 1 -# else -# define MDSPAN_USE_PAREN_OPERATOR 0 -# endif -#endif - -#if MDSPAN_USE_BRACKET_OPERATOR -# define __MDSPAN_OP(mds,...) mds[__VA_ARGS__] -// Corentins demo compiler for subscript chokes on empty [] call, -// though I believe the proposal supports it? -#ifdef MDSPAN_NO_EMPTY_BRACKET_OPERATOR -# define __MDSPAN_OP0(mds) mds.accessor().access(mds.data_handle(),0) -#else -# define __MDSPAN_OP0(mds) mds[] -#endif -# define __MDSPAN_OP1(mds, a) mds[a] -# define __MDSPAN_OP2(mds, a, b) mds[a,b] -# define __MDSPAN_OP3(mds, a, b, c) mds[a,b,c] -# define __MDSPAN_OP4(mds, a, b, c, d) mds[a,b,c,d] -# define __MDSPAN_OP5(mds, a, b, c, d, e) mds[a,b,c,d,e] -# define __MDSPAN_OP6(mds, a, b, c, d, e, f) mds[a,b,c,d,e,f] -#else -# define __MDSPAN_OP(mds,...) mds(__VA_ARGS__) -# define __MDSPAN_OP0(mds) mds() -# define __MDSPAN_OP1(mds, a) mds(a) -# define __MDSPAN_OP2(mds, a, b) mds(a,b) -# define __MDSPAN_OP3(mds, a, b, c) mds(a,b,c) -# define __MDSPAN_OP4(mds, a, b, c, d) mds(a,b,c,d) -# define __MDSPAN_OP5(mds, a, b, c, d, e) mds(a,b,c,d,e) -# define __MDSPAN_OP6(mds, a, b, c, d, e, f) mds(a,b,c,d,e,f) -#endif -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/config.hpp - -#include -#include -#include // std::is_void -#if defined(_MDSPAN_HAS_CUDA) || defined(_MDSPAN_HAS_HIP) || defined(_MDSPAN_HAS_SYCL) -#include "assert.h" -#endif - -#ifndef _MDSPAN_HOST_DEVICE -# if defined(_MDSPAN_HAS_CUDA) || defined(_MDSPAN_HAS_HIP) -# define _MDSPAN_HOST_DEVICE __host__ __device__ -# else -# define _MDSPAN_HOST_DEVICE -# endif -#endif - -#ifndef MDSPAN_FORCE_INLINE_FUNCTION -# ifdef _MDSPAN_COMPILER_MSVC // Microsoft compilers -# define MDSPAN_FORCE_INLINE_FUNCTION __forceinline _MDSPAN_HOST_DEVICE -# else -# define MDSPAN_FORCE_INLINE_FUNCTION __attribute__((always_inline)) _MDSPAN_HOST_DEVICE -# endif -#endif - -#ifndef MDSPAN_INLINE_FUNCTION -# define MDSPAN_INLINE_FUNCTION inline _MDSPAN_HOST_DEVICE -#endif - -#ifndef MDSPAN_FUNCTION -# define MDSPAN_FUNCTION _MDSPAN_HOST_DEVICE -#endif - -#ifdef _MDSPAN_HAS_HIP -# define MDSPAN_DEDUCTION_GUIDE _MDSPAN_HOST_DEVICE -#else -# define MDSPAN_DEDUCTION_GUIDE -#endif - -// In CUDA defaulted functions do not need host device markup -#ifndef MDSPAN_INLINE_FUNCTION_DEFAULTED -# define MDSPAN_INLINE_FUNCTION_DEFAULTED -#endif - -//============================================================================== -// {{{1 - -#define MDSPAN_PP_COUNT(...) \ - _MDSPAN_PP_INTERNAL_EXPAND_ARGS_PRIVATE( \ - _MDSPAN_PP_INTERNAL_ARGS_AUGMENTER(__VA_ARGS__) \ - ) - -#define _MDSPAN_PP_INTERNAL_ARGS_AUGMENTER(...) unused, __VA_ARGS__ -#define _MDSPAN_PP_INTERNAL_EXPAND(x) x -#define _MDSPAN_PP_INTERNAL_EXPAND_ARGS_PRIVATE(...) \ - _MDSPAN_PP_INTERNAL_EXPAND( \ - _MDSPAN_PP_INTERNAL_COUNT_PRIVATE( \ - __VA_ARGS__, 69, 68, 67, 66, 65, 64, 63, 62, 61, \ - 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, \ - 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, \ - 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, \ - 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, \ - 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 \ - ) \ - ) -# define _MDSPAN_PP_INTERNAL_COUNT_PRIVATE( \ - _1_, _2_, _3_, _4_, _5_, _6_, _7_, _8_, _9_, \ - _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ - _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ - _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, \ - _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, \ - _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, \ - _60, _61, _62, _63, _64, _65, _66, _67, _68, _69, \ - _70, count, ...) count \ - /**/ - -#define MDSPAN_PP_STRINGIFY_IMPL(x) #x -#define MDSPAN_PP_STRINGIFY(x) MDSPAN_PP_STRINGIFY_IMPL(x) - -#define MDSPAN_PP_CAT_IMPL(x, y) x ## y -#define MDSPAN_PP_CAT(x, y) MDSPAN_PP_CAT_IMPL(x, y) - -#define MDSPAN_PP_EVAL(X, ...) X(__VA_ARGS__) - -#define MDSPAN_PP_REMOVE_PARENS_IMPL(...) __VA_ARGS__ -#define MDSPAN_PP_REMOVE_PARENS(...) MDSPAN_PP_REMOVE_PARENS_IMPL __VA_ARGS__ - -#define MDSPAN_IMPL_STANDARD_NAMESPACE_STRING MDSPAN_PP_STRINGIFY(MDSPAN_IMPL_STANDARD_NAMESPACE) -#define MDSPAN_IMPL_PROPOSED_NAMESPACE_STRING MDSPAN_PP_STRINGIFY(MDSPAN_IMPL_STANDARD_NAMESPACE) "::" MDSPAN_PP_STRINGIFY(MDSPAN_IMPL_PROPOSED_NAMESPACE) - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace detail { - -#if defined(_MDSPAN_HAS_CUDA) || defined(_MDSPAN_HAS_HIP) -MDSPAN_FUNCTION inline void default_precondition_violation_handler(const char* cond, const char* file, unsigned line) -{ - printf("%s:%u: precondition failure: `%s`\n", file, line, cond); - assert(0); -} -#elif defined(_MDSPAN_HAS_SYCL) -MDSPAN_FUNCTION inline void default_precondition_violation_handler(const char* cond, const char* file, unsigned line) -{ - sycl::ext::oneapi::experimental::printf("%s:%u: precondition failure: `%s`\n", file, line, cond); - assert(0); -} -#else -MDSPAN_FUNCTION inline void default_precondition_violation_handler(const char* cond, const char* file, unsigned line) -{ - std::fprintf(stderr, "%s:%u: precondition failure: `%s`\n", file, line, cond); - std::abort(); -} -#endif - -} // namespace detail -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -#ifndef MDSPAN_IMPL_PRECONDITION_VIOLATION_HANDLER -#define MDSPAN_IMPL_PRECONDITION_VIOLATION_HANDLER(cond, file, line) \ - MDSPAN_IMPL_STANDARD_NAMESPACE::detail::default_precondition_violation_handler(cond, file, line) -#endif - -#ifndef MDSPAN_IMPL_CHECK_PRECONDITION - #ifndef NDEBUG - #define MDSPAN_IMPL_CHECK_PRECONDITION 0 - #else - #define MDSPAN_IMPL_CHECK_PRECONDITION 1 - #endif -#endif - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace detail { - -template -MDSPAN_FUNCTION constexpr void precondition(const char* cond, const char* file, unsigned line) -{ - if (not check) { return; } - // in case the macro doesn't use the arguments for custom macros - (void) cond; - (void) file; - (void) line; - MDSPAN_IMPL_PRECONDITION_VIOLATION_HANDLER(cond, file, line); -} - -} // namespace detail -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -#define MDSPAN_IMPL_PRECONDITION(...) \ - do { \ - if (not (__VA_ARGS__)) { \ - MDSPAN_IMPL_STANDARD_NAMESPACE::detail::precondition(#__VA_ARGS__, __FILE__, __LINE__); \ - } \ - } while (0) - -// end Preprocessor helpers }}}1 -//============================================================================== - -//============================================================================== -// {{{1 - -// These compatibility macros don't help with partial ordering, but they should do the trick -// for what we need to do with concepts in mdspan -#ifdef _MDSPAN_USE_CONCEPTS -# define MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) > requires REQ -# define MDSPAN_FUNCTION_REQUIRES(PAREN_PREQUALS, FNAME, PAREN_PARAMS, QUALS, REQ) \ - MDSPAN_PP_REMOVE_PARENS(PAREN_PREQUALS) FNAME PAREN_PARAMS QUALS requires REQ \ - /**/ -#else -# define MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) , typename ::std::enable_if<(REQ), int>::type = 0> -# define MDSPAN_FUNCTION_REQUIRES(PAREN_PREQUALS, FNAME, PAREN_PARAMS, QUALS, REQ) \ - MDSPAN_TEMPLATE_REQUIRES( \ - class __function_requires_ignored=void, \ - (std::is_void<__function_requires_ignored>::value && REQ) \ - ) MDSPAN_PP_REMOVE_PARENS(PAREN_PREQUALS) FNAME PAREN_PARAMS QUALS \ - /**/ -#endif - -#if defined(_MDSPAN_COMPILER_MSVC) && (!defined(_MSVC_TRADITIONAL) || _MSVC_TRADITIONAL) -# define MDSPAN_TEMPLATE_REQUIRES(...) \ - MDSPAN_PP_CAT( \ - MDSPAN_PP_CAT(MDSPAN_TEMPLATE_REQUIRES_, MDSPAN_PP_COUNT(__VA_ARGS__))\ - (__VA_ARGS__), \ - ) \ - /**/ -#else -# define MDSPAN_TEMPLATE_REQUIRES(...) \ - MDSPAN_PP_EVAL( \ - MDSPAN_PP_CAT(MDSPAN_TEMPLATE_REQUIRES_, MDSPAN_PP_COUNT(__VA_ARGS__)), \ - __VA_ARGS__ \ - ) \ - /**/ -#endif - -#define MDSPAN_TEMPLATE_REQUIRES_2(TP1, REQ) \ - template end Concept emulation }}}1 -//============================================================================== - -//============================================================================== -// {{{1 - -#ifdef _MDSPAN_USE_INLINE_VARIABLES -# define _MDSPAN_INLINE_VARIABLE inline -#else -# define _MDSPAN_INLINE_VARIABLE -#endif - -// end inline variables }}}1 -//============================================================================== - -//============================================================================== -// {{{1 - -#if _MDSPAN_USE_RETURN_TYPE_DEDUCTION -# define _MDSPAN_DEDUCE_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ - auto MDSPAN_PP_REMOVE_PARENS(SIGNATURE) { return MDSPAN_PP_REMOVE_PARENS(BODY); } -# define _MDSPAN_DEDUCE_DECLTYPE_AUTO_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ - decltype(auto) MDSPAN_PP_REMOVE_PARENS(SIGNATURE) { return MDSPAN_PP_REMOVE_PARENS(BODY); } -#else -# define _MDSPAN_DEDUCE_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ - auto MDSPAN_PP_REMOVE_PARENS(SIGNATURE) \ - -> std::remove_cv_t> \ - { return MDSPAN_PP_REMOVE_PARENS(BODY); } -# define _MDSPAN_DEDUCE_DECLTYPE_AUTO_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ - auto MDSPAN_PP_REMOVE_PARENS(SIGNATURE) \ - -> decltype(BODY) \ - { return MDSPAN_PP_REMOVE_PARENS(BODY); } - -#endif - -// end Return type deduction }}}1 -//============================================================================== - -//============================================================================== -// {{{1 - -struct __mdspan_enable_fold_comma { }; - -#ifdef _MDSPAN_USE_FOLD_EXPRESSIONS -# define _MDSPAN_FOLD_AND(...) ((__VA_ARGS__) && ...) -# define _MDSPAN_FOLD_AND_TEMPLATE(...) ((__VA_ARGS__) && ...) -# define _MDSPAN_FOLD_OR(...) ((__VA_ARGS__) || ...) -# define _MDSPAN_FOLD_ASSIGN_LEFT(INIT, ...) (INIT = ... = (__VA_ARGS__)) -# define _MDSPAN_FOLD_ASSIGN_RIGHT(PACK, ...) (PACK = ... = (__VA_ARGS__)) -# define _MDSPAN_FOLD_TIMES_RIGHT(PACK, ...) (PACK * ... * (__VA_ARGS__)) -# define _MDSPAN_FOLD_PLUS_RIGHT(PACK, ...) (PACK + ... + (__VA_ARGS__)) -# define _MDSPAN_FOLD_COMMA(...) ((__VA_ARGS__), ...) -#else - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -namespace __fold_compatibility_impl { - -// We could probably be more clever here, but at the (small) risk of losing some compiler understanding. For the -// few operations we need, it's not worth generalizing over the operation - -#if _MDSPAN_USE_RETURN_TYPE_DEDUCTION - -MDSPAN_FORCE_INLINE_FUNCTION -constexpr decltype(auto) __fold_right_and_impl() { - return true; -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr decltype(auto) __fold_right_and_impl(Arg&& arg, Args&&... args) { - return ((Arg&&)arg) && __fold_compatibility_impl::__fold_right_and_impl((Args&&)args...); -} - -MDSPAN_FORCE_INLINE_FUNCTION -constexpr decltype(auto) __fold_right_or_impl() { - return false; -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr auto __fold_right_or_impl(Arg&& arg, Args&&... args) { - return ((Arg&&)arg) || __fold_compatibility_impl::__fold_right_or_impl((Args&&)args...); -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr auto __fold_left_assign_impl(Arg1&& arg1) { - return (Arg1&&)arg1; -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr auto __fold_left_assign_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { - return __fold_compatibility_impl::__fold_left_assign_impl((((Arg1&&)arg1) = ((Arg2&&)arg2)), (Args&&)args...); -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr auto __fold_right_assign_impl(Arg1&& arg1) { - return (Arg1&&)arg1; -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr auto __fold_right_assign_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { - return ((Arg1&&)arg1) = __fold_compatibility_impl::__fold_right_assign_impl((Arg2&&)arg2, (Args&&)args...); -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr auto __fold_right_plus_impl(Arg1&& arg1) { - return (Arg1&&)arg1; -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr auto __fold_right_plus_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { - return ((Arg1&&)arg1) + __fold_compatibility_impl::__fold_right_plus_impl((Arg2&&)arg2, (Args&&)args...); -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr auto __fold_right_times_impl(Arg1&& arg1) { - return (Arg1&&)arg1; -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr auto __fold_right_times_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { - return ((Arg1&&)arg1) * __fold_compatibility_impl::__fold_right_times_impl((Arg2&&)arg2, (Args&&)args...); -} - -#else - -//------------------------------------------------------------------------------ -// {{{2 - -template -struct __fold_right_and_impl_; -template <> -struct __fold_right_and_impl_<> { - using __rv = bool; - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl() noexcept { - return true; - } -}; -template -struct __fold_right_and_impl_ { - using __next_t = __fold_right_and_impl_; - using __rv = decltype(std::declval() && std::declval()); - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg&& arg, Args&&... args) noexcept { - return ((Arg&&)arg) && __next_t::__impl((Args&&)args...); - } -}; - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr typename __fold_right_and_impl_::__rv -__fold_right_and_impl(Args&&... args) { - return __fold_right_and_impl_::__impl((Args&&)args...); -} - -// end right and }}}2 -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ -// {{{2 - -template -struct __fold_right_or_impl_; -template <> -struct __fold_right_or_impl_<> { - using __rv = bool; - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl() noexcept { - return false; - } -}; -template -struct __fold_right_or_impl_ { - using __next_t = __fold_right_or_impl_; - using __rv = decltype(std::declval() || std::declval()); - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg&& arg, Args&&... args) noexcept { - return ((Arg&&)arg) || __next_t::__impl((Args&&)args...); - } -}; - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr typename __fold_right_or_impl_::__rv -__fold_right_or_impl(Args&&... args) { - return __fold_right_or_impl_::__impl((Args&&)args...); -} - -// end right or }}}2 -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ -// {{{2 - -template -struct __fold_right_plus_impl_; -template -struct __fold_right_plus_impl_ { - using __rv = Arg&&; - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg&& arg) noexcept { - return (Arg&&)arg; - } -}; -template -struct __fold_right_plus_impl_ { - using __next_t = __fold_right_plus_impl_; - using __rv = decltype(std::declval() + std::declval()); - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { - return ((Arg1&&)arg) + __next_t::__impl((Arg2&&)arg2, (Args&&)args...); - } -}; - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr typename __fold_right_plus_impl_::__rv -__fold_right_plus_impl(Args&&... args) { - return __fold_right_plus_impl_::__impl((Args&&)args...); -} - -// end right plus }}}2 -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ -// {{{2 - -template -struct __fold_right_times_impl_; -template -struct __fold_right_times_impl_ { - using __rv = Arg&&; - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg&& arg) noexcept { - return (Arg&&)arg; - } -}; -template -struct __fold_right_times_impl_ { - using __next_t = __fold_right_times_impl_; - using __rv = decltype(std::declval() * std::declval()); - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { - return ((Arg1&&)arg) * __next_t::__impl((Arg2&&)arg2, (Args&&)args...); - } -}; - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr typename __fold_right_times_impl_::__rv -__fold_right_times_impl(Args&&... args) { - return __fold_right_times_impl_::__impl((Args&&)args...); -} - -// end right times }}}2 -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ -// {{{2 - -template -struct __fold_right_assign_impl_; -template -struct __fold_right_assign_impl_ { - using __rv = Arg&&; - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg&& arg) noexcept { - return (Arg&&)arg; - } -}; -template -struct __fold_right_assign_impl_ { - using __next_t = __fold_right_assign_impl_; - using __rv = decltype(std::declval() = std::declval()); - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { - return ((Arg1&&)arg) = __next_t::__impl((Arg2&&)arg2, (Args&&)args...); - } -}; - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr typename __fold_right_assign_impl_::__rv -__fold_right_assign_impl(Args&&... args) { - return __fold_right_assign_impl_::__impl((Args&&)args...); -} - -// end right assign }}}2 -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ -// {{{2 - -template -struct __fold_left_assign_impl_; -template -struct __fold_left_assign_impl_ { - using __rv = Arg&&; - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg&& arg) noexcept { - return (Arg&&)arg; - } -}; -template -struct __fold_left_assign_impl_ { - using __assign_result_t = decltype(std::declval() = std::declval()); - using __next_t = __fold_left_assign_impl_<__assign_result_t, Args...>; - using __rv = typename __next_t::__rv; - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { - return __next_t::__impl(((Arg1&&)arg) = (Arg2&&)arg2, (Args&&)args...); - } -}; - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr typename __fold_left_assign_impl_::__rv -__fold_left_assign_impl(Args&&... args) { - return __fold_left_assign_impl_::__impl((Args&&)args...); -} - -// end left assign }}}2 -//------------------------------------------------------------------------------ - -#endif - - -template -constexpr __mdspan_enable_fold_comma __fold_comma_impl(Args&&... args) noexcept { return { }; } - -template -struct __bools; - -} // __fold_compatibility_impl - -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -# define _MDSPAN_FOLD_AND(...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_and_impl((__VA_ARGS__)...) -# define _MDSPAN_FOLD_OR(...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_or_impl((__VA_ARGS__)...) -# define _MDSPAN_FOLD_ASSIGN_LEFT(INIT, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_left_assign_impl(INIT, (__VA_ARGS__)...) -# define _MDSPAN_FOLD_ASSIGN_RIGHT(PACK, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_assign_impl((PACK)..., __VA_ARGS__) -# define _MDSPAN_FOLD_TIMES_RIGHT(PACK, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_times_impl((PACK)..., __VA_ARGS__) -# define _MDSPAN_FOLD_PLUS_RIGHT(PACK, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_plus_impl((PACK)..., __VA_ARGS__) -# define _MDSPAN_FOLD_COMMA(...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_comma_impl((__VA_ARGS__)...) - -# define _MDSPAN_FOLD_AND_TEMPLATE(...) \ - _MDSPAN_TRAIT(std::is_same, __fold_compatibility_impl::__bools<(__VA_ARGS__)..., true>, __fold_compatibility_impl::__bools) - -#endif - -// end fold expressions }}}1 -//============================================================================== - -//============================================================================== -// {{{1 - -#if _MDSPAN_USE_VARIABLE_TEMPLATES -# define _MDSPAN_TRAIT(TRAIT, ...) TRAIT##_v<__VA_ARGS__> -#else -# define _MDSPAN_TRAIT(TRAIT, ...) TRAIT<__VA_ARGS__>::value -#endif - -// end Variable template compatibility }}}1 -//============================================================================== - -//============================================================================== -// {{{1 - -#if _MDSPAN_USE_CONSTEXPR_14 -# define _MDSPAN_CONSTEXPR_14 constexpr -// Workaround for a bug (I think?) in EDG frontends -# ifdef __EDG__ -# define _MDSPAN_CONSTEXPR_14_DEFAULTED -# else -# define _MDSPAN_CONSTEXPR_14_DEFAULTED constexpr -# endif -#else -# define _MDSPAN_CONSTEXPR_14 -# define _MDSPAN_CONSTEXPR_14_DEFAULTED -#endif - -// end Pre-C++14 constexpr }}}1 -//============================================================================== -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/macros.hpp - -#include // size_t - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -template -struct default_accessor { - - using offset_policy = default_accessor; - using element_type = ElementType; - using reference = ElementType&; - using data_handle_type = ElementType*; - - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr default_accessor() noexcept = default; - - MDSPAN_TEMPLATE_REQUIRES( - class OtherElementType, - /* requires */ ( - _MDSPAN_TRAIT(std::is_convertible, OtherElementType(*)[], element_type(*)[]) - ) - ) - MDSPAN_INLINE_FUNCTION - constexpr default_accessor(default_accessor) noexcept {} - - MDSPAN_INLINE_FUNCTION - constexpr data_handle_type - offset(data_handle_type p, size_t i) const noexcept { - return p + i; - } - - MDSPAN_FORCE_INLINE_FUNCTION - constexpr reference access(data_handle_type p, size_t i) const noexcept { - return p[i]; - } - -}; - -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/default_accessor.hpp -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/full_extent_t.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -struct full_extent_t { explicit full_extent_t() = default; }; - -_MDSPAN_INLINE_VARIABLE constexpr auto full_extent = full_extent_t{ }; - -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/full_extent_t.hpp -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/mdspan.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/layout_right.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/trait_backports.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER -#ifndef MDSPAN_INCLUDE_EXPERIMENTAL_BITS_TRAIT_BACKPORTS_HPP_ -#define MDSPAN_INCLUDE_EXPERIMENTAL_BITS_TRAIT_BACKPORTS_HPP_ - - -#include -#include // integer_sequence - -//============================================================================== -// {{{1 - -#ifdef _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS - -#if _MDSPAN_USE_VARIABLE_TEMPLATES -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -#define _MDSPAN_BACKPORT_TRAIT(TRAIT) \ - template _MDSPAN_INLINE_VARIABLE constexpr auto TRAIT##_v = TRAIT::value; - -_MDSPAN_BACKPORT_TRAIT(is_assignable) -_MDSPAN_BACKPORT_TRAIT(is_constructible) -_MDSPAN_BACKPORT_TRAIT(is_convertible) -_MDSPAN_BACKPORT_TRAIT(is_default_constructible) -_MDSPAN_BACKPORT_TRAIT(is_trivially_destructible) -_MDSPAN_BACKPORT_TRAIT(is_same) -_MDSPAN_BACKPORT_TRAIT(is_empty) -_MDSPAN_BACKPORT_TRAIT(is_void) - -#undef _MDSPAN_BACKPORT_TRAIT - -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -#endif // _MDSPAN_USE_VARIABLE_TEMPLATES - -#endif // _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS - -// end Variable template trait backports (e.g., is_void_v) }}}1 -//============================================================================== - -//============================================================================== -// {{{1 - -#if !defined(_MDSPAN_USE_INTEGER_SEQUENCE) || !_MDSPAN_USE_INTEGER_SEQUENCE - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -template -struct integer_sequence { - static constexpr size_t size() noexcept { return sizeof...(Vals); } - using value_type = T; -}; - -template -using index_sequence = std::integer_sequence; - -namespace __detail { - -template -struct __make_int_seq_impl; - -template -struct __make_int_seq_impl> -{ - using type = integer_sequence; -}; - -template -struct __make_int_seq_impl< - T, N, I, integer_sequence -> : __make_int_seq_impl> -{ }; - -} // end namespace __detail - -template -using make_integer_sequence = typename __detail::__make_int_seq_impl>::type; - -template -using make_index_sequence = typename __detail::__make_int_seq_impl>::type; - -template -using index_sequence_for = make_index_sequence; - -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -#endif - -// end integer sequence (ugh...) }}}1 -//============================================================================== - -//============================================================================== -// {{{1 - -#if !defined(_MDSPAN_USE_STANDARD_TRAIT_ALIASES) || !_MDSPAN_USE_STANDARD_TRAIT_ALIASES - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -#define _MDSPAN_BACKPORT_TRAIT_ALIAS(TRAIT) \ - template using TRAIT##_t = typename TRAIT::type; - -_MDSPAN_BACKPORT_TRAIT_ALIAS(remove_cv) -_MDSPAN_BACKPORT_TRAIT_ALIAS(remove_reference) - -template -using enable_if_t = typename enable_if<_B, _T>::type; - -#undef _MDSPAN_BACKPORT_TRAIT_ALIAS - -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -#endif - -// end standard trait aliases }}}1 -//============================================================================== - -#endif //MDSPAN_INCLUDE_EXPERIMENTAL_BITS_TRAIT_BACKPORTS_HPP_ -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/trait_backports.hpp -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/extents.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/dynamic_extent.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -#if defined(__cpp_lib_span) -#include -#endif - -#include // size_t -#include // numeric_limits - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -#if defined(__cpp_lib_span) -using std::dynamic_extent; -#else -_MDSPAN_INLINE_VARIABLE constexpr auto dynamic_extent = std::numeric_limits::max(); -#endif -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -//============================================================================================================== -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/dynamic_extent.hpp -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/utility.hpp - -#include -#include - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace detail { - -// type alias used for rank-based tag dispatch -// -// this is used to enable alternatives to constexpr if when building for C++14 -// -template -using with_rank = std::integral_constant; - -template -constexpr bool common_integral_compare(I1 x, I2 y) -{ - static_assert(std::is_integral::value and - std::is_integral::value, ""); - - using I = std::common_type_t; - return static_cast(x) == static_cast(y); -} - -template -constexpr bool rankwise_equal(with_rank<0>, const T1&, const T2&, F) -{ - return true; -} -template -constexpr bool rankwise_equal(with_rank, const T1& x, const T2& y, F func) -{ - bool match = true; - - for (std::size_t r = 0; r < N; r++) { - match = match && common_integral_compare(func(x, r), func(y, r)); - } - - return match; -} - -constexpr struct -{ - template - constexpr auto operator()(const T& x, I i) const - { - return x.extent(i); - } -} extent; - -constexpr struct -{ - template - constexpr auto operator()(const T& x, I i) const - { - return x.stride(i); - } -} stride; - -} // namespace detail - -constexpr struct mdspan_non_standard_tag { -} mdspan_non_standard; - -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/utility.hpp - -#ifdef __cpp_lib_span -#include -#endif -#include -#include - -#include -#include - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace detail { - -// Function used to check compatibility of extents in converting constructor -// can't be a private member function for some reason. -template -static constexpr std::integral_constant __check_compatible_extents( - std::integral_constant, - std::integer_sequence, - std::integer_sequence) noexcept { - return {}; -} - -// This helper prevents ICE's on MSVC. -template -struct __compare_extent_compatible : std::integral_constant -{}; - -template -static constexpr std::integral_constant< - bool, _MDSPAN_FOLD_AND(__compare_extent_compatible::value)> -__check_compatible_extents( - std::integral_constant, - std::integer_sequence, - std::integer_sequence) noexcept { - return {}; -} - -template -MDSPAN_INLINE_FUNCTION -static constexpr bool are_valid_indices() { - return - _MDSPAN_FOLD_AND(std::is_convertible::value) && - _MDSPAN_FOLD_AND(std::is_nothrow_constructible::value); -} - -// ------------------------------------------------------------------ -// ------------ static_array ---------------------------------------- -// ------------------------------------------------------------------ - -// array like class which provides an array of static values with get -// function and operator []. - -// Implementation of Static Array with recursive implementation of get. -template struct static_array_impl; - -template -struct static_array_impl { - MDSPAN_INLINE_FUNCTION - constexpr static T get(size_t r) { - if (r == R) - return FirstExt; - else - return static_array_impl::get(r); - } - template MDSPAN_INLINE_FUNCTION constexpr static T get() { -#if MDSPAN_HAS_CXX_17 - if constexpr (r == R) - return FirstExt; - else - return static_array_impl::template get(); -#else - get(r); -#endif - } -}; - -// End the recursion -template -struct static_array_impl { - MDSPAN_INLINE_FUNCTION - constexpr static T get(size_t) { return FirstExt; } - template MDSPAN_INLINE_FUNCTION constexpr static T get() { - return FirstExt; - } -}; - -// Don't start recursion if size 0 -template struct static_array_impl<0, T> { - MDSPAN_INLINE_FUNCTION - constexpr static T get(size_t) { return T(); } - template MDSPAN_INLINE_FUNCTION constexpr static T get() { - return T(); - } -}; - -// Static array, provides get(), get(r) and operator[r] -template struct static_array: - public static_array_impl<0, T, Values...> { - -public: - using value_type = T; - - MDSPAN_INLINE_FUNCTION - constexpr static size_t size() { return sizeof...(Values); } -}; - - -// ------------------------------------------------------------------ -// ------------ index_sequence_scan --------------------------------- -// ------------------------------------------------------------------ - -// index_sequence_scan takes compile time values and provides get(r) -// and get() which return the sum of the first r-1 values. - -// Recursive implementation for get -template struct index_sequence_scan_impl; - -template -struct index_sequence_scan_impl { - MDSPAN_INLINE_FUNCTION - constexpr static size_t get(size_t r) { - if (r > R) - return FirstVal + index_sequence_scan_impl::get(r); - else - return 0; - } -}; - -template -struct index_sequence_scan_impl { -#if defined(__NVCC__) || defined(__NVCOMPILER) || \ - defined(_MDSPAN_COMPILER_INTEL) - // NVCC warns about pointless comparison with 0 for R==0 and r being const - // evaluatable and also 0. - MDSPAN_INLINE_FUNCTION - constexpr static size_t get(size_t r) { - return static_cast(R) > static_cast(r) ? FirstVal : 0; - } -#else - MDSPAN_INLINE_FUNCTION - constexpr static size_t get(size_t r) { return R > r ? FirstVal : 0; } -#endif -}; -template <> struct index_sequence_scan_impl<0> { - MDSPAN_INLINE_FUNCTION - constexpr static size_t get(size_t) { return 0; } -}; - -// ------------------------------------------------------------------ -// ------------ possibly_empty_array ------------------------------- -// ------------------------------------------------------------------ - -// array like class which provides get function and operator [], and -// has a specialization for the size 0 case. -// This is needed to make the maybe_static_array be truly empty, for -// all static values. - -template struct possibly_empty_array { - T vals[N]{}; - MDSPAN_INLINE_FUNCTION - constexpr T &operator[](size_t r) { return vals[r]; } - MDSPAN_INLINE_FUNCTION - constexpr const T &operator[](size_t r) const { return vals[r]; } -}; - -template struct possibly_empty_array { - MDSPAN_INLINE_FUNCTION - constexpr T operator[](size_t) { return T(); } - MDSPAN_INLINE_FUNCTION - constexpr const T operator[](size_t) const { return T(); } -}; - -// ------------------------------------------------------------------ -// ------------ maybe_static_array ---------------------------------- -// ------------------------------------------------------------------ - -// array like class which has a mix of static and runtime values but -// only stores the runtime values. -// The type of the static and the runtime values can be different. -// The position of a dynamic value is indicated through a tag value. -template -struct maybe_static_array { - - static_assert(std::is_convertible::value, "maybe_static_array: TStatic must be convertible to TDynamic"); - static_assert(std::is_convertible::value, "maybe_static_array: TDynamic must be convertible to TStatic"); - -private: - // Static values member - using static_vals_t = static_array; - constexpr static size_t m_size = sizeof...(Values); - constexpr static size_t m_size_dynamic = - _MDSPAN_FOLD_PLUS_RIGHT((Values == dyn_tag), 0); - - // Dynamic values member - _MDSPAN_NO_UNIQUE_ADDRESS possibly_empty_array - m_dyn_vals; - - // static mapping of indices to the position in the dynamic values array - using dyn_map_t = index_sequence_scan_impl<0, static_cast(Values == dyn_tag)...>; -public: - - // two types for static and dynamic values - using value_type = TDynamic; - using static_value_type = TStatic; - // tag value indicating dynamic value - constexpr static static_value_type tag_value = dyn_tag; - - constexpr maybe_static_array() = default; - - // constructor for all static values - // TODO: add precondition check? - MDSPAN_TEMPLATE_REQUIRES(class... Vals, - /* requires */ ((m_size_dynamic == 0) && - (sizeof...(Vals) > 0))) - MDSPAN_INLINE_FUNCTION - constexpr maybe_static_array(Vals...) : m_dyn_vals{} {} - - // constructors from dynamic values only - MDSPAN_TEMPLATE_REQUIRES(class... DynVals, - /* requires */ (sizeof...(DynVals) == - m_size_dynamic && - m_size_dynamic > 0)) - MDSPAN_INLINE_FUNCTION - constexpr maybe_static_array(DynVals... vals) - : m_dyn_vals{static_cast(vals)...} {} - - - MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, - /* requires */ (N == m_size_dynamic && N > 0)) - MDSPAN_INLINE_FUNCTION - constexpr maybe_static_array(const std::array &vals) { - for (size_t r = 0; r < N; r++) - m_dyn_vals[r] = static_cast(vals[r]); - } - - MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, - /* requires */ (N == m_size_dynamic && N == 0)) - MDSPAN_INLINE_FUNCTION - constexpr maybe_static_array(const std::array &) : m_dyn_vals{} {} - -#ifdef __cpp_lib_span - MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, - /* requires */ (N == m_size_dynamic && N > 0)) - MDSPAN_INLINE_FUNCTION - constexpr maybe_static_array(const std::span &vals) { - for (size_t r = 0; r < N; r++) - m_dyn_vals[r] = static_cast(vals[r]); - } - - MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, - /* requires */ (N == m_size_dynamic && N == 0)) - MDSPAN_INLINE_FUNCTION - constexpr maybe_static_array(const std::span &) : m_dyn_vals{} {} -#endif - - // constructors from all values - MDSPAN_TEMPLATE_REQUIRES(class... DynVals, - /* requires */ (sizeof...(DynVals) != - m_size_dynamic && - m_size_dynamic > 0)) - MDSPAN_INLINE_FUNCTION - constexpr maybe_static_array(DynVals... vals) - : m_dyn_vals{} { - static_assert((sizeof...(DynVals) == m_size), "Invalid number of values."); - TDynamic values[m_size]{static_cast(vals)...}; - for (size_t r = 0; r < m_size; r++) { - TStatic static_val = static_vals_t::get(r); - if (static_val == dyn_tag) { - m_dyn_vals[dyn_map_t::get(r)] = values[r]; - } -// Precondition check -#ifdef _MDSPAN_DEBUG - else { - assert(values[r] == static_cast(static_val)); - } -#endif - } - } - - MDSPAN_TEMPLATE_REQUIRES( - class T, size_t N, - /* requires */ (N != m_size_dynamic && m_size_dynamic > 0)) - MDSPAN_INLINE_FUNCTION - constexpr maybe_static_array(const std::array &vals) { - static_assert((N == m_size), "Invalid number of values."); -// Precondition check -#ifdef _MDSPAN_DEBUG - assert(N == m_size); -#endif - for (size_t r = 0; r < m_size; r++) { - TStatic static_val = static_vals_t::get(r); - if (static_val == dyn_tag) { - m_dyn_vals[dyn_map_t::get(r)] = static_cast(vals[r]); - } -// Precondition check -#ifdef _MDSPAN_DEBUG - else { - assert(static_cast(vals[r]) == - static_cast(static_val)); - } -#endif - } - } - -#ifdef __cpp_lib_span - MDSPAN_TEMPLATE_REQUIRES( - class T, size_t N, - /* requires */ (N != m_size_dynamic && m_size_dynamic > 0)) - MDSPAN_INLINE_FUNCTION - constexpr maybe_static_array(const std::span &vals) { - static_assert((N == m_size) || (m_size == dynamic_extent)); -#ifdef _MDSPAN_DEBUG - assert(N == m_size); -#endif - for (size_t r = 0; r < m_size; r++) { - TStatic static_val = static_vals_t::get(r); - if (static_val == dyn_tag) { - m_dyn_vals[dyn_map_t::get(r)] = static_cast(vals[r]); - } -#ifdef _MDSPAN_DEBUG - else { - assert(static_cast(vals[r]) == - static_cast(static_val)); - } -#endif - } - } -#endif - - // access functions - MDSPAN_INLINE_FUNCTION - constexpr static TStatic static_value(size_t r) { return static_vals_t::get(r); } - - MDSPAN_INLINE_FUNCTION - constexpr TDynamic value(size_t r) const { - TStatic static_val = static_vals_t::get(r); - return static_val == dyn_tag ? m_dyn_vals[dyn_map_t::get(r)] - : static_cast(static_val); - } - MDSPAN_INLINE_FUNCTION - constexpr TDynamic operator[](size_t r) const { return value(r); } - - - // observers - MDSPAN_INLINE_FUNCTION - constexpr static size_t size() { return m_size; } - MDSPAN_INLINE_FUNCTION - constexpr static size_t size_dynamic() { return m_size_dynamic; } -}; - -} // namespace detail -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -// ------------------------------------------------------------------ -// ------------ extents --------------------------------------------- -// ------------------------------------------------------------------ - -// Class to describe the extents of a multi dimensional array. -// Used by mdspan, mdarray and layout mappings. -// See ISO C++ standard [mdspan.extents] - -template class extents { -public: - // typedefs for integral types used - using index_type = IndexType; - using size_type = std::make_unsigned_t; - using rank_type = size_t; - - static_assert(std::is_integral::value && !std::is_same::value, - MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents::index_type must be a signed or unsigned integer type"); -private: - constexpr static rank_type m_rank = sizeof...(Extents); - constexpr static rank_type m_rank_dynamic = - _MDSPAN_FOLD_PLUS_RIGHT((Extents == dynamic_extent), /* + ... + */ 0); - - // internal storage type using maybe_static_array - using vals_t = - detail::maybe_static_array; - _MDSPAN_NO_UNIQUE_ADDRESS vals_t m_vals; - -public: - // [mdspan.extents.obs], observers of multidimensional index space - MDSPAN_INLINE_FUNCTION - constexpr static rank_type rank() noexcept { return m_rank; } - MDSPAN_INLINE_FUNCTION - constexpr static rank_type rank_dynamic() noexcept { return m_rank_dynamic; } - - MDSPAN_INLINE_FUNCTION - constexpr index_type extent(rank_type r) const noexcept { return m_vals.value(r); } - MDSPAN_INLINE_FUNCTION - constexpr static size_t static_extent(rank_type r) noexcept { - return vals_t::static_value(r); - } - - // [mdspan.extents.cons], constructors - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr extents() noexcept = default; - - // Construction from just dynamic or all values. - // Precondition check is deferred to maybe_static_array constructor - MDSPAN_TEMPLATE_REQUIRES( - class... OtherIndexTypes, - /* requires */ ( - _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_convertible, OtherIndexTypes, - index_type) /* && ... */) && - _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, - OtherIndexTypes) /* && ... */) && - (sizeof...(OtherIndexTypes) == m_rank || - sizeof...(OtherIndexTypes) == m_rank_dynamic))) - MDSPAN_INLINE_FUNCTION - constexpr explicit extents(OtherIndexTypes... dynvals) noexcept - : m_vals(static_cast(dynvals)...) {} - - MDSPAN_TEMPLATE_REQUIRES( - class OtherIndexType, size_t N, - /* requires */ - ( - _MDSPAN_TRAIT(std::is_convertible, const OtherIndexType&, index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, - const OtherIndexType&) && - (N == m_rank || N == m_rank_dynamic))) - MDSPAN_INLINE_FUNCTION - MDSPAN_CONDITIONAL_EXPLICIT(N != m_rank_dynamic) - constexpr extents(const std::array &exts) noexcept - : m_vals(std::move(exts)) {} - -#ifdef __cpp_lib_span - MDSPAN_TEMPLATE_REQUIRES( - class OtherIndexType, size_t N, - /* requires */ - (_MDSPAN_TRAIT(std::is_convertible, const OtherIndexType&, index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const OtherIndexType&) && - (N == m_rank || N == m_rank_dynamic))) - MDSPAN_INLINE_FUNCTION - MDSPAN_CONDITIONAL_EXPLICIT(N != m_rank_dynamic) - constexpr extents(const std::span &exts) noexcept - : m_vals(std::move(exts)) {} -#endif - -private: - // Function to construct extents storage from other extents. - // With C++ 17 the first two variants could be collapsed using if constexpr - // in which case you don't need all the requires clauses. - // in C++ 14 mode that doesn't work due to infinite recursion - MDSPAN_TEMPLATE_REQUIRES( - size_t DynCount, size_t R, class OtherExtents, class... DynamicValues, - /* requires */ ((R < m_rank) && (static_extent(R) == dynamic_extent))) - MDSPAN_INLINE_FUNCTION - constexpr - vals_t __construct_vals_from_extents(std::integral_constant, - std::integral_constant, - const OtherExtents &exts, - DynamicValues... dynamic_values) noexcept { - return __construct_vals_from_extents( - std::integral_constant(), - std::integral_constant(), exts, dynamic_values..., - exts.extent(R)); - } - - MDSPAN_TEMPLATE_REQUIRES( - size_t DynCount, size_t R, class OtherExtents, class... DynamicValues, - /* requires */ ((R < m_rank) && (static_extent(R) != dynamic_extent))) - MDSPAN_INLINE_FUNCTION - constexpr - vals_t __construct_vals_from_extents(std::integral_constant, - std::integral_constant, - const OtherExtents &exts, - DynamicValues... dynamic_values) noexcept { - return __construct_vals_from_extents( - std::integral_constant(), - std::integral_constant(), exts, dynamic_values...); - } - - MDSPAN_TEMPLATE_REQUIRES( - size_t DynCount, size_t R, class OtherExtents, class... DynamicValues, - /* requires */ ((R == m_rank) && (DynCount == m_rank_dynamic))) - MDSPAN_INLINE_FUNCTION - constexpr - vals_t __construct_vals_from_extents(std::integral_constant, - std::integral_constant, - const OtherExtents &, - DynamicValues... dynamic_values) noexcept { - return vals_t{static_cast(dynamic_values)...}; - } - -public: - - // Converting constructor from other extents specializations - MDSPAN_TEMPLATE_REQUIRES( - class OtherIndexType, size_t... OtherExtents, - /* requires */ - ( - /* multi-stage check to protect from invalid pack expansion when sizes - don't match? */ - decltype(detail::__check_compatible_extents( - // using: sizeof...(Extents) == sizeof...(OtherExtents) as the second argument fails with MSVC+NVCC with some obscure expansion error - // MSVC: 19.38.33133 NVCC: 12.0 - std::integral_constant::rank() == extents::rank()>{}, - std::integer_sequence{}, - std::integer_sequence{}))::value - ) - ) - MDSPAN_INLINE_FUNCTION - MDSPAN_CONDITIONAL_EXPLICIT((((Extents != dynamic_extent) && - (OtherExtents == dynamic_extent)) || - ...) || - (std::numeric_limits::max() < - std::numeric_limits::max())) - constexpr extents(const extents &other) noexcept - : m_vals(__construct_vals_from_extents( - std::integral_constant(), - std::integral_constant(), other)) {} - - // Comparison operator - template - MDSPAN_INLINE_FUNCTION friend constexpr bool - operator==(const extents &lhs, - const extents &rhs) noexcept { - return - rank() == extents::rank() && - detail::rankwise_equal(detail::with_rank{}, rhs, lhs, detail::extent); - } - -#if !(MDSPAN_HAS_CXX_20) - template - MDSPAN_INLINE_FUNCTION friend constexpr bool - operator!=(extents const &lhs, - extents const &rhs) noexcept { - return !(lhs == rhs); - } -#endif -}; - -// Recursive helper classes to implement dextents alias for extents -namespace detail { - -template > -struct __make_dextents; - -template -struct __make_dextents< - IndexType, Rank, ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents> -{ - using type = typename __make_dextents< - IndexType, Rank - 1, - ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents>::type; -}; - -template -struct __make_dextents< - IndexType, 0, ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents> -{ - using type = ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents; -}; - -} // end namespace detail - -// [mdspan.extents.dextents], alias template -template -using dextents = typename detail::__make_dextents::type; - -// Deduction guide for extents -#if defined(_MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION) -template -extents(IndexTypes...) - -> extents; -#endif - -// Helper type traits for identifying a class as extents. -namespace detail { - -template struct __is_extents : ::std::false_type {}; - -template -struct __is_extents<::MDSPAN_IMPL_STANDARD_NAMESPACE::extents> - : ::std::true_type {}; - -template -#if MDSPAN_HAS_CXX_17 -inline -#else -static -#endif -constexpr bool __is_extents_v = __is_extents::value; - -template -MDSPAN_INLINE_FUNCTION -constexpr void -check_lower_bound(InputIndexType user_index, - ExtentsIndexType /* current_extent */, - std::true_type /* is_signed */) -{ - (void) user_index; // prevent unused variable warning -#ifdef _MDSPAN_DEBUG - assert(static_cast(user_index) >= 0); -#endif -} - -template -MDSPAN_INLINE_FUNCTION -constexpr void -check_lower_bound(InputIndexType /* user_index */, - ExtentsIndexType /* current_extent */, - std::false_type /* is_signed */) -{} - -template -MDSPAN_INLINE_FUNCTION -constexpr void -check_upper_bound(InputIndexType user_index, - ExtentsIndexType current_extent) -{ - (void) user_index; // prevent unused variable warnings - (void) current_extent; -#ifdef _MDSPAN_DEBUG - assert(static_cast(user_index) < current_extent); -#endif -} - -// Returning true to use AND fold instead of comma -// CPP14 mode doesn't like the use of void expressions -// with the way the _MDSPAN_FOLD_AND is set up -template -MDSPAN_INLINE_FUNCTION -constexpr bool -check_one_index(InputIndex user_index, - ExtentsIndexType current_extent) -{ - check_lower_bound(user_index, current_extent, - std::integral_constant::value>{}); - check_upper_bound(user_index, current_extent); - return true; -} - -template -MDSPAN_INLINE_FUNCTION -constexpr void -check_all_indices_helper(std::index_sequence, - const extents& exts, - Indices... indices) -{ - // Suppress warning about statement has no effect - (void) _MDSPAN_FOLD_AND( - (check_one_index(indices, exts.extent(RankIndices))) - ); -} - -template -MDSPAN_INLINE_FUNCTION -constexpr void -check_all_indices(const extents& exts, - Indices... indices) -{ - check_all_indices_helper(std::make_index_sequence(), - exts, indices...); -} - -} // namespace detail -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/extents.hpp -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/layout_stride.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/compressed_pair.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/no_unique_address.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace detail { - -//============================================================================== - -template -struct __no_unique_address_emulation { - using __stored_type = _T; - _T __v; - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T const &__ref() const noexcept { - return __v; - } - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T &__ref() noexcept { - return __v; - } -}; - -// Empty case -// This doesn't work if _T is final, of course, but we're not using anything -// like that currently. That kind of thing could be added pretty easily though -template -struct __no_unique_address_emulation< - _T, _Disambiguator, - std::enable_if_t<_MDSPAN_TRAIT(std::is_empty, _T) && - // If the type isn't trivially destructible, its destructor - // won't be called at the right time, so don't use this - // specialization - _MDSPAN_TRAIT(std::is_trivially_destructible, _T)>> : -#ifdef _MDSPAN_COMPILER_MSVC - // MSVC doesn't allow you to access public static member functions of a type - // when you *happen* to privately inherit from that type. - protected -#else - // But we still want this to be private if possible so that we don't accidentally - // access members of _T directly rather than calling __ref() first, which wouldn't - // work if _T happens to be stateful and thus we're using the unspecialized definition - // of __no_unique_address_emulation above. - private -#endif - _T { - using __stored_type = _T; - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T const &__ref() const noexcept { - return *static_cast<_T const *>(this); - } - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T &__ref() noexcept { - return *static_cast<_T *>(this); - } - - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __no_unique_address_emulation() noexcept = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __no_unique_address_emulation( - __no_unique_address_emulation const &) noexcept = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __no_unique_address_emulation( - __no_unique_address_emulation &&) noexcept = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __no_unique_address_emulation & - operator=(__no_unique_address_emulation const &) noexcept = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __no_unique_address_emulation & - operator=(__no_unique_address_emulation &&) noexcept = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - ~__no_unique_address_emulation() noexcept = default; - - // Explicitly make this not a reference so that the copy or move - // constructor still gets called. - MDSPAN_INLINE_FUNCTION - explicit constexpr __no_unique_address_emulation(_T const& __v) noexcept : _T(__v) {} - MDSPAN_INLINE_FUNCTION - explicit constexpr __no_unique_address_emulation(_T&& __v) noexcept : _T(::std::move(__v)) {} -}; - -//============================================================================== - -} // end namespace detail -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/no_unique_address.hpp -#endif - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace detail { - -// For no unique address emulation, this is the case taken when neither are empty. -// For real `[[no_unique_address]]`, this case is always taken. -template struct __compressed_pair { - _MDSPAN_NO_UNIQUE_ADDRESS _T1 __t1_val{}; - _MDSPAN_NO_UNIQUE_ADDRESS _T2 __t2_val{}; - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T1 &__first() noexcept { return __t1_val; } - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T1 const &__first() const noexcept { - return __t1_val; - } - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T2 &__second() noexcept { return __t2_val; } - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T2 const &__second() const noexcept { - return __t2_val; - } - - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair() = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair(__compressed_pair const &) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair(__compressed_pair &&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & - operator=(__compressed_pair const &) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & - operator=(__compressed_pair &&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - ~__compressed_pair() = default; - template - MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_T1Like &&__t1, _T2Like &&__t2) - : __t1_val((_T1Like &&) __t1), __t2_val((_T2Like &&) __t2) {} -}; - -#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - -// First empty. -template -struct __compressed_pair< - _T1, _T2, - std::enable_if_t<_MDSPAN_TRAIT(std::is_empty, _T1) && !_MDSPAN_TRAIT(std::is_empty, _T2)>> - : private _T1 { - _T2 __t2_val{}; - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T1 &__first() noexcept { - return *static_cast<_T1 *>(this); - } - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T1 const &__first() const noexcept { - return *static_cast<_T1 const *>(this); - } - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T2 &__second() noexcept { return __t2_val; } - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T2 const &__second() const noexcept { - return __t2_val; - } - - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair() = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair(__compressed_pair const &) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair(__compressed_pair &&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & - operator=(__compressed_pair const &) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & - operator=(__compressed_pair &&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - ~__compressed_pair() = default; - template - MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_T1Like &&__t1, _T2Like &&__t2) - : _T1((_T1Like &&) __t1), __t2_val((_T2Like &&) __t2) {} -}; - -// Second empty. -template -struct __compressed_pair< - _T1, _T2, - std::enable_if_t> - : private _T2 { - _T1 __t1_val{}; - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T1 &__first() noexcept { return __t1_val; } - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T1 const &__first() const noexcept { - return __t1_val; - } - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T2 &__second() noexcept { - return *static_cast<_T2 *>(this); - } - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T2 const &__second() const noexcept { - return *static_cast<_T2 const *>(this); - } - - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair() = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair(__compressed_pair const &) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair(__compressed_pair &&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & - operator=(__compressed_pair const &) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & - operator=(__compressed_pair &&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - ~__compressed_pair() = default; - - template - MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_T1Like &&__t1, _T2Like &&__t2) - : _T2((_T2Like &&) __t2), __t1_val((_T1Like &&) __t1) {} -}; - -// Both empty. -template -struct __compressed_pair< - _T1, _T2, - std::enable_if_t<_MDSPAN_TRAIT(std::is_empty, _T1) && _MDSPAN_TRAIT(std::is_empty, _T2)>> - // We need to use the __no_unique_address_emulation wrapper here to avoid - // base class ambiguities. -#ifdef _MDSPAN_COMPILER_MSVC -// MSVC doesn't allow you to access public static member functions of a type -// when you *happen* to privately inherit from that type. - : protected __no_unique_address_emulation<_T1, 0>, - protected __no_unique_address_emulation<_T2, 1> -#else - : private __no_unique_address_emulation<_T1, 0>, - private __no_unique_address_emulation<_T2, 1> -#endif -{ - using __first_base_t = __no_unique_address_emulation<_T1, 0>; - using __second_base_t = __no_unique_address_emulation<_T2, 1>; - - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T1 &__first() noexcept { - return this->__first_base_t::__ref(); - } - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T1 const &__first() const noexcept { - return this->__first_base_t::__ref(); - } - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T2 &__second() noexcept { - return this->__second_base_t::__ref(); - } - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T2 const &__second() const noexcept { - return this->__second_base_t::__ref(); - } - - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair() = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair(__compressed_pair const &) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair(__compressed_pair &&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & - operator=(__compressed_pair const &) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & - operator=(__compressed_pair &&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - ~__compressed_pair() = default; - template - MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_T1Like &&__t1, _T2Like &&__t2) noexcept - : __first_base_t(_T1((_T1Like &&) __t1)), - __second_base_t(_T2((_T2Like &&) __t2)) - { } -}; - -#endif // !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - -} // end namespace detail -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/compressed_pair.hpp - -#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) -#endif - -#include -#include -#include - -#ifdef __cpp_lib_span -#include -#endif -#if defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20 && defined(__cpp_lib_concepts) -# include -#endif - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -struct layout_left { - template - class mapping; -}; -struct layout_right { - template - class mapping; -}; - -namespace detail { - template - constexpr bool __is_mapping_of = - std::is_same, Mapping>::value; - -#if defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20 -# if !defined(__cpp_lib_concepts) - namespace internal { - namespace detail { - template - concept __same_as = std::is_same_v<_Tp, _Up>; - } // namespace detail - template - concept __same_as = detail::__same_as && detail::__same_as; - } // namespace internal -# endif - - template - concept __layout_mapping_alike = requires { - requires __is_extents::value; -#if defined(__cpp_lib_concepts) - { M::is_always_strided() } -> std::same_as; - { M::is_always_exhaustive() } -> std::same_as; - { M::is_always_unique() } -> std::same_as; -#else - { M::is_always_strided() } -> internal::__same_as; - { M::is_always_exhaustive() } -> internal::__same_as; - { M::is_always_unique() } -> internal::__same_as; -#endif - std::bool_constant::value; - std::bool_constant::value; - std::bool_constant::value; - }; -#endif - -} // namespace detail - -struct layout_stride { - template - class mapping -#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - : private detail::__no_unique_address_emulation< - detail::__compressed_pair< - Extents, - detail::possibly_empty_array - > - > -#endif - { - public: - using extents_type = Extents; - using index_type = typename extents_type::index_type; - using size_type = typename extents_type::size_type; - using rank_type = typename extents_type::rank_type; - using layout_type = layout_stride; - - // This could be a `requires`, but I think it's better and clearer as a `static_assert`. - static_assert(detail::__is_extents_v, - MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::layout_stride::mapping must be instantiated with a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); - - - private: - - //---------------------------------------------------------------------------- - - using __strides_storage_t = detail::possibly_empty_array; - using __member_pair_t = detail::__compressed_pair; - -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - _MDSPAN_NO_UNIQUE_ADDRESS __member_pair_t __members; -#else - using __base_t = detail::__no_unique_address_emulation<__member_pair_t>; -#endif - - MDSPAN_FORCE_INLINE_FUNCTION constexpr __strides_storage_t const& - __strides_storage() const noexcept { -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - return __members.__second(); -#else - return this->__base_t::__ref().__second(); -#endif - } - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 __strides_storage_t& - __strides_storage() noexcept { -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - return __members.__second(); -#else - return this->__base_t::__ref().__second(); -#endif - } - - template - _MDSPAN_HOST_DEVICE - constexpr index_type __get_size(::MDSPAN_IMPL_STANDARD_NAMESPACE::extents,std::integer_sequence) const { - return _MDSPAN_FOLD_TIMES_RIGHT( static_cast(extents().extent(Idx)), 1 ); - } - - //---------------------------------------------------------------------------- - - template - friend class mapping; - - //---------------------------------------------------------------------------- - - // Workaround for non-deducibility of the index sequence template parameter if it's given at the top level - template - struct __deduction_workaround; - - template - struct __deduction_workaround> - { - template - MDSPAN_INLINE_FUNCTION - static constexpr bool _eq_impl(mapping const& self, mapping const& other) noexcept { - using common_t = std::common_type_t; - return _MDSPAN_FOLD_AND((static_cast(self.stride(Idxs)) == static_cast(other.stride(Idxs))) /* && ... */) - && _MDSPAN_FOLD_AND((static_cast(self.extents().extent(Idxs)) == static_cast(other.extents().extent(Idxs))) /* || ... */); - } - template - MDSPAN_INLINE_FUNCTION - static constexpr bool _not_eq_impl(mapping const& self, mapping const& other) noexcept { - using common_t = std::common_type_t; - return _MDSPAN_FOLD_OR((static_cast(self.stride(Idxs)) != static_cast(other.stride(Idxs))) /* || ... */) - || _MDSPAN_FOLD_OR((static_cast(self.extents().extent(Idxs)) != static_cast(other.extents().extent(Idxs))) /* || ... */); - } - - template - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr size_t _call_op_impl(mapping const& self, Integral... idxs) noexcept { - return _MDSPAN_FOLD_PLUS_RIGHT((idxs * self.stride(Idxs)), /* + ... + */ 0); - } - - MDSPAN_INLINE_FUNCTION - static constexpr size_t _req_span_size_impl(mapping const& self) noexcept { - // assumes no negative strides; not sure if I'm allowed to assume that or not - return __impl::_call_op_impl(self, (self.extents().template __extent() - 1)...) + 1; - } - - template - MDSPAN_INLINE_FUNCTION - static constexpr const __strides_storage_t fill_strides(const OtherMapping& map) { - return __strides_storage_t{static_cast(map.stride(Idxs))...}; - } - - MDSPAN_INLINE_FUNCTION - static constexpr const __strides_storage_t& fill_strides(const __strides_storage_t& s) { - return s; - } - - template - MDSPAN_INLINE_FUNCTION - static constexpr const __strides_storage_t fill_strides(const std::array& s) { - return __strides_storage_t{static_cast(s[Idxs])...}; - } - - template - MDSPAN_INLINE_FUNCTION - static constexpr const __strides_storage_t fill_strides(mdspan_non_standard_tag, const IntegralType (&s)[extents_type::rank()]) { - return __strides_storage_t{static_cast(s[Idxs])...}; - } - -#ifdef __cpp_lib_span - template - MDSPAN_INLINE_FUNCTION - static constexpr const __strides_storage_t fill_strides(const std::span& s) { - return __strides_storage_t{static_cast(s[Idxs])...}; - } -#endif - - MDSPAN_INLINE_FUNCTION - static constexpr std::array return_strides(const __strides_storage_t& s) { - return std::array{s[Idxs]...}; - } - - template - MDSPAN_INLINE_FUNCTION - static constexpr size_t __return_zero() { return 0; } - - template - MDSPAN_INLINE_FUNCTION - static constexpr typename Mapping::index_type - __OFFSET(const Mapping& m) { return m(__return_zero()...); } - }; - - // Can't use defaulted parameter in the __deduction_workaround template because of a bug in MSVC warning C4348. - using __impl = __deduction_workaround>; - - static constexpr __strides_storage_t strides_storage(detail::with_rank<0>) { - return {}; - } - template - static constexpr __strides_storage_t strides_storage(detail::with_rank) { - __strides_storage_t s{}; - - extents_type e; - index_type stride = 1; - for(int r = static_cast(extents_type::rank() - 1); r >= 0; r--) { - s[r] = stride; - stride *= e.extent(r); - } - - return s; - } - - //---------------------------------------------------------------------------- - -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - MDSPAN_INLINE_FUNCTION constexpr explicit - mapping(__member_pair_t&& __m) : __members(::std::move(__m)) {} -#else - MDSPAN_INLINE_FUNCTION constexpr explicit - mapping(__base_t&& __b) : __base_t(::std::move(__b)) {} -#endif - - public: - - //-------------------------------------------------------------------------------- - - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping() noexcept -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - : __members{ -#else - : __base_t(__base_t{__member_pair_t( -#endif - extents_type(), - __strides_storage_t(strides_storage(detail::with_rank{})) -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - } -#else - )}) -#endif - {} - - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(mapping const&) noexcept = default; - - MDSPAN_TEMPLATE_REQUIRES( - class IntegralTypes, - /* requires */ ( - // MSVC 19.32 does not like using index_type here, requires the typename Extents::index_type - // error C2641: cannot deduce template arguments for 'MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride::mapping' - _MDSPAN_TRAIT(std::is_convertible, const std::remove_const_t&, typename Extents::index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, typename Extents::index_type, const std::remove_const_t&) - ) - ) - MDSPAN_INLINE_FUNCTION - constexpr - mapping( - extents_type const& e, - std::array const& s - ) noexcept -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - : __members{ -#else - : __base_t(__base_t{__member_pair_t( -#endif - e, __strides_storage_t(__impl::fill_strides(s)) -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - } -#else - )}) -#endif - { - /* - * TODO: check preconditions - * - s[i] > 0 is true for all i in the range [0, rank_ ). - * - REQUIRED-SPAN-SIZE(e, s) is a representable value of type index_type ([basic.fundamental]). - * - If rank_ is greater than 0, then there exists a permutation P of the integers in the - * range [0, rank_), such that s[ pi ] >= s[ pi − 1 ] * e.extent( pi − 1 ) is true for - * all i in the range [1, rank_ ), where pi is the ith element of P. - */ - } - - MDSPAN_TEMPLATE_REQUIRES( - class IntegralTypes, - /* requires */ ( - // MSVC 19.32 does not like using index_type here, requires the typename Extents::index_type - // error C2641: cannot deduce template arguments for 'MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride::mapping' - _MDSPAN_TRAIT(std::is_convertible, const std::remove_const_t&, typename Extents::index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, typename Extents::index_type, const std::remove_const_t&) - ) - ) - MDSPAN_INLINE_FUNCTION - constexpr - mapping( - mdspan_non_standard_tag, - extents_type const& e, - IntegralTypes (&s)[extents_type::rank()] - ) noexcept -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - : __members{ -#else - : __base_t(__base_t{__member_pair_t( -#endif - e, __strides_storage_t(__impl::fill_strides(mdspan_non_standard, s)) -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - } -#else - )}) -#endif - { - /* - * TODO: check preconditions - * - s[i] > 0 is true for all i in the range [0, rank_ ). - * - REQUIRED-SPAN-SIZE(e, s) is a representable value of type index_type ([basic.fundamental]). - * - If rank_ is greater than 0, then there exists a permutation P of the integers in the - * range [0, rank_), such that s[ pi ] >= s[ pi − 1 ] * e.extent( pi − 1 ) is true for - * all i in the range [1, rank_ ), where pi is the ith element of P. - */ - } - -#ifdef __cpp_lib_span - MDSPAN_TEMPLATE_REQUIRES( - class IntegralTypes, - /* requires */ ( - // MSVC 19.32 does not like using index_type here, requires the typename Extents::index_type - // error C2641: cannot deduce template arguments for 'MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride::mapping' - _MDSPAN_TRAIT(std::is_convertible, const std::remove_const_t&, typename Extents::index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, typename Extents::index_type, const std::remove_const_t&) - ) - ) - MDSPAN_INLINE_FUNCTION - constexpr - mapping( - extents_type const& e, - std::span const& s - ) noexcept -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - : __members{ -#else - : __base_t(__base_t{__member_pair_t( -#endif - e, __strides_storage_t(__impl::fill_strides(s)) -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - } -#else - )}) -#endif - { - /* - * TODO: check preconditions - * - s[i] > 0 is true for all i in the range [0, rank_ ). - * - REQUIRED-SPAN-SIZE(e, s) is a representable value of type index_type ([basic.fundamental]). - * - If rank_ is greater than 0, then there exists a permutation P of the integers in the - * range [0, rank_), such that s[ pi ] >= s[ pi − 1 ] * e.extent( pi − 1 ) is true for - * all i in the range [1, rank_ ), where pi is the ith element of P. - */ - } -#endif // __cpp_lib_span - -#if !(defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20) - MDSPAN_TEMPLATE_REQUIRES( - class StridedLayoutMapping, - /* requires */ ( - _MDSPAN_TRAIT(std::is_constructible, extents_type, typename StridedLayoutMapping::extents_type) && - detail::__is_mapping_of && - StridedLayoutMapping::is_always_unique() && - StridedLayoutMapping::is_always_strided() - ) - ) -#else - template - requires( - detail::__layout_mapping_alike && - _MDSPAN_TRAIT(std::is_constructible, extents_type, typename StridedLayoutMapping::extents_type) && - StridedLayoutMapping::is_always_unique() && - StridedLayoutMapping::is_always_strided() - ) -#endif - MDSPAN_CONDITIONAL_EXPLICIT( - !(std::is_convertible::value && - (detail::__is_mapping_of || - detail::__is_mapping_of || - detail::__is_mapping_of)) - ) // needs two () due to comma - MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 - mapping(StridedLayoutMapping const& other) noexcept // NOLINT(google-explicit-constructor) -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - : __members{ -#else - : __base_t(__base_t{__member_pair_t( -#endif - other.extents(), __strides_storage_t(__impl::fill_strides(other)) -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - } -#else - )}) -#endif - { - /* - * TODO: check preconditions - * - other.stride(i) > 0 is true for all i in the range [0, rank_ ). - * - other.required_span_size() is a representable value of type index_type ([basic.fundamental]). - * - OFFSET(other) == 0 - */ - } - - //-------------------------------------------------------------------------------- - - MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED - mapping& operator=(mapping const&) noexcept = default; - - MDSPAN_INLINE_FUNCTION constexpr const extents_type& extents() const noexcept { -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - return __members.__first(); -#else - return this->__base_t::__ref().__first(); -#endif - }; - - MDSPAN_INLINE_FUNCTION - constexpr std::array< index_type, extents_type::rank() > strides() const noexcept { - return __impl::return_strides(__strides_storage()); - } - - MDSPAN_INLINE_FUNCTION - constexpr index_type required_span_size() const noexcept { - index_type span_size = 1; - for(unsigned r = 0; r < extents_type::rank(); r++) { - // Return early if any of the extents are zero - if(extents().extent(r)==0) return 0; - span_size += ( static_cast(extents().extent(r) - 1 ) * __strides_storage()[r]); - } - return span_size; - } - - - MDSPAN_TEMPLATE_REQUIRES( - class... Indices, - /* requires */ ( - sizeof...(Indices) == Extents::rank() && - (detail::are_valid_indices()) - ) - ) - MDSPAN_FORCE_INLINE_FUNCTION - constexpr index_type operator()(Indices... idxs) const noexcept { -#if ! defined(NDEBUG) - detail::check_all_indices(this->extents(), idxs...); -#endif // ! NDEBUG - return static_cast(__impl::_call_op_impl(*this, static_cast(idxs)...)); - } - - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { - return false; - } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return true; } - - MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { return true; } - - private: - constexpr bool exhaustive_for_nonzero_span_size() const - { - return required_span_size() == __get_size(extents(), std::make_index_sequence()); - } - - constexpr bool is_exhaustive_impl(detail::with_rank<0>) const - { - return true; - } - constexpr bool is_exhaustive_impl(detail::with_rank<1>) const - { - if (required_span_size() != static_cast(0)) { - return exhaustive_for_nonzero_span_size(); - } - return stride(0) == 1; - } - template - constexpr bool is_exhaustive_impl(detail::with_rank) const - { - if (required_span_size() != static_cast(0)) { - return exhaustive_for_nonzero_span_size(); - } - - rank_type r_largest = 0; - for (rank_type r = 1; r < extents_type::rank(); r++) { - if (stride(r) > stride(r_largest)) { - r_largest = r; - } - } - for (rank_type r = 0; r < extents_type::rank(); r++) { - if (extents().extent(r) == 0 && r != r_largest) { - return false; - } - } - return true; - } - - public: - MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 bool is_exhaustive() const noexcept { - return is_exhaustive_impl(detail::with_rank{}); - } - MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { return true; } - - - MDSPAN_INLINE_FUNCTION - constexpr index_type stride(rank_type r) const noexcept { - return __strides_storage()[r]; - } - -#if !(defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20) - MDSPAN_TEMPLATE_REQUIRES( - class StridedLayoutMapping, - /* requires */ ( - detail::__is_mapping_of && - (extents_type::rank() == StridedLayoutMapping::extents_type::rank()) && - StridedLayoutMapping::is_always_strided() - ) - ) -#else - template - requires( - detail::__layout_mapping_alike && - (extents_type::rank() == StridedLayoutMapping::extents_type::rank()) && - StridedLayoutMapping::is_always_strided() - ) -#endif - MDSPAN_INLINE_FUNCTION - friend constexpr bool operator==(const mapping& x, const StridedLayoutMapping& y) noexcept { - return (x.extents() == y.extents()) && - (__impl::__OFFSET(y) == static_cast(0)) && - detail::rankwise_equal(detail::with_rank{}, x, y, detail::stride); - } - - // This one is not technically part of the proposal. Just here to make implementation a bit more optimal hopefully - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( - (extents_type::rank() == OtherExtents::rank()) - ) - ) - MDSPAN_INLINE_FUNCTION - friend constexpr bool operator==(mapping const& lhs, mapping const& rhs) noexcept { - return __impl::_eq_impl(lhs, rhs); - } - -#if !MDSPAN_HAS_CXX_20 - MDSPAN_TEMPLATE_REQUIRES( - class StridedLayoutMapping, - /* requires */ ( - detail::__is_mapping_of && - (extents_type::rank() == StridedLayoutMapping::extents_type::rank()) && - StridedLayoutMapping::is_always_strided() - ) - ) - MDSPAN_INLINE_FUNCTION - friend constexpr bool operator!=(const mapping& x, const StridedLayoutMapping& y) noexcept { - return not (x == y); - } - - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( - (extents_type::rank() == OtherExtents::rank()) - ) - ) - MDSPAN_INLINE_FUNCTION - friend constexpr bool operator!=(mapping const& lhs, mapping const& rhs) noexcept { - return __impl::_not_eq_impl(lhs, rhs); - } -#endif - - // [mdspan.submdspan.mapping], submdspan mapping specialization - template - MDSPAN_INLINE_FUNCTION - constexpr auto submdspan_mapping_impl( - SliceSpecifiers... slices) const; - - template - friend constexpr auto submdspan_mapping( - const mapping& src, SliceSpecifiers... slices) { - return src.submdspan_mapping_impl(slices...); - } - }; -}; - -namespace detail { - -template -constexpr void validate_strides(with_rank<0>, Layout, const Extents&, const Mapping&) -{} - -template -constexpr void validate_strides(with_rank, Layout, const Extents& ext, const Mapping& other) -{ - static_assert(std::is_same::value and - (std::is_same::value or - std::is_same::value) - , "This function is only intended to validate construction of " - "a layout_left or layout_right mapping from a layout_stride mapping."); - - constexpr auto is_left = std::is_same::value; - - typename Extents::index_type stride = 1; - - for (std::size_t r = 0; r < N; r++) { - const std::size_t s = is_left ? r : N - 1 - r; - - MDSPAN_IMPL_PRECONDITION(common_integral_compare(stride, other.stride(s)) - and "invalid strides for layout_{left,right}"); - - stride *= ext.extent(s); - } -} - -} // namespace detail -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/layout_stride.hpp -#if MDSPAN_HAS_CXX_17 -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2642_bits/layout_padded_fwd.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - -#include - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace MDSPAN_IMPL_PROPOSED_NAMESPACE { - -template -struct layout_left_padded { - template - class mapping; -}; - -template -struct layout_right_padded { - template - class mapping; -}; - -namespace detail { -// The layout_padded_constants structs are only useful if rank > 1, otherwise they may wrap -template -struct layout_padded_constants; - -template -struct layout_padded_constants, _ExtentsType> -{ - using rank_type = typename _ExtentsType::rank_type; - static constexpr rank_type padded_stride_idx = 1; - static constexpr rank_type extent_to_pad_idx = 0; -}; - -template -struct layout_padded_constants, _ExtentsType> -{ - using rank_type = typename _ExtentsType::rank_type; - static constexpr rank_type padded_stride_idx = _ExtentsType::rank() - 2; - static constexpr rank_type extent_to_pad_idx = _ExtentsType::rank() - 1; -}; - -template -struct is_layout_left_padded : std::false_type {}; - -template -struct is_layout_left_padded> : std::true_type {}; - -template -struct is_layout_left_padded_mapping : std::false_type {}; - -template -struct is_layout_left_padded_mapping<_Mapping, - std::enable_if_t::template mapping>::value>> - : std::true_type {}; - -template -struct is_layout_right_padded : std::false_type {}; - -template -struct is_layout_right_padded> : std::true_type {}; - -template -struct is_layout_right_padded_mapping : std::false_type {}; - -template -struct is_layout_right_padded_mapping<_Mapping, - std::enable_if_t::template mapping>::value>> - : std::true_type {}; - - -template -constexpr void check_padded_layout_converting_constructor_mandates(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<0>) {} - -template -constexpr void check_padded_layout_converting_constructor_mandates(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<1>) {} - -template -constexpr void check_padded_layout_converting_constructor_mandates(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank) -{ - using extents_type = typename _PaddedLayoutMappingType::extents_type; - constexpr auto padding_value = _PaddedLayoutMappingType::padding_value; - constexpr auto idx = layout_padded_constants::extent_to_pad_idx; - - constexpr auto statically_determinable = - (_LayoutExtentsType::static_extent(idx) != dynamic_extent) && - (extents_type::static_extent(idx) != dynamic_extent) && - (padding_value != dynamic_extent); - - static_assert(not statically_determinable or - (padding_value == 0 - ? _LayoutExtentsType::static_extent(idx) == 0 - : _LayoutExtentsType::static_extent(idx) % padding_value == 0), - ""); -} - -template -constexpr void check_padded_layout_converting_constructor_preconditions(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<0>, - const _OtherMapping&) {} -template -constexpr void check_padded_layout_converting_constructor_preconditions(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<1>, - const _OtherMapping&) {} -template -constexpr void check_padded_layout_converting_constructor_preconditions(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank, - const _OtherMapping &other_mapping) { - constexpr auto padded_stride_idx = - layout_padded_constants::padded_stride_idx; - constexpr auto extent_to_pad_idx = layout_padded_constants::extent_to_pad_idx; - MDSPAN_IMPL_PRECONDITION(other_mapping.stride(padded_stride_idx) == other_mapping.extents().extent(extent_to_pad_idx)); -} - - -} -} -} -//END_FILE_INCLUDE: mdspan/include/experimental/__p2642_bits/layout_padded_fwd.hpp -#endif - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -//============================================================================== -template -class layout_right::mapping { - public: - using extents_type = Extents; - using index_type = typename extents_type::index_type; - using size_type = typename extents_type::size_type; - using rank_type = typename extents_type::rank_type; - using layout_type = layout_right; - private: - - static_assert(detail::__is_extents_v, - MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::layout_right::mapping must be instantiated with a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); - - template - friend class mapping; - - // i0+(i1 + E(1)*(i2 + E(2)*i3)) - template - struct __rank_count {}; - - template - _MDSPAN_HOST_DEVICE - constexpr index_type __compute_offset( - index_type offset, __rank_count, const I& i, Indices... idx) const { - return __compute_offset(offset * __extents.extent(r) + i,__rank_count(), idx...); - } - - template - _MDSPAN_HOST_DEVICE - constexpr index_type __compute_offset( - __rank_count<0,extents_type::rank()>, const I& i, Indices... idx) const { - return __compute_offset(i,__rank_count<1,extents_type::rank()>(),idx...); - } - - _MDSPAN_HOST_DEVICE - constexpr index_type __compute_offset(size_t offset, __rank_count) const { - return static_cast(offset); - } - - _MDSPAN_HOST_DEVICE - constexpr index_type __compute_offset(__rank_count<0,0>) const { return 0; } - - public: - - //-------------------------------------------------------------------------------- - - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping() noexcept = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(mapping const&) noexcept = default; - - _MDSPAN_HOST_DEVICE - constexpr mapping(extents_type const& __exts) noexcept - :__extents(__exts) - { } - - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( - _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma - MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 - mapping(mapping const& other) noexcept // NOLINT(google-explicit-constructor) - :__extents(other.extents()) - { - /* - * TODO: check precondition - * other.required_span_size() is a representable value of type index_type - */ - } - - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( - _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) && - (extents_type::rank() <= 1) - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma - MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 - mapping(layout_left::mapping const& other) noexcept // NOLINT(google-explicit-constructor) - :__extents(other.extents()) - { - /* - * TODO: check precondition - * other.required_span_size() is a representable value of type index_type - */ - } - - /** - * Converting constructor from `layout_right_padded::mapping`. - * - * This overload participates in overload resolution only if _Mapping is a layout_right_padded mapping and - * extents_type is constructible from _Mapping::extents_type. - * - * \note There is currently a difference from p2642r2, where this function is specified as taking - * `layout_right_padded< padding_value >::mapping< Extents>`. However, this makes `padding_value` non-deducible. - */ -#if MDSPAN_HAS_CXX_17 - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ ( - MDSPAN_IMPL_PROPOSED_NAMESPACE::detail::is_layout_right_padded_mapping<_Mapping>::value - && std::is_constructible_v)) - MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible_v)) - mapping(const _Mapping &__other) noexcept - : __extents(__other.extents()) - { - MDSPAN_IMPL_PROPOSED_NAMESPACE::detail:: - check_padded_layout_converting_constructor_mandates< - extents_type, _Mapping>(detail::with_rank{}); - MDSPAN_IMPL_PROPOSED_NAMESPACE::detail:: - check_padded_layout_converting_constructor_preconditions< - extents_type>(detail::with_rank{}, __other); - } -#endif - - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( - _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0)) - MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 - mapping(layout_stride::mapping const& other) noexcept // NOLINT(google-explicit-constructor) - :__extents(other.extents()) - { - /* - * TODO: check precondition - * other.required_span_size() is a representable value of type index_type - */ - detail::validate_strides(detail::with_rank{}, layout_right{}, __extents, other); - } - - MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mapping& operator=(mapping const&) noexcept = default; - - MDSPAN_INLINE_FUNCTION - constexpr const extents_type& extents() const noexcept { - return __extents; - } - - MDSPAN_INLINE_FUNCTION - constexpr index_type required_span_size() const noexcept { - index_type value = 1; - for(rank_type r=0; r != extents_type::rank(); ++r) value*=__extents.extent(r); - return value; - } - - //-------------------------------------------------------------------------------- - - MDSPAN_TEMPLATE_REQUIRES( - class ... Indices, - /* requires */ ( - (sizeof...(Indices) == extents_type::rank()) && - (detail::are_valid_indices()) - ) - ) - _MDSPAN_HOST_DEVICE - constexpr index_type operator()(Indices... idxs) const noexcept { -#if ! defined(NDEBUG) - detail::check_all_indices(this->extents(), idxs...); -#endif // ! NDEBUG - return __compute_offset(__rank_count<0, extents_type::rank()>(), static_cast(idxs)...); - } - - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_exhaustive() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { return true; } - - MDSPAN_INLINE_FUNCTION - constexpr index_type stride(rank_type i) const noexcept -#if MDSPAN_HAS_CXX_20 - requires ( Extents::rank() > 0 ) -#endif - { - index_type value = 1; - for(rank_type r=extents_type::rank()-1; r>i; r--) value*=__extents.extent(r); - return value; - } - - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( Extents::rank() == OtherExtents::rank()) - ) - MDSPAN_INLINE_FUNCTION - friend constexpr bool operator==(mapping const& lhs, mapping const& rhs) noexcept { - return lhs.extents() == rhs.extents(); - } - - // In C++ 20 the not equal exists if equal is found -#if !(MDSPAN_HAS_CXX_20) - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ (Extents::rank() == OtherExtents::rank()) - ) - MDSPAN_INLINE_FUNCTION - friend constexpr bool operator!=(mapping const& lhs, mapping const& rhs) noexcept { - return lhs.extents() != rhs.extents(); - } -#endif - - // Not really public, but currently needed to implement fully constexpr useable submdspan: - template - constexpr index_type __get_stride(MDSPAN_IMPL_STANDARD_NAMESPACE::extents,std::integer_sequence) const { - return _MDSPAN_FOLD_TIMES_RIGHT((Idx>N? __extents.template __extent():1),1); - } - template - constexpr index_type __stride() const noexcept { - return __get_stride(__extents, std::make_index_sequence()); - } - -private: - _MDSPAN_NO_UNIQUE_ADDRESS extents_type __extents{}; - - // [mdspan.submdspan.mapping], submdspan mapping specialization - template - MDSPAN_INLINE_FUNCTION - constexpr auto submdspan_mapping_impl( - SliceSpecifiers... slices) const; - - template - friend constexpr auto submdspan_mapping( - const mapping& src, SliceSpecifiers... slices) { - return src.submdspan_mapping_impl(slices...); - } -}; - -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/layout_right.hpp - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -template < - class ElementType, - class Extents, - class LayoutPolicy = layout_right, - class AccessorPolicy = default_accessor -> -class mdspan -{ -private: - static_assert(detail::__is_extents_v, - MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::mdspan's Extents template parameter must be a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); - static_assert(std::is_same::value, - MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::mdspan's ElementType template parameter must be the same as its AccessorPolicy::element_type."); - - // Workaround for non-deducibility of the index sequence template parameter if it's given at the top level - template - struct __deduction_workaround; - - template - struct __deduction_workaround> - { - MDSPAN_FORCE_INLINE_FUNCTION static constexpr - size_t __size(mdspan const& __self) noexcept { - return _MDSPAN_FOLD_TIMES_RIGHT((__self.__mapping_ref().extents().extent(Idxs)), /* * ... * */ size_t(1)); - } - MDSPAN_FORCE_INLINE_FUNCTION static constexpr - bool __empty(mdspan const& __self) noexcept { - return (__self.rank()>0) && _MDSPAN_FOLD_OR((__self.__mapping_ref().extents().extent(Idxs)==index_type(0))); - } - template - MDSPAN_FORCE_INLINE_FUNCTION static constexpr - ReferenceType __callop(mdspan const& __self, const std::array& indices) noexcept { - return __self.__accessor_ref().access(__self.__ptr_ref(), __self.__mapping_ref()(indices[Idxs]...)); - } -#ifdef __cpp_lib_span - template - MDSPAN_FORCE_INLINE_FUNCTION static constexpr - ReferenceType __callop(mdspan const& __self, const std::span& indices) noexcept { - return __self.__accessor_ref().access(__self.__ptr_ref(), __self.__mapping_ref()(indices[Idxs]...)); - } -#endif - }; - -public: - - //-------------------------------------------------------------------------------- - // Domain and codomain types - - using extents_type = Extents; - using layout_type = LayoutPolicy; - using accessor_type = AccessorPolicy; - using mapping_type = typename layout_type::template mapping; - using element_type = ElementType; - using value_type = std::remove_cv_t; - using index_type = typename extents_type::index_type; - using size_type = typename extents_type::size_type; - using rank_type = typename extents_type::rank_type; - using data_handle_type = typename accessor_type::data_handle_type; - using reference = typename accessor_type::reference; - - MDSPAN_INLINE_FUNCTION static constexpr size_t rank() noexcept { return extents_type::rank(); } - MDSPAN_INLINE_FUNCTION static constexpr size_t rank_dynamic() noexcept { return extents_type::rank_dynamic(); } - MDSPAN_INLINE_FUNCTION static constexpr size_t static_extent(size_t r) noexcept { return extents_type::static_extent(r); } - MDSPAN_INLINE_FUNCTION constexpr index_type extent(size_t r) const noexcept { return __mapping_ref().extents().extent(r); }; - -private: - - // Can't use defaulted parameter in the __deduction_workaround template because of a bug in MSVC warning C4348. - using __impl = __deduction_workaround>; - - using __map_acc_pair_t = detail::__compressed_pair; - -public: - - //-------------------------------------------------------------------------------- - // [mdspan.basic.cons], mdspan constructors, assignment, and destructor - -#if !MDSPAN_HAS_CXX_20 - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan() = default; -#else - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan() - requires( - // nvhpc has a bug where using just rank_dynamic() here doesn't work ... - (extents_type::rank_dynamic() > 0) && - _MDSPAN_TRAIT(std::is_default_constructible, data_handle_type) && - _MDSPAN_TRAIT(std::is_default_constructible, mapping_type) && - _MDSPAN_TRAIT(std::is_default_constructible, accessor_type) - ) = default; -#endif - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan(const mdspan&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan(mdspan&&) = default; - - MDSPAN_TEMPLATE_REQUIRES( - class... SizeTypes, - /* requires */ ( - ((sizeof...(SizeTypes) == rank()) || (sizeof...(SizeTypes) == rank_dynamic())) && - (detail::are_valid_indices()) && - _MDSPAN_TRAIT(std::is_constructible, mapping_type, extents_type) && - _MDSPAN_TRAIT(std::is_default_constructible, accessor_type) - ) - ) - MDSPAN_INLINE_FUNCTION - explicit constexpr mdspan(data_handle_type p, SizeTypes... dynamic_extents) - // TODO @proposal-bug shouldn't I be allowed to do `move(p)` here? - : __members(std::move(p), __map_acc_pair_t(mapping_type(extents_type(static_cast(std::move(dynamic_extents))...)), accessor_type())) - { } - - MDSPAN_TEMPLATE_REQUIRES( - class SizeType, size_t N, - /* requires */ ( - _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) && - ((N == rank()) || (N == rank_dynamic())) && - _MDSPAN_TRAIT(std::is_constructible, mapping_type, extents_type) && - _MDSPAN_TRAIT(std::is_default_constructible, accessor_type) - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT(N != rank_dynamic()) - MDSPAN_INLINE_FUNCTION - constexpr mdspan(data_handle_type p, const std::array& dynamic_extents) - : __members(std::move(p), __map_acc_pair_t(mapping_type(extents_type(dynamic_extents)), accessor_type())) - { } - -#ifdef __cpp_lib_span - MDSPAN_TEMPLATE_REQUIRES( - class SizeType, size_t N, - /* requires */ ( - _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) && - ((N == rank()) || (N == rank_dynamic())) && - _MDSPAN_TRAIT(std::is_constructible, mapping_type, extents_type) && - _MDSPAN_TRAIT(std::is_default_constructible, accessor_type) - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT(N != rank_dynamic()) - MDSPAN_INLINE_FUNCTION - constexpr mdspan(data_handle_type p, std::span dynamic_extents) - : __members(std::move(p), __map_acc_pair_t(mapping_type(extents_type(as_const(dynamic_extents))), accessor_type())) - { } -#endif - - MDSPAN_FUNCTION_REQUIRES( - (MDSPAN_INLINE_FUNCTION constexpr), - mdspan, (data_handle_type p, const extents_type& exts), , - /* requires */ (_MDSPAN_TRAIT(std::is_default_constructible, accessor_type) && - _MDSPAN_TRAIT(std::is_constructible, mapping_type, const extents_type&)) - ) : __members(std::move(p), __map_acc_pair_t(mapping_type(exts), accessor_type())) - { } - - MDSPAN_FUNCTION_REQUIRES( - (MDSPAN_INLINE_FUNCTION constexpr), - mdspan, (data_handle_type p, const mapping_type& m), , - /* requires */ (_MDSPAN_TRAIT(std::is_default_constructible, accessor_type)) - ) : __members(std::move(p), __map_acc_pair_t(m, accessor_type())) - { } - - MDSPAN_INLINE_FUNCTION - constexpr mdspan(data_handle_type p, const mapping_type& m, const accessor_type& a) - : __members(std::move(p), __map_acc_pair_t(m, a)) - { } - - MDSPAN_TEMPLATE_REQUIRES( - class OtherElementType, class OtherExtents, class OtherLayoutPolicy, class OtherAccessor, - /* requires */ ( - _MDSPAN_TRAIT(std::is_constructible, mapping_type, const typename OtherLayoutPolicy::template mapping&) && - _MDSPAN_TRAIT(std::is_constructible, accessor_type, const OtherAccessor&) - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT( - !_MDSPAN_TRAIT(std::is_convertible, const typename OtherLayoutPolicy::template mapping&, mapping_type) || - !_MDSPAN_TRAIT(std::is_convertible, const OtherAccessor&, accessor_type) - ) - MDSPAN_INLINE_FUNCTION - constexpr mdspan(const mdspan& other) - : __members(other.__ptr_ref(), __map_acc_pair_t(other.__mapping_ref(), other.__accessor_ref())) - { - static_assert(_MDSPAN_TRAIT(std::is_constructible, data_handle_type, typename OtherAccessor::data_handle_type),"Incompatible data_handle_type for mdspan construction"); - static_assert(_MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents),"Incompatible extents for mdspan construction"); - /* - * TODO: Check precondition - * For each rank index r of extents_type, static_extent(r) == dynamic_extent || static_extent(r) == other.extent(r) is true. - */ - } - - /* Might need this on NVIDIA? - MDSPAN_INLINE_FUNCTION_DEFAULTED - ~mdspan() = default; - */ - - MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mdspan& operator=(const mdspan&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mdspan& operator=(mdspan&&) = default; - - - //-------------------------------------------------------------------------------- - // [mdspan.basic.mapping], mdspan mapping domain multidimensional index to access codomain element - - #if MDSPAN_USE_BRACKET_OPERATOR - MDSPAN_TEMPLATE_REQUIRES( - class... SizeTypes, - /* requires */ ( - _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_convertible, SizeTypes, index_type) /* && ... */) && - _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, SizeTypes) /* && ... */) && - (rank() == sizeof...(SizeTypes)) - ) - ) - MDSPAN_FORCE_INLINE_FUNCTION - constexpr reference operator[](SizeTypes... indices) const - { - return __accessor_ref().access(__ptr_ref(), __mapping_ref()(static_cast(std::move(indices))...)); - } - #endif - - MDSPAN_TEMPLATE_REQUIRES( - class SizeType, - /* requires */ ( - _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) - ) - ) - MDSPAN_FORCE_INLINE_FUNCTION - constexpr reference operator[](const std::array< SizeType, rank()>& indices) const - { - return __impl::template __callop(*this, indices); - } - - #ifdef __cpp_lib_span - MDSPAN_TEMPLATE_REQUIRES( - class SizeType, - /* requires */ ( - _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) - ) - ) - MDSPAN_FORCE_INLINE_FUNCTION - constexpr reference operator[](std::span indices) const - { - return __impl::template __callop(*this, indices); - } - #endif // __cpp_lib_span - - #if !MDSPAN_USE_BRACKET_OPERATOR - MDSPAN_TEMPLATE_REQUIRES( - class Index, - /* requires */ ( - _MDSPAN_TRAIT(std::is_convertible, Index, index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, Index) && - extents_type::rank() == 1 - ) - ) - MDSPAN_FORCE_INLINE_FUNCTION - constexpr reference operator[](Index idx) const - { - return __accessor_ref().access(__ptr_ref(), __mapping_ref()(static_cast(std::move(idx)))); - } - #endif - - #if MDSPAN_USE_PAREN_OPERATOR - MDSPAN_TEMPLATE_REQUIRES( - class... SizeTypes, - /* requires */ ( - extents_type::rank() == sizeof...(SizeTypes) && - (detail::are_valid_indices()) - ) - ) - MDSPAN_FORCE_INLINE_FUNCTION - constexpr reference operator()(SizeTypes... indices) const - { - return __accessor_ref().access(__ptr_ref(), __mapping_ref()(static_cast(std::move(indices))...)); - } - - MDSPAN_TEMPLATE_REQUIRES( - class SizeType, - /* requires */ ( - _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) - ) - ) - MDSPAN_FORCE_INLINE_FUNCTION - constexpr reference operator()(const std::array& indices) const - { - return __impl::template __callop(*this, indices); - } - - #ifdef __cpp_lib_span - MDSPAN_TEMPLATE_REQUIRES( - class SizeType, - /* requires */ ( - _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) - ) - ) - MDSPAN_FORCE_INLINE_FUNCTION - constexpr reference operator()(std::span indices) const - { - return __impl::template __callop(*this, indices); - } - #endif // __cpp_lib_span - #endif // MDSPAN_USE_PAREN_OPERATOR - - MDSPAN_INLINE_FUNCTION constexpr size_type size() const noexcept { - return __impl::__size(*this); - }; - - MDSPAN_INLINE_FUNCTION constexpr bool empty() const noexcept { - return __impl::__empty(*this); - }; - - MDSPAN_INLINE_FUNCTION - friend constexpr void swap(mdspan& x, mdspan& y) noexcept { - // can't call the std::swap inside on HIP - #if !defined(_MDSPAN_HAS_HIP) && !defined(_MDSPAN_HAS_CUDA) - using std::swap; - swap(x.__ptr_ref(), y.__ptr_ref()); - swap(x.__mapping_ref(), y.__mapping_ref()); - swap(x.__accessor_ref(), y.__accessor_ref()); - #else - mdspan tmp = y; - y = x; - x = tmp; - #endif - } - - //-------------------------------------------------------------------------------- - // [mdspan.basic.domobs], mdspan observers of the domain multidimensional index space - - - MDSPAN_INLINE_FUNCTION constexpr const extents_type& extents() const noexcept { return __mapping_ref().extents(); }; - MDSPAN_INLINE_FUNCTION constexpr const data_handle_type& data_handle() const noexcept { return __ptr_ref(); }; - MDSPAN_INLINE_FUNCTION constexpr const mapping_type& mapping() const noexcept { return __mapping_ref(); }; - MDSPAN_INLINE_FUNCTION constexpr const accessor_type& accessor() const noexcept { return __accessor_ref(); }; - - //-------------------------------------------------------------------------------- - // [mdspan.basic.obs], mdspan observers of the mapping - - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() { return mapping_type::is_always_unique(); }; - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() { return mapping_type::is_always_exhaustive(); }; - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() { return mapping_type::is_always_strided(); }; - - MDSPAN_INLINE_FUNCTION constexpr bool is_unique() const { return __mapping_ref().is_unique(); }; - MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const { return __mapping_ref().is_exhaustive(); }; - MDSPAN_INLINE_FUNCTION constexpr bool is_strided() const { return __mapping_ref().is_strided(); }; - MDSPAN_INLINE_FUNCTION constexpr index_type stride(size_t r) const { return __mapping_ref().stride(r); }; - -private: - - detail::__compressed_pair __members{}; - - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 data_handle_type& __ptr_ref() noexcept { return __members.__first(); } - MDSPAN_FORCE_INLINE_FUNCTION constexpr data_handle_type const& __ptr_ref() const noexcept { return __members.__first(); } - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 mapping_type& __mapping_ref() noexcept { return __members.__second().__first(); } - MDSPAN_FORCE_INLINE_FUNCTION constexpr mapping_type const& __mapping_ref() const noexcept { return __members.__second().__first(); } - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 accessor_type& __accessor_ref() noexcept { return __members.__second().__second(); } - MDSPAN_FORCE_INLINE_FUNCTION constexpr accessor_type const& __accessor_ref() const noexcept { return __members.__second().__second(); } - - template - friend class mdspan; - -}; - -#if defined(_MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION) -MDSPAN_TEMPLATE_REQUIRES( - class ElementType, class... SizeTypes, - /* requires */ _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_convertible, SizeTypes, size_t) /* && ... */) && - (sizeof...(SizeTypes) > 0) -) -MDSPAN_DEDUCTION_GUIDE explicit mdspan(ElementType*, SizeTypes...) - -> mdspan>; - -MDSPAN_TEMPLATE_REQUIRES( - class Pointer, - (_MDSPAN_TRAIT(std::is_pointer, std::remove_reference_t)) -) -MDSPAN_DEDUCTION_GUIDE mdspan(Pointer&&) -> mdspan>, extents>; - -MDSPAN_TEMPLATE_REQUIRES( - class CArray, - (_MDSPAN_TRAIT(std::is_array, CArray) && (std::rank_v == 1)) -) -MDSPAN_DEDUCTION_GUIDE mdspan(CArray&) -> mdspan, extents>>; - -template -MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, const ::std::array&) - -> mdspan>; - -#ifdef __cpp_lib_span -template -MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, ::std::span) - -> mdspan>; -#endif - -// This one is necessary because all the constructors take `data_handle_type`s, not -// `ElementType*`s, and `data_handle_type` is taken from `accessor_type::data_handle_type`, which -// seems to throw off automatic deduction guides. -template -MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, const extents&) - -> mdspan>; - -template -MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, const MappingType&) - -> mdspan; - -template -MDSPAN_DEDUCTION_GUIDE mdspan(const typename AccessorType::data_handle_type, const MappingType&, const AccessorType&) - -> mdspan; -#endif - -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/mdspan.hpp -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/layout_left.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - -#if MDSPAN_HAS_CXX_17 -#endif -#include - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -//============================================================================== - -template -class layout_left::mapping { - public: - using extents_type = Extents; - using index_type = typename extents_type::index_type; - using size_type = typename extents_type::size_type; - using rank_type = typename extents_type::rank_type; - using layout_type = layout_left; - private: - - static_assert(detail::__is_extents_v, - MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::layout_left::mapping must be instantiated with a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); - - template - friend class mapping; - - // i0+(i1 + E(1)*(i2 + E(2)*i3)) - template - struct __rank_count {}; - - template - _MDSPAN_HOST_DEVICE - constexpr index_type __compute_offset( - __rank_count, const I& i, Indices... idx) const { - return __compute_offset(__rank_count(), idx...) * - __extents.extent(r) + i; - } - - template - _MDSPAN_HOST_DEVICE - constexpr index_type __compute_offset( - __rank_count, const I& i) const { - return i; - } - - _MDSPAN_HOST_DEVICE - constexpr index_type __compute_offset(__rank_count<0,0>) const { return 0; } - - public: - - //-------------------------------------------------------------------------------- - - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping() noexcept = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(mapping const&) noexcept = default; - - _MDSPAN_HOST_DEVICE - constexpr mapping(extents_type const& __exts) noexcept - :__extents(__exts) - { } - - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( - _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma - MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 - mapping(mapping const& other) noexcept // NOLINT(google-explicit-constructor) - :__extents(other.extents()) - { - /* - * TODO: check precondition - * other.required_span_size() is a representable value of type index_type - */ - } - - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( - _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) && - (extents_type::rank() <= 1) - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma - MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 - mapping(layout_right::mapping const& other) noexcept // NOLINT(google-explicit-constructor) - :__extents(other.extents()) - { - /* - * TODO: check precondition - * other.required_span_size() is a representable value of type index_type - */ - } - -#if MDSPAN_HAS_CXX_17 - /** - * Converting constructor from `layout_left_padded::mapping`. - * - * This overload participates in overload resolution only if _Mapping is a layout_left_padded mapping and - * extents_type is constructible from _Mapping::extents_type. - * - * \note There is currently a difference from p2642r2, where this function is specified as taking - * `layout_left_padded< padding_value >::mapping< Extents>`. However, this makes `padding_value` non-deducible. - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ ( - MDSPAN_IMPL_PROPOSED_NAMESPACE::detail::is_layout_left_padded_mapping<_Mapping>::value - && std::is_constructible_v - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible_v)) - mapping(const _Mapping& __other) noexcept - : __extents(__other.extents()) - { - MDSPAN_IMPL_PROPOSED_NAMESPACE::detail:: - check_padded_layout_converting_constructor_mandates< - extents_type, _Mapping>(detail::with_rank{}); - MDSPAN_IMPL_PROPOSED_NAMESPACE::detail:: - check_padded_layout_converting_constructor_preconditions< - extents_type>(detail::with_rank{}, __other); - } -#endif - - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( - _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0)) - MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 - mapping(layout_stride::mapping const& other) noexcept // NOLINT(google-explicit-constructor) - :__extents(other.extents()) - { - /* - * TODO: check precondition - * other.required_span_size() is a representable value of type index_type - */ - detail::validate_strides(detail::with_rank{}, layout_left{}, __extents, other); - } - - MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mapping& operator=(mapping const&) noexcept = default; - - MDSPAN_INLINE_FUNCTION - constexpr const extents_type& extents() const noexcept { - return __extents; - } - - MDSPAN_INLINE_FUNCTION - constexpr index_type required_span_size() const noexcept { - index_type value = 1; - for(rank_type r=0; r()) - ) - ) - _MDSPAN_HOST_DEVICE - constexpr index_type operator()(Indices... idxs) const noexcept { -#if ! defined(NDEBUG) - detail::check_all_indices(this->extents(), idxs...); -#endif // ! NDEBUG - return __compute_offset(__rank_count<0, extents_type::rank()>(), static_cast(idxs)...); - } - - - - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return true; } - - MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_exhaustive() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { return true; } - - MDSPAN_INLINE_FUNCTION - constexpr index_type stride(rank_type i) const noexcept -#if MDSPAN_HAS_CXX_20 - requires ( Extents::rank() > 0 ) -#endif - { - index_type value = 1; - for(rank_type r=0; r const& rhs) noexcept { - return lhs.extents() == rhs.extents(); - } - - // In C++ 20 the not equal exists if equal is found -#if !(MDSPAN_HAS_CXX_20) - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( Extents::rank() == OtherExtents::rank()) - ) - MDSPAN_INLINE_FUNCTION - friend constexpr bool operator!=(mapping const& lhs, mapping const& rhs) noexcept { - return lhs.extents() != rhs.extents(); - } -#endif - - // Not really public, but currently needed to implement fully constexpr useable submdspan: - template - constexpr index_type __get_stride(MDSPAN_IMPL_STANDARD_NAMESPACE::extents,std::integer_sequence) const { - return _MDSPAN_FOLD_TIMES_RIGHT((Idx():1),1); - } - template - constexpr index_type __stride() const noexcept { - return __get_stride(__extents, std::make_index_sequence()); - } - -private: - _MDSPAN_NO_UNIQUE_ADDRESS extents_type __extents{}; - - // [mdspan.submdspan.mapping], submdspan mapping specialization - template - MDSPAN_INLINE_FUNCTION - constexpr auto submdspan_mapping_impl( - SliceSpecifiers... slices) const; - - template - friend constexpr auto submdspan_mapping( - const mapping& src, SliceSpecifiers... slices) { - return src.submdspan_mapping_impl(slices...); - } -}; - - -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/layout_left.hpp -#if MDSPAN_HAS_CXX_17 -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2642_bits/layout_padded.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - -#include - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace MDSPAN_IMPL_PROPOSED_NAMESPACE { - -namespace detail { -template -MDSPAN_INLINE_FUNCTION -constexpr _T -find_next_multiple(_T alignment, _T offset) -{ - if ( alignment == 0 ) { - return _T(0); - } else { - return ( ( offset + alignment - 1 ) / alignment) * alignment; - } -} - -template -MDSPAN_INLINE_FUNCTION constexpr size_t get_actual_static_padding_value() { - constexpr auto rank = _ExtentsType::rank(); - - if constexpr (rank <= typename _ExtentsType::rank_type(1)) { - return 0; - } else if constexpr (_PaddingValue != dynamic_extent && - _ExtentsType::static_extent(_ExtentToPadIdx) != - dynamic_extent) { - static_assert( - (_PaddingValue != 0) || - (_ExtentsType::static_extent(_ExtentToPadIdx) == 0), - "padding stride can be 0 only if " - "extents_type::static_extent(extent-to-pad) is 0 or dynamic_extent"); - return find_next_multiple(_PaddingValue, - _ExtentsType::static_extent(_ExtentToPadIdx)); - } else { - return dynamic_extent; - } - // Missing return statement warning from NVCC -#ifdef __NVCC__ - return 0; -#endif -} - -template -struct static_array_type_for_padded_extent -{ - static constexpr size_t padding_value = _PaddingValue; - using index_type = typename _Extents::index_type; - using extents_type = _Extents; - using type = ::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::maybe_static_array< - index_type, size_t, dynamic_extent, - detail::get_actual_static_padding_value()>; -}; - -template -struct static_array_type_for_padded_extent<_PaddingValue, _Extents, - _ExtentToPadIdx, Rank, std::enable_if_t> { - using index_type = typename _Extents::index_type; - using extents_type = _Extents; - using type = - ::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::maybe_static_array< - index_type, size_t, dynamic_extent, 0>; -}; - -template -struct padded_extent { - static constexpr size_t padding_value = _PaddingValue; - using index_type = typename _Extents::index_type; - using extents_type = _Extents; - using static_array_type = typename static_array_type_for_padded_extent< - padding_value, _Extents, _ExtentToPadIdx, _Extents::rank()>::type; - - static constexpr auto static_value() { return static_array_type::static_value(0); } - - MDSPAN_INLINE_FUNCTION - static constexpr static_array_type - init_padding(const _Extents &exts) { - if constexpr ((_Extents::rank() > 1) && (padding_value == dynamic_extent)) { - return {exts.extent(_ExtentToPadIdx)}; - } else { - return init_padding(exts, padding_value); - } - // Missing return statement warning from NVCC -#ifdef __NVCC__ - return {}; -#endif - } - - MDSPAN_INLINE_FUNCTION static constexpr static_array_type - init_padding([[maybe_unused]] const _Extents &exts, - [[maybe_unused]] index_type pv) { - if constexpr (_Extents::rank() > 1) { - return {find_next_multiple(pv, - exts.extent(_ExtentToPadIdx))}; - } else { - return {}; - } - // Missing return statement warning from NVCC -#ifdef __NVCC__ - return {}; -#endif - } - - template - MDSPAN_INLINE_FUNCTION static constexpr static_array_type - init_padding([[maybe_unused]] const _Mapping &other_mapping, - std::integral_constant) { - if constexpr (_Extents::rank() > 1) { - return {other_mapping.stride(_PaddingStrideIdx)}; - } else { - return {}; - } - // Missing return statement warning from NVCC -#ifdef __NVCC__ - return {}; -#endif - } -}; -} // namespace detail - -template -template -class layout_left_padded::mapping { -public: - static constexpr size_t padding_value = PaddingValue; - - using extents_type = Extents; - using index_type = typename extents_type::index_type; - using size_type = typename extents_type::size_type; - using rank_type = typename extents_type::rank_type; - using layout_type = layout_left_padded; - -#ifndef MDSPAN_INTERNAL_TEST -private: -#endif // MDSPAN_INTERNAL_TEST - - static constexpr rank_type padded_stride_idx = detail::layout_padded_constants::padded_stride_idx; - static constexpr rank_type extent_to_pad_idx = detail::layout_padded_constants::extent_to_pad_idx; - - static_assert((padding_value != 0) - || (extents_type::static_extent(extent_to_pad_idx) == 0) - || (extents_type::static_extent(extent_to_pad_idx) == dynamic_extent), - "out of bounds access for rank 0"); - - using padded_stride_type = detail::padded_extent< padding_value, extents_type, extent_to_pad_idx >; - - static constexpr size_t static_padding_stride = padded_stride_type::static_value(); - - typename padded_stride_type::static_array_type padded_stride = {}; - extents_type exts = {}; - - MDSPAN_INLINE_FUNCTION constexpr index_type - compute_offset(std::index_sequence<>) const { - return 0; - } - - template - MDSPAN_INLINE_FUNCTION constexpr index_type - compute_offset(std::index_sequence, IndexOffset index_offset) const { - return index_offset; - } - - template - MDSPAN_INLINE_FUNCTION constexpr index_type - compute_offset(std::index_sequence, - IndexOffsets... index_offsets) const { - index_type indices[] = {static_cast(index_offsets)...}; - // self-recursive fold trick from - // https://github.com/llvm/llvm-project/blob/96e1914aa2e6d8966acbfbe2f4d184201f1aa318/libcxx/include/mdspan/layout_left.h#L144 - index_type res = 0; - ((res = indices[extents_type::rank() - 1 - Ranks] + - ((extents_type::rank() - 1 - Ranks) == extent_to_pad_idx - ? padded_stride.value(0) - : exts.extent(extents_type::rank() - 1 - Ranks)) * - res), - ...); - return res; - } - -public: -#if !MDSPAN_HAS_CXX_20 - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr mapping() - : mapping(extents_type{}) - {} -#else - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr mapping() - requires(static_padding_stride != dynamic_extent) = default; - - MDSPAN_INLINE_FUNCTION - constexpr mapping() - requires(static_padding_stride == dynamic_extent) - : mapping(extents_type{}) - {} -#endif - - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(const mapping&) noexcept = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED mapping& operator=(const mapping&) noexcept = default; - - /** - * Initializes the mapping with the given extents. - * - * \param ext the given extents - */ - MDSPAN_INLINE_FUNCTION - constexpr mapping(const extents_type& ext) - : padded_stride(padded_stride_type::init_padding(ext)), exts(ext) - {} - - /** - * Initializes the mapping with the given extents and the specified padding value. - * - * This overload participates in overload resolution only if `is_convertible_v` - * is `true` and `is_nothrow_constructible_v` is `true` - * - * \param ext the given extents - * \param padding_value the padding value - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Size, - /* requires */ ( - std::is_convertible_v<_Size, index_type> - && std::is_nothrow_constructible_v - ) - ) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const extents_type &ext, _Size dynamic_padding_value) - : padded_stride(padded_stride_type::init_padding(ext, dynamic_padding_value)), exts(ext) - { - assert((padding_value == dynamic_extent) || (static_cast(padding_value) == static_cast(dynamic_padding_value))); - } - - /** - * Converting constructor from `layout_left::mapping`. - * - * This overload participates in overload resolution only if - * `is_constructible_v` is true. If - * `OtherExtents::rank() > 1` then one of `padding_value`, `static_extent(0)`, - * or `OtherExtents::static_extent(0)` must be `dynamic_extent`; otherwise, - * `OtherExtents::static_extent(0)` must be equal to the least multiple of - * `padding_value` greater than or equal to `extents_type::static_extent(0)` - */ - MDSPAN_TEMPLATE_REQUIRES( - class _OtherExtents, - /* requires */ (std::is_constructible_v)) - MDSPAN_CONDITIONAL_EXPLICIT( - (!std::is_convertible_v<_OtherExtents, extents_type>)) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const layout_left::mapping<_OtherExtents> &other_mapping) - : padded_stride(padded_stride_type::init_padding( - other_mapping, - std::integral_constant{})), - exts(other_mapping.extents()) { - static_assert( - (_OtherExtents::rank() > 1) || - (static_padding_stride != dynamic_extent) || - (_OtherExtents::static_extent(extent_to_pad_idx) != dynamic_extent) || - (static_padding_stride == - _OtherExtents::static_extent(extent_to_pad_idx))); - } - - /** - * Converting constructor from `layout_stride::mapping`. - * - * This overload participates in overload resolution only if - * `is_constructible_v` is true - */ - MDSPAN_TEMPLATE_REQUIRES( - class _OtherExtents, - /* requires */ (std::is_constructible_v)) - MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0)) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const layout_stride::mapping<_OtherExtents> &other_mapping) - : padded_stride(padded_stride_type::init_padding( - other_mapping, - std::integral_constant{})), - exts(other_mapping.extents()) {} - - /** - * Converting constructor from `layout_left_padded::mapping`. - * - * This overload participates in overload resolution only if - * `is_constructible_v` is true. Either - * `padding_value` or `OtherPaddingStride` must be `std::dynamic_extent`, or - * `padding_value == OtherPaddingStride`. - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ (detail::is_layout_left_padded_mapping<_Mapping>::value - &&std::is_constructible_v< - extents_type, typename _Mapping::extents_type>)) - MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 1 && - (padding_value == dynamic_extent || - _Mapping::padding_value == dynamic_extent))) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const _Mapping &other_mapping) - : padded_stride(padded_stride_type::init_padding( - other_mapping, - std::integral_constant{})), - exts(other_mapping.extents()) { - static_assert(padding_value == dynamic_extent || - _Mapping::padding_value == dynamic_extent || - padding_value == _Mapping::padding_value); - } - - /** - * Converting constructor from `layout_right_padded::mapping`. - * - * This overload participates in overload resolution only if - * `extents_type::rank()` is 0 or 1 and `is_constructible_v` is `true`. - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ (detail::is_layout_right_padded_mapping<_Mapping>::value - &&extents_type::rank() <= 1 && - std::is_constructible_v)) - MDSPAN_CONDITIONAL_EXPLICIT( - (!std::is_convertible_v)) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const _Mapping &other_mapping) noexcept - : padded_stride(padded_stride_type::init_padding( - other_mapping.extents(), - other_mapping.extents().extent(extent_to_pad_idx))), - exts(other_mapping.extents()) {} - - MDSPAN_INLINE_FUNCTION constexpr const extents_type & - extents() const noexcept { - return exts; - } - - MDSPAN_INLINE_FUNCTION constexpr std::array - strides() const noexcept { - if constexpr (extents_type::rank() == 0) { - return {}; - } else if constexpr (extents_type::rank() == 1) { - return {1}; - } else { - index_type value = 1; - std::array s{}; - s[extent_to_pad_idx] = value; - value *= padded_stride.value(0); - for (rank_type r = extent_to_pad_idx + 1; r < extents_type::rank() - 1; - ++r) { - s[r] = value; - value *= exts.extent(r); - } - s[extents_type::rank() - 1] = value; - return s; - } - } - - MDSPAN_INLINE_FUNCTION constexpr index_type - required_span_size() const noexcept { - if constexpr (extents_type::rank() == 0) { - return 1; - } else if constexpr (extents_type::rank() == 1) { - return exts.extent(0); - } else { - index_type value = padded_stride.value(0); - for (rank_type r = 1; r < extents_type::rank(); ++r) { - value *= exts.extent(r); - } - return value; - } - } - - /** - * Return the mapping given the provided indices per rank. - * - * This overload participates in overload resolution only if: - * - `sizeof...(Indices) == extents_type::rank()`, - * - `(is_convertible_v && ...) is true`, and - * - (is_nothrow_constructible_v && ...) is true. - */ - MDSPAN_TEMPLATE_REQUIRES( - class... _Indices, - /* requires */ (sizeof...(_Indices) == extents_type::rank() && - (::MDSPAN_IMPL_STANDARD_NAMESPACE::detail:: - are_valid_indices()))) - MDSPAN_INLINE_FUNCTION constexpr size_t - operator()(_Indices... idxs) const noexcept { -#if !defined(NDEBUG) - ::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::check_all_indices(this->extents(), - idxs...); -#endif // ! NDEBUG - return compute_offset(std::index_sequence_for<_Indices...>{}, idxs...); - } - - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { - return true; - } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { - return (extents_type::rank() <= rank_type(1)) || - (extents_type::static_extent(extent_to_pad_idx) != dynamic_extent && - extents_type::static_extent(extent_to_pad_idx) == - padded_stride_type::static_value()); - } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { - return true; - } - - MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { - return true; - } - MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const noexcept { - return (extents_type::rank() < 2) || - (exts.extent(extent_to_pad_idx) == padded_stride.value(0)); - } - MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { - return true; - } - - MDSPAN_INLINE_FUNCTION - constexpr index_type stride(rank_type r) const noexcept { - assert(r < extents_type::rank()); - if (r == 0) - return index_type(1); - - index_type value = padded_stride.value(0); - for (rank_type k = 1; k < r; k++) - value *= exts.extent(k); - - return value; - } - - /** - * Equality operator between `layout_left_padded`s - * - * This overload only participates in overload resolution if - * `OtherExtents::rank() == extents_type::rank()`. - * - * \note There is currently a difference from p2642r2, where this function is - * specified as taking `layout_left_padded< padding_value >::mapping< - * Extents>`. However, this makes `padding_value` non-deducible. - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ (detail::is_layout_left_padded_mapping<_Mapping>::value && - (_Mapping::extents_type::rank() == extents_type::rank()))) - MDSPAN_INLINE_FUNCTION friend constexpr bool - operator==(const mapping &left, const _Mapping &right) noexcept { - // Workaround for some compilers not short-circuiting properly with - // compile-time checks i.e. we can't access stride(_padding_stride_idx) of a - // rank 0 mapping - bool strides_equal = true; - if constexpr (extents_type::rank() > rank_type(1)) { - strides_equal = - left.stride(padded_stride_idx) == right.stride(padded_stride_idx); - } - return (left.extents() == right.extents()) && strides_equal; - } - -#if !MDSPAN_HAS_CXX_20 - /** - * Inequality operator between `layout_left_padded`s - * - * This overload only participates in overload resolution if - * `OtherExtents::rank() == extents_type::rank()`. - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ (detail::is_layout_left_padded_mapping<_Mapping>::value && - (_Mapping::extents_type::rank() == extents_type::rank()))) - MDSPAN_INLINE_FUNCTION friend constexpr bool - operator!=(const mapping &left, const _Mapping &right) noexcept { - return !(left == right); - } -#endif -}; - -template -template -class layout_right_padded::mapping { -public: - static constexpr size_t padding_value = PaddingValue; - - using extents_type = Extents; - using index_type = typename extents_type::index_type; - using size_type = typename extents_type::size_type; - using rank_type = typename extents_type::rank_type; - using layout_type = layout_right_padded; - -#ifndef MDSPAN_INTERNAL_TEST - private: -#endif // MDSPAN_INTERNAL_TEST - - static constexpr rank_type padded_stride_idx = detail::layout_padded_constants::padded_stride_idx; - static constexpr rank_type extent_to_pad_idx = detail::layout_padded_constants::extent_to_pad_idx; - - static_assert((padding_value != 0) - || (extents_type::static_extent(extent_to_pad_idx) == 0) - || (extents_type::static_extent(extent_to_pad_idx) == dynamic_extent), - "if padding stride is 0, static_extent(extent-to-pad-rank) must also be 0 or dynamic_extent"); - - using padded_stride_type = detail::padded_extent< padding_value, extents_type, extent_to_pad_idx >; - static constexpr size_t static_padding_stride = padded_stride_type::static_value(); - - typename padded_stride_type::static_array_type padded_stride = {}; - extents_type exts = {}; - - MDSPAN_INLINE_FUNCTION constexpr index_type - compute_offset(std::index_sequence<>) const { - return 0; - } - - template - MDSPAN_INLINE_FUNCTION constexpr index_type - compute_offset(std::index_sequence, IndexOffset index_offset) const { - return index_offset; - } - - template - MDSPAN_INLINE_FUNCTION constexpr index_type - compute_offset(std::index_sequence, - IndexOffsets... index_offsets) const { - // self-recursive fold trick from - // https://github.com/llvm/llvm-project/blob/4d9771741d40cc9cfcccb6b033f43689d36b705a/libcxx/include/mdspan/layout_right.h#L141 - index_type res = 0; - ((res = static_cast(index_offsets) + - (Ranks == extent_to_pad_idx ? padded_stride.value(0) - : exts.extent(Ranks)) * - res), - ...); - return res; - } - -public: -#if !MDSPAN_HAS_CXX_20 - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr mapping() - : mapping(extents_type{}) - {} -#else - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr mapping() - requires(static_padding_stride != dynamic_extent) = default; - - MDSPAN_INLINE_FUNCTION - constexpr mapping() - requires(static_padding_stride == dynamic_extent) - : mapping(extents_type{}) - {} -#endif - - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(const mapping&) noexcept = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED mapping& operator=(const mapping&) noexcept = default; - - /** - * Initializes the mapping with the given extents. - * - * \param ext the given extents - */ - MDSPAN_INLINE_FUNCTION - constexpr mapping(const extents_type &ext) - : padded_stride(padded_stride_type::init_padding(ext)), exts(ext) {} - - /** - * Initializes the mapping with the given extents and the specified padding value. - * - * This overload participates in overload resolution only if `is_convertible_v` - * is `true` and `is_nothrow_constructible_v` is `true` - * - * \param ext the given extents - * \param padding_value the padding value - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Size, - /* requires */ ( - std::is_convertible_v<_Size, index_type> - && std::is_nothrow_constructible_v - ) - ) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const extents_type &ext, _Size dynamic_padding_value) - : padded_stride(padded_stride_type::init_padding(ext, static_cast(dynamic_padding_value))), - exts(ext) { - assert((padding_value == dynamic_extent) || - (static_cast(padding_value) == static_cast(dynamic_padding_value))); - } - - /** - * Converting constructor from `layout_right::mapping`. - * - * This overload participates in overload resolution only if `is_constructible_v` is true. - * If `OtherExtents::rank() > 1` then one of `padding_value`, `static_extent(0)`, or `OtherExtents::static_extent(0)` must be `dynamic_extent`; - * otherwise, `OtherExtents::static_extent(0)` must be equal to the least multiple of `padding_value` greater than or equal to `extents_type::static_extent(0)` - */ - MDSPAN_TEMPLATE_REQUIRES( - class _OtherExtents, - /* requires */ (std::is_constructible_v)) - MDSPAN_CONDITIONAL_EXPLICIT( - (!std::is_convertible_v<_OtherExtents, extents_type>)) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const layout_right::mapping<_OtherExtents> &other_mapping) - : padded_stride(padded_stride_type::init_padding( - other_mapping, - std::integral_constant{})), - exts(other_mapping.extents()) { - static_assert( - (_OtherExtents::rank() > 1) || - (padded_stride_type::static_value() != dynamic_extent) || - (_OtherExtents::static_extent(extent_to_pad_idx) != dynamic_extent) || - (padded_stride_type::static_value() == - _OtherExtents::static_extent(extent_to_pad_idx))); - } - - /** - * Converting constructor from `layout_stride::mapping`. - * - * This overload participates in overload resolution only if - * `is_constructible_v` is true - */ - MDSPAN_TEMPLATE_REQUIRES( - class _OtherExtents, - /* requires */ (std::is_constructible_v)) - MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0)) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const layout_stride::mapping<_OtherExtents> &other_mapping) - : padded_stride(padded_stride_type::init_padding( - other_mapping, - std::integral_constant{})), - exts(other_mapping.extents()) {} - - /** - * Converting constructor from `layout_right_padded::mapping`. - * - * This overload participates in overload resolution only if - * `is_constructible_v` is true. Either - * `padding_value` or `OtherPaddingStride` must be `std::dynamic_extent`, or - * `padding_value == OtherPaddingStride`. - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ (detail::is_layout_right_padded_mapping<_Mapping>::value - &&std::is_constructible_v< - extents_type, typename _Mapping::extents_type>)) - MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 1 && - (padding_value == dynamic_extent || - _Mapping::padding_value == dynamic_extent))) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const _Mapping &other_mapping) - : padded_stride(padded_stride_type::init_padding( - other_mapping, - std::integral_constant{})), - exts(other_mapping.extents()) { - static_assert(padding_value == dynamic_extent || - _Mapping::padding_value == dynamic_extent || - padding_value == _Mapping::padding_value); - } - - /** - * Converting constructor from `layout_left_padded::mapping`. - * - * This overload participates in overload resolution only if - * `extents_type::rank()` is 0 or 1 and `is_constructible_v` is `true`. - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ (detail::is_layout_left_padded_mapping<_Mapping>::value - &&extents_type::rank() <= 1 && - std::is_constructible_v)) - MDSPAN_CONDITIONAL_EXPLICIT( - (!std::is_convertible_v)) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const _Mapping &other_mapping) noexcept - : padded_stride(padded_stride_type::init_padding( - other_mapping.extents(), - other_mapping.extents().extent(extent_to_pad_idx))), - exts(other_mapping.extents()) {} - - MDSPAN_INLINE_FUNCTION constexpr const extents_type & - extents() const noexcept { - return exts; - } - - MDSPAN_INLINE_FUNCTION constexpr std::array - strides() const noexcept { - if constexpr (extents_type::rank() == 0) { - return {}; - } else if constexpr (extents_type::rank() == 1) { - return {1}; - } else { - index_type value = 1; - std::array s{}; - s[extent_to_pad_idx] = value; - value *= padded_stride.value(0); - for (rank_type r = extent_to_pad_idx - 1; r > 0; --r) { - s[r] = value; - value *= exts.extent(r); - } - s[0] = value; - return s; - } - } - - MDSPAN_INLINE_FUNCTION constexpr index_type - required_span_size() const noexcept { - if constexpr (extents_type::rank() == 0) { - return 1; - } else if constexpr (extents_type::rank() == 1) { - return exts.extent(0); - } else { - index_type value = 1; - for (rank_type r = 0; r < extent_to_pad_idx; ++r) { - value *= exts.extent(r); - } - return value * padded_stride.value(0); - } - } - - /** - * Return the mapping given the provided indices per rank. - * - * This overload participates in overload resolution only if: - * - `sizeof...(Indices) == extents_type::rank()`, - * - `(is_convertible_v && ...) is true`, and - * - (is_nothrow_constructible_v && ...) is true. - */ - MDSPAN_TEMPLATE_REQUIRES( - class... _Indices, - /* requires */ (sizeof...(_Indices) == extents_type::rank() && - (::MDSPAN_IMPL_STANDARD_NAMESPACE::detail:: - are_valid_indices()))) - MDSPAN_INLINE_FUNCTION constexpr size_t - operator()(_Indices... idxs) const noexcept { - return compute_offset(std::index_sequence_for<_Indices...>{}, idxs...); - } - - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { - return true; - } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { - return (extents_type::rank() <= rank_type(1)) || - (extents_type::static_extent(extent_to_pad_idx) != dynamic_extent && - extents_type::static_extent(extent_to_pad_idx) == - padded_stride_type::static_value()); - } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { - return true; - } - - MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { - return true; - } - MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const noexcept { - return (extents_type::rank() < 2) || - (exts.extent(extent_to_pad_idx) == padded_stride.value(0)); - } - MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { - return true; - } - - MDSPAN_INLINE_FUNCTION constexpr index_type - stride(rank_type r) const noexcept { - assert(r < extents_type::rank()); - if (r == extents_type::rank() - 1) - return index_type(1); - - index_type value = padded_stride.value(0); - for (rank_type k = extents_type::rank() - 2; k > r; k--) - value *= exts.extent(k); - - return value; - } - - /** - * Equality operator between `layout_right_padded`s - * - * This overload only participates in overload resolution if - * `OtherExtents::rank() == extents_type::rank()`. - * - * \note There is currently a difference from p2642r2, where this function is - * specified as taking `layout_right_padded< padding_value >::mapping< - * Extents>`. However, this makes `padding_value` non-deducible. - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ (detail::is_layout_right_padded_mapping<_Mapping>::value && - (_Mapping::extents_type::rank() == extents_type::rank()))) - MDSPAN_INLINE_FUNCTION friend constexpr bool - operator==(const mapping &left, const _Mapping &right) noexcept { - // Workaround for some compilers not short-circuiting properly with - // compile-time checks i.e. we can't access stride(_padding_stride_idx) of a - // rank 0 mapping - bool strides_equal = true; - if constexpr (extents_type::rank() > rank_type(1)) { - strides_equal = - left.stride(padded_stride_idx) == right.stride(padded_stride_idx); - } - return (left.extents() == right.extents()) && strides_equal; - } - -#if !MDSPAN_HAS_CXX_20 - /** - * Inequality operator between `layout_right_padded`s - * - * This overload only participates in overload resolution if - * `OtherExtents::rank() == extents_type::rank()`. - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ (detail::is_layout_right_padded_mapping<_Mapping>::value && - (_Mapping::extents_type::rank() == extents_type::rank()))) - MDSPAN_INLINE_FUNCTION friend constexpr bool - operator!=(const mapping &left, const _Mapping &right) noexcept { - return !(left == right); - } -#endif -}; -} -} -//END_FILE_INCLUDE: mdspan/include/experimental/__p2642_bits/layout_padded.hpp -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/submdspan.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/submdspan_extents.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -#include - -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/strided_slice.hpp - -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -#include - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -namespace { - template - struct __mdspan_is_integral_constant: std::false_type {}; - - template - struct __mdspan_is_integral_constant>: std::true_type {}; -} - -// Slice Specifier allowing for strides and compile time extent -template -struct strided_slice { - using offset_type = OffsetType; - using extent_type = ExtentType; - using stride_type = StrideType; - - _MDSPAN_NO_UNIQUE_ADDRESS OffsetType offset{}; - _MDSPAN_NO_UNIQUE_ADDRESS ExtentType extent{}; - _MDSPAN_NO_UNIQUE_ADDRESS StrideType stride{}; - - static_assert(std::is_integral_v || __mdspan_is_integral_constant::value); - static_assert(std::is_integral_v || __mdspan_is_integral_constant::value); - static_assert(std::is_integral_v || __mdspan_is_integral_constant::value); -}; - -} // MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/strided_slice.hpp -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace detail { - -// Mapping from submapping ranks to srcmapping ranks -// InvMapRank is an index_sequence, which we build recursively -// to contain the mapped indices. -// end of recursion specialization containing the final index_sequence -template -MDSPAN_INLINE_FUNCTION -constexpr auto inv_map_rank(std::integral_constant, std::index_sequence) { - return std::index_sequence(); -} - -// specialization reducing rank by one (i.e., integral slice specifier) -template -MDSPAN_INLINE_FUNCTION -constexpr auto inv_map_rank(std::integral_constant, std::index_sequence, Slice, - SliceSpecifiers... slices) { - using next_idx_seq_t = std::conditional_t, - std::index_sequence, - std::index_sequence>; - - return inv_map_rank(std::integral_constant(), next_idx_seq_t(), - slices...); -} - -// Helper for identifying strided_slice -template struct is_strided_slice : std::false_type {}; - -template -struct is_strided_slice< - strided_slice> : std::true_type {}; - -// first_of(slice): getting begin of slice specifier range -MDSPAN_TEMPLATE_REQUIRES( - class Integral, - /* requires */(std::is_convertible_v) -) -MDSPAN_INLINE_FUNCTION -constexpr Integral first_of(const Integral &i) { - return i; -} - -MDSPAN_INLINE_FUNCTION -constexpr std::integral_constant -first_of(const ::MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent_t &) { - return std::integral_constant(); -} - -MDSPAN_TEMPLATE_REQUIRES( - class Slice, - /* requires */(std::is_convertible_v>) -) -MDSPAN_INLINE_FUNCTION -constexpr auto first_of(const Slice &i) { - return std::get<0>(i); -} - -template -MDSPAN_INLINE_FUNCTION -constexpr OffsetType -first_of(const strided_slice &r) { - return r.offset; -} - -// last_of(slice): getting end of slice specifier range -// We need however not just the slice but also the extents -// of the original view and which rank from the extents. -// This is needed in the case of slice being full_extent_t. -MDSPAN_TEMPLATE_REQUIRES( - size_t k, class Extents, class Integral, - /* requires */(std::is_convertible_v) -) -MDSPAN_INLINE_FUNCTION -constexpr Integral - last_of(std::integral_constant, const Extents &, const Integral &i) { - return i; -} - -MDSPAN_TEMPLATE_REQUIRES( - size_t k, class Extents, class Slice, - /* requires */(std::is_convertible_v>) -) -MDSPAN_INLINE_FUNCTION -constexpr auto last_of(std::integral_constant, const Extents &, - const Slice &i) { - return std::get<1>(i); -} - -// Suppress spurious warning with NVCC about no return statement. -// This is a known issue in NVCC and NVC++ -// Depending on the CUDA and GCC version we need both the builtin -// and the diagnostic push. I tried really hard to find something shorter -// but no luck ... -#if defined __NVCC__ - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic push - #pragma nv_diag_suppress = implicit_return_from_non_void_function - #else - #ifdef __CUDA_ARCH__ - #pragma diagnostic push - #pragma diag_suppress implicit_return_from_non_void_function - #endif - #endif -#elif defined __NVCOMPILER - #pragma diagnostic push - #pragma diag_suppress = implicit_return_from_non_void_function -#endif -template -MDSPAN_INLINE_FUNCTION -constexpr auto last_of(std::integral_constant, const Extents &ext, - ::MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent_t) { - if constexpr (Extents::static_extent(k) == dynamic_extent) { - return ext.extent(k); - } else { - return std::integral_constant(); - } -#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) - // Even with CUDA_ARCH protection this thing warns about calling host function - __builtin_unreachable(); -#endif -} -#if defined __NVCC__ - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic pop - #else - #ifdef __CUDA_ARCH__ - #pragma diagnostic pop - #endif - #endif -#elif defined __NVCOMPILER - #pragma diagnostic pop -#endif - -template -MDSPAN_INLINE_FUNCTION -constexpr OffsetType -last_of(std::integral_constant, const Extents &, - const strided_slice &r) { - return r.extent; -} - -// get stride of slices -template -MDSPAN_INLINE_FUNCTION -constexpr auto stride_of(const T &) { - return std::integral_constant(); -} - -template -MDSPAN_INLINE_FUNCTION -constexpr auto -stride_of(const strided_slice &r) { - return r.stride; -} - -// divide which can deal with integral constant preservation -template -MDSPAN_INLINE_FUNCTION -constexpr auto divide(const T0 &v0, const T1 &v1) { - return IndexT(v0) / IndexT(v1); -} - -template -MDSPAN_INLINE_FUNCTION -constexpr auto divide(const std::integral_constant &, - const std::integral_constant &) { - // cutting short division by zero - // this is used for strided_slice with zero extent/stride - return std::integral_constant(); -} - -// multiply which can deal with integral constant preservation -template -MDSPAN_INLINE_FUNCTION -constexpr auto multiply(const T0 &v0, const T1 &v1) { - return IndexT(v0) * IndexT(v1); -} - -template -MDSPAN_INLINE_FUNCTION -constexpr auto multiply(const std::integral_constant &, - const std::integral_constant &) { - return std::integral_constant(); -} - -// compute new static extent from range, preserving static knowledge -template struct StaticExtentFromRange { - constexpr static size_t value = dynamic_extent; -}; - -template -struct StaticExtentFromRange, - std::integral_constant> { - constexpr static size_t value = val1 - val0; -}; - -// compute new static extent from strided_slice, preserving static -// knowledge -template struct StaticExtentFromStridedRange { - constexpr static size_t value = dynamic_extent; -}; - -template -struct StaticExtentFromStridedRange, - std::integral_constant> { - constexpr static size_t value = val0 > 0 ? 1 + (val0 - 1) / val1 : 0; -}; - -// creates new extents through recursive calls to next_extent member function -// next_extent has different overloads for different types of stride specifiers -template -struct extents_constructor { - MDSPAN_TEMPLATE_REQUIRES( - class Slice, class... SlicesAndExtents, - /* requires */(!std::is_convertible_v && - !is_strided_slice::value) - ) - MDSPAN_INLINE_FUNCTION - constexpr static auto next_extent(const Extents &ext, const Slice &sl, - SlicesAndExtents... slices_and_extents) { - constexpr size_t new_static_extent = StaticExtentFromRange< - decltype(first_of(std::declval())), - decltype(last_of(std::integral_constant(), - std::declval(), - std::declval()))>::value; - - using next_t = - extents_constructor; - using index_t = typename Extents::index_type; - return next_t::next_extent( - ext, slices_and_extents..., - index_t(last_of(std::integral_constant(), ext, - sl)) - - index_t(first_of(sl))); - } - - MDSPAN_TEMPLATE_REQUIRES( - class Slice, class... SlicesAndExtents, - /* requires */ (std::is_convertible_v) - ) - MDSPAN_INLINE_FUNCTION - constexpr static auto next_extent(const Extents &ext, const Slice &, - SlicesAndExtents... slices_and_extents) { - using next_t = extents_constructor; - return next_t::next_extent(ext, slices_and_extents...); - } - - template - MDSPAN_INLINE_FUNCTION - constexpr static auto - next_extent(const Extents &ext, - const strided_slice &r, - SlicesAndExtents... slices_and_extents) { - using index_t = typename Extents::index_type; - using new_static_extent_t = - StaticExtentFromStridedRange; - if constexpr (new_static_extent_t::value == dynamic_extent) { - using next_t = - extents_constructor; - return next_t::next_extent( - ext, slices_and_extents..., - r.extent > 0 ? 1 + divide(r.extent - 1, r.stride) : 0); - } else { - constexpr size_t new_static_extent = new_static_extent_t::value; - using next_t = - extents_constructor; - return next_t::next_extent( - ext, slices_and_extents..., index_t(divide(ExtentType(), StrideType()))); - } - } -}; - -template -struct extents_constructor<0, Extents, NewStaticExtents...> { - - template - MDSPAN_INLINE_FUNCTION - constexpr static auto next_extent(const Extents &, NewExtents... new_exts) { - return extents( - new_exts...); - } -}; - -} // namespace detail - -// submdspan_extents creates new extents given src extents and submdspan slice -// specifiers -template -MDSPAN_INLINE_FUNCTION -constexpr auto submdspan_extents(const extents &src_exts, - SliceSpecifiers... slices) { - - using ext_t = extents; - return detail::extents_constructor::next_extent( - src_exts, slices...); -} -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/submdspan_extents.hpp -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/submdspan_mapping.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -#include -#include -#include -#include // index_sequence - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -//****************************************** -// Return type of submdspan_mapping overloads -//****************************************** -template struct submdspan_mapping_result { - _MDSPAN_NO_UNIQUE_ADDRESS LayoutMapping mapping{}; - size_t offset; -}; - -namespace detail { - -// We use const Slice& and not Slice&& because the various -// submdspan_mapping_impl overloads use their slices arguments -// multiple times. This makes perfect forwarding not useful, but we -// still don't want to pass those (possibly of size 64 x 3 bits) -// objects by value. -template -MDSPAN_INLINE_FUNCTION -constexpr bool -one_slice_out_of_bounds(const IndexType& extent, const Slice& slice) -{ - using common_t = std::common_type_t; - return static_cast(detail::first_of(slice)) == static_cast(extent); -} - -template -MDSPAN_INLINE_FUNCTION -constexpr bool -any_slice_out_of_bounds_helper(std::index_sequence, - const extents& exts, - const Slices& ... slices) -{ - return _MDSPAN_FOLD_OR( - (one_slice_out_of_bounds(exts.extent(RankIndices), slices)) - ); -} - -template -MDSPAN_INLINE_FUNCTION -constexpr bool -any_slice_out_of_bounds(const extents& exts, - const Slices& ... slices) -{ - return any_slice_out_of_bounds_helper( - std::make_index_sequence(), - exts, slices...); -} - -// constructs sub strides -template -MDSPAN_INLINE_FUNCTION -constexpr auto -construct_sub_strides(const SrcMapping &src_mapping, - std::index_sequence, - const std::tuple &slices_stride_factor) { - using index_type = typename SrcMapping::index_type; - return std::array{ - (static_cast(src_mapping.stride(InvMapIdxs)) * - static_cast(std::get(slices_stride_factor)))...}; -} -} // namespace detail - -//********************************** -// layout_left submdspan_mapping -//********************************* -namespace detail { - -// Figure out whether to preserve layout_left -template -struct preserve_layout_left_mapping; - -template -struct preserve_layout_left_mapping, SubRank, - SliceSpecifiers...> { - constexpr static bool value = - // Preserve layout for rank 0 - (SubRank == 0) || - ( - // Slice specifiers up to subrank need to be full_extent_t - except - // for the last one which could also be tuple but not a strided index - // range slice specifiers after subrank are integrals - ((Idx > SubRank - 1) || // these are only integral slice specifiers - (std::is_same_v) || - ((Idx == SubRank - 1) && - std::is_convertible_v>)) && - ...); -}; -} // namespace detail - -// Suppress spurious warning with NVCC about no return statement. -// This is a known issue in NVCC and NVC++ -// Depending on the CUDA and GCC version we need both the builtin -// and the diagnostic push. I tried really hard to find something shorter -// but no luck ... -#if defined __NVCC__ - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic push - #pragma nv_diag_suppress = implicit_return_from_non_void_function - #else - #ifdef __CUDA_ARCH__ - #pragma diagnostic push - #pragma diag_suppress implicit_return_from_non_void_function - #endif - #endif -#elif defined __NVCOMPILER - #pragma diagnostic push - #pragma diag_suppress = implicit_return_from_non_void_function -#endif -// Actual submdspan mapping call -template -template -MDSPAN_INLINE_FUNCTION -constexpr auto -layout_left::mapping::submdspan_mapping_impl(SliceSpecifiers... slices) const { - - // compute sub extents - using src_ext_t = Extents; - auto dst_ext = submdspan_extents(extents(), slices...); - using dst_ext_t = decltype(dst_ext); - - // figure out sub layout type - constexpr bool preserve_layout = detail::preserve_layout_left_mapping< - decltype(std::make_index_sequence()), dst_ext_t::rank(), - SliceSpecifiers...>::value; - using dst_layout_t = - std::conditional_t; - using dst_mapping_t = typename dst_layout_t::template mapping; - - // Figure out if any slice's lower bound equals the corresponding extent. - // If so, bypass evaluating the layout mapping. This fixes LWG Issue 4060. - const bool out_of_bounds = - detail::any_slice_out_of_bounds(this->extents(), slices...); - auto offset = static_cast( - out_of_bounds ? - this->required_span_size() : - this->operator()(detail::first_of(slices)...) - ); - - if constexpr (std::is_same_v) { - // layout_left case - return submdspan_mapping_result{dst_mapping_t(dst_ext), offset}; - } else { - // layout_stride case - auto inv_map = detail::inv_map_rank( - std::integral_constant(), - std::index_sequence<>(), - slices...); - return submdspan_mapping_result{ - dst_mapping_t(dst_ext, detail::construct_sub_strides( - *this, inv_map, - // HIP needs deduction guides to have markups so we need to be explicit - // NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have the issue - // But Clang-CUDA also doesn't accept the use of deduction guide so disable it for CUDA alltogether - #if defined(_MDSPAN_HAS_HIP) || defined(_MDSPAN_HAS_CUDA) - std::tuple{detail::stride_of(slices)...})), - #else - std::tuple{detail::stride_of(slices)...})), - #endif - offset}; - } -#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) - __builtin_unreachable(); -#endif -} -#if defined __NVCC__ - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic pop - #else - #ifdef __CUDA_ARCH__ - #pragma diagnostic pop - #endif - #endif -#elif defined __NVCOMPILER - #pragma diagnostic pop -#endif - -//********************************** -// layout_right submdspan_mapping -//********************************* -namespace detail { - -// Figure out whether to preserve layout_right -template -struct preserve_layout_right_mapping; - -template -struct preserve_layout_right_mapping, SubRank, - SliceSpecifiers...> { - constexpr static size_t SrcRank = sizeof...(SliceSpecifiers); - constexpr static bool value = - // Preserve layout for rank 0 - (SubRank == 0) || - ( - // The last subrank slice specifiers need to be full_extent_t - except - // for the srcrank-subrank one which could also be tuple but not a - // strided index range slice specifiers before srcrank-subrank are - // integrals - ((Idx < - SrcRank - SubRank) || // these are only integral slice specifiers - (std::is_same_v) || - ((Idx == SrcRank - SubRank) && - std::is_convertible_v>)) && - ...); -}; -} // namespace detail - -// Suppress spurious warning with NVCC about no return statement. -// This is a known issue in NVCC and NVC++ -// Depending on the CUDA and GCC version we need both the builtin -// and the diagnostic push. I tried really hard to find something shorter -// but no luck ... -#if defined __NVCC__ - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic push - #pragma nv_diag_suppress = implicit_return_from_non_void_function - #else - #ifdef __CUDA_ARCH__ - #pragma diagnostic push - #pragma diag_suppress implicit_return_from_non_void_function - #endif - #endif -#elif defined __NVCOMPILER - #pragma diagnostic push - #pragma diag_suppress = implicit_return_from_non_void_function -#endif -template -template -MDSPAN_INLINE_FUNCTION -constexpr auto -layout_right::mapping::submdspan_mapping_impl( - SliceSpecifiers... slices) const { - // get sub extents - using src_ext_t = Extents; - auto dst_ext = submdspan_extents(extents(), slices...); - using dst_ext_t = decltype(dst_ext); - - // determine new layout type - constexpr bool preserve_layout = detail::preserve_layout_right_mapping< - decltype(std::make_index_sequence()), dst_ext_t::rank(), - SliceSpecifiers...>::value; - using dst_layout_t = - std::conditional_t; - using dst_mapping_t = typename dst_layout_t::template mapping; - - // Figure out if any slice's lower bound equals the corresponding extent. - // If so, bypass evaluating the layout mapping. This fixes LWG Issue 4060. - const bool out_of_bounds = - detail::any_slice_out_of_bounds(this->extents(), slices...); - auto offset = static_cast( - out_of_bounds ? - this->required_span_size() : - this->operator()(detail::first_of(slices)...) - ); - - if constexpr (std::is_same_v) { - // layout_right case - return submdspan_mapping_result{dst_mapping_t(dst_ext), offset}; - } else { - // layout_stride case - auto inv_map = detail::inv_map_rank( - std::integral_constant(), - std::index_sequence<>(), - slices...); - return submdspan_mapping_result{ - dst_mapping_t(dst_ext, detail::construct_sub_strides( - *this, inv_map, - // HIP needs deduction guides to have markups so we need to be explicit - // NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have the issue - // But Clang-CUDA also doesn't accept the use of deduction guide so disable it for CUDA alltogether - #if defined(_MDSPAN_HAS_HIP) || defined(_MDSPAN_HAS_CUDA) - std::tuple{detail::stride_of(slices)...})), - #else - std::tuple{detail::stride_of(slices)...})), - #endif - offset}; - } -#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) - __builtin_unreachable(); -#endif -} -#if defined __NVCC__ - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic pop - #else - #ifdef __CUDA_ARCH__ - #pragma diagnostic pop - #endif - #endif -#elif defined __NVCOMPILER - #pragma diagnostic pop -#endif - -//********************************** -// layout_stride submdspan_mapping -//********************************* -template -template -MDSPAN_INLINE_FUNCTION -constexpr auto -layout_stride::mapping::submdspan_mapping_impl( - SliceSpecifiers... slices) const { - auto dst_ext = submdspan_extents(extents(), slices...); - using dst_ext_t = decltype(dst_ext); - auto inv_map = detail::inv_map_rank( - std::integral_constant(), - std::index_sequence<>(), - slices...); - using dst_mapping_t = typename layout_stride::template mapping; - - // Figure out if any slice's lower bound equals the corresponding extent. - // If so, bypass evaluating the layout mapping. This fixes LWG Issue 4060. - const bool out_of_bounds = - detail::any_slice_out_of_bounds(this->extents(), slices...); - auto offset = static_cast( - out_of_bounds ? - this->required_span_size() : - this->operator()(detail::first_of(slices)...) - ); - - return submdspan_mapping_result{ - dst_mapping_t(dst_ext, detail::construct_sub_strides( - *this, inv_map, - // HIP needs deduction guides to have markups so we need to be explicit - // NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have the issue - #if defined(_MDSPAN_HAS_HIP) || (defined(__NVCC__) && (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__ * 10) < 1120) - std::tuple(detail::stride_of(slices)...))), -#else - std::tuple(detail::stride_of(slices)...))), -#endif - offset}; -} - -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/submdspan_mapping.hpp - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -template -MDSPAN_INLINE_FUNCTION -constexpr auto -submdspan(const mdspan &src, - SliceSpecifiers... slices) { - const auto sub_submdspan_mapping_result = submdspan_mapping(src.mapping(), slices...); - // NVCC has a problem with the deduction so lets figure out the type - using sub_mapping_t = std::remove_cv_t; - using sub_extents_t = typename sub_mapping_t::extents_type; - using sub_layout_t = typename sub_mapping_t::layout_type; - using sub_accessor_t = typename AccessorPolicy::offset_policy; - return mdspan( - src.accessor().offset(src.data_handle(), sub_submdspan_mapping_result.offset), - sub_submdspan_mapping_result.mapping, - sub_accessor_t(src.accessor())); -} -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/submdspan.hpp -#endif -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2389_bits/dims.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -// backward compatibility import into experimental -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace MDSPAN_IMPL_PROPOSED_NAMESPACE { - -template< ::std::size_t Rank, class IndexType = std::size_t> -using dims = - :: MDSPAN_IMPL_STANDARD_NAMESPACE :: dextents; - -} // namespace MDSPAN_IMPL_PROPOSED_NAMESPACE -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p2389_bits/dims.hpp - -#endif // MDSPAN_HPP_ -//END_FILE_INCLUDE: mdspan/include/mdspan/mdspan.hpp -#endif // _MDSPAN_SINGLE_HEADER_INCLUDE_GUARD_ - diff --git a/scipy/special/xsf/tools.h b/scipy/special/xsf/tools.h deleted file mode 100644 index e349f6a5fb4f..000000000000 --- a/scipy/special/xsf/tools.h +++ /dev/null @@ -1,427 +0,0 @@ -/* Building blocks for implementing special functions */ - -#pragma once - -#include "config.h" -#include "error.h" - -namespace xsf { -namespace detail { - - /* Result type of a "generator", a callable object that produces a value - * each time it is called. - */ - template - using generator_result_t = typename std::decay::type>::type; - - /* Used to deduce the type of the numerator/denominator of a fraction. */ - template - struct pair_traits; - - template - struct pair_traits> { - using value_type = T; - }; - - template - using pair_value_t = typename pair_traits::value_type; - - /* Used to extract the "value type" of a complex type. */ - template - struct real_type { - using type = T; - }; - - template - struct real_type> { - using type = T; - }; - - template - using real_type_t = typename real_type::type; - - // Return NaN, handling both real and complex types. - template - XSF_HOST_DEVICE inline typename std::enable_if::value, T>::type maybe_complex_NaN() { - return std::numeric_limits::quiet_NaN(); - } - - template - XSF_HOST_DEVICE inline typename std::enable_if::value, T>::type maybe_complex_NaN() { - using V = typename T::value_type; - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - - // Series evaluators. - template > - XSF_HOST_DEVICE T - series_eval(Generator &g, T init_val, real_type_t tol, std::uint64_t max_terms, const char *func_name) { - /* Sum an infinite series to a given precision. - * - * g : a generator of terms for the series. - * - * init_val : A starting value that terms are added to. This argument determines the - * type of the result. - * - * tol : relative tolerance for stopping criterion. - * - * max_terms : The maximum number of terms to add before giving up and declaring - * non-convergence. - * - * func_name : The name of the function within SciPy where this call to series_eval - * will ultimately be used. This is needed to pass to set_error in case - * of non-convergence. - */ - T result = init_val; - T term; - for (std::uint64_t i = 0; i < max_terms; ++i) { - term = g(); - result += term; - if (std::abs(term) < std::abs(result) * tol) { - return result; - } - } - // Exceeded max terms without converging. Return NaN. - set_error(func_name, SF_ERROR_NO_RESULT, NULL); - return maybe_complex_NaN(); - } - - template > - XSF_HOST_DEVICE T series_eval_fixed_length(Generator &g, T init_val, std::uint64_t num_terms) { - /* Sum a fixed number of terms from a series. - * - * g : a generator of terms for the series. - * - * init_val : A starting value that terms are added to. This argument determines the - * type of the result. - * - * max_terms : The number of terms from the series to sum. - * - */ - T result = init_val; - for (std::uint64_t i = 0; i < num_terms; ++i) { - result += g(); - } - return result; - } - - /* Performs one step of Kahan summation. */ - template - XSF_HOST_DEVICE void kahan_step(T &sum, T &comp, T x) { - T y = x - comp; - T t = sum + y; - comp = (t - sum) - y; - sum = t; - } - - /* Evaluates an infinite series using Kahan summation. - * - * Denote the series by - * - * S = a[0] + a[1] + a[2] + ... - * - * And for n = 0, 1, 2, ..., denote its n-th partial sum by - * - * S[n] = a[0] + a[1] + ... + a[n] - * - * This function computes S[0], S[1], ... until a[n] is sufficiently - * small or if the maximum number of terms have been evaluated. - * - * Parameters - * ---------- - * g - * Reference to generator that yields the sequence of values a[1], - * a[2], a[3], ... - * - * tol - * Relative tolerance for convergence. Specifically, stop iteration - * as soon as `abs(a[n]) <= tol * abs(S[n])` for some n >= 1. - * - * max_terms - * Maximum number of terms after a[0] to evaluate. It should be set - * large enough such that the convergence criterion is guaranteed - * to have been satisfied within that many terms if there is no - * rounding error. - * - * init_val - * a[0]. Default is zero. The type of this parameter (T) is used - * for intermediary computations as well as the result. - * - * Return Value - * ------------ - * If the convergence criterion is satisfied by some `n <= max_terms`, - * returns `(S[n], n)`. Otherwise, returns `(S[max_terms], 0)`. - */ - template > - XSF_HOST_DEVICE std::pair - series_eval_kahan(Generator &&g, real_type_t tol, std::uint64_t max_terms, T init_val = T(0)) { - - using std::abs; - T sum = init_val; - T comp = T(0); - for (std::uint64_t i = 0; i < max_terms; ++i) { - T term = g(); - kahan_step(sum, comp, term); - if (abs(term) <= tol * abs(sum)) { - return {sum, i + 1}; - } - } - return {sum, 0}; - } - - /* Generator that yields the difference of successive convergents of a - * continued fraction. - * - * Let f[n] denote the n-th convergent of a continued fraction: - * - * a[1] a[2] a[n] - * f[n] = b[0] + ------ ------ ... ---- - * b[1] + b[2] + b[n] - * - * with f[0] = b[0]. This generator yields the sequence of values - * f[1]-f[0], f[2]-f[1], f[3]-f[2], ... - * - * Constructor Arguments - * --------------------- - * cf - * Reference to generator that yields the terms of the continued - * fraction as (numerator, denominator) pairs, starting from - * (a[1], b[1]). - * - * `cf` must outlive the ContinuedFractionSeriesGenerator object. - * - * The constructed object always eagerly retrieves the next term - * of the continued fraction. Specifically, (a[1], b[1]) is - * retrieved upon construction, and (a[n], b[n]) is retrieved after - * (n-1) calls of `()`. - * - * Type Arguments - * -------------- - * T - * Type in which computations are performed and results are turned. - * - * Remarks - * ------- - * The series is computed using the recurrence relation described in [1]. - * Let v[n], n >= 1 denote the terms of the series. Then - * - * v[1] = a[1] / b[1] - * v[n] = v[n-1] * r[n-1], n >= 2 - * - * where - * - * -(a[n] + a[n] * r[n-1]) - * r[1] = 0, r[n] = ------------------------------------------, n >= 2 - * (a[n] + a[n] * r[n-1]) + (b[n] * b[n-1]) - * - * No error checking is performed. The caller must ensure that all terms - * are finite and that intermediary computations do not trigger floating - * point exceptions such as overflow. - * - * The numerical stability of this method depends on the characteristics - * of the continued fraction being evaluated. - * - * Reference - * --------- - * [1] Gautschi, W. (1967). “Computational Aspects of Three-Term - * Recurrence Relations.” SIAM Review, 9(1):24-82. - */ - template >> - class ContinuedFractionSeriesGenerator { - - public: - XSF_HOST_DEVICE explicit ContinuedFractionSeriesGenerator(Generator &cf) : cf_(cf) { init(); } - - XSF_HOST_DEVICE T operator()() { - T v = v_; - advance(); - return v; - } - - private: - XSF_HOST_DEVICE void init() { - auto [num, denom] = cf_(); - T a = num; - T b = denom; - r_ = T(0); - v_ = a / b; - b_ = b; - } - - XSF_HOST_DEVICE void advance() { - auto [num, denom] = cf_(); - T a = num; - T b = denom; - T p = a + a * r_; - T q = p + b * b_; - r_ = -p / q; - v_ = v_ * r_; - b_ = b; - } - - Generator &cf_; // reference to continued fraction generator - T v_; // v[n] == f[n] - f[n-1], n >= 1 - T r_; // r[1] = 0, r[n] = v[n]/v[n-1], n >= 2 - T b_; // last denominator, i.e. b[n-1] - }; - - /* Converts a continued fraction into a series whose terms are the - * difference of its successive convergents. - * - * See ContinuedFractionSeriesGenerator for details. - */ - template >> - XSF_HOST_DEVICE ContinuedFractionSeriesGenerator continued_fraction_series(Generator &cf) { - return ContinuedFractionSeriesGenerator(cf); - } - - /* Find initial bracket for a bracketing scalar root finder. A valid bracket is a pair of points a < b for - * which the signs of f(a) and f(b) differ. If f(x0) = 0, where x0 is the initial guess, this bracket finder - * will return the bracket (x0, x0). It is expected that the rootfinder will check if the bracket - * endpoints are roots. - * - * This is a private function intended specifically for the situation where - * the goal is to invert a CDF function F for a parametrized family of distributions with respect to one - * parameter, when the other parameters are known, and where F is monotonic with respect to the unknown parameter. - */ - template - XSF_HOST_DEVICE inline std::tuple bracket_root_for_cdf_inversion( - Function func, double x0, double xmin, double xmax, double step0_left, - double step0_right, double factor_left, double factor_right, bool increasing, std::uint64_t maxiter - ) { - double y0 = func(x0); - - if (y0 == 0) { - // Initial guess is correct. - return {x0, x0, y0, y0, 0}; - } - - double y0_sgn = std::signbit(y0); - - bool search_left; - /* The frontier is the new leading endpoint of the expanding bracket. The - * interior endpoint trails behind the frontier. In each step, the old frontier - * endpoint becomes the new interior endpoint. */ - double interior, frontier, y_interior, y_frontier, y_interior_sgn, y_frontier_sgn, boundary, factor; - if ((increasing && y0 < 0) || (!increasing && y0 > 0)) { - /* If func is increasing and func(x_right) < 0 or if func is decreasing and - * f(y_right) > 0, we should expand the bracket to the right. */ - interior = x0, y_interior = y0; - frontier = x0 + step0_right; - y_interior_sgn = y0_sgn; - search_left = false; - boundary = xmax; - factor = factor_right; - } else { - /* Otherwise we move and expand the bracket to the left. */ - interior = x0, y_interior = y0; - frontier = x0 + step0_left; - y_interior_sgn = y0_sgn; - search_left = true; - boundary = xmin; - factor = factor_left; - } - - bool reached_boundary = false; - for (std::uint64_t i = 0; i < maxiter; i++) { - y_frontier = func(frontier); - y_frontier_sgn = std::signbit(y_frontier); - if (y_frontier_sgn != y_interior_sgn || (y_frontier == 0.0)) { - /* Stopping condition, func evaluated at endpoints of bracket has opposing signs, - * meeting requirement for bracketing root finder. (Or endpoint has reached a - * zero.) */ - if (search_left) { - /* Ensure we return an interval (a, b) with a < b. */ - std::swap(interior, frontier); - std::swap(y_interior, y_frontier); - } - return {interior, frontier, y_interior, y_frontier, 0}; - } - if (reached_boundary) { - /* We've reached a boundary point without finding a root . */ - return { - std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), - search_left ? 1 : 2 - }; - } - double step = (frontier - interior) * factor; - interior = frontier; - y_interior = y_frontier; - y_interior_sgn = y_frontier_sgn; - frontier += step; - if ((search_left && frontier <= boundary) || (!search_left && frontier >= boundary)) { - /* If the frontier has reached the boundary, set a flag so the algorithm will know - * not to search beyond this point. */ - frontier = boundary; - reached_boundary = true; - } - } - /* Failed to converge within maxiter iterations. If maxiter is sufficiently high and - * factor_left and factor_right are set appropriately, this should only happen due to - * a bug in this function. Limiting the number of iterations is a defensive programming measure. */ - return { - std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), 3 - }; - } - - /* Find root of a scalar function using Chandrupatla's algorithm */ - template - XSF_HOST_DEVICE inline std::pair find_root_chandrupatla( - Function func, double x1, double x2, double f1, double f2, double rtol, - double atol, std::uint64_t maxiter - ) { - if (f1 == 0) { - return {x1, 0}; - } - if (f2 == 0) { - return {x2, 0}; - } - double t = 0.5, x3, f3; - for (uint64_t i = 0; i < maxiter; i++) { - double x = x1 + t * (x2 - x1); - double f = func(x); - if (std::signbit(f) == std::signbit(f1)) { - x3 = x1; - x1 = x; - f3 = f1; - f1 = f; - } else { - x3 = x2; - x2 = x1; - x1 = x; - f3 = f2; - f2 = f1; - f1 = f; - } - double xm, fm; - if (std::abs(f2) < std::abs(f1)) { - xm = x2; - fm = f2; - } else { - xm = x1; - fm = f1; - } - double tol = 2.0 * rtol * std::abs(xm) + 0.5 * atol; - double tl = tol / std::abs(x2 - x1); - if (tl > 0.5 || fm == 0) { - return {xm, 0}; - } - double xi = (x1 - x2) / (x3 - x2); - double phi = (f1 - f2) / (f3 - f2); - double fl = 1.0 - std::sqrt(1.0 - xi); - double fh = std::sqrt(xi); - - if ((fl < phi) && (phi < fh)) { - t = (f1 / (f2 - f1)) * (f3 / (f2 - f3)) + (f1 / (f3 - f1)) * (f2 / (f3 - f2)) * ((x3 - x1) / (x2 - x1)); - } else { - t = 0.5; - } - t = std::fmin(std::fmax(t, tl), 1.0 - tl); - } - return {std::numeric_limits::quiet_NaN(), 1}; - } - -} // namespace detail -} // namespace xsf diff --git a/scipy/special/xsf/trig.h b/scipy/special/xsf/trig.h deleted file mode 100644 index a0221e00bbe3..000000000000 --- a/scipy/special/xsf/trig.h +++ /dev/null @@ -1,164 +0,0 @@ -/* Translated from Cython into C++ by SciPy developers in 2023. - * - * Original author: Josh Wilson, 2016. - */ - -/* Implement sin(pi*z) and cos(pi*z) for complex z. Since the periods - * of these functions are integral (and thus better representable in - * floating point), it's possible to compute them with greater accuracy - * than sin(z), cos(z). - */ - -#pragma once - -#include "cephes/sindg.h" -#include "cephes/tandg.h" -#include "cephes/trig.h" -#include "cephes/unity.h" -#include "config.h" -#include "evalpoly.h" - -namespace xsf { - -template -XSF_HOST_DEVICE T sinpi(T x) { - return cephes::sinpi(x); -} - -template -XSF_HOST_DEVICE std::complex sinpi(std::complex z) { - T x = z.real(); - T piy = M_PI * z.imag(); - T abspiy = std::abs(piy); - T sinpix = cephes::sinpi(x); - T cospix = cephes::cospi(x); - - if (abspiy < 700) { - return {sinpix * std::cosh(piy), cospix * std::sinh(piy)}; - } - - /* Have to be careful--sinh/cosh could overflow while cos/sin are small. - * At this large of values - * - * cosh(y) ~ exp(y)/2 - * sinh(y) ~ sgn(y)*exp(y)/2 - * - * so we can compute exp(y/2), scale by the right factor of sin/cos - * and then multiply by exp(y/2) to avoid overflow. */ - T exphpiy = std::exp(abspiy / 2); - T coshfac; - T sinhfac; - if (exphpiy == std::numeric_limits::infinity()) { - if (sinpix == 0.0) { - // Preserve the sign of zero. - coshfac = std::copysign(0.0, sinpix); - } else { - coshfac = std::copysign(std::numeric_limits::infinity(), sinpix); - } - if (cospix == 0.0) { - // Preserve the sign of zero. - sinhfac = std::copysign(0.0, cospix); - } else { - sinhfac = std::copysign(std::numeric_limits::infinity(), cospix); - } - return {coshfac, sinhfac}; - } - - coshfac = 0.5 * sinpix * exphpiy; - sinhfac = 0.5 * cospix * exphpiy; - return {coshfac * exphpiy, sinhfac * exphpiy}; -} - -template -XSF_HOST_DEVICE T cospi(T x) { - return cephes::cospi(x); -} - -template -XSF_HOST_DEVICE std::complex cospi(std::complex z) { - T x = z.real(); - T piy = M_PI * z.imag(); - T abspiy = std::abs(piy); - T sinpix = cephes::sinpi(x); - T cospix = cephes::cospi(x); - - if (abspiy < 700) { - return {cospix * std::cosh(piy), -sinpix * std::sinh(piy)}; - } - - // See csinpi(z) for an idea of what's going on here. - T exphpiy = std::exp(abspiy / 2); - T coshfac; - T sinhfac; - if (exphpiy == std::numeric_limits::infinity()) { - if (sinpix == 0.0) { - // Preserve the sign of zero. - coshfac = std::copysign(0.0, cospix); - } else { - coshfac = std::copysign(std::numeric_limits::infinity(), cospix); - } - if (cospix == 0.0) { - // Preserve the sign of zero. - sinhfac = std::copysign(0.0, sinpix); - } else { - sinhfac = std::copysign(std::numeric_limits::infinity(), sinpix); - } - return {coshfac, sinhfac}; - } - - coshfac = 0.5 * cospix * exphpiy; - sinhfac = 0.5 * sinpix * exphpiy; - return {coshfac * exphpiy, sinhfac * exphpiy}; -} - -template -XSF_HOST_DEVICE T sindg(T x) { - return cephes::sindg(x); -} - -template <> -XSF_HOST_DEVICE inline float sindg(float x) { - return sindg(static_cast(x)); -} - -template -XSF_HOST_DEVICE T cosdg(T x) { - return cephes::cosdg(x); -} - -template <> -XSF_HOST_DEVICE inline float cosdg(float x) { - return cosdg(static_cast(x)); -} - -template -XSF_HOST_DEVICE T tandg(T x) { - return cephes::tandg(x); -} - -template <> -XSF_HOST_DEVICE inline float tandg(float x) { - return tandg(static_cast(x)); -} - -template -XSF_HOST_DEVICE T cotdg(T x) { - return cephes::cotdg(x); -} - -template <> -XSF_HOST_DEVICE inline float cotdg(float x) { - return cotdg(static_cast(x)); -} - -inline double radian(double d, double m, double s) { return cephes::radian(d, m, s); } - -inline float radian(float d, float m, float s) { - return radian(static_cast(d), static_cast(m), static_cast(s)); -} - -inline double cosm1(double x) { return cephes::cosm1(x); } - -inline float cosm1(float x) { return cosm1(static_cast(x)); } - -} // namespace xsf diff --git a/scipy/special/xsf/wright_bessel.h b/scipy/special/xsf/wright_bessel.h deleted file mode 100644 index 77cf165a0fc3..000000000000 --- a/scipy/special/xsf/wright_bessel.h +++ /dev/null @@ -1,843 +0,0 @@ -/* Translated from Cython into C++ by SciPy developers in 2023. - * Original header with Copyright information appears below. - */ - -/* Implementation of Wright's generalized Bessel function Phi, see - * https://dlmf.nist.gov/10.46.E1 - * - * Copyright: Christian Lorentzen - * - * Distributed under the same license as SciPy - * - * - * Implementation Overview: - * - * First, different functions are implemented valid for certain domains of the - * three arguments. - * Finally they are put together in wright_bessel. See the docstring of - * that function for more details. - */ - -#pragma once - -#include "cephes/lanczos.h" -#include "cephes/polevl.h" -#include "cephes/rgamma.h" -#include "config.h" -#include "digamma.h" -#include "error.h" - -namespace xsf { - -namespace detail { - // rgamma_zero: smallest value x for which rgamma(x) == 0 as x gets large - constexpr double rgamma_zero = 178.47241115886637; - - XSF_HOST_DEVICE inline double exp_rgamma(double x, double y) { - /* Compute exp(x) / gamma(y) = exp(x) * rgamma(y). - * - * This helper function avoids overflow by using the lanczos - * approximation of the gamma function. - */ - return std::exp(x + (1 - std::log(y + cephes::lanczos_g - 0.5)) * (y - 0.5)) / - cephes::lanczos_sum_expg_scaled(y); - } - - XSF_HOST_DEVICE inline double wb_series(double a, double b, double x, unsigned int nstart, unsigned int nstop) { - /* 1. Taylor series expansion in x=0 for x <= 1. - * - * Phi(a, b, x) = sum_k x^k / k! / Gamma(a*k + b) - * - * Note that every term, and therefore also Phi(a, b, x) is - * monotone decreasing with increasing a or b. - */ - double xk_k = std::pow(x, nstart) * cephes::rgamma(nstart + 1); // x^k/k! - double res = xk_k * cephes::rgamma(nstart * a + b); - // term k=nstart+1, +2, +3, ... - if (nstop > nstart) { - // series expansion until term k such that a*k+b <= rgamma_zero - unsigned int k_max = std::floor((rgamma_zero - b) / a); - if (nstop > k_max) { - nstop = k_max; - } - for (unsigned int k = nstart + 1; k < nstop; k++) { - xk_k *= x / k; - res += xk_k * cephes::rgamma(a * k + b); - } - } - return res; - } - - template - XSF_HOST_DEVICE inline double wb_large_a(double a, double b, double x, int n) { - /* 2. Taylor series expansion in x=0, for large a. - * - * Phi(a, b, x) = sum_k x^k / k! / Gamma(a*k + b) - * - * Use Stirling's formula to find k=k_max, the maximum term. - * Then use n terms of Taylor series around k_max. - */ - int k_max = static_cast(std::pow(std::pow(a, -a) * x, 1.0 / (1 + a))); - - int nstart = k_max - n / 2; - if (nstart < 0) { - nstart = 0; - } - - double res = 0; - double lnx = std::log(x); - // For numerical stability, we factor out the maximum term exp(..) with k=k_max - // but only if it is larger than 0. - double max_exponent = std::fmax(0, k_max * lnx - cephes::lgam(k_max + 1) - cephes::lgam(a * k_max + b)); - for (int k = nstart; k < nstart + n; k++) { - res += std::exp(k * lnx - cephes::lgam(k + 1) - cephes::lgam(a * k + b) - max_exponent); - } - - if (!log_wb) { - res *= std::exp(max_exponent); - } else { - // logarithm of Wright's function - res = max_exponent + std::log(res); - } - return res; - } - - template - XSF_HOST_DEVICE inline double wb_small_a(double a, double b, double x, int order) { - /* 3. Taylor series in a=0 up to order 5, for tiny a and not too large x - * - * Phi(a, b, x) = exp(x)/Gamma(b) - * (1 - a*x * Psi(b) + a^2/2*x*(1+x) * (Psi(b)^2 - Psi'(b) - + ... ) - + O(a^6)) - * - * where Psi is the digamma function. - * - * Parameter order takes effect only when b > 1e-3 and 2 <= order <= 5, - * otherwise it defaults to 2, or if b <= 1e-3, to 5. The lower order is, - * the fewer polygamma functions have to be computed. - * - * Call: python _precompute/wright_bessel.py 1 - * - * For small b, i.e. b <= 1e-3, cancellation of poles of digamma(b)/Gamma(b) - * and polygamma needs to be carried out => series expansion in a=0 to order 5 - * and in b=0 to order 4. - * Call: python _precompute/wright_bessel.py 2 - */ - double A[6]; // coefficients of a^k (1, -x * Psi(b), ...) - double B[6]; // powers of b^k/k! or terms in polygamma functions - constexpr double C[5] = { // coefficients of a^k1 * b^k2 - 1.0000000000000000, // C[0] - 1.1544313298030657, // C[1] - -3.9352684291215233, // C[2] - -1.0080632408182857, // C[3] - 19.984633365874979, // C[4] - }; - double X[6] = { // polynomials in x; - 1, // X[0] - x, // X[1] - x * (x + 1), // X[2] - x * (x * (x + 3) + 1), // X[3] - x * (x * (x * (x + 6) + 7) + 1), // X[4] - x * (x * (x * (x * (x + 10) + 25) + 15) + 1), // X[5] - }; - double res; - - if (b <= 1E-3) { - /* Series expansion of both a and b up to order 5: - * M_PI = pi - * M_EG = Euler Gamma aka Euler Mascheroni constant - * M_Z3 = zeta(3) - * C[0] = 1 - * C[1] = 2*M_EG - * C[2] = 3*M_EG^2 - M_PI^2/2 - * C[3] = 4*M_EG^3 - 2*M_EG*M_PI^2 + 8*M_Z3 - * C[4] = 5*M_EG^4 - 5*M_EG^2*M_PI^2 + 40*M_EG*M_Z3 + M_PI^4/12 - */ - B[0] = 1.; - for (int k = 1; k < 5; k++) { - B[k] = b / k * B[k - 1]; - } - // Note that polevl assumes inverse ordering => A[5] = 0th term - A[5] = cephes::rgamma(b); - A[4] = X[1] * (C[0] + C[1] * b + C[2] * B[2] + C[3] * B[3] + C[4] * B[4]); - A[3] = X[2] / 2. * (C[1] + C[2] * b + C[3] * B[2] + C[4] * B[3]); - A[2] = X[3] / 6. * (C[2] + C[3] * b + C[4] * B[2]); - A[1] = X[4] / 24. * (C[3] + C[4] * b); - A[0] = X[5] / 120. * C[4]; - // res = exp(x) * (A[5] + A[4] * a + A[3] * a^2 + A[2] * a^3 + ...) - if (!log_wb) { - res = exp(x) * cephes::polevl(a, A, 5); - } else { - // logarithm of Wright's function - res = x + std::log(cephes::polevl(a, A, 5)); - } - } else { - /* Phi(a, b, x) = exp(x)/gamma(b) * sum(A[i] * X[i] * B[i], i=0..5) - * A[n] = a^n/n! - * But here, we repurpose A[n] = X[n] * B[n] / n! - * Note that polevl assumes inverse ordering => A[order] = 0th term */ - double dg = digamma(b); - // pg1 = polygamma(1, b) - double pg1 = cephes::zeta(2, b); - if (order <= 2) { - res = 1 + a * x * (-dg + 0.5 * a * (1 + x) * (dg * dg - pg1)); - } else { - if (order > 5) { - order = 5; - } - // pg2 = polygamma(2, b) - double pg2 = -2 * cephes::zeta(3, b); - B[0] = 1; - B[1] = -dg; - B[2] = dg * dg - pg1; - B[3] = (-dg * dg + 3 * pg1) * dg - pg2; - A[order] = 1; - A[order - 1] = X[1] * B[1]; - A[order - 2] = X[2] * B[2] / 2.; - A[order - 3] = X[3] * B[3] / 6.; - if (order >= 4) { - // double pg3 = polygamma(3, b) - double pg3 = 6 * cephes::zeta(4, b); - B[4] = ((dg * dg - 6 * pg1) * dg + 4 * pg2) * dg + 3 * pg1 * pg1 - pg3; - A[order - 4] = X[4] * B[4] / 24.; - if (order >= 5) { - // pg4 = polygamma(4, b) - double pg4 = -24 * cephes::zeta(5, b); - B[5] = - ((((-dg * dg + 10 * pg1) * dg - 10 * pg2) * dg - 15 * pg1 * pg1 + 5 * pg3) * dg + - 10 * pg1 * pg2 - pg4); - A[order - 5] = X[5] * B[5] / 120.; - } - } - res = cephes::polevl(a, A, order); - } - // res *= exp(x) * rgamma(b) - if (!log_wb) { - res *= exp_rgamma(x, b); - } else { - // logarithm of Wright's function - res = x - cephes::lgam(b) + std::log(res); - } - } - return res; - } - - template - XSF_HOST_DEVICE inline double wb_asymptotic(double a, double b, double x) { - /* 4. Asymptotic expansion for large x up to order 8 - * - * Phi(a, b, x) ~ Z^(1/2-b) * exp((1+a)/a * Z) * sum_k (-1)^k * C_k / Z^k - * - * with Z = (a*x)^(1/(1+a)). - * Call: python _precompute/wright_bessel.py 3 - */ - double A[15]; // powers of a - double B[17]; // powers of b - double Ap1[9]; // powers of (1+a) - double C[9]; // coefficients of asymptotic series a_k - - A[0] = 1.; - B[0] = 1.; - Ap1[0] = 1.; - for (int k = 1; k < 15; k++) { - A[k] = A[k - 1] * a; - } - for (int k = 1; k < 17; k++) { - B[k] = B[k - 1] * b; - } - for (int k = 1; k < 9; k++) { - Ap1[k] = Ap1[k - 1] * (1 + a); - } - - C[0] = 1. / std::sqrt(2. * M_PI * Ap1[1]); - - C[1] = C[0] / (24 * Ap1[1]); - C[1] *= (2 * a + 1) * (2 + a) - 12 * b * (1 + a - b); - - C[2] = C[0] / (1152 * Ap1[2]); - C[2] *= - (144 * B[4] - 96 * B[3] * (5 * a + 1) + 24 * B[2] * (20 * A[2] + 5 * a - 4) - - 24 * b * Ap1[1] * (6 * A[2] - 7 * a - 2) + (a + 2) * (2 * a + 1) * (2 * A[2] - 19 * a + 2)); - - C[3] = C[0] / (414720 * Ap1[3]); - C[3] *= - (8640 * B[6] - 8640 * B[5] * (7 * a - 1) + 10800 * B[4] * (14 * A[2] - 7 * a - 2) - - 1440 * B[3] * (112 * A[3] - 147 * A[2] - 63 * a + 8) + - 180 * B[2] * (364 * A[4] - 1288 * A[3] - 567 * A[2] + 392 * a + 76) - - 180 * b * Ap1[1] * (20 * A[4] - 516 * A[3] + 417 * A[2] + 172 * a - 12) - - (a + 2) * (2 * a + 1) * (556 * A[4] + 1628 * A[3] - 9093 * A[2] + 1628 * a + 556)); - - C[4] = C[0] / (39813120 * Ap1[4]); - C[4] *= - (103680 * B[8] - 414720 * B[7] * (3 * a - 1) + 725760 * B[6] * a * (8 * a - 7) - - 48384 * B[5] * (274 * A[3] - 489 * A[2] + 39 * a + 26) + - 30240 * B[4] * (500 * A[4] - 1740 * A[3] + 495 * A[2] + 340 * a - 12) - - 2880 * B[3] * (2588 * A[5] - 19780 * A[4] + 14453 * A[3] + 9697 * A[2] - 1892 * a - 404) + - 48 * B[2] * - (11488 * A[6] - 547836 * A[5] + 1007484 * A[4] + 593353 * A[3] - 411276 * A[2] - 114396 * a + 4288) + - 48 * b * Ap1[1] * - (7784 * A[6] + 48180 * A[5] - 491202 * A[4] + 336347 * A[3] + 163734 * A[2] - 28908 * a - 5560) - - (a + 2) * (2 * a + 1) * - (4568 * A[6] - 226668 * A[5] - 465702 * A[4] + 2013479 * A[3] - 465702 * A[2] - 226668 * a + 4568)); - - C[5] = C[0] / (6688604160. * Ap1[5]); - C[5] *= - (1741824 * B[10] - 2903040 * B[9] * (11 * a - 5) + 2177280 * B[8] * (110 * A[2] - 121 * a + 14) - - 580608 * B[7] * (1628 * A[3] - 3333 * A[2] + 1023 * a + 52) + - 169344 * B[6] * (12364 * A[4] - 43648 * A[3] + 26763 * A[2] + 1232 * a - 788) - - 24192 * B[5] * (104852 * A[5] - 646624 * A[4] + 721391 * A[3] - 16841 * A[2] - 74096 * a + 148) + - 2016 * B[4] * - (710248 * A[6] - 8878716 * A[5] + 17928834 * A[4] - 3333407 * A[3] - 4339566 * A[2] + 287364 * a + - 89128) - - 1344 * B[3] * - (87824 * A[7] - 7150220 * A[6] + 29202756 * A[5] - 15113527 * A[4] - 14223011 * A[3] + 3462492 * A[2] + - 1137092 * a - 18896) - - 84 * B[2] * - (1690480 * A[8] + 14139136 * A[7] - 232575464 * A[6] + 296712592 * A[5] + 215856619 * A[4] - - 152181392 * A[3] - 47718440 * A[2] + 5813632 * a + 943216) + - 84 * b * Ap1[1] * - (82224 * A[8] - 5628896 * A[7] - 26466520 * A[6] + 168779208 * A[5] - 104808005 * A[4] - - 56259736 * A[3] + 15879912 * A[2] + 4020640 * a - 63952) + - (a + 2) * (2 * a + 1) * - (2622064 * A[8] + 12598624 * A[7] - 167685080 * A[6] - 302008904 * A[5] + 1115235367. * A[4] - - 302008904 * A[3] - 167685080 * A[2] + 12598624 * a + 2622064)); - - C[6] = C[0] / (4815794995200. * Ap1[6]); - C[6] *= - (104509440 * B[12] - 209018880 * B[11] * (13 * a - 7) + 574801920 * B[10] * (52 * A[2] - 65 * a + 12) - - 63866880 * B[9] * (2834 * A[3] - 6279 * A[2] + 2769 * a - 134) + - 23950080 * B[8] * (27404 * A[4] - 98228 * A[3] + 78663 * A[2] - 10868 * a - 1012) - - 13685760 * B[7] * (105612 * A[5] - 599196 * A[4] + 791843 * A[3] - 224913 * A[2] - 27612 * a + 4540) + - 2661120 * B[6] * - (693680 * A[6] - 6473532 * A[5] + 13736424 * A[4] - 7047469 * A[3] - 723840 * A[2] + 471588 * a + 7376 - ) - - 2661120 * B[5] * - (432536 * A[7] - 7850804 * A[6] + 27531114 * A[5] - 24234457 * A[4] - 703001 * A[3] + 3633474 * A[2] - - 36244 * a - 45128) + - 166320 * B[4] * - (548912 * A[8] - 75660832 * A[7] + 502902712 * A[6] - 764807992 * A[5] + 91248287 * A[4] + - 217811464 * A[3] - 20365384 * A[2] - 9776416 * a + 37936) + - 10080 * B[3] * - (18759728 * A[9] + 165932208 * A[8] - 4710418440. * A[7] + 13686052536. * A[6] - 5456818809. * A[5] - - 6834514245. * A[4] + 1919299512. * A[3] + 752176152 * A[2] - 45661200 * a - 8616848) - - 360 * B[2] * - (32743360 * A[10] - 3381871792. * A[9] - 21488827776. * A[8] + 200389923864. * A[7] - - 198708005340. * A[6] - 171633799779. * A[5] + 123124874028. * A[4] + 40072774872. * A[3] - - 9137993280. * A[2] - 1895843248. * a + 18929728) - - 360 * b * Ap1[1] * - (57685408 * A[10] + 406929456 * A[9] - 6125375760. * A[8] - 27094918920. * A[7] + - 128752249410. * A[6] - 74866710561. * A[5] - 42917416470. * A[4] + 16256951352. * A[3] + - 4375268400. * A[2] - 316500688 * a - 47197152) + - (a + 2) * (2 * a + 1) * - (167898208 * A[10] - 22774946512. * A[9] - 88280004528. * A[8] + 611863976472. * A[7] + - 1041430242126. * A[6] - 3446851131657. * A[5] + 1041430242126. * A[4] + 611863976472. * A[3] - - 88280004528. * A[2] - 22774946512. * a + 167898208)); - - C[7] = C[0] / (115579079884800. * Ap1[7]); - C[7] *= - (179159040 * B[14] - 1254113280. * B[13] * (5 * a - 3) + 1358622720. * B[12] * (70 * A[2] - 95 * a + 22) - - 905748480 * B[11] * (904 * A[3] - 2109 * A[2] + 1119 * a - 112) + - 1245404160. * B[10] * (3532 * A[4] - 12824 * A[3] + 11829 * A[2] - 2824 * a + 44) - - 59304960 * B[9] * (256820 * A[5] - 1397680 * A[4] + 2025545 * A[3] - 869495 * A[2] + 52000 * a + 8788) + - 14826240 * B[8] * - (2274536 * A[6] - 18601572 * A[5] + 40698318 * A[4] - 28230079 * A[3] + 3916398 * A[2] + 832668 * a - - 65176) - - 59304960 * B[7] * - (760224 * A[7] - 9849164 * A[6] + 32495784 * A[5] - 34813869 * A[4] + 9175207 * A[3] + 1898688 * A[2] - - 469788 * a - 13184) + - 25945920 * B[6] * - (1167504 * A[8] - 28779840 * A[7] + 149752856 * A[6] - 246026112 * A[5] + 111944073 * A[4] + - 18341600 * A[3] - 12131496 * A[2] - 274368 * a + 102800) - - 157248 * B[5] * - (12341872 * A[9] - 3122991216. * A[8] + 29900054232. * A[7] - 78024816720. * A[6] + - 58914656739. * A[5] + 4637150811. * A[4] - 11523402480. * A[3] + 236218968 * A[2] + 337923216 * a + - 1592048) - - 28080 * B[4] * - (265154912 * A[10] + 2276098704. * A[9] - 105569461008. * A[8] + 496560666360. * A[7] - - 627891462858. * A[6] + 41935358025. * A[5] + 203913875814. * A[4] - 23984801544. * A[3] - - 13869306000. * A[2] + 372786832 * a + 103532640) + - 1440 * B[3] * - (310292864 * A[11] - 55169117872. * A[10] - 358957020112. * A[9] + 5714152556088. * A[8] - - 13241597459352. * A[7] + 4220720097141. * A[6] + 6845418090249. * A[5] - 2129559215808. * A[4] - - 909225098472. * A[3] + 107518582576. * A[2] + 25619444368. * a - 113832704) + - 12 * B[2] * - (135319651136. * A[12] + 1119107842176. * A[11] - 22193518174320. * A[10] - 133421793595520. * A[9] + - 860103051087996. * A[8] - 703353374803080. * A[7] - 704240127687381. * A[6] + - 513111704637960. * A[5] + 166909061348316. * A[4] - 57671564069120. * A[3] - 12453426246000. * A[2] + - 695901207936. * a + 93786157376.) - - 12 * b * Ap1[1] * - (4365353408. * A[12] - 720248637504. * A[11] - 4222331152560. * A[10] + 29413934270560. * A[9] + - 132123980710980. * A[8] - 511247376962820. * A[7] + 283403639131779. * A[6] + - 170415792320940. * A[5] - 79274388426588. * A[4] - 21009953050400. * A[3] + 3284035340880. * A[2] + - 589294339776. * a - 3693760576.) - - (a + 2) * (2 * a + 1) * - (34221025984. * A[12] + 226022948160. * A[11] - 5067505612464. * A[10] - 18868361443936. * A[9] + - 86215425028308. * A[8] + 143500920544692. * A[7] - 437682618704613. * A[6] + 143500920544692. * A[5] + - 86215425028308. * A[4] - 18868361443936. * A[3] - 5067505612464. * A[2] + 226022948160. * a + - 34221025984.)); - - C[8] = C[0] / (22191183337881600. * Ap1[8]); - C[8] *= - (2149908480. * B[16] - 5733089280. * B[15] * (17 * a - 11) + - 7166361600. * B[14] * (272 * A[2] - 391 * a + 104) - - 3344302080. * B[13] * (6766 * A[3] - 16371 * A[2] + 9741 * a - 1306) + - 1811496960. * B[12] * (93092 * A[4] - 341564 * A[3] + 344199 * A[2] - 104924 * a + 6308) - - 517570560 * B[11] * - (1626220 * A[5] - 8641508 * A[4] + 13274773 * A[3] - 6952303 * A[2] + 1007420 * a + 5564) + - 284663808 * B[10] * - (9979136 * A[6] - 75766892 * A[5] + 169256148 * A[4] - 136824959 * A[3] + 35714348 * A[2] - - 463692 * a - 293664) - - 1423319040. * B[9] * - (4466648 * A[7] - 49231116 * A[6] + 157507414 * A[5] - 187114257 * A[4] + 78372295 * A[3] - - 4470082 * A[2] - 1913996 * a + 82424) + - 266872320 * B[8] * - (33133136 * A[8] - 564264544 * A[7] + 2618606424. * A[6] - 4491310104. * A[5] + 2853943765. * A[4] - - 374694552 * A[3] - 135365288 * A[2] + 17623968 * a + 696912) - - 2156544 * B[7] * - (2914256144. * A[9] - 93491712432. * A[8] + 664876176984. * A[7] - 1661362937880. * A[6] + - 1563719627313. * A[5] - 382840842843. * A[4] - 115399415640. * A[3] + 34565562936. * A[2] + - 1609337232. * a - 217321904) + - 179712 * B[6] * - (1266018560. * A[10] - 789261834512. * A[9] + 10186841596896. * A[8] - 38877799073352. * A[7] + - 54334425968952. * A[6] - 22529574889533. * A[5] - 5132942328000. * A[4] + 3438377465592. * A[3] + - 84287641248. * A[2] - 72493479440. * a - 807415936) + - 13824 * B[5] * - (156356794976. * A[11] + 1180898077328. * A[10] - 90615270907936. * A[9] + 609258947056248. * A[8] - - 1312655191366722. * A[7] + 885900509321745. * A[6] + 112162151855265. * A[5] - - 212803071513258. * A[4] + 6805217831352. * A[3] + 10051742651296. * A[2] - 55035924848. * a - - 52946379296.) - - 576 * B[4] * - (143943926464. * A[12] - 60115486481856. * A[11] - 376366989757200. * A[10] + - 9534223075576160. * A[9] - 35603777465262396. * A[8] + 39375990156664980. * A[7] - - 868175004137259. * A[6] - 14279180718355020. * A[5] + 1985747535239364. * A[4] + - 1264001337603680. * A[3] - 75972792514320. * A[2] - 23855850572736. * a - 4996648256.) - - 384 * B[3] * - (2038525473856. * A[13] + 16057322146112. * A[12] - 502133360559024. * A[11] - - 2985686417468080. * A[10] + 32418922182093292. * A[9] - 63665380623022452. * A[8] + - 16481208821092575. * A[7] + 34161547357596099. * A[6] - 11490298497454932. * A[5] - - 5117272758337156. * A[4] + 933703210750480. * A[3] + 234855186762000. * A[2] - 7860524600000. * a - - 1226607567040.) + - 96 * B[2] * - (324439754752. * A[14] - 77231415197120. * A[13] - 539102931841856. * A[12] + - 4618258299956336. * A[11] + 28588485529469792. * A[10] - 141383982651179428. * A[9] + - 98783147840417772. * A[8] + 112831723492305801. * A[7] - 83329761150975036. * A[6] - - 26553582937192900. * A[5] + 12469117738765952. * A[4] + 2587165396642160. * A[3] - - 340406368038080. * A[2] - 53659641606080. * a + 219671272960.) + - 96 * b * Ap1[1] * - (1026630779520. * A[14] + 8781958472768. * A[13] - 210659786204384. * A[12] - - 1222283505284208. * A[11] + 5064251967491416. * A[10] + 24013052207628140. * A[9] - - 79710880160087370. * A[8] + 42596558293213227. * A[7] + 26570293386695790. * A[6] - - 14407831324576884. * A[5] - 3617322833922440. * A[4] + 950664948554384. * A[3] + - 172358006894496. * A[2] - 7430887938496. * a - 889746675584.) - - (a + 2) * (2 * a + 1) * - (573840801152. * A[14] - 156998277198784. * A[13] - 898376974770592. * A[12] + - 8622589006459984. * A[11] + 32874204024803560. * A[10] - 111492707520083828. * A[9] - - 184768503480287646. * A[8] + 528612016938984183. * A[7] - 184768503480287646. * A[6] - - 111492707520083828. * A[5] + 32874204024803560. * A[4] + 8622589006459984. * A[3] - - 898376974770592. * A[2] - 156998277198784. * a + 573840801152.)); - - double Z = std::pow(a * x, 1 / Ap1[1]); - double Zp = 1.; - double res = C[0]; - for (int k = 1; k < 9; k++) { - Zp /= Z; - res += (k % 2 == 0 ? 1 : -1) * C[k] * Zp; - } - if (!log_wb) { - res *= std::pow(Z, 0.5 - b) * std::exp(Ap1[1] / a * Z); - } else { - // logarithm of Wright's function - res = std::log(Z) * (0.5 - b) + Ap1[1] / a * Z + std::log(res); - } - return res; - } - - XSF_HOST_DEVICE inline double wb_Kmod(double exp_term, double eps, double a, double b, double x, double r) { - /* Compute integrand Kmod(eps, a, b, x, r) for Gauss-Laguerre quadrature. - * - * K(a, b, x, r+eps) = exp(-r-eps) * Kmod(eps, a, b, x, r) - * - * Kmod(eps, a, b, x, r) = exp(x * (r+eps)^(-a) * cos(pi*a)) * (r+eps)^(-b) - * * sin(x * (r+eps)^(-a) * sin(pi*a) + pi * b) - * - * Note that we additionally factor out exp(exp_term) which helps with large - * terms in the exponent of exp(...) - */ - double x_r_a = x * std::pow(r + eps, -a); - return std::exp(x_r_a * cephes::cospi(a) + exp_term) * std::pow(r + eps, -b) * - std::sin(x_r_a * cephes::sinpi(a) + M_PI * b); - } - - XSF_HOST_DEVICE inline double wb_P(double exp_term, double eps, double a, double b, double x, double phi) { - /* Compute integrand P for Gauss-Legendre quadrature. - * - * P(eps, a, b, x, phi) = exp(eps * cos(phi) + x * eps^(-a) * cos(a*phi)) - * * cos(eps * sin(phi) - x * eps^(-a) * sin(a*phi) - * + (1-b)*phi) - * - * Note that we additionally factor out exp(exp_term) which helps with large - * terms in the exponent of exp(...) - */ - double x_eps_a = x * std::pow(eps, -a); - return std::exp(eps * std::cos(phi) + x_eps_a * std::cos(a * phi) + exp_term) * - std::cos(eps * std::sin(phi) - x_eps_a * std::sin(a * phi) + (1 - b) * phi); - } - - /* roots of laguerre polynomial of order 50 - * scipy.special.roots_laguerre(50)[0] or - * sympy.integrals.quadrature.import gauss_laguerre(50, 16)[0] */ - constexpr double wb_x_laguerre[] = { - 0.02863051833937908, 0.1508829356769337, 0.3709487815348964, 0.6890906998810479, 1.105625023539913, - 1.620961751102501, 2.23561037591518, 2.950183366641835, 3.765399774405782, 4.682089387559285, - 5.70119757478489, 6.823790909794551, 8.051063669390792, 9.384345308258407, 10.82510903154915, - 12.37498160875746, 14.03575459982991, 15.80939719784467, 17.69807093335025, 19.70414653546156, - 21.83022330657825, 24.0791514444115, 26.45405784125298, 28.95837601193738, 31.59588095662286, - 34.37072996309045, 37.28751061055049, 40.35129757358607, 43.56772026999502, 46.94304399160304, - 50.48426796312992, 54.19924488016862, 58.09682801724853, 62.18705417568891, 66.48137387844482, - 70.99294482661949, 75.73701154772731, 80.73140480247769, 85.99721113646323, 91.55969041253388, - 97.44956561485056, 103.7048912366923, 110.3738588076403, 117.5191982031112, 125.2254701334734, - 133.6120279227287, 142.8583254892541, 153.2603719726036, 165.3856433166825, 180.6983437092145 - }; - /* weights for laguerre polynomial of order 50 - * sympy.integrals.quadrature.import gauss_laguerre(50, 16)[1] */ - constexpr double wb_w_laguerre[] = { - 0.07140472613518988, 0.1471486069645884, 0.1856716275748313, 0.1843853825273539, - 0.1542011686063556, 0.1116853699022688, 0.07105288549019586, 0.04002027691150833, - 0.02005062308007171, 0.008960851203646281, 0.00357811241531566, 0.00127761715678905, - 0.0004080302449837189, 0.0001165288322309724, 2.974170493694165e-5, 6.777842526542028e-6, - 1.37747950317136e-6, 2.492886181720092e-7, 4.010354350427827e-8, 5.723331748141425e-9, - 7.229434249182665e-10, 8.061710142281779e-11, 7.913393099943723e-12, 6.81573661767678e-13, - 5.13242671658949e-14, 3.365624762437814e-15, 1.913476326965035e-16, 9.385589781827253e-18, - 3.950069964503411e-19, 1.417749517827512e-20, 4.309970276292175e-22, 1.101257519845548e-23, - 2.344617755608987e-25, 4.11854415463823e-27, 5.902246763596448e-29, 6.812008916553065e-31, - 6.237449498812102e-33, 4.452440579683377e-35, 2.426862352250487e-37, 9.852971481049686e-40, - 2.891078872318428e-42, 5.906162708112361e-45, 8.01287459750397e-48, 6.789575424396417e-51, - 3.308173010849252e-54, 8.250964876440456e-58, 8.848728128298018e-62, 3.064894889844417e-66, - 1.988708229330752e-71, 6.049567152238783e-78 - }; - /* roots of legendre polynomial of order 50 - * sympy.integrals.quadrature.import gauss_legendre(50, 16)[0] */ - constexpr double wb_x_legendre[] = { - -0.998866404420071, -0.9940319694320907, -0.9853540840480059, -0.9728643851066921, -0.9566109552428079, - -0.9366566189448779, -0.9130785566557919, -0.885967979523613, -0.8554297694299461, -0.8215820708593359, - -0.7845558329003993, -0.7444943022260685, -0.7015524687068223, -0.6558964656854394, -0.6077029271849502, - -0.5571583045146501, -0.5044581449074642, -0.4498063349740388, -0.3934143118975651, -0.3355002454194374, - -0.276288193779532, -0.2160072368760418, -0.1548905899981459, -0.09317470156008614, -0.03109833832718888, - 0.03109833832718888, 0.09317470156008614, 0.1548905899981459, 0.2160072368760418, 0.276288193779532, - 0.3355002454194374, 0.3934143118975651, 0.4498063349740388, 0.5044581449074642, 0.5571583045146501, - 0.6077029271849502, 0.6558964656854394, 0.7015524687068223, 0.7444943022260685, 0.7845558329003993, - 0.8215820708593359, 0.8554297694299461, 0.885967979523613, 0.9130785566557919, 0.9366566189448779, - 0.9566109552428079, 0.9728643851066921, 0.9853540840480059, 0.9940319694320907, 0.998866404420071 - }; - /* weights for legendre polynomial of order 50 - * sympy.integrals.quadrature.import gauss_legendre(50, 16)[1] */ - constexpr double wb_w_legendre[] = { - 0.002908622553155141, 0.006759799195745401, 0.01059054838365097, 0.01438082276148557, 0.01811556071348939, - 0.02178024317012479, 0.02536067357001239, 0.0288429935805352, 0.03221372822357802, 0.03545983561514615, - 0.03856875661258768, 0.0415284630901477, 0.04432750433880328, 0.04695505130394843, 0.04940093844946632, - 0.05165570306958114, 0.05371062188899625, 0.05555774480621252, 0.05718992564772838, 0.05860084981322245, - 0.05978505870426546, 0.06073797084177022, 0.06145589959031666, 0.06193606742068324, 0.06217661665534726, - 0.06217661665534726, 0.06193606742068324, 0.06145589959031666, 0.06073797084177022, 0.05978505870426546, - 0.05860084981322245, 0.05718992564772838, 0.05555774480621252, 0.05371062188899625, 0.05165570306958114, - 0.04940093844946632, 0.04695505130394843, 0.04432750433880328, 0.0415284630901477, 0.03856875661258768, - 0.03545983561514615, 0.03221372822357802, 0.0288429935805352, 0.02536067357001239, 0.02178024317012479, - 0.01811556071348939, 0.01438082276148557, 0.01059054838365097, 0.006759799195745401, 0.002908622553155141 - }; - /* Fitted parameters for optimal choice of eps - * Call: python _precompute/wright_bessel.py 4 */ - constexpr double wb_A[] = {0.41037, 0.30833, 6.9952, 18.382, -2.8566, 2.1122}; - - template - XSF_HOST_DEVICE inline double wright_bessel_integral(double a, double b, double x) { - /* 5. Integral representation - * - * K(a, b, x, r) = exp(-r + x * r^(-a) * cos(pi*a)) * r^(-b) - * * sin(x * r^(-a) * sin(pi*a) + pi * b) - * P(eps, a, b, x, phi) = exp(eps * cos(phi) + x * eps^(-a) * cos(a*phi)) - * * cos(eps * sin(phi) - x * eps^(-a) * sin(a*phi) - * + (1-b)*phi) - * - * Phi(a, b, x) = 1/pi * int_eps^inf K(a, b, x, r) * dr - * + eps^(1-b)/pi * int_0^pi P(eps, a, b, x, phi) * dphi - * - * for any eps > 0. - * - * Note that P has a misprint in Luchko (2008) Eq. 9, the cos(phi(beta-1)) at - * the end of the first line should be removed and the −sin(phi(beta−1)) at - * the end of the second line should read +(1-b)*phi. - * This integral representation introduced the free parameter eps (from the - * radius of complex contour integration). We try to choose eps such that - * the integrand behaves smoothly. Note that this is quite diffrent from how - * Luchko (2008) deals with eps: he is either looking for the limit eps -> 0 - * or he sets (silently) eps=1. But having the freedom to set eps is much more - * powerful for numerical evaluation. - * - * As K has a leading exp(-r), we factor this out and apply Gauss-Laguerre - * quadrature rule: - * - * int_0^inf K(a, b, x, r+eps) dr = exp(-eps) int_0^inf exp(-r) Kmod(.., r) dr - * - * Note the shift r -> r+eps to have integation from 0 to infinity. - * The integral over P is done via a Gauss-Legendre quadrature rule. - * - * Note: Hardest argument range is large z, large b and small eps. - */ - - /* We use the free choice of eps to make the integral better behaved. - * 1. Concern is oscillatory behaviour of P. Therefore, we'd like to - * make the change in the argument of cosine small, i.e. make arc length - * int_0^phi sqrt(1 + f'(phi)^2) dphi small, with - * f(phi) = eps * sin(phi) - x * eps^(-a) * sin(a*phi) + (1-b)*phi - * Proxy, make |f'(phi)| small. - * 2. Concern is int_0 K ~ int_0 (r+eps)^(-b) .. dr - * This is difficult as r -> 0 for large b. It behaves better for larger - * values of eps. - */ - - // Minimize oscillatory behavoir of P - double eps = - (wb_A[0] * b * std::exp(-0.5 * a) + - std::exp( - wb_A[1] + 1 / (1 + a) * std::log(x) - wb_A[2] * std::exp(-wb_A[3] * a) + - wb_A[4] / (1 + std::exp(wb_A[5] * a)) - )); - - if (a >= 4 && x >= 100) { - eps += 1; // This part is hard to fit - } - - // Large b - if (b >= 8) { - /* Make P small compared to K by setting eps large enough. - * int K ~ exp(-eps) and int P ~ eps^(1-b) */ - eps = std::fmax(eps, std::pow(b, -b / (1. - b)) + 0.1 * b); - } - - // safeguard, higher better for larger a, lower better for tiny a. - eps = std::fmin(eps, 150.); - eps = std::fmax(eps, 3.); // 3 seems to be a pretty good choice in general. - - // We factor out exp(-exp_term) from wb_Kmod and wb_P to avoid overflow of - // exp(..). - double exp_term = 0; - // From the exponent of K: - double r = wb_x_laguerre[50-1]; // largest value of x used in wb_Kmod - double x_r_a = x * std::pow(r + eps, -a); - exp_term = std::fmax(exp_term, x_r_a * cephes::cospi(a)); - // From the exponent of P: - double x_eps_a = x * std::pow(eps, -a); - // phi = 0 => cos(phi) = cos(a * phi) = 1 - exp_term = std::fmax(exp_term, eps + x_eps_a); - // phi = pi => cos(phi) = -1 - exp_term = std::fmax(exp_term, -eps + x_eps_a * cephes::cospi(a)); - - double res1 = 0; - double res2 = 0; - - double y; - for (int k = 0; k < 50; k++) { - res1 += wb_w_laguerre[k] * wb_Kmod(-exp_term, eps, a, b, x, wb_x_laguerre[k]); - // y = (b-a)*(x+1)/2.0 + a for integration from a=0 to b=pi - y = M_PI * (wb_x_legendre[k] + 1) / 2.0; - res2 += wb_w_legendre[k] * wb_P(-exp_term, eps, a, b, x, y); - } - res1 *= std::exp(-eps); - // (b-a)/2.0 * np.sum(w*func(y, *args), axis=-1) - res2 *= M_PI / 2.0; - res2 *= std::pow(eps, 1 - b); - - if (!log_wb) { - // Remember the factored out exp_term from wb_Kmod and wb_P - return std::exp(exp_term) / M_PI * (res1 + res2); - } else { - // logarithm of Wright's function - return exp_term + std::log((res1 + res2) / M_PI); - } - } -} // namespace detail - -template -XSF_HOST_DEVICE inline double wright_bessel_t(double a, double b, double x) { - /* Compute Wright's generalized Bessel function for scalar arguments. - * - * According to [1], it is an entire function defined as - * - * .. math:: \Phi(a, b; x) = \sum_{k=0}^\infty \frac{x^k}{k! \Gamma(a k + b)} - * - * So far, only non-negative values of rho=a, beta=b and z=x are implemented. - * There are 5 different approaches depending on the ranges of the arguments: - * - * 1. Taylor series expansion in x=0 [1], for x <= 1. - * Involves gamma funtions in each term. - * 2. Taylor series expansion in x=0 [2], for large a. - * 3. Taylor series in a=0, for tiny a and not too large x. - * 4. Asymptotic expansion for large x [3, 4]. - * Suitable for large x while still small a and b. - * 5. Integral representation [5], in principle for all arguments. - * - * References - * ---------- - * [1] https://dlmf.nist.gov/10.46.E1 - * [2] P. K. Dunn, G. K. Smyth (2005), Series evaluation of Tweedie exponential - * dispersion model densities. Statistics and Computing 15 (2005): 267-280. - * [3] E. M. Wright (1935), The asymptotic expansion of the generalized Bessel - * function. Proc. London Math. Soc. (2) 38, pp. 257-270. - * https://doi.org/10.1112/plms/s2-38.1.257 - * [4] R. B. Paris (2017), The asymptotics of the generalised Bessel function, - * Mathematica Aeterna, Vol. 7, 2017, no. 4, 381 - 406, - * https://arxiv.org/abs/1711.03006 - * [5] Y. F. Luchko (2008), Algorithms for Evaluation of the Wright Function for - * the Real Arguments' Values, Fractional Calculus and Applied Analysis 11(1) - * http://sci-gems.math.bas.bg/jspui/bitstream/10525/1298/1/fcaa-vol11-num1-2008-57p-75p.pdf - */ - if (std::isnan(a) || std::isnan(b) || std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - if (a < 0 || b < 0 || x < 0) { - set_error("wright_bessel", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - if (std::isinf(x)) { - if (std::isinf(a) || std::isinf(b)) { - return std::numeric_limits::quiet_NaN(); - } - return std::numeric_limits::infinity(); - } - if (std::isinf(a) || std::isinf(b)) { - return std::numeric_limits::quiet_NaN(); // or 0 - } - if (a >= detail::rgamma_zero || b >= detail::rgamma_zero) { - set_error("wright_bessel", SF_ERROR_OVERFLOW, NULL); - return std::numeric_limits::quiet_NaN(); - } - if (x == 0) { - // return rgamma(b) - if (!log_wb) { - return cephes::rgamma(b); - } else { - // logarithm of Wright's function - return -cephes::lgam(b); - } - } - if (a == 0) { - // return exp(x) * rgamma(b) - if (!log_wb) { - return detail::exp_rgamma(x, b); - } else { - // logarithm of Wright's function - return x - cephes::lgam(b); - } - } - - constexpr double exp_inf = 709.78271289338403; - int order; - if ((a <= 1e-3 && b <= 50 && x <= 9) || (a <= 1e-4 && b <= 70 && x <= 100) || - (a <= 1e-5 && b <= 170 && (x < exp_inf || (log_wb && x <= 1e3)))) { - /* Taylor Series expansion in a=0 to order=order => precision <= 1e-11 - * If beta is also small => precision <= 1e-11. - * max order = 5 */ - if (a <= 1e-5) { - if (x <= 1) { - order = 2; - } else if (x <= 10) { - order = 3; - } else if (x <= 100) { - order = 4; - } else { // x < exp_inf - order = 5; - } - } else if (a <= 1e-4) { - if (x <= 1e-2) { - order = 2; - } else if (x <= 1) { - order = 3; - } else if (x <= 10) { - order = 4; - } else { // x <= 100 - order = 5; - } - } else { // a <= 1e-3 - if (x <= 1e-5) { - order = 2; - } else if (x <= 1e-1) { - order = 3; - } else if (x <= 1) { - order = 4; - } else { // x <= 9 - order = 5; - } - } - - return detail::wb_small_a(a, b, x, order); - } - - if (x <= 1) { - // 18 term Taylor Series => error mostly smaller 5e-14 - double res = detail::wb_series(a, b, x, 0, 18); - if (log_wb) res = std::log(res); - return res; - } - if (x <= 2) { - // 20 term Taylor Series => error mostly smaller 1e-12 to 1e-13 - double res = detail::wb_series(a, b, x, 0, 20); - if (log_wb) res = std::log(res); - return res; - } - if (a >= 5) { - /* Taylor series around the approximate maximum term. - * Set number of terms=order. */ - if (a >= 10) { - if (x <= 1e11) { - order = 6; - } else { - order = static_cast(std::fmin(std::log10(x) - 5 + b / 10, 30)); - } - } else { - if (x <= 1e4) { - order = 6; - } else if (x <= 1e8) { - order = static_cast(2 * std::log10(x)); - } else if (x <= 1e10) { - order = static_cast(4 * std::log10(x) - 16); - } else { - order = static_cast(std::fmin(6 * std::log10(x) - 36, 100)); - } - } - return detail::wb_large_a(a, b, x, order); - } - if (std::pow(a * x, 1 / (1. + a)) >= 14 + b * b / (2 * (1 + a))) { - /* Asymptotic expansion in Z = (a*x)^(1/(1+a)) up to 8th term 1/Z^8. - * For 1/Z^k, the highest term in b is b^(2*k) * a0 / (2^k k! (1+a)^k). - * As a0 is a common factor to all orders, this explains a bit the - * domain of good convergence set above. - * => precision ~ 1e-11 but can go down to ~1e-8 or 1e-7 - * Note: We ensured a <= 5 as this is a bad approximation for large a. */ - return detail::wb_asymptotic(a, b, x); - } - if (0.5 <= a && a <= 1.8 && 100 <= b && 1e5 <= x) { - // This is a very hard domain. This condition is placed after wb_asymptotic. - // TODO: Explore ways to cover this domain. - return std::numeric_limits::quiet_NaN(); - } - return detail::wright_bessel_integral(a, b, x); -} - - -XSF_HOST_DEVICE inline double wright_bessel(double a, double b, double x) { - return wright_bessel_t(a, b, x); -} - -XSF_HOST_DEVICE inline float wright_bessel(float a, float b, float x) { - return wright_bessel(static_cast(a), static_cast(b), static_cast(x)); -} - -XSF_HOST_DEVICE inline double log_wright_bessel(double a, double b, double x) { - return wright_bessel_t(a, b, x); -} - -XSF_HOST_DEVICE inline float log_wright_bessel(float a, float b, float x) { - return log_wright_bessel(static_cast(a), static_cast(b), static_cast(x)); -} - -} // namespace xsf diff --git a/scipy/special/xsf/zeta.h b/scipy/special/xsf/zeta.h deleted file mode 100644 index ff52f6e7c83a..000000000000 --- a/scipy/special/xsf/zeta.h +++ /dev/null @@ -1,406 +0,0 @@ -/* Complex riemann-zeta function implementation based on Python implementation - * written by Matt Haberland (@mdhaber) in: - * https://colab.research.google.com/drive/1zMDSAJlXCLRqMMtJ0e9nDGjQ8iZCnmn5?usp=sharing - */ - -#pragma once - -#include "config.h" -#include "error.h" -#include "gamma.h" -#include "trig.h" - -#include "cephes/const.h" -#include "cephes/zeta.h" -#include "cephes/zetac.h" - -namespace xsf { - -namespace detail { - - /* Log of absolute value of expansion coefficients for Euler-Maclaurin - * summation formula. log(|B2k / (2k)!|) - * - * See https://en.wikipedia.org/wiki/Riemann_zeta_function#Numerical_algorithms - * - * Generated with the script - * - * import numpy as np - * from mpmath import mp - * - * mp.dps = 10000 - * - * results = [] - * for k in range(51): - * results.append( - * float( - * mp.log(abs(mp.bernoulli(2*k)/(mp.factorial(2*k)))) - * ) - * ) - */ - constexpr double zeta_em_log_abs_coeff_lookup[] = { - 0.0, - -2.4849066497880004, - -6.579251212010101, - -10.31692083029347, - -14.005800284407405, - -17.68462940266784, - -21.361131560073222, - -25.037070502911423, - -28.712870599846948, - -32.388636197522295, - -36.06439319366539, - -39.740148041995184, - -43.41590235365616, - -47.091656531181485, - -50.76741067517639, - -54.44316481078909, - -58.11891894430628, - -61.79467307729959, - -65.47042721016194, - -69.14618134299154, - -72.82193547581296, - -76.49768960863234, - -80.1734437414512, - -83.84919787426993, - -87.52495200708863, - -91.20070613990733, - -94.87646027272602, - -98.55221440554472, - -102.2279685383634, - -105.9037226711821, - -109.57947680400078, - -113.25523093681947, - -116.93098506963817, - -120.60673920245685, - -124.28249333527555, - -127.95824746809424, - -131.63400160091294, - -135.30975573373163, - -138.9855098665503, - -142.661263999369, - -146.3370181321877, - -150.0127722650064, - -153.6885263978251, - -157.36428053064375, - -161.04003466346245, - -164.71578879628115, - -168.39154292909984, - -172.06729706191854, - -175.74305119473723, - -179.4188053275559, - -183.0945594603746 - }; - - // Complex log of expansion coefficients for Euler-Maclaurin summation formula. - XSF_HOST_DEVICE inline std::complex zeta_em_log_coeff(std::size_t n) { - std::complex J(0.0, 1.0); - std::complex result; - if (n < 50) { - result = zeta_em_log_abs_coeff_lookup[n]; - } else { - /* Asymptotic formula - * Uses https://dlmf.nist.gov/24.11#E1 to approximate B_{2n} and - * Stirling's approximation for (2n)!. - */ - result = std::log(2.0) - 2.0*n*std::log(2*M_PI); - } - if (n % 2 == 0) { - /* B_{2n}/(2n)! is negative for even n. This contributes a term - * pi*i when taking the log. */ - result += M_PI * J; - } - return result; - } - - /* Compute riemann_zeta for complex input z using the Euler-Maclaurin formula. - * Computation of individual terms in expansion are logarithmized to avoid - * overflow. TODO: only logarithmize when necessary. */ - XSF_HOST_DEVICE inline std::complex zeta_euler_maclaurin(std::complex z) { - if (z == 1.0) { - /* Return NaN at pole since value depends on how z approaches 1.0. */ - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - std::size_t n = static_cast(std::max(std::abs(z.imag()) / 4.0, 50.0)); - std::size_t m = n; - std::complex result = 0.0; - for (std::size_t i = 1; i < n; i++) { - std::complex term = std::pow(static_cast(i), -z); - result += term; - // When z.real() > 1, series converges and we can consider early termination - if (z.real() > 1 && std::abs(term) / std::abs(result) <= std::numeric_limits::epsilon()) { - return result; - } - } - double N = static_cast(n); - std::complex b = std::pow(n, -z); - result += b * (0.5 + N / (z - 1.0)); - /* The terms of the Euler-Maclaurin - * expansion below are T(k, n) = B2k/(2k)! * n^(1 - z - 2k) * z(z+1)...(z+2k-2). - * We work with logarithms to avoid overflow in all cases at the expense of - * some accuracy. At the start of iteration k: - * log_poch will equal log(z(z+1)...(z+2k-2)) - * log_factor will equal log(n^(1 - z - 2k)) - * These are updated one extra time after the loop completes for use in the - * Euler-Maclaurin error estimate. - */ - std::complex log_poch = std::log(z); - std::complex log_factor = -(z + 1.0) * std::log(N); - for (std::size_t k = 1; k <= m; k++) { - std::complex term = std::exp(zeta_em_log_coeff(k) + log_factor + log_poch); - result += term; - if (std::abs(term)/std::abs(result) <= std::numeric_limits::epsilon()) { - return result; - } - log_poch += std::log(z + static_cast(2*k - 1)) + std::log(z + static_cast(2*k)); - log_factor -= 2*std::log(N); - } - /* Euler-maclaurin absolute error estimate. - * The error is bounded above by |(z + 2m + 1)/(z.real + 2m + 1) * T(m+1, n)| - * See https://en.wikipedia.org/wiki/Riemann_zeta_function#Numerical_algorithms - */ - double error; - error = std::abs(std::exp(zeta_em_log_coeff(m + 1) + log_factor + log_poch)); - error *= std::abs((z + 2.0*m + 1.0)/(z.real() + 2.0*m + 1.0)); - // convert to relative error estimate - error /= std::abs(result); - if (error > 1e-8) { - if (error > 1e-1) { - /* If error estimate predicts we don't even get 1 digit of precision, return NaN - * and signal no result */ - set_error("zeta", SF_ERROR_NO_RESULT, NULL); - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - // Signal reduced precision. - set_error("zeta", SF_ERROR_LOSS, NULL); - } - return result; - } - - /* Lookup table of coefficients for Algorithm 2 from Borwein 1995 - * Borwein, Peter B.. “An efficient algorithm for the Riemann zeta function.” (1995). - * - * Stores coefficients as dk / dn, where dn is the final coefficient. - * - * Generated with the Python script: - * - * import numpy as np - * import math - * from mpmath import mp - * - * mp.dps = 1000 - * n = 50 - * - * coeffs = [] - * S = mp.zero - * for i in range(n + 1): - * num = math.factorial(n + i - 1) * 4**i - * den = math.factorial(n - i) * math.factorial(2*i) - * S += mp.mpf(num) / mp.mpf(den) - * coeffs.append(S*n) - * - * dn = coeffs[-1] - * coeffs = [float(dk/dn) for dk in coeffs[:-1]] - * coeffs = np.asarray(coeffs) - */ - constexpr double zeta_borwein_coeff[] = { - 1.0555078361382878e-38, - 5.278594688527578e-35, - 4.4014687322044963e-32, - 1.467453546497519e-29, - 2.617862196688831e-27, - 2.900097799958025e-25, - 2.184440361492933e-23, - 1.1890977312913296e-21, - 4.8871396166872276e-20, - 1.5672253698802734e-18, - 4.022931234264572e-17, - 8.435973533351745e-16, - 1.469296379914116e-14, - 2.1548747054571902e-13, - 2.6919530537535124e-12, - 2.8925409162768484e-11, - 2.6957505693699856e-10, - 2.194772239130839e-09, - 1.57078229370057e-08, - 9.936187220749133e-08, - 5.581721578217702e-07, - 2.796271112037765e-06, - 1.253886254275813e-05, - 5.049261002939051e-05, - 0.00018312884459703666, - 0.0005997690328552426, - 0.0017780501082460968, - 0.004781802283665968, - 0.011690432287131671, - 0.026034302929535964, - 0.052922982472754856, - 0.09842471411648858, - 0.16789610796540344, - 0.26350429194768626, - 0.3819442810600665, - 0.5137731385068898, - 0.645292538541866, - 0.7625449322050362, - 0.8556063057019102, - 0.921056062886525, - 0.9616100580028147, - 0.9835905431606953, - 0.9939187229336753, - 0.9980782525166648, - 0.9994930141459889, - 0.999891478844585, - 0.9999819091990203, - 0.9999977981288319, - 0.9999998260580315, - 0.9999999933099243 - }; - - /* Compute riemann_zeta for complex input z using Algorithm 2 from Borwein 1995. */ - XSF_HOST_DEVICE inline std::complex zeta_borwein(std::complex z) { - std::complex result = 0.0; - // Sum in reverse order because smaller terms come later. - for (int k = 49; k >= 0; k--) { - double sign = std::pow(-1.0, k); - std::complex den = std::pow(k + 1, z); - std::complex term = sign * (zeta_borwein_coeff[k] - 1.0) / den; - result += term; - } - return result * -1.0/(1.0 - std::pow(2.0, 1.0 - z)); - } - - /* Compute riemann zeta for complex z and real part >= 0 */ - XSF_HOST_DEVICE inline std::complex zeta_right_halfplane(std::complex z) { - if (z == 1.0) { - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - /* Cutoff for using Euler-MacLaurin chosen based on cursory empirical search. - * TODO: Choose cutoffs in a more principled way. */ - if (z.real() < 50.0 && std::abs(z.imag()) > 50.0) { - if (z.real() >= 0.0 && z.real() < 2.5 && std::abs(z.imag()) > 1e9) { - /* Euler-MacLaurin summation starts to take an unreasonable amount of time in this - * region, so just give up and return NaN instead. */ - set_error("zeta", SF_ERROR_NO_RESULT, NULL); - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - return zeta_euler_maclaurin(z); - } - return zeta_borwein(z); - } - - XSF_HOST_DEVICE inline std::complex exppi(std::complex z) { - // exp(pi*z) for complex z. - double x = z.real(); - double y = z.imag(); - std::complex factor1(xsf::cospi(y), xsf::sinpi(y)); - double factor2 = std::exp(M_PI*x); - return factor1 * factor2; - } - - XSF_HOST_DEVICE inline std::complex logsinpi(std::complex z) { - /* log(sinpi(z)) using sin(z) = (exp(i*pi*z) - exp(-i*pi*z)) / 2i - * - * No attempt is made to choose any particular branch of the logarithm. - * This is an internal function and the intent is that this that the - * result of log(sinpi(z)) will be added to other terms, and the sum - * will then be exponentiated, making the choice of a specific branch - * unnecessary. - */ - std::complex result = std::log(xsf::sinpi(z)); - // If it doesn't overflow, just do the regular calculation. - if (std::isfinite(result.real()) && !std::isfinite(result.imag())) { - return result; - } - /* Otherwise factor before taking log. This is where we may end up - * taking a branch other than the principal branch. */ - std::complex J(0.0, 1.0); - /* Calculating log((exp(i*pi*z) - exp(-i*pi*z)) / 2i). Factor out term - * with larger magnitude before taking log. */ - if (z.imag() > 0 ) { - /* if z.imag() > 0 then, exp(-i*pi*z) has greatest magnitude. Factor it - * out to get: - * log(exp(-i*pi*z)*((exp(2*i*pi*z) - 1.0)/(2i)) = - * log(exp(-i*pi*z)) + log((exp(2*i*pi*z) - 1.0)/(2i)) = - * -i*pi*z + log((exp(2*i*pi*z) - 1.0)/(2i)) */ - return -J * M_PI * z + std::log((exppi(2.0 * z * J) - 1.0) / (2.0*J)); - } - /* if z.imag() < 0 then, exp(i*pi*z) has greatest magnitude. Factor similarly - * to above */ - return J * M_PI * z + std::log((1.0 - exppi(-2.0 * z * J)) / (2.0*J)); - } - - /* Leading factor in reflection formula for zeta function. - * zeta(z) = 2^z * pi^(z-1) * sin(pi*z/2) * gamma(1 - z) * zeta(1 - z) - * This computes 2^z * pi^(z - 1) * sin(pi*z/2) * gamma(1 - z) - * - * Computation is logarithimized to prevent overflow. - * TODO: Complexify the cephes zeta_reflection implementation, which uses - * the lanczos approximation for the gamma function. */ - XSF_HOST_DEVICE inline std::complex zeta_reflection_factor_with_logs(std::complex z) { - std::complex t1 = z * M_LN2; - std::complex t2 = (z - 1.0) * xsf::cephes::detail::LOGPI; - std::complex t3 = logsinpi(z / 2.0); - std::complex t4 = xsf::loggamma(1.0 - z); - std::complex factor = std::exp(t1 + t2 + t3 + t4); - return factor; - } - - XSF_HOST_DEVICE inline std::complex zeta_reflection(std::complex z) { - std::complex factor = 2.0 * std::pow(2*M_PI, z - 1.0) * xsf::sinpi(z/2.0) * xsf::gamma(1.0 - z); - if (!std::isfinite(factor.real()) || !std::isfinite(factor.imag())) { - // Try again with logs if standard calculation had overflow. - factor = zeta_reflection_factor_with_logs(z); - } - std::complex result = zeta_right_halfplane(1.0 - z); - /* zeta tends to 1.0 as real part tends to +inf. In cases where - * the real part of zeta tends to -inf, then zeta(1 - z) in the - * reflection formula will tend to 1.0. Factor overflows then, - * factor * result below will become NaN. In this case, we just - * return factor to preserve complex infinity. Only zeta(1 - z) == 1.0 - * is handled because this is the only practical case where we should - * expect zeta(1 - z) == x for a real number x when z is not on the - * real line. */ - return (result == 1.0) ? factor : factor * result; - } -} - -XSF_HOST_DEVICE inline std::complex riemann_zeta(std::complex z) { - if (z.imag() == 0.0) { - return cephes::riemann_zeta(z.real()); - } - if (z.real() >= 0.5) { - return detail::zeta_right_halfplane(z); - } - return detail::zeta_reflection(z); -} - -XSF_HOST_DEVICE inline std::complex riemann_zeta(std::complex z) { - return static_cast>(riemann_zeta(static_cast>(z))); -} - -XSF_HOST_DEVICE inline double riemann_zeta(double x) { return cephes::riemann_zeta(x); } - -XSF_HOST_DEVICE inline float riemann_zeta(float x) { return riemann_zeta(static_cast(x)); } - -XSF_HOST_DEVICE inline double zeta(double x, double q) { return cephes::zeta(x, q); } - -XSF_HOST_DEVICE inline float zeta(float x, float q) { return zeta(static_cast(x), static_cast(q)); } - -XSF_HOST_DEVICE inline std::complex zeta(std::complex z, double q) { - if (z.imag() == 0.0) { - return zeta(z.real(), q); - } - // Complex input for Hurwitz Zeta is not currently supported. - set_error("zeta", SF_ERROR_DOMAIN, NULL); - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; -} - -XSF_HOST_DEVICE inline std::complex zeta(std::complex z, float q) { - return static_cast>(zeta(static_cast>(z), static_cast(q))); -} - -XSF_HOST_DEVICE inline double zetac(double x) { return cephes::zetac(x); } - -XSF_HOST_DEVICE inline float zetac(float x) { return zetac(static_cast(x)); } - -} // namespace xsf diff --git a/scipy/special/xsf/zlog1.h b/scipy/special/xsf/zlog1.h deleted file mode 100644 index 64e83ca390a0..000000000000 --- a/scipy/special/xsf/zlog1.h +++ /dev/null @@ -1,35 +0,0 @@ -/* Translated from Cython into C++ by SciPy developers in 2023. - * - * Original author: Josh Wilson, 2016. - */ - -#pragma once - -#include "config.h" - -namespace xsf { -namespace detail { - - XSF_HOST_DEVICE inline std::complex zlog1(std::complex z) { - /* Compute log, paying special attention to accuracy around 1. We - * implement this ourselves because some systems (most notably the - * Travis CI machines) are weak in this regime. */ - std::complex coeff = -1.0; - std::complex res = 0.0; - - if (std::abs(z - 1.0) > 0.1) { - return std::log(z); - } - - z -= 1.0; - for (int n = 1; n < 17; n++) { - coeff *= -z; - res += coeff / static_cast(n); - if (std::abs(res / coeff) < std::numeric_limits::epsilon()) { - break; - } - } - return res; - } -} // namespace detail -} // namespace xsf diff --git a/scipy/special/xsf_special.h b/scipy/special/xsf_special.h index 2a7000bb3d24..3bd13e7a827c 100644 --- a/scipy/special/xsf_special.h +++ b/scipy/special/xsf_special.h @@ -2,8 +2,8 @@ #include "Python.h" -#include "xsf/bessel.h" -#include "xsf/sph_harm.h" +#include +#include // This header exists to add behaviors to special functions from the xsf library, // either because they involve some Python-specific features or because there are diff --git a/scipy/special/xsf_wrappers.cpp b/scipy/special/xsf_wrappers.cpp index 9550f10cf6ba..52f7b8bb9a86 100644 --- a/scipy/special/xsf_wrappers.cpp +++ b/scipy/special/xsf_wrappers.cpp @@ -1,55 +1,54 @@ #include "xsf_wrappers.h" -#include "xsf/airy.h" -#include "xsf/alg.h" -#include "xsf/amos.h" -#include "xsf/bessel.h" -#include "xsf/beta.h" -#include "xsf/binom.h" -#include "xsf/cdflib.h" -#include "xsf/digamma.h" -#include "xsf/ellip.h" -#include "xsf/erf.h" -#include "xsf/exp.h" -#include "xsf/expint.h" -#include "xsf/fresnel.h" -#include "xsf/gamma.h" -#include "xsf/hyp2f1.h" -#include "xsf/kelvin.h" -#include "xsf/lambertw.h" -#include "xsf/log.h" -#include "xsf/log_exp.h" -#include "xsf/loggamma.h" -#include "xsf/mathieu.h" -#include "xsf/par_cyl.h" -#include "xsf/sici.h" -#include "xsf/specfun.h" -#include "xsf/sph_bessel.h" -#include "xsf/sph_harm.h" -#include "xsf/sphd_wave.h" -#include "xsf/stats.h" -#include "xsf/struve.h" -#include "xsf/trig.h" -#include "xsf/wright_bessel.h" -#include "xsf/zeta.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "xsf_special.h" -#include "xsf/cephes/cbrt.h" -#include "xsf/cephes/erfinv.h" -#include "xsf/cephes/expn.h" -#include "xsf/cephes/fresnl.h" -#include "xsf/cephes/hyperg.h" -#include "xsf/cephes/igam.h" -#include "xsf/cephes/igami.h" -#include "xsf/cephes/jv.h" -#include "xsf/cephes/lanczos.h" -#include "xsf/cephes/poch.h" -#include "xsf/cephes/rgamma.h" -#include "xsf/cephes/round.h" -#include "xsf/cephes/scipy_iv.h" -#include "xsf/cephes/spence.h" -#include "xsf/cephes/trig.h" -#include "xsf/cephes/unity.h" -#include "xsf/cephes/yn.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include using namespace std; @@ -61,7 +60,7 @@ npy_cdouble to_ccomplex(complex z) { return {z.real(), z.imag()}; } } // namespace -npy_cdouble chyp1f1_wrap(double a, double b, npy_cdouble z) { return to_ccomplex(xsf::chyp1f1(a, b, to_complex(z))); } +npy_cdouble chyp1f1_wrap(double a, double b, npy_cdouble z) { return to_ccomplex(xsf::hyp1f1(a, b, to_complex(z))); } double hypU_wrap(double a, double b, double x) { return xsf::hypu(a, b, x); } @@ -310,17 +309,17 @@ int cephes_ellpj_wrap(double u, double m, double *sn, double *cn, double *dn, do return xsf::cephes::ellpj(u, m, sn, cn, dn, ph); } -int xsf_sici(double x, double *si, double *ci) { return xsf::sici(x, si, ci); } +int xsf_sici(double x, double *si, double *ci) { return xsf::sici(x, *si, *ci); } -int xsf_shichi(double x, double *si, double *ci) { return xsf::shichi(x, si, ci); } +int xsf_shichi(double x, double *si, double *ci) { return xsf::shichi(x, *si, *ci); } int xsf_csici(npy_cdouble x, npy_cdouble *si, npy_cdouble *ci) { - return xsf::sici(to_complex(x), reinterpret_cast *>(si), reinterpret_cast *>(ci)); + return xsf::sici(to_complex(x), *reinterpret_cast *>(si), *reinterpret_cast *>(ci)); } int xsf_cshichi(npy_cdouble x, npy_cdouble *shi, npy_cdouble *chi) { - return xsf::shichi(to_complex(x), reinterpret_cast *>(shi), - reinterpret_cast *>(chi)); + return xsf::shichi(to_complex(x), *reinterpret_cast *>(shi), + *reinterpret_cast *>(chi)); } double cephes__struve_asymp_large_z(double v, double z, Py_ssize_t is_h, double *err) { @@ -352,7 +351,7 @@ double xsf_beta(double a, double b) { return xsf::beta(a, b); } double xsf_betaln(double a, double b) { return xsf::betaln(a, b); } -double xsf_cbrt(double x) { return xsf::cbrt(x); } +double xsf_cbrt(double x) { return xsf::cephes::cbrt(x); } double xsf_gamma(double x) { return xsf::gamma(x); } diff --git a/scipy/stats/__init__.py b/scipy/stats/__init__.py index ec92fa632890..5bd1ed2ef475 100644 --- a/scipy/stats/__init__.py +++ b/scipy/stats/__init__.py @@ -260,7 +260,6 @@ tstd -- tsem -- variation -- Coefficient of variation - find_repeats rankdata tiecorrect trim_mean @@ -472,6 +471,7 @@ make_distribution Normal Uniform + Binomial Mixture order_statistic truncate @@ -650,7 +650,7 @@ from ._distribution_infrastructure import ( make_distribution, Mixture, order_statistic, truncate, exp, log, abs ) -from ._new_distributions import Normal, Uniform +from ._new_distributions import Normal, Uniform, Binomial from ._mgc import multiscale_graphcorr from ._correlation import chatterjeexi from ._quantile import quantile diff --git a/scipy/stats/_axis_nan_policy.py b/scipy/stats/_axis_nan_policy.py index 0f1f0559633d..dd780d0f6270 100644 --- a/scipy/stats/_axis_nan_policy.py +++ b/scipy/stats/_axis_nan_policy.py @@ -403,17 +403,9 @@ def _axis_nan_policy_factory(tuple_to_result, default_axis=0, override.update(temp) if result_to_tuple is None: - def result_to_tuple(res): + def result_to_tuple(res, _): return res - # The only `result_to_tuple` that needs the second argument (number of - # outputs) is the one for `moment`, and this was realized very late. - # Rather than changing all `result_to_tuple` definitions, we wrap them - # here to accept a second argument if they don't already. - if len(inspect.signature(result_to_tuple).parameters) == 1: - def result_to_tuple(res, _, f=result_to_tuple): - return f(res) - if not callable(too_small): def is_too_small(samples, *ts_args, axis=-1, **ts_kwargs): for sample in samples: diff --git a/scipy/stats/_continued_fraction.py b/scipy/stats/_continued_fraction.py index efa0411608ab..4966b1c99ed0 100644 --- a/scipy/stats/_continued_fraction.py +++ b/scipy/stats/_continued_fraction.py @@ -8,6 +8,7 @@ from scipy import special # Todo: +# Avoid special-casing key 'n' in _lib._elementwise_iterative_method::_check_termination # Rearrange termination condition to allow absolute and relative tolerances? # Interpret/return |f_n - f_{n-1}| as an error estimate? # Return gracefully for size=0 arrays diff --git a/scipy/stats/_continuous_distns.py b/scipy/stats/_continuous_distns.py index b9c10e2abf61..52208164a2ff 100644 --- a/scipy/stats/_continuous_distns.py +++ b/scipy/stats/_continuous_distns.py @@ -11381,6 +11381,10 @@ def _fitstart(self, data): data = data._uncensor() return 0.5, np.min(data), np.ptp(data)/(2*np.pi) + @inherit_docstring_from(rv_continuous) + def rvs(self, *args, **kwds): + rvs = super().rvs(*args, **kwds) + return np.mod(rvs, 2*np.pi) wrapcauchy = wrapcauchy_gen(a=0.0, b=2*np.pi, name='wrapcauchy') @@ -11453,6 +11457,15 @@ def _sf(self, x, beta): def _isf(self, x, beta): return -self._ppf(x, beta) + def _munp(self, n, beta): + if n == 0: + return 1. + if n % 2 == 0: + c1, cn = sc.gammaln([1.0/beta, (n + 1.0)/beta]) + return np.exp(cn - c1) + else: + return 0. + def _stats(self, beta): c1, c3, c5 = sc.gammaln([1.0/beta, 3.0/beta, 5.0/beta]) return 0., np.exp(c3 - c1), 0., np.exp(c5 + c1 - 2.0*c3) - 3. diff --git a/scipy/stats/_correlation.py b/scipy/stats/_correlation.py index 2e47cd618044..85aaae90b550 100644 --- a/scipy/stats/_correlation.py +++ b/scipy/stats/_correlation.py @@ -81,7 +81,7 @@ def _chatterjeexi_iv(y_continuous, method): return y_continuous, method -def _unpack(res): +def _unpack(res, _): return res.statistic, res.pvalue diff --git a/scipy/stats/_discrete_distns.py b/scipy/stats/_discrete_distns.py index fc0ba63e5f30..ac12ef17aa3d 100644 --- a/scipy/stats/_discrete_distns.py +++ b/scipy/stats/_discrete_distns.py @@ -1056,7 +1056,7 @@ class planck_gen(rv_discrete): """ def _shape_info(self): - return [_ShapeInfo("lambda", False, (0, np.inf), (False, False))] + return [_ShapeInfo("lambda_", False, (0, np.inf), (False, False))] def _argcheck(self, lambda_): return lambda_ > 0 @@ -1371,12 +1371,13 @@ def _gen_harmonic_leq1(n, a): """Generalized harmonic number, a <= 1""" if not np.size(n): return n - n_max = np.max(n) # loop starts at maximum of all n + n_max = np.nanmax(n) # loop starts at maximum of all n out = np.zeros_like(a, dtype=float) # add terms of harmonic series; starting from smallest to avoid roundoff for i in np.arange(n_max, 0, -1, dtype=float): mask = i <= n # don't add terms after nth out[mask] += 1/i**a[mask] + out[np.isnan(n)] = np.nan return out @@ -1449,10 +1450,11 @@ def _pmf(self, k, a, n): return 1.0 / _gen_harmonic(n, a) * k**-a def _cdf(self, k, a, n): + k = np.floor(k) return _gen_harmonic(k, a) / _gen_harmonic(n, a) def _sf(self, k, a, n): - k = k + 1 # # to match SciPy convention + k = np.floor(k + 1) # # to match SciPy convention # see http://www.math.wm.edu/~leemis/chart/UDR/PDFs/Zipf.pdf return ((k**a*(_gen_harmonic(n, a) - _gen_harmonic(k, a)) + 1) / (k**a*_gen_harmonic(n, a))) @@ -1870,9 +1872,9 @@ def _get_support(self, M, n, N, odds): def _argcheck(self, M, n, N, odds): M, n = np.asarray(M), np.asarray(n), N, odds = np.asarray(N), np.asarray(odds) - cond1 = (M.astype(int) == M) & (M >= 0) - cond2 = (n.astype(int) == n) & (n >= 0) - cond3 = (N.astype(int) == N) & (N >= 0) + cond1 = (~np.isnan(M)) & (M.astype(int) == M) & (M >= 0) + cond2 = (~np.isnan(n)) & (n.astype(int) == n) & (n >= 0) + cond3 = (~np.isnan(N)) & (N.astype(int) == N) & (N >= 0) cond4 = odds > 0 cond5 = N <= M cond6 = n <= M @@ -1882,6 +1884,8 @@ def _rvs(self, M, n, N, odds, size=None, random_state=None): @_vectorize_rvs_over_shapes def _rvs1(M, n, N, odds, size, random_state): + if np.isnan(M) | np.isnan(n) | np.isnan(N): + return np.full(size, np.nan) length = np.prod(size) urn = _PyStochasticLib3() rv_gen = getattr(urn, self.rvs_name) @@ -1899,15 +1903,19 @@ def _pmf(self, x, M, n, N, odds): @np.vectorize def _pmf1(x, M, n, N, odds): + if np.isnan(x) | np.isnan(M) | np.isnan(n) | np.isnan(N): + return np.nan urn = self.dist(N, n, M, odds, 1e-12) return urn.probability(x) return _pmf1(x, M, n, N, odds) - def _stats(self, M, n, N, odds, moments): + def _stats(self, M, n, N, odds, moments='mv'): @np.vectorize def _moments1(M, n, N, odds): + if np.isnan(M) | np.isnan(n) | np.isnan(N): + return np.nan, np.nan urn = self.dist(N, n, M, odds, 1e-12) return urn.moments() diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 760501219279..66f24eba0ca5 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -7,11 +7,12 @@ import numpy as np from numpy import inf -from scipy._lib._util import _rng_spawn +from scipy._lib._array_api import xp_promote +from scipy._lib._util import _rng_spawn, _RichResult from scipy._lib._docscrape import ClassDoc, NumpyDocString from scipy import special, stats from scipy.special._ufuncs import _log1mexp -from scipy.integrate import tanhsinh as _tanhsinh +from scipy.integrate import tanhsinh as _tanhsinh, nsum from scipy.optimize._bracket import _bracket_root, _bracket_minimum from scipy.optimize._chandrupatla import _chandrupatla, _chandrupatla_minimize from scipy.stats._probability_distribution import _ProbabilityDistribution @@ -234,8 +235,8 @@ def __str__(self): raise NotImplementedError() -class _SimpleDomain(_Domain): - r""" Representation of a simply-connected domain defined by two endpoints. +class _Interval(_Domain): + r""" Representation of an interval defined by two endpoints. Each endpoint may be a finite scalar, positive or negative infinity, or be given by a single parameter. The domain may include the endpoints or @@ -268,6 +269,8 @@ class _SimpleDomain(_Domain): defined symbolically or through a callable. contains(item, parameter_values) Determines whether the argument is contained within the domain + draw(size, rng, proportions, parameter_values) + Draws random values based on the domain. """ def __init__(self, endpoints=(-inf, inf), inclusive=(False, False)): @@ -318,11 +321,10 @@ def get_numerical_endpoints(self, parameter_values): Numerical values of the endpoints """ - # TODO: ensure outputs are floats a, b = self.endpoints # If `a` (`b`) is a string - the name of the parameter that defines # the endpoint of the domain - then corresponding numerical values - # will be found in the `parameter_values` dictionary. + # will be found in the `parameter_values` dictionary. # If a callable, it will be executed with `parameter_values` passed as # keyword arguments, and it will return the numerical values. # Otherwise, it is itself the array of numerical values of the endpoint. @@ -342,6 +344,9 @@ def get_numerical_endpoints(self, parameter_values): "all required distribution parameters as keyword " "arguments.") raise TypeError(message) from e + # Floating point types are used for even integer parameters. + # Convert to float here to ensure consistency throughout framework. + a, b = xp_promote(a, b, force_floating=True, xp=np) return a, b def contains(self, item, parameter_values=None): @@ -378,52 +383,6 @@ def contains(self, item, parameter_values=None): in_right = item <= b if right_inclusive else item < b return in_left & in_right - -class _RealDomain(_SimpleDomain): - r""" Represents a simply-connected subset of the real line; i.e., an interval - - Completes the implementation of the `_SimpleDomain` class for simple - domains on the real line. - - Methods - ------- - define_parameters(*parameters) - (Inherited) Records any parameters used to define the endpoints of the - domain. - get_numerical_endpoints(parameter_values) - (Inherited) Gets the numerical values of the domain endpoints, which - may have been defined symbolically, or through a callable. - contains(item, parameter_values) - (Inherited) Determines whether the argument is contained within the - domain - __str__() - Returns a string representation of the domain, e.g. "[a, b)". - draw(size, rng, proportions, parameter_values) - Draws random values based on the domain. Proportions of values within - the domain, on the endpoints of the domain, outside the domain, - and having value NaN are specified by `proportions`. - - """ - def _get_endpoint_str(self, endpoint, funcname): - if callable(endpoint): - if endpoint.__doc__ is not None: - return endpoint.__doc__ - params = inspect.signature(endpoint).parameters.values() - params = [ - p.name for p in params if p.kind == inspect.Parameter.KEYWORD_ONLY - ] - return f"{funcname}({','.join(params)})" - return self.symbols.get(endpoint, f"{endpoint}") - - def __str__(self): - a, b = self.endpoints - a, b = self._get_endpoint_str(a, "f1"), self._get_endpoint_str(b, "f2") - left_inclusive, right_inclusive = self.inclusive - left = "[" if left_inclusive else "(" - right = "]" if right_inclusive else ")" - - return f"{left}{a}, {b}{right}" - def draw(self, n, type_, min, max, squeezed_base_shape, rng=None): r""" Draw random values from the domain. @@ -448,6 +407,9 @@ def draw(self, n, type_, min, max, squeezed_base_shape, rng=None): """ rng = np.random.default_rng(rng) + def ints(*args, **kwargs): return rng.integers(*args, **kwargs, endpoint=True) + uniform = rng.uniform if isinstance(self, _RealInterval) else ints + # get copies of min and max with no nans so that uniform doesn't fail min_nn, max_nn = min.copy(), max.copy() i = np.isnan(min_nn) | np.isnan(max_nn) @@ -457,7 +419,7 @@ def draw(self, n, type_, min, max, squeezed_base_shape, rng=None): shape = (n,) + squeezed_base_shape if type_ == 'in': - z = rng.uniform(min_nn, max_nn, size=shape) + z = uniform(min_nn, max_nn, size=shape) elif type_ == 'on': z_on_shape = shape @@ -467,9 +429,8 @@ def draw(self, n, type_, min, max, squeezed_base_shape, rng=None): z[~i] = max elif type_ == 'out': - # make this work for infinite bounds - z = min_nn - rng.uniform(size=shape) - zr = max_nn + rng.uniform(size=shape) + z = min_nn - uniform(1, 5, size=shape) # 1, 5 is arbitary; we just want + zr = max_nn + uniform(1, 5, size=shape) # some numbers outside domain i = rng.random(size=n) < 0.5 z[i] = zr[i] @@ -479,16 +440,114 @@ def draw(self, n, type_, min, max, squeezed_base_shape, rng=None): return z -class _IntegerDomain(_SimpleDomain): - r""" Representation of a domain of consecutive integers. +class _RealInterval(_Interval): + r""" Represents a simply-connected subset of the real line; i.e., an interval - Completes the implementation of the `_SimpleDomain` class for domains - composed of consecutive integer values. + Completes the implementation of the `_Interval` class for intervals + on the real line. - To be completed when needed. + Methods + ------- + define_parameters(*parameters) + (Inherited) Records any parameters used to define the endpoints of the + domain. + get_numerical_endpoints(parameter_values) + (Inherited) Gets the numerical values of the domain endpoints, which + may have been defined symbolically. + contains(item, parameter_values) + (Inherited) Determines whether the argument is contained within the + domain + __str__() + Returns a string representation of the domain, e.g. "[a, b)". """ - def __init__(self): - raise NotImplementedError + + def __str__(self): + a, b = self.endpoints + a, b = self._get_endpoint_str(a, "f1"), self._get_endpoint_str(b, "f2") + left_inclusive, right_inclusive = self.inclusive + left = "[" if left_inclusive else "(" + right = "]" if right_inclusive else ")" + + return f"{left}{a}, {b}{right}" + + def _get_endpoint_str(self, endpoint, funcname): + if callable(endpoint): + if endpoint.__doc__ is not None: + return endpoint.__doc__ + params = inspect.signature(endpoint).parameters.values() + params = [ + p.name for p in params if p.kind == inspect.Parameter.KEYWORD_ONLY + ] + return f"{funcname}({','.join(params)})" + return self.symbols.get(endpoint, f"{endpoint}") + + +class _IntegerInterval(_Interval): + r""" Represents an interval of integers + + Completes the implementation of the `_Interval` class for simple + domains on the integers. + + Methods + ------- + define_parameters(*parameters) + (Inherited) Records any parameters used to define the endpoints of the + domain. + get_numerical_endpoints(parameter_values) + (Inherited) Gets the numerical values of the domain endpoints, which + may have been defined symbolically. + contains(item, parameter_values) + (Overridden) Determines whether the argument is contained within the + domain + draw(n, type_, min, max, squeezed_base_shape, rng=None) + (Inherited) Draws random values based on the domain. + __str__() + Returns a string representation of the domain, e.g. "{a, a+1, ..., b-1, b}". + + """ + def contains(self, item, parameter_values=None): + super_contains = super().contains(item, parameter_values) + integral = (item == np.round(item)) + return super_contains & integral + + def __str__(self): + a, b = self.endpoints + a = self.symbols.get(a, a) + b = self.symbols.get(b, b) + + a_str, b_str = isinstance(a, str), isinstance(b, str) + a_inf = a == r"-\infty" if a_str else np.isinf(a) + b_inf = b == r"\infty" if b_str else np.isinf(b) + + # This doesn't work well for cases where ``a`` is floating point + # number large enough that ``nextafter(a, inf) > a + 1``, and + # similarly for ``b`` and nextafter(b, -inf). There may not be any + # distributions fit for SciPy where we would actually need to handle these + # cases though. + ap1 = f"{a} + 1" if a_str else f"{a + 1}" + bm1 = f"{b} - 1" if b_str else f"{b - 1}" + + if not a_str and not b_str: + gap = b - a + if gap == 3: + return f"\\{{{a}, {ap1}, {bm1}, {b}\\}}" + if gap == 2: + return f"\\{{{a}, {ap1}, {b}\\}}" + if gap == 1: + return f"\\{{{a}, {b}\\}}" + if gap == 0: + return f"\\{{{a}\\}}" + + if not a_inf and b_inf: + ap2 = f"{a} + 2" if a_str else f"{a + 2}" + return f"\\{{{a}, {ap1}, {ap2}, ...\\}}" + if a_inf and not b_inf: + bm2 = f"{b} - 2" if b_str else f"{b - 2}" + return f"\\{{{b}, {bm1}, {bm2}, ...\\}}" + if a_inf and b_inf: + return "\\{..., -2, -1, 0, 1, 2, ...\\}" + + return f"\\{{{a}, {ap1}, ..., {bm1}, {b}\\}}" class _Parameter(ABC): @@ -536,7 +595,7 @@ def __init__(self, name, *, domain, symbol=None, typical=None): self.symbol = symbol or name self.domain = domain if typical is not None and not isinstance(typical, _Domain): - typical = _RealDomain(typical) + typical = domain.__class__(typical) self.typical = typical or domain def __str__(self): @@ -838,7 +897,7 @@ def draw(self, sizes=None, rng=None, proportions=None, region='domain'): # we can draw values in order a, b, c. parameter_values = {} - if not len(sizes) or not np.iterable(sizes[0]): + if sizes is None or not len(sizes) or not np.iterable(sizes[0]): sizes = [sizes]*len(self.parameters) for size, param in zip(sizes, self.parameters.values()): @@ -855,6 +914,7 @@ def _set_invalid_nan(f): # Wrapper for input / output validation and standardization of distribution # functions that accept either the quantile or percentile as an argument: # logpdf, pdf + # logpmf, pmf # logcdf, cdf # logccdf, ccdf # ilogcdf, icdf @@ -870,12 +930,15 @@ def _set_invalid_nan(f): endpoints = {'icdf': (0, 1), 'iccdf': (0, 1), 'ilogcdf': (-np.inf, 0), 'ilogccdf': (-np.inf, 0)} replacements = {'logpdf': (-inf, -inf), 'pdf': (0, 0), + 'logpmf': (-inf, -inf), 'pmf': (0, 0), '_logcdf1': (-inf, 0), '_logccdf1': (0, -inf), '_cdf1': (0, 1), '_ccdf1': (1, 0)} - replace_strict = {'pdf', 'logpdf'} + replace_strict = {'pdf', 'logpdf', 'pmf', 'logpmf'} replace_exact = {'icdf', 'iccdf', 'ilogcdf', 'ilogccdf'} clip = {'_cdf1', '_ccdf1'} clip_log = {'_logcdf1', '_logccdf1'} + # relevant to discrete distributions only + replace_non_integral = {'pmf', 'logpmf', 'pdf', 'logpdf'} @functools.wraps(f) def filtered(self, x, *args, **kwargs): @@ -886,6 +949,9 @@ def filtered(self, x, *args, **kwargs): x = np.asarray(x) dtype = self._dtype shape = self._shape + discrete = isinstance(self, DiscreteDistribution) + keep_low_endpoint = discrete and method_name in {'_cdf1', '_logcdf1', + '_ccdf1', '_logccdf1'} # Ensure that argument is at least as precise as distribution # parameters, which are already at least floats. This will avoid issues @@ -914,7 +980,7 @@ def filtered(self, x, *args, **kwargs): # and the result will be set to the appropriate value. left_inc, right_inc = self._variable.domain.inclusive mask_low = (x < low if (method_name in replace_strict and left_inc) - else x <= low) + or keep_low_endpoint else x <= low) mask_high = (x > high if (method_name in replace_strict and right_inc) else x >= high) mask_invalid = (mask_low | mask_high) @@ -931,6 +997,14 @@ def filtered(self, x, *args, **kwargs): any_endpoint = (mask_endpoint if mask_endpoint.shape == () else np.any(mask_endpoint)) + # Check for non-integral arguments to PMF method + # or PDF of a discrete distribution. + any_non_integral = False + if discrete and method_name in replace_non_integral: + mask_non_integral = (x != np.floor(x)) + any_non_integral = (mask_non_integral if mask_non_integral.shape == () + else np.any(mask_non_integral)) + # Set out-of-domain arguments to NaN. The result will be set to the # appropriate value later. if any_invalid: @@ -948,12 +1022,19 @@ def filtered(self, x, *args, **kwargs): if res.shape != shape: # faster to check first res = np.broadcast_to(res, self._shape) - res_needs_copy = res_needs_copy or any_invalid or any_endpoint + res_needs_copy = (res_needs_copy or any_invalid + or any_endpoint or any_non_integral) if res_needs_copy: res = np.array(res, dtype=dtype, copy=True) - # For arguments outside the function domain, replace results + # For non-integral arguments to PMF (and PDF of discrete distribution) + # replace with zero. + if any_non_integral: + zero = -np.inf if method_name in {'logpmf', 'logpdf'} else 0 + res[mask_non_integral & ~np.isnan(res)] = zero + + # For arguments outside the function domain, replace results if any_invalid: replace_low, replace_high = ( replacements.get(method_name, (np.nan, np.nan))) @@ -974,7 +1055,8 @@ def filtered(self, x, *args, **kwargs): a[mask_high_endpoint] if method_name.endswith('ccdf') else b[mask_high_endpoint]) - res[mask_low_endpoint] = replace_low_endpoint + if not keep_low_endpoint: + res[mask_low_endpoint] = replace_low_endpoint res[mask_high_endpoint] = replace_high_endpoint # Clip probabilities to [0, 1] @@ -1167,7 +1249,7 @@ def _kwargs2args(f, args=None, kwargs=None): def wrapped(x, *args): return f(x, *args[:n_args], **dict(zip(names, args[n_args:]))) - args = list(args) + list(kwargs.values()) + args = tuple(args) + tuple(kwargs.values()) return wrapped, args @@ -1239,7 +1321,7 @@ def _combine_docs(dist_family, *, include_examples=True): fields.remove('Examples') doc = ClassDoc(dist_family) - superdoc = ClassDoc(ContinuousDistribution) + superdoc = ClassDoc(UnivariateDistribution) for field in fields: if field in {"Methods", "Attributes"}: doc[field] = superdoc[field] @@ -1257,7 +1339,7 @@ def _combine_docs(dist_family, *, include_examples=True): def _generate_domain_support(dist_family): n_parameterizations = len(dist_family._parameterizations) - domain = f"\nfor :math:`x` in {dist_family._variable.domain}.\n" + domain = f"\nfor :math:`x \\in {dist_family._variable.domain}`.\n" if n_parameterizations == 0: support = """ @@ -1307,7 +1389,7 @@ def _generate_example(dist_family): p = 0.32 x = round(X.icdf(p), 2) - y = round(X.icdf(2 * p), 2) + y = round(X.icdf(2 * p), 2) # noqa: F841 example = f""" To use the distribution class, it must be instantiated using keyword @@ -1342,19 +1424,24 @@ def _generate_example(dist_family): """ example += f""" - To evaluate the probability density function of the underlying distribution + To evaluate the probability density/mass function of the underlying distribution at argument ``x={x}``: >>> x = {x} - >>> X.pdf(x) - {X.pdf(x)} + >>> X.pdf(x), X.pmf(x) + {X.pdf(x), X.pmf(x)} The cumulative distribution function, its complement, and the logarithm of these functions are evaluated similarly. >>> np.allclose(np.exp(X.logccdf(x)), 1 - X.cdf(x)) True + """ + # When two-arg CDF is implemented for DiscreteDistribution, consider removing + # the special-casing here. + if issubclass(dist_family, ContinuousDistribution): + example_continuous = f""" The inverse of these functions with respect to the argument ``x`` is also available. @@ -1370,22 +1457,40 @@ def _generate_example(dist_family): >>> y = {y} >>> np.allclose(X.ccdf(x, y), 1 - (X.cdf(y) - X.cdf(x))) True + """ + example += example_continuous + example += f""" There are methods for computing measures of central tendency, dispersion, higher moments, and entropy. >>> X.mean(), X.median(), X.mode() {X.mean(), X.median(), X.mode()} + >>> X.variance(), X.standard_deviation() {X.variance(), X.standard_deviation()} + >>> X.skewness(), X.kurtosis() {X.skewness(), X.kurtosis()} + >>> np.allclose(X.moment(order=6, kind='standardized'), ... X.moment(order=6, kind='central') / X.variance()**3) True + """ + + # When logentropy is implemented for DiscreteDistribution, remove special-casing + if issubclass(dist_family, ContinuousDistribution): + example += """ >>> np.allclose(np.exp(X.logentropy()), X.entropy()) True + """ + else: + example += f""" + >>> X.entropy() + {X.entropy()} + """ + example += f""" Pseudo-random samples can be drawn from the underlying distribution using ``sample``. @@ -1398,7 +1503,7 @@ def _generate_example(dist_family): return example -class ContinuousDistribution(_ProbabilityDistribution): +class UnivariateDistribution(_ProbabilityDistribution): r""" Class that represents a continuous statistical distribution. Parameters @@ -1983,12 +2088,24 @@ def _quadrature(self, integrand, limits=None, args=None, args = np.broadcast_arrays(*args) # If we know the median or mean, consider breaking up the interval rtol = None if _isnull(self.tol) else self.tol - res = _tanhsinh(f, a, b, args=args, log=log, rtol=rtol) # For now, we ignore the status, but I want to return the error # estimate - see question 5 at the top. - return res.integral - - def _solve_bounded(self, f, p, *, bounds=None, params=None): + if isinstance(self, ContinuousDistribution): + res = _tanhsinh(f, a, b, args=args, log=log, rtol=rtol) + return res.integral + else: + res = nsum(f, a, b, args=args, log=log, tolerances=dict(rtol=rtol)).sum + res = np.asarray(res) + # The result should be nan when parameters are nan, so need to special + # case this. + cond = np.isnan(params.popitem()[1]) if params else np.True_ + cond = np.broadcast_to(cond, a.shape) + res[(a > b)] = -np.inf if log else 0 # fix in nsum? + res[cond] = np.nan + + return res[()] + + def _solve_bounded(self, f, p, *, bounds=None, params=None, xatol=None): # Finds the argument of a function that produces the desired output. # Much of this should be added to _bracket_root / _chandrupatla. xmin, xmax = self._support(**params) if bounds is None else bounds @@ -1997,7 +2114,10 @@ def _solve_bounded(self, f, p, *, bounds=None, params=None): p, xmin, xmax = np.broadcast_arrays(p, xmin, xmax) if not p.size: # might need to figure out result type based on p - return np.empty(p.shape, dtype=self._dtype) + res = _RichResult() + empty = np.empty(p.shape, dtype=self._dtype) + res.xl, res.x, res.xr = empty, empty, empty + res.fl, res.fr = empty, empty def f2(x, _p, **kwargs): # named `_p` to avoid conflict with shape `p` return f(x, **kwargs) - _p @@ -2018,8 +2138,11 @@ def f2(x, _p, **kwargs): # named `_p` to avoid conflict with shape `p` res = _bracket_root(f3, xl0=xl0, xr0=xr0, xmin=xmin, xmax=xmax, args=args) # For now, we ignore the status, but I want to use the bracket width # as an error estimate - see question 5 at the top. + xrtol = None if _isnull(self.tol) else self.tol - return _chandrupatla(f3, a=res.xl, b=res.xr, args=args, xrtol=xrtol).x + xatol = None if xatol is None else xatol + tolerances = dict(xrtol=xrtol, xatol=xatol, fatol=0, frtol=0) + return _chandrupatla(f3, a=res.xl, b=res.xr, args=args, **tolerances) ## Other @@ -2037,7 +2160,7 @@ def _overrides(self, method_name): # For more complete discussion of the considerations, see: # https://github.com/scipy/scipy/pull/21050#discussion_r1707798901 method = getattr(self.__class__, method_name, None) - super_method = getattr(ContinuousDistribution, method_name, None) + super_method = getattr(UnivariateDistribution, method_name, None) return method is not super_method ### Distribution properties @@ -2166,8 +2289,8 @@ def _logentropy_logexp_safe(self, **params): def _logentropy_quadrature(self, **params): def logintegrand(x, **params): - logpdf = self._logpdf_dispatch(x, **params) - return logpdf + np.log(0j+logpdf) + logpxf = self._logpxf_dispatch(x, **params) + return logpxf + np.log(0j+logpxf) res = self._quadrature(logintegrand, params=params, log=True) return _log_real_standardize(res + np.pi*1j) @@ -2193,9 +2316,12 @@ def _entropy_logexp(self, **params): def _entropy_quadrature(self, **params): def integrand(x, **params): - pdf = self._pdf_dispatch(x, **params) - logpdf = self._logpdf_dispatch(x, **params) - return logpdf * pdf + pxf = self._pxf_dispatch(x, **params) + logpxf = self._logpxf_dispatch(x, **params) + temp = np.asarray(pxf) + i = (pxf != 0) # 0 * inf -> nan; should be 0 + temp[i] = pxf[i]*logpxf[i] + return temp return -self._quadrature(integrand, params=params) @_set_invalid_nan_property @@ -2233,17 +2359,18 @@ def _mode_dispatch(self, method=None, **params): def _mode_formula(self, **params): raise NotImplementedError(self._not_implemented) - def _mode_optimization(self, **params): + def _mode_optimization(self, xatol=None, **params): if not self._size: return np.empty(self._shape, dtype=self._dtype) a, b = self._support(**params) m = self._median_dispatch(**params) - f, args = _kwargs2args(lambda x, **params: -self._pdf_dispatch(x, **params), + f, args = _kwargs2args(lambda x, **params: -self._pxf_dispatch(x, **params), args=(), kwargs=params) res_b = _bracket_minimum(f, m, xmin=a, xmax=b, args=args) - res = _chandrupatla_minimize(f, res_b.xl, res_b.xm, res_b.xr, args=args) + res = _chandrupatla_minimize(f, res_b.xl, res_b.xm, res_b.xr, + args=args, xatol=xatol) mode = np.asarray(res.x) mode_at_boundary = res_b.status == -1 mode_at_left = mode_at_boundary & (res_b.fl <= res_b.fm) @@ -2307,7 +2434,7 @@ def kurtosis(self, *, method=None, convention='non-excess'): # See the note corresponding with the "Distribution Parameters" for more # information. - ## Probability Density Functions + ## Probability Density/Mass Functions @_set_invalid_nan def logpdf(self, x, /, *, method=None): @@ -2345,6 +2472,43 @@ def _pdf_formula(self, x, **params): def _pdf_logexp(self, x, **params): return np.exp(self._logpdf_dispatch(x, **params)) + @_set_invalid_nan + def logpmf(self, x, /, *, method=None): + return self._logpmf_dispatch(x, method=method, **self._parameters) + + @_dispatch + def _logpmf_dispatch(self, x, *, method=None, **params): + if self._overrides('_logpmf_formula'): + method = self._logpmf_formula + elif _isnull(self.tol): # ensure that developers override _logpmf + method = self._logpmf_logexp + return method + + def _logpmf_formula(self, x, **params): + raise NotImplementedError(self._not_implemented) + + def _logpmf_logexp(self, x, **params): + with np.errstate(divide='ignore'): + return np.log(self._pmf_dispatch(x, **params)) + + @_set_invalid_nan + def pmf(self, x, /, *, method=None): + return self._pmf_dispatch(x, method=method, **self._parameters) + + @_dispatch + def _pmf_dispatch(self, x, *, method=None, **params): + if self._overrides('_pmf_formula'): + method = self._pmf_formula + else: + method = self._pmf_logexp + return method + + def _pmf_formula(self, x, **params): + raise NotImplementedError(self._not_implemented) + + def _pmf_logexp(self, x, **params): + return np.exp(self._logpmf_dispatch(x, **params)) + ## Cumulative Distribution Functions def logcdf(self, x, y=None, /, *, method=None): @@ -2410,7 +2574,7 @@ def _logcdf2_logexp_safe(self, x, y, **params): return out[()] def _logcdf2_quadrature(self, x, y, **params): - logres = self._quadrature(self._logpdf_dispatch, limits=(x, y), + logres = self._quadrature(self._logpxf_dispatch, limits=(x, y), log=True, params=params) return logres @@ -2451,7 +2615,7 @@ def _logcdf_logexp_safe(self, x, **params): def _logcdf_quadrature(self, x, **params): a, _ = self._support(**params) - return self._quadrature(self._logpdf_dispatch, limits=(a, x), + return self._quadrature(self._logpxf_dispatch, limits=(a, x), params=params, log=True) def cdf(self, x, y=None, /, *, method=None): @@ -2515,11 +2679,11 @@ def _cdf2_subtraction_safe(self, x, y, **params): params_mask = {key: np.broadcast_to(val, mask.shape)[mask] for key, val in params.items()} out = np.asarray(out) - out[mask] = self._cdf2_quadrature(x[mask], y[mask], *params_mask) + out[mask] = self._cdf2_quadrature(x[mask], y[mask], **params_mask) return out[()] def _cdf2_quadrature(self, x, y, **params): - return self._quadrature(self._pdf_dispatch, limits=(x, y), params=params) + return self._quadrature(self._pxf_dispatch, limits=(x, y), params=params) @_set_invalid_nan def _cdf1(self, x, *, method): @@ -2561,7 +2725,7 @@ def _cdf_complement_safe(self, x, **params): def _cdf_quadrature(self, x, **params): a, _ = self._support(**params) - return self._quadrature(self._pdf_dispatch, limits=(a, x), + return self._quadrature(self._pxf_dispatch, limits=(a, x), params=params) def logccdf(self, x, y=None, /, *, method=None): @@ -2629,7 +2793,7 @@ def _logccdf_logexp_safe(self, x, **params): def _logccdf_quadrature(self, x, **params): _, b = self._support(**params) - return self._quadrature(self._logpdf_dispatch, limits=(x, b), + return self._quadrature(self._logpxf_dispatch, limits=(x, b), params=params, log=True) def ccdf(self, x, y=None, /, *, method=None): @@ -2699,7 +2863,7 @@ def _ccdf_complement_safe(self, x, **params): def _ccdf_quadrature(self, x, **params): _, b = self._support(**params) - return self._quadrature(self._pdf_dispatch, limits=(x, b), + return self._quadrature(self._pxf_dispatch, limits=(x, b), params=params) ## Inverse cumulative distribution functions @@ -2725,7 +2889,7 @@ def _ilogcdf_complement(self, x, **params): return self._ilogccdf_dispatch(_log1mexp(x), **params) def _ilogcdf_inversion(self, x, **params): - return self._solve_bounded(self._logcdf_dispatch, x, params=params) + return self._solve_bounded_continuous(self._logcdf_dispatch, x, params=params) @_set_invalid_nan def icdf(self, p, /, *, method=None): @@ -2760,7 +2924,7 @@ def _icdf_complement_safe(self, x, **params): return out[()] def _icdf_inversion(self, x, **params): - return self._solve_bounded(self._cdf_dispatch, x, params=params) + return self._solve_bounded_continuous(self._cdf_dispatch, x, params=params) @_set_invalid_nan def ilogccdf(self, logp, /, *, method=None): @@ -2783,7 +2947,7 @@ def _ilogccdf_complement(self, x, **params): return self._ilogcdf_dispatch(_log1mexp(x), **params) def _ilogccdf_inversion(self, x, **params): - return self._solve_bounded(self._logccdf_dispatch, x, params=params) + return self._solve_bounded_continuous(self._logccdf_dispatch, x, params=params) @_set_invalid_nan def iccdf(self, p, /, *, method=None): @@ -2818,7 +2982,7 @@ def _iccdf_complement_safe(self, x, **params): return out[()] def _iccdf_inversion(self, x, **params): - return self._solve_bounded(self._ccdf_dispatch, x, params=params) + return self._solve_bounded_continuous(self._ccdf_dispatch, x, params=params) ### Sampling Functions # The following functions for drawing samples from the distribution are @@ -3002,7 +3166,7 @@ def _moment_raw_dispatch(self, order, *, methods, **params): moment = self._moment_raw_general(order, **params) if moment is None and 'quadrature' in methods: - moment = self._moment_integrate_pdf(order, center=self._zero, **params) + moment = self._moment_from_pxf(order, center=self._zero, **params) if moment is None and 'quadrature_icdf' in methods: moment = self._moment_integrate_icdf(order, center=self._zero, **params) @@ -3065,7 +3229,7 @@ def _moment_central_dispatch(self, order, *, methods, **params): if moment is None and 'quadrature' in methods: mean = self._moment_raw_dispatch(self._one, **params, methods=self._moment_methods) - moment = self._moment_integrate_pdf(order, center=mean, **params) + moment = self._moment_from_pxf(order, center=mean, **params) if moment is None and 'quadrature_icdf' in methods: mean = self._moment_raw_dispatch(self._one, **params, @@ -3156,10 +3320,10 @@ def _moment_standardized_general(self, order, **params): general_standard_moments = {0: self._one, 1: self._zero, 2: self._one} return general_standard_moments.get(order, None) - def _moment_integrate_pdf(self, order, center, **params): + def _moment_from_pxf(self, order, center, **params): def integrand(x, order, center, **params): - pdf = self._pdf_dispatch(x, **params) - return pdf*(x-center)**order + pxf = self._pxf_dispatch(x, **params) + return pxf*(x-center)**order return self._quadrature(integrand, args=(order, center), params=params) def _moment_integrate_icdf(self, order, center, **params): @@ -3195,7 +3359,7 @@ def _logmoment(self, order=1, *, logcenter=None, standardized=False): def _logmoment_quad(self, order, logcenter, **params): def logintegrand(x, order, logcenter, **params): - logpdf = self._logpdf_dispatch(x, **params) + logpdf = self._logpxf_dispatch(x, **params) return logpdf + order * _logexpxmexpy(np.log(x + 0j), logcenter) ## if logx == logcenter, `_logexpxmexpy` returns (-inf + 0j) ## multiplying by order produces (-inf + nan j) - bad @@ -3211,7 +3375,7 @@ def logintegrand(x, order, logcenter, **params): ### Convenience - def plot(self, x='x', y='pdf', *, t=('cdf', 0.0005, 0.9995), ax=None): + def plot(self, x='x', y=None, *, t=None, ax=None): r"""Plot a function of the distribution. Convenience function for quick visualization of the distribution @@ -3222,14 +3386,18 @@ def plot(self, x='x', y='pdf', *, t=('cdf', 0.0005, 0.9995), ax=None): x, y : str, optional String indicating the quantities to be used as the abscissa and ordinate (horizontal and vertical coordinates), respectively. - Defaults are ``'x'`` (the domain of the random variable) and - ``'pdf'`` (the probability density function). Valid values are: - 'x', 'pdf', 'cdf', 'ccdf', 'icdf', 'iccdf', 'logpdf', 'logcdf', - 'logccdf', 'ilogcdf', 'ilogccdf'. + Defaults are ``'x'`` (the domain of the random variable) and either + ``'pdf'`` (the probability density function) (continuous) or + ``'pdf'`` (the probability density function) (discrete). + Valid values are: + 'x', 'pdf', 'pmf', 'cdf', 'ccdf', 'icdf', 'iccdf', 'logpdf', 'logpmf', + 'logcdf', 'logccdf', 'ilogcdf', 'ilogccdf'. t : 3-tuple of (str, float, float), optional Tuple indicating the limits within which the quantities are plotted. - Default is ``('cdf', 0.001, 0.999)`` indicating that the central - 99.9% of the distribution is to be shown. Valid values are: + The default is ``('cdf', 0.0005, 0.9995)`` if the domain is infinite, + indicating that the central 99.9% of the distribution is to be shown; + otherwise, endpoints of the support are used where they are finite. + Valid values are: 'x', 'cdf', 'ccdf', 'icdf', 'iccdf', 'logcdf', 'logccdf', 'ilogcdf', 'ilogccdf'. ax : `matplotlib.axes`, optional @@ -3287,14 +3455,24 @@ def plot(self, x='x', y='pdf', *, t=('cdf', 0.0005, 0.9995), ax=None): # - when the parameters of the distribution are an array, # use the full range of abscissae for all curves + discrete = isinstance(self, DiscreteDistribution) t_is_quantile = {'x', 'icdf', 'iccdf', 'ilogcdf', 'ilogccdf'} t_is_probability = {'cdf', 'ccdf', 'logcdf', 'logccdf'} valid_t = t_is_quantile.union(t_is_probability) - valid_xy = valid_t.union({'pdf', 'logpdf'}) + valid_xy = valid_t.union({'pdf', 'logpdf', 'pmf', 'logpmf'}) + y_default = 'pmf' if discrete else 'pdf' + y = y_default if y is None else y ndim = self._ndim x_name, y_name = x, y - t_name, tlim = t[0], np.asarray(t[1:]) + t_name = 'cdf' if t is None else t[0] + + a, b = self.support() + tliml_default = 0 if np.all(np.isfinite(a)) else 0.0005 + tliml = tliml_default if t is None else t[1] + tlimr_default = 1 if np.all(np.isfinite(b)) else 0.9995 + tlimr = tlimr_default if t is None else t[2] + tlim = np.asarray([tliml, tlimr]) tlim = tlim[:, np.newaxis] if ndim else tlim # pdf/logpdf are not valid for `t` because we can't easily invert them @@ -3310,7 +3488,7 @@ def plot(self, x='x', y='pdf', *, t=('cdf', 0.0005, 0.9995), ax=None): message = (f'Argument `y` of `{self.__class__.__name__}.plot` "' f'must be one of {valid_xy}') - if t_name not in valid_xy: + if y_name not in valid_xy: raise ValueError(message) # This could just be a warning @@ -3343,16 +3521,32 @@ def plot(self, x='x', y='pdf', *, t=('cdf', 0.0005, 0.9995), ax=None): raise ValueError(message) # form quantile grid - grid = np.linspace(0, 1, 300) - grid = grid[:, np.newaxis] if ndim else grid - q = qlim[0] + (qlim[1] - qlim[0]) * grid + if discrete and x_name in t_is_quantile: + # should probably aggregate for large ranges + q = np.arange(np.min(qlim[0]), np.max(qlim[1]) + 1) + q = q[:, np.newaxis] if ndim else q + else: + grid = np.linspace(0, 1, 300) + grid = grid[:, np.newaxis] if ndim else grid + q = qlim[0] + (qlim[1] - qlim[0]) * grid + q = np.round(q) if discrete else q # compute requested x and y at quantile grid x = q if x_name in t_is_quantile else getattr(self, x_name)(q) y = q if y_name in t_is_quantile else getattr(self, y_name)(q) # make plot - ax.plot(x, y) + x, y = np.broadcast_arrays(x.T, np.atleast_2d(y.T)) + for xi, yi in zip(x, y): # plot is vectorized, but bar/step don't seem to be + if discrete and x_name in t_is_quantile and y_name == 'pmf': + # should this just be a step plot, too? + ax.bar(xi, yi, alpha=np.sqrt(1/y.shape[0])) # alpha heuristic + elif discrete and x_name in t_is_quantile: + values = yi + edges = np.concatenate((xi, [xi[-1]+1])) + ax.stairs(values, edges, baseline=None) + else: + ax.plot(xi, yi) ax.set_xlabel(f"${x_name}$") ax.set_ylabel(f"${y_name}$") ax.set_title(str(self)) @@ -3402,8 +3596,176 @@ def plot(self, x='x', y='pdf', *, t=('cdf', 0.0005, 0.9995), ax=None): # these methods reasonably efficiently. +class ContinuousDistribution(UnivariateDistribution): + def _overrides(self, method_name): + if method_name in {'_logpmf_formula', '_pmf_formula'}: + return True + return super()._overrides(method_name) + + def _pmf_formula(self, x, **params): + return np.zeros_like(x) + + def _logpmf_formula(self, x, **params): + return np.full_like(x, -np.inf) + + def _pxf_dispatch(self, x, *, method=None, **params): + return self._pdf_dispatch(x, method=method, **params) + + def _logpxf_dispatch(self, x, *, method=None, **params): + return self._logpdf_dispatch(x, method=method, **params) + + def _solve_bounded_continuous(self, func, p, params, xatol=None): + return self._solve_bounded(func, p, params=params, xatol=xatol).x + + +class DiscreteDistribution(UnivariateDistribution): + def _overrides(self, method_name): + if method_name in {'_logpdf_formula', '_pdf_formula'}: + return True + return super()._overrides(method_name) + + def _logpdf_formula(self, x, **params): + if params: + p = next(iter(params.values())) + nan_result = np.isnan(x) | np.isnan(p) + else: + nan_result = np.isnan(x) + return np.where(nan_result, np.nan, np.inf) + + def _pdf_formula(self, x, **params): + if params: + p = next(iter(params.values())) + nan_result = np.isnan(x) | np.isnan(p) + else: + nan_result = np.isnan(x) + return np.where(nan_result, np.nan, np.inf) + + def _pxf_dispatch(self, x, *, method=None, **params): + return self._pmf_dispatch(x, method=method, **params) + + def _logpxf_dispatch(self, x, *, method=None, **params): + return self._logpmf_dispatch(x, method=method, **params) + + def _cdf_quadrature(self, x, **params): + return super()._cdf_quadrature(np.floor(x), **params) + + def _logcdf_quadrature(self, x, **params): + return super()._logcdf_quadrature(np.floor(x), **params) + + def _ccdf_quadrature(self, x, **params): + return super()._ccdf_quadrature(np.floor(x + 1), **params) + + def _logccdf_quadrature(self, x, **params): + return super()._logccdf_quadrature(np.floor(x + 1), **params) + + def _cdf2(self, x, y, *, method): + raise NotImplementedError( + "Two argument cdf functions are currently only supported for " + "continuous distributions.") + + def _ccdf2(self, x, y, *, method): + raise NotImplementedError( + "Two argument cdf functions are currently only supported for " + "continuous distributions.") + + def _logcdf2(self, x, y, *, method): + raise NotImplementedError( + "Two argument cdf functions are currently only supported for " + "continuous distributions.") + + def _logccdf2(self, x, y, *, method): + raise NotImplementedError( + "Two argument cdf functions are currently only supported for " + "continuous distributions.") + + def _solve_bounded_discrete(self, func, p, params, comp): + res = self._solve_bounded(func, p, params=params, xatol=0.9) + x = np.asarray(np.floor(res.xr)) + + # if _chandrupatla finds exact inverse, the bracket may not have been reduced + # enough for `np.floor(res.x)` to be the appropriate value of `x`. + mask = res.fun == 0 + x[mask] = np.floor(res.x[mask]) + + xmin, xmax = self._support(**params) + p, xmin, xmax = np.broadcast_arrays(p, xmin, xmax) + mask = comp(func(xmin, **params), p) + x[mask] = xmin[mask] + + return x + + def _base_discrete_inversion(self, p, func, comp, /, **params): + # For discrete distributions, icdf(p) is defined as the minimum n + # such that cdf(n) >= p. iccdf(p) is defined as the minimum n such + # that ccdf(n) <= p, or equivalently as iccdf(p) = icdf(1 - p). + + # First try to find where cdf(x) == p for the continuous extension of the + # cdf. res.xl and res.xr will be a bracket for this root. The parameter + # xatol in solve_bounded controls the bracket width. We thus know that + # know cdf(res.xr) >= p, cdf(res.xl) <= p, and |res.xr - res.xl| <= 0.9. + # This means the minimum integer n such that cdf(n) >= p is either floor(x) + # or floor(x) + 1. + x = self._solve_bounded_discrete(func, p, params=params, comp=comp) + # comp should be <= for ccdf, >= for cdf. + f = func(x, **params) + res = np.where(comp(f, p), x, x + 1.0) + # xr is a bracket endpoint, and will usually be a finite value even when + # the computed result should be nan. We need to explicitly handle this + # case. + res[np.isnan(f) | np.isnan(p)] = np.nan + return res[()] + + def _icdf_inversion(self, x, **params): + return self._base_discrete_inversion(x, self._cdf_dispatch, + np.greater_equal, **params) + + def _ilogcdf_inversion(self, x, **params): + return self._base_discrete_inversion(x, self._logcdf_dispatch, + np.greater_equal, **params) + + def _iccdf_inversion(self, x, **params): + return self._base_discrete_inversion(x, self._ccdf_dispatch, + np.less_equal, **params) + + def _ilogccdf_inversion(self, x, **params): + return self._base_discrete_inversion(x, self._logccdf_dispatch, + np.less_equal, **params) + + def _mode_optimization(self, **params): + # If `x` is the true mode of a unimodal continuous function, we can find + # the mode among integers by rounding in each direction and checking + # which is better. If the difference between `x` and the nearest integer + # is less than `xatol`, the computed value of `x` may end up on the wrong + # side of the nearest integer. Setting `xatol=0.5` guarantees that at most + # three integers need to be checked, the two nearest integers, ``floor(x)`` + # and ``round(x)`` and the nearest integer other than these. + x = super()._mode_optimization(xatol=0.5, **params) + low, high = self.support() + xl, xr = np.floor(x), np.ceil(x) + nearest = np.round(x) + # Clip to stay within support. There will be redundant calculation + # when clipping since `xo` will be one of `xl` or `xr`, but let's + # keep the implementation simple for now. + xo = np.clip(nearest + np.copysign(1, nearest - x), low, high) + x = np.stack([xl, xo, xr]) + idx = np.argmax(self._pmf_dispatch(x, **params), axis=0) + return np.choose(idx, [xl, xo, xr]) + + def _logentropy_quadrature(self, **params): + def logintegrand(x, **params): + logpmf = self._logpmf_dispatch(x, **params) + # Entropy summand is -pmf*log(pmf), so log-entropy summand is + # logpmf + log(logpmf) + pi*j. But pmf is always between 0 and 1, + # so logpmf is always negative, and so log(logpmf) = log(-logpmf) + pi*j. + # The two imaginary components "cancel" each other out (which we would + # expect because each term of the entropy summand is positive). + return np.where(np.isfinite(logpmf), logpmf + np.log(-logpmf), -np.inf) + return self._quadrature(logintegrand, params=params, log=True) + + # Special case the names of some new-style distributions in `make_distribution` _distribution_names = { + # Continuous 'argus': 'ARGUS', 'betaprime': 'BetaPrime', 'chi2': 'ChiSquared', @@ -3476,34 +3838,52 @@ def plot(self, x='x', y='pdf', *, t=('cdf', 0.0005, 0.9995), ax=None): 'weibull_min': 'Weibull', 'weibull_max': 'ReflectedWeibull', 'wrapcauchy': 'WrappedCauchyLine', + # Discrete + 'betabinom': 'BetaBinomial', + 'betanbinom': 'BetaNegativeBinomial', + 'dlaplace': 'LaplaceDiscrete', + 'geom': 'Geometric', + 'hypergeom': 'Hypergeometric', + 'logser': 'LogarithmicSeries', + 'nbinom': 'NegativeBinomial', + 'nchypergeom_fisher': 'NoncentralHypergeometricFisher', + 'nchypergeom_wallenius': 'NoncentralHypergeometricWallenius', + 'nhypergeom': 'NegativeHypergeometric', + 'poisson_binom': 'PoissonBinomial', + 'randint': 'UniformDiscrete', + 'yulesimon': 'YuleSimon', + 'zipf': 'Zeta', } # beta, genextreme, gengamma, t, tukeylambda need work for 1D arrays def make_distribution(dist): - """Generate a `ContinuousDistribution` class from a compatible object + """Generate a `UnivariateDistribution` class from a compatible object The argument may be an instance of `rv_continuous` or an instance of another class that satisfies the interface described below. - The returned value is a `ContinuousDistribution` subclass. Like any subclass - of `ContinuousDistribution`, it must be instantiated (i.e. by passing all shape - parameters as keyword arguments) before use. Once instantiated, the resulting - object will have the same interface as any other instance of - `ContinuousDistribution`; e.g., `scipy.stats.Normal`. + The returned value is a `ContinuousDistribution` subclass if the input is an + instance of `rv_continuous` or a `DiscreteDistribution` subclass if the input + is an instance of `rv_discrete`. Like any subclass of `UnivariateDistribution`, + it must be instantiated (i.e. by passing all shape parameters as keyword + arguments) before use. Once instantiated, the resulting object will have the + same interface as any other instance of `UnivariateDistribution`; e.g., + `scipy.stats.Normal`, `scipy.stats.Binomial`. .. note:: `make_distribution` does not work perfectly with all instances of - `rv_continuous`. Known failures include `levy_stable` and `vonmises`, - and some methods of some distributions will not support array shape - parameters. + `rv_continuous`. Known failures include `levy_stable`, `vonmises`, + `hypergeom`, 'nchypergeom_fisher', 'nchypergeom_wallenius', and + `poisson_binom`. Some methods of some distributions will not support + array shape parameters. Parameters ---------- dist : `rv_continuous` - Instance of `rv_continuous` OR an instance of any class with the following - attributes: + Instance of `rv_continuous`, `rv_discrete`, or an instance of any class with + the following attributes: __make_distribution_version__ : str A string containing the version number of SciPy in which this interface @@ -3558,7 +3938,7 @@ def make_distribution(dist): ``moment``, and ``sample``. If defined, these methods must accept the parameters of the distribution as keyword arguments and also accept any positional-only arguments accepted by - the corresponding method of `ContinuousDistribution`. + the corresponding method of `ContinuousDistribution`. When multiple parameterizations are defined, these methods must accept all parameters from all parameterizations. The ``moment`` method must accept the ``order`` and ``kind`` arguments by position or keyword, but @@ -3571,14 +3951,14 @@ def make_distribution(dist): Returns ------- - CustomDistribution : `ContinuousDistribution` - A subclass of `ContinuousDistribution` corresponding with `dist`. The + CustomDistribution : `UnivariateDistribution` + A subclass of `UnivariateDistribution` corresponding with `dist`. The initializer requires all shape parameters to be passed as keyword arguments - (using the same names as the instance of `rv_continuous`). + (using the same names as the instance of `rv_continuous`/`rv_discrete`). Notes ----- - The documentation of `ContinuousDistribution` is not rendered. See below for + The documentation of `UnivariateDistribution` is not rendered. See below for an example of how to instantiate the class (i.e. pass all shape parameters of `dist` to the initializer as keyword arguments). Documentation of all methods is identical to that of `scipy.stats.Normal`. Use ``help`` on the returned @@ -3638,7 +4018,7 @@ class or its methods for more information. ... ... @property ... def parameters(self): - ... return {"a": (-np.inf, np.inf), + ... return {"a": (-np.inf, np.inf), ... "b": {'endpoints':('a', np.inf), 'inclusive':(True, False)}} ... ... @property @@ -3704,33 +4084,41 @@ class or its methods for more information. np.True_ """ - if dist in {stats.levy_stable, stats.vonmises}: + if dist in {stats.levy_stable, stats.vonmises, stats.hypergeom, + stats.nchypergeom_fisher, stats.nchypergeom_wallenius, + stats.poisson_binom}: raise NotImplementedError(f"`{dist.name}` is not supported.") - if isinstance(dist, stats.rv_continuous): + if isinstance(dist, stats.rv_continuous | stats.rv_discrete): return _make_distribution_rv_generic(dist) elif getattr(dist, "__make_distribution_version__", "0.0.0") >= "1.16.0": return _make_distribution_custom(dist) else: - message = ("The argument must be an instance of `rv_continuous` or an instance " - "of a class with attribute `__make_distribution_version__ >= 1.16`.") + message = ("The argument must be an instance of `rv_continuous`, " + "`rv_discrete`, or an instance of a class with attribute " + "`__make_distribution_version__ >= 1.16`.") raise ValueError(message) - def _make_distribution_rv_generic(dist): parameters = [] names = [] support = getattr(dist, '_support', (dist.a, dist.b)) for shape_info in dist._shape_info(): - domain = _RealDomain(endpoints=shape_info.endpoints, + domain = _RealInterval(endpoints=shape_info.endpoints, inclusive=shape_info.inclusive) param = _RealParameter(shape_info.name, domain=domain) parameters.append(param) names.append(shape_info.name) + repr_str = _distribution_names.get(dist.name, dist.name.capitalize()) + if isinstance(dist, stats.rv_continuous): + old_class, new_class = stats.rv_continuous, ContinuousDistribution + else: + old_class, new_class = stats.rv_discrete, DiscreteDistribution + def _overrides(method_name): return (getattr(dist.__class__, method_name, None) - is not getattr(stats.rv_continuous, method_name, None)) + is not getattr(old_class, method_name, None)) if _overrides("_get_support"): def left(**parameter_values): @@ -3745,12 +4133,10 @@ def right(**parameter_values): else: endpoints = support - _x_support = _RealDomain(endpoints=endpoints, inclusive=(True, True)) + _x_support = _RealInterval(endpoints=endpoints, inclusive=(True, True)) _x_param = _RealParameter('x', domain=_x_support, typical=(-1, 1)) - repr_str = _distribution_names.get(dist.name, dist.name.capitalize()) - - class CustomDistribution(ContinuousDistribution): + class CustomDistribution(new_class): _parameterizations = ([_Parameterization(*parameters)] if parameters else []) _variable = _x_param @@ -3794,6 +4180,8 @@ def _moment_standard_formula(self, order, **kwargs): methods = {'_logpdf': '_logpdf_formula', '_pdf': '_pdf_formula', + '_logpmf': '_logpmf_formula', + '_pmf': '_pmf_formula', '_logcdf': '_logcdf_formula', '_cdf': '_cdf_formula', '_logsf': '_logccdf_formula', @@ -3811,7 +4199,7 @@ def _moment_standard_formula(self, order, **kwargs): continue # If method of old distribution overrides generic implementation... method = getattr(dist.__class__, old_method, None) - super_method = getattr(stats.rv_continuous, old_method, None) + super_method = getattr(old_class, old_method, None) if method is not super_method: # Make it an attribute of the new object with the new name setattr(CustomDistribution, new_method, getattr(dist, old_method)) @@ -3831,7 +4219,7 @@ def _moment_standard_formula(self, order, **kwargs): support_etc = _combine_docs(CustomDistribution, include_examples=False).lstrip() docs = [ f"This class represents `scipy.stats.{dist.name}` as a subclass of " - "`ContinuousDistribution`.", + f"`{new_class}`.", f"The `repr`/`str` of class instances is `{repr_str}`.", f"The PDF of the distribution is defined {support_etc}" ] @@ -3861,13 +4249,13 @@ def _make_distribution_custom(dist): for name, info in parameterization.items(): domain_info, typical = _get_domain_info(info) - domain = _RealDomain(**domain_info) + domain = _RealInterval(**domain_info) param = _RealParameter(name, domain=domain, typical=typical) parameters.append(param) parameterizations.append(_Parameterization(*parameters) if parameters else []) domain_info, _ = _get_domain_info(dist.support) - _x_support = _RealDomain(**domain_info) + _x_support = _RealInterval(**domain_info) _x_param = _RealParameter('x', domain=_x_support) repr_str = dist.__class__.__name__ @@ -3998,6 +4386,9 @@ def wrapped(self, p, *args, loc, scale, sign, **kwargs): class TransformedDistribution(ContinuousDistribution): def __init__(self, X, /, *args, **kwargs): + if not isinstance(X, ContinuousDistribution): + message = "Transformations are currently only supported for continuous RVs." + raise NotImplementedError(message) self._copy_parameterization() self._variable = X._variable self._dist = X @@ -4053,11 +4444,11 @@ class TruncatedDistribution(TransformedDistribution): # - if the mode of `_dist` is within the support, it's still the mode # - rejection sampling might be more efficient than inverse transform - _lb_domain = _RealDomain(endpoints=(-inf, 'ub'), inclusive=(True, False)) + _lb_domain = _RealInterval(endpoints=(-inf, 'ub'), inclusive=(True, False)) _lb_param = _RealParameter('lb', symbol=r'b_l', domain=_lb_domain, typical=(0.1, 0.2)) - _ub_domain = _RealDomain(endpoints=('lb', inf), inclusive=(False, True)) + _ub_domain = _RealInterval(endpoints=('lb', inf), inclusive=(False, True)) _ub_param = _RealParameter('ub', symbol=r'b_u', domain=_ub_domain, typical=(0.8, 0.9)) @@ -4188,7 +4579,7 @@ def truncate(X, lb=-np.inf, ub=np.inf): Furthermore, `truncate` can be applied to any random variable: >>> Rayleigh = stats.make_distribution(stats.rayleigh) - >>> W = stats.truncate(Rayleigh(), lb=0, ub=3) + >>> W = stats.truncate(Rayleigh(), lb=0.5, ub=3) >>> W.plot() >>> plt.show() @@ -4199,11 +4590,11 @@ def truncate(X, lb=-np.inf, ub=np.inf): class ShiftedScaledDistribution(TransformedDistribution): """Distribution with a standard shift/scale transformation.""" # Unclear whether infinite loc/scale will work reasonably in all cases - _loc_domain = _RealDomain(endpoints=(-inf, inf), inclusive=(True, True)) + _loc_domain = _RealInterval(endpoints=(-inf, inf), inclusive=(True, True)) _loc_param = _RealParameter('loc', symbol=r'\mu', domain=_loc_domain, typical=(1, 2)) - _scale_domain = _RealDomain(endpoints=(-inf, inf), inclusive=(True, True)) + _scale_domain = _RealInterval(endpoints=(-inf, inf), inclusive=(True, True)) _scale_param = _RealParameter('scale', symbol=r'\sigma', domain=_scale_domain, typical=(0.1, 10)) @@ -4295,6 +4686,26 @@ def _pdf_dispatch(self, x, *args, loc, scale, sign, **params): pdf = self._dist._pdf_dispatch(x, *args, **params) return pdf / np.abs(scale) + def _logpmf_dispatch(self, x, *args, loc, scale, sign, **params): + x = self._transform(x, loc, scale) + logpmf = self._dist._logpmf_dispatch(x, *args, **params) + return logpmf - np.log(np.abs(scale)) + + def _pmf_dispatch(self, x, *args, loc, scale, sign, **params): + x = self._transform(x, loc, scale) + pmf = self._dist._pmf_dispatch(x, *args, **params) + return pmf / np.abs(scale) + + def _logpxf_dispatch(self, x, *args, loc, scale, sign, **params): + x = self._transform(x, loc, scale) + logpxf = self._dist._logpxf_dispatch(x, *args, **params) + return logpxf - np.log(np.abs(scale)) + + def _pxf_dispatch(self, x, *args, loc, scale, sign, **params): + x = self._transform(x, loc, scale) + pxf = self._dist._pxf_dispatch(x, *args, **params) + return pxf / np.abs(scale) + # Sorry about the magic. This is just a draft to show the behavior. @_shift_scale_distribution_function def _logcdf_dispatch(self, x, *, method=None, **params): @@ -4460,12 +4871,12 @@ class OrderStatisticDistribution(TransformedDistribution): """ - # These can be restricted to _IntegerDomain/_IntegerParameter in a separate + # These can be restricted to _IntegerInterval/_IntegerParameter in a separate # PR if desired. - _r_domain = _RealDomain(endpoints=(1, 'n'), inclusive=(True, True)) + _r_domain = _RealInterval(endpoints=(1, 'n'), inclusive=(True, True)) _r_param = _RealParameter('r', domain=_r_domain, typical=(1, 2)) - _n_domain = _RealDomain(endpoints=(1, np.inf), inclusive=(True, True)) + _n_domain = _RealInterval(endpoints=(1, np.inf), inclusive=(True, True)) _n_param = _RealParameter('n', domain=_n_domain, typical=(1, 4)) _r_domain.define_parameters(_n_param) @@ -4864,6 +5275,14 @@ def logpdf(self, x, /, *, method=None): self._raise_if_method(method) return self._logsum('logpdf', x) + def pmf(self, x, /, *, method=None): + self._raise_if_method(method) + return self._sum('pmf', x) + + def logpmf(self, x, /, *, method=None): + self._raise_if_method(method) + return self._logsum('logpmf', x) + def cdf(self, x, y=None, /, *, method=None): self._raise_if_method(method) args = (x,) if y is None else (x, y) @@ -5149,7 +5568,7 @@ def _logccdf_dispatch(self, x, *args, method=None, **params): a, b = self._dist._support(**params) xl = np.maximum(-x, a) xr = np.minimum(x, b) - return self._dist._logccdf2_dispatch(xl, xr, *args, method=method, + return self._dist._logccdf2_dispatch(xl, xr, *args, method=method, **params).real def _ccdf_dispatch(self, x, *args, method=None, **params): diff --git a/scipy/stats/_entropy.py b/scipy/stats/_entropy.py index 12e7c45a0dd7..aa32dc762e09 100644 --- a/scipy/stats/_entropy.py +++ b/scipy/stats/_entropy.py @@ -7,8 +7,9 @@ import math import numpy as np from scipy import special -from ._axis_nan_policy import _axis_nan_policy_factory, _broadcast_arrays -from scipy._lib._array_api import array_namespace, xp_promote +from ._axis_nan_policy import _axis_nan_policy_factory + +from scipy._lib._array_api import array_namespace, xp_promote, is_marray, _share_masks from scipy._lib import array_api_extra as xpx __all__ = ['entropy', 'differential_entropy'] @@ -20,7 +21,7 @@ 2 if ("qk" in kwgs and kwgs["qk"] is not None) else 1 ), - n_outputs=1, result_to_tuple=lambda x: (x,), paired=True, + n_outputs=1, result_to_tuple=lambda x, _: (x,), paired=True, too_small=-1 # entropy doesn't have too small inputs ) def entropy(pk: np.typing.ArrayLike, @@ -140,19 +141,24 @@ def entropy(pk: np.typing.ArrayLike, if base is not None and base <= 0: raise ValueError("`base` must be a positive number or `None`.") - xp = array_namespace(pk) if qk is None else array_namespace(pk, qk) + xp = array_namespace(pk, qk) + pk, qk = xp_promote(pk, qk, broadcast=True, xp=xp) - pk = xp.asarray(pk) with np.errstate(invalid='ignore'): - pk = 1.0*pk / xp.sum(pk, axis=axis, keepdims=True) # type: ignore[operator] + if qk is not None: + pk, qk = _share_masks(pk, qk, xp=xp) + qk = qk / xp.sum(qk, axis=axis, keepdims=True) + pk = pk / xp.sum(pk, axis=axis, keepdims=True) + if qk is None: vec = special.entr(pk) else: - qk = xp.asarray(qk) - pk, qk = _broadcast_arrays((pk, qk), axis=None, xp=xp) # don't ignore any axes - sum_kwargs = dict(axis=axis, keepdims=True) - qk = 1.0*qk / xp.sum(qk, **sum_kwargs) # type: ignore[operator, call-overload] - vec = special.rel_entr(pk, qk) + if is_marray(xp): # compensate for mdhaber/marray#97 + vec = special.rel_entr(pk.data, qk.data) # type: ignore[union-attr] + vec = xp.asarray(vec, mask=pk.mask) # type: ignore[union-attr] + else: + vec = special.rel_entr(pk, qk) + S = xp.sum(vec, axis=axis) if base is not None: S /= math.log(base) @@ -170,7 +176,7 @@ def _differential_entropy_is_too_small(samples, kwargs, axis=-1): @_axis_nan_policy_factory( - lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,), + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,), too_small=_differential_entropy_is_too_small ) def differential_entropy( diff --git a/scipy/stats/_hypotests.py b/scipy/stats/_hypotests.py index 4535ea7fe9f8..ca451fdb9bb2 100644 --- a/scipy/stats/_hypotests.py +++ b/scipy/stats/_hypotests.py @@ -482,7 +482,7 @@ def _cdf_cvm(x, n=None): return y -def _cvm_result_to_tuple(res): +def _cvm_result_to_tuple(res, _): return res.statistic, res.pvalue @@ -1749,13 +1749,12 @@ def __str__(self): s = ("Pairwise Group Comparisons" f" ({self._ci_cl*100:.1f}% Confidence Interval)\n") s += "Comparison Statistic p-value Lower CI Upper CI\n" - for i in range(self.pvalue.shape[0]): - for j in range(self.pvalue.shape[0]): - if i != j: - s += (f" ({i} - {j}) {self.statistic[i, j]:>10.3f}" - f"{self.pvalue[i, j]:>10.3f}" - f"{self._ci.low[i, j]:>10.3f}" - f"{self._ci.high[i, j]:>10.3f}\n") + for i, j in np.ndindex(self.pvalue.shape): + if i != j: + s += (f" ({i} - {j}) {self.statistic[i, j]:>10.3f}" + f"{self.pvalue[i, j]:>10.3f}" + f"{self._ci.low[i, j]:>10.3f}" + f"{self._ci.high[i, j]:>10.3f}\n") return s def confidence_interval(self, confidence_level=.95): diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index b701eb3e7068..552469afe83d 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -11,19 +11,23 @@ from scipy import optimize, special, interpolate, stats from scipy._lib._bunch import _make_tuple_bunch from scipy._lib._util import _rename_parameter, _contains_nan, _get_nan +import scipy._lib.array_api_extra as xpx from scipy._lib._array_api import ( array_namespace, xp_size, xp_vector_norm, xp_promote, + is_marray, + xp_ravel, + _length_nonmasked, ) from ._ansari_swilk_statistics import gscale, swilk from . import _stats_py, _wilcoxon from ._fit import FitResult -from ._stats_py import (find_repeats, _get_pvalue, SignificanceResult, # noqa:F401 - _SimpleNormal, _SimpleChi2, _length_nonmasked) +from ._stats_py import (_get_pvalue, SignificanceResult, # noqa:F401 + _SimpleNormal, _SimpleChi2) from .contingency import chi2_contingency from . import distributions from ._distn_infrastructure import rv_generic @@ -222,7 +226,7 @@ def mvsdist(data): @_axis_nan_policy_factory( - lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1, default_axis=None + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1, default_axis=None ) def kstat(data, n=2, *, axis=None): r""" @@ -327,7 +331,7 @@ def kstat(data, n=2, *, axis=None): @_axis_nan_policy_factory( - lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1, default_axis=None + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1, default_axis=None ) def kstatvar(data, n=2, *, axis=None): r"""Return an unbiased estimator of the variance of the k-statistic. @@ -984,7 +988,7 @@ def boxcox_llf(lmb, data, *, axis=0, keepdims=False, nan_policy='propagate'): @_axis_nan_policy_factory(lambda x: x, n_outputs=1, default_axis=0, - result_to_tuple=lambda x: (x,)) + result_to_tuple=lambda x, _: (x,)) def _boxcox_llf(data, axis=0, *, lmb): xp = array_namespace(data) lmb, data = xp_promote(lmb, data, force_floating=True, xp=xp) @@ -2931,15 +2935,20 @@ def bartlett(*samples, axis=0): if k < 2: raise ValueError("Must enter at least two input sample vectors.") - samples = _broadcast_arrays(samples, axis=axis, xp=xp) - samples = [xp.moveaxis(sample, axis, -1) for sample in samples] + if axis is None: + samples = [xp_ravel(sample) for sample in samples] + else: + samples = _broadcast_arrays(samples, axis=axis, xp=xp) + samples = [xp.moveaxis(sample, axis, -1) for sample in samples] - Ni = [xp.asarray(sample.shape[-1], dtype=sample.dtype) for sample in samples] + Ni = [xp.asarray(_length_nonmasked(sample, axis=-1, xp=xp), dtype=sample.dtype) + for sample in samples] Ni = [xp.broadcast_to(N, samples[0].shape[:-1]) for N in Ni] ssq = [xp.var(sample, correction=1, axis=-1) for sample in samples] Ni = [arr[xp.newaxis, ...] for arr in Ni] ssq = [arr[xp.newaxis, ...] for arr in ssq] Ni = xp.concat(Ni, axis=0) + Ni = xpx.at(Ni)[Ni == 0].set(xp.nan) ssq = xp.concat(ssq, axis=0) dtype = Ni.dtype Ntot = xp.sum(Ni, axis=0) @@ -3496,7 +3505,7 @@ def mood(x, y, axis=0, alternative="two-sided"): WilcoxonResult = _make_tuple_bunch('WilcoxonResult', ['statistic', 'pvalue']) -def wilcoxon_result_unpacker(res): +def wilcoxon_result_unpacker(res, _): if hasattr(res, 'zstatistic'): return res.statistic, res.pvalue, res.zstatistic else: @@ -3993,7 +4002,7 @@ def _circfuncs_common(samples, period, xp=None): @_axis_nan_policy_factory( lambda x: x, n_outputs=1, default_axis=None, - result_to_tuple=lambda x: (x,) + result_to_tuple=lambda x, _: (x,) ) def circmean(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): r"""Compute the circular mean of a sample of angle observations. @@ -4086,7 +4095,7 @@ def circmean(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): @_axis_nan_policy_factory( lambda x: x, n_outputs=1, default_axis=None, - result_to_tuple=lambda x: (x,) + result_to_tuple=lambda x, _: (x,) ) def circvar(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): r"""Compute the circular variance of a sample of angle observations. @@ -4180,7 +4189,7 @@ def circvar(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): @_axis_nan_policy_factory( lambda x: x, n_outputs=1, default_axis=None, - result_to_tuple=lambda x: (x,) + result_to_tuple=lambda x, _: (x,) ) def circstd(samples, high=2*pi, low=0, axis=None, nan_policy='propagate', *, normalize=False): @@ -4416,6 +4425,12 @@ def directional_stats(samples, *, axis=0, normalize=True): raise ValueError("samples must at least be two-dimensional. " f"Instead samples has shape: {tuple(samples.shape)}") samples = xp.moveaxis(samples, axis, 0) + + if is_marray(xp): + _xp = array_namespace(samples.mask) + mask = _xp.any(samples.mask, axis=-1, keepdims=True) + samples = xp.asarray(samples.data, mask=mask) + if normalize: vectornorms = xp_vector_norm(samples, axis=-1, keepdims=True, xp=xp) samples = samples/vectornorms diff --git a/scipy/stats/_new_distributions.py b/scipy/stats/_new_distributions.py index 7775b3132f52..96257801a0d2 100644 --- a/scipy/stats/_new_distributions.py +++ b/scipy/stats/_new_distributions.py @@ -5,10 +5,10 @@ from scipy import special from scipy.stats._distribution_infrastructure import ( - ContinuousDistribution, _RealDomain, _RealParameter, _Parameterization, - _combine_docs) + ContinuousDistribution, DiscreteDistribution, _RealInterval, _IntegerInterval, + _RealParameter, _Parameterization, _combine_docs) -__all__ = ['Normal', 'Uniform'] +__all__ = ['Normal', 'Uniform', 'Binomial'] class Normal(ContinuousDistribution): @@ -25,9 +25,9 @@ class Normal(ContinuousDistribution): # `ShiftedScaledDistribution` allows this to be generated automatically from # an instance of `StandardNormal`, but the normal distribution is so frequently # used that it's worth a bit of code duplication to get better performance. - _mu_domain = _RealDomain(endpoints=(-inf, inf)) - _sigma_domain = _RealDomain(endpoints=(0, inf)) - _x_support = _RealDomain(endpoints=(-inf, inf)) + _mu_domain = _RealInterval(endpoints=(-inf, inf)) + _sigma_domain = _RealInterval(endpoints=(0, inf)) + _x_support = _RealInterval(endpoints=(-inf, inf)) _mu_param = _RealParameter('mu', symbol=r'\mu', domain=_mu_domain, typical=(-1, 1)) @@ -132,7 +132,7 @@ class StandardNormal(Normal): f(x) = \frac{1}{\sqrt{2 \pi}} \exp \left( -\frac{1}{2} x^2 \right) """ - _x_support = _RealDomain(endpoints=(-inf, inf)) + _x_support = _RealInterval(endpoints=(-inf, inf)) _x_param = _RealParameter('x', domain=_x_support, typical=(-5, 5)) _variable = _x_param _parameterizations = [] @@ -217,11 +217,11 @@ class _LogUniform(ContinuousDistribution): """ - _a_domain = _RealDomain(endpoints=(0, inf)) - _b_domain = _RealDomain(endpoints=('a', inf)) - _log_a_domain = _RealDomain(endpoints=(-inf, inf)) - _log_b_domain = _RealDomain(endpoints=('log_a', inf)) - _x_support = _RealDomain(endpoints=('a', 'b'), inclusive=(True, True)) + _a_domain = _RealInterval(endpoints=(0, inf)) + _b_domain = _RealInterval(endpoints=('a', inf)) + _log_a_domain = _RealInterval(endpoints=(-inf, inf)) + _log_b_domain = _RealInterval(endpoints=('log_a', inf)) + _x_support = _RealInterval(endpoints=('a', 'b'), inclusive=(True, True)) _a_param = _RealParameter('a', domain=_a_domain, typical=(1e-3, 0.9)) _b_param = _RealParameter('b', domain=_b_domain, typical=(1.1, 1e3)) @@ -279,9 +279,9 @@ class Uniform(ContinuousDistribution): """ - _a_domain = _RealDomain(endpoints=(-inf, inf)) - _b_domain = _RealDomain(endpoints=('a', inf)) - _x_support = _RealDomain(endpoints=('a', 'b'), inclusive=(True, True)) + _a_domain = _RealInterval(endpoints=(-inf, inf)) + _b_domain = _RealInterval(endpoints=('a', inf)) + _x_support = _RealInterval(endpoints=('a', 'b'), inclusive=(True, True)) _a_param = _RealParameter('a', domain=_a_domain, typical=(1e-3, 0.9)) _b_param = _RealParameter('b', domain=_b_domain, typical=(1.1, 1e3)) @@ -354,8 +354,8 @@ def _sample_formula(self, full_shape, rng, a, b, ab, **kwargs): class _Gamma(ContinuousDistribution): # Gamma distribution for testing only - _a_domain = _RealDomain(endpoints=(0, inf)) - _x_support = _RealDomain(endpoints=(0, inf), inclusive=(False, False)) + _a_domain = _RealInterval(endpoints=(0, inf)) + _x_support = _RealInterval(endpoints=(0, inf), inclusive=(False, False)) _a_param = _RealParameter('a', domain=_a_domain, typical=(0.1, 10)) _x_param = _RealParameter('x', domain=_x_support, typical=(0.1, 10)) @@ -367,6 +367,83 @@ def _pdf_formula(self, x, *, a, **kwargs): return x ** (a - 1) * np.exp(-x) / special.gamma(a) +class Binomial(DiscreteDistribution): + r"""Binomial distribution with prescribed success probability and number of trials + + The probability density function of the binomial distribution is: + + .. math:: + + f(x) = {n \choose x} p^x (1 - p)^{n-x} + + """ + _n_domain = _IntegerInterval(endpoints=(0, inf), inclusive=(False, False)) + _p_domain = _RealInterval(endpoints=(0, 1), inclusive=(False, False)) + _x_support = _IntegerInterval(endpoints=(0, 'n'), inclusive=(True, True)) + + _n_param = _RealParameter('n', domain=_n_domain, typical=(10, 20)) + _p_param = _RealParameter('p', domain=_p_domain, typical=(0.25, 0.75)) + _x_param = _RealParameter('x', domain=_x_support, typical=(0, 10)) + + _parameterizations = [_Parameterization(_n_param, _p_param)] + _variable = _x_param + + def __init__(self, *, n, p, **kwargs): + super().__init__(n=n, p=p, **kwargs) + + def _pmf_formula(self, x, *, n, p, **kwargs): + return special._ufuncs._binom_pmf(x, n, p) + + def _logpmf_formula(self, x, *, n, p, **kwargs): + # This implementation is from the ``scipy.stats.binom`` and could be improved + # by using a more numerically sound implementation of the absolute value of + # the binomial coefficient. + combiln = ( + special.gammaln(n+1) - (special.gammaln(x+1) + special.gammaln(n-x+1)) + ) + return combiln + special.xlogy(x, p) + special.xlog1py(n-x, -p) + + def _cdf_formula(self, x, *, n, p, **kwargs): + return special._ufuncs._binom_cdf(x, n, p) + + def _ccdf_formula(self, x, *, n, p, **kwargs): + return special._ufuncs._binom_sf(x, n, p) + + def _icdf_formula(self, x, *, n, p, **kwargs): + return special._ufuncs._binom_ppf(x, n, p) + + def _iccdf_formula(self, x, *, n, p, **kwargs): + return special._ufuncs._binom_isf(x, n, p) + + def _mode_formula(self, *, n, p, **kwargs): + # https://en.wikipedia.org/wiki/Binomial_distribution#Mode + mode = np.floor((n+1)*p) + mode = np.where(p == 1, mode - 1, mode) + return mode[()] + + def _moment_raw_formula(self, order, *, n, p, **kwargs): + # https://en.wikipedia.org/wiki/Binomial_distribution#Higher_moments + if order == 1: + return n*p + if order == 2: + return n*p*(1 - p + n*p) + return None + _moment_raw_formula.orders = [1, 2] # type: ignore[attr-defined] + + def _moment_central_formula(self, order, *, n, p, **kwargs): + # https://en.wikipedia.org/wiki/Binomial_distribution#Higher_moments + if order == 1: + return np.zeros_like(n) + if order == 2: + return n*p*(1 - p) + if order == 3: + return n*p*(1 - p)*(1 - 2*p) + if order == 4: + return n*p*(1 - p)*(1 + (3*n - 6)*p*(1 - p)) + return None + _moment_central_formula.orders = [1, 2, 3, 4] # type: ignore[attr-defined] + + # Distribution classes need only define the summary and beginning of the extended # summary portion of the class documentation. All other documentation, including # examples, is generated automatically. diff --git a/scipy/stats/_probability_distribution.py b/scipy/stats/_probability_distribution.py index 9b092694240e..0e3c915f8cc9 100644 --- a/scipy/stats/_probability_distribution.py +++ b/scipy/stats/_probability_distribution.py @@ -30,8 +30,8 @@ def support(self): Notes ----- Suppose a continuous probability distribution has support ``(l, r)``. - The following table summarizes the value returned by methods - of ``ContinuousDistribution`` for arguments outside the support. + The following table summarizes the value returned by several + methods when the argument is outside the support. +----------------+---------------------+---------------------+ | Method | Value for ``x < l`` | Value for ``x > r`` | @@ -49,13 +49,16 @@ def support(self): | ``logccdf(x)`` | 0 | -inf | +----------------+---------------------+---------------------+ - For the ``cdf`` and related methods, the inequality need not be - strict; i.e. the tabulated value is returned when the method is - evaluated *at* the corresponding boundary. + For discrete distributions, the same table is applicable with + ``pmf`` and ``logpmf`` substituted for ``pdf`` and ``logpdf``. + + For the ``cdf`` and related methods of continuous distributions, the + inequality need not be strict; i.e. the tabulated value is returned + when the method is evaluated *at* the corresponding boundary. The following table summarizes the value returned by the inverse - methods of ``ContinuousDistribution`` for arguments at the boundaries - of the domain ``0`` to ``1``. + methods for arguments ``0`` and ``1``, whether the distribution + is continuous or discrete. +-------------+-----------+-----------+ | Method | ``x = 0`` | ``x = 1`` | @@ -65,7 +68,7 @@ def support(self): | ``icdf(x)`` | ``r`` | ``l`` | +-------------+-----------+-----------+ - For the inverse log-functions, the same values are returned for + For the inverse log-functions, the same values are returned for ``x = log(0)`` and ``x = log(1)``. All inverse functions return ``nan`` when evaluated at an argument outside the domain ``0`` to ``1``. @@ -173,7 +176,7 @@ def moment(self, order, kind, *, method): In terms of probability density function :math:`f(x)` and support :math:`\chi`, the "raw" moment (about the origin) of order :math:`n` of - a random variable :math:`X` is: + a continuous random variable :math:`X` is: .. math:: @@ -195,6 +198,9 @@ def moment(self, order, kind, *, method): \tilde{\mu}_n(X) = \frac{\mu_n(X)} {\sigma^n} + The definitions for discrete random variables are analogous, with + sums over the support replacing the integrals. + Parameters ---------- order : int @@ -217,7 +223,8 @@ def moment(self, order, kind, *, method): vice versa (see Notes) - ``'normalize'``: normalize a central moment to get a standardized or vice versa - - ``'quadrature'``: numerically integrate according to the definition + - ``'quadrature'``: numerically integrate (or, in the discrete case, sum) + according to the definition Not all `method` options are available for all orders, kinds, and distributions. If the selected `method` is not available, a @@ -365,15 +372,19 @@ def mean(self, *, method): @abstractmethod def median(self, *, method): - r"""Median (50th percentil) + r"""Median (50th percentile) If a continuous random variable :math:`X` has probability :math:`0.5` of taking on a value less than :math:`m`, then :math:`m` is the median. - That is, the median is the value :math:`m` for which: + + More generally, a median is a value :math:`m` for which: .. math:: - P(X ≤ m) = 0.5 = P(X ≥ m) + P(X ≤ m) ≤ 0.5 ≥ P(X ≥ m) + + For discrete random variables, the median may not be unique, in which + case the smallest value satisfying the definition is reported. Parameters ---------- @@ -428,8 +439,8 @@ def mode(self, *, method): Informally, the mode is a value that a random variable has the highest probability (density) of assuming. That is, the mode is the element of - the support :math:`\chi` that maximizes the probability density - function :math:`f(x)`: + the support :math:`\chi` that maximizes the probability density (or mass, + for discrete random variables) function :math:`f(x)`: .. math:: @@ -443,7 +454,7 @@ def mode(self, *, method): following options, listed in order of precedence. - ``'formula'``: use a formula for the median - - ``'optimization'``: numerically maximize the PDF + - ``'optimization'``: numerically maximize the PDF/PMF Not all `method` options are available for all distributions. If the selected `method` is not available, a ``NotImplementedError`` @@ -465,7 +476,7 @@ def mode(self, *, method): For some distributions #. the mode is not unique (e.g. the uniform distribution); - #. the PDF has one or more singularities, and it is debateable whether + #. the PDF has one or more singularities, and it is debatable whether a singularity is considered to be in the domain and called the mode (e.g. the gamma distribution with shape parameter less than 1); and/or #. the probability density function may have one or more local maxima @@ -737,9 +748,12 @@ def pdf(self, x, /, *, method): By definition of the support, the PDF evaluates to its minimum value of :math:`0` outside the support; i.e. for :math:`x < l` or :math:`x > r`. The maximum of the PDF may be less than or greater than - :math:`1`; since the valus is a probability *density*, only its integral + :math:`1`; since the value is a probability *density*, only its integral over the support must equal :math:`1`. + For discrete distributions, `pdf` returns ``inf`` at supported points + and ``0`` elsewhere. + References ---------- .. [1] Probability density function, *Wikipedia*, @@ -823,6 +837,9 @@ def logpdf(self, x, /, *, method): to work with the logarithms of probabilities and probability densities to avoid underflow. + For discrete distributions, `logpdf` returns ``inf`` at supported points and + ``-inf`` (``log(0)``) elsewhere. + References ---------- .. [1] Probability density function, *Wikipedia*, @@ -846,6 +863,156 @@ def logpdf(self, x, /, *, method): """ raise NotImplementedError() + def pmf(self, x, /, *, method=None): + r"""Probability mass function + + The probability mass function ("PMF"), denoted :math:`f(x)`, is the + probability that the random variable :math:`X` will assume the value :math:`x`. + + .. math:: + + f(x) = P(X = x) + + `pmf` accepts `x` for :math:`x`. + + Parameters + ---------- + x : array_like + The argument of the PMF. + method : {None, 'formula', 'logexp'} + The strategy used to evaluate the PMF. By default (``None``), the + infrastructure chooses between the following options, listed in + order of precedence. + + - ``'formula'``: use a formula for the PMF itself + - ``'logexp'``: evaluate the log-PMF and exponentiate + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The PMF evaluated at the argument `x`. + + See Also + -------- + cdf + logpmf + + Notes + ----- + Suppose a discrete probability distribution has support over the integers + :math:`{l, l+1, ..., r-1, r}`. + By definition of the support, the PMF evaluates to its minimum value + of :math:`0` for non-integral :math:`x` and for :math:`x` outside the support; + i.e. for :math:`x < l` or :math:`x > r`. + + For continuous distributions, `pmf` returns ``0`` at all real arguments. + + References + ---------- + .. [1] Probability mass function, *Wikipedia*, + https://en.wikipedia.org/wiki/Probability_mass_function + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> from scipy import stats + >>> X = stats.Binomial(n=10, p=0.5) + + Evaluate the PMF at the desired argument: + + >>> X.pmf(5) + np.float64(0.24609375) + + """ + raise NotImplementedError() + + def logpmf(self, x, /, *, method=None): + r"""Log of the probability mass function + + The probability mass function ("PMF"), denoted :math:`f(x)`, is the + probability that the random variable :math:`X` will assume the value :math:`x`. + + .. math:: + + f(x) = \frac{d}{dx} F(x) + + `logpmf` computes the logarithm of the probability mass function + ("log-PMF"), :math:`\log(f(x))`, but it may be numerically favorable + compared to the naive implementation (computing :math:`f(x)` and + taking the logarithm). + + `logpmf` accepts `x` for :math:`x`. + + Parameters + ---------- + x : array_like + The argument of the log-PMF. + method : {None, 'formula', 'logexp'} + The strategy used to evaluate the log-PMF. By default (``None``), the + infrastructure chooses between the following options, listed in order + of precedence. + + - ``'formula'``: use a formula for the log-PMF itself + - ``'logexp'``: evaluate the PMF and takes its logarithm + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The log-PMF evaluated at the argument `x`. + + See Also + -------- + pmf + logcdf + + Notes + ----- + Suppose a discrete probability distribution has support over the integers + :math:`{l, l+1, ..., r-1, r}`. + By definition of the support, the log-PMF evaluates to its minimum value + of :math:`-\infty` (i.e. :math:`\log(0)`) for non-integral :math:`x` and + for :math:`x` outside the support; i.e. for :math:`x < l` or :math:`x > r`. + + For distributions with infinite support, it is common for `pmf` to return + a value of ``0`` when the argument is theoretically within the support; + this can occur because the true value of the PMF is too small to be + represented by the chosen dtype. The log-PMF, however, will often be finite + (not ``-inf``) over a much larger domain. Consequently, it may be preferred + to work with the logarithms of probabilities and probability densities to + avoid underflow. + + References + ---------- + .. [1] Probability density function, *Wikipedia*, + https://en.wikipedia.org/wiki/Probability_density_function + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> import numpy as np + >>> from scipy import stats + >>> X = stats.Binomial(n=10, p=0.5) + + Evaluate the log-PMF at the desired argument: + + >>> X.logpmf(5) + np.float64(-1.4020427180880297) + >>> np.allclose(X.logpmf(5), np.log(X.pmf(5))) + True + + """ + raise NotImplementedError() + @abstractmethod def cdf(self, x, y, /, *, method): r"""Cumulative distribution function @@ -880,7 +1047,8 @@ def cdf(self, x, y, /, *, method): - ``'formula'``: use a formula for the CDF itself - ``'logexp'``: evaluate the log-CDF and exponentiate - ``'complement'``: evaluate the CCDF and take the complement - - ``'quadrature'``: numerically integrate the PDF + - ``'quadrature'``: numerically integrate the PDF (or, in the discrete + case, sum the PMF) In place of ``'complement'``, the two-argument form accepts: @@ -920,6 +1088,17 @@ def cdf(self, x, y, /, *, method): The CDF evaluates to its minimum value of :math:`0` for :math:`x ≤ l` and its maximum value of :math:`1` for :math:`x ≥ r`. + Suppose a discrete probability distribution has support :math:`[l, r]`. + The CDF :math:`F(x)` is related to the probability mass function + :math:`f(x)` by: + + .. math:: + + F(x) = \sum_{u=l}^{\lfloor x \rfloor} f(u) + + The CDF evaluates to its minimum value of :math:`0` for :math:`x < l` + and its maximum value of :math:`1` for :math:`x ≥ r`. + The CDF is also known simply as the "distribution function". References @@ -951,14 +1130,24 @@ def cdf(self, x, y, /, *, method): def icdf(self, p, /, *, method): r"""Inverse of the cumulative distribution function. - The inverse of the cumulative distribution function ("inverse CDF"), - denoted :math:`F^{-1}(p)`, is the argument :math:`x` for which the - cumulative distribution function :math:`F(x)` evaluates to :math:`p`. + For monotonic continuous distributions, the inverse of the cumulative + distribution function ("inverse CDF"), denoted :math:`F^{-1}(p)`, is the + argument :math:`x` for which the cumulative distribution function + :math:`F(x)` evaluates to :math:`p`. .. math:: F^{-1}(p) = x \quad \text{s.t.} \quad F(x) = p + When a strict "inverse" of the cumulative distribution function does not + exist (e.g. discrete random variables), the "inverse CDF" is defined by + convention as the smallest value within the support :math:`\chi` for which + :math:`F(x)` is at least :math:`p`. + + .. math:: + + F^{-1}(p) = \min_\chi \quad \text{s.t.} \quad F(x) ≥ p + `icdf` accepts `p` for :math:`p \in [0, 1]`. Parameters @@ -992,7 +1181,7 @@ def icdf(self, p, /, *, method): Notes ----- - Suppose a continuous probability distribution has support :math:`[l, r]`. The + Suppose a probability distribution has support :math:`[l, r]`. The inverse CDF returns its minimum value of :math:`l` at :math:`p = 0` and its maximum value of :math:`r` at :math:`p = 1`. Because the CDF has range :math:`[0, 1]`, the inverse CDF is only defined on the @@ -1063,7 +1252,8 @@ def ccdf(self, x, y, /, *, method): - ``'formula'``: use a formula for the CCDF itself - ``'logexp'``: evaluate the log-CCDF and exponentiate - ``'complement'``: evaluate the CDF and take the complement - - ``'quadrature'``: numerically integrate the PDF + - ``'quadrature'``: numerically integrate the PDF (or, in the discrete + case, sum the PMF) The two-argument form chooses between: @@ -1103,6 +1293,17 @@ def ccdf(self, x, y, /, *, method): The CCDF returns its minimum value of :math:`0` for :math:`x ≥ r` and its maximum value of :math:`1` for :math:`x ≤ l`. + Suppose a discrete probability distribution has support :math:`[l, r]`. + The CCDF :math:`G(x)` is related to the probability mass function + :math:`f(x)` by: + + .. math:: + + G(x) = \sum_{u=\lfloor x + 1 \rfloor}^{r} f(u) + + The CCDF evaluates to its minimum value of :math:`0` for :math:`x ≥ r` + and its maximum value of :math:`1` for :math:`x < l`. + The CCDF is also known as the "survival function". References @@ -1146,6 +1347,15 @@ def iccdf(self, p, /, *, method): G^{-1}(p) = x \quad \text{s.t.} \quad G(x) = p + When a strict "inverse" of the complementary cumulative distribution function + does not exist (e.g. discrete random variables), the "inverse CCDF" is defined + by convention as the smallest value within the support :math:`\chi` for which + :math:`G(x)` is no greater than :math:`p`. + + .. math:: + + G^{-1}(p) = \min_\chi \quad \text{s.t.} \quad G(x) ≤ p + `iccdf` accepts `p` for :math:`p \in [0, 1]`. Parameters @@ -1174,7 +1384,7 @@ def iccdf(self, p, /, *, method): Notes ----- - Suppose a continuous probability distribution has support :math:`[l, r]`. The + Suppose a probability distribution has support :math:`[l, r]`. The inverse CCDF returns its minimum value of :math:`l` at :math:`p = 1` and its maximum value of :math:`r` at :math:`p = 0`. Because the CCDF has range :math:`[0, 1]`, the inverse CCDF is only defined on the @@ -1249,7 +1459,8 @@ def logcdf(self, x, y, /, *, method): - ``'logexp'``: evaluate the CDF and take the logarithm - ``'complement'``: evaluate the log-CCDF and take the logarithmic complement (see Notes) - - ``'quadrature'``: numerically log-integrate the log-PDF + - ``'quadrature'``: numerically log-integrate the log-PDF (or, in the + discrete case, log-sum the log-PMF) In place of ``'complement'``, the two-argument form accepts: @@ -1275,7 +1486,8 @@ def logcdf(self, x, y, /, *, method): Suppose a continuous probability distribution has support :math:`[l, r]`. The log-CDF evaluates to its minimum value of :math:`\log(0) = -\infty` for :math:`x ≤ l` and its maximum value of :math:`\log(1) = 0` for - :math:`x ≥ r`. + :math:`x ≥ r`. An analogous statement can be made for discrete distributions, + but the inequality governing the minimum value is strict. For distributions with infinite support, it is common for `cdf` to return a value of ``0`` when the argument @@ -1368,7 +1580,7 @@ def ilogcdf(self, logp, /, *, method): Notes ----- - Suppose a continuous probability distribution has support :math:`[l, r]`. + Suppose a probability distribution has support :math:`[l, r]`. The inverse log-CDF returns its minimum value of :math:`l` at :math:`\log(p) = \log(0) = -\infty` and its maximum value of :math:`r` at :math:`\log(p) = \log(1) = 0`. Because the log-CDF has range @@ -1418,7 +1630,7 @@ def logccdf(self, x, y, /, *, method): G(x) = 1 - F(x) = P(X > x) - A two-argument variant of this function is: + A two-argument variant of this function is: .. math:: @@ -1444,7 +1656,8 @@ def logccdf(self, x, y, /, *, method): - ``'logexp'``: evaluate the CCDF and take the logarithm - ``'complement'``: evaluate the log-CDF and take the logarithmic complement (see Notes) - - ``'quadrature'``: numerically log-integrate the log-PDF + - ``'quadrature'``: numerically log-integrate the log-PDF (or, in the + discrete case, log-sum the log-PMF) The two-argument form chooses between: @@ -1471,7 +1684,8 @@ def logccdf(self, x, y, /, *, method): Suppose a continuous probability distribution has support :math:`[l, r]`. The log-CCDF returns its minimum value of :math:`\log(0)=-\infty` for :math:`x ≥ r` and its maximum value of :math:`\log(1) = 0` for - :math:`x ≤ l`. + :math:`x ≤ l`. An analogous statement can be made for discrete distributions, + but the inequality governing the maximum value is strict. For distributions with infinite support, it is common for `ccdf` to return a value of ``0`` when the argument @@ -1554,7 +1768,7 @@ def ilogccdf(self, logp, /, *, method): Notes ----- - Suppose a continuous probability distribution has support :math:`[l, r]`. The + Suppose a probability distribution has support :math:`[l, r]`. The inverse log-CCDF returns its minimum value of :math:`l` at :math:`\log(p) = \log(1) = 0` and its maximum value of :math:`r` at :math:`\log(p) = \log(0) = -\infty`. Because the log-CCDF has range @@ -1567,7 +1781,7 @@ def ilogccdf(self, logp, /, *, method): however, the *logarithm* of this resulting probability may be represented in floating point arithmetic, in which case this function may be used to find the argument of the CCDF for which the *logarithm* - of the resulting probability is `y = \log(p)`. + of the resulting probability is :math:`y = \log(p)`. The "logarithmic complement" of a number :math:`z` is mathematically equivalent to :math:`\log(1-\exp(z))`, but it is computed to avoid loss @@ -1601,15 +1815,18 @@ def logentropy(self, *, method): r"""Logarithm of the differential entropy In terms of probability density function :math:`f(x)` and support - :math:`\chi`, the differential entropy (or simply "entropy") of a random - variable :math:`X` is: + :math:`\chi`, the differential entropy (or simply "entropy") of a + continuous random variable :math:`X` is: .. math:: h(X) = - \int_{\chi} f(x) \log f(x) dx + The definition for a discrete random variable is analogous, with the PMF + replacing the PDF and a sum over the support replacing the integral. + `logentropy` computes the logarithm of the differential entropy - ("log-entropy"), :math:`log(h(X))`, but it may be numerically favorable + ("log-entropy"), :math:`\log(h(X))`, but it may be numerically favorable compared to the naive implementation (computing :math:`h(X)` then taking the logarithm). @@ -1622,8 +1839,8 @@ def logentropy(self, *, method): - ``'formula'``: use a formula for the log-entropy itself - ``'logexp'``: evaluate the entropy and take the logarithm - - ``'quadrature'``: numerically log-integrate the logarithm of the - entropy integrand + - ``'quadrature'``: numerically log-integrate (or, in the discrete + case, log-sum) the logarithm of the entropy integrand (summand) Not all `method` options are available for all distributions. If the selected `method` is not available, a ``NotImplementedError`` @@ -1641,9 +1858,9 @@ def logentropy(self, *, method): Notes ----- - If the entropy of a distribution is negative, then the log-entropy - is complex with imaginary part :math:`\pi`. For - consistency, the result of this function always has complex dtype, + The differential entropy of a continuous distribution can be negative. + In this case, the log-entropy is complex with imaginary part :math:`\pi`. + For consistency, the result of this function always has complex dtype, regardless of the value of the imaginary part. References @@ -1675,7 +1892,7 @@ def logentropy(self, *, method): """ raise NotImplementedError() - + @abstractmethod def entropy(self, *, method): r"""Differential entropy @@ -1688,6 +1905,9 @@ def entropy(self, *, method): h(X) = - \int_{\chi} f(x) \log f(x) dx + The definition for a discrete random variable is analogous, with the + PMF replacing the PDF and a sum over the support replacing the integral. + Parameters ---------- method : {None, 'formula', 'logexp', 'quadrature'} @@ -1697,7 +1917,9 @@ def entropy(self, *, method): - ``'formula'``: use a formula for the entropy itself - ``'logexp'``: evaluate the log-entropy and exponentiate - - ``'quadrature'``: use numerical integration + - ``'quadrature'``: numerically integrate (or, in the discrete + case, sum) the entropy integrand (summand) + Not all `method` options are available for all distributions. If the selected `method` is not available, a ``NotImplementedError`` diff --git a/scipy/stats/_qmc.py b/scipy/stats/_qmc.py index 69767a086840..32f06ad17114 100644 --- a/scipy/stats/_qmc.py +++ b/scipy/stats/_qmc.py @@ -1863,7 +1863,7 @@ def _random( # verify n is 2**n if not (n & (n - 1) == 0): warnings.warn("The balance properties of Sobol' points require" - " n to be a power of 2.", stacklevel=2) + " n to be a power of 2.", stacklevel=3) if n == 1: sample = self._first_point diff --git a/scipy/stats/_quantile.py b/scipy/stats/_quantile.py index 4d2a8778b82c..3f92632f0a48 100644 --- a/scipy/stats/_quantile.py +++ b/scipy/stats/_quantile.py @@ -1,9 +1,9 @@ import numpy as np from scipy.special import betainc -from scipy._lib._array_api import xp_ravel, array_namespace, xp_promote +from scipy._lib._array_api import (xp_ravel, array_namespace, xp_promote, + _length_nonmasked) import scipy._lib.array_api_extra as xpx from scipy.stats._axis_nan_policy import _broadcast_arrays, _contains_nan -from scipy.stats._stats_py import _length_nonmasked def _quantile_iv(x, p, method, axis, nan_policy, keepdims): @@ -200,8 +200,9 @@ def quantile(x, p, *, method='linear', axis=0, nan_policy='propagate', keepdims= Note that indices ``j`` and ``j + 1`` are clipped to the range ``0`` to ``n - 1`` when the results of the formula would be outside the allowed - range of non-negative indices. The ``-1`` in the formulas for ``j`` and - ``g`` accounts for Python's 0-based indexing. + range of non-negative indices. When ``j`` is clipped to zero, ``g`` is + set to zero as well. The ``-1`` in the formulas for ``j`` and ``g`` + accounts for Python's 0-based indexing. The table above includes only the estimators from [1]_ that are continuous functions of probability `p` (estimators 4-9). SciPy also provides the @@ -308,6 +309,8 @@ def _quantile_hf(y, p, n, method, xp): if method in {'inverted_cdf', 'averaged_inverted_cdf', 'closest_observation'}: g = xp.asarray(g) g = xpx.at(g, jg < 0).set(0) + + g[j < 0] = 0 j = xp.clip(j, 0., n - 1) jp1 = xp.clip(j + 1, 0., n - 1) diff --git a/scipy/stats/_stats_mstats_common.py b/scipy/stats/_stats_mstats_common.py index 9a621016e157..3a0ec400d36c 100644 --- a/scipy/stats/_stats_mstats_common.py +++ b/scipy/stats/_stats_mstats_common.py @@ -21,7 +21,8 @@ def _n_samples_optional_x(kwargs): @_axis_nan_policy_factory(TheilslopesResult, default_axis=None, n_outputs=4, n_samples=_n_samples_optional_x, - result_to_tuple=tuple, paired=True, too_small=1) + result_to_tuple=lambda x, _: tuple(x), paired=True, + too_small=1) def theilslopes(y, x=None, alpha=0.95, method='separate'): r""" Computes the Theil-Sen estimator for a set of points (x, y). @@ -204,7 +205,8 @@ def _find_repeats(arr): @_axis_nan_policy_factory(SiegelslopesResult, default_axis=None, n_outputs=2, n_samples=_n_samples_optional_x, - result_to_tuple=tuple, paired=True, too_small=1) + result_to_tuple=lambda x, _: tuple(x), paired=True, + too_small=1) def siegelslopes(y, x=None, method="hierarchical"): r""" Computes the Siegel estimator for a set of points (x, y). diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 2f7e8e1af065..2b2afe56dd24 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -26,7 +26,6 @@ York. 2000. """ -import functools import math import operator import warnings @@ -43,7 +42,7 @@ from scipy._lib._util import (check_random_state, _get_nan, _rename_parameter, _contains_nan, normalize_axis_index, np_vecdot, AxisError) -from scipy._lib.deprecation import _deprecate_positional_args, _deprecated +from scipy._lib.deprecation import _deprecate_positional_args import scipy.special as special # Import unused here but needs to stay until end of deprecation periode @@ -75,19 +74,23 @@ array_namespace, is_lazy_array, is_numpy, - is_marray, + is_cupy, xp_size, xp_vector_norm, xp_promote, xp_capabilities, xp_ravel, + _length_nonmasked, + _share_masks, + xp_swapaxes, + xp_default_dtype, ) import scipy._lib.array_api_extra as xpx # Functions/classes in other files should be added in `__init__.py`, not here -__all__ = ['find_repeats', 'gmean', 'hmean', 'pmean', 'mode', 'tmean', 'tvar', +__all__ = ['gmean', 'hmean', 'pmean', 'mode', 'tmean', 'tvar', 'tmin', 'tmax', 'tstd', 'tsem', 'moment', 'skew', 'kurtosis', 'describe', 'skewtest', 'kurtosistest', 'normaltest', 'jarque_bera', @@ -157,7 +160,7 @@ def _pack_CorrelationResult(statistic, pvalue, correlation): return res -def _unpack_CorrelationResult(res): +def _unpack_CorrelationResult(res, _): return res.statistic, res.pvalue, res.correlation @@ -165,7 +168,7 @@ def _unpack_CorrelationResult(res): @xp_capabilities() @_axis_nan_policy_factory( lambda x: x, n_samples=1, n_outputs=1, too_small=0, paired=True, - result_to_tuple=lambda x: (x,), kwd_samples=['weights']) + result_to_tuple=lambda x, _: (x,), kwd_samples=['weights']) def gmean(a, axis=0, dtype=None, weights=None): r"""Compute the weighted geometric mean along the specified axis. @@ -246,10 +249,10 @@ def gmean(a, axis=0, dtype=None, weights=None): return xp.exp(_xp_mean(log_a, axis=axis, weights=weights)) -@xp_capabilities() +@xp_capabilities(jax_jit=False, allow_dask_compute=1) @_axis_nan_policy_factory( lambda x: x, n_samples=1, n_outputs=1, too_small=0, paired=True, - result_to_tuple=lambda x: (x,), kwd_samples=['weights']) + result_to_tuple=lambda x, _: (x,), kwd_samples=['weights']) def hmean(a, axis=0, dtype=None, *, weights=None): r"""Calculate the weighted harmonic mean along the specified axis. @@ -347,10 +350,10 @@ def hmean(a, axis=0, dtype=None, *, weights=None): return 1.0 / _xp_mean(1.0 / a, axis=axis, weights=weights) -@xp_capabilities() +@xp_capabilities(jax_jit=False, allow_dask_compute=1) @_axis_nan_policy_factory( lambda x: x, n_samples=1, n_outputs=1, too_small=0, paired=True, - result_to_tuple=lambda x: (x,), kwd_samples=['weights']) + result_to_tuple=lambda x, _: (x,), kwd_samples=['weights']) def pmean(a, p, *, axis=0, dtype=None, weights=None): r"""Calculate the weighted power mean along the specified axis. @@ -631,7 +634,7 @@ def _put_val_to_limits(a, limits, inclusive, val=np.nan, xp=None): @xp_capabilities() @_axis_nan_policy_factory( lambda x: x, n_outputs=1, default_axis=None, - result_to_tuple=lambda x: (x,) + result_to_tuple=lambda x, _: (x,) ) def tmean(a, limits=None, inclusive=(True, True), axis=None): """Compute the trimmed mean. @@ -686,7 +689,7 @@ def tmean(a, limits=None, inclusive=(True, True), axis=None): @xp_capabilities() @_axis_nan_policy_factory( - lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,) ) def tvar(a, limits=None, inclusive=(True, True), axis=0, ddof=1): """Compute the trimmed variance. @@ -746,7 +749,7 @@ def tvar(a, limits=None, inclusive=(True, True), axis=0, ddof=1): @xp_capabilities() @_axis_nan_policy_factory( - lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,) ) def tmin(a, lowerlimit=None, axis=0, inclusive=True, nan_policy='propagate'): """Compute the trimmed minimum. @@ -810,7 +813,7 @@ def tmin(a, lowerlimit=None, axis=0, inclusive=True, nan_policy='propagate'): @xp_capabilities() @_axis_nan_policy_factory( - lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,) ) def tmax(a, upperlimit=None, axis=0, inclusive=True, nan_policy='propagate'): """Compute the trimmed maximum. @@ -867,13 +870,13 @@ def tmax(a, upperlimit=None, axis=0, inclusive=True, nan_policy='propagate'): # Possible loss of precision for int types res = xp_promote(res, force_floating=True, xp=xp) res = xp.where(invalid, xp.nan, res) - + return res[()] if res.ndim == 0 else res @xp_capabilities() @_axis_nan_policy_factory( - lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,) ) def tstd(a, limits=None, inclusive=(True, True), axis=0, ddof=1): """Compute the trimmed sample standard deviation. @@ -926,7 +929,7 @@ def tstd(a, limits=None, inclusive=(True, True), axis=0, ddof=1): @xp_capabilities() @_axis_nan_policy_factory( - lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,) ) def tsem(a, limits=None, inclusive=(True, True), axis=0, ddof=1): """Compute the trimmed standard error of the mean. @@ -1016,7 +1019,6 @@ def _moment_tuple(x, n_out): return tuple(x) if n_out > 1 else (x,) -@xp_capabilities() # `moment` fits into the `_axis_nan_policy` pattern, but it is a bit unusual # because the number of outputs is variable. Specifically, # `result_to_tuple=lambda x: (x,)` may be surprising for a function that @@ -1040,6 +1042,7 @@ def _moment_tuple(x, n_out): # empty, there is no distinction between the `moment` function being called # with parameter `order=1` and `order=[1]`; the latter *should* produce # the same as the former but with a singleton zeroth dimension. +@xp_capabilities(jax_jit=False, allow_dask_compute=True) @_rename_parameter('moment', 'order') @_axis_nan_policy_factory( # noqa: E302 _moment_result_object, n_samples=1, result_to_tuple=_moment_tuple, @@ -1241,28 +1244,9 @@ def _var(x, axis=0, ddof=0, mean=None, xp=None): return var -def _length_nonmasked(x, axis, keepdims=False, xp=None): - xp = array_namespace(x) if xp is None else xp - if is_marray(xp): - if np.iterable(axis): - message = '`axis` must be an integer or None for use with `MArray`.' - raise NotImplementedError(message) - return xp.astype(xp.count(x, axis=axis, keepdims=keepdims), x.dtype) - return (xp_size(x) if axis is None else - # compact way to deal with axis tuples or ints - int(np.prod(np.asarray(x.shape)[np.asarray(axis)]))) - - -def _share_masks(*args, xp): - if is_marray(xp): - mask = functools.reduce(operator.or_, (arg.mask for arg in args)) - args = [xp.asarray(arg.data, mask=mask) for arg in args] - return args[0] if len(args) == 1 else args - - -@xp_capabilities() +@xp_capabilities(jax_jit=False, allow_dask_compute=2) @_axis_nan_policy_factory( - lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1 + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1 ) # nan_policy handled by `_axis_nan_policy`, but needs to be left # in signature to preserve use as a positional argument @@ -1361,9 +1345,9 @@ def skew(a, axis=0, bias=True, nan_policy='propagate'): return vals[()] if vals.ndim == 0 else vals -@xp_capabilities() +@xp_capabilities(jax_jit=False, allow_dask_compute=2) @_axis_nan_policy_factory( - lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1 + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1 ) # nan_policy handled by `_axis_nan_policy`, but needs to be left # in signature to preserve use as a positional argument @@ -1475,7 +1459,7 @@ def kurtosis(a, axis=0, fisher=True, bias=True, nan_policy='propagate'): 'kurtosis')) -@xp_capabilities() +@xp_capabilities(jax_jit=False, allow_dask_compute=True) def describe(a, axis=0, ddof=1, bias=True, nan_policy='propagate'): """Compute several descriptive statistics of the passed array. @@ -1599,7 +1583,7 @@ def _get_pvalue(statistic, distribution, alternative, symmetric=True, xp=None): SkewtestResult = namedtuple('SkewtestResult', ('statistic', 'pvalue')) -@xp_capabilities() +@xp_capabilities(jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(SkewtestResult, n_samples=1, too_small=7) # nan_policy handled by `_axis_nan_policy`, but needs to be left # in signature to preserve use as a positional argument @@ -1709,7 +1693,7 @@ def skewtest(a, axis=0, nan_policy='propagate', alternative='two-sided'): KurtosistestResult = namedtuple('KurtosistestResult', ('statistic', 'pvalue')) -@xp_capabilities() +@xp_capabilities(jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(KurtosistestResult, n_samples=1, too_small=4) def kurtosistest(a, axis=0, nan_policy='propagate', alternative='two-sided'): r"""Test whether a dataset has normal kurtosis. @@ -1823,7 +1807,7 @@ def kurtosistest(a, axis=0, nan_policy='propagate', alternative='two-sided'): NormaltestResult = namedtuple('NormaltestResult', ('statistic', 'pvalue')) -@xp_capabilities() +@xp_capabilities(jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(NormaltestResult, n_samples=1, too_small=7) def normaltest(a, axis=0, nan_policy='propagate'): r"""Test whether a sample differs from a normal distribution. @@ -1901,7 +1885,7 @@ def normaltest(a, axis=0, nan_policy='propagate'): return NormaltestResult(statistic, pvalue) -@xp_capabilities() +@xp_capabilities(jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(SignificanceResult, default_axis=None) def jarque_bera(x, *, axis=None): r"""Perform the Jarque-Bera goodness of fit test on sample data. @@ -2597,9 +2581,9 @@ def obrientransform(*samples): return np.array(arrays) -@xp_capabilities() +@xp_capabilities(jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory( - lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1, too_small=1 + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1, too_small=1 ) def sem(a, axis=0, ddof=1, nan_policy='propagate'): """Compute standard error of the mean. @@ -3067,7 +3051,7 @@ def gstd(a, axis=0, ddof=1, *, keepdims=False, nan_policy='propagate'): @_axis_nan_policy_factory( - lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1, + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1, default_axis=None, override={'nan_propagation': False} ) def iqr(x, axis=None, rng=(25, 75), scale=1.0, nan_policy='propagate', @@ -4065,7 +4049,7 @@ class AlexanderGovernResult: @_axis_nan_policy_factory( AlexanderGovernResult, n_samples=None, - result_to_tuple=lambda x: (x.statistic, x.pvalue), + result_to_tuple=lambda x, _: (x.statistic, x.pvalue), too_small=1 ) def alexandergovern(*samples, nan_policy='propagate', axis=0): @@ -4236,33 +4220,35 @@ def _pearsonr_fisher_ci(r, n, confidence_level, alternative): """ xp = array_namespace(r) - with np.errstate(divide='ignore'): - zr = xp.atanh(r) - ones = xp.ones_like(r) n = xp.asarray(n, dtype=r.dtype) confidence_level = xp.asarray(confidence_level, dtype=r.dtype) - if n > 3: + + with np.errstate(divide='ignore', invalid='ignore'): + zr = xp.atanh(r) se = xp.sqrt(1 / (n - 3)) - if alternative == "two-sided": - h = special.ndtri(0.5 + confidence_level/2) - zlo = zr - h*se - zhi = zr + h*se - rlo = xp.tanh(zlo) - rhi = xp.tanh(zhi) - elif alternative == "less": - h = special.ndtri(confidence_level) - zhi = zr + h*se - rhi = xp.tanh(zhi) - rlo = -ones - else: - # alternative == "greater": - h = special.ndtri(confidence_level) - zlo = zr - h*se - rlo = xp.tanh(zlo) - rhi = ones + + if alternative == "two-sided": + h = special.ndtri(0.5 + confidence_level/2) + zlo = zr - h*se + zhi = zr + h*se + rlo = xp.tanh(zlo) + rhi = xp.tanh(zhi) + elif alternative == "less": + h = special.ndtri(confidence_level) + zhi = zr + h*se + rhi = xp.tanh(zhi) + rlo = -ones else: - rlo, rhi = -ones, ones + # alternative == "greater": + h = special.ndtri(confidence_level) + zlo = zr - h*se + rlo = xp.tanh(zlo) + rhi = ones + + mask = (n <= 3) + rlo = xpx.at(rlo)[mask].set(-1) + rhi = xpx.at(rhi)[mask].set(1) rlo = rlo[()] if rlo.ndim == 0 else rlo rhi = rhi[()] if rhi.ndim == 0 else rhi @@ -4384,7 +4370,9 @@ def confidence_interval(self, confidence_level=0.95, method=None): return ci -@xp_capabilities(cpu_only=True, exceptions=['cupy']) +@xp_capabilities(skip_backends = [('dask.array', 'data-apis/array-api-extra#196')], + cpu_only=True, exceptions=['cupy'], + jax_jit=False, allow_dask_compute=True) def pearsonr(x, y, *, alternative='two-sided', method=None, axis=0): r""" Pearson correlation coefficient and p-value for testing non-correlation. @@ -4677,13 +4665,15 @@ def pearsonr(x, y, *, alternative='two-sided', method=None, axis=0): message = '`x` and `y` must be broadcastable.' raise ValueError(message) from e - n = x.shape[axis] - if n != y.shape[axis]: + if x.shape[axis] != y.shape[axis]: raise ValueError('`x` and `y` must have the same length along `axis`.') - if n < 2: + if x.shape[axis] < 2: raise ValueError('`x` and `y` must have length at least 2.') + x, y = _share_masks(x, y, xp=xp) + n = xp.asarray(_length_nonmasked(x, axis=axis), dtype=x.dtype) + x = xp.moveaxis(x, axis, -1) y = xp.moveaxis(y, axis, -1) axis = -1 @@ -4776,18 +4766,16 @@ def statistic(x, y, axis): r = xp.clip(r, -1., 1.) r = xpx.at(r, const_xy).set(xp.nan) - # Make sure we return exact 1.0 or -1.0 values for n == 2 case as promised - # in the docs. - if n == 2: - r = xp.round(r) - one = xp.asarray(1, dtype=dtype) - pvalue = xp.where(xp.asarray(xp.isnan(r)), xp.nan*one, one) - else: - # As explained in the docstring, the distribution of `r` under the null - # hypothesis is the beta distribution on (-1, 1) with a = b = n/2 - 1. - ab = xp.asarray(n/2 - 1) - dist = _SimpleBeta(ab, ab, loc=-1, scale=2) - pvalue = _get_pvalue(r, dist, alternative, xp=xp) + # As explained in the docstring, the distribution of `r` under the null + # hypothesis is the beta distribution on (-1, 1) with a = b = n/2 - 1. + ab = xp.asarray(n/2 - 1) + dist = _SimpleBeta(ab, ab, loc=-1, scale=2) + pvalue = _get_pvalue(r, dist, alternative, xp=xp) + + mask = (n == 2) # return exactly 1.0 or -1.0 values for n == 2 case as promised + def special_case(r): return xp.where(xp.isnan(r), xp.nan, xp.ones_like(r)) + r = xpx.apply_where(mask, (r,), xp.round, fill_value=r) + pvalue = xpx.apply_where(mask, (r,), special_case, fill_value=pvalue) r = r[()] if r.ndim == 0 else r pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue @@ -5617,8 +5605,8 @@ def kendalltau(x, y, *, nan_policy='propagate', tau_c = 2 (P - Q) / (n**2 * (m - 1) / m) where P is the number of concordant pairs, Q the number of discordant - pairs, T the number of ties only in `x`, and U the number of ties only in - `y`. If a tie occurs for the same pair in both `x` and `y`, it is not + pairs, T the number of tied pairs only in `x`, and U the number of tied pairs only + in `y`. If a tie occurs for the same pair in both `x` and `y`, it is not added to either T or U. n is the total number of samples, and m is the number of unique values in either `x` or `y`, whichever is smaller. @@ -6023,12 +6011,13 @@ def pack_TtestResult(statistic, pvalue, df, alternative, standard_error, standard_error=standard_error, estimate=estimate) -def unpack_TtestResult(res): +def unpack_TtestResult(res, _): return (res.statistic, res.pvalue, res.df, res._alternative, res._standard_error, res._estimate) -@xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"]) +@xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"], + jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(pack_TtestResult, default_axis=0, n_samples=2, result_to_tuple=unpack_TtestResult, n_outputs=6) # nan_policy handled by `_axis_nan_policy`, but needs to be left @@ -6762,8 +6751,7 @@ def ttest_ind(a, b, *, axis=0, equal_var=True, nan_policy='propagate', raise NotImplementedError(message) result_shape = _broadcast_array_shapes_remove_axis((a, b), axis=axis) - NaN = xp.full(result_shape, _get_nan(a, b, xp=xp)) - NaN = NaN[()] if NaN.ndim == 0 else NaN + NaN = _get_nan(a, b, shape=result_shape, xp=xp) if xp_size(a) == 0 or xp_size(b) == 0: return TtestResult(NaN, NaN, df=NaN, alternative=NaN, standard_error=NaN, estimate=NaN) @@ -7052,6 +7040,8 @@ def _get_len(a, axis, msg): return n +@xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"], + jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(pack_TtestResult, default_axis=0, n_samples=2, result_to_tuple=unpack_TtestResult, n_outputs=6, paired=True) @@ -7177,7 +7167,7 @@ def _pd_nsamples(kwargs): return 2 if kwargs.get('f_exp', None) is not None else 1 -@xp_capabilities() +@xp_capabilities(jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(Power_divergenceResult, paired=True, n_samples=_pd_nsamples, too_small=-1) def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): @@ -7418,7 +7408,7 @@ def _power_divergence(f_obs, f_exp, ddof, axis, lambda_, sum_check=True): -@xp_capabilities() +@xp_capabilities(jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(Power_divergenceResult, paired=True, n_samples=_pd_nsamples, too_small=-1) def chisquare(f_obs, f_exp=None, ddof=0, axis=0, *, sum_check=True): @@ -7627,7 +7617,7 @@ def _tuple_to_KstestResult(statistic, pvalue, statistic_sign=statistic_sign) -def _KstestResult_to_tuple(res): +def _KstestResult_to_tuple(res, _): return *res, res.statistic_location, res.statistic_sign @@ -8907,7 +8897,8 @@ def brunnermunzel(x, y, alternative="two-sided", distribution="t", @xp_capabilities(cpu_only=True, exceptions=['cupy', 'jax.numpy'], - reason=('Delegation for `special.stdtr` only implemented for CuPy and JAX.')) + reason='Delegation for `special.stdtr` only implemented for CuPy and JAX.', + jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(SignificanceResult, kwd_samples=['weights'], paired=True) def combine_pvalues(pvalues, method='fisher', weights=None, *, axis=0): """ @@ -10067,51 +10058,6 @@ def _validate_distribution(values, weights): # SUPPORT FUNCTIONS # ##################################### -RepeatedResults = namedtuple('RepeatedResults', ('values', 'counts')) - - -@_deprecated("`scipy.stats.find_repeats` is deprecated as of SciPy 1.15.0 " - "and will be removed in SciPy 1.17.0. Please use " - "`numpy.unique`/`numpy.unique_counts` instead.") -def find_repeats(arr): - """Find repeats and repeat counts. - - .. deprecated:: 1.15.0 - - This function is deprecated as of SciPy 1.15.0 and will be removed - in SciPy 1.17.0. Please use `numpy.unique` / `numpy.unique_counts` instead. - - Parameters - ---------- - arr : array_like - Input array. This is cast to float64. - - Returns - ------- - values : ndarray - The unique values from the (flattened) input that are repeated. - - counts : ndarray - Number of times the corresponding 'value' is repeated. - - Notes - ----- - In numpy >= 1.9 `numpy.unique` provides similar functionality. The main - difference is that `find_repeats` only returns repeated values. - - Examples - -------- - >>> from scipy import stats - >>> stats.find_repeats([2, 1, 2, 3, 2, 2, 5]) - RepeatedResults(values=array([2.]), counts=array([4])) - - >>> stats.find_repeats([[10, 20, 1, 2], [5, 5, 4, 4]]) - RepeatedResults(values=array([4., 5.]), counts=array([2, 2])) - - """ - # Note: always copies. - return RepeatedResults(*_find_repeats(np.array(arr, dtype=np.float64))) - def _sum_of_squares(a, axis=0): """Square each element of the input array, and return the sum(s) of that. @@ -10168,6 +10114,10 @@ def _square_of_sums(a, axis=0): return float(s) * s +@xp_capabilities(skip_backends=[("torch", "no `repeat`"), + ("cupy", "`repeat` can't handle array second arg"), + ("dask.array", "no `take_along_axis`")], + jax_jit=False, allow_dask_compute=True) def rankdata(a, method='average', *, axis=None, nan_policy='propagate'): """Assign ranks to data, dealing with ties appropriately. @@ -10259,61 +10209,71 @@ def rankdata(a, method='average', *, axis=None, nan_policy='propagate'): if method not in methods: raise ValueError(f'unknown method "{method}"') - x = np.asarray(a) + xp = array_namespace(a) + x = xp.asarray(a) if axis is None: - x = x.ravel() + x = xp_ravel(x) axis = -1 - if x.size == 0: - dtype = float if method == 'average' else np.dtype("long") - return np.empty(x.shape, dtype=dtype) + if xp_size(x) == 0: + dtype = xp.asarray(1.).dtype if method == 'average' else xp.asarray(1).dtype + return xp.empty(x.shape, dtype=dtype) contains_nan = _contains_nan(x, nan_policy) - x = np.swapaxes(x, axis, -1) - ranks = _rankdata(x, method) + x = xp_swapaxes(x, axis, -1, xp=xp) + ranks = _rankdata(x, method, xp=xp) if contains_nan: - i_nan = (np.isnan(x) if nan_policy == 'omit' - else np.isnan(x).any(axis=-1)) - ranks = ranks.astype(float, copy=False) - ranks[i_nan] = np.nan + default_float = xp_default_dtype(xp) + i_nan = (xp.isnan(x) if nan_policy == 'omit' + else xp.any(xp.isnan(x), axis=-1)) + ranks = xp.asarray(ranks, dtype=default_float) # copy=False when implemented + ranks[i_nan] = xp.nan - ranks = np.swapaxes(ranks, axis, -1) + ranks = xp_swapaxes(ranks, axis, -1, xp=xp) return ranks -def _order_ranks(ranks, j): +def _order_ranks(ranks, j, *, xp): # Reorder ascending order `ranks` according to `j` - ordered_ranks = np.empty(j.shape, dtype=ranks.dtype) - np.put_along_axis(ordered_ranks, j, ranks, axis=-1) + xp = array_namespace(ranks) if xp is None else xp + if is_numpy(xp) or is_cupy(xp): + ordered_ranks = xp.empty(j.shape, dtype=ranks.dtype) + xp.put_along_axis(ordered_ranks, j, ranks, axis=-1) + else: + # `put_along_axis` not in array API (data-apis/array-api#177) + # so argsort the argsort and take_along_axis... + j_inv = xp.argsort(j, axis=-1) + ordered_ranks = xp.take_along_axis(ranks, j_inv, axis=-1) return ordered_ranks -def _rankdata(x, method, return_ties=False): +def _rankdata(x, method, return_ties=False, xp=None): # Rank data `x` by desired `method`; `return_ties` if desired + xp = array_namespace(x) if xp is None else xp shape = x.shape + dtype = xp.asarray(1.).dtype if method == 'average' else xp.asarray(1).dtype # Get sort order - kind = 'mergesort' if method == 'ordinal' else 'quicksort' - j = np.argsort(x, axis=-1, kind=kind) - ordinal_ranks = np.broadcast_to(np.arange(1, shape[-1]+1, dtype=int), shape) + j = xp.argsort(x, axis=-1) + ordinal_ranks = xp.broadcast_to(xp.arange(1, shape[-1]+1, dtype=dtype), shape) # Ordinal ranks is very easy because ties don't matter. We're done. if method == 'ordinal': - return _order_ranks(ordinal_ranks, j) # never return ties + return _order_ranks(ordinal_ranks, j, xp=xp) # never return ties # Sort array - y = np.take_along_axis(x, j, axis=-1) + y = xp.take_along_axis(x, j, axis=-1) # Logical indices of unique elements - i = np.concatenate([np.ones(shape[:-1] + (1,), dtype=np.bool_), - y[..., :-1] != y[..., 1:]], axis=-1) + i = xp.concat([xp.ones(shape[:-1] + (1,), dtype=xp.bool), + y[..., :-1] != y[..., 1:]], axis=-1) # Integer indices of unique elements - indices = np.arange(y.size)[i.ravel()] + indices = xp.arange(xp_size(y))[xp.reshape(i, (-1,))] # i gets raveled # Counts of unique elements - counts = np.diff(indices, append=y.size) + counts = xp.diff(indices, append=xp.asarray([xp_size(y)], dtype=indices.dtype)) # Compute `'min'`, `'max'`, and `'mid'` ranks of unique elements if method == 'min': @@ -10321,12 +10281,13 @@ def _rankdata(x, method, return_ties=False): elif method == 'max': ranks = ordinal_ranks[i] + counts - 1 elif method == 'average': - ranks = ordinal_ranks[i] + (counts - 1)/2 + # array API doesn't promote integers to floats + ranks = ordinal_ranks[i] + (xp.asarray(counts, dtype=dtype) - 1)/2 elif method == 'dense': - ranks = np.cumsum(i, axis=-1)[i] + ranks = xp.cumulative_sum(xp.astype(i, dtype, copy=False), axis=-1)[i] - ranks = np.repeat(ranks, counts).reshape(shape) - ranks = _order_ranks(ranks, j) + ranks = xp.reshape(xp.repeat(ranks, counts), shape) + ranks = _order_ranks(ranks, j, xp=xp) if return_ties: # Tie information is returned in a format that is useful to functions that @@ -10345,7 +10306,7 @@ def _rankdata(x, method, return_ties=False): # sorted order, so this does not unnecessarily reorder them. # - One exception is `wilcoxon`, which needs the number of zeros. Zeros always # have the lowest rank, so it is easy to find them at the zeroth index. - t = np.zeros(shape, dtype=float) + t = xp.zeros(shape, dtype=xp.float64) t[i] = counts return ranks, t return ranks @@ -10629,7 +10590,7 @@ def _pack_LinregressResult(slope, intercept, rvalue, pvalue, stderr, intercept_s intercept_stderr=intercept_stderr) -def _unpack_LinregressResult(res): +def _unpack_LinregressResult(res, _): return tuple(res) + (res.intercept_stderr,) diff --git a/scipy/stats/_variation.py b/scipy/stats/_variation.py index 09f18f6896d9..880c6f6f1b5a 100644 --- a/scipy/stats/_variation.py +++ b/scipy/stats/_variation.py @@ -1,13 +1,14 @@ +import warnings import numpy as np -from scipy._lib._util import _get_nan -from scipy._lib._array_api import array_namespace +from scipy._lib._array_api import array_namespace, _length_nonmasked +import scipy._lib.array_api_extra as xpx from ._axis_nan_policy import _axis_nan_policy_factory @_axis_nan_policy_factory( - lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,) ) def variation(a, axis=0, nan_policy='propagate', ddof=0, *, keepdims=False): """ @@ -96,33 +97,25 @@ def variation(a, axis=0, nan_policy='propagate', ddof=0, *, keepdims=False): """ xp = array_namespace(a) a = xp.asarray(a) + # `nan_policy` and `keepdims` are handled by `_axis_nan_policy` - # `axis=None` is only handled for NumPy backend if axis is None: a = xp.reshape(a, (-1,)) axis = 0 - n = a.shape[axis] - NaN = _get_nan(a) - - if a.size == 0 or ddof > n: - # Handle as a special case to avoid spurious warnings. - # The return values, if any, are all nan. - shp = list(a.shape) - shp.pop(axis) - result = xp.full(shp, fill_value=NaN) - return result[()] if result.ndim == 0 else result + n = xp.asarray(_length_nonmasked(a, axis=axis), dtype=a.dtype) - mean_a = xp.mean(a, axis=axis) + with (np.errstate(divide='ignore', invalid='ignore'), warnings.catch_warnings()): + warnings.simplefilter("ignore") + mean_a = xp.mean(a, axis=axis) + std_a = xp.std(a, axis=axis) + correction = (n / (n - ddof))**0.5 # we may need uncorrected std below + result = std_a * correction / mean_a - if ddof == n: - # Another special case. Result is either inf or nan. - std_a = xp.std(a, axis=axis, correction=0) - result = xp.where(std_a > 0, xp.copysign(xp.inf, mean_a), NaN) - return result[()] if result.ndim == 0 else result + def special_case(std_a, mean_a): + return xp.where(std_a > 0, xp.copysign(xp.inf, mean_a), xp.nan) - with np.errstate(divide='ignore', invalid='ignore'): - std_a = xp.std(a, axis=axis, correction=ddof) - result = std_a / mean_a + result = xpx.apply_where((ddof == n), (std_a, mean_a), + special_case, fill_value=result) return result[()] if result.ndim == 0 else result diff --git a/scipy/stats/morestats.py b/scipy/stats/morestats.py index ee8e6f43b7aa..76040ea0ca52 100644 --- a/scipy/stats/morestats.py +++ b/scipy/stats/morestats.py @@ -13,7 +13,7 @@ 'fligner', 'mood', 'wilcoxon', 'median_test', 'circmean', 'circvar', 'circstd', 'anderson_ksamp', 'yeojohnson_llf', 'yeojohnson', 'yeojohnson_normmax', - 'yeojohnson_normplot', 'find_repeats', 'chi2_contingency', 'distributions', + 'yeojohnson_normplot', 'chi2_contingency', 'distributions', ] diff --git a/scipy/stats/stats.py b/scipy/stats/stats.py index d5d278e209ce..6879c9c07cb0 100644 --- a/scipy/stats/stats.py +++ b/scipy/stats/stats.py @@ -6,7 +6,7 @@ __all__ = [ # noqa: F822 - 'find_repeats', 'gmean', 'hmean', 'pmean', 'mode', 'tmean', 'tvar', + 'gmean', 'hmean', 'pmean', 'mode', 'tmean', 'tvar', 'tmin', 'tmax', 'tstd', 'tsem', 'moment', 'skew', 'kurtosis', 'describe', 'skewtest', 'kurtosistest', 'normaltest', 'jarque_bera', diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index 0178be02430d..c73561eb3586 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -15,20 +15,21 @@ from scipy.stats._fit import _kolmogorov_smirnov from scipy.stats._ksstats import kolmogn from scipy.stats import qmc -from scipy.stats._distr_params import distcont +from scipy.stats._distr_params import distcont, distdiscrete from scipy.stats._distribution_infrastructure import ( - _Domain, _RealDomain, _Parameter, _Parameterization, _RealParameter, + _Domain, _RealInterval, _Parameter, _Parameterization, _RealParameter, ContinuousDistribution, ShiftedScaledDistribution, _fiinfo, _generate_domain_support, Mixture) from scipy.stats._new_distributions import StandardNormal, _LogUniform, _Gamma -from scipy.stats import Normal, Uniform +from scipy.stats._new_distributions import DiscreteDistribution +from scipy.stats import Normal, Uniform, Binomial -class Test_RealDomain: +class Test_RealInterval: rng = np.random.default_rng(349849812549824) def test_iv(self): - domain = _RealDomain(endpoints=('a', 'b')) + domain = _RealInterval(endpoints=('a', 'b')) message = "The endpoints of the distribution are defined..." with pytest.raises(TypeError, match=message): domain.get_numerical_endpoints(dict) @@ -39,7 +40,7 @@ def test_iv(self): def test_contains_simple(self, x): # Test `contains` when endpoints are defined by constants a, b = -np.inf, np.pi - domain = _RealDomain(endpoints=(a, b), inclusive=(False, True)) + domain = _RealInterval(endpoints=(a, b), inclusive=(False, True)) assert_equal(domain.contains(x), (a < x) & (x <= b)) @pytest.mark.slow @@ -70,10 +71,10 @@ def test_contains(self, shapes, inclusive_a, inclusive_b, data): np.linspace(a, b, 10), np.linspace(b, b+d, 10)]) # Domain is defined by two parameters, 'a' and 'b' - domain = _RealDomain(endpoints=('a', 'b'), + domain = _RealInterval(endpoints=('a', 'b'), inclusive=(inclusive_a, inclusive_b)) - domain.define_parameters(_RealParameter('a', domain=_RealDomain()), - _RealParameter('b', domain=_RealDomain())) + domain.define_parameters(_RealParameter('a', domain=_RealInterval()), + _RealParameter('b', domain=_RealInterval())) # Check that domain and string evaluation give the same result res = domain.contains(x, dict(a=a, b=b)) @@ -93,7 +94,7 @@ def test_contains(self, shapes, inclusive_a, inclusive_b, data): def test_contains_function_endpoints(self, inclusive, a, b): # Test `contains` when endpoints are defined by functions. endpoints = (lambda a, b: (a - b) / 2, lambda a, b: (a + b) / 2) - domain = _RealDomain(endpoints=endpoints, inclusive=inclusive) + domain = _RealInterval(endpoints=endpoints, inclusive=inclusive) x = np.asarray([(a - 2*b)/2, (a - b)/2, a/2, (a + b)/2, (a + 2*b)/2]) res = domain.contains(x, dict(a=a, b=b)) @@ -112,7 +113,7 @@ def test_contains_function_endpoints(self, inclusive, a, b): ('a', 5, True, False, "[a, 5)") ]) def test_str(self, case): - domain = _RealDomain(endpoints=case[:2], inclusive=case[2:4]) + domain = _RealInterval(endpoints=case[:2], inclusive=case[2:4]) assert str(domain) == case[4] @pytest.mark.slow @@ -135,7 +136,7 @@ def test_str2(self, a, b, inclusive_a, inclusive_b): b = _Domain.symbols.get(b, b) left_bracket = '[' if inclusive_a else '(' right_bracket = ']' if inclusive_b else ')' - domain = _RealDomain(endpoints=(a, b), + domain = _RealInterval(endpoints=(a, b), inclusive=(inclusive_a, inclusive_b)) ref = f"{left_bracket}{a}, {b}{right_bracket}" assert str(domain) == ref @@ -143,8 +144,8 @@ def test_str2(self, a, b, inclusive_a, inclusive_b): def test_symbols_gh22137(self): # `symbols` was accidentally shared between instances originally # Check that this is no longer the case - domain1 = _RealDomain(endpoints=(0, 1)) - domain2 = _RealDomain(endpoints=(0, 1)) + domain1 = _RealInterval(endpoints=(0, 1)) + domain2 = _RealInterval(endpoints=(0, 1)) assert domain1.symbols is not domain2.symbols @@ -176,7 +177,7 @@ def draw_distribution_from_family(family, data, rng, proportions, min_side=0): y = dist._variable.draw(y_shape, parameter_values=dist._parameters, proportions=proportions, rng=rng, region='typical') xy_result_shape = np.broadcast_shapes(y_shape, x_result_shape) - p_domain = _RealDomain((0, 1), (True, True)) + p_domain = _RealInterval((0, 1), (True, True)) p_var = _RealParameter('p', domain=p_domain) p = p_var.draw(x_shape, proportions=proportions, rng=rng) with np.errstate(divide='ignore', invalid='ignore'): @@ -185,13 +186,19 @@ def draw_distribution_from_family(family, data, rng, proportions, min_side=0): return dist, x, y, p, logp, result_shape, x_result_shape, xy_result_shape -families = [ +continuous_families = [ StandardNormal, Normal, Uniform, _LogUniform ] +discrete_families = [ + Binomial, +] + +families = continuous_families + discrete_families + class TestDistributions: @pytest.mark.fail_slow(60) # need to break up check_moment_funcs @@ -326,6 +333,8 @@ def test_subtraction_safe(self): # Safe subtraction is needed in special cases x = np.asarray([-1e-20, -1e-21, 1e-20, 1e-21, -1e-20]) y = np.asarray([-1e-21, -1e-20, 1e-21, 1e-20, 1e-20]) + + p0 = X.pdf(0)*(y-x) p1 = X.cdf(x, y, method='subtraction_safe') p2 = X.cdf(x, y, method='subtraction') @@ -411,7 +420,11 @@ def check_sample_shape_NaNs(dist, fname, sample_shape, result_shape, rng): sample1 = sample_method(sample_shape, method=method, rng=42) sample2 = sample_method(sample_shape, method=method, rng=42) - assert not np.any(np.equal(res, sample1)) + if not isinstance(dist, DiscreteDistribution): + # The idea is that it's very unlikely that the random sample + # for a randomly chosen seed will match that for seed 42, + # but it is not so unlikely if `dist` is a discrete distribution. + assert not np.any(np.equal(res, sample1)) assert_equal(sample1, sample2) @@ -511,6 +524,13 @@ def check_cdf2(dist, log, x, y, result_shape, methods): assert np.isscalar(ref) for method in methods: + if isinstance(dist, DiscreteDistribution): + message = ("Two argument cdf functions are currently only supported for " + "continuous distributions.") + with pytest.raises(NotImplementedError, match=message): + res = (np.exp(dist.logcdf(x, y, method=method)) if log + else dist.cdf(x, y, method=method)) + continue res = (np.exp(dist.logcdf(x, y, method=method)) if log else dist.cdf(x, y, method=method)) np.testing.assert_allclose(res, ref, atol=1e-14) @@ -539,6 +559,13 @@ def check_ccdf2(dist, log, x, y, result_shape, methods): assert np.isscalar(ref) for method in methods: + message = ("Two argument cdf functions are currently only supported for " + "continuous distributions.") + if isinstance(dist, DiscreteDistribution): + with pytest.raises(NotImplementedError, match=message): + res = (np.exp(dist.logccdf(x, y, method=method)) if log + else dist.ccdf(x, y, method=method)) + continue res = (np.exp(dist.logccdf(x, y, method=method)) if log else dist.ccdf(x, y, method=method)) np.testing.assert_allclose(res, ref, atol=1e-14) @@ -552,9 +579,9 @@ def check_nans_and_edges(dist, fname, arg, res): valid_parameters = get_valid_parameters(dist) if fname in {'icdf', 'iccdf'}: - arg_domain = _RealDomain(endpoints=(0, 1), inclusive=(True, True)) + arg_domain = _RealInterval(endpoints=(0, 1), inclusive=(True, True)) elif fname in {'ilogcdf', 'ilogccdf'}: - arg_domain = _RealDomain(endpoints=(-inf, 0), inclusive=(True, True)) + arg_domain = _RealInterval(endpoints=(-inf, 0), inclusive=(True, True)) else: arg_domain = dist._variable.domain @@ -576,50 +603,65 @@ def check_nans_and_edges(dist, fname, arg, res): outside_arg_plus = (outside_arg == 1) & valid_parameters endpoint_arg_minus = (endpoint_arg == -1) & valid_parameters endpoint_arg_plus = (endpoint_arg == 1) & valid_parameters + + is_discrete = isinstance(dist, DiscreteDistribution) # Writing this independently of how the are set in the distribution # infrastructure. That is very compact; this is very verbose. if fname in {'logpdf'}: assert_equal(res[outside_arg_minus], -np.inf) assert_equal(res[outside_arg_plus], -np.inf) - assert_equal(res[endpoint_arg_minus & ~valid_arg], -np.inf) - assert_equal(res[endpoint_arg_plus & ~valid_arg], -np.inf) + ref = -np.inf if not is_discrete else np.inf + assert_equal(res[endpoint_arg_minus & ~valid_arg], ref) + assert_equal(res[endpoint_arg_plus & ~valid_arg], ref) elif fname in {'pdf'}: assert_equal(res[outside_arg_minus], 0) assert_equal(res[outside_arg_plus], 0) - assert_equal(res[endpoint_arg_minus & ~valid_arg], 0) - assert_equal(res[endpoint_arg_plus & ~valid_arg], 0) - elif fname in {'logcdf'}: + ref = 0 if not is_discrete else np.inf + assert_equal(res[endpoint_arg_minus & ~valid_arg], ref) + assert_equal(res[endpoint_arg_plus & ~valid_arg], ref) + elif fname in {'logcdf'} and not is_discrete: assert_equal(res[outside_arg_minus], -inf) assert_equal(res[outside_arg_plus], 0) assert_equal(res[endpoint_arg_minus], -inf) assert_equal(res[endpoint_arg_plus], 0) - elif fname in {'cdf'}: + elif fname in {'cdf'} and not is_discrete: assert_equal(res[outside_arg_minus], 0) assert_equal(res[outside_arg_plus], 1) assert_equal(res[endpoint_arg_minus], 0) assert_equal(res[endpoint_arg_plus], 1) - elif fname in {'logccdf'}: + elif fname in {'logccdf'} and not is_discrete: assert_equal(res[outside_arg_minus], 0) assert_equal(res[outside_arg_plus], -inf) assert_equal(res[endpoint_arg_minus], 0) assert_equal(res[endpoint_arg_plus], -inf) - elif fname in {'ccdf'}: + elif fname in {'ccdf'} and not is_discrete: assert_equal(res[outside_arg_minus], 1) assert_equal(res[outside_arg_plus], 0) assert_equal(res[endpoint_arg_minus], 1) assert_equal(res[endpoint_arg_plus], 0) - elif fname in {'ilogcdf', 'icdf'}: + elif fname in {'ilogcdf', 'icdf'} and not is_discrete: assert_equal(res[outside_arg == -1], np.nan) assert_equal(res[outside_arg == 1], np.nan) assert_equal(res[endpoint_arg == -1], a[endpoint_arg == -1]) assert_equal(res[endpoint_arg == 1], b[endpoint_arg == 1]) - elif fname in {'ilogccdf', 'iccdf'}: + elif fname in {'ilogccdf', 'iccdf'} and not is_discrete: assert_equal(res[outside_arg == -1], np.nan) assert_equal(res[outside_arg == 1], np.nan) assert_equal(res[endpoint_arg == -1], b[endpoint_arg == -1]) assert_equal(res[endpoint_arg == 1], a[endpoint_arg == 1]) - if fname not in {'logmean', 'mean', 'logskewness', 'skewness', 'support'}: + exclude = {'logmean', 'mean', 'logskewness', 'skewness', 'support'} + if isinstance(dist, DiscreteDistribution): + exclude.update({'pdf', 'logpdf'}) + + if ( + fname not in exclude + and not (isinstance(dist, Binomial) + and np.any((dist.n == 0) | (dist.p == 0) | (dist.p == 1)))): + # This can fail in degenerate case where Binomial distribution is a point + # distribution. Further on, we could factor out an is_degenerate function + # for the tests, or think about storing info about degeneracy in the + # instances. assert np.isfinite(res[all_valid & (endpoint_arg == 0)]).all() @@ -649,7 +691,6 @@ def has_formula(order, kind): orders = getattr(formula, 'orders', set(range(6))) return order in orders - dist.reset_cache() ### Check Raw Moments ### @@ -702,6 +743,7 @@ def has_formula(order, kind): dist.moment(i, 'raw') check(i, 'central', 'transform', ref) + variance = dist.variance() dist.reset_cache() # If we have standard moment formulas, or if there are @@ -712,9 +754,9 @@ def has_formula(order, kind): for i in range(3, 6): ref = dist.moment(i, 'central', method='quadrature') check(i, 'central', 'normalize', ref, - success=has_formula(i, 'standardized')) + success=has_formula(i, 'standardized') and not np.any(variance == 0)) dist.moment(i, 'standardized') # build up the cache - check(i, 'central', 'normalize', ref) + check(i, 'central', 'normalize', ref, success=not np.any(variance == 0)) ### Check Standardized Moments ### @@ -727,7 +769,13 @@ def has_formula(order, kind): assert ref.shape == result_shape check(i, 'standardized', 'formula', ref, success=has_formula(i, 'standardized')) - check(i, 'standardized', 'general', ref, success=i <= 2) + if not ( + isinstance(dist, Binomial) + and np.any((dist.n == 0) | (dist.p == 0) | (dist.p == 1)) + ): + # This test will fail for degenerate case where binomial distribution + # is a point distribution. + check(i, 'standardized', 'general', ref, success=i <= 2) check(i, 'standardized', 'normalize', ref) if isinstance(dist, ShiftedScaledDistribution): @@ -853,7 +901,7 @@ def classify_arg(dist, arg, arg_domain): def test_input_validation(): class Test(ContinuousDistribution): - _variable = _RealParameter('x', domain=_RealDomain()) + _variable = _RealParameter('x', domain=_RealInterval()) message = ("The `Test` distribution family does not accept parameters, " "but parameters `{'a'}` were provided.") @@ -882,10 +930,10 @@ class Test(ContinuousDistribution): Test().moment(2, kind='coconut') class Test2(ContinuousDistribution): - _p1 = _RealParameter('c', domain=_RealDomain()) - _p2 = _RealParameter('d', domain=_RealDomain()) + _p1 = _RealParameter('c', domain=_RealInterval()) + _p2 = _RealParameter('d', domain=_RealInterval()) _parameterizations = [_Parameterization(_p1, _p2)] - _variable = _RealParameter('x', domain=_RealDomain()) + _variable = _RealParameter('x', domain=_RealInterval()) message = ("The provided parameters `{a}` do not match a supported " "parameterization of the `Test2` distribution family.") @@ -1057,29 +1105,35 @@ def test_shapes(self): class TestMakeDistribution: - @pytest.mark.parametrize('i, distdata', enumerate(distcont)) + @pytest.mark.parametrize('i, distdata', enumerate(distcont + distdiscrete)) def test_rv_generic(self, i, distdata): distname = distdata[0] slow = {'argus', 'exponpow', 'exponweib', 'genexpon', 'gompertz', 'halfgennorm', 'johnsonsb', 'kappa4', 'ksone', 'kstwo', 'kstwobign', 'norminvgauss', 'powerlognorm', 'powernorm', 'recipinvgauss', 'studentized_range', - 'vonmises_line'} + 'vonmises_line', # continuous + 'betanbinom', 'logser', 'skellam', 'zipf'} # discrete if not int(os.environ.get('SCIPY_XSLOW', '0')) and distname in slow: pytest.skip('Skipping as XSLOW') - if distname in { # skip these distributions - 'levy_stable', # private methods seem to require >= 1d args - 'vonmises', # circular distribution; shouldn't work + if distname in { # skip these distributions + 'levy_stable', # private methods seem to require >= 1d args + 'vonmises', # circular distribution; shouldn't work + 'poisson_binom', # vector shape parameter + 'hypergeom', # distribution functions need interpolation + 'nchypergeom_fisher', # distribution functions need interpolation + 'nchypergeom_wallenius', # distribution functions need interpolation }: return # skip single test, mostly due to slight disagreement custom_tolerances = {'ksone': 1e-5, 'kstwo': 1e-5} # discontinuous PDF skip_entropy = {'kstwobign', 'pearson3'} # tolerance issue - skip_skewness = {'exponpow', 'ksone'} # tolerance issue - skip_kurtosis = {'chi', 'exponpow', 'invgamma', # tolerance issue - 'johnsonsb', 'ksone', 'kstwo'} # tolerance issue + skip_skewness = {'exponpow', 'ksone', 'nchypergeom_wallenius'} # tolerance + skip_kurtosis = {'chi', 'exponpow', 'invgamma', # tolerance + 'johnsonsb', 'ksone', 'kstwo', # tolerance + 'nchypergeom_wallenius'} # tolerance skip_logccdf = {'arcsine', 'skewcauchy', 'trapezoid', 'triang'} # tolerance skip_raw = {2: {'alpha', 'foldcauchy', 'halfcauchy', 'levy', 'levy_l'}, 3: {'pareto'}, # stats.pareto is just wrong @@ -1102,6 +1156,10 @@ def test_rv_generic(self, i, distdata): assert_allclose(X.support(), Y.support()) if distname not in skip_entropy: assert_allclose(X.entropy(), Y.entropy(), rtol=rtol) + if isinstance(Y, stats.rv_discrete): + # some continuous distributions have trouble with `logentropy` because + # it uses complex numbers + assert_allclose(np.exp(X.logentropy()), Y.entropy(), rtol=rtol) assert_allclose(X.median(), Y.median(), rtol=rtol) assert_allclose(X.mean(), m, rtol=rtol, atol=atol) assert_allclose(X.variance(), v, rtol=rtol, atol=atol) @@ -1110,15 +1168,29 @@ def test_rv_generic(self, i, distdata): if distname not in skip_kurtosis: assert_allclose(X.kurtosis(convention='excess'), k, rtol=rtol, atol=atol) - assert_allclose(X.logpdf(x), Y.logpdf(x), rtol=rtol) - assert_allclose(X.pdf(x), Y.pdf(x), rtol=rtol) + if isinstance(dist, stats.rv_continuous): + assert_allclose(X.logpdf(x), Y.logpdf(x), rtol=rtol) + assert_allclose(X.pdf(x), Y.pdf(x), rtol=rtol) + else: + assert_allclose(X.logpmf(x), Y.logpmf(x), rtol=rtol) + assert_allclose(X.pmf(x), Y.pmf(x), rtol=rtol) assert_allclose(X.logcdf(x), Y.logcdf(x), rtol=rtol) assert_allclose(X.cdf(x), Y.cdf(x), rtol=rtol) if distname not in skip_logccdf: assert_allclose(X.logccdf(x), Y.logsf(x), rtol=rtol) assert_allclose(X.ccdf(x), Y.sf(x), rtol=rtol) - assert_allclose(X.icdf(p), Y.ppf(p), rtol=rtol) - assert_allclose(X.iccdf(p), Y.isf(p), rtol=rtol) + + # old infrastructure convention for ppf(p=0) and isf(p=1) is different than + # new infrastructure. Adjust reference values accordingly. + a, _ = Y.support() + ref_ppf = Y.ppf(p) + ref_ppf[p == 0] = a + ref_isf = Y.isf(p) + ref_isf[p == 1] = a + + assert_allclose(X.icdf(p), ref_ppf, rtol=rtol) + assert_allclose(X.iccdf(p), ref_isf, rtol=rtol) + for order in range(5): if distname not in skip_raw.get(order, {}): assert_allclose(X.moment(order, kind='raw'), @@ -1127,10 +1199,14 @@ def test_rv_generic(self, i, distdata): if distname not in skip_standardized: assert_allclose(X.moment(order, kind='standardized'), Y.stats('mvsk'[order-1]), rtol=rtol, atol=atol) - seed = 845298245687345 - assert_allclose(X.sample(shape=10, rng=seed), - Y.rvs(size=10, random_state=np.random.default_rng(seed)), - rtol=rtol) + if isinstance(dist, stats.rv_continuous): + # For discrete distributions, these won't agree at the far left end + # of the support, and the new infrastructure is slow there (for now). + seed = 845298245687345 + assert_allclose(X.sample(shape=10, rng=seed), + Y.rvs(size=10, + random_state=np.random.default_rng(seed)), + rtol=rtol) def test_custom(self): rng = np.random.default_rng(7548723590230982) @@ -1349,7 +1425,7 @@ def test_input_validation(self): with pytest.raises(NotImplementedError, match=message): stats.make_distribution(stats.vonmises) - message = "The argument must be an instance of `rv_continuous`." + message = "The argument must be an instance of..." with pytest.raises(ValueError, match=message): stats.make_distribution(object()) @@ -1373,7 +1449,14 @@ def test_repr_str_docs(self): class TestTransforms: - # putting this at the top to hopefully avoid merge conflicts + def test_ContinuousDistribution_only(self): + X = stats.Binomial(n=10, p=0.5) + # This is applied at the top level TransformedDistribution, + # so testing one subclass is enough + message = "Transformations are currently only supported for continuous RVs." + with pytest.raises(NotImplementedError, match=message): + stats.exp(X) + def test_truncate(self): rng = np.random.default_rng(81345982345826) lb = rng.random((3, 1)) @@ -1848,7 +1931,7 @@ def test_Parameter(self): [(np.float16, np.float16), (np.int16, np.float64)]) def test_RealParameter_uncommon_dtypes(self, dtype_in, dtype_out): - domain = _RealDomain((-1, 1)) + domain = _RealInterval((-1, 1)) parameter = _RealParameter('x', domain=domain) x = np.asarray([0.5, 2.5], dtype=dtype_in) @@ -1863,7 +1946,7 @@ def test_ContinuousDistribution_set_invalid_nan(self): # to return the right shape and dytpe, but this would need to be # configurable. class TestDist(ContinuousDistribution): - _variable = _RealParameter('x', domain=_RealDomain(endpoints=(0., 1.))) + _variable = _RealParameter('x', domain=_RealInterval(endpoints=(0., 1.))) def _logpdf_formula(self, x, *args, **kwargs): return 0 @@ -1990,7 +2073,7 @@ def test_not_too_long(self, dist): class MixedDist(ContinuousDistribution): - _variable = _RealParameter('x', domain=_RealDomain(endpoints=(-np.inf, np.inf))) + _variable = _RealParameter('x', domain=_RealInterval(endpoints=(-np.inf, np.inf))) def _pdf_formula(self, x, *args, **kwargs): return (0.4 * 1/(1.1 * np.sqrt(2*np.pi)) * np.exp(-0.5*((x+0.25)/1.1)**2) + 0.6 * 1/(0.9 * np.sqrt(2*np.pi)) * np.exp(-0.5*((x-0.5)/0.9)**2)) diff --git a/scipy/stats/tests/test_distributions.py b/scipy/stats/tests/test_distributions.py index f5673437b8ec..e2bf87b6b8a1 100644 --- a/scipy/stats/tests/test_distributions.py +++ b/scipy/stats/tests/test_distributions.py @@ -9951,6 +9951,15 @@ def test_cdf(self): assert_allclose(p[0], np.arctan(cr*np.tan(x1/2))/np.pi) assert_allclose(p[1], 1 - np.arctan(cr*np.tan(np.pi - x2/2))/np.pi) + @pytest.mark.parametrize('c', [1e-10, 1e-1]) + @pytest.mark.parametrize('loc', [-100, -2*np.pi, -np.pi, 0, np.pi, 2*np.pi, 100]) + @pytest.mark.parametrize('scale', [1e-10, 1, 1e10]) + def test_rvs_lie_on_circle(self, c, loc, scale): + # Check that the random variates lie in range [0, 2*pi] + x = stats.wrapcauchy.rvs(c=c, loc=loc, scale=scale, size=1000) + assert np.all(x >= 0) + assert np.all(x <= 2 * np.pi) + def test_rvs_no_size_error(): # _rvs methods must have parameter `size`; see gh-11394 diff --git a/scipy/stats/tests/test_marray.py b/scipy/stats/tests/test_marray.py index ffb3121fcf35..901d28dcb3c1 100644 --- a/scipy/stats/tests/test_marray.py +++ b/scipy/stats/tests/test_marray.py @@ -2,8 +2,8 @@ import numpy as np from scipy import stats -from scipy._lib._array_api import xp_assert_close, xp_assert_equal -from scipy.stats._stats_py import _xp_mean, _xp_var, _length_nonmasked +from scipy._lib._array_api import xp_assert_close, xp_assert_equal, _length_nonmasked +from scipy.stats._stats_py import _xp_mean, _xp_var from scipy.stats._axis_nan_policy import _axis_nan_policy_factory @@ -82,6 +82,8 @@ def test_xp_mean(axis, keepdims, xp): (stats.circmean, {}), (stats.circvar, {}), (stats.circstd, {}), + (stats.gstd, {}), + (stats.variation, {}), (_xp_var, {}), (stats.tmean, {'limits': (0.1, 0.9)}), (stats.tvar, {'limits': (0.1, 0.9)}), @@ -275,6 +277,7 @@ def test_ttest_ind_from_stats(xp): assert res.statistic.shape == shape assert res.pvalue.shape == shape + def test_length_nonmasked_marray_iterable_axis_raises(): xp = marray._get_namespace(np) @@ -287,3 +290,65 @@ def test_length_nonmasked_marray_iterable_axis_raises(): with pytest.raises(NotImplementedError, match="`axis` must be an integer or None for use with `MArray`"): _length_nonmasked(marr, axis=(0, 1), xp=xp) + + +@skip_backend('dask.array', reason='Arrays need `device` attribute: dask/dask#11711') +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@skip_backend('torch', reason="array-api-compat#242") +@pytest.mark.filterwarnings("ignore::RuntimeWarning") # mdhaber/marray#120 +def test_directional_stats(xp): + mxp, marrays, narrays = get_arrays(1, shape=(100, 3), xp=xp) + res = stats.directional_stats(*marrays) + narrays[0] = narrays[0][~np.any(np.isnan(narrays[0]), axis=1)] + ref = stats.directional_stats(*narrays) + xp_assert_close(res.mean_direction.data, xp.asarray(ref.mean_direction)) + xp_assert_close(res.mean_resultant_length.data, + xp.asarray(ref.mean_resultant_length)) + assert not xp.any(res.mean_direction.mask) + assert not xp.any(res.mean_resultant_length.mask) + + +@skip_backend('dask.array', reason='Arrays need `device` attribute: dask/dask#11711') +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@skip_backend('torch', reason="array-api-compat#242") +@skip_backend('cupy', reason="special functions won't work") +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_bartlett(axis, xp): + mxp, marrays, narrays = get_arrays(3, xp=xp) + res = stats.bartlett(*marrays, axis=axis) + ref = stats.bartlett(*narrays, nan_policy='omit', axis=axis) + xp_assert_close(res.statistic.data, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue.data, xp.asarray(ref.pvalue)) + + +@skip_backend('dask.array', reason='Arrays need `device` attribute: dask/dask#11711') +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@skip_backend('torch', reason="array-api-compat#242") +@skip_backend('cupy', reason="special functions won't work") +@pytest.mark.parametrize('f', [stats.pearsonr, stats.pointbiserialr]) +def test_pearsonr(f, xp): + mxp, marrays, narrays = get_arrays(2, shape=(25,), xp=xp) + res = f(*marrays) + + x, y = narrays + mask = np.isnan(x) | np.isnan(y) + ref = f(x[~mask], y[~mask]) + + xp_assert_close(res.statistic.data, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue.data, xp.asarray(ref.pvalue)) + + if f == stats.pearsonr: + res_ci_low, res_ci_high = res.confidence_interval() + ref_ci_low, ref_ci_high = ref.confidence_interval() + xp_assert_close(res_ci_low.data, xp.asarray(ref_ci_low)) + xp_assert_close(res_ci_high.data, xp.asarray(ref_ci_high)) + +@skip_backend('dask.array', reason='Arrays need `device` attribute: dask/dask#11711') +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.parametrize('qk', [False, True]) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_entropy(qk, axis, xp): + mxp, marrays, narrays = get_arrays(2 if qk else 1, xp=xp) + res = stats.entropy(*marrays, axis=axis) + ref = stats.entropy(*narrays, nan_policy='omit', axis=axis) + xp_assert_close(res.data, xp.asarray(ref)) diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index f54e7ab3e627..936cf68ace69 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -2,10 +2,11 @@ # # Further enhancements and tests added by numerous SciPy developers. # +import contextlib import math -import warnings +import re import sys -import contextlib +import warnings from functools import partial import numpy as np @@ -16,7 +17,7 @@ suppress_warnings) import pytest from pytest import raises as assert_raises -import re + from scipy import optimize, stats, special from scipy.stats._morestats import _abw_state, _get_As_weibull, _Avals_weibull from .common_tests import check_named_results @@ -26,7 +27,7 @@ from scipy.stats._axis_nan_policy import (SmallSampleWarning, too_small_nd_omit, too_small_1d_omit, too_small_1d_not_omit) -from scipy._lib._array_api import is_numpy +from scipy._lib._array_api import is_numpy, is_torch from scipy._lib._array_api_no_0d import ( xp_assert_close, xp_assert_equal, @@ -365,6 +366,7 @@ def test_weibull_min_case_B(self): with pytest.raises(ValueError, match=message): stats.anderson(x, 'weibull_min') + @pytest.mark.thread_unsafe def test_weibull_warning_error(self): # Check for warning message when there are too few observations # This is also an example in which an error occurs during fitting @@ -1802,7 +1804,7 @@ class TestKstat: def test_moments_normal_distribution(self, xp): rng = np.random.RandomState(32149) data = xp.asarray(rng.randn(12345), dtype=xp.float64) - moments = xp.asarray([stats.kstat(data, n) for n in [1, 2, 3, 4]]) + moments = xp.stack([stats.kstat(data, n) for n in [1, 2, 3, 4]]) expected = xp.asarray([0.011315, 1.017931, 0.05811052, 0.0754134], dtype=data.dtype) @@ -1812,7 +1814,7 @@ def test_moments_normal_distribution(self, xp): m1 = stats.moment(data, order=1) m2 = stats.moment(data, order=2) m3 = stats.moment(data, order=3) - xp_assert_close(xp.asarray((m1, m2, m3)), expected[:-1], atol=0.02, rtol=1e-2) + xp_assert_close(xp.stack((m1, m2, m3)), expected[:-1], atol=0.02, rtol=1e-2) @pytest.mark.filterwarnings("ignore:invalid value encountered in scalar divide") def test_empty_input(self, xp): @@ -2024,6 +2026,7 @@ def test_2d_input(self, xp): llf2 = stats.boxcox_llf(lmbda, np.vstack([x, x]).T) xp_assert_close(xp.asarray([llf, llf]), xp.asarray(llf2), rtol=1e-12) + @pytest.mark.thread_unsafe def test_empty(self, xp): message = "One or more sample arguments is too small..." context = (pytest.warns(SmallSampleWarning, match=message) if is_numpy(xp) @@ -2044,7 +2047,8 @@ def test_instability_gh20021(self, xp): llf = stats.boxcox_llf(1e-8, data) # The expected value was computed with mpsci, set mpmath.mp.dps=100 # expect float64 output for integer input - xp_assert_close(llf, xp.asarray(-15.32401272869016598, dtype=xp.float64)) + xp_assert_close(llf, xp.asarray(-15.32401272869016598, dtype=xp.float64), + rtol=1e-7) def test_axis(self, xp): data = xp.asarray([[100, 200], [300, 400]]) @@ -3226,6 +3230,7 @@ def test_edge_cases(self): assert_array_equal(stats.false_discovery_control([]), []) +@skip_xp_backends("dask.array", reason='data-apis/array-api-extra#196') class TestCommonAxis: # More thorough testing of `axis` in `test_axis_nan_policy`, # but those tests aren't run with array API yet. This class @@ -3238,16 +3243,18 @@ class TestCommonAxis: (stats.kstat, {'n': 2}), (stats.variation, {})]) def test_axis(self, case, xp): + if is_torch(xp) and case[0] == stats.variation: + pytest.xfail(reason="copysign doesn't accept scalar array-api-compat#271") fun, kwargs = case rng = np.random.default_rng(24598245982345) x = xp.asarray(rng.random((6, 7))) res = fun(x, **kwargs, axis=0) - ref = xp.asarray([fun(x[:, i], **kwargs) for i in range(x.shape[1])]) + ref = xp.stack([fun(x[:, i], **kwargs) for i in range(x.shape[1])]) xp_assert_close(res, ref) res = fun(x, **kwargs, axis=1) - ref = xp.asarray([fun(x[i, :], **kwargs) for i in range(x.shape[0])]) + ref = xp.stack([fun(x[i, :], **kwargs) for i in range(x.shape[0])]) xp_assert_close(res, ref) res = fun(x, **kwargs, axis=None) diff --git a/scipy/stats/tests/test_quantile.py b/scipy/stats/tests/test_quantile.py index 744d9e2cea29..e9e0fbc797bb 100644 --- a/scipy/stats/tests/test_quantile.py +++ b/scipy/stats/tests/test_quantile.py @@ -86,8 +86,8 @@ def test_input_validation(self, xp): 'hazen', 'interpolated_inverted_cdf', 'linear', 'median_unbiased', 'normal_unbiased', 'weibull']) @pytest.mark.parametrize('shape_x, shape_p, axis', - [(10, None, -1), (10, 3, -1), (10, (2, 3), -1), - ((10, 2), None, 0), ((10, 2), None, 0)]) + [(10, None, -1), (10, 10, -1), (10, (2, 3), -1), + ((10, 2), None, 0), ((10, 2), None, 0),]) def test_against_numpy(self, method, shape_x, shape_p, axis, xp): dtype = xp_default_dtype(xp) rng = np.random.default_rng(23458924568734956) diff --git a/scipy/stats/tests/test_rank.py b/scipy/stats/tests/test_rank.py index 0df57aa7e875..cf154866eec4 100644 --- a/scipy/stats/tests/test_rank.py +++ b/scipy/stats/tests/test_rank.py @@ -2,10 +2,10 @@ from numpy.testing import assert_equal, assert_array_equal import pytest +from scipy import stats from scipy.conftest import skip_xp_invalid_arg from scipy.stats import rankdata, tiecorrect -from scipy._lib._util import np_long - +from scipy._lib._array_api import xp_assert_equal, make_xp_test_case class TestTieCorrect: @@ -73,63 +73,69 @@ def test_overflow(self): assert_equal(out, 1.0 - k * (ntie**3 - ntie) / float(n**3 - n)) +@make_xp_test_case(stats.rankdata) class TestRankData: - def test_empty(self): - """stats.rankdata([]) should return an empty array.""" - a = np.array([], dtype=int) + def desired_dtype(self, method='average', has_nans=False, *, xp): + if has_nans: + return xp.asarray(1.).dtype + return xp.asarray(1.).dtype if method=='average' else xp.asarray(1).dtype + + def test_empty(self, xp): + """stats.rankdata of empty array should return an empty array.""" + a = xp.asarray([], dtype=xp.int64) r = rankdata(a) - assert_array_equal(r, np.array([], dtype=np.float64)) + xp_assert_equal(r, xp.asarray([], dtype=self.desired_dtype(xp=xp))) + + def test_list(self): + # test that NumPy still accepts lists r = rankdata([]) - assert_array_equal(r, np.array([], dtype=np.float64)) + assert_array_equal(r, np.array([])) + + r = rankdata([40, 10, 30, 10, 50]) + assert_equal(r, [4.0, 1.5, 3.0, 1.5, 5.0]) @pytest.mark.parametrize("shape", [(0, 1, 2)]) @pytest.mark.parametrize("axis", [None, *range(3)]) - def test_empty_multidim(self, shape, axis): - a = np.empty(shape, dtype=int) + def test_empty_multidim(self, shape, axis, xp): + a = xp.empty(shape, dtype=xp.int64) r = rankdata(a, axis=axis) expected_shape = (0,) if axis is None else shape - assert_equal(r.shape, expected_shape) - assert_equal(r.dtype, np.float64) + xp_assert_equal(r, xp.empty(expected_shape, dtype=self.desired_dtype(xp=xp))) - def test_one(self): + def test_one(self, xp): """Check stats.rankdata with an array of length 1.""" data = [100] - a = np.array(data, dtype=int) + a = xp.asarray(data, dtype=xp.int64) r = rankdata(a) - assert_array_equal(r, np.array([1.0], dtype=np.float64)) - r = rankdata(data) - assert_array_equal(r, np.array([1.0], dtype=np.float64)) + xp_assert_equal(r, xp.asarray([1.0], dtype=self.desired_dtype(xp=xp))) - def test_basic(self): + def test_basic(self, xp): """Basic tests of stats.rankdata.""" + desired_dtype = self.desired_dtype(xp=xp) + data = [100, 10, 50] - expected = np.array([3.0, 1.0, 2.0], dtype=np.float64) - a = np.array(data, dtype=int) + expected = xp.asarray([3.0, 1.0, 2.0], dtype=desired_dtype) + a = xp.asarray(data, dtype=xp.int64) r = rankdata(a) - assert_array_equal(r, expected) - r = rankdata(data) - assert_array_equal(r, expected) + xp_assert_equal(r, expected) data = [40, 10, 30, 10, 50] - expected = np.array([4.0, 1.5, 3.0, 1.5, 5.0], dtype=np.float64) - a = np.array(data, dtype=int) + expected = xp.asarray([4.0, 1.5, 3.0, 1.5, 5.0], dtype=desired_dtype) + a = xp.asarray(data, dtype=xp.int64) r = rankdata(a) - assert_array_equal(r, expected) - r = rankdata(data) - assert_array_equal(r, expected) + xp_assert_equal(r, expected) data = [20, 20, 20, 10, 10, 10] - expected = np.array([5.0, 5.0, 5.0, 2.0, 2.0, 2.0], dtype=np.float64) - a = np.array(data, dtype=int) + expected = xp.asarray([5.0, 5.0, 5.0, 2.0, 2.0, 2.0], dtype=desired_dtype) + a = xp.asarray(data, dtype=xp.int64) r = rankdata(a) - assert_array_equal(r, expected) - r = rankdata(data) - assert_array_equal(r, expected) - # The docstring states explicitly that the argument is flattened. - a2d = a.reshape(2, 3) + xp_assert_equal(r, expected) + + # # The docstring states explicitly that the argument is flattened. + a2d = xp.reshape(a, (2, 3)) r = rankdata(a2d) - assert_array_equal(r, expected) + xp_assert_equal(r, expected) @skip_xp_invalid_arg def test_rankdata_object_string(self): @@ -165,50 +171,51 @@ def check_ranks(a): val = np.array([0, 1, 2, 2.718, 3, 3.141], dtype='object') check_ranks(np.random.choice(val, 200).astype('object')) - def test_large_int(self): - data = np.array([2**60, 2**60+1], dtype=np.uint64) + def test_large_int(self, xp): + if hasattr(xp, 'uint64'): + data = xp.asarray([2**60, 2**60+1], dtype=xp.uint64) + r = rankdata(data) + xp_assert_equal(r, xp.asarray([1.0, 2.0], dtype=self.desired_dtype(xp=xp))) + + data = xp.asarray([2**60, 2**60+1], dtype=xp.int64) r = rankdata(data) - assert_array_equal(r, [1.0, 2.0]) + xp_assert_equal(r, xp.asarray([1.0, 2.0], dtype=self.desired_dtype(xp=xp))) - data = np.array([2**60, 2**60+1], dtype=np.int64) + data = xp.asarray([2**60, -2**60+1], dtype=xp.int64) r = rankdata(data) - assert_array_equal(r, [1.0, 2.0]) + xp_assert_equal(r, xp.asarray([2.0, 1.0], dtype=self.desired_dtype(xp=xp))) - data = np.array([2**60, -2**60+1], dtype=np.int64) + @pytest.mark.parametrize('n', [10000, 100000, 1000000]) + def test_big_tie(self, n, xp): + data = xp.ones(n) r = rankdata(data) - assert_array_equal(r, [2.0, 1.0]) + expected_rank = 0.5 * (n + 1) + ref = xp.asarray(expected_rank * data, dtype=self.desired_dtype(xp=xp)) + xp_assert_equal(r, ref) - def test_big_tie(self): - for n in [10000, 100000, 1000000]: - data = np.ones(n, dtype=int) - r = rankdata(data) - expected_rank = 0.5 * (n + 1) - assert_array_equal(r, expected_rank * data, - err_msg=f"test failed with n={n}") - - def test_axis(self): - data = [[0, 2, 1], - [4, 2, 2]] - expected0 = [[1., 1.5, 1.], - [2., 1.5, 2.]] + def test_axis(self, xp): + data = xp.asarray([[0, 2, 1], [4, 2, 2]]) + + expected0 = xp.asarray([[1., 1.5, 1.], [2., 1.5, 2.]]) r0 = rankdata(data, axis=0) - assert_array_equal(r0, expected0) - expected1 = [[1., 3., 2.], - [3., 1.5, 1.5]] + xp_assert_equal(r0, expected0) + + expected1 = xp.asarray([[1., 3., 2.], [3., 1.5, 1.5]]) r1 = rankdata(data, axis=1) - assert_array_equal(r1, expected1) + xp_assert_equal(r1, expected1) - methods = ["average", "min", "max", "dense", "ordinal"] - dtypes = [np.float64] + [np_long]*4 + methods= ["average", "min", "max", "dense", "ordinal"] @pytest.mark.parametrize("axis", [0, 1]) - @pytest.mark.parametrize("method, dtype", zip(methods, dtypes)) - def test_size_0_axis(self, axis, method, dtype): + @pytest.mark.parametrize("method", methods) + def test_size_0_axis(self, axis, method, xp): shape = (3, 0) - data = np.zeros(shape) + desired_dtype = self.desired_dtype(method, xp=xp) + data = xp.zeros(shape) r = rankdata(data, method=method, axis=axis) assert_equal(r.shape, shape) - assert_equal(r.dtype, dtype) + assert_equal(r.dtype, desired_dtype) + xp_assert_equal(r, xp.empty(shape, dtype=desired_dtype)) @pytest.mark.parametrize('axis', range(3)) @pytest.mark.parametrize('method', methods) @@ -289,50 +296,50 @@ def test_nan_policy_propagate(self): [np.nan, np.nan, np.nan], [1, 2.5, 2.5]]) - -_cases = ( - # values, method, expected - ([], 'average', []), - ([], 'min', []), - ([], 'max', []), - ([], 'dense', []), - ([], 'ordinal', []), - # - ([100], 'average', [1.0]), - ([100], 'min', [1.0]), - ([100], 'max', [1.0]), - ([100], 'dense', [1.0]), - ([100], 'ordinal', [1.0]), - # - ([100, 100, 100], 'average', [2.0, 2.0, 2.0]), - ([100, 100, 100], 'min', [1.0, 1.0, 1.0]), - ([100, 100, 100], 'max', [3.0, 3.0, 3.0]), - ([100, 100, 100], 'dense', [1.0, 1.0, 1.0]), - ([100, 100, 100], 'ordinal', [1.0, 2.0, 3.0]), - # - ([100, 300, 200], 'average', [1.0, 3.0, 2.0]), - ([100, 300, 200], 'min', [1.0, 3.0, 2.0]), - ([100, 300, 200], 'max', [1.0, 3.0, 2.0]), - ([100, 300, 200], 'dense', [1.0, 3.0, 2.0]), - ([100, 300, 200], 'ordinal', [1.0, 3.0, 2.0]), - # - ([100, 200, 300, 200], 'average', [1.0, 2.5, 4.0, 2.5]), - ([100, 200, 300, 200], 'min', [1.0, 2.0, 4.0, 2.0]), - ([100, 200, 300, 200], 'max', [1.0, 3.0, 4.0, 3.0]), - ([100, 200, 300, 200], 'dense', [1.0, 2.0, 3.0, 2.0]), - ([100, 200, 300, 200], 'ordinal', [1.0, 2.0, 4.0, 3.0]), - # - ([100, 200, 300, 200, 100], 'average', [1.5, 3.5, 5.0, 3.5, 1.5]), - ([100, 200, 300, 200, 100], 'min', [1.0, 3.0, 5.0, 3.0, 1.0]), - ([100, 200, 300, 200, 100], 'max', [2.0, 4.0, 5.0, 4.0, 2.0]), - ([100, 200, 300, 200, 100], 'dense', [1.0, 2.0, 3.0, 2.0, 1.0]), - ([100, 200, 300, 200, 100], 'ordinal', [1.0, 3.0, 5.0, 4.0, 2.0]), - # - ([10] * 30, 'ordinal', np.arange(1.0, 31.0)), -) - - -def test_cases(): - for values, method, expected in _cases: - r = rankdata(values, method=method) - assert_array_equal(r, expected) + _rankdata_cases = ( + # values, method, expected + ([], 'average', []), + ([], 'min', []), + ([], 'max', []), + ([], 'dense', []), + ([], 'ordinal', []), + # + ([100], 'average', [1.0]), + ([100], 'min', [1.0]), + ([100], 'max', [1.0]), + ([100], 'dense', [1.0]), + ([100], 'ordinal', [1.0]), + # + ([100, 100, 100], 'average', [2.0, 2.0, 2.0]), + ([100, 100, 100], 'min', [1.0, 1.0, 1.0]), + ([100, 100, 100], 'max', [3.0, 3.0, 3.0]), + ([100, 100, 100], 'dense', [1.0, 1.0, 1.0]), + ([100, 100, 100], 'ordinal', [1.0, 2.0, 3.0]), + # + ([100, 300, 200], 'average', [1.0, 3.0, 2.0]), + ([100, 300, 200], 'min', [1.0, 3.0, 2.0]), + ([100, 300, 200], 'max', [1.0, 3.0, 2.0]), + ([100, 300, 200], 'dense', [1.0, 3.0, 2.0]), + ([100, 300, 200], 'ordinal', [1.0, 3.0, 2.0]), + # + ([100, 200, 300, 200], 'average', [1.0, 2.5, 4.0, 2.5]), + ([100, 200, 300, 200], 'min', [1.0, 2.0, 4.0, 2.0]), + ([100, 200, 300, 200], 'max', [1.0, 3.0, 4.0, 3.0]), + ([100, 200, 300, 200], 'dense', [1.0, 2.0, 3.0, 2.0]), + ([100, 200, 300, 200], 'ordinal', [1.0, 2.0, 4.0, 3.0]), + # + ([100, 200, 300, 200, 100], 'average', [1.5, 3.5, 5.0, 3.5, 1.5]), + ([100, 200, 300, 200, 100], 'min', [1.0, 3.0, 5.0, 3.0, 1.0]), + ([100, 200, 300, 200, 100], 'max', [2.0, 4.0, 5.0, 4.0, 2.0]), + ([100, 200, 300, 200, 100], 'dense', [1.0, 2.0, 3.0, 2.0, 1.0]), + ([100, 200, 300, 200, 100], 'ordinal', [1.0, 3.0, 5.0, 4.0, 2.0]), + # + ([10] * 30, 'ordinal', np.arange(1.0, 31.0)), + ) + + @pytest.mark.parametrize('case', _rankdata_cases) + def test_cases(self, case, xp): + values, method, expected = case + r = rankdata(xp.asarray(values), method=method) + ref = xp.asarray(expected, dtype=self.desired_dtype(method, xp=xp)) + xp_assert_equal(r, ref) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 9260e64e0704..eb4d1847cc73 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -40,13 +40,13 @@ LinregressResult, _xp_mean, _xp_var, _SimpleChi2) from scipy._lib._util import AxisError from scipy.conftest import skip_xp_invalid_arg -from scipy._lib._array_api import (array_namespace, is_lazy_array, is_numpy, - is_torch, xp_default_dtype, xp_size, SCIPY_ARRAY_API, - make_skip_xp_backends) +from scipy._lib._array_api import (array_namespace, eager_warns, is_lazy_array, + is_numpy, is_torch, xp_default_dtype, xp_size, + SCIPY_ARRAY_API, make_xp_test_case) from scipy._lib._array_api_no_0d import xp_assert_close, xp_assert_equal import scipy._lib.array_api_extra as xpx -from scipy._lib.array_api_extra.testing import lazy_xp_function +lazy_xp_modules = [stats] skip_xp_backends = pytest.mark.skip_xp_backends @@ -74,20 +74,12 @@ TINY = array([1e-12,2e-12,3e-12,4e-12,5e-12,6e-12,7e-12,8e-12,9e-12], float) ROUND = array([0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5,8.5], float) -lazy_xp_modules = [stats] -lazy_xp_function(stats.tmean, static_argnames=("inclusive", "axis")) -lazy_xp_function(stats.tvar, static_argnames=("inclusive", "axis", "ddof")) -lazy_xp_function(stats.tstd, static_argnames=("inclusive", "axis", "ddof")) -lazy_xp_function(stats.tsem, static_argnames=("inclusive", "axis", "ddof")) -lazy_xp_function(stats.tmin, static_argnames=("inclusive", "axis")) -lazy_xp_function(stats.tmax, static_argnames=("inclusive", "axis")) - class TestTrimmedStats: # TODO: write these tests to handle missing values properly dprec = np.finfo(np.float64).precision - @make_skip_xp_backends(stats.tmean) + @make_xp_test_case(stats.tmean) def test_tmean(self, xp): default_dtype = xp_default_dtype(xp) x = xp.asarray(X, dtype=default_dtype) @@ -134,7 +126,7 @@ def test_tmean(self, xp): y_true = [4.5, 10, 17, 21, xp.nan, xp.nan, xp.nan, xp.nan, xp.nan] xp_assert_close(y, xp.asarray(y_true)) - @make_skip_xp_backends(stats.tvar) + @make_xp_test_case(stats.tvar) @pytest.mark.filterwarnings( "ignore:invalid value encountered in divide:RuntimeWarning:dask" ) @@ -166,7 +158,7 @@ def test_tvar(self, xp): xp_assert_close(y[0], xp.asarray(4.666666666666667)) xp_assert_equal(y[1], xp.asarray(xp.nan)) - @make_skip_xp_backends(stats.tstd) + @make_xp_test_case(stats.tstd) def test_tstd(self, xp): x = xp.asarray(X.tolist()) # use default dtype of xp @@ -176,7 +168,7 @@ def test_tstd(self, xp): y = stats.tstd(x, limits=None) xp_assert_close(y, xp.std(x, correction=1)) - @make_skip_xp_backends(stats.tmin) + @make_xp_test_case(stats.tmin) def test_tmin(self, xp): x = xp.arange(10.) xp_assert_equal(stats.tmin(x), xp.asarray(0.)) @@ -215,7 +207,7 @@ def test_tmin_scalar_and_nanpolicy(self, xp): with assert_raises(ValueError, match=msg): stats.tmin(x, nan_policy='foobar') - @make_skip_xp_backends(stats.tmax) + @make_xp_test_case(stats.tmax) def test_tmax(self, xp): x = xp.arange(10.) xp_assert_equal(stats.tmax(x), xp.asarray(9.)) @@ -256,7 +248,7 @@ def test_tmax_scalar_and_nanpolicy(self, xp): with assert_raises(ValueError, match=msg): stats.tmax(x, nan_policy='foobar') - @make_skip_xp_backends(stats.tmin, stats.tmax) + @make_xp_test_case(stats.tmin, stats.tmax) def test_tmin_tmax_int_dtype(self, xp): x = xp.reshape(xp.arange(10, dtype=xp.int16), (2, 5)).T @@ -272,14 +264,14 @@ def test_tmin_tmax_int_dtype(self, xp): xp_assert_equal(stats.tmax(x, upperlimit=3), xp.asarray([3., xp.nan])) @skip_xp_backends(eager_only=True, reason="Only with data-dependent output dtype") - @make_skip_xp_backends(stats.tmin, stats.tmax) + @make_xp_test_case(stats.tmin, stats.tmax) def test_gh_22626(self, xp): # Test that `tmin`/`tmax` returns exact result with outrageously large integers x = xp.arange(2**62, 2**62+10) xp_assert_equal(stats.tmin(x[None, :]), x) xp_assert_equal(stats.tmax(x[None, :]), x) - @make_skip_xp_backends(stats.tsem) + @make_xp_test_case(stats.tsem) def test_tsem(self, xp): x = xp.asarray(X.tolist()) # use default dtype of xp @@ -404,7 +396,7 @@ def test_pROUNDROUND(self): assert_approx_equal(r,1.0) -@make_skip_xp_backends(stats.pearsonr) +@make_xp_test_case(stats.pearsonr) class TestPearsonr: def test_pearsonr_result_attributes(self): res = stats.pearsonr(X, X) @@ -651,6 +643,7 @@ def test_resampling_pvalue(self, method_name, alternative): assert_equal(res2.statistic, res.statistic) assert_equal(res2.pvalue, res.pvalue) + @pytest.mark.slow @pytest.mark.parametrize('alternative', ('less', 'greater', 'two-sided')) def test_bootstrap_ci(self, alternative): rng = np.random.default_rng(2462935790378923) @@ -2148,26 +2141,6 @@ def weigher(x): rng.shuffle(rank) -class TestFindRepeats: - - def test_basic(self): - a = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 5] - message = "`scipy.stats.find_repeats` is deprecated..." - with pytest.deprecated_call(match=message): - res, nums = stats.find_repeats(a) - assert_array_equal(res, [1, 2, 3, 4]) - assert_array_equal(nums, [3, 3, 2, 2]) - - def test_empty_result(self): - # Check that empty arrays are returned when there are no repeats. - for a in [[10, 20, 50, 30, 40], []]: - message = "`scipy.stats.find_repeats` is deprecated..." - with pytest.deprecated_call(match=message): - repeated, counts = stats.find_repeats(a) - assert_array_equal(repeated, []) - assert_array_equal(counts, []) - - class TestRegression: def test_linregressBIGX(self): # W.II.F. Regress BIG on X. @@ -2824,7 +2797,7 @@ def __array__(self, dtype=None, copy=None): stats.mode(np.arange(3, dtype=object)) -@make_skip_xp_backends(stats.sem) +@make_xp_test_case(stats.sem) class TestSEM: testcase = [1., 2., 3., 4.] @@ -2871,7 +2844,7 @@ def test_sem_nan_policy(self, xp): assert_raises(ValueError, stats.sem, x, nan_policy='foobar') -@make_skip_xp_backends(stats.zmap) +@make_xp_test_case(stats.zmap) class TestZmap: @pytest.mark.parametrize( @@ -2970,11 +2943,7 @@ def test_degenerate_input(self, xp): scores = xp.arange(3) compare = xp.ones(3) ref = xp.asarray([-xp.inf, xp.nan, xp.inf]) - warn_ctx = ( - contextlib.nullcontext() if is_lazy_array(scores) - else pytest.warns(RuntimeWarning, match="Precision loss occurred...")) - - with warn_ctx: + with eager_warns(scores, RuntimeWarning, match="Precision loss occurred..."): res = stats.zmap(scores, compare) xp_assert_equal(res, ref) @@ -2985,7 +2954,7 @@ def test_complex_gh22404(self, xp): xp_assert_close(res, ref) -@make_skip_xp_backends(stats.zscore) +@make_xp_test_case(stats.zscore) class TestZscore: def test_zscore(self, xp): # not in R, so tested by using: @@ -3062,31 +3031,20 @@ def test_zscore_nan_raise(self, xp): def test_zscore_constant_input_1d(self, xp): x = xp.asarray([-0.087] * 3) - warn_ctx = ( - contextlib.nullcontext() if is_lazy_array(x) - else pytest.warns(RuntimeWarning, match="Precision loss occurred...")) - - with warn_ctx: + with eager_warns(x, RuntimeWarning, match="Precision loss occurred..."): z = stats.zscore(x) xp_assert_equal(z, xp.full(x.shape, xp.nan)) - @pytest.mark.filterwarnings( - "ignore:The `numpy.copyto` function is not implemented:FutureWarning:dask" - ) @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") def test_zscore_constant_input_2d(self, xp): x = xp.asarray([[10.0, 10.0, 10.0, 10.0], [10.0, 11.0, 12.0, 13.0]]) - warn_ctx = ( - contextlib.nullcontext() if is_lazy_array(x) - else pytest.warns(RuntimeWarning, match="Precision loss occurred...")) - - with warn_ctx: + with eager_warns(x, RuntimeWarning, match="Precision loss occurred..."): z0 = stats.zscore(x, axis=0) xp_assert_close(z0, xp.asarray([[xp.nan, -1.0, -1.0, -1.0], [xp.nan, 1.0, 1.0, 1.0]])) - with warn_ctx: + with eager_warns(x, RuntimeWarning, match="Precision loss occurred..."): z1 = stats.zscore(x, axis=1) xp_assert_equal(z1, xp.stack([xp.asarray([xp.nan, xp.nan, xp.nan, xp.nan]), stats.zscore(x[1, :])])) @@ -3095,9 +3053,9 @@ def test_zscore_constant_input_2d(self, xp): xp_assert_equal(z, xp.reshape(stats.zscore(xp.reshape(x, (-1,))), x.shape)) y = xp.ones((3, 6)) - with warn_ctx: + with eager_warns(y, RuntimeWarning, match="Precision loss occurred..."): z = stats.zscore(y, axis=None) - xp_assert_equal(z, xp.full(y.shape, xp.asarray(xp.nan))) + xp_assert_equal(z, xp.full(y.shape, xp.nan)) @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") def test_zscore_constant_input_2d_nan_policy_omit(self, xp): @@ -3106,17 +3064,14 @@ def test_zscore_constant_input_2d_nan_policy_omit(self, xp): [10.0, 12.0, xp.nan, 10.0]]) s = (3/2)**0.5 s2 = 2**0.5 - warn_ctx = ( - contextlib.nullcontext() if is_lazy_array(x) - else pytest.warns(RuntimeWarning, match="Precision loss occurred...")) - with warn_ctx: + with eager_warns(x, RuntimeWarning, match="Precision loss occurred..."): z0 = stats.zscore(x, nan_policy='omit', axis=0) xp_assert_close(z0, xp.asarray([[xp.nan, -s, -1.0, xp.nan], [xp.nan, 0, 1.0, xp.nan], [xp.nan, s, xp.nan, xp.nan]])) - with warn_ctx: + with eager_warns(x, RuntimeWarning, match="Precision loss occurred..."): z1 = stats.zscore(x, nan_policy='omit', axis=1) xp_assert_close(z1, xp.asarray([[xp.nan, xp.nan, xp.nan, xp.nan], [-s, 0, s, xp.nan], @@ -3169,7 +3124,7 @@ def test_zscore_masked_element_0_gh19039(self, xp): assert_equal(res[1:], np.nan) -@make_skip_xp_backends(stats.gzscore) +@make_xp_test_case(stats.gzscore) class TestGZscore: def test_gzscore_normal_array(self, xp): x = np.asarray([1, 2, 3, 4]) @@ -3498,7 +3453,7 @@ def test_scale(self): assert_raises(ValueError, stats.iqr, x, scale='foobar') -@make_skip_xp_backends(stats.moment) +@make_xp_test_case(stats.moment) class TestMoments: """ Comparison numbers are found using R v.1.5.1 @@ -3686,7 +3641,7 @@ def test_empty_1d(self, xp): xp_assert_equal(res, xp.asarray(xp.nan)) -@make_skip_xp_backends(stats.skew) +@make_xp_test_case(stats.skew) class TestSkew(SkewKurtosisTest): def stat_fun(self, x): return stats.skew(x) @@ -3733,26 +3688,18 @@ def test_skew_propagate_nan(self, xp): def test_skew_constant_value(self, xp): # Skewness of a constant input should be NaN (gh-16061) - a = xp.asarray([-0.27829495]*10) # xp.repeat not currently available - warn_ctx = ( - contextlib.nullcontext() if is_lazy_array(a) - else pytest.warns(RuntimeWarning, match="Precision loss occurred...")) + a = xp.repeat(xp.asarray([-0.27829495]), 10) - with warn_ctx: + with eager_warns(a, RuntimeWarning, match="Precision loss occurred"): xp_assert_equal(stats.skew(a), xp.asarray(xp.nan)) - with warn_ctx: xp_assert_equal(stats.skew(a*2.**50), xp.asarray(xp.nan)) - with warn_ctx: xp_assert_equal(stats.skew(a/2.**50), xp.asarray(xp.nan)) - with warn_ctx: xp_assert_equal(stats.skew(a, bias=False), xp.asarray(xp.nan)) - # # similarly, from gh-11086: - a = xp.asarray([14.3]*7) - with warn_ctx: + # # similarly, from gh-11086: + a = xp.asarray([14.3]*7) xp_assert_equal(stats.skew(a), xp.asarray(xp.nan)) - a = 1. + xp.arange(-3., 4)*1e-16 - with warn_ctx: + a = 1. + xp.arange(-3., 4)*1e-16 xp_assert_equal(stats.skew(a), xp.asarray(xp.nan)) @skip_xp_backends(eager_only=True) @@ -3793,7 +3740,7 @@ def skewness(a, axis, bias): xp_assert_close(res, ref) -@make_skip_xp_backends(stats.kurtosis) +@make_xp_test_case(stats.kurtosis) class TestKurtosis(SkewKurtosisTest): def stat_fun(self, x): return stats.kurtosis(x) @@ -3854,17 +3801,10 @@ def test_kurtosis_propagate_nan(self): def test_kurtosis_constant_value(self, xp): # Kurtosis of a constant input should be NaN (gh-16061) a = xp.asarray([-0.27829495]*10) - warn_ctx = ( - contextlib.nullcontext() if is_lazy_array(a) - else pytest.warns(RuntimeWarning, match="Precision loss occurred...")) - - with warn_ctx: + with eager_warns(a, RuntimeWarning, match="Precision loss occurred"): assert xp.isnan(stats.kurtosis(a, fisher=False)) - with warn_ctx: assert xp.isnan(stats.kurtosis(a * float(2**50), fisher=False)) - with warn_ctx: assert xp.isnan(stats.kurtosis(a / float(2**50), fisher=False)) - with warn_ctx: assert xp.isnan(stats.kurtosis(a, fisher=False, bias=False)) @pytest.mark.parametrize('axis', [-1, 0, 2, None]) @@ -3931,7 +3871,7 @@ def ttest_data_axis_strategy(draw): return data, axis -@make_skip_xp_backends(stats.ttest_1samp) +@make_xp_test_case(stats.ttest_1samp) class TestStudentTest: # Preserving original test cases. # Recomputed statistics and p-values with R t.test, e.g. @@ -4251,7 +4191,7 @@ def test_nd(self, shape): ] -@make_skip_xp_backends(stats.power_divergence) +@make_xp_test_case(stats.power_divergence) class TestPowerDivergence: def check_power_divergence(self, f_obs, f_exp, ddof, axis, lambda_, @@ -4448,7 +4388,7 @@ def test_power_divergence_against_cressie_read_data(self, xp): xp_assert_close(stat, expected_stat, rtol=5e-3) -@make_skip_xp_backends(stats.chisquare) +@make_xp_test_case(stats.chisquare) class TestChisquare: def test_chisquare_12282a(self, xp): # Currently `chisquare` is implemented via power_divergence @@ -5210,13 +5150,13 @@ def _stats(x, axis=0): return _stats(x1, axis) + _stats(x2, axis) -@make_skip_xp_backends(stats.ttest_ind, stats.ttest_ind_from_stats) +@make_xp_test_case(stats.ttest_ind, stats.ttest_ind_from_stats) def test_ttest_ind(xp): # regression test tr = xp.asarray(1.0912746897927283) pr = xp.asarray(0.27647818616351882) - tr_2D = xp.asarray([tr, -tr]) - pr_2D = xp.asarray([pr, pr]) + tr_2D = xp.stack([tr, -tr]) + pr_2D = xp.stack([pr, pr]) rvs1 = xp.linspace(5, 105, 100) rvs2 = xp.linspace(1, 100, 100) @@ -5922,7 +5862,7 @@ def test_trim_bounds_error(self, trim): stats.ttest_ind([1, 2], [2, 1], trim=trim) -@make_skip_xp_backends(stats.ttest_ind) +@make_xp_test_case(stats.ttest_ind) class Test_ttest_CI: # indices in order [alternative={two-sided, less, greater}, # equal_var={False, True}, trim={0, 0.2}] @@ -6017,9 +5957,9 @@ def test__broadcast_concatenate(): assert b[i, j, k, l - a.shape[-3], m, n] == c[i, j, k, l, m, n] -@make_skip_xp_backends(stats.ttest_ind) +@make_xp_test_case(stats.ttest_ind) class TestTTestInd: - @make_skip_xp_backends(stats.ttest_ind_from_stats) + @make_xp_test_case(stats.ttest_ind_from_stats) def test_ttest_ind_with_uneq_var(self, xp): # check vs. R `t.test`, e.g. # options(digits=20) @@ -6057,8 +5997,8 @@ def test_ttest_ind_with_uneq_var(self, xp): tr_uneq_n = xp.asarray(0.66745638708050492) pr = xp.asarray(0.27647831993021388) pr_uneq_n = xp.asarray(0.50873585065616544) - tr_2D = xp.asarray([tr, -tr]) - pr_2D = xp.asarray([pr, pr]) + tr_2D = xp.stack([tr, -tr]) + pr_2D = xp.stack([pr, pr]) rvs3 = xp.linspace(1, 100, 25) rvs2 = xp.linspace(1, 100, 100) @@ -6110,11 +6050,8 @@ def test_ttest_ind_zero_division(self, xp): # test zero division problem x = xp.zeros(3) y = xp.ones(3) - warn_ctx = ( - contextlib.nullcontext() if is_lazy_array(x) - else pytest.warns(RuntimeWarning, match="Precision loss occurred...")) - with warn_ctx: + with eager_warns(x, RuntimeWarning, match="Precision loss occurred"): t, p = stats.ttest_ind(x, y, equal_var=False) xp_assert_equal(t, xp.asarray(-xp.inf)) @@ -6152,10 +6089,6 @@ def test_ttest_ind_nan_2nd_arg(self): assert_allclose(r2, (-2.5354627641855498, 0.052181400457057901), atol=1e-15) - # internal dask warning we can't do anything about - @pytest.mark.filterwarnings( - "ignore:The `numpy.copyto` function is not implemented:FutureWarning:dask" - ) def test_ttest_ind_empty_1d_returns_nan(self, xp): # Two empty inputs should return a TtestResult containing nan # for both values. @@ -6169,10 +6102,6 @@ def test_ttest_ind_empty_1d_returns_nan(self, xp): xp_assert_equal(res.statistic, NaN) xp_assert_equal(res.pvalue, NaN) - # internal dask warning we can't do anything about - @pytest.mark.filterwarnings( - "ignore:The `numpy.copyto` function is not implemented:FutureWarning:dask" - ) @pytest.mark.parametrize('b, expected_shape', [(np.empty((1, 5, 0)), (3, 5)), (np.empty((1, 0, 0)), (3, 0))]) @@ -6181,7 +6110,7 @@ def test_ttest_ind_axis_size_zero(self, b, expected_shape, xp): # The results should be arrays containing nan with shape # given by the broadcast nonaxis dimensions. a = xp.empty((3, 1, 0)) - b = xp.asarray(b) + b = xp.asarray(b, dtype=a.dtype) with np.testing.suppress_warnings() as sup: # first case should warn, second shouldn't? sup.filter(SmallSampleWarning, too_small_nd_not_omit) @@ -6217,7 +6146,7 @@ def test_ttest_ind_nonaxis_size_zero_different_lengths(self, xp): assert res.pvalue.shape == (5, 0) -@make_skip_xp_backends(stats.ttest_ind_from_stats) +@make_xp_test_case(stats.ttest_ind_from_stats) class TestTTestIndFromStats: @pytest.mark.skip_xp_backends(np_only=True, reason="Other backends don't like integers") @@ -6282,7 +6211,7 @@ def _convert_pvalue_alternative(t, p, alt, xp): @pytest.mark.slow @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") -@make_skip_xp_backends(stats.ttest_1samp) +@make_xp_test_case(stats.ttest_1samp) def test_ttest_1samp_new(xp): n1, n2, n3 = (10, 15, 20) rvn1 = stats.norm.rvs(loc=5, scale=10, size=(n1, n2, n3)) @@ -6366,7 +6295,7 @@ def test_ttest_1samp_new_omit(xp): xp_assert_close(t, tr) -@make_skip_xp_backends(stats.ttest_1samp) +@make_xp_test_case(stats.ttest_1samp) @pytest.mark.skip_xp_backends('jax.numpy', reason='Generic stdtrit mutates array.') def test_ttest_1samp_popmean_array(xp): # when popmean.shape[axis] != 1, raise an error @@ -6397,7 +6326,7 @@ def test_ttest_1samp_popmean_array(xp): xp_assert_close(res.pvalue, ref) -@make_skip_xp_backends(stats.describe) +@make_xp_test_case(stats.describe) class TestDescribe: @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") def test_describe_scalar(self, xp): @@ -6588,7 +6517,7 @@ def test_nan(self, xp): xp_assert_equal(res.pvalue, NaN) -@make_skip_xp_backends(stats.skewtest) +@make_xp_test_case(stats.skewtest) class TestSkewTest(NormalityTests): test_name = 'skewtest' case_ref = (1.98078826090875881, 0.04761502382843208) # statistic, pvalue @@ -6616,7 +6545,7 @@ def test_skewtest_too_few_observations(self, xp): xp_assert_equal(res.pvalue, NaN) -@make_skip_xp_backends(stats.kurtosistest) +@make_xp_test_case(stats.kurtosistest) class TestKurtosisTest(NormalityTests): test_name = 'kurtosistest' case_ref = (-0.01403734404759738, 0.98880018772590561) # statistic, pvalue @@ -6650,7 +6579,7 @@ def test_kurtosistest_too_few_observations(self, xp): xp_assert_equal(res.pvalue, NaN) -@make_skip_xp_backends(stats.normaltest) +@make_xp_test_case(stats.normaltest) class TestNormalTest(NormalityTests): test_name = 'normaltest' case_ref = (3.92371918158185551, 0.14059672529747502) # statistic, pvalue @@ -6690,7 +6619,7 @@ def test_input_validation(self): stats.ranksums(self.x, self.y, alternative='foobar') -@make_skip_xp_backends(stats.jarque_bera) +@make_xp_test_case(stats.jarque_bera) class TestJarqueBera: def test_jarque_bera_against_R(self, xp): # library(tseries) @@ -6740,8 +6669,8 @@ def test_axis(self, xp): res = stats.jarque_bera(x, axis=1) s0, p0 = stats.jarque_bera(x[0, :]) s1, p1 = stats.jarque_bera(x[1, :]) - xp_assert_close(res.statistic, xp.asarray([s0, s1])) - xp_assert_close(res.pvalue, xp.asarray([p0, p1])) + xp_assert_close(res.statistic, xp.stack([s0, s1])) + xp_assert_close(res.pvalue, xp.stack([p0, p1])) resT = stats.jarque_bera(x.T, axis=0) xp_assert_close(res.statistic, resT.statistic) @@ -6962,7 +6891,7 @@ def check_equal_pmean(*args, **kwargs): return check_equal_xmean(*args, mean_fun=stats.pmean, **kwargs) -@make_skip_xp_backends(stats.hmean) +@make_xp_test_case(stats.hmean) class TestHMean: @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") def test_0(self, xp): @@ -7079,7 +7008,7 @@ def test_weights_masked_1d_array(self, xp): dtype=np.float64, xp=xp) -@make_skip_xp_backends(stats.gmean) +@make_xp_test_case(stats.gmean) class TestGMean: @pytest.mark.filterwarnings( "ignore:divide by zero encountered in log:RuntimeWarning:dask" @@ -7193,7 +7122,7 @@ def test_weights_masked_1d_array(self, xp): dtype=np.float64, xp=xp) -@make_skip_xp_backends(stats.pmean) +@make_xp_test_case(stats.pmean) class TestPMean: def pmean_reference(a, p): @@ -7313,7 +7242,7 @@ def fun(a, axis, weights): check_equal_pmean(a, p, desired, axis=axis, weights=weights, rtol=1e-5, xp=xp) -@make_skip_xp_backends(stats.gstd) +@make_xp_test_case(stats.gstd) class TestGSTD: # must add 1 as `gstd` is only defined for positive values array_1d = (np.arange(2 * 3 * 4) + 1).tolist() @@ -8225,7 +8154,7 @@ def test_no_args_gh20661(self): -@make_skip_xp_backends(stats.combine_pvalues) +@make_xp_test_case(stats.combine_pvalues) class TestCombinePvalues: # Reference values computed using the following R code: # options(digits=16) @@ -8288,7 +8217,7 @@ def test_monotonicity(self, variant, method, xp): pvaluess = xp.sort(xp.asarray(rng.uniform(0, 1, size=(m, n))), axis=0) combined_pvalues = xp.asarray([ - stats.combine_pvalues(pvaluess[i, :], method=method)[1] + float(stats.combine_pvalues(pvaluess[i, :], method=method)[1]) for i in range(pvaluess.shape[0]) ]) assert xp.all(combined_pvalues[1:] - combined_pvalues[:-1] >= 0) diff --git a/scipy/stats/tests/test_variation.py b/scipy/stats/tests/test_variation.py index 5fc906530a07..222620c8befd 100644 --- a/scipy/stats/tests/test_variation.py +++ b/scipy/stats/tests/test_variation.py @@ -14,6 +14,7 @@ skip_xp_backends = pytest.mark.skip_xp_backends +@skip_xp_backends("dask.array", reason='data-apis/array-api-extra#196') @skip_xp_backends('torch', reason='data-apis/array-api-compat#271') class TestVariation: """ @@ -133,10 +134,6 @@ def test_return_nan(self, x, xp): y = variation(x) xp_assert_equal(y, xp.asarray(xp.nan, dtype=x.dtype)) - # internal dask warning we can't do anything about - @pytest.mark.filterwarnings( - "ignore:The `numpy.copyto` function is not implemented:FutureWarning:dask" - ) @pytest.mark.parametrize('axis, expected', [(0, []), (1, [np.nan]*3), (None, np.nan)]) def test_2d_size_zero_with_axis(self, axis, expected, xp): diff --git a/scipy/_lib/boost_math b/subprojects/boost_math/math similarity index 100% rename from scipy/_lib/boost_math rename to subprojects/boost_math/math diff --git a/subprojects/boost_math/meson.build b/subprojects/boost_math/meson.build new file mode 100644 index 000000000000..6b16ff260255 --- /dev/null +++ b/subprojects/boost_math/meson.build @@ -0,0 +1,11 @@ +project('boost-math', + version : '1.88.0', + meson_version: '>= 1.5.0', +) + +fs = import('fs') +if not fs.exists('math/README.md') + error('Missing the `boost_math` submodule! Run `git submodule update --init` to fix this.') +endif + +boost_math_dep = declare_dependency(include_directories: 'math/include') diff --git a/scipy/spatial/qhull_src/Announce.txt b/subprojects/qhull_r/libqhull_r/Announce.txt similarity index 96% rename from scipy/spatial/qhull_src/Announce.txt rename to subprojects/qhull_r/libqhull_r/Announce.txt index b2333a5861f5..704cb5259f49 100644 --- a/scipy/spatial/qhull_src/Announce.txt +++ b/subprojects/qhull_r/libqhull_r/Announce.txt @@ -1,5 +1,5 @@ - Qhull 2019.1 2019/06/21 + Qhull 2020.2 2020/08/31 (8.0.2) http://www.qhull.org http://github.com/qhull/qhull/wiki @@ -17,11 +17,12 @@ input transformations, randomization, tracing, multiple output formats, and execution statistics. The program can be called from within your application. You can view the results in 2-d, 3-d and 4-d with Geomview. -To download Qhull: +Download Qhull: + http://www.qhull.org/download git@github.com:qhull/qhull.git -Download qhull-96.ps for: +Reference: Barber, C. B., D.P. Dobkin, and H.T. Huhdanpaa, "The Quickhull Algorithm for Convex Hulls," ACM Trans. on diff --git a/scipy/spatial/qhull_src/COPYING.txt b/subprojects/qhull_r/libqhull_r/COPYING.txt similarity index 80% rename from scipy/spatial/qhull_src/COPYING.txt rename to subprojects/qhull_r/libqhull_r/COPYING.txt index 4ac02a07f45d..122a00a4fa99 100644 --- a/scipy/spatial/qhull_src/COPYING.txt +++ b/subprojects/qhull_r/libqhull_r/COPYING.txt @@ -1,4 +1,4 @@ - Qhull, Copyright (c) 1993-2019 + Qhull, Copyright (c) 1993-2020 C.B. Barber Arlington, MA @@ -13,9 +13,10 @@ email: qhull@qhull.org This software includes Qhull from C.B. Barber and The Geometry Center. -Qhull is copyrighted as noted above. Qhull is free software and may -be obtained via http from www.qhull.org. It may be freely copied, modified, -and redistributed under the following conditions: +Files derived from Qhull 1.0 are copyrighted by the Geometry Center. The +remaining files are copyrighted by C.B. Barber. Qhull is free software +and may be obtained via http from www.qhull.org. It may be freely copied, +modified, and redistributed under the following conditions: 1. All copyright notices must remain intact in all files. diff --git a/scipy/spatial/qhull_src/src/geom2_r.c b/subprojects/qhull_r/libqhull_r/geom2_r.c similarity index 99% rename from scipy/spatial/qhull_src/src/geom2_r.c rename to subprojects/qhull_r/libqhull_r/geom2_r.c index e8e189a80872..9e0f997f6327 100644 --- a/scipy/spatial/qhull_src/src/geom2_r.c +++ b/subprojects/qhull_r/libqhull_r/geom2_r.c @@ -7,9 +7,9 @@ see qh-geom_r.htm and geom_r.h - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/geom2_r.c#15 $$Change: 2712 $ - $DateTime: 2019/06/28 12:57:00 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/geom2_r.c#17 $$Change: 3037 $ + $DateTime: 2020/09/03 17:28:32 $$Author: bbarber $ frequently used code goes into geom_r.c */ @@ -27,7 +27,8 @@ notes: qh_free the returned points to avoid a memory leak */ -coordT *qh_copypoints(qhT *qh, coordT *points, int numpoints, int dimension) { +coordT *qh_copypoints(qhT *qh, coordT *points, int numpoints, int dimension) +{ int size; coordT *newpoints; diff --git a/scipy/spatial/qhull_src/src/geom_r.c b/subprojects/qhull_r/libqhull_r/geom_r.c similarity index 99% rename from scipy/spatial/qhull_src/src/geom_r.c rename to subprojects/qhull_r/libqhull_r/geom_r.c index a7a1cb0015f2..22faead499fb 100644 --- a/scipy/spatial/qhull_src/src/geom_r.c +++ b/subprojects/qhull_r/libqhull_r/geom_r.c @@ -6,9 +6,9 @@ see qh-geom_r.htm and geom_r.h - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/geom_r.c#4 $$Change: 2712 $ - $DateTime: 2019/06/28 12:57:00 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/geom_r.c#5 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ infrequent code goes into geom2_r.c */ diff --git a/scipy/spatial/qhull_src/src/geom_r.h b/subprojects/qhull_r/libqhull_r/geom_r.h similarity index 97% rename from scipy/spatial/qhull_src/src/geom_r.h rename to subprojects/qhull_r/libqhull_r/geom_r.h index a5f76452f5f1..f3f8ee814002 100644 --- a/scipy/spatial/qhull_src/src/geom_r.h +++ b/subprojects/qhull_r/libqhull_r/geom_r.h @@ -6,9 +6,9 @@ see qh-geom_r.htm and geom_r.c - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/geom_r.h#1 $$Change: 2661 $ - $DateTime: 2019/05/24 20:09:58 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/geom_r.h#2 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #ifndef qhDEFgeom diff --git a/scipy/spatial/qhull_src/src/global_r.c b/subprojects/qhull_r/libqhull_r/global_r.c similarity index 99% rename from scipy/spatial/qhull_src/src/global_r.c rename to subprojects/qhull_r/libqhull_r/global_r.c index 3e6919f5c422..04b9b4d74ec2 100644 --- a/scipy/spatial/qhull_src/src/global_r.c +++ b/subprojects/qhull_r/libqhull_r/global_r.c @@ -11,9 +11,9 @@ see qhull_ra.h for internal functions - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/global_r.c#12 $$Change: 2712 $ - $DateTime: 2019/06/28 12:57:00 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/global_r.c#19 $$Change: 3037 $ + $DateTime: 2020/09/03 17:28:32 $$Author: bbarber $ */ #include "qhull_ra.h" @@ -33,14 +33,14 @@ change date: Changes.txt, Announce.txt, index.htm, README.txt, qhull-news.html, Eudora signatures, CMakeLists.txt change version: README.txt, qh-get.htm, File_id.diz, Makefile.txt, CMakeLists.txt - check that CmakeLists @version is the same as qh_version2 + check that CMakeLists.txt @version is the same as qh_version2 change year: Copying.txt check download size recompile user_eg_r.c, rbox_r.c, libqhull_r.c, qconvex_r.c, qdelaun_r.c qvoronoi_r.c, qhalf_r.c, testqset_r.c */ -const char qh_version[]= "2019.1.r 2019/06/21"; -const char qh_version2[]= "qhull_r 7.3.2 (2019.1.r 2019/06/21)"; +const char qh_version[]= "2020.2.r 2020/08/31"; +const char qh_version2[]= "qhull_r 8.0.2 (2020.2.r 2020/08/31)"; /*--------------------------------- @@ -1517,7 +1517,7 @@ void qh_initflags(qhT *qh, char *command) { lastwarning= command; } if (lastwarning && !qh->ALLOWwarning) { - qh_fprintf(qh, qh->ferr, 6035, "qhull option error: see previous warnings, use 'Qw' to override: '%s' (last offset %d)\n", + qh_fprintf(qh, qh->ferr, 6035, "qhull option error: see previous warnings, use 'Qw' to override: '%s' (last offset %d)\n", command, (int)(lastwarning-command)); qh_errexit(qh, qh_ERRinput, NULL, NULL); } @@ -1626,7 +1626,7 @@ void qh_initqhull_globals(qhT *qh, coordT *points, int numpoints, int dim, boolT #endif } if (qh->TRIangulate && qh->JOGGLEmax < REALmax/2 && !qh->PREmerge && !qh->POSTmerge && qh->PRINTprecision) - qh_fprintf(qh, qh->ferr, 7038, "qhull option warning: joggle ('QJ') produces simplicial output (i.e., triangles in 2-D). Unless merging is requested, option 'Qt' has no effect\n"); + qh_fprintf(qh, qh->ferr, 7038, "qhull option warning: joggle ('QJ') produces simplicial output (i.e., triangles in 2-D). Unless merging is requested, option 'Qt' has no effect\n"); if (qh->JOGGLEmax < REALmax/2 && qh->DELAUNAY && !qh->SCALEinput && !qh->SCALElast) { qh->SCALElast= True; qh_option(qh, "Qbbound-last-qj", NULL, NULL); @@ -2099,14 +2099,14 @@ void qh_initthresholds(qhT *qh, char *command) { if (!isdigit(*s)) { qh_fprintf(qh, qh->ferr, 7047, "qhull option warning: no dimension given for Qhull option 'Q%c'\n", key); - lastwarning= lastoption; + lastwarning= lastoption; continue; } idx= qh_strtol(s, &s); if (idx >= maxdim) { qh_fprintf(qh, qh->ferr, 7048, "qhull option warning: dimension %d for Qhull option 'Q%c' is >= %d. Ignored\n", idx, key, maxdim); - lastwarning= lastoption; + lastwarning= lastoption; continue; } if (*s == ':') { @@ -2141,7 +2141,7 @@ void qh_initthresholds(qhT *qh, char *command) { qh->GOODthreshold= True; } if (lastwarning && !qh->ALLOWwarning) { - qh_fprintf(qh, qh->ferr, 6036, "qhull option error: see previous warnings, use 'Qw' to override: '%s' (last offset %d)\n", + qh_fprintf(qh, qh->ferr, 6036, "qhull option error: see previous warnings, use 'Qw' to override: '%s' (last offset %d)\n", command, (int)(lastwarning-command)); qh_errexit(qh, qh_ERRinput, NULL, NULL); } @@ -2205,7 +2205,7 @@ void qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeT last_errcode= 6254; } if (last_errcode) { - qh_fprintf_stderr(6259, "qhull internal error (qh_lib_check): Cannot continue due to QH%d. '%s' is not reentrant (e.g., qhull.so) or out-of-date. Exit with %d\n", + qh_fprintf_stderr(6259, "qhull internal error (qh_lib_check): Cannot continue due to QH%d. '%s' is not reentrant (e.g., qhull.so) or out-of-date. Exit with %d\n", last_errcode, qh_version2, last_errcode - 6200); qh_exit(last_errcode - 6200); /* can not use qh_errexit(), must be less than 255 */ } diff --git a/scipy/spatial/qhull_src/src/io_r.c b/subprojects/qhull_r/libqhull_r/io_r.c similarity index 97% rename from scipy/spatial/qhull_src/src/io_r.c rename to subprojects/qhull_r/libqhull_r/io_r.c index 4c05920da901..a80a5b14a47c 100644 --- a/scipy/spatial/qhull_src/src/io_r.c +++ b/subprojects/qhull_r/libqhull_r/io_r.c @@ -13,9 +13,9 @@ unix_r.c and user_r.c are the only callers of io_r.c functions This allows the user to avoid loading io_r.o from qhull.a - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/io_r.c#7 $$Change: 2683 $ - $DateTime: 2019/06/14 16:05:16 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/io_r.c#12 $$Change: 2965 $ + $DateTime: 2020/06/04 15:37:41 $$Author: bbarber $ */ #include "qhull_ra.h" @@ -383,6 +383,7 @@ pointT *qh_detvnorm(qhT *qh, vertexT *vertex, vertexT *vertexA, setT *centers, r normal= gmcoord; qh_sethyperplane_gauss(qh, dim, qh->gm_row, point0, True, normal, &offset, &nearzero); + /* nearzero is true for axis-parallel hyperplanes (e.g., a bounding box). Should detect degenerate hyperplanes. See 'Tv' check following */ if (qh->GOODvertexp == vertexA->point) inpoint= vertexA->point; else @@ -1016,43 +1017,75 @@ setT *qh_markvoronoi(qhT *qh, facetT *facetlist, setT *facets, boolT printall, b >-------------------------------- qh_order_vertexneighbors(qh, vertex ) - order facet neighbors of a 2-d or 3-d vertex by adjacency + order facet neighbors of vertex by 2-d (orientation), 3-d (adjacency), or n-d (f.visitid,id) notes: - does not orient the neighbors - - design: + error if qh_vertexneighbors not called beforehand + only 2-d orients the neighbors + for 4-d and higher + set or clear f.visitid for qh_compare_facetvisit + for example, use qh_markvoronoi (e.g., qh_printvornoi) or qh_countfacets (e.g., qh_printvneighbors) + + design (2-d): + see qh_printextremes_2d + design (3-d): initialize a new neighbor set with the first facet in vertex->neighbors while vertex->neighbors non-empty select next neighbor in the previous facet's neighbor set set vertex->neighbors to the new neighbor set + design (n-d): + qsort by f.visitid, or f.facetid (qh_compare_facetvisit) + facet_id is negated (sorted before visit_id facets) */ void qh_order_vertexneighbors(qhT *qh, vertexT *vertex) { setT *newset; - facetT *facet, *neighbor, **neighborp; + facetT *facet, *facetA, *facetB, *neighbor, **neighborp; + vertexT *vertexA; + int numneighbors; - trace4((qh, qh->ferr, 4018, "qh_order_vertexneighbors: order neighbors of v%d for 3-d\n", vertex->id)); - newset= qh_settemp(qh, qh_setsize(qh, vertex->neighbors)); - facet= (facetT *)qh_setdellast(vertex->neighbors); - qh_setappend(qh, &newset, facet); - while (qh_setsize(qh, vertex->neighbors)) { - FOREACHneighbor_(vertex) { - if (qh_setin(facet->neighbors, neighbor)) { - qh_setdel(vertex->neighbors, neighbor); - qh_setappend(qh, &newset, neighbor); - facet= neighbor; - break; - } + trace4((qh, qh->ferr, 4018, "qh_order_vertexneighbors: order facet neighbors of v%d by 2-d (orientation), 3-d (adjacency), or n-d (f.visitid,id)\n", vertex->id)); + if (!qh->VERTEXneighbors) { + qh_fprintf(qh, qh->ferr, 6428, "qhull internal error (qh_order_vertexneighbors): call qh_vertexneighbors before calling qh_order_vertexneighbors\n"); + qh_errexit(qh, qh_ERRqhull, NULL, NULL); + } + if (qh->hull_dim == 2) { + facetA= SETfirstt_(vertex->neighbors, facetT); + if (facetA->toporient ^ qh_ORIENTclock) + vertexA= SETfirstt_(facetA->vertices, vertexT); + else + vertexA= SETsecondt_(facetA->vertices, vertexT); + if (vertexA!=vertex) { + facetB= SETsecondt_(vertex->neighbors, facetT); + SETfirst_(vertex->neighbors)= facetB; + SETsecond_(vertex->neighbors)= facetA; } - if (!neighbor) { - qh_fprintf(qh, qh->ferr, 6066, "qhull internal error (qh_order_vertexneighbors): no neighbor of v%d for f%d\n", - vertex->id, facet->id); - qh_errexit(qh, qh_ERRqhull, facet, NULL); + }else if (qh->hull_dim == 3) { + newset= qh_settemp(qh, qh_setsize(qh, vertex->neighbors)); + facet= (facetT *)qh_setdellast(vertex->neighbors); + qh_setappend(qh, &newset, facet); + while (qh_setsize(qh, vertex->neighbors)) { + FOREACHneighbor_(vertex) { + if (qh_setin(facet->neighbors, neighbor)) { + qh_setdel(vertex->neighbors, neighbor); + qh_setappend(qh, &newset, neighbor); + facet= neighbor; + break; + } + } + if (!neighbor) { + qh_fprintf(qh, qh->ferr, 6066, "qhull internal error (qh_order_vertexneighbors): no neighbor of v%d for f%d\n", + vertex->id, facet->id); + qh_errexit(qh, qh_ERRqhull, facet, NULL); + } } + qh_setfree(qh, &vertex->neighbors); + qh_settemppop(qh); + vertex->neighbors= newset; + }else { /* qh.hull_dim >= 4 */ + numneighbors= qh_setsize(qh, vertex->neighbors); + qsort(SETaddr_(vertex->neighbors, facetT), (size_t)numneighbors, + sizeof(facetT *), qh_compare_facetvisit); } - qh_setfree(qh, &vertex->neighbors); - qh_settemppop(qh); - vertex->neighbors= newset; } /* order_vertexneighbors */ /*-neighbors); qh_fprintf(qh, fp, 9249, "%d", numneighbors); - if (qh->hull_dim == 3) - qh_order_vertexneighbors(qh, vertex); - else if (qh->hull_dim >= 4) - qsort(SETaddr_(vertex->neighbors, facetT), (size_t)numneighbors, - sizeof(facetT *), qh_compare_facetvisit); + qh_order_vertexneighbors(qh, vertex); FOREACHneighbor_(vertex) qh_fprintf(qh, fp, 9250, " %d", neighbor->visitid ? neighbor->visitid - 1 : 0 - neighbor->id); @@ -3467,12 +3496,7 @@ void qh_printvoronoi(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT numneighbors= 0; numinf=0; if (vertex) { - if (qh->hull_dim == 3) - qh_order_vertexneighbors(qh, vertex); - else if (qh->hull_dim >= 4) - qsort(SETaddr_(vertex->neighbors, facetT), - (size_t)qh_setsize(qh, vertex->neighbors), - sizeof(facetT *), qh_compare_facetvisit); + qh_order_vertexneighbors(qh, vertex); FOREACHneighbor_(vertex) { if (neighbor->visitid == 0) numinf= 1; diff --git a/scipy/spatial/qhull_src/src/io_r.h b/subprojects/qhull_r/libqhull_r/io_r.h similarity index 98% rename from scipy/spatial/qhull_src/src/io_r.h rename to subprojects/qhull_r/libqhull_r/io_r.h index 94ea9c1329b0..eb3c75149238 100644 --- a/scipy/spatial/qhull_src/src/io_r.h +++ b/subprojects/qhull_r/libqhull_r/io_r.h @@ -6,9 +6,9 @@ see README, libqhull_r.h and io_r.c - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/io_r.h#2 $$Change: 2671 $ - $DateTime: 2019/06/06 11:24:01 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/io_r.h#3 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #ifndef qhDEFio diff --git a/scipy/spatial/qhull_src/src/libqhull_r.c b/subprojects/qhull_r/libqhull_r/libqhull_r.c similarity index 99% rename from scipy/spatial/qhull_src/src/libqhull_r.c rename to subprojects/qhull_r/libqhull_r/libqhull_r.c index 7754fa5fad35..0d41d7be0539 100644 --- a/scipy/spatial/qhull_src/src/libqhull_r.c +++ b/subprojects/qhull_r/libqhull_r/libqhull_r.c @@ -10,9 +10,9 @@ see qhull_ra.h for internal functions - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/libqhull_r.c#16 $$Change: 2712 $ - $DateTime: 2019/06/28 12:57:00 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/libqhull_r.c#17 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #include "qhull_ra.h" diff --git a/scipy/spatial/qhull_src/src/libqhull_r.h b/subprojects/qhull_r/libqhull_r/libqhull_r.h similarity index 99% rename from scipy/spatial/qhull_src/src/libqhull_r.h rename to subprojects/qhull_r/libqhull_r/libqhull_r.h index a48dd4e585dd..376c1e20ff41 100644 --- a/scipy/spatial/qhull_src/src/libqhull_r.h +++ b/subprojects/qhull_r/libqhull_r/libqhull_r.h @@ -6,9 +6,9 @@ see qh-qhull_r.htm, qhull_ra.h - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/libqhull_r.h#13 $$Change: 2714 $ - $DateTime: 2019/06/28 16:16:13 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/libqhull_r.h#16 $$Change: 3037 $ + $DateTime: 2020/09/03 17:28:32 $$Author: bbarber $ includes function prototypes for libqhull_r.c, geom_r.c, global_r.c, io_r.c, user_r.c @@ -188,7 +188,7 @@ typedef enum {qh_PRINTnone= 0, #define qh_ERRother 6 /* other error detected */ #define qh_ERRtopology 7 /* topology error, maybe due to nearly adjacent vertices, calls qh_printhelp_topology */ #define qh_ERRwide 8 /* wide facet error, maybe due to nearly adjacent vertices, calls qh_printhelp_wide */ -#define qh_ERRdebug 9 /* qh_errexit from debugging code */ +#define qh_ERRdebug 9 /* qh_errexit from debugging code */ /*---------------------------------- @@ -424,7 +424,7 @@ struct vertexT { initialized in io_r.c or after first merge qh_update_vertices for qh_addpoint or qh_triangulate updated by merges - qh_order_vertexneighbors for 2-d and 3-d */ + qh_order_vertexneighbors by 2-d (orientation) 3-d (adjacency), n-d (f.visitid,id) */ unsigned int id; /* unique identifier, 1..qh.vertex_id, 0 for sentinel, printed as 'r%d' */ unsigned int visitid; /* for use with qh.vertex_visit, size must match */ flagT seen:1; /* used to perform operations only once */ @@ -851,6 +851,8 @@ struct qhT { int rbox_isinteger; double rbox_out_offset; void * cpp_object; /* C++ pointer. Currently used by RboxPoints.qh_fprintf_rbox */ + void * cpp_other; /* C++ pointer. Reserved for other users */ + void * cpp_user; /* C++ pointer. Currently used by QhullUser.qh_fprintf */ /* Last, otherwise zero'd by qh_initqhull_start2 (global_r.c */ qhmemT qhmem; /* Qhull managed memory (mem_r.h) */ diff --git a/scipy/spatial/qhull_src/src/mem_r.c b/subprojects/qhull_r/libqhull_r/mem_r.c similarity index 99% rename from scipy/spatial/qhull_src/src/mem_r.c rename to subprojects/qhull_r/libqhull_r/mem_r.c index 1f865f4f69b9..7d5509eb4f4d 100644 --- a/scipy/spatial/qhull_src/src/mem_r.c +++ b/subprojects/qhull_r/libqhull_r/mem_r.c @@ -29,9 +29,9 @@ qh-mem_r.htm and mem_r.h global_r.c (qh_initbuffers) for an example of using mem_r.c - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/mem_r.c#6 $$Change: 2711 $ - $DateTime: 2019/06/27 22:34:56 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/mem_r.c#7 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #include "libqhull_r.h" /* includes user_r.h and mem_r.h */ diff --git a/scipy/spatial/qhull_src/src/mem_r.h b/subprojects/qhull_r/libqhull_r/mem_r.h similarity index 98% rename from scipy/spatial/qhull_src/src/mem_r.h rename to subprojects/qhull_r/libqhull_r/mem_r.h index f38aabd066c6..aeb761b100ef 100644 --- a/scipy/spatial/qhull_src/src/mem_r.h +++ b/subprojects/qhull_r/libqhull_r/mem_r.h @@ -11,9 +11,9 @@ and qh_errexit(qhT *qh, qhmem_ERRqhull, NULL, NULL) otherwise - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/mem_r.h#5 $$Change: 2698 $ - $DateTime: 2019/06/24 14:52:34 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/mem_r.h#6 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #ifndef qhDEFmem diff --git a/scipy/spatial/qhull_src/src/merge_r.c b/subprojects/qhull_r/libqhull_r/merge_r.c similarity index 99% rename from scipy/spatial/qhull_src/src/merge_r.c rename to subprojects/qhull_r/libqhull_r/merge_r.c index ee6b3116520d..f3c899cd6e66 100644 --- a/scipy/spatial/qhull_src/src/merge_r.c +++ b/subprojects/qhull_r/libqhull_r/merge_r.c @@ -20,9 +20,9 @@ merges occur in qh_mergefacet and in qh_mergecycle vertex->neighbors not set until the first merge occurs - Copyright (c) 1993-2019 C.B. Barber. - $Id: //main/2019/qhull/src/libqhull_r/merge_r.c#12 $$Change: 2712 $ - $DateTime: 2019/06/28 12:57:00 $$Author: bbarber $ + Copyright (c) 1993-2020 C.B. Barber. + $Id: //main/2019/qhull/src/libqhull_r/merge_r.c#14 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #include "qhull_ra.h" @@ -452,7 +452,7 @@ void qh_appendmergeset(qhT *qh, facetT *facet, facetT *neighbor, mergeType merge merge->ridge1= NULL; merge->ridge2= NULL; merge->mergetype= mergetype; - if(mergetype > 0 && mergetype <= sizeof(mergetypes)) + if(mergetype > 0 && mergetype < sizeof(mergetypes)/sizeof(char *)) mergename= mergetypes[mergetype]; else mergename= mergetypes[MRGnone]; @@ -528,7 +528,7 @@ void qh_appendvertexmerge(qhT *qh, vertexT *vertex, vertexT *destination, mergeT merge->ridge1= ridge1; merge->ridge2= ridge2; merge->mergetype= mergetype; - if(mergetype > 0 && mergetype <= sizeof(mergetypes)) + if(mergetype > 0 && mergetype < sizeof(mergetypes)/sizeof(char *)) mergename= mergetypes[mergetype]; else mergename= mergetypes[MRGnone]; @@ -3367,7 +3367,7 @@ void qh_mergefacet(qhT *qh, facetT *facet1, facetT *facet2, mergeType mergetype, int tracerestore=0, nummerge; const char *mergename; - if(mergetype > 0 && mergetype <= sizeof(mergetypes)) + if(mergetype > 0 && mergetype < sizeof(mergetypes)/sizeof(char *)) mergename= mergetypes[mergetype]; else mergename= mergetypes[MRGnone]; @@ -5298,7 +5298,7 @@ void qh_tracemerge(qhT *qh, facetT *facet1, facetT *facet2, mergeType mergetype) const char *mergename; #ifndef qh_NOtrace - if(mergetype > 0 && mergetype <= sizeof(mergetypes)) + if(mergetype > 0 && mergetype < sizeof(mergetypes)/sizeof(char *)) mergename= mergetypes[mergetype]; else mergename= mergetypes[MRGnone]; diff --git a/scipy/spatial/qhull_src/src/merge_r.h b/subprojects/qhull_r/libqhull_r/merge_r.h similarity index 98% rename from scipy/spatial/qhull_src/src/merge_r.h rename to subprojects/qhull_r/libqhull_r/merge_r.h index ded13a629852..a0d4091ec87a 100644 --- a/scipy/spatial/qhull_src/src/merge_r.h +++ b/subprojects/qhull_r/libqhull_r/merge_r.h @@ -6,9 +6,9 @@ see qh-merge_r.htm and merge_r.c - Copyright (c) 1993-2019 C.B. Barber. - $Id: //main/2019/qhull/src/libqhull_r/merge_r.h#1 $$Change: 2661 $ - $DateTime: 2019/05/24 20:09:58 $$Author: bbarber $ + Copyright (c) 1993-2020 C.B. Barber. + $Id: //main/2019/qhull/src/libqhull_r/merge_r.h#2 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #ifndef qhDEFmerge diff --git a/scipy/spatial/qhull_src/src/poly2_r.c b/subprojects/qhull_r/libqhull_r/poly2_r.c similarity index 99% rename from scipy/spatial/qhull_src/src/poly2_r.c rename to subprojects/qhull_r/libqhull_r/poly2_r.c index 11ea4e223a61..1ab52444ffc3 100644 --- a/scipy/spatial/qhull_src/src/poly2_r.c +++ b/subprojects/qhull_r/libqhull_r/poly2_r.c @@ -8,9 +8,9 @@ frequently used code is in poly_r.c - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/poly2_r.c#18 $$Change: 2712 $ - $DateTime: 2019/06/28 12:57:00 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/poly2_r.c#20 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #include "qhull_ra.h" @@ -2941,10 +2941,38 @@ vertexT *qh_newvertex(qhT *qh, pointT *point) { return(vertex); } /* newvertex */ +/*--------------------------------- + + qh_nextfacet2d( facet, &nextvertex ) + return next facet and vertex for a 2d facet in qh_ORIENTclock order + returns NULL on error + + notes: + in qh_ORIENTclock order (default counter-clockwise) + nextvertex is in between the two facets + does not use qhT or qh_errexit [QhullFacet.cpp] + + design: + see io_r.c/qh_printextremes_2d +*/ +facetT *qh_nextfacet2d(facetT *facet, vertexT **nextvertexp) { + facetT *nextfacet; + + if (facet->toporient ^ qh_ORIENTclock) { + *nextvertexp= SETfirstt_(facet->vertices, vertexT); + nextfacet= SETfirstt_(facet->neighbors, facetT); + }else { + *nextvertexp= SETsecondt_(facet->vertices, vertexT); + nextfacet= SETsecondt_(facet->neighbors, facetT); + } + return nextfacet; +} /* nextfacet2d */ + /*--------------------------------- - qh_nextridge3d( atridge, facet, vertex ) + qh_nextridge3d( atridge, facet, &vertex ) return next ridge and vertex for a 3d facet returns NULL on error [for QhullFacet::nextRidge3d] Does not call qh_errexit nor access qhT. diff --git a/scipy/spatial/qhull_src/src/poly_r.c b/subprojects/qhull_r/libqhull_r/poly_r.c similarity index 99% rename from scipy/spatial/qhull_src/src/poly_r.c rename to subprojects/qhull_r/libqhull_r/poly_r.c index 6e769437c3eb..d6a5e7a3d8c5 100644 --- a/scipy/spatial/qhull_src/src/poly_r.c +++ b/subprojects/qhull_r/libqhull_r/poly_r.c @@ -9,9 +9,9 @@ infrequent code is in poly2_r.c (all but top 50 and their callers 12/3/95) - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/poly_r.c#7 $$Change: 2705 $ - $DateTime: 2019/06/26 16:34:45 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/poly_r.c#8 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #include "qhull_ra.h" diff --git a/scipy/spatial/qhull_src/src/poly_r.h b/subprojects/qhull_r/libqhull_r/poly_r.h similarity index 98% rename from scipy/spatial/qhull_src/src/poly_r.h rename to subprojects/qhull_r/libqhull_r/poly_r.h index 13dbbf4e23c8..83c59140de7d 100644 --- a/scipy/spatial/qhull_src/src/poly_r.h +++ b/subprojects/qhull_r/libqhull_r/poly_r.h @@ -6,9 +6,9 @@ see qh-poly_r.htm, libqhull_r.h and poly_r.c - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/poly_r.h#3 $$Change: 2701 $ - $DateTime: 2019/06/25 15:24:47 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/poly_r.h#5 $$Change: 2963 $ + $DateTime: 2020/06/03 19:31:01 $$Author: bbarber $ */ #ifndef qhDEFpoly @@ -279,6 +279,7 @@ void qh_nearcoplanar(qhT *qh /* qh.facet_list */); vertexT *qh_nearvertex(qhT *qh, facetT *facet, pointT *point, realT *bestdistp); int qh_newhashtable(qhT *qh, int newsize); vertexT *qh_newvertex(qhT *qh, pointT *point); +facetT *qh_nextfacet2d(facetT *facet, vertexT **nextvertexp); ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp); vertexT *qh_opposite_vertex(qhT *qh, facetT *facetA, facetT *neighbor); void qh_outcoplanar(qhT *qh /* qh.facet_list */); diff --git a/scipy/spatial/qhull_src/src/qhull_ra.h b/subprojects/qhull_r/libqhull_r/qhull_ra.h similarity index 96% rename from scipy/spatial/qhull_src/src/qhull_ra.h rename to subprojects/qhull_r/libqhull_r/qhull_ra.h index a3ba3d2d6350..52ccd85a02a8 100644 --- a/scipy/spatial/qhull_src/src/qhull_ra.h +++ b/subprojects/qhull_r/libqhull_r/qhull_ra.h @@ -13,9 +13,9 @@ defines internal functions for libqhull_r.c global_r.c - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/qhull_ra.h#1 $$Change: 2661 $ - $DateTime: 2019/05/24 20:09:58 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/qhull_ra.h#2 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ Notes: grep for ((" and (" to catch fprintf("lkasdjf"); full parens around (x?y:z) diff --git a/scipy/spatial/qhull_src/src/qset_r.c b/subprojects/qhull_r/libqhull_r/qset_r.c similarity index 99% rename from scipy/spatial/qhull_src/src/qset_r.c rename to subprojects/qhull_r/libqhull_r/qset_r.c index 5f7095b4cb7d..c3bec5ffa49c 100644 --- a/scipy/spatial/qhull_src/src/qset_r.c +++ b/subprojects/qhull_r/libqhull_r/qset_r.c @@ -13,9 +13,9 @@ Only reference qh for qhmem or qhstat. Otherwise the matching code in qset.c will bring in qhT - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/qset_r.c#7 $$Change: 2711 $ - $DateTime: 2019/06/27 22:34:56 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/qset_r.c#8 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #include "libqhull_r.h" /* for qhT and QHULL_CRTDBG */ diff --git a/scipy/spatial/qhull_src/src/qset_r.h b/subprojects/qhull_r/libqhull_r/qset_r.h similarity index 98% rename from scipy/spatial/qhull_src/src/qset_r.h rename to subprojects/qhull_r/libqhull_r/qset_r.h index 81c16dc2f300..b41dac0084ee 100644 --- a/scipy/spatial/qhull_src/src/qset_r.h +++ b/subprojects/qhull_r/libqhull_r/qset_r.h @@ -16,9 +16,9 @@ - every set is NULL terminated - sets may be sorted or unsorted, the caller must distinguish this - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/qset_r.h#3 $$Change: 2700 $ - $DateTime: 2019/06/25 05:52:18 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/qset_r.h#4 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #ifndef qhDEFset diff --git a/scipy/spatial/qhull_src/src/random_r.c b/subprojects/qhull_r/libqhull_r/random_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/random_r.c rename to subprojects/qhull_r/libqhull_r/random_r.c diff --git a/scipy/spatial/qhull_src/src/random_r.h b/subprojects/qhull_r/libqhull_r/random_r.h similarity index 83% rename from scipy/spatial/qhull_src/src/random_r.h rename to subprojects/qhull_r/libqhull_r/random_r.h index 70cfe1c9b515..a17549d3b933 100644 --- a/scipy/spatial/qhull_src/src/random_r.h +++ b/subprojects/qhull_r/libqhull_r/random_r.h @@ -6,9 +6,9 @@ see qh-geom_r.htm and random_r.c - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/random_r.h#2 $$Change: 2666 $ - $DateTime: 2019/05/30 10:11:25 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/random_r.h#3 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #ifndef qhDEFrandom diff --git a/scipy/spatial/qhull_src/src/rboxlib_r.c b/subprojects/qhull_r/libqhull_r/rboxlib_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/rboxlib_r.c rename to subprojects/qhull_r/libqhull_r/rboxlib_r.c diff --git a/scipy/spatial/qhull_src/src/stat_r.c b/subprojects/qhull_r/libqhull_r/stat_r.c similarity index 99% rename from scipy/spatial/qhull_src/src/stat_r.c rename to subprojects/qhull_r/libqhull_r/stat_r.c index efc16ffc6808..5661e010e45b 100644 --- a/scipy/spatial/qhull_src/src/stat_r.c +++ b/subprojects/qhull_r/libqhull_r/stat_r.c @@ -6,9 +6,9 @@ see qh-stat_r.htm and stat_r.h - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/stat_r.c#7 $$Change: 2712 $ - $DateTime: 2019/06/28 12:57:00 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/stat_r.c#9 $$Change: 3037 $ + $DateTime: 2020/09/03 17:28:32 $$Author: bbarber $ */ #include "qhull_ra.h" @@ -714,7 +714,7 @@ realT qh_stddev(qhT *qh, int num, realT tot, realT tot2, realT *ave) { /* for qh QHULL_UNUSED(tot) QHULL_UNUSED(tot2) QHULL_UNUSED(ave) - + return 0.0; } #endif /* qh_KEEPstatistics */ diff --git a/scipy/spatial/qhull_src/src/stat_r.h b/subprojects/qhull_r/libqhull_r/stat_r.h similarity index 98% rename from scipy/spatial/qhull_src/src/stat_r.h rename to subprojects/qhull_r/libqhull_r/stat_r.h index 4aaef8681bbd..41b6e5171ddc 100644 --- a/scipy/spatial/qhull_src/src/stat_r.h +++ b/subprojects/qhull_r/libqhull_r/stat_r.h @@ -6,9 +6,9 @@ see qh-stat_r.htm and stat_r.c - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/stat_r.h#3 $$Change: 2711 $ - $DateTime: 2019/06/27 22:34:56 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/stat_r.h#4 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ recompile qhull if you change this file diff --git a/scipy/spatial/qhull_src/src/user_r.c b/subprojects/qhull_r/libqhull_r/user_r.c similarity index 99% rename from scipy/spatial/qhull_src/src/user_r.c rename to subprojects/qhull_r/libqhull_r/user_r.c index 745c50ff3055..4f554a45964a 100644 --- a/scipy/spatial/qhull_src/src/user_r.c +++ b/subprojects/qhull_r/libqhull_r/user_r.c @@ -149,7 +149,7 @@ int qh_new_qhull(qhT *qh, int dim, int numpoints, coordT *points, boolT ismalloc } trace1((qh, qh->ferr, 1044, "qh_new_qhull: build new Qhull for %d %d-d points with %s\n", numpoints, dim, qhull_cmd)); exitcode= setjmp(qh->errexit); - if (!exitcode){ + if (!exitcode) { qh->NOerrexit= False; qh_initflags(qh, qhull_cmd); if (qh->DELAUNAY) diff --git a/scipy/spatial/qhull_src/src/user_r.h b/subprojects/qhull_r/libqhull_r/user_r.h similarity index 99% rename from scipy/spatial/qhull_src/src/user_r.h rename to subprojects/qhull_r/libqhull_r/user_r.h index 71128e46aeb4..8c100fac098b 100644 --- a/scipy/spatial/qhull_src/src/user_r.h +++ b/subprojects/qhull_r/libqhull_r/user_r.h @@ -65,7 +65,7 @@ Code flags -- Cannot use '0031' since it would be octal def counters = [31/32/33/38, 1067, 2113, 3079, 4097, 5006, - 6428, 7027/7028/7035/7068/7070/7102, 8163, 9428, 10000, 11034] + 6429, 7027/7028/7035/7068/7070/7102, 8163, 9428, 10000, 11034] See: qh_ERR* [libqhull_r.h] */ @@ -227,24 +227,24 @@ typedef int countT; #if (qh_CLOCKtype == 1) #if defined(CLOCKS_PER_SECOND) -#define qh_CPUclock ((unsigned long)clock()) /* return CPU clock */ +#define qh_CPUclock ((unsigned long)clock()) /* return CPU clock, may be converted to approximate double */ #define qh_SECticks CLOCKS_PER_SECOND #elif defined(CLOCKS_PER_SEC) -#define qh_CPUclock ((unsigned long)clock()) /* return CPU clock */ +#define qh_CPUclock ((unsigned long)clock()) /* return CPU clock, may be converted to approximate double */ #define qh_SECticks CLOCKS_PER_SEC #elif defined(CLK_TCK) -#define qh_CPUclock ((unsigned long)clock()) /* return CPU clock */ +#define qh_CPUclock ((unsigned long)clock()) /* return CPU clock, may be converted to approximate double */ #define qh_SECticks CLK_TCK #else -#define qh_CPUclock ((unsigned long)clock()) /* return CPU clock */ +#define qh_CPUclock ((unsigned long)clock()) /* return CPU clock, may be converted to approximate double */ #define qh_SECticks 1E6 #endif #elif (qh_CLOCKtype == 2) -#define qh_CPUclock qh_clock() /* return CPU clock */ +#define qh_CPUclock qh_clock() /* return CPU clock, may be converted to approximate double */ #define qh_SECticks 100 #else /* qh_CLOCKtype == ? */ diff --git a/scipy/spatial/qhull_src/src/usermem_r.c b/subprojects/qhull_r/libqhull_r/usermem_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/usermem_r.c rename to subprojects/qhull_r/libqhull_r/usermem_r.c diff --git a/scipy/spatial/qhull_src/src/userprintf_r.c b/subprojects/qhull_r/libqhull_r/userprintf_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/userprintf_r.c rename to subprojects/qhull_r/libqhull_r/userprintf_r.c diff --git a/scipy/spatial/qhull_src/src/userprintf_rbox_r.c b/subprojects/qhull_r/libqhull_r/userprintf_rbox_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/userprintf_rbox_r.c rename to subprojects/qhull_r/libqhull_r/userprintf_rbox_r.c diff --git a/subprojects/qhull_r/meson.build b/subprojects/qhull_r/meson.build new file mode 100644 index 000000000000..7911c36f8f16 --- /dev/null +++ b/subprojects/qhull_r/meson.build @@ -0,0 +1,43 @@ +project('qhull_r', + 'c', + version : '8.0.2', + meson_version: '>= 1.5.0', +) + +cc = meson.get_compiler('c') + +libqhull_r_sources = [ + 'libqhull_r/geom2_r.c', + 'libqhull_r/geom_r.c', + 'libqhull_r/global_r.c', + 'libqhull_r/io_r.c', + 'libqhull_r/libqhull_r.c', + 'libqhull_r/mem_r.c', + 'libqhull_r/merge_r.c', + 'libqhull_r/poly2_r.c', + 'libqhull_r/poly_r.c', + 'libqhull_r/qset_r.c', + 'libqhull_r/random_r.c', + 'libqhull_r/rboxlib_r.c', + 'libqhull_r/stat_r.c', + 'libqhull_r/user_r.c', + 'libqhull_r/usermem_r.c', + 'libqhull_r/userprintf_r.c', + 'libqhull_r/userprintf_rbox_r.c' +] + +libqhull_r = static_library( + 'libqhull_r', + [libqhull_r_sources], + c_args: cc.get_supported_arguments('-Wno-unused-but-set-variable'), + include_directories: 'libqhull_r', + # Ensure that if we link a static library into a shared library, + # private symbols don't get re-exported. + gnu_symbol_visibility: 'inlineshidden', + install: false, +) + +qhull_r_dep = declare_dependency( + link_with: libqhull_r, + include_directories: '.', +) diff --git a/subprojects/xsf b/subprojects/xsf new file mode 160000 index 000000000000..4fff9b2cb2b5 --- /dev/null +++ b/subprojects/xsf @@ -0,0 +1 @@ +Subproject commit 4fff9b2cb2b5c31a0cf0b0f609d2699a5eeac53b diff --git a/tools/check_installation.py b/tools/check_installation.py index 9ffa58ef03bb..32287a47c722 100644 --- a/tools/check_installation.py +++ b/tools/check_installation.py @@ -7,7 +7,7 @@ install_directory_name: the relative path from the root of the repo to the directory where - SciPy is installed (for dev.py usually "build-install") + SciPy is installed (for `spin` usually "build-install") Notes ===== diff --git a/tools/check_test_name.py b/tools/check_test_name.py index 5d9e6531ae18..ac0dfa81d27c 100755 --- a/tools/check_test_name.py +++ b/tools/check_test_name.py @@ -141,7 +141,7 @@ def main(content: str, file: str) -> int: Path("scipy").rglob("**/tests/**/test*.py"), ["scipy/_lib/_testutils.py"], ): - with open(os.path.join(_file)) as fd: + with open(os.path.join(_file), encoding="utf-8") as fd: _content = fd.read() if f"self.{_node.name}" in _content: should_continue = True diff --git a/tools/check_unicode.py b/tools/check_unicode.py index 3075be1e34aa..893b97685984 100755 --- a/tools/check_unicode.py +++ b/tools/check_unicode.py @@ -18,7 +18,7 @@ latin1_letters = set(chr(cp) for cp in range(192, 256)) greek_letters = set('αβγδεζηθικλμνξoπρστυϕχψω' + 'ΓΔΘΛΞΠΣϒΦΨΩ') box_drawing_chars = set(chr(cp) for cp in range(0x2500, 0x2580)) -extra_symbols = set('®ő∫≠≥≤±∞²³·→√✓✗') +extra_symbols = set('®ő∫≠≥≤±∞²³·→√✅⛔⚠️') allowed = latin1_letters | greek_letters | box_drawing_chars | extra_symbols # END_INCLUDE_RST (do not change this line!) diff --git a/tools/download-wheels.py b/tools/download-wheels.py index 3d5a117e2ba2..5ff3e1578be2 100644 --- a/tools/download-wheels.py +++ b/tools/download-wheels.py @@ -16,6 +16,7 @@ __version__ = '0.1' # Edit these for other projects. +STAGING_FILE_URL = "https://pypi.anaconda.org/multibuild-wheels-staging/simple/scipy/" STAGING_URL = 'https://anaconda.org/multibuild-wheels-staging/scipy' PREFIX = 'scipy' @@ -47,7 +48,7 @@ def get_wheel_names(version): """ http = http_manager() tmpl = re.compile(rf"^.*{PREFIX}-{version}-.*\.whl$") - index_url = f"{STAGING_URL}/files" + index_url = f"{STAGING_FILE_URL}" index_html = http.request('GET', index_url) soup = BeautifulSoup(index_html.data, 'html.parser') return soup.findAll(string=tmpl) diff --git a/tools/trim_sdist_content.py b/tools/trim_sdist_content.py new file mode 100644 index 000000000000..2f7391d02536 --- /dev/null +++ b/tools/trim_sdist_content.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +""" +The purpose of this script is to remove files from the sdist that are not +needed and bloat the sdist size too much. This deals with files from +git submodules, because those cannot be removed by using `export-ignore` +in the top-level `.gitattributes` file. +""" + +import os +import pathlib +import shutil + +dist_root = pathlib.Path(os.environ['MESON_DIST_ROOT']) + +for name in [dist_root / d for d in ( + 'subprojects/boost_math/math/.github', + 'subprojects/boost_math/math/build', + 'subprojects/boost_math/math/config', + 'subprojects/boost_math/math/doc', + 'subprojects/boost_math/math/example', + 'subprojects/boost_math/math/meta', + 'subprojects/boost_math/math/reporting', + 'subprojects/boost_math/math/src', + 'subprojects/boost_math/math/test', + 'subprojects/boost_math/math/tools', + 'subprojects/highs/.github', + 'subprojects/highs/app', + 'subprojects/highs/check', + 'subprojects/highs/docs', + 'subprojects/highs/examples', + 'subprojects/highs/nuget', + 'subprojects/highs/scripts', + 'subprojects/highs/tests', + 'subprojects/xsf/.github', + 'subprojects/xsf/pixi.lock', + 'subprojects/xsf/tests', + )]: + if name.is_file(): + name.unlink() + else: + shutil.rmtree(name) + diff --git a/tools/vendor_qhull.sh b/tools/vendor_qhull.sh new file mode 100755 index 000000000000..93711a1e3e3a --- /dev/null +++ b/tools/vendor_qhull.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# Vendors qhull from https://github.com/qhull/qhull + +set -o nounset +set -o errexit + +REPO_URL="https://github.com/qhull/qhull" +COMMIT_HASH="613debeaea72ee66626dace9ba1a2eff11b5d37d" + +# XXX: run this from the repo top level like `./tools/vendor_qhull.sh` +ROOT_DIR="subprojects/qhull_r/libqhull_r" + +rm -rf $ROOT_DIR +mkdir $ROOT_DIR +mkdir $ROOT_DIR/.tmp +git clone $REPO_URL $ROOT_DIR/.tmp +pushd $ROOT_DIR/.tmp +git checkout $COMMIT_HASH +pushd src/libqhull_r/ +rm *.htm +rm *.pro +rm *.def +rm Makefile +popd # $ROOT_DIR/.tmp +popd +mv -v $ROOT_DIR/.tmp/COPYING.txt $ROOT_DIR/ +mv -v $ROOT_DIR/.tmp/Announce.txt $ROOT_DIR/ +cp -v $ROOT_DIR/.tmp/src/libqhull_r/* $ROOT_DIR/ +rm -rf $ROOT_DIR/.tmp