├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── config.yml │ ├── documentation.md │ ├── feature-request.md │ └── issue.md ├── dependabot.yml ├── labeler.yml ├── pull_request_template.md └── workflows │ ├── ci-citation.yml │ ├── ci-label.yml │ ├── ci-locks.yml │ ├── ci-manifest.yml │ ├── ci-template-check.yml │ ├── ci-tests.yml │ ├── ci-wheels.yml │ └── stale.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CITATION.cff ├── Dockerfile ├── LICENSE ├── MANIFEST.in ├── README.md ├── codecov.yml ├── index.ipynb ├── pyproject.toml ├── requirements ├── locks │ ├── README.md │ ├── py310-linux-64.lock │ ├── py311-linux-64.lock │ └── py312-linux-64.lock ├── py310.yml ├── py311.yml ├── py312.yml ├── pypi-core.txt └── stratify.yml ├── setup.py ├── src └── stratify │ ├── __init__.py │ ├── _bounded_vinterp.py │ ├── _conservative.pyx │ ├── _vinterp.pyx │ └── tests │ ├── __init__.py │ ├── performance.py │ ├── test_bounded_vinterp.py │ └── test_vinterp.py └── summary.png /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Bug Report \U0001F41B" 3 | about: Submit a bug report to help us improve Python-Stratify. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 🐛 Bug Report 11 | 12 | 13 | ## How To Reproduce 14 | Steps to reproduce the behaviour: 15 | 16 | 1. 17 | 2. 18 | 3. 19 | 20 | ## Expected behaviour 21 | 22 | 23 | ## Screenshots 24 | 25 | 26 | ## Environment 27 | - OS & Version: [e.g., Ubuntu 20.04 LTS] 28 | - Stratify Version 29 | 30 | ## Additional context 31 | 32 |
33 | Click to expand this section... 34 | 35 | ``` 36 | Please add additional verbose information in this section e.g., code, output, tracebacks, screenshots etc 37 | ``` 38 |
39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # reference: https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser 2 | blank_issues-enabled: false 3 | contact_links: 4 | - name: 💬 Python-Stratify GitHub Discussions 5 | url: https://github.com/Scitools/python-stratify/discussions 6 | about: Engage with the Python-Stratify community to discuss your issue 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Documentation \U0001F4DA" 3 | about: Report an issue with the Python-Stratify documentation. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 📚 Documentation 11 | 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request ✨ 3 | about: Submit a request for a new feature in Python-Stratify 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## ✨ Feature Request 11 | 12 | 13 | ## Motivation 14 | 15 | 16 | 17 | ## Additional context 18 | 19 |
20 | Click to expand this section... 21 | 22 | ``` 23 | Please add additional verbose information in this section e.g., references, screenshots, listings etc 24 | ``` 25 |
26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Custom Issue \U0001F4F0" 3 | about: Submit a generic issue to help us improve Python-Stratify. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 📰 Custom Issue 11 | 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Reference: 2 | # - https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/keeping-your-actions-up-to-date-with-dependabot 3 | 4 | version: 2 5 | updates: 6 | 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | # Check later in the week - the upstream dependabot check in `workflows` runs deliberately early in the week. 11 | # Therefore allowing time for the `workflows` update to be merged-and-released first. 12 | interval: "weekly" 13 | day: "thursday" 14 | time: "01:00" 15 | timezone: "Europe/London" 16 | groups: 17 | dependencies: 18 | patterns: 19 | - "*" 20 | labels: 21 | - "New: Pull Request" 22 | - "Bot" 23 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | 'Type: Dependencies': 2 | - any: 3 | - changed-files: 4 | - any-glob-to-any-file: 'requirements/stratify.yml' 5 | - any-glob-to-any-file: 'requirements/py*.yml' 6 | - any-glob-to-any-file: 'requirements/locks/pypi*.txt' 7 | - any-glob-to-any-file: 'requirements/locks/py*.lock' 8 | 9 | 'Type: Documentation': 10 | - any: 11 | changed-files: 12 | - any-glob-to-any-file: 'index.ipynb' 13 | - any-glob-to-any-file: 'README.md' 14 | 15 | 'Type: GHA': 16 | - any: 17 | - changed-files: 18 | - any-glob-to-any-file: '.github/*.yml' 19 | - any-glob-to-any-file: '.github/**/*.yml' 20 | 21 | 'Type: Infrastructure': 22 | - any: 23 | changed-files: 24 | - any-glob-to-any-file: '*' 25 | 26 | 'Type: Testing': 27 | - any: 28 | changed-files: 29 | - any-glob-to-any-file: 'src/stratify/tests/*' 30 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## 🚀 Pull Request 2 | 3 | ### Description 4 | 5 | 6 | 7 | 8 | --- 9 | -------------------------------------------------------------------------------- /.github/workflows/ci-citation.yml: -------------------------------------------------------------------------------- 1 | name: ci-citation 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "CITATION.cff" 7 | 8 | push: 9 | paths: 10 | - "CITATION.cff" 11 | 12 | workflow_dispatch: 13 | 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.ref }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | validate: 20 | name: "validate" 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 0 26 | 27 | - name: "check CITATION.cff" 28 | uses: citation-file-format/cffconvert-github-action@2.0.0 29 | with: 30 | args: "--validate" 31 | -------------------------------------------------------------------------------- /.github/workflows/ci-label.yml: -------------------------------------------------------------------------------- 1 | # Reference 2 | # - https://github.com/actions/labeler 3 | 4 | name: ci-label 5 | 6 | on: 7 | pull_request_target: 8 | types: [opened, edited, reopened] 9 | 10 | jobs: 11 | PR_triage: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/labeler@v5 15 | with: 16 | configuration-path: .github/labeler.yml 17 | repo-token: ${{ secrets.GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /.github/workflows/ci-locks.yml: -------------------------------------------------------------------------------- 1 | # Updates the environment lock files. See the called workflow in the 2 | # scitools/workflows repo for more details. 3 | 4 | name: ci-locks 5 | 6 | 7 | on: 8 | workflow_dispatch: 9 | schedule: 10 | # Run once a week on a Saturday night 11 | # N.B. "should" be quoted, according to 12 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onschedule 13 | - cron: "3 0 * * 6" 14 | 15 | concurrency: 16 | group: ${{ github.workflow }}-${{ github.ref }} 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | refresh_lockfiles: 21 | uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@2025.05.1 22 | secrets: inherit 23 | -------------------------------------------------------------------------------- /.github/workflows/ci-manifest.yml: -------------------------------------------------------------------------------- 1 | # Reference 2 | # - https://github.com/actions/checkout 3 | 4 | name: ci-manifest 5 | 6 | on: 7 | pull_request: 8 | 9 | push: 10 | branches: 11 | - "master" 12 | - "v*x" 13 | - "!auto-update-lockfiles" 14 | - "!pre-commit-ci-update-config" 15 | - "!dependabot/*" 16 | tags: 17 | - "v*" 18 | 19 | workflow_dispatch: 20 | 21 | concurrency: 22 | group: ${{ github.workflow }}-${{ github.ref }} 23 | cancel-in-progress: true 24 | 25 | jobs: 26 | manifest: 27 | name: "check-manifest" 28 | uses: scitools/workflows/.github/workflows/ci-manifest.yml@2025.05.1 29 | -------------------------------------------------------------------------------- /.github/workflows/ci-template-check.yml: -------------------------------------------------------------------------------- 1 | # Checks if a PR makes any changes that ought to be shared via templating. 2 | # See the called workflow in the scitools/workflows repo for more details. 3 | 4 | name: ci-template-check 5 | 6 | on: 7 | pull_request_target: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | prompt-share: 13 | uses: scitools/workflows/.github/workflows/ci-template-check.yml@2025.05.1 14 | secrets: inherit 15 | with: 16 | pr_number: ${{ github.event.pull_request.number }} 17 | -------------------------------------------------------------------------------- /.github/workflows/ci-tests.yml: -------------------------------------------------------------------------------- 1 | # Reference: 2 | # - https://github.com/actions/cache 3 | # - https://github.com/actions/checkout 4 | # - https://github.com/actions/download-artifact 5 | # - https://github.com/actions/upload-artifact 6 | # - https://github.com/conda-incubator/setup-miniconda 7 | 8 | name: ci-tests 9 | 10 | on: 11 | pull_request: 12 | 13 | push: 14 | branches: 15 | - "master" 16 | - "v*x" 17 | - "!auto-update-lockfiles" 18 | - "!pre-commit-ci-update-config" 19 | - "!dependabot/*" 20 | tags: 21 | - "v*" 22 | 23 | workflow_dispatch: 24 | 25 | concurrency: 26 | group: ${{ github.workflow }}-${{ github.ref }} 27 | cancel-in-progress: true 28 | 29 | jobs: 30 | tests: 31 | name: "tests (py${{ matrix.python-version }} ${{ matrix.os }})" 32 | 33 | runs-on: ubuntu-latest 34 | 35 | defaults: 36 | run: 37 | shell: bash -l {0} 38 | 39 | env: 40 | ENV_NAME: "ci-tests" 41 | CYTHON_COVERAGE: true 42 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 43 | 44 | strategy: 45 | fail-fast: false 46 | matrix: 47 | os: ["ubuntu-latest"] 48 | python-version: ["310", "311", "312"] 49 | include: 50 | - python-version: "312" 51 | cov-report: "--cov-report=xml --cov" 52 | codecov: "codecov" 53 | 54 | steps: 55 | - uses: actions/checkout@v4 56 | with: 57 | fetch-depth: 0 58 | 59 | - name: "mambaforge setup (python ${{ matrix.python-version }})" 60 | uses: conda-incubator/setup-miniconda@v3 61 | with: 62 | miniforge-variant: Miniforge3 63 | miniforge-version: latest 64 | channels: conda-forge 65 | channel-priority: true 66 | auto-update-conda: true 67 | environment-file: "requirements/locks/py${{ matrix.python-version }}-linux-64.lock" 68 | activate-environment: ${{ env.ENV_NAME }} 69 | 70 | - name: "stratify tests (py${{ matrix.python-version }})" 71 | run: | 72 | python -m pip install --no-deps --editable . 73 | python setup.py build_ext --inplace 74 | pytest ${{ matrix.cov-report }} 75 | ${{ matrix.codecov }} 76 | -------------------------------------------------------------------------------- /.github/workflows/ci-wheels.yml: -------------------------------------------------------------------------------- 1 | # Reference: 2 | # - https://github.com/actions/checkout 3 | # - https://github.com/actions/download-artifact 4 | # - https://github.com/actions/upload-artifact 5 | # - https://github.com/pypa/cibuildwheel 6 | # - https://github.com/pypa/build 7 | # - https://github.com/pypa/gh-action-pypi-publish 8 | # - https://test.pypi.org/help/#apitoken 9 | 10 | name: ci-wheels 11 | 12 | on: 13 | pull_request: 14 | 15 | push: 16 | branches: 17 | - "main" 18 | - "v*x" 19 | - "!auto-update-lockfiles" 20 | - "!pre-commit-ci-update-config" 21 | - "!dependabot/*" 22 | tags: 23 | - "v*" 24 | 25 | jobs: 26 | build_bdist: 27 | name: "build ${{ matrix.os }} (${{ matrix.arch }}) wheels" 28 | runs-on: ${{ matrix.os }} 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | os: ["ubuntu-latest", "macos-latest", "windows-latest"] 33 | arch: ["x86_64", "arm64"] 34 | exclude: 35 | - os: "ubuntu-latest" 36 | arch: "arm64" 37 | - os: "windows-latest" 38 | arch: "x86_64" 39 | - os: "windows-latest" 40 | arch: "arm64" 41 | include: 42 | - os: "windows-latest" 43 | arch: "AMD64" 44 | 45 | steps: 46 | - uses: actions/checkout@v4 47 | with: 48 | fetch-depth: 0 49 | 50 | - name: "build ${{ matrix.os }} (${{ matrix.arch }}) wheels" 51 | uses: pypa/cibuildwheel@v2.23.3 52 | env: 53 | CIBW_SKIP: "cp39-* cp313-* pp* *-musllinux*" 54 | CIBW_ARCHS: ${{ matrix.arch }} 55 | CIBW_BUILD_FRONTEND: "build" 56 | CIBW_MANYLINUX_X86_64_IMAGE: "manylinux2014" 57 | CIBW_TEST_SKIP: "*-macosx_arm64" 58 | CIBW_TEST_REQUIRES: "pytest" 59 | CIBW_TEST_COMMAND: > 60 | python -m pytest --pyargs stratify 61 | 62 | - uses: actions/upload-artifact@v4 63 | with: 64 | name: pypi-${{ matrix.os }}-${{ matrix.arch }}-artifact 65 | path: ${{ github.workspace }}/wheelhouse/*.whl 66 | 67 | 68 | build_sdist: 69 | name: "Build sdist" 70 | runs-on: ubuntu-latest 71 | steps: 72 | - uses: actions/checkout@v4 73 | with: 74 | fetch-depth: 0 75 | 76 | - name: "Building sdist" 77 | shell: bash 78 | run: | 79 | pipx run build --sdist 80 | 81 | - uses: actions/upload-artifact@v4 82 | with: 83 | name: pypi-sdist 84 | path: ${{ github.workspace }}/dist/*.tar.gz 85 | 86 | 87 | show-artifacts: 88 | needs: [build_bdist, build_sdist] 89 | name: "Show artifacts" 90 | runs-on: ubuntu-latest 91 | steps: 92 | - uses: actions/download-artifact@v4 93 | with: 94 | merge-multiple: true 95 | path: ${{ github.workspace }}/dist 96 | 97 | - shell: bash 98 | run: | 99 | ls -l ${{ github.workspace }}/dist 100 | 101 | 102 | publish-artifacts-test-pypi: 103 | needs: [build_bdist, build_sdist] 104 | name: "Publish to Test PyPI" 105 | runs-on: ubuntu-latest 106 | # upload to Test PyPI for every commit on main branch 107 | if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' 108 | steps: 109 | - uses: actions/download-artifact@v4 110 | with: 111 | merge-multiple: true 112 | path: ${{ github.workspace }}/dist 113 | 114 | - uses: pypa/gh-action-pypi-publish@release/v1 115 | with: 116 | user: __token__ 117 | password: ${{ secrets.TEST_PYPI_API_TOKEN }} 118 | repository_url: https://test.pypi.org/legacy/ 119 | skip_existing: true 120 | print_hash: true 121 | 122 | 123 | publish-artifacts-pypi: 124 | needs: [build_bdist, build_sdist] 125 | name: "Publish to PyPI" 126 | runs-on: ubuntu-latest 127 | # upload to PyPI for every tag starting with 'v' 128 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') 129 | steps: 130 | - uses: actions/download-artifact@v4 131 | with: 132 | merge-multiple: true 133 | path: ${{ github.workspace }}/dist 134 | 135 | - uses: pypa/gh-action-pypi-publish@release/v1 136 | with: 137 | user: __token__ 138 | password: ${{ secrets.PYPI_API_TOKEN }} 139 | print_hash: true 140 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # See https://github.com/actions/stale 2 | 3 | name: Stale issues and pull-requests 4 | 5 | on: 6 | schedule: 7 | # Run once a day 8 | # N.B. "should" be quoted, according to 9 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onschedule 10 | - cron: "0 0 * * *" 11 | 12 | jobs: 13 | stale: 14 | if: "github.repository == 'SciTools/python-stratify'" 15 | runs-on: ubuntu-latest 16 | 17 | env: 18 | DAYS_BEFORE_STALE: 500 19 | DAYS_BEFORE_CLOSE: 28 20 | 21 | steps: 22 | - uses: actions/stale@v9 23 | with: 24 | repo-token: ${{ secrets.GITHUB_TOKEN }} 25 | 26 | # Idle number of days before marking issues/prs stale. 27 | days-before-stale: ${{ env.DAYS_BEFORE_STALE }} 28 | 29 | # Idle number of days before closing stale issues/prs. 30 | days-before-close: ${{ env.DAYS_BEFORE_CLOSE }} 31 | 32 | # Comment on the staled issues. 33 | stale-issue-message: | 34 | In order to maintain a backlog of relevant issues, we automatically label them as stale after ${{ env.DAYS_BEFORE_STALE }} days of inactivity. 35 | 36 | If this issue is still important to you, then please comment on this issue and the stale label will be removed. 37 | 38 | Otherwise this issue will be automatically closed in ${{ env.DAYS_BEFORE_CLOSE }} days time. 39 | 40 | # Comment on the staled prs. 41 | stale-pr-message: | 42 | In order to maintain a backlog of relevant PRs, we automatically label them as stale after ${{ env.DAYS_BEFORE_STALE }} days of inactivity. 43 | 44 | If this PR is still important to you, then please comment on this PR and the stale label will be removed. 45 | 46 | Otherwise this PR will be automatically closed in ${{ env.DAYS_BEFORE_CLOSE }} days time. 47 | 48 | # Comment on the staled issues while closed. 49 | close-issue-message: | 50 | This stale issue has been automatically closed due to a lack of community activity. 51 | 52 | If you still care about this issue, then please either: 53 | * Re-open this issue, if you have sufficient permissions, or 54 | * Add a comment pinging `@SciTools/stratify-maintainers` who will re-open on your behalf. 55 | 56 | # Comment on the staled prs while closed. 57 | close-pr-message: | 58 | This stale PR has been automatically closed due to a lack of community activity. 59 | 60 | If you still care about this PR, then please either: 61 | * Re-open this PR, if you have sufficient permissions, or 62 | * Add a comment pinging `@SciTools/stratify-maintainers` who will re-open on your behalf. 63 | 64 | # Label to apply on staled issues. 65 | stale-issue-label: Stale 66 | 67 | # Label to apply on staled prs. 68 | stale-pr-label: Stale 69 | 70 | # Labels on issues exempted from stale. 71 | exempt-issue-labels: 72 | "Status: Blocked,Status: Decision Required" 73 | 74 | # Labels on prs exempted from stale. 75 | exempt-pr-labels: 76 | "Status: Blocked,Status: Decision Required" 77 | 78 | # Max number of operations per run. 79 | operations-per-run: 300 80 | 81 | # Remove stale label from issues/prs on updates/comments. 82 | remove-stale-when-updated: true 83 | 84 | # Order to get issues/PRs. 85 | ascending: true 86 | 87 | # Exempt all issues/prs with milestones from stale. 88 | exempt-all-milestones: true 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # setuptools-scm 2 | _version.py 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | *.c 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | pip-wheel-metadata/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | 135 | # PyCharm 136 | .idea 137 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | # See https://pre-commit.ci/#configuration 4 | # See https://github.com/scientific-python/cookie#sp-repo-review 5 | 6 | ci: 7 | autofix_prs: false 8 | autoupdate_commit_msg: "chore: update pre-commit hooks" 9 | 10 | repos: 11 | - repo: https://github.com/pre-commit/pre-commit-hooks 12 | rev: 'v5.0.0' 13 | hooks: 14 | # Prevent giant files from being committed. 15 | - id: check-added-large-files 16 | # Check whether files parse as valid Python. 17 | - id: check-ast 18 | # Check for file name conflicts on case-insensitive filesytems. 19 | - id: check-case-conflict 20 | # Check for files that contain merge conflict strings. 21 | - id: check-merge-conflict 22 | # Check for debugger imports and py37+ `breakpoint()` calls in Python source. 23 | - id: debug-statements 24 | # Check TOML file syntax. 25 | - id: check-toml 26 | # Check YAML file syntax. 27 | - id: check-yaml 28 | # Makes sure files end in a newline and only a newline. 29 | # Duplicates Ruff W292 but also works on non-Python files. 30 | - id: end-of-file-fixer 31 | # Replaces or checks mixed line ending. 32 | - id: mixed-line-ending 33 | # Don't commit to master branch. 34 | - id: no-commit-to-branch 35 | # Trims trailing whitespace. 36 | # Duplicates Ruff W291 but also works on non-Python files. 37 | - id: trailing-whitespace 38 | 39 | 40 | - repo: https://github.com/astral-sh/ruff-pre-commit 41 | rev: "v0.11.8" 42 | hooks: 43 | - id: ruff 44 | types: [file, python] 45 | args: [--fix, --show-fixes] 46 | - id: ruff-format 47 | types: [file, python] 48 | 49 | - repo: https://github.com/codespell-project/codespell 50 | rev: "v2.4.1" 51 | hooks: 52 | - id: codespell 53 | types_or: [asciidoc, python, markdown, rst] 54 | additional_dependencies: [tomli] 55 | 56 | 57 | 58 | - repo: https://github.com/pycqa/isort 59 | rev: '6.0.1' 60 | hooks: 61 | - id: isort 62 | name: isort (cython) 63 | types: [file, cython] 64 | args: [--filter-files] 65 | 66 | - repo: https://github.com/aio-libs/sort-all 67 | rev: "v1.3.0" 68 | hooks: 69 | - id: sort-all 70 | types: [file, python] 71 | 72 | - repo: https://github.com/pre-commit/mirrors-mypy 73 | rev: 'v1.15.0' 74 | hooks: 75 | - id: mypy 76 | exclude: 'noxfile\.py|docs/conf\.py' 77 | 78 | - repo: https://github.com/abravalheri/validate-pyproject 79 | # More exhaustive than Ruff RUF200. 80 | rev: "v0.24.1" 81 | hooks: 82 | - id: validate-pyproject 83 | 84 | - repo: https://github.com/scientific-python/cookie 85 | rev: 2025.05.02 86 | hooks: 87 | - id: sp-repo-review 88 | additional_dependencies: ["repo-review[cli]"] 89 | args: ["--show=errskip"] 90 | 91 | - repo: https://github.com/numpy/numpydoc 92 | rev: v1.8.0 93 | hooks: 94 | - id: numpydoc-validation 95 | types: [file, python] 96 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If Python-Stratify played an important part in your research then please add us to your reference list by using the references below." 3 | authors: 4 | - family-names: "Elson" 5 | given-names: "Phil" 6 | orcid: "https://orcid.org/0000-0001-6360-749X" 7 | - family-names: "Little" 8 | given-names: "Bill" 9 | orcid: "https://orcid.org/0000-0002-1345-9465" 10 | - family-names: "Andela" 11 | given-names: "Bouwe" 12 | orcid: "https://orcid.org/0000-0001-9005-8940" 13 | - family-names: "Dreyer" 14 | given-names: "Laura" 15 | orcid: "https://orcid.org/0009-0006-6310-3658" 16 | - family-names: "Fernandes" 17 | given-names: "Filipe" 18 | orcid: "https://orcid.org/0000-0003-4165-2913" 19 | - family-names: "Rol" 20 | given-names: "Evert" 21 | orcid: "https://orcid.org/0000-0001-8357-4453" 22 | - family-names: "Wright" 23 | given-names: "Henry" 24 | orcid: "https://orcid.org/0009-0005-1489-760X" 25 | - family-names: "Yeo" 26 | given-names: "Martin" 27 | orcid: "https://orcid.org/0009-0000-6857-560X" 28 | title: "python-stratify" 29 | abstract: "Interpolation for restratification, particularly useful for Nd vertical interpolation of atmospheric and oceanographic datasets" 30 | license: "BSD-3-Clause" 31 | license-url: "https://spdx.org/licenses/BSD-3-Clause.html" 32 | date-released: "2018-06-01" 33 | doi: "10.5281/zenodo.7863857" 34 | repository-code: "https://github.com/SciTools/python-stratify" 35 | type: "software" 36 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM andrewosh/binder-base 2 | 3 | LABEL maintainer "bjlittle.pub@gmail.com" 4 | 5 | USER main 6 | RUN conda install -c conda-forge python-stratify matplotlib 7 | WORKDIR $HOME/notebooks 8 | RUN rm -rf stratify 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | Copyright (c) 2015 - 2024, Met Office. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the name of conda-buildall nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | prune .github 2 | prune requirements 3 | recursive-include requirements *.txt 4 | recursive-include src *.py *.pyx 5 | include CITATION.cff 6 | exclude .flake8 7 | exclude .gitignore 8 | exclude .pre-commit-config.yaml 9 | exclude Dockerfile 10 | exclude codecov.yml 11 | exclude index.ipynb 12 | exclude summary.png 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stratify 2 | 3 | Interpolation for restratification, particularly useful for Nd vertical interpolation of atmospheric and oceanographic datasets 4 | 5 | | | | 6 | |------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 7 | | ⚙️ CI | [![ci-locks](https://github.com/SciTools/python-stratify/actions/workflows/ci-locks.yml/badge.svg)](https://github.com/SciTools/python-stratify/actions/workflows/ci-locks.yml) [![ci-manifest](https://github.com/SciTools/python-stratify/actions/workflows/ci-manifest.yml/badge.svg)](https://github.com/SciTools/python-stratify/actions/workflows/ci-manifest.yml) [![ci-tests](https://github.com/SciTools/python-stratify/actions/workflows/ci-tests.yml/badge.svg)](https://github.com/SciTools/python-stratify/actions/workflows/ci-tests.yml) [![ci-wheels](https://github.com/SciTools/python-stratify/actions/workflows/ci-wheels.yml/badge.svg)](https://github.com/SciTools/python-stratify/actions/workflows/ci-wheels.yml) [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/SciTools/python-stratify/master.svg)](https://results.pre-commit.ci/latest/github/SciTools/python-stratify/master) | 8 | | 💬 Community | [![GH Discussions](https://img.shields.io/badge/github-discussions%20%F0%9F%92%AC-yellow?logo=github&logoColor=lightgrey)](https://github.com/SciTools/python-stratify/discussions) | 9 | | 📖 Documentation | [![Binder](http://mybinder.org/badge.svg)](http://mybinder.org:/repo/scitools-incubator/python-stratify) | 10 | | 📈 Health | [![codecov](https://codecov.io/gh/SciTools/python-stratify/branch/master/graph/badge.svg?token=v1R1bJ4kYr)](https://codecov.io/gh/SciTools/python-stratify) | 11 | | ✨ Meta | [![code style - black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![license - bds-3-clause](https://img.shields.io/github/license/SciTools/python-stratify)](https://github.com/python-stratify/python-stratify/blob/main/LICENSE) | 12 | | 📦 Package | [![DOI](https://img.shields.io/badge/DOI-10.5281%2Fzenodo.7863857-blue)](https://doi.org/10.5281/zenodo.7863857) [![conda-forge](https://img.shields.io/conda/vn/conda-forge/python-stratify?color=orange&label=conda-forge&logo=conda-forge&logoColor=white)](https://anaconda.org/conda-forge/python-stratify) [![pypi](https://img.shields.io/pypi/v/stratify?color=orange&label=pypi&logo=python&logoColor=white)](https://pypi.org/project/stratify/) [![pypi - python version](https://img.shields.io/pypi/pyversions/stratify.svg?color=orange&logo=python&label=python&logoColor=white)](https://pypi.org/project/stratify/) | 13 | | 🧰 Repo | [![contributors](https://img.shields.io/github/contributors/SciTools/python-stratify)](https://github.com/SciTools/python-stratify/graphs/contributors) | 14 | | | | 15 | 16 | 17 | **Are you a [SciTools/iris](https://github.com/SciTools/iris) user?** Did you know Iris provides a convenience for using Stratify with Iris `Cube`s? Try out [`iris.experimental.stratify`](https://scitools-iris.readthedocs.io/en/latest/generated/api/iris/experimental/stratify.html) and let the devs know what you think! 18 | 19 | ## Introduction 20 | 21 | Discover the capabilities of `stratify` with this introductory [Jupyter Notebook](https://github.com/SciTools-incubator/python-stratify/blob/master/index.ipynb). 22 | 23 | ![](https://SciTools-incubator.github.io/python-stratify/summary.png) 24 | 25 | ## Installation 26 | 27 | ```shell 28 | conda install --channel conda-forge python-stratify 29 | ``` 30 | ```shell 31 | pip install stratify 32 | ``` 33 | 34 | ## License 35 | Stratify is licenced under a [BSD 3-Clause License](LICENSE). 36 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | # see https://docs.codecov.com/docs/commit-status 3 | status: 4 | project: 5 | default: 6 | target: auto 7 | # coverage can drop by up to % while still posting success 8 | threshold: 3% 9 | patch: off 10 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | # Defined by PEP 518 3 | requires = [ 4 | "Cython<3.1.0", 5 | "numpy>=2", 6 | "setuptools>=77.0.3", 7 | "setuptools_scm[toml]>=8", 8 | "wheel", 9 | ] 10 | # Defined by PEP 517 11 | build-backend = "setuptools.build_meta" 12 | 13 | [project] 14 | authors = [ 15 | {name = "Stratify Contributors", email = "scitools.pub@gmail.com"} 16 | ] 17 | classifiers = [ 18 | "Development Status :: 5 - Production/Stable", 19 | "Intended Audience :: Science/Research", 20 | "Operating System :: POSIX", 21 | "Operating System :: POSIX :: Linux", 22 | "Operating System :: Unix", 23 | "Programming Language :: Python", 24 | "Programming Language :: Python :: 3 :: Only", 25 | "Programming Language :: Python :: 3.10", 26 | "Programming Language :: Python :: 3.11", 27 | "Programming Language :: Python :: 3.12", 28 | "Programming Language :: Python :: Implementation :: CPython", 29 | "Topic :: Scientific/Engineering", 30 | "Topic :: Scientific/Engineering :: Atmospheric Science", 31 | ] 32 | description = "Vectorized interpolators for Nd atmospheric and oceanographic data" 33 | dynamic = [ 34 | "dependencies", 35 | "readme", 36 | "version", 37 | ] 38 | keywords = [ 39 | "atmospheric-science", 40 | "cython", 41 | "interpolation", 42 | "numpy", 43 | "python", 44 | "ocean-science", 45 | ] 46 | license = "BSD-3-Clause" 47 | license-files = ["LICENSE"] 48 | name = "stratify" 49 | requires-python = ">=3.10" 50 | 51 | [project.urls] 52 | Code = "https://github.com/SciTools-incubator/python-stratify" 53 | Issues = "https://github.com/SciTools-incubator/python-stratify/issues" 54 | 55 | [tool.setuptools] 56 | zip-safe = false 57 | 58 | [tool.setuptools.dynamic] 59 | dependencies = {file = "requirements/pypi-core.txt"} 60 | readme = {file = "README.md", content-type = "text/markdown"} 61 | 62 | [tool.setuptools.packages.find] 63 | include = ["stratify*"] 64 | where = ["src"] 65 | 66 | [tool.setuptools_scm] 67 | write_to = "src/stratify/_version.py" 68 | local_scheme = "dirty-tag" 69 | 70 | [tool.check-manifest] 71 | ignore = [ 72 | "src/stratify/_conservative.c", 73 | "src/stratify/_version.py", 74 | "src/stratify/_vinterp.c", 75 | ] 76 | 77 | [tool.pytest.ini_options] 78 | addopts = "-ra -v --doctest-modules" 79 | testpaths = ["src/stratify"] 80 | 81 | [tool.mypy] 82 | disable_error_code = ["call-arg", "no-untyped-def", "no-untyped-call", "attr-defined", "misc", "index", "var-annotated", "assignment"] 83 | enable_error_code = ["ignore-without-code", "truthy-bool", "redundant-expr"] 84 | warn_unreachable = true 85 | strict = true 86 | 87 | [tool.numpydoc_validation] 88 | checks = [ 89 | "all", # Enable all numpydoc validation rules, apart from the following: 90 | 91 | # -> Docstring text (summary) should start in the line immediately 92 | # after the opening quotes (not in the same line, or leaving a 93 | # blank line in between) 94 | "GL01", # Permit summary line on same line as docstring opening quotes. 95 | 96 | # -> Closing quotes should be placed in the line after the last text 97 | # in the docstring (do not close the quotes in the same line as 98 | # the text, or leave a blank line between the last text and the 99 | # quotes) 100 | "GL02", # Permit a blank line before docstring closing quotes. 101 | 102 | # -> Double line break found; please use only one blank line to 103 | # separate sections or paragraphs, and do not leave blank lines 104 | # at the end of docstrings 105 | "GL03", # Ignoring. 106 | 107 | "GL08", # The object does not have a docstring 108 | 109 | "PR01", # Parameters {missing_params} not documented 110 | "PR02", # Unknown parameters {unknown_params} 111 | "PR10", # Parameter "{param_name}" requires a space before the colon separating the parameter name and type 112 | 113 | "RT04", # Return value description should start with a capital letter 114 | 115 | "SS06", # Summary should fit in a single line 116 | 117 | 118 | # -> See Also section not found 119 | "SA01", # Not all docstrings require a "See Also" section. 120 | 121 | # -> No extended summary found 122 | "ES01", # Not all docstrings require an "Extended Summary" section. 123 | 124 | # -> No examples section found 125 | "EX01", # Not all docstrings require an "Examples" section. 126 | 127 | # -> No Yields section found 128 | "YD01", # Not all docstrings require a "Yields" section. 129 | 130 | # Record temporarily ignored checks below; will be reviewed at a later date: 131 | 132 | ] 133 | exclude = [ 134 | '\.__eq__$', 135 | '\.__ne__$', 136 | '\.__repr__$', 137 | ] 138 | 139 | [tool.ruff] 140 | line-length = 88 141 | 142 | [tool.ruff.format] 143 | preview = false 144 | 145 | [tool.ruff.lint] 146 | ignore = [ 147 | 148 | # flake8-annotations (ANN) 149 | # https://docs.astral.sh/ruff/rules/#flake8-annotations-ann 150 | "ANN001", # Missing type annotation for function argument {name} 151 | "ANN002", # Missing type annotation for *{name} 152 | "ANN003", # Missing type annotation for **{name} 153 | "ANN201", # Missing return type annotation for public function {name} 154 | "ANN202", # Missing return type annotation for private function {name} 155 | "ANN204", # Missing return type annotation for special method {name} 156 | 157 | "ARG002", # Unused method argument: {name} 158 | "ARG003", # Unused class method argument: {name} 159 | 160 | # flake8-bugbear (B) 161 | # https://docs.astral.sh/ruff/rules/#flake8-bugbear-b 162 | "B028", # No explicit stacklevel keyword argument found 163 | 164 | # flake8-comprehensions (C4) 165 | # https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 166 | "C405", # Unnecessary {obj_type} literal (rewrite as a set literal) 167 | "C419", # Unnecessary list comprehension 168 | 169 | # flake8-commas (COM) 170 | # https://docs.astral.sh/ruff/rules/#flake8-commas-com 171 | "COM812", # Trailing comma missing. 172 | "COM819", # Trailing comma prohibited. 173 | 174 | # pydocstyle (D) 175 | # https://docs.astral.sh/ruff/rules/#pydocstyle-d 176 | "D100", # Missing docstring in public module 177 | "D101", # Missing docstring in public class 178 | "D102", # Missing docstring in public method 179 | "D103", # Missing docstring in public function 180 | "D104", # Missing docstring in public package 181 | "D106", # Missing docstring in public nested class 182 | "D205", # 1 blank line required between summary line and description 183 | 184 | # https://docs.astral.sh/ruff/rules/#eradicate-era 185 | "ERA001", # Found commented-out code 186 | 187 | # flake8-boolean-trap (FBT) 188 | # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt 189 | "FBT002", # Boolean default positional argument in function definition 190 | 191 | # flake8-implicit-str-concat (ISC) 192 | # https://docs.astral.sh/ruff/rules/single-line-implicit-string-concatenation/ 193 | # NOTE: This rule may cause conflicts when used with "ruff format". 194 | "ISC001", # Implicitly concatenate string literals on one line. 195 | 196 | # pep8-naming (N) 197 | # https://docs.astral.sh/ruff/rules/#pep8-naming-n 198 | "N801", # Class name {name} should use CapWords convention 199 | 200 | # Refactor (R) 201 | # https://docs.astral.sh/ruff/rules/#refactor-r 202 | "PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable 203 | 204 | # flake8-pytest-style (PT) 205 | # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt 206 | "PT009", # Use a regular assert instead of unittest-style {assertion} 207 | "PT027", # Use pytest.raises instead of unittest-style {assertion} 208 | 209 | # flake8-return (RET) 210 | # https://docs.astral.sh/ruff/rules/#flake8-return-ret 211 | "RET504", # Unnecessary assignment to {name} before return statement 212 | 213 | # Ruff-specific rules (RUF) 214 | # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf 215 | "RUF005", # Consider {expression} instead of concatenation 216 | "RUF012", # Mutable class attributes should be annotated with typing.ClassVar 217 | 218 | # flake8-self (SLF) 219 | # https://docs.astral.sh/ruff/rules/#flake8-self-slf 220 | "SLF001", # Private member accessed: {access} 221 | 222 | # flake8-print (T20) 223 | # https://docs.astral.sh/ruff/rules/#flake8-print-t20 224 | "T201", # print found 225 | 226 | ] 227 | preview = false 228 | select = [ 229 | "ALL", 230 | # list specific rules to include that is skipped using numpy convention. 231 | "D212", # Multi-line docstring summary should start at the first line 232 | ] 233 | 234 | [tool.ruff.lint.isort] 235 | force-sort-within-sections = true 236 | # Change to match specific package name: 237 | known-first-party = ["iris"] 238 | 239 | [tool.ruff.lint.per-file-ignores] 240 | # All test scripts 241 | 242 | # Change to match specific package path: 243 | "lib/iris/tests/*.py" = [ 244 | # https://docs.astral.sh/ruff/rules/undocumented-public-module/ 245 | "D100", # Missing docstring in public module 246 | "D205", # 1 blank line required between summary line and description 247 | "D401", # 1 First line of docstring should be in imperative mood 248 | ] 249 | 250 | [tool.ruff.lint.pydocstyle] 251 | convention = "numpy" 252 | 253 | [tool.codespell] 254 | ignore-words-list = "ND,Nd" 255 | 256 | [tool.repo-review] 257 | # These are a list of the currently failing tests to be fixed later: 258 | ignore = [ 259 | "PY004", # Has docs folder 260 | "PP302", # Sets a minimum pytest to at least 6 261 | "PP304", # Sets the log level in pytest 262 | "PP305", # Specifies xfail_strict 263 | "PP306", # Specifies strict config 264 | "PP307", # Specifies strict markers 265 | "PP309", # Filter warnings specified 266 | "PY007", # Supports an easy task runner (nox or tox) 267 | "PP003", # Does not list wheel as a build-dep 268 | "PC111", # Uses blacken-docs 269 | "PC170", # Uses PyGrep hooks (only needed if rST present) 270 | "PC180", # Uses a markdown formatter 271 | "RTD100" # Uses ReadTheDocs (pyproject config) 272 | ] 273 | -------------------------------------------------------------------------------- /requirements/locks/README.md: -------------------------------------------------------------------------------- 1 | # ⚠️ 2 | 3 | This directory contains auto-generated `conda-lock` environment files for each `python` distribution supported by `python-stratify`. 4 | 5 | Please **do not** manually edit these files as they will be overwritten by the GHA CI workflow. 6 | -------------------------------------------------------------------------------- /requirements/locks/py310-linux-64.lock: -------------------------------------------------------------------------------- 1 | # Generated by conda-lock. 2 | # platform: linux-64 3 | # input_hash: 526538341013c7b9d1904478a3fdd47179375908e4675950d963788dc53c5125 4 | @EXPLICIT 5 | https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 6 | https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-headers-1.21.0-ha770c72_0.conda#11b1bed92c943d3b741e8a1e1a815ed1 7 | https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.12.0-h3f2d84a_0.conda#d76872d096d063e226482c99337209dc 8 | https://conda.anaconda.org/conda-forge/noarch/python_abi-3.10-7_cp310.conda#44e871cba2b162368476a84b8d040b6c 9 | https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda#4222072737ccff51314b5ece9c7d6f5a 10 | https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda#95db94f75ba080a22eb623590993167b 11 | https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda#01f8d123c96816249efd255a31ad7712 12 | https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda#fbe7d535ff9d3a168c148e07358cd5b1 13 | https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d 14 | https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda#ea8ac52380885ed41c1baa8f1d6d2b93 15 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-common-0.12.3-hb9d3cd8_0.conda#8448031a22c697fac3ed98d69e8a9160 16 | https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda#f7f0d6cc2dc986d42ac2689ec88192be 17 | https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.1.0-hb9d3cd8_2.conda#41b599ed2b02abcfdd84302bff174b23 18 | https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.24-h86f0d12_0.conda#64f0c503da58ec25ebd359e4d990afa8 19 | https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda#db0bfbe7dd197b68ad5f30333bae6ce0 20 | https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda#ede4673863426c0883c0063d853bbd85 21 | https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda#ddca86c7040dd0e73b2b69bd7833d225 22 | https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda#01de444988ed960031dbe84cf4f9b1fc 23 | https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda#e796ff8ddc598affdf7c173d6145f087 24 | https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.0-hb9d3cd8_0.conda#9fa334557db9f63da6c9285fd2a48638 25 | https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda#a76fd702c93cd2dfd89eff30a5fd45a8 26 | https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda#1cb1c67961f6dd257eae9e9691b341aa 27 | https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.10.0-h202a827_0.conda#0f98f3e95272d118f7931b6bef69bfe5 28 | https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.5.0-h851e524_0.conda#63f790534398730f59e1b899c3644d4a 29 | https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda#edb0dca6bc32e4f4789199455a1dbeb8 30 | https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda#47e340acb35de30501a76c7c799c41d7 31 | https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda#de356753cfdbffcde5bb1e86e3aa6cd0 32 | https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda#b3c17d95b5a10c6e64a21fa17573e70e 33 | https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb9d3cd8_0.conda#f6ebe2cb3f82ba6c057dde5d9debe4f7 34 | https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb9d3cd8_0.conda#8035c64cb77ed555e3f150b7b3972480 35 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.9.2-h5e3027f_0.conda#0ead3ab65460d51efb27e5186f50f8e4 36 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-compression-0.3.1-hafb2847_5.conda#e96cc668c0f9478f5771b37d57f90386 37 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.2.4-hafb2847_0.conda#65853df44b7e4029d978c50be888ed89 38 | https://conda.anaconda.org/conda-forge/linux-64/aws-checksums-0.2.7-hafb2847_1.conda#6d28d50637fac4f081a0903b4b33d56d 39 | https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda#62ee74e96c5ebb0af99386de58cf9553 40 | https://conda.anaconda.org/conda-forge/linux-64/gflags-2.2.2-h5888daf_1005.conda#d411fc29e338efb48c5fd4576d71d881 41 | https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 42 | https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h0aef613_1.conda#9344155d33912347b37f0ae6c410a835 43 | https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250127.1-cxx17_hbbce691_0.conda#00290e549c5c8a32cc271020acc9ec6b 44 | https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.1.0-hb9d3cd8_2.conda#9566f0bd264fbd463002e759b8a82401 45 | https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.1.0-hb9d3cd8_2.conda#06f70867945ea6a84d35836af780f1de 46 | https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda#c277e0a4d549b03ac1e9d6cbbe3d017b 47 | https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda#172bf1cd1ff8629f2b1179945ed45055 48 | https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-hf998b51_1.conda#a1cfcc585f0c42bf8d5546bb1dfb668d 49 | https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda#f92e6e0a3c0c0c85561ef61aa59d555d 50 | https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda#30fd6e37fe21f86f4bd26d6ee73eeec7 51 | https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.47-h943b412_0.conda#55199e2ae2c3651f6f9b2a447b47bdc9 52 | https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.0-hee588c1_0.conda#71888e92098d0f8c41b09a671ad289bc 53 | https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda#eecce068c7e4eddeb169591baac20ac4 54 | https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda#9d2072af184b5caa29492bf2344597bb 55 | https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda#40b61aab5c7ba9ff276c41cfffe6b80b 56 | https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda#92ed62436b625154323d40d5f2f11dd7 57 | https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda#5aa797f8787fe7a17d1b0821485b5adc 58 | https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda#9de5350a85c4a20c685259b889aa6393 59 | https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda#283b96675859b20a825f8fa30f311446 60 | https://conda.anaconda.org/conda-forge/linux-64/s2n-1.5.21-h7ab7c64_0.conda#28b5a7895024a754249b2ad7de372faa 61 | https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.1-h8bd8927_1.conda#3b3e64af585eadfb52bb90b553db5edf 62 | https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda#a0116df4f4ed05c303811a837d5b39d8 63 | https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae 64 | https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-hb9d3cd8_2.conda#c9f075ab2f33b3bbee9e62d4ad0a6cd8 65 | https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda#6432cb5d4ac0046c3ac0a8a0f95842f9 66 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.19.1-hdfce8c9_3.conda#012df4026887e82115796d4e664abe2d 67 | https://conda.anaconda.org/conda-forge/linux-64/glog-0.7.1-hbabe93e_0.conda#ff862eebdfeb2fd048ae9dc92510baca 68 | https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda#8b189310083baabfb622af68fd9d3ae3 69 | https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda#3f43953b7d3fb3aaa1d0d0723d91e368 70 | https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2#c965a5aa0d5c1c37ffc62dff36e28400 71 | https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.13.3-h48d6fc4_1.conda#3c255be50a506c50765a93a6644f32fe 72 | https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.64.0-h161d5f1_0.conda#19e57602824042dfd0446292ef90488b 73 | https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.29-pthreads_h94d23a6_0.conda#0a4d0252248ef9a0f88f2ba8b8a08e12 74 | https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.29.3-h501fc15_1.conda#edb86556cf4a0c133e7932a1597ff236 75 | https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hba17884_3.conda#545e93a513c10603327c76c15485e946 76 | https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.21.0-h0e7cc3e_0.conda#dcb95c0a98ba9ff737f7ae482aef7833 77 | https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.0-hf01ce69_5.conda#e79a094918988bb1807462cd42c83962 78 | https://conda.anaconda.org/conda-forge/linux-64/python-3.10.18-hd6af730_0_cpython.conda#4ea0c77cdcb0b81813a0436b162d7316 79 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.5.4-haaa725d_10.conda#ed15f12bd23f3861d61e3d71c0e639ee 80 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.1-hcfde5e4_4.conda#1609e2c1c556f66dbfff36d376c0d0e4 81 | https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py310hf71b8c6_2.conda#bf502c169c71e3c6ac0d6175addfacc2 82 | https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda#c33eeaaa33f45031be34cda513df39b6 83 | https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_1.conda#57df494053e17dce2ac3a0b33e1b2a2e 84 | https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda#40fe4284b8b5835a9073a645139f35af 85 | https://conda.anaconda.org/conda-forge/noarch/click-8.2.1-pyh707e725_0.conda#94b550b8d3a614dbd326af798c7dfb40 86 | https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda#364ba6c9fb03886ac979b482f39ebb92 87 | https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda#962b9857ee8e7018c22f2776ffa0b2d7 88 | https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py310had8cdd9_0.conda#b630fe36f0b621d23e74872dc4fd2bd7 89 | https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.9-pyhd8ed1ab_1.conda#8d88f4a2242e6b96f9ecff9a6a05b2f1 90 | https://conda.anaconda.org/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda#4547b39256e296bb758166893e909a7c 91 | https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.5.1-pyhd8ed1ab_0.conda#2d2c9ef879a7e64e2dc657b09272c2b6 92 | https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda#0a802cb9888dd14eeefc611f05c40b6e 93 | https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda#8e6923fc12f1fe8f8c4e5c9f343256ac 94 | https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda#39a4f67be3286c86d696df570b1201b7 95 | https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda#6837f3eff7dcea42ecd714ce1ac2b108 96 | https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.17-h717163a_0.conda#000e85703f0fd9594c81710dd5066471 97 | https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_h59b9bed_openblas.conda#728dbebd0f7a20337218beacffd37916 98 | https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.14.1-h332b0f4_0.conda#45f6713cb00f124af300342512219182 99 | https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.13.3-ha770c72_1.conda#51f5be229d83ecd401fb369ab96ae669 100 | https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.8-h4bc477f_0.conda#14dbe05b929e329dbaa6f2d0aa19466d 101 | https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 102 | https://conda.anaconda.org/conda-forge/linux-64/lz4-4.4.4-py310h80b8a69_0.conda#5081569b9d3c98c1969d38a595b3cd1f 103 | https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py310h89163eb_1.conda#8ce3f0332fd6de0d737e2911d329523f 104 | https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.1.0-py310h3788b33_0.conda#6b586fb03d84e5bfbb1a8a3d9e2c9b60 105 | https://conda.anaconda.org/conda-forge/noarch/narwhals-1.41.1-pyhe01879c_0.conda#b8c443460cd4f4130a95f7f9a92ef21b 106 | https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.3-h5fbd93e_0.conda#9e5816bc95d285c115a3ebc2f8563564 107 | https://conda.anaconda.org/conda-forge/linux-64/orc-2.1.2-h17f744e_0.conda#ef7f9897a244b2023a066c22a1089ce4 108 | https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda#58335b26c38bf4a20f399384c33cbcf9 109 | https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.8-pyhe01879c_0.conda#424844562f5d337077b445ec6b1398a7 110 | https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda#7da7ccd349dbf6487a7778579d2bb971 111 | https://conda.anaconda.org/conda-forge/linux-64/psutil-7.0.0-py310ha75aee5_0.conda#da7d592394ff9084a23f62a1186451a2 112 | https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda#12c566707c80111f9799308d9e265aef 113 | https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda#232fb4577b6687b2d503ef8e254270c9 114 | https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda#461219d1a5bd61342293efa2c0c90eac 115 | https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.2-pyhd8ed1ab_0.conda#88476ae6ebd24f39261e0854ac244f33 116 | https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda#bc8e3267d44011051f2eb14d22fb0960 117 | https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py310h89163eb_2.conda#fd343408e64cf1e273ab7c710da374db 118 | https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_3.conda#6f445fb139c356f903746b2b91bbe786 119 | https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda#4de79c071274a53dcaf2a8c749d1499e 120 | https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda#a451d576819089b0d672f18768be0f65 121 | https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda#0401a17ae845fa72c7210e206ec5647d 122 | https://conda.anaconda.org/conda-forge/noarch/tblib-3.1.0-pyhd8ed1ab_0.conda#a15c62b8a306b8978f094f76da2f903f 123 | https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_1.conda#b0dd904de08b7db706167240bf37b164 124 | https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda#ac944244f1fed2eb49bae07193ae8215 125 | https://conda.anaconda.org/conda-forge/noarch/toolz-1.0.0-pyhd8ed1ab_1.conda#40d0ed782a8aaa16ef248e68c06c168d 126 | https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.1-py310ha75aee5_0.conda#6f3da1072c0c4d2a1beb1e84615f7c9c 127 | https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.14.0-pyhe01879c_0.conda#2adcd9bb86f656d3d43bf84af59a1faf 128 | https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda#75cb7132eb58d97896e173ef12ac9986 129 | https://conda.anaconda.org/conda-forge/noarch/xyzservices-2025.4.0-pyhd8ed1ab_0.conda#5663fa346821cd06dc1ece2c2600be2c 130 | https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda#e52c2ef711ccf31bb7f70ca87d144b9e 131 | https://conda.anaconda.org/conda-forge/noarch/zipp-3.22.0-pyhd8ed1ab_0.conda#234be740b00b8e41567e5b0ed95aaba9 132 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.9.0-hd3ec96c_11.conda#29deff273a14d358bfe58065b30a0085 133 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.13.1-h7b3935a_0.conda#0ad7e0a85f207c2e25399d8217d8b8e5 134 | https://conda.anaconda.org/conda-forge/linux-64/azure-core-cpp-1.14.0-h5cfcd09_0.conda#0a8838771cc2e985cd295e01ae83baf1 135 | https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py310h8deb56e_0.conda#1fc24a3196ad5ede2a68148be61894f4 136 | https://conda.anaconda.org/conda-forge/linux-64/coverage-7.8.2-py310h89163eb_0.conda#5ca8ab35287adc83b2d1996e5c2ac14c 137 | https://conda.anaconda.org/conda-forge/linux-64/cytoolz-1.0.1-py310ha75aee5_0.conda#d0be1adaa04a03aed745f3d02afb59ce 138 | https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda#72e42d28960d875c7654614f8b50939a 139 | https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda#b4754fb1bdcb70c8fd54f918301582c6 140 | https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda#63ccfdc3a3ce25b027b8767eb722fca8 141 | https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda#446bd6c8cb26050d528881df495ce646 142 | https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_he106b2a_openblas.conda#abb32c727da370c481a1c206f5159ce9 143 | https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.71.0-h8e591d7_1.conda#c3cfd72cbb14113abee7bbd86f44ad69 144 | https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_h7ac8fdf_openblas.conda#452b98eafe050ecff932f0ec832dd03f 145 | https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda#7ba3f09fceae6a120d664217e58fe686 146 | https://conda.anaconda.org/conda-forge/noarch/partd-1.4.2-pyhd8ed1ab_0.conda#0badf9c54e24cecfb0ad2f99d680c163 147 | https://conda.anaconda.org/conda-forge/linux-64/pillow-11.2.1-py310h7e6dc6c_0.conda#5645a243d90adb50909b9edc209d84fe 148 | https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda#32d0781ace05105cc99af55d36cbec7c 149 | https://conda.anaconda.org/conda-forge/linux-64/prometheus-cpp-1.3.0-ha5d0236_0.conda#a83f6a2fdc079e643237887a37460668 150 | https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhff2d567_1.conda#5ba79d7c71f03c678c8ead841f347d6e 151 | https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.14.0-h32cad80_0.conda#a1cdd40fc962e2f7944bc19e01c7e584 152 | https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.31.2-pyhd8ed1ab_0.conda#c0600c1b374efa7a1ff444befee108ca 153 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.8.0-h670bddd_2.conda#9c9ec5641de50fff32aa71e5296e627e 154 | https://conda.anaconda.org/conda-forge/linux-64/azure-identity-cpp-1.10.0-h113e628_0.conda#73f73f60854f325a55f1d31459f2ab73 155 | https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.8.0-h736e048_1.conda#13de36be8de3ae3f05ba127631599213 156 | https://conda.anaconda.org/conda-forge/noarch/dask-core-2025.5.1-pyhd8ed1ab_0.conda#8f0ef561cd615a17df3256742a3457c4 157 | https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-2.36.0-hc4361e1_1.conda#ae36e6296a8dd8e8a9a8375965bf6398 158 | https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-1.21.0-hd1b1c89_0.conda#4b25cd8720fd8d5319206e4f899f2707 159 | https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.6-py310hefbff90_0.conda#b0cea2c364bf65cd19e023040eeab05d 160 | https://conda.anaconda.org/conda-forge/noarch/pytest-8.4.0-pyhd8ed1ab_0.conda#516d31f063ce7e49ced17f105b63a1f1 161 | https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-8.3.1-pyhd8ed1ab_0.conda#996376098e3648237b3efb0e0ad460c1 162 | https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310h3788b33_5.conda#e05b0475166b68c9dc4d7937e0315654 163 | https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py310ha75aee5_2.conda#f9254b5b0193982416b91edcb4b2676f 164 | https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.32.8-h0ead740_2.conda#df290cfb1fc024a1dc9e30f6167579a5 165 | https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.13.0-h3cf044e_1.conda#7eb66060455c7a47d9dcdbfa9f46579b 166 | https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.2-py310h3788b33_0.conda#b6420d29123c7c823de168f49ccdfe6a 167 | https://conda.anaconda.org/conda-forge/noarch/identify-2.6.12-pyhd8ed1ab_0.conda#84463b10c1eb198541cd54125c7efe90 168 | https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-storage-2.36.0-h0121fbd_1.conda#a0f7588c1f0a26d550e7bae4fb49427a 169 | https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.0-py310h5eaa309_0.conda#379844614e3a24e59e59d8c69c6e9403 170 | https://conda.anaconda.org/conda-forge/noarch/pytest-cov-6.1.1-pyhd8ed1ab_0.conda#1e35d8f975bc0e984a19819aa91c440a 171 | https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda#c1e349028e0052c4eea844e94f773065 172 | https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.510-h4607db7_10.conda#96f240f245fe2e031ec59dbb3044bd6c 173 | https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.12.0-ha633028_1.conda#7c1980f89dd41b097549782121a73490 174 | https://conda.anaconda.org/conda-forge/noarch/bokeh-3.7.3-pyhd8ed1ab_0.conda#708d2f99b8a2c833ff164a225a265e76 175 | https://conda.anaconda.org/conda-forge/noarch/distributed-2025.5.1-pyhd8ed1ab_0.conda#d2949f56a1479507e36e847681903376 176 | https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.2.0-pyha770c72_0.conda#17e487cc8b5507cd3abc09398cf27949 177 | https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda#a9b9368f3701a417eac9edbcae7cb737 178 | https://conda.anaconda.org/conda-forge/noarch/codecov-2.1.13-pyhd8ed1ab_1.conda#d924fe46139596ebc3d4d424ec39ed51 179 | https://conda.anaconda.org/conda-forge/linux-64/libarrow-20.0.0-h314c690_6_cpu.conda#36f91dcd71682a790981f59b1f25e81a 180 | https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-20.0.0-hcb10f89_6_cpu.conda#d30eef9d28672b910decb5c11b00e09e 181 | https://conda.anaconda.org/conda-forge/linux-64/libparquet-20.0.0-h081d1f1_6_cpu.conda#640b79f7a2c691ab4101345c5d87a540 182 | https://conda.anaconda.org/conda-forge/linux-64/pyarrow-core-20.0.0-py310hac404ae_0_cpu.conda#01d158af8c0d9c2abc09a29ac39284a5 183 | https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-20.0.0-hcb10f89_6_cpu.conda#528a0f333effc7e8b1ef1c40c6437bae 184 | https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-20.0.0-h1bed206_6_cpu.conda#b051b8e680398c103567e331ce3a339c 185 | https://conda.anaconda.org/conda-forge/linux-64/pyarrow-20.0.0-py310hff52083_0.conda#e66347b55094a2cba9551ec4524fd136 186 | https://conda.anaconda.org/conda-forge/noarch/dask-2025.5.1-pyhd8ed1ab_0.conda#97ee3885b8de1d729c40f28c2dddb335 187 | -------------------------------------------------------------------------------- /requirements/locks/py311-linux-64.lock: -------------------------------------------------------------------------------- 1 | # Generated by conda-lock. 2 | # platform: linux-64 3 | # input_hash: eb4d750b88cb5e2276a31938ef8c7fc097c0a69fae33ac90079bdbabc8ebbc41 4 | @EXPLICIT 5 | https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 6 | https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-headers-1.21.0-ha770c72_0.conda#11b1bed92c943d3b741e8a1e1a815ed1 7 | https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.12.0-h3f2d84a_0.conda#d76872d096d063e226482c99337209dc 8 | https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-7_cp311.conda#6320dac78b3b215ceac35858b2cfdb70 9 | https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda#4222072737ccff51314b5ece9c7d6f5a 10 | https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda#95db94f75ba080a22eb623590993167b 11 | https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda#01f8d123c96816249efd255a31ad7712 12 | https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda#fbe7d535ff9d3a168c148e07358cd5b1 13 | https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d 14 | https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda#ea8ac52380885ed41c1baa8f1d6d2b93 15 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-common-0.12.3-hb9d3cd8_0.conda#8448031a22c697fac3ed98d69e8a9160 16 | https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda#f7f0d6cc2dc986d42ac2689ec88192be 17 | https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.1.0-hb9d3cd8_2.conda#41b599ed2b02abcfdd84302bff174b23 18 | https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.24-h86f0d12_0.conda#64f0c503da58ec25ebd359e4d990afa8 19 | https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda#db0bfbe7dd197b68ad5f30333bae6ce0 20 | https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda#ede4673863426c0883c0063d853bbd85 21 | https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda#ddca86c7040dd0e73b2b69bd7833d225 22 | https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda#01de444988ed960031dbe84cf4f9b1fc 23 | https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda#e796ff8ddc598affdf7c173d6145f087 24 | https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.0-hb9d3cd8_0.conda#9fa334557db9f63da6c9285fd2a48638 25 | https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda#a76fd702c93cd2dfd89eff30a5fd45a8 26 | https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda#1cb1c67961f6dd257eae9e9691b341aa 27 | https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.10.0-h202a827_0.conda#0f98f3e95272d118f7931b6bef69bfe5 28 | https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.5.0-h851e524_0.conda#63f790534398730f59e1b899c3644d4a 29 | https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda#edb0dca6bc32e4f4789199455a1dbeb8 30 | https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda#47e340acb35de30501a76c7c799c41d7 31 | https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda#de356753cfdbffcde5bb1e86e3aa6cd0 32 | https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda#b3c17d95b5a10c6e64a21fa17573e70e 33 | https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb9d3cd8_0.conda#f6ebe2cb3f82ba6c057dde5d9debe4f7 34 | https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb9d3cd8_0.conda#8035c64cb77ed555e3f150b7b3972480 35 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.9.2-h5e3027f_0.conda#0ead3ab65460d51efb27e5186f50f8e4 36 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-compression-0.3.1-hafb2847_5.conda#e96cc668c0f9478f5771b37d57f90386 37 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.2.4-hafb2847_0.conda#65853df44b7e4029d978c50be888ed89 38 | https://conda.anaconda.org/conda-forge/linux-64/aws-checksums-0.2.7-hafb2847_1.conda#6d28d50637fac4f081a0903b4b33d56d 39 | https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda#62ee74e96c5ebb0af99386de58cf9553 40 | https://conda.anaconda.org/conda-forge/linux-64/gflags-2.2.2-h5888daf_1005.conda#d411fc29e338efb48c5fd4576d71d881 41 | https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 42 | https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h0aef613_1.conda#9344155d33912347b37f0ae6c410a835 43 | https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250127.1-cxx17_hbbce691_0.conda#00290e549c5c8a32cc271020acc9ec6b 44 | https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.1.0-hb9d3cd8_2.conda#9566f0bd264fbd463002e759b8a82401 45 | https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.1.0-hb9d3cd8_2.conda#06f70867945ea6a84d35836af780f1de 46 | https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda#c277e0a4d549b03ac1e9d6cbbe3d017b 47 | https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda#172bf1cd1ff8629f2b1179945ed45055 48 | https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-hf998b51_1.conda#a1cfcc585f0c42bf8d5546bb1dfb668d 49 | https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda#f92e6e0a3c0c0c85561ef61aa59d555d 50 | https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda#30fd6e37fe21f86f4bd26d6ee73eeec7 51 | https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.47-h943b412_0.conda#55199e2ae2c3651f6f9b2a447b47bdc9 52 | https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.0-hee588c1_0.conda#71888e92098d0f8c41b09a671ad289bc 53 | https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda#eecce068c7e4eddeb169591baac20ac4 54 | https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda#9d2072af184b5caa29492bf2344597bb 55 | https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda#40b61aab5c7ba9ff276c41cfffe6b80b 56 | https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda#92ed62436b625154323d40d5f2f11dd7 57 | https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda#5aa797f8787fe7a17d1b0821485b5adc 58 | https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda#9de5350a85c4a20c685259b889aa6393 59 | https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda#283b96675859b20a825f8fa30f311446 60 | https://conda.anaconda.org/conda-forge/linux-64/s2n-1.5.21-h7ab7c64_0.conda#28b5a7895024a754249b2ad7de372faa 61 | https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.1-h8bd8927_1.conda#3b3e64af585eadfb52bb90b553db5edf 62 | https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda#a0116df4f4ed05c303811a837d5b39d8 63 | https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae 64 | https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-hb9d3cd8_2.conda#c9f075ab2f33b3bbee9e62d4ad0a6cd8 65 | https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda#6432cb5d4ac0046c3ac0a8a0f95842f9 66 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.19.1-hdfce8c9_3.conda#012df4026887e82115796d4e664abe2d 67 | https://conda.anaconda.org/conda-forge/linux-64/glog-0.7.1-hbabe93e_0.conda#ff862eebdfeb2fd048ae9dc92510baca 68 | https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda#8b189310083baabfb622af68fd9d3ae3 69 | https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda#3f43953b7d3fb3aaa1d0d0723d91e368 70 | https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2#c965a5aa0d5c1c37ffc62dff36e28400 71 | https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.13.3-h48d6fc4_1.conda#3c255be50a506c50765a93a6644f32fe 72 | https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.64.0-h161d5f1_0.conda#19e57602824042dfd0446292ef90488b 73 | https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.29-pthreads_h94d23a6_0.conda#0a4d0252248ef9a0f88f2ba8b8a08e12 74 | https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.29.3-h501fc15_1.conda#edb86556cf4a0c133e7932a1597ff236 75 | https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hba17884_3.conda#545e93a513c10603327c76c15485e946 76 | https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.21.0-h0e7cc3e_0.conda#dcb95c0a98ba9ff737f7ae482aef7833 77 | https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.0-hf01ce69_5.conda#e79a094918988bb1807462cd42c83962 78 | https://conda.anaconda.org/conda-forge/linux-64/python-3.11.13-h9e4cc4f_0_cpython.conda#8c399445b6dc73eab839659e6c7b5ad1 79 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.5.4-haaa725d_10.conda#ed15f12bd23f3861d61e3d71c0e639ee 80 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.1-hcfde5e4_4.conda#1609e2c1c556f66dbfff36d376c0d0e4 81 | https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py311hfdbb021_2.conda#d21daab070d76490cb39a8f1d1729d79 82 | https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda#c33eeaaa33f45031be34cda513df39b6 83 | https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_1.conda#57df494053e17dce2ac3a0b33e1b2a2e 84 | https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda#40fe4284b8b5835a9073a645139f35af 85 | https://conda.anaconda.org/conda-forge/noarch/click-8.2.1-pyh707e725_0.conda#94b550b8d3a614dbd326af798c7dfb40 86 | https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda#364ba6c9fb03886ac979b482f39ebb92 87 | https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda#962b9857ee8e7018c22f2776ffa0b2d7 88 | https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py311ha3e34f5_0.conda#5a4b11606b86b0b760ea7220c485e475 89 | https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.9-pyhd8ed1ab_1.conda#8d88f4a2242e6b96f9ecff9a6a05b2f1 90 | https://conda.anaconda.org/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda#4547b39256e296bb758166893e909a7c 91 | https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.5.1-pyhd8ed1ab_0.conda#2d2c9ef879a7e64e2dc657b09272c2b6 92 | https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda#0a802cb9888dd14eeefc611f05c40b6e 93 | https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda#8e6923fc12f1fe8f8c4e5c9f343256ac 94 | https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda#39a4f67be3286c86d696df570b1201b7 95 | https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda#6837f3eff7dcea42ecd714ce1ac2b108 96 | https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.17-h717163a_0.conda#000e85703f0fd9594c81710dd5066471 97 | https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_h59b9bed_openblas.conda#728dbebd0f7a20337218beacffd37916 98 | https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.14.1-h332b0f4_0.conda#45f6713cb00f124af300342512219182 99 | https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.13.3-ha770c72_1.conda#51f5be229d83ecd401fb369ab96ae669 100 | https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.8-h4bc477f_0.conda#14dbe05b929e329dbaa6f2d0aa19466d 101 | https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 102 | https://conda.anaconda.org/conda-forge/linux-64/lz4-4.4.4-py311h8c6ae76_0.conda#cb99a4a8a0828c76d2869d807ef92f7a 103 | https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py311h2dc5d0c_1.conda#6565a715337ae279e351d0abd8ffe88a 104 | https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.1.0-py311hd18a35c_0.conda#682f76920687f7d9283039eb542fdacf 105 | https://conda.anaconda.org/conda-forge/noarch/narwhals-1.41.1-pyhe01879c_0.conda#b8c443460cd4f4130a95f7f9a92ef21b 106 | https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.3-h5fbd93e_0.conda#9e5816bc95d285c115a3ebc2f8563564 107 | https://conda.anaconda.org/conda-forge/linux-64/orc-2.1.2-h17f744e_0.conda#ef7f9897a244b2023a066c22a1089ce4 108 | https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda#58335b26c38bf4a20f399384c33cbcf9 109 | https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.8-pyhe01879c_0.conda#424844562f5d337077b445ec6b1398a7 110 | https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda#7da7ccd349dbf6487a7778579d2bb971 111 | https://conda.anaconda.org/conda-forge/linux-64/psutil-7.0.0-py311h9ecbd09_0.conda#1a390a54b2752169f5ba4ada5a8108e4 112 | https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda#12c566707c80111f9799308d9e265aef 113 | https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda#232fb4577b6687b2d503ef8e254270c9 114 | https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda#461219d1a5bd61342293efa2c0c90eac 115 | https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.2-pyhd8ed1ab_0.conda#88476ae6ebd24f39261e0854ac244f33 116 | https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda#bc8e3267d44011051f2eb14d22fb0960 117 | https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py311h2dc5d0c_2.conda#014417753f948da1f70d132b2de573be 118 | https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_3.conda#6f445fb139c356f903746b2b91bbe786 119 | https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda#4de79c071274a53dcaf2a8c749d1499e 120 | https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda#a451d576819089b0d672f18768be0f65 121 | https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda#0401a17ae845fa72c7210e206ec5647d 122 | https://conda.anaconda.org/conda-forge/noarch/tblib-3.1.0-pyhd8ed1ab_0.conda#a15c62b8a306b8978f094f76da2f903f 123 | https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_1.conda#b0dd904de08b7db706167240bf37b164 124 | https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda#ac944244f1fed2eb49bae07193ae8215 125 | https://conda.anaconda.org/conda-forge/noarch/toolz-1.0.0-pyhd8ed1ab_1.conda#40d0ed782a8aaa16ef248e68c06c168d 126 | https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.1-py311h9ecbd09_0.conda#24e9f474abd101554b7a91313b9dfad6 127 | https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.14.0-pyhe01879c_0.conda#2adcd9bb86f656d3d43bf84af59a1faf 128 | https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda#75cb7132eb58d97896e173ef12ac9986 129 | https://conda.anaconda.org/conda-forge/noarch/xyzservices-2025.4.0-pyhd8ed1ab_0.conda#5663fa346821cd06dc1ece2c2600be2c 130 | https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda#e52c2ef711ccf31bb7f70ca87d144b9e 131 | https://conda.anaconda.org/conda-forge/noarch/zipp-3.22.0-pyhd8ed1ab_0.conda#234be740b00b8e41567e5b0ed95aaba9 132 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.9.0-hd3ec96c_11.conda#29deff273a14d358bfe58065b30a0085 133 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.13.1-h7b3935a_0.conda#0ad7e0a85f207c2e25399d8217d8b8e5 134 | https://conda.anaconda.org/conda-forge/linux-64/azure-core-cpp-1.14.0-h5cfcd09_0.conda#0a8838771cc2e985cd295e01ae83baf1 135 | https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py311hf29c0ef_0.conda#55553ecd5328336368db611f350b7039 136 | https://conda.anaconda.org/conda-forge/linux-64/coverage-7.8.2-py311h2dc5d0c_0.conda#21c1ef48cc2bf485e6d38c5611e91da2 137 | https://conda.anaconda.org/conda-forge/linux-64/cytoolz-1.0.1-py311h9ecbd09_0.conda#69a0a85acdcc5e6d0f1cc915c067ad4c 138 | https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda#72e42d28960d875c7654614f8b50939a 139 | https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda#b4754fb1bdcb70c8fd54f918301582c6 140 | https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda#63ccfdc3a3ce25b027b8767eb722fca8 141 | https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda#446bd6c8cb26050d528881df495ce646 142 | https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_he106b2a_openblas.conda#abb32c727da370c481a1c206f5159ce9 143 | https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.71.0-h8e591d7_1.conda#c3cfd72cbb14113abee7bbd86f44ad69 144 | https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_h7ac8fdf_openblas.conda#452b98eafe050ecff932f0ec832dd03f 145 | https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda#7ba3f09fceae6a120d664217e58fe686 146 | https://conda.anaconda.org/conda-forge/noarch/partd-1.4.2-pyhd8ed1ab_0.conda#0badf9c54e24cecfb0ad2f99d680c163 147 | https://conda.anaconda.org/conda-forge/linux-64/pillow-11.2.1-py311h1322bbf_0.conda#4c49bdabd1d4e09386dabc676fb6bd65 148 | https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda#32d0781ace05105cc99af55d36cbec7c 149 | https://conda.anaconda.org/conda-forge/linux-64/prometheus-cpp-1.3.0-ha5d0236_0.conda#a83f6a2fdc079e643237887a37460668 150 | https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhff2d567_1.conda#5ba79d7c71f03c678c8ead841f347d6e 151 | https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.14.0-h32cad80_0.conda#a1cdd40fc962e2f7944bc19e01c7e584 152 | https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.31.2-pyhd8ed1ab_0.conda#c0600c1b374efa7a1ff444befee108ca 153 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.8.0-h670bddd_2.conda#9c9ec5641de50fff32aa71e5296e627e 154 | https://conda.anaconda.org/conda-forge/linux-64/azure-identity-cpp-1.10.0-h113e628_0.conda#73f73f60854f325a55f1d31459f2ab73 155 | https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.8.0-h736e048_1.conda#13de36be8de3ae3f05ba127631599213 156 | https://conda.anaconda.org/conda-forge/noarch/dask-core-2025.5.1-pyhd8ed1ab_0.conda#8f0ef561cd615a17df3256742a3457c4 157 | https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-2.36.0-hc4361e1_1.conda#ae36e6296a8dd8e8a9a8375965bf6398 158 | https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-1.21.0-hd1b1c89_0.conda#4b25cd8720fd8d5319206e4f899f2707 159 | https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.6-py311h5d046bc_0.conda#babce4d9841ebfcee64249d98eb4e0d4 160 | https://conda.anaconda.org/conda-forge/noarch/pytest-8.4.0-pyhd8ed1ab_0.conda#516d31f063ce7e49ced17f105b63a1f1 161 | https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-8.3.1-pyhd8ed1ab_0.conda#996376098e3648237b3efb0e0ad460c1 162 | https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py311hd18a35c_5.conda#4e8447ca8558a203ec0577b4730073f3 163 | https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py311h9ecbd09_2.conda#ca02de88df1cc3cfc8f24766ff50cb3c 164 | https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.32.8-h0ead740_2.conda#df290cfb1fc024a1dc9e30f6167579a5 165 | https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.13.0-h3cf044e_1.conda#7eb66060455c7a47d9dcdbfa9f46579b 166 | https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.2-py311hd18a35c_0.conda#f8e440efa026c394461a45a46cea49fc 167 | https://conda.anaconda.org/conda-forge/noarch/identify-2.6.12-pyhd8ed1ab_0.conda#84463b10c1eb198541cd54125c7efe90 168 | https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-storage-2.36.0-h0121fbd_1.conda#a0f7588c1f0a26d550e7bae4fb49427a 169 | https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.0-py311h7db5c69_0.conda#805040d254f51cb15df55eff6e213d09 170 | https://conda.anaconda.org/conda-forge/noarch/pytest-cov-6.1.1-pyhd8ed1ab_0.conda#1e35d8f975bc0e984a19819aa91c440a 171 | https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda#c1e349028e0052c4eea844e94f773065 172 | https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.510-h4607db7_10.conda#96f240f245fe2e031ec59dbb3044bd6c 173 | https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.12.0-ha633028_1.conda#7c1980f89dd41b097549782121a73490 174 | https://conda.anaconda.org/conda-forge/noarch/bokeh-3.7.3-pyhd8ed1ab_0.conda#708d2f99b8a2c833ff164a225a265e76 175 | https://conda.anaconda.org/conda-forge/noarch/distributed-2025.5.1-pyhd8ed1ab_0.conda#d2949f56a1479507e36e847681903376 176 | https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.2.0-pyha770c72_0.conda#17e487cc8b5507cd3abc09398cf27949 177 | https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda#a9b9368f3701a417eac9edbcae7cb737 178 | https://conda.anaconda.org/conda-forge/noarch/codecov-2.1.13-pyhd8ed1ab_1.conda#d924fe46139596ebc3d4d424ec39ed51 179 | https://conda.anaconda.org/conda-forge/linux-64/libarrow-20.0.0-h314c690_6_cpu.conda#36f91dcd71682a790981f59b1f25e81a 180 | https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-20.0.0-hcb10f89_6_cpu.conda#d30eef9d28672b910decb5c11b00e09e 181 | https://conda.anaconda.org/conda-forge/linux-64/libparquet-20.0.0-h081d1f1_6_cpu.conda#640b79f7a2c691ab4101345c5d87a540 182 | https://conda.anaconda.org/conda-forge/linux-64/pyarrow-core-20.0.0-py311h4854187_0_cpu.conda#74b6c9bcba2625faea89bae1efb15992 183 | https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-20.0.0-hcb10f89_6_cpu.conda#528a0f333effc7e8b1ef1c40c6437bae 184 | https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-20.0.0-h1bed206_6_cpu.conda#b051b8e680398c103567e331ce3a339c 185 | https://conda.anaconda.org/conda-forge/linux-64/pyarrow-20.0.0-py311h38be061_0.conda#db68146cca95fbbb357c1d90fc1568b8 186 | https://conda.anaconda.org/conda-forge/noarch/dask-2025.5.1-pyhd8ed1ab_0.conda#97ee3885b8de1d729c40f28c2dddb335 187 | -------------------------------------------------------------------------------- /requirements/locks/py312-linux-64.lock: -------------------------------------------------------------------------------- 1 | # Generated by conda-lock. 2 | # platform: linux-64 3 | # input_hash: 73de33296cbf880ad7f0f0270486c178327e666689ce7054214b5b2952afc7b8 4 | @EXPLICIT 5 | https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 6 | https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-headers-1.21.0-ha770c72_0.conda#11b1bed92c943d3b741e8a1e1a815ed1 7 | https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.12.0-h3f2d84a_0.conda#d76872d096d063e226482c99337209dc 8 | https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda#0dfcdc155cf23812a0c9deada86fb723 9 | https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda#4222072737ccff51314b5ece9c7d6f5a 10 | https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda#95db94f75ba080a22eb623590993167b 11 | https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda#01f8d123c96816249efd255a31ad7712 12 | https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda#fbe7d535ff9d3a168c148e07358cd5b1 13 | https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d 14 | https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda#ea8ac52380885ed41c1baa8f1d6d2b93 15 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-common-0.12.3-hb9d3cd8_0.conda#8448031a22c697fac3ed98d69e8a9160 16 | https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda#f7f0d6cc2dc986d42ac2689ec88192be 17 | https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.1.0-hb9d3cd8_2.conda#41b599ed2b02abcfdd84302bff174b23 18 | https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.24-h86f0d12_0.conda#64f0c503da58ec25ebd359e4d990afa8 19 | https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda#db0bfbe7dd197b68ad5f30333bae6ce0 20 | https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda#ede4673863426c0883c0063d853bbd85 21 | https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda#ddca86c7040dd0e73b2b69bd7833d225 22 | https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda#01de444988ed960031dbe84cf4f9b1fc 23 | https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda#e796ff8ddc598affdf7c173d6145f087 24 | https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.0-hb9d3cd8_0.conda#9fa334557db9f63da6c9285fd2a48638 25 | https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda#a76fd702c93cd2dfd89eff30a5fd45a8 26 | https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda#1cb1c67961f6dd257eae9e9691b341aa 27 | https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.10.0-h202a827_0.conda#0f98f3e95272d118f7931b6bef69bfe5 28 | https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.5.0-h851e524_0.conda#63f790534398730f59e1b899c3644d4a 29 | https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda#edb0dca6bc32e4f4789199455a1dbeb8 30 | https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda#47e340acb35de30501a76c7c799c41d7 31 | https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda#de356753cfdbffcde5bb1e86e3aa6cd0 32 | https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda#b3c17d95b5a10c6e64a21fa17573e70e 33 | https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb9d3cd8_0.conda#f6ebe2cb3f82ba6c057dde5d9debe4f7 34 | https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb9d3cd8_0.conda#8035c64cb77ed555e3f150b7b3972480 35 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.9.2-h5e3027f_0.conda#0ead3ab65460d51efb27e5186f50f8e4 36 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-compression-0.3.1-hafb2847_5.conda#e96cc668c0f9478f5771b37d57f90386 37 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.2.4-hafb2847_0.conda#65853df44b7e4029d978c50be888ed89 38 | https://conda.anaconda.org/conda-forge/linux-64/aws-checksums-0.2.7-hafb2847_1.conda#6d28d50637fac4f081a0903b4b33d56d 39 | https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda#62ee74e96c5ebb0af99386de58cf9553 40 | https://conda.anaconda.org/conda-forge/linux-64/gflags-2.2.2-h5888daf_1005.conda#d411fc29e338efb48c5fd4576d71d881 41 | https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 42 | https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h0aef613_1.conda#9344155d33912347b37f0ae6c410a835 43 | https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250127.1-cxx17_hbbce691_0.conda#00290e549c5c8a32cc271020acc9ec6b 44 | https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.1.0-hb9d3cd8_2.conda#9566f0bd264fbd463002e759b8a82401 45 | https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.1.0-hb9d3cd8_2.conda#06f70867945ea6a84d35836af780f1de 46 | https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda#c277e0a4d549b03ac1e9d6cbbe3d017b 47 | https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda#172bf1cd1ff8629f2b1179945ed45055 48 | https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-hf998b51_1.conda#a1cfcc585f0c42bf8d5546bb1dfb668d 49 | https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda#f92e6e0a3c0c0c85561ef61aa59d555d 50 | https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda#30fd6e37fe21f86f4bd26d6ee73eeec7 51 | https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.47-h943b412_0.conda#55199e2ae2c3651f6f9b2a447b47bdc9 52 | https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.0-hee588c1_0.conda#71888e92098d0f8c41b09a671ad289bc 53 | https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda#eecce068c7e4eddeb169591baac20ac4 54 | https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda#9d2072af184b5caa29492bf2344597bb 55 | https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda#40b61aab5c7ba9ff276c41cfffe6b80b 56 | https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda#92ed62436b625154323d40d5f2f11dd7 57 | https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda#5aa797f8787fe7a17d1b0821485b5adc 58 | https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda#9de5350a85c4a20c685259b889aa6393 59 | https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda#283b96675859b20a825f8fa30f311446 60 | https://conda.anaconda.org/conda-forge/linux-64/s2n-1.5.21-h7ab7c64_0.conda#28b5a7895024a754249b2ad7de372faa 61 | https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.1-h8bd8927_1.conda#3b3e64af585eadfb52bb90b553db5edf 62 | https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda#a0116df4f4ed05c303811a837d5b39d8 63 | https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae 64 | https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-hb9d3cd8_2.conda#c9f075ab2f33b3bbee9e62d4ad0a6cd8 65 | https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda#6432cb5d4ac0046c3ac0a8a0f95842f9 66 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.19.1-hdfce8c9_3.conda#012df4026887e82115796d4e664abe2d 67 | https://conda.anaconda.org/conda-forge/linux-64/glog-0.7.1-hbabe93e_0.conda#ff862eebdfeb2fd048ae9dc92510baca 68 | https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda#8b189310083baabfb622af68fd9d3ae3 69 | https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda#3f43953b7d3fb3aaa1d0d0723d91e368 70 | https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2#c965a5aa0d5c1c37ffc62dff36e28400 71 | https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.13.3-h48d6fc4_1.conda#3c255be50a506c50765a93a6644f32fe 72 | https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.64.0-h161d5f1_0.conda#19e57602824042dfd0446292ef90488b 73 | https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.29-pthreads_h94d23a6_0.conda#0a4d0252248ef9a0f88f2ba8b8a08e12 74 | https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.29.3-h501fc15_1.conda#edb86556cf4a0c133e7932a1597ff236 75 | https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hba17884_3.conda#545e93a513c10603327c76c15485e946 76 | https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.21.0-h0e7cc3e_0.conda#dcb95c0a98ba9ff737f7ae482aef7833 77 | https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.0-hf01ce69_5.conda#e79a094918988bb1807462cd42c83962 78 | https://conda.anaconda.org/conda-forge/linux-64/python-3.12.11-h9e4cc4f_0_cpython.conda#94206474a5608243a10c92cefbe0908f 79 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.5.4-haaa725d_10.conda#ed15f12bd23f3861d61e3d71c0e639ee 80 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.1-hcfde5e4_4.conda#1609e2c1c556f66dbfff36d376c0d0e4 81 | https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda#b0b867af6fc74b2a0aa206da29c0f3cf 82 | https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda#c33eeaaa33f45031be34cda513df39b6 83 | https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_1.conda#57df494053e17dce2ac3a0b33e1b2a2e 84 | https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda#40fe4284b8b5835a9073a645139f35af 85 | https://conda.anaconda.org/conda-forge/noarch/click-8.2.1-pyh707e725_0.conda#94b550b8d3a614dbd326af798c7dfb40 86 | https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda#364ba6c9fb03886ac979b482f39ebb92 87 | https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda#962b9857ee8e7018c22f2776ffa0b2d7 88 | https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda#e5d2a28866ee990a340bde1eabde587a 89 | https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.9-pyhd8ed1ab_1.conda#8d88f4a2242e6b96f9ecff9a6a05b2f1 90 | https://conda.anaconda.org/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda#4547b39256e296bb758166893e909a7c 91 | https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.5.1-pyhd8ed1ab_0.conda#2d2c9ef879a7e64e2dc657b09272c2b6 92 | https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda#0a802cb9888dd14eeefc611f05c40b6e 93 | https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda#8e6923fc12f1fe8f8c4e5c9f343256ac 94 | https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda#39a4f67be3286c86d696df570b1201b7 95 | https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda#6837f3eff7dcea42ecd714ce1ac2b108 96 | https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.17-h717163a_0.conda#000e85703f0fd9594c81710dd5066471 97 | https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_h59b9bed_openblas.conda#728dbebd0f7a20337218beacffd37916 98 | https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.14.1-h332b0f4_0.conda#45f6713cb00f124af300342512219182 99 | https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.13.3-ha770c72_1.conda#51f5be229d83ecd401fb369ab96ae669 100 | https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.8-h4bc477f_0.conda#14dbe05b929e329dbaa6f2d0aa19466d 101 | https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 102 | https://conda.anaconda.org/conda-forge/linux-64/lz4-4.4.4-py312hf0f0c11_0.conda#f770ae71fc1800e7a735a7b452c0ab81 103 | https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_1.conda#eb227c3e0bf58f5bd69c0532b157975b 104 | https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.1.0-py312h68727a3_0.conda#5c9b020a3f86799cdc6115e55df06146 105 | https://conda.anaconda.org/conda-forge/noarch/narwhals-1.41.1-pyhe01879c_0.conda#b8c443460cd4f4130a95f7f9a92ef21b 106 | https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.3-h5fbd93e_0.conda#9e5816bc95d285c115a3ebc2f8563564 107 | https://conda.anaconda.org/conda-forge/linux-64/orc-2.1.2-h17f744e_0.conda#ef7f9897a244b2023a066c22a1089ce4 108 | https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda#58335b26c38bf4a20f399384c33cbcf9 109 | https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.8-pyhe01879c_0.conda#424844562f5d337077b445ec6b1398a7 110 | https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda#7da7ccd349dbf6487a7778579d2bb971 111 | https://conda.anaconda.org/conda-forge/linux-64/psutil-7.0.0-py312h66e93f0_0.conda#8e30db4239508a538e4a3b3cdf5b9616 112 | https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda#12c566707c80111f9799308d9e265aef 113 | https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda#232fb4577b6687b2d503ef8e254270c9 114 | https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda#461219d1a5bd61342293efa2c0c90eac 115 | https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.2-pyhd8ed1ab_0.conda#88476ae6ebd24f39261e0854ac244f33 116 | https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda#bc8e3267d44011051f2eb14d22fb0960 117 | https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py312h178313f_2.conda#cf2485f39740de96e2a7f2bb18ed2fee 118 | https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_3.conda#6f445fb139c356f903746b2b91bbe786 119 | https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda#4de79c071274a53dcaf2a8c749d1499e 120 | https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda#a451d576819089b0d672f18768be0f65 121 | https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda#0401a17ae845fa72c7210e206ec5647d 122 | https://conda.anaconda.org/conda-forge/noarch/tblib-3.1.0-pyhd8ed1ab_0.conda#a15c62b8a306b8978f094f76da2f903f 123 | https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_1.conda#b0dd904de08b7db706167240bf37b164 124 | https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda#ac944244f1fed2eb49bae07193ae8215 125 | https://conda.anaconda.org/conda-forge/noarch/toolz-1.0.0-pyhd8ed1ab_1.conda#40d0ed782a8aaa16ef248e68c06c168d 126 | https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.1-py312h66e93f0_0.conda#c532a6ee766bed75c4fa0c39e959d132 127 | https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.14.0-pyhe01879c_0.conda#2adcd9bb86f656d3d43bf84af59a1faf 128 | https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda#75cb7132eb58d97896e173ef12ac9986 129 | https://conda.anaconda.org/conda-forge/noarch/xyzservices-2025.4.0-pyhd8ed1ab_0.conda#5663fa346821cd06dc1ece2c2600be2c 130 | https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda#e52c2ef711ccf31bb7f70ca87d144b9e 131 | https://conda.anaconda.org/conda-forge/noarch/zipp-3.22.0-pyhd8ed1ab_0.conda#234be740b00b8e41567e5b0ed95aaba9 132 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.9.0-hd3ec96c_11.conda#29deff273a14d358bfe58065b30a0085 133 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.13.1-h7b3935a_0.conda#0ad7e0a85f207c2e25399d8217d8b8e5 134 | https://conda.anaconda.org/conda-forge/linux-64/azure-core-cpp-1.14.0-h5cfcd09_0.conda#0a8838771cc2e985cd295e01ae83baf1 135 | https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda#a861504bbea4161a9170b85d4d2be840 136 | https://conda.anaconda.org/conda-forge/linux-64/coverage-7.8.2-py312h178313f_0.conda#141e4480d38281c3988f3a9aa917b07d 137 | https://conda.anaconda.org/conda-forge/linux-64/cytoolz-1.0.1-py312h66e93f0_0.conda#6198b134b1c08173f33653896974d477 138 | https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda#72e42d28960d875c7654614f8b50939a 139 | https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda#b4754fb1bdcb70c8fd54f918301582c6 140 | https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda#63ccfdc3a3ce25b027b8767eb722fca8 141 | https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda#446bd6c8cb26050d528881df495ce646 142 | https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_he106b2a_openblas.conda#abb32c727da370c481a1c206f5159ce9 143 | https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.71.0-h8e591d7_1.conda#c3cfd72cbb14113abee7bbd86f44ad69 144 | https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_h7ac8fdf_openblas.conda#452b98eafe050ecff932f0ec832dd03f 145 | https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda#7ba3f09fceae6a120d664217e58fe686 146 | https://conda.anaconda.org/conda-forge/noarch/partd-1.4.2-pyhd8ed1ab_0.conda#0badf9c54e24cecfb0ad2f99d680c163 147 | https://conda.anaconda.org/conda-forge/linux-64/pillow-11.2.1-py312h80c1187_0.conda#ca438bf57e4f2423d261987fe423a0dd 148 | https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda#32d0781ace05105cc99af55d36cbec7c 149 | https://conda.anaconda.org/conda-forge/linux-64/prometheus-cpp-1.3.0-ha5d0236_0.conda#a83f6a2fdc079e643237887a37460668 150 | https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhff2d567_1.conda#5ba79d7c71f03c678c8ead841f347d6e 151 | https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.14.0-h32cad80_0.conda#a1cdd40fc962e2f7944bc19e01c7e584 152 | https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.31.2-pyhd8ed1ab_0.conda#c0600c1b374efa7a1ff444befee108ca 153 | https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.8.0-h670bddd_2.conda#9c9ec5641de50fff32aa71e5296e627e 154 | https://conda.anaconda.org/conda-forge/linux-64/azure-identity-cpp-1.10.0-h113e628_0.conda#73f73f60854f325a55f1d31459f2ab73 155 | https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.8.0-h736e048_1.conda#13de36be8de3ae3f05ba127631599213 156 | https://conda.anaconda.org/conda-forge/noarch/dask-core-2025.5.1-pyhd8ed1ab_0.conda#8f0ef561cd615a17df3256742a3457c4 157 | https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-2.36.0-hc4361e1_1.conda#ae36e6296a8dd8e8a9a8375965bf6398 158 | https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-1.21.0-hd1b1c89_0.conda#4b25cd8720fd8d5319206e4f899f2707 159 | https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.6-py312h72c5963_0.conda#17fac9db62daa5c810091c2882b28f45 160 | https://conda.anaconda.org/conda-forge/noarch/pytest-8.4.0-pyhd8ed1ab_0.conda#516d31f063ce7e49ced17f105b63a1f1 161 | https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-8.3.1-pyhd8ed1ab_0.conda#996376098e3648237b3efb0e0ad460c1 162 | https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py312h68727a3_5.conda#f9664ee31aed96c85b7319ab0a693341 163 | https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda#630db208bc7bbb96725ce9832c7423bb 164 | https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.32.8-h0ead740_2.conda#df290cfb1fc024a1dc9e30f6167579a5 165 | https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.13.0-h3cf044e_1.conda#7eb66060455c7a47d9dcdbfa9f46579b 166 | https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.2-py312h68727a3_0.conda#e688276449452cdfe9f8f5d3e74c23f6 167 | https://conda.anaconda.org/conda-forge/noarch/identify-2.6.12-pyhd8ed1ab_0.conda#84463b10c1eb198541cd54125c7efe90 168 | https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-storage-2.36.0-h0121fbd_1.conda#a0f7588c1f0a26d550e7bae4fb49427a 169 | https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.0-py312hf9745cd_0.conda#ac82ac336dbe61106e21fb2e11704459 170 | https://conda.anaconda.org/conda-forge/noarch/pytest-cov-6.1.1-pyhd8ed1ab_0.conda#1e35d8f975bc0e984a19819aa91c440a 171 | https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda#c1e349028e0052c4eea844e94f773065 172 | https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.510-h4607db7_10.conda#96f240f245fe2e031ec59dbb3044bd6c 173 | https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.12.0-ha633028_1.conda#7c1980f89dd41b097549782121a73490 174 | https://conda.anaconda.org/conda-forge/noarch/bokeh-3.7.3-pyhd8ed1ab_0.conda#708d2f99b8a2c833ff164a225a265e76 175 | https://conda.anaconda.org/conda-forge/noarch/distributed-2025.5.1-pyhd8ed1ab_0.conda#d2949f56a1479507e36e847681903376 176 | https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.2.0-pyha770c72_0.conda#17e487cc8b5507cd3abc09398cf27949 177 | https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda#a9b9368f3701a417eac9edbcae7cb737 178 | https://conda.anaconda.org/conda-forge/noarch/codecov-2.1.13-pyhd8ed1ab_1.conda#d924fe46139596ebc3d4d424ec39ed51 179 | https://conda.anaconda.org/conda-forge/linux-64/libarrow-20.0.0-h314c690_6_cpu.conda#36f91dcd71682a790981f59b1f25e81a 180 | https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-20.0.0-hcb10f89_6_cpu.conda#d30eef9d28672b910decb5c11b00e09e 181 | https://conda.anaconda.org/conda-forge/linux-64/libparquet-20.0.0-h081d1f1_6_cpu.conda#640b79f7a2c691ab4101345c5d87a540 182 | https://conda.anaconda.org/conda-forge/linux-64/pyarrow-core-20.0.0-py312h01725c0_0_cpu.conda#9b1b453cdb91a2f24fb0257bbec798af 183 | https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-20.0.0-hcb10f89_6_cpu.conda#528a0f333effc7e8b1ef1c40c6437bae 184 | https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-20.0.0-h1bed206_6_cpu.conda#b051b8e680398c103567e331ce3a339c 185 | https://conda.anaconda.org/conda-forge/linux-64/pyarrow-20.0.0-py312h7900ff3_0.conda#57b626b4232b77ee6410c7c03a99774d 186 | https://conda.anaconda.org/conda-forge/noarch/dask-2025.5.1-pyhd8ed1ab_0.conda#97ee3885b8de1d729c40f28c2dddb335 187 | -------------------------------------------------------------------------------- /requirements/py310.yml: -------------------------------------------------------------------------------- 1 | name: stratify-dev 2 | 3 | channels: 4 | - conda-forge 5 | 6 | dependencies: 7 | - python=3.10 8 | 9 | # Setup dependencies. 10 | - setuptools>=77.0.3 11 | - setuptools-scm>=8 12 | 13 | # Core dependencies. 14 | - numpy>=2 15 | - cython<3.1.0 16 | 17 | # Optional dependencies. 18 | - dask 19 | 20 | # Developer dependencies. 21 | - pip 22 | - pre-commit 23 | 24 | # Test dependencies. 25 | - codecov 26 | - pytest 27 | - pytest-cov 28 | -------------------------------------------------------------------------------- /requirements/py311.yml: -------------------------------------------------------------------------------- 1 | name: stratify-dev 2 | 3 | channels: 4 | - conda-forge 5 | 6 | dependencies: 7 | - python=3.11 8 | 9 | # Setup dependencies. 10 | - setuptools>=77.0.3 11 | - setuptools-scm>=8 12 | 13 | # Core dependencies. 14 | - numpy>=2 15 | - cython<3.1.0 16 | 17 | # Optional dependencies. 18 | - dask 19 | 20 | # Developer dependencies. 21 | - pip 22 | - pre-commit 23 | 24 | # Test dependencies. 25 | - codecov 26 | - pytest 27 | - pytest-cov 28 | -------------------------------------------------------------------------------- /requirements/py312.yml: -------------------------------------------------------------------------------- 1 | name: stratify-dev 2 | 3 | channels: 4 | - conda-forge 5 | 6 | dependencies: 7 | - python=3.12 8 | 9 | # Setup dependencies. 10 | - setuptools>=77.0.3 11 | - setuptools-scm>=8 12 | 13 | # Core dependencies. 14 | - numpy>=2 15 | - cython<3.1.0 16 | 17 | # Optional dependencies. 18 | - dask 19 | 20 | # Developer dependencies. 21 | - pip 22 | - pre-commit 23 | 24 | # Test dependencies. 25 | - codecov 26 | - pytest 27 | - pytest-cov 28 | -------------------------------------------------------------------------------- /requirements/pypi-core.txt: -------------------------------------------------------------------------------- 1 | Cython<3.1.0 2 | dask[array] 3 | numpy>=2 4 | -------------------------------------------------------------------------------- /requirements/stratify.yml: -------------------------------------------------------------------------------- 1 | py311.yml -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | import sys 4 | import warnings 5 | 6 | # safe to import numpy here thanks to pep518 7 | import numpy as np 8 | from setuptools import Command, Extension, setup 9 | 10 | try: 11 | from Cython.Build import cythonize # isort:skip 12 | except ImportError: 13 | wmsg = "Cython unavailable, unable to build stratify extensions!" 14 | warnings.warn(wmsg) 15 | cythonize = None 16 | 17 | 18 | BASE_DIR = Path(__file__).resolve().parent 19 | PACKAGE_NAME = "stratify" 20 | SRC_DIR = BASE_DIR / "src" 21 | STRATIFY_DIR = SRC_DIR / PACKAGE_NAME 22 | CMDS_NOCYTHONIZE = ["clean", "sdist"] 23 | FLAG_COVERAGE = "--cython-coverage" # custom flag enabling Cython line tracing 24 | 25 | 26 | class CleanCython(Command): 27 | description = "Purge artifacts built by Cython" 28 | user_options = [] 29 | 30 | def initialize_options(self): 31 | pass 32 | 33 | def finalize_options(self): 34 | pass 35 | 36 | def run(self): 37 | for path in STRATIFY_DIR.rglob("*"): 38 | if path.suffix in (".pyc", ".pyo", ".c", ".so"): 39 | msg = f"clean: removing file {path}" 40 | print(msg) 41 | path.unlink() 42 | 43 | 44 | cython_coverage_enabled = ( 45 | os.environ.get("CYTHON_COVERAGE", None) or FLAG_COVERAGE in sys.argv 46 | ) 47 | cython_directives = {} 48 | define_macros = [] 49 | extensions = [] 50 | 51 | if cythonize and cython_coverage_enabled: 52 | define_macros.extend( 53 | [ 54 | ("CYTHON_TRACE", "1"), 55 | ("CYTHON_TRACE_NOGIL", "1"), 56 | ] 57 | ) 58 | cython_directives.update({"linetrace": True}) 59 | if FLAG_COVERAGE in sys.argv: 60 | sys.argv.remove(FLAG_COVERAGE) 61 | print('enabled "linetrace" Cython compiler directive') 62 | 63 | for fname in SRC_DIR.glob(f"{PACKAGE_NAME}/*.pyx"): 64 | # ref: https://setuptools.pypa.io/en/latest/userguide/ext_modules.html 65 | extension = Extension( 66 | f"{PACKAGE_NAME}.{fname.stem}", 67 | sources=[str(fname.relative_to(BASE_DIR))], 68 | include_dirs=[np.get_include()], 69 | define_macros=define_macros, 70 | ) 71 | extensions.append(extension) 72 | 73 | if cythonize and not any([arg in CMDS_NOCYTHONIZE for arg in sys.argv]): 74 | extensions = cythonize( 75 | extensions, compiler_directives=cython_directives, language_level=3 76 | ) 77 | 78 | cmdclass = {"clean_cython": CleanCython} 79 | kwargs = {"cmdclass": cmdclass, "ext_modules": extensions} 80 | setup(**kwargs) 81 | -------------------------------------------------------------------------------- /src/stratify/__init__.py: -------------------------------------------------------------------------------- 1 | from ._bounded_vinterp import interpolate_conservative # noqa: F401 2 | from ._version import version as __version__ # noqa: F401 3 | from ._vinterp import * # noqa: F403 4 | -------------------------------------------------------------------------------- /src/stratify/_bounded_vinterp.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from ._conservative import conservative_interpolation 4 | 5 | 6 | def interpolate_conservative(z_target, z_src, fz_src, axis=-1): 7 | """1d conservative interpolation across multiple dimensions. 8 | 9 | This function provides the ability to perform 1d interpolation on datasets 10 | with more than one dimension. For instance, this function can be used to 11 | interpolate a set of vertical levels, even if the interpolation coordinate 12 | depends upon other dimensions. 13 | 14 | A good use case might be when wanting to interpolate at a specific height 15 | for height data which also depends on x and y - e.g. extract 1000hPa level 16 | from a 3d dataset and associated pressure field. In the case of this 17 | example, pressure would be the `z` coordinate, and the dataset 18 | (e.g. geopotential height / temperature etc.) would be `f(z)`. 19 | 20 | Parameters 21 | ---------- 22 | z_target: :class:`np.ndarray` 23 | Target coordinate. 24 | This coordinate defines the levels to interpolate the source data 25 | ``fz_src`` to. ``z_target`` must have the same dimensionality as the 26 | source coordinate ``z_src``, and the shape of ``z_target`` must match 27 | the shape of ``z_src``, although the axis of interpolation may differ 28 | in dimension size. 29 | z_src: :class:`np.ndarray` 30 | Source coordinate. 31 | This coordinate defines the levels that the source data ``fz_src`` is 32 | interpolated from. 33 | fz_src: :class:`np.ndarray` 34 | The source data; the phenomenon data values to be interpolated from 35 | ``z_src`` to ``z_target``. 36 | The data array must be at least ``z_src.ndim``, and its trailing 37 | dimensions (i.e. those on its right hand side) must be exactly 38 | the same as the shape of ``z_src``. 39 | axis: int (default -1) 40 | The ``fz_src`` axis to perform the interpolation over. 41 | 42 | Returns 43 | ------- 44 | : :class:`np.ndarray` 45 | fz_src interpolated from z_src to z_target. 46 | 47 | Note 48 | ---- 49 | - Support for 1D z_target and corresponding ND z_src will be provided in 50 | future as driven by user requirement. 51 | - Those cells, where 'nan' values in the source data contribute, a 'nan' 52 | value is returned. 53 | 54 | """ 55 | if z_src.ndim != z_target.ndim: 56 | msg = ( 57 | "Expecting source and target levels dimensionality to be " 58 | "identical. {} != {}." 59 | ) 60 | raise ValueError(msg.format(z_src.ndim, z_target.ndim)) 61 | 62 | # Relative axis 63 | axis = axis % fz_src.ndim 64 | axis_relative = axis - (fz_src.ndim - (z_target.ndim - 1)) 65 | 66 | src_shape = list(z_src.shape) 67 | src_shape.pop(axis_relative) 68 | tgt_shape = list(z_target.shape) 69 | tgt_shape.pop(axis_relative) 70 | 71 | if src_shape != tgt_shape: 72 | src_shape = list(z_src.shape) 73 | src_shape[axis_relative] = "-" 74 | tgt_shape = list(z_target.shape) 75 | src_shape[axis_relative] = "-" 76 | msg = ( 77 | "Expecting the shape of the source and target levels except " 78 | "the axis of interpolation to be identical. {} != {}" 79 | ) 80 | raise ValueError(msg.format(tuple(src_shape), tuple(tgt_shape))) 81 | 82 | dat_shape = list(fz_src.shape) 83 | dat_shape = dat_shape[-(z_src.ndim - 1) :] 84 | src_shape = list(z_src.shape[:-1]) 85 | if dat_shape != src_shape: 86 | dat_shape = list(fz_src.shape) 87 | dat_shape[: -(z_src.ndim - 1)] = "-" 88 | msg = ( 89 | "The provided data is not of compatible shape with the " 90 | "provided source bounds. {} != {}" 91 | ) 92 | raise ValueError(msg.format(tuple(dat_shape), tuple(src_shape))) 93 | 94 | if z_src.shape[-1] != 2: 95 | msg = "Unexpected source and target bounds shape. shape[-1] != 2" 96 | raise ValueError(msg) 97 | 98 | # Define our source in a consistent way. 99 | # [broadcasting_dims, axis_interpolation, z_varying] 100 | 101 | # src_data 102 | bdims = list(range(fz_src.ndim - (z_src.ndim - 1))) 103 | data_vdims = [ind for ind in range(fz_src.ndim) if ind not in (bdims + [axis])] 104 | data_transpose = bdims + [axis] + data_vdims 105 | fz_src_reshaped = np.transpose(fz_src, data_transpose) 106 | fz_src_orig = list(fz_src_reshaped.shape) 107 | shape = ( 108 | int(np.prod(fz_src_reshaped.shape[: len(bdims)])), 109 | fz_src_reshaped.shape[len(bdims)], 110 | int(np.prod(fz_src_reshaped.shape[len(bdims) + 1 :])), 111 | ) 112 | fz_src_reshaped = fz_src_reshaped.reshape(shape) 113 | 114 | # Define our src and target bounds in a consistent way. 115 | # [axis_interpolation, z_varying, 2] 116 | vdims = list(set(range(z_src.ndim)) - set([axis_relative])) 117 | z_src_reshaped = np.transpose(z_src, [axis_relative] + vdims) 118 | z_target_reshaped = np.transpose(z_target, [axis_relative] + vdims) 119 | 120 | shape = int(np.prod(z_src_reshaped.shape[1:-1])) 121 | z_src_reshaped = z_src_reshaped.reshape( 122 | [z_src_reshaped.shape[0], shape, z_src_reshaped.shape[-1]] 123 | ) 124 | shape = int(np.prod(z_target_reshaped.shape[1:-1])) 125 | z_target_reshaped = z_target_reshaped.reshape( 126 | [z_target_reshaped.shape[0], shape, z_target_reshaped.shape[-1]] 127 | ) 128 | 129 | result = conservative_interpolation( 130 | z_src_reshaped, z_target_reshaped, fz_src_reshaped 131 | ) 132 | 133 | # Turn the result into a shape consistent with the source. 134 | # First reshape, then reverse transpose. 135 | shape = fz_src_orig 136 | shape[len(bdims)] = z_target.shape[axis_relative] 137 | result = result.reshape(shape) 138 | invert_transpose = [data_transpose.index(ind) for ind in list(range(result.ndim))] 139 | result = result.transpose(invert_transpose) 140 | return result 141 | -------------------------------------------------------------------------------- /src/stratify/_conservative.pyx: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | cimport numpy as np 3 | 4 | # Must be called to use the C-API with Numpy. 5 | np.import_array() 6 | 7 | cdef calculate_weights(np.ndarray[np.float64_t, ndim=2] src_point, 8 | np.ndarray[np.float64_t, ndim=2] tgt_point): 9 | """ 10 | Calculate weights for a given point. 11 | 12 | The following visually illustrates the calculation:: 13 | 14 | src_min src_max 15 | |----------| : Source 16 | tgt_min tgt_max 17 | |------------| : Target 18 | |----------| : Delta (src_max - src_min) 19 | |----| : Overlap (between src & tgt) 20 | weight = overlap / delta 21 | 22 | Parameters 23 | ---------- 24 | src_point (2d double array) - Source point (at a specific location). 25 | tgt_point (2d double array) - Target point (at a specific location). 26 | 27 | Returns 28 | ------- 29 | 2d double array - Weights corresponding to shape [src_point.shape[0], 30 | tgt_point.shape[0]]. 31 | 32 | """ 33 | cdef Py_ssize_t src_ind, tgt_ind 34 | cdef np.float64_t delta, weight 35 | cdef np.ndarray[np.float64_t, ndim=2] weights 36 | cdef np.ndarray[np.float64_t, ndim=1] src_cell, tgt_cell 37 | 38 | weights = np.zeros([src_point.shape[0], tgt_point.shape[0]], 39 | dtype=np.float64) 40 | for src_ind, src_cell in enumerate(src_point): 41 | delta = src_cell.max() - src_cell.min() 42 | for tgt_ind, tgt_cell in enumerate(tgt_point): 43 | weight = (min(src_cell.max(), tgt_cell.max()) - 44 | max(src_cell.min(), tgt_cell.min())) / float(delta) 45 | if weight > 0: 46 | weights[src_ind, tgt_ind] = weight 47 | return weights 48 | 49 | 50 | cdef apply_weights(np.ndarray[np.float64_t, ndim=3] src_point, 51 | np.ndarray[np.float64_t, ndim=3] tgt_point, 52 | np.ndarray[np.float64_t, ndim=3] src_data): 53 | """ 54 | Perform conservative interpolation. 55 | 56 | Conservation interpolation of a dataset between a provided source 57 | coordinate and a target coordinate. Where no source cells contribute to a 58 | target cell, a np.nan value is returned. 59 | 60 | Parameters 61 | ---------- 62 | src_points (3d double array) - Source coordinate, taking the form 63 | [axis_interpolation, z_varying, 2]. 64 | tgt_points (3d double array) - Target coordinate, taking the form 65 | [axis_interpolation, z_varying, 2]. 66 | src_data (3d double array) - The source data, the phenomenon data to be 67 | interpolated from ``src_points`` to ``tgt_points``. Taking the form 68 | [broadcasting_dims, axis_interpolation, z_varying]. 69 | 70 | Returns 71 | ------- 72 | 3d double array - Interpolated result over target levels (``tgt_points``). 73 | Taking the form [broadcasting_dims, axis_interpolation, z_varying]. 74 | 75 | """ 76 | cdef Py_ssize_t ind 77 | cdef np.ndarray[np.float64_t, ndim=3] results, weighted_contrib 78 | cdef np.ndarray[np.float64_t, ndim=2] weights 79 | results = np.zeros( 80 | [src_data.shape[0], tgt_point.shape[0], src_data.shape[2]], 81 | dtype='float64') 82 | # Calculate and apply weights 83 | for ind in range(src_data.shape[2]): 84 | weights = calculate_weights(src_point[:, ind], tgt_point[:, ind]) 85 | if not (weights.sum(axis=1) == 1).all(): 86 | msg = ('Weights calculation yields a less than conservative ' 87 | 'result. Aborting.') 88 | raise ValueError(msg) 89 | weighted_contrib = weights * src_data[..., ind][..., None] 90 | results[..., ind] = ( 91 | np.nansum(weighted_contrib, axis=1)) 92 | # Return nan values for those target cells, where there is any 93 | # contribution of nan data from the source data. 94 | results[..., ind][ 95 | ((weights > 0) * np.isnan(weighted_contrib)).any(axis=1)] = np.nan 96 | 97 | # Return np.nan for those target cells where no source contributes. 98 | results[:, weights.sum(axis=0) == 0, ind] = np.nan 99 | return results 100 | 101 | 102 | def conservative_interpolation(src_points, tgt_points, src_data): 103 | """ 104 | Perform conservative interpolation. 105 | 106 | Conservation interpolation of a dataset between a provided source 107 | coordinate and a target coordinate. All inputs are recast to 64-bit float 108 | arrays before calculation. 109 | 110 | Parameters 111 | ---------- 112 | src_points (3d array) - Source coordinate, taking the form 113 | [axis_interpolation, z_varying, 2]. 114 | tgt_points (3d array) - Target coordinate, taking the form 115 | [axis_interpolation, z_varying, 2]. 116 | src_data (3d array) - The source data, the phenonenon data to be 117 | interpolated from ``src_points`` to ``tgt_points``. Taking the form 118 | [broadcasting_dims, axis_interpolation, z_varying]. 119 | 120 | Returns 121 | ------- 122 | 3d double array - Interpolated result over target levels (``tgt_points``). 123 | Taking the form [broadcasting_dims, axis_interpolation, z_varying]. 124 | 125 | """ 126 | return apply_weights(src_points.astype('float64'), 127 | tgt_points.astype('float64'), 128 | src_data.astype('float64')) 129 | -------------------------------------------------------------------------------- /src/stratify/_vinterp.pyx: -------------------------------------------------------------------------------- 1 | # Terminology: 2 | # Z - the coordinate over which we are interpolating. 3 | # z_src - the values of Z where fz_src is defined 4 | # z_target - the desired values of Z to generate new data for. 5 | # fz_src - the data, defined at each z_src 6 | import functools 7 | 8 | import numpy as np 9 | 10 | cimport cython 11 | cimport numpy as np 12 | 13 | # Must be called to use the C-API with Numpy. 14 | np.import_array() 15 | 16 | cdef extern from "numpy/npy_math.h" nogil: 17 | bint isnan "npy_isnan"(long double) 18 | float NAN "NPY_NAN" 19 | float INFINITY "NPY_INFINITY" 20 | 21 | 22 | cdef extern from "math.h" nogil: 23 | double fabs(double z) 24 | 25 | 26 | __all__ = ['interpolate', 'interp_schemes', 'extrap_schemes', 27 | 'INTERPOLATE_LINEAR', 'INTERPOLATE_NEAREST', 28 | 'EXTRAPOLATE_NAN', 'EXTRAPOLATE_NEAREST', 'EXTRAPOLATE_LINEAR', 29 | 'PyFuncExtrapolator', 'PyFuncInterpolator'] 30 | 31 | 32 | cdef inline int relative_sign(double z, double z_base) nogil: 33 | """ 34 | Return the sign of z relative to z_base. 35 | 36 | Parameters 37 | ---------- 38 | z - the value to compare to z_base 39 | z_base - the other one 40 | 41 | Returns 42 | ------- 43 | +1 if z > z_base, 0 if z == z_base, and -1 if z < z_base 44 | 45 | """ 46 | cdef double delta 47 | 48 | delta = z - z_base 49 | # 1, -1, or 0. http://stackoverflow.com/a/1903975/741316 50 | return (delta > 0) - (delta < 0) 51 | 52 | 53 | @cython.boundscheck(False) 54 | @cython.wraparound(False) 55 | cdef long gridwise_interpolation(double[:] z_target, double[:] z_src, 56 | double[:, :] fz_src, bint increasing, 57 | Interpolator interpolation, 58 | Extrapolator extrapolation, 59 | double [:, :] fz_target) nogil except -1: 60 | """ 61 | Computes the interpolation of multiple levels of a single column. 62 | 63 | Parameters 64 | ---------- 65 | z_target - the levels to interpolate the source data ``fz_src`` to. 66 | z_src - the levels that the source data ``fz_src`` is interpolated from. 67 | fz_src - the source data to be interpolated. 68 | increasing - true when increasing Z index generally implies increasing Z values 69 | interpolation - the inner interpolation functionality. See the definition of 70 | Interpolator. 71 | extrapolation - the inner extrapolation functionality. See the definition of 72 | Extrapolator. 73 | fz_target - the pre-allocated array to be used for the interpolated result 74 | 75 | Note: This algorithm is not symmetric. It does not make assumptions about monotonicity 76 | of z_src nor z_target. Instead, the algorithm marches forwards from the last 77 | z_target found. To visualise this, imagine a single vertical column of temperature 78 | values being our non-monotonic Z *coordinate*, with our fx data being height. 79 | At low indices of Z (i.e. at the bottom of the column) the temperature is high, and 80 | decreases as we ascend. As some point, the trend reverses, and the temperature 81 | again begins to rise, before finally trailing off again as we reach the very top 82 | of our column. Algorithmically, we march the column looking for the next z_target, 83 | when a crossing is detected we invoke the interpolation for fx at our current index. 84 | We then continue from this index, only looking for the crossing of the next z_target. 85 | 86 | For this reason, the order that the levels are provided is important. 87 | If z_src = [2, 4, 6], fz_src = [2, 4, 6] and z_target = [3, 5], fz_target will be 88 | [3, 5]. But if z_target = [5, 3], fz_target will be [5, ]. 89 | 90 | """ 91 | cdef unsigned int i_src, i_target, n_src, n_target, i, m 92 | cdef bint all_nans = True 93 | cdef double z_before, z_current, z_after, z_last 94 | cdef int sign_after, sign_before, extrapolating 95 | 96 | n_src = z_src.shape[0] 97 | n_target = z_target.shape[0] 98 | 99 | # Check for a source coordinate that has only NaN values. 100 | if n_target and isnan(z_src[0]): 101 | for i in range(n_src): 102 | all_nans = isnan(z_src[i]) 103 | if not all_nans: 104 | break 105 | if all_nans: 106 | # The result is also only NaN values. 107 | m = fz_target.shape[0] 108 | for i in range(m): 109 | for i_target in range(n_target): 110 | fz_target[i, i_target] = NAN 111 | return 0 112 | 113 | interpolation.prepare_column(z_target, z_src, fz_src, increasing) 114 | extrapolation.prepare_column(z_target, z_src, fz_src, increasing) 115 | 116 | if increasing: 117 | z_before = -INFINITY 118 | else: 119 | z_before = INFINITY 120 | 121 | z_last = -z_before 122 | 123 | i_src = 0 124 | # The first window for which we are looking for a crossing is between the 125 | # first window value (typically -inf, but may be +inf) and the first z_src. 126 | # This search window will be moved along until a crossing is detected, at 127 | # which point we will do an interpolation. 128 | z_after = z_src[0] 129 | 130 | # We start in extrapolation mode. This will be turned off as soon as we 131 | # start increasing i_src. 132 | extrapolating = -1 133 | 134 | for i_target in range(n_target): 135 | # Move the level we are looking for forwards one. 136 | z_current = z_target[i_target] 137 | 138 | if isnan(z_current): 139 | with gil: 140 | raise ValueError('The target coordinate may not contain NaN values.') 141 | 142 | # Determine if the z_current has a crossing within 143 | # the current window. 144 | sign_before = relative_sign(z_before, z_current) 145 | sign_after = relative_sign(z_after, z_current) 146 | 147 | # Move the window forwards until a crossing *is* found for this level. 148 | # If we run out of z_src to check, we go back to extrapolation, and put the 149 | # upper edge of the window at z_last (typically +inf). 150 | while sign_before == sign_after: 151 | i_src += 1 152 | if i_src < n_src: 153 | extrapolating = 0 154 | z_after = z_src[i_src] 155 | if isnan(z_after): 156 | with gil: 157 | raise ValueError('The source coordinate may not contain NaN values.') 158 | sign_after = relative_sign(z_after, z_current) 159 | else: 160 | extrapolating = 1 161 | z_after = z_last 162 | sign_after = relative_sign(z_after, z_current) 163 | break 164 | 165 | if extrapolating == 0 or sign_after == 0: 166 | interpolation.kernel(i_src, z_src, fz_src, z_current, 167 | fz_target[:, i_target]) 168 | else: 169 | extrapolation.kernel(extrapolating, z_src, fz_src, z_current, 170 | fz_target[:, i_target]) 171 | 172 | # Move the lower edge of the window forwards to the level we've just computed, 173 | # thus preventing the levels from stepping back within a single index. 174 | z_before = z_current 175 | 176 | 177 | cdef class Interpolator(object): 178 | cdef long kernel(self, unsigned int index, 179 | double[:] z_src, double[:, :] fz_src, 180 | double level, double[:] fz_level 181 | ) nogil except -1: 182 | """ 183 | The inner part of an interpolation operation. 184 | 185 | Parameters: 186 | ---------- 187 | i (unsigned int) - the current (upper) index along z_src. 0 <= i < z_src.size[0] 188 | i will only ever be 0 if z_src[i] == current_level. 189 | the interpolation value may lie on exactly i, but will never lie on exactly i-1. 190 | z_src (double array) - the 1d column of z_src values. 191 | fz_src (2d double array) - the m 1d columns of fz_src values. 192 | fz_src.shape[1] == z_src.shape[0]. 193 | fz_src.shape[0] may be 1 (common). 194 | current_level (double) - the value that we are interpolating for 195 | fz_target (double array) - the pre-allocated array to put the resulting 196 | interpolated values into. 197 | 198 | """ 199 | with gil: 200 | raise RuntimeError('Interpolator subclasses should implement ' 201 | 'the kernel function.') 202 | 203 | cdef bint prepare_column(self, double[:] z_target, double[:] z_src, 204 | double[:, :] fz_src, bint increasing) nogil except -1: 205 | # Called before all levels are interpolated. 206 | pass 207 | 208 | 209 | cdef class LinearInterpolator(Interpolator): 210 | @cython.boundscheck(False) 211 | @cython.wraparound(False) 212 | cdef long kernel(self, unsigned int index, double[:] z_src, double[:, :] fz_src, double level, 213 | double[:] fz_target) nogil except -1: 214 | """ 215 | Compute a linear interpolation. 216 | 217 | The behaviour is as follows: 218 | * if we've found a crossing and it is exactly on the level, use the 219 | exact value from the original data (i.e. level == z_src[index]) 220 | * otherwise, compute the distance of the level from i and i-1, and 221 | use these as proportions for linearly combining the fz_src values at 222 | those indices. 223 | 224 | """ 225 | cdef unsigned int m = fz_src.shape[0] 226 | cdef double frac 227 | cdef unsigned int i 228 | 229 | if level == z_src[index]: 230 | for i in range(m): 231 | fz_target[i] = fz_src[i, index] 232 | else: 233 | frac = ((level - z_src[index - 1]) / 234 | (z_src[index] - z_src[index - 1])) 235 | 236 | for i in range(m): 237 | fz_target[i] = fz_src[i, index - 1] + \ 238 | frac * (fz_src[i, index] - fz_src[i, index - 1]) 239 | 240 | 241 | cdef class NearestNInterpolator(Interpolator): 242 | @cython.boundscheck(False) 243 | @cython.wraparound(False) 244 | cdef long kernel(self, unsigned int index, double[:] z_src, double[:, :] fz_src, double level, 245 | double[:] fz_target) nogil except -1: 246 | """Compute a nearest-neighbour interpolation.""" 247 | cdef unsigned int m = fz_src.shape[0] 248 | cdef unsigned int nearest_index, i 249 | 250 | if index != 0 and fabs(level - z_src[index - 1]) <= fabs(level - z_src[index]): 251 | nearest_index = index - 1 252 | else: 253 | nearest_index = index 254 | 255 | for i in range(m): 256 | fz_target[i] = fz_src[i, nearest_index] 257 | 258 | 259 | cdef class PyFuncInterpolator(Interpolator): 260 | cdef bint use_column_prep 261 | 262 | def __init__(self, use_column_prep=True): 263 | self.use_column_prep = use_column_prep 264 | 265 | def column_prep(self, z_target, z_src, fz_src, increasing): 266 | """ 267 | Called each time this interpolator sees a new data array. 268 | This method may be used for validation of a column, or for column 269 | based pre-interpolation calculations (e.g. spline gradients). 270 | 271 | Note: This method is not called if :attr:`.call_column_prep` is False. 272 | 273 | """ 274 | pass 275 | 276 | cdef bint prepare_column(self, double[:] z_target, double[:] z_src, 277 | double[:, :] fz_src, bint increasing) nogil except -1: 278 | if self.use_column_prep: 279 | with gil: 280 | self.column_prep(z_target, z_src, fz_src, increasing) 281 | 282 | def interp_kernel(self, index, z_src, fz_src, level, output_array): 283 | # Fill the output array with the fz_src data at the given index. 284 | # This is nealy equivalent to nearest neighbour, but doesn't take 285 | # into account which neighbour is nearest. 286 | output_array[:] = fz_src[:, index] 287 | 288 | @cython.boundscheck(False) 289 | @cython.wraparound(False) 290 | cdef long kernel(self, 291 | unsigned int index, double[:] z_src, 292 | double[:, :] fz_src, double level, 293 | double[:] fz_target) nogil except -1: 294 | with gil: 295 | self.interp_kernel(index, z_src, fz_src, level, fz_target) 296 | 297 | 298 | cdef class Extrapolator(object): 299 | cdef long kernel(self, int direction, 300 | double[:] z_src, double[:, :] fz_src, 301 | double current_level, double[:] fz_target 302 | ) nogil except -1: 303 | """ 304 | Defines the inner part of an extrapolation operation. 305 | 306 | Parameters: 307 | ---------- 308 | direction (int) - -1 for the bottom edge, +1 for the top edge 309 | z_src (double array) - the 1d column of z_src values. 310 | fz_src (2d double array) - the m 1d columns of fz_src values. 311 | fz_src.shape[1] == z_src.shape[0]. 312 | fz_src.shape[0] may be 1 (common). 313 | current_level (double) - the value that we are interpolating for 314 | fz_target (double array) - the pre-allocated array to put the resulting 315 | extrapolated values into. 316 | """ 317 | with gil: 318 | raise RuntimeError('Interpolator subclasses should implement ' 319 | 'the kernel function.') 320 | 321 | cdef bint prepare_column(self, double[:] z_target, double[:] z_src, 322 | double[:, :] fz_src, bint increasing) nogil except -1: 323 | pass 324 | 325 | 326 | cdef class NaNExtrapolator(Extrapolator): 327 | @cython.boundscheck(False) 328 | @cython.wraparound(False) 329 | cdef long kernel(self, int direction, double[:] z_src, 330 | double[:, :] fz_src, double level, 331 | double[:] fz_target) nogil except -1: 332 | """NaN values for extrapolation.""" 333 | cdef unsigned int m = fz_src.shape[0] 334 | cdef unsigned int i 335 | 336 | for i in range(m): 337 | fz_target[i] = NAN 338 | 339 | 340 | cdef class NearestNExtrapolator(Extrapolator): 341 | @cython.boundscheck(False) 342 | @cython.wraparound(False) 343 | cdef long kernel(self, 344 | int direction, double[:] z_src, 345 | double[:, :] fz_src, double level, 346 | double[:] fz_target) nogil except -1: 347 | """Nearest-neighbour/edge extrapolation.""" 348 | cdef unsigned int m = fz_src.shape[0] 349 | cdef unsigned int index, i 350 | 351 | if direction < 0: 352 | index = 0 353 | else: 354 | index = fz_src.shape[1] - 1 355 | 356 | for i in range(m): 357 | fz_target[i] = fz_src[i, index] 358 | 359 | 360 | cdef class LinearExtrapolator(Extrapolator): 361 | cdef bint prepare_column(self, double[:] z_target, double[:] z_src, 362 | double[:, :] fz_src, bint increasing) nogil except -1: 363 | cdef unsigned int n_src_pts = z_src.shape[0] 364 | 365 | if n_src_pts < 2: 366 | with gil: 367 | raise ValueError('Linear extrapolation requires at least ' 368 | '2 source points. Got {}.'.format(n_src_pts)) 369 | 370 | @cython.boundscheck(False) 371 | @cython.wraparound(False) 372 | cdef long kernel(self, int direction, double[:] z_src, 373 | double[:, :] fz_src, double level, 374 | double[:] fz_target) nogil except -1: 375 | """Linear extrapolation using either the first or last 2 values.""" 376 | cdef unsigned int m = fz_src.shape[0] 377 | cdef unsigned int n_src_pts = fz_src.shape[1] 378 | cdef unsigned int p0, p1, i 379 | cdef double frac, z_step 380 | 381 | if direction < 0: 382 | p0, p1 = 0, 1 383 | else: 384 | p0, p1 = n_src_pts - 2, n_src_pts - 1 385 | 386 | # Compute the normalised distance of the target point between p0 and p1 387 | z_step = z_src[p1] - z_src[p0] 388 | if z_step == 0: 389 | # If there is nothing between the last two points then we 390 | # extrapolate using a 0 gradient. 391 | frac = 0 392 | else: 393 | frac = ((level - z_src[p0]) / z_step) 394 | 395 | for i in range(m): 396 | fz_target[i] = fz_src[i, p0] + frac * (fz_src[i, p1] - fz_src[i, p0]) 397 | 398 | 399 | cdef class PyFuncExtrapolator(Extrapolator): 400 | cdef bint use_column_prep 401 | 402 | def __init__(self, use_column_prep=True): 403 | self.use_column_prep = use_column_prep 404 | 405 | def column_prep(self, z_target, z_src, fz_src, increasing): 406 | """ 407 | Called each time this extrapolator sees a new data array. 408 | This method may be used for validation of a column, or for column 409 | based pre-interpolation calculations (e.g. spline gradients). 410 | 411 | Note: This method is not called if :attr:`.call_column_prep` is False. 412 | 413 | """ 414 | pass 415 | 416 | cdef bint prepare_column(self, double[:] z_target, double[:] z_src, 417 | double[:, :] fz_src, bint increasing) nogil except -1: 418 | if self.use_column_prep: 419 | with gil: 420 | self.column_prep(z_target, z_src, fz_src, increasing) 421 | 422 | def extrap_kernel(self, direction, z_src, fz_src, level, output_array): 423 | # Fill the output array with nans. 424 | output_array[:] = np.nan 425 | 426 | @cython.boundscheck(False) 427 | @cython.wraparound(False) 428 | cdef long kernel(self, 429 | int direction, double[:] z_src, 430 | double[:, :] fz_src, double level, 431 | double[:] fz_target) nogil except -1: 432 | with gil: 433 | self.extrap_kernel(direction, z_src, fz_src, level, fz_target) 434 | 435 | 436 | interp_schemes = {'nearest': NearestNInterpolator, 437 | 'linear': LinearInterpolator} 438 | 439 | extrap_schemes = {'nearest': NearestNExtrapolator, 440 | 'linear': LinearExtrapolator, 441 | 'nan': NaNExtrapolator} 442 | 443 | 444 | # Construct interp/extrap constants exposed to the user. 445 | INTERPOLATE_LINEAR = interp_schemes['linear']() 446 | INTERPOLATE_NEAREST = interp_schemes['nearest']() 447 | EXTRAPOLATE_NAN = extrap_schemes['nan']() 448 | EXTRAPOLATE_NEAREST = extrap_schemes['nearest']() 449 | EXTRAPOLATE_LINEAR = extrap_schemes['linear']() 450 | 451 | 452 | def interpolate(z_target, z_src, fz_src, axis=-1, rising=None, 453 | interpolation='linear', extrapolation='nan'): 454 | """ 455 | Interface for optimised 1d interpolation across multiple dimensions. 456 | 457 | This function provides the ability to perform 1d interpolation on datasets 458 | with more than one dimension. For instance, this function can be used to 459 | interpolate a set of vertical levels, even if the interpolation coordinate 460 | depends upon other dimensions. 461 | 462 | A good use case might be when wanting to interpolate at a specific height 463 | for height data which also depends on x and y - e.g. extract 1000hPa level 464 | from a 3d dataset and associated pressure field. In the case of this 465 | example, pressure would be the `z` coordinate, and the dataset 466 | (e.g. geopotential height / temperature etc.) would be `f(z)`. 467 | 468 | Parameters 469 | ---------- 470 | z_target: 1d or nd array 471 | Target coordinate. 472 | This coordinate defines the levels to interpolate the source data 473 | ``fz_src`` to. If ``z_target`` is an nd array, it must have the same 474 | dimensionality as the source coordinate ``z_src``, and the shape of 475 | ``z_target`` must match the shape of ``z_src``, although the axis 476 | of interpolation may differ in dimension size. 477 | z_src: nd array 478 | Source coordinate. 479 | This coordinate defines the levels that the source data ``fz_src`` is 480 | interpolated from. 481 | fz_src: nd array 482 | The source data; the phenomenon data values to be interpolated from 483 | ``z_src`` to ``z_target``. 484 | The data array must be at least ``z_src.ndim``, and its trailing 485 | dimensions (i.e. those on its right hand side) must be exactly 486 | the same as the shape of ``z_src``. 487 | axis: int (default -1) 488 | The ``fz_src`` axis to perform the interpolation over. 489 | rising: bool (default None) 490 | Whether the values of the source's interpolation coordinate values 491 | are generally rising or generally falling. For example, values of 492 | pressure levels will be generally falling as the z coordinate 493 | increases. 494 | This will determine whether extrapolation needs to occur for 495 | ``z_target`` below the first and above the last ``z_src``. 496 | If rising is None, the first two interpolation coordinate values 497 | will be used to determine the general direction. In most cases, 498 | this is a good option. 499 | interpolation: :class:`.Interpolator` instance or valid scheme name 500 | The core interpolation operation to use. :attr:`.INTERPOLATE_LINEAR` 501 | and :attr:`_INTERPOLATE_NEAREST` are provided for convenient 502 | iterpolation modes. Linear interpolation is the default. 503 | extrapolation: :class:`.Extrapolator` instance or valid scheme name 504 | The core extrapolation operation to use. :attr:`.EXTRAPOLATE_NAN` and 505 | :attr:`.EXTRAPOLATE_NEAREST` are provided for convenient extrapolation 506 | modes. NaN extrapolation is the default. 507 | 508 | """ 509 | func = functools.partial( 510 | _interpolate, 511 | axis=axis, 512 | rising=rising, 513 | interpolation=interpolation, 514 | extrapolation=extrapolation 515 | ) 516 | if not hasattr(fz_src, 'compute'): 517 | # Numpy array 518 | return func(z_target, z_src, fz_src) 519 | 520 | # Dask array 521 | import dask.array as da 522 | 523 | # Ensure z_target is an array. 524 | if not isinstance(z_target, (np.ndarray, da.Array)): 525 | z_target = np.array(z_target) 526 | 527 | # Compute chunk sizes 528 | if axis < 0: 529 | axis += fz_src.ndim 530 | in_chunks = list(fz_src.chunks) 531 | in_chunks[axis] = fz_src.shape[axis] 532 | 533 | out_chunks = list(in_chunks) 534 | if z_target.ndim == 1: 535 | out_chunks[axis] = z_target.shape[0] 536 | else: 537 | out_chunks[axis] = z_target.shape[axis] 538 | 539 | # Ensure `fz_src` is not chunked along `axis`. 540 | fz_src = fz_src.rechunk(in_chunks) 541 | 542 | # Ensure z_src is a dask array with the correct chunks. 543 | if isinstance(z_src, da.Array): 544 | z_src = z_src.rechunk(in_chunks) 545 | else: 546 | z_src = da.asarray(z_src, chunks=in_chunks) 547 | 548 | # Compute with 1-dimensional target array. 549 | if z_target.ndim == 1: 550 | func = functools.partial(func, z_target) 551 | return da.map_blocks(func, z_src, fz_src, 552 | chunks=out_chunks, dtype=fz_src.dtype, 553 | meta=np.array((), dtype=fz_src.dtype)) 554 | 555 | # Ensure z_target is a dask array with the correct chunks 556 | if isinstance(z_target, da.Array): 557 | z_target = z_target.rechunk(out_chunks) 558 | else: 559 | z_target = da.asarray(z_target, chunks=out_chunks) 560 | 561 | # Compute with multi-dimensional target array. 562 | return da.map_blocks(func, z_target, z_src, fz_src, 563 | chunks=out_chunks, dtype=fz_src.dtype, 564 | meta=np.array((), dtype=fz_src.dtype)) 565 | 566 | 567 | def _interpolate(z_target, z_src, fz_src, axis=-1, rising=None, 568 | interpolation='linear', extrapolation='nan'): 569 | if interpolation in interp_schemes: 570 | interpolation = interp_schemes[interpolation]() 571 | if extrapolation in extrap_schemes: 572 | extrapolation = extrap_schemes[extrapolation]() 573 | 574 | interp = _Interpolation(z_target, z_src, fz_src, rising=rising, axis=axis, 575 | interpolation=interpolation, 576 | extrapolation=extrapolation) 577 | if interp.z_target.ndim == 1: 578 | return interp.interpolate() 579 | else: 580 | return interp.interpolate_z_target_nd() 581 | 582 | 583 | cdef class _Interpolation(object): 584 | """ 585 | Where the magic happens for gridwise_interp. The work of this __init__ is 586 | mostly for putting the input nd arrays into a 3 and 4 dimensional form for 587 | convenient (read: efficient) Cython form. Inline comments should help with 588 | understanding. 589 | 590 | """ 591 | cdef Interpolator interpolation 592 | cdef Extrapolator extrapolation 593 | 594 | cdef public np.dtype _target_dtype 595 | cdef int rising 596 | cdef public z_target, orig_shape, axis, _zp_reshaped, _fp_reshaped 597 | cdef public _result_working_shape, result_shape 598 | 599 | def __init__(self, z_target, z_src, fz_src, axis=-1, 600 | rising=None, 601 | Interpolator interpolation=INTERPOLATE_LINEAR, 602 | Extrapolator extrapolation=EXTRAPOLATE_NAN): 603 | # Cast data to numpy arrays if not already. 604 | z_target = np.array(z_target, dtype=np.float64) 605 | z_src = np.array(z_src, dtype=np.float64) 606 | fz_src = np.array(fz_src) 607 | #: The result data dtype. 608 | if np.issubdtype(fz_src.dtype, np.signedinteger): 609 | self._target_dtype = np.dtype('f8') 610 | else: 611 | self._target_dtype = fz_src.dtype 612 | fz_src = fz_src.astype(np.float64) 613 | 614 | # Compute the axis in absolute terms. 615 | fp_axis = (axis + fz_src.ndim) % fz_src.ndim 616 | zp_axis = fp_axis - (fz_src.ndim - z_src.ndim) 617 | if (not 0 <= zp_axis < z_src.ndim) or (axis >= fz_src.ndim): 618 | raise ValueError('Axis {} out of range.'.format(axis)) 619 | 620 | # Ensure that fz_src's shape is a superset of z_src's. 621 | if z_src.shape != fz_src.shape[-z_src.ndim:]: 622 | emsg = 'Shape for z_src {} is not a subset of fz_src {}.' 623 | raise ValueError(emsg.format(z_src.shape, fz_src.shape)) 624 | 625 | if z_target.ndim == 1: 626 | z_target_size = z_target.shape[0] 627 | else: 628 | # Ensure z_target and z_src have same ndims. 629 | if z_target.ndim != z_src.ndim: 630 | emsg = ('z_target and z_src must have the same number ' 631 | 'of dimensions, got {} != {}.') 632 | raise ValueError(emsg.format(z_target.ndim, z_src.ndim)) 633 | # Ensure z_target and z_src have same shape over their 634 | # non-interpolated axes i.e. we need to ignore the axis of 635 | # interpolation when comparing the shapes of z_target and z_src. 636 | # E.g a z_target.shape=(3, 4, 5) and z_src.shape=(3, 10, 5), 637 | # interpolating over zp_axis=1 is fine as (3, :, 5) == (3, :, 5). 638 | # However, a z_target.shape=(3, 4, 6) and z_src.shape=(3, 10, 5), 639 | # interpolating over zp_axis=1 must fail as (3, :, 6) != (3, :, 5) 640 | zts, zss = z_target.shape, z_src.shape 641 | ztsp, zssp = zip(*[(str(j), str(k)) if i!=zp_axis else (':', ':') 642 | for i, (j, k) in enumerate(zip(zts, zss))]) 643 | if ztsp != zssp: 644 | sep, emsg = ', ', ('z_target and z_src have different shapes, ' 645 | 'got ({}) != ({}).') 646 | raise ValueError(emsg.format(sep.join(ztsp), sep.join(zssp))) 647 | z_target_size = zts[zp_axis] 648 | 649 | # We are going to put the source coordinate into a 3d shape for convenience of 650 | # Cython interface. Writing generic, fast, n-dimensional Cython code 651 | # is not possible, but it is possible to always support a 3d array with 652 | # the middle dimensions being from the axis argument. 653 | 654 | # Work out the shape of the left hand side of the 3d array. 655 | lh_dim_size = ([1] + list(np.cumprod(z_src.shape)))[zp_axis] 656 | 657 | # The coordinate shape will therefore be (size of lhs, size of axis, the rest). 658 | new_shape = (lh_dim_size, z_src.shape[zp_axis], -1) 659 | 660 | #: The levels to interpolate onto. 661 | self.z_target = z_target 662 | #: The shape of the input data (fz_src). 663 | self.orig_shape = fz_src.shape 664 | #: The fz_src axis over which to do the interpolation. 665 | self.axis = axis 666 | 667 | #: The source z coordinate data reshaped into 3d working shape form. 668 | self._zp_reshaped = z_src.reshape(new_shape) 669 | #: The fz_src data reshaped into 4d working shape form. The left-most 670 | #: dimension is the dimension broadcast dimension - these are the 671 | #: values which are not dependent on z, and come from the fact that 672 | #: fz_src may be higher dimensional than z_src. 673 | self._fp_reshaped = fz_src.reshape((-1, ) + self._zp_reshaped.shape) 674 | 675 | # Figure out the normalised 4d shape of the working result array. 676 | # This will be the same as _fp_reshaped, but the length of the 677 | # interpolation axis will change. 678 | result_working_shape = list(self._fp_reshaped.shape) 679 | # Update the axis to be of the size of the levels. 680 | result_working_shape[2] = z_target_size 681 | 682 | #: The shape of result while the interpolation is being calculated. 683 | self._result_working_shape = tuple(result_working_shape) 684 | 685 | # Figure out the nd shape of the result array. 686 | # This will be the same as fz_src, but the length of the 687 | # interpolation axis will change. 688 | result_shape = list(self.orig_shape) 689 | # Update the axis to be of the size of the levels. 690 | result_shape[fp_axis] = z_target_size 691 | 692 | #: The shape of the interpolated data. 693 | self.result_shape = tuple(result_shape) 694 | 695 | if rising is None: 696 | if z_src.shape[zp_axis] < 2: 697 | raise ValueError('The rising keyword must be defined when ' 698 | 'the size of the source array is <2 in ' 699 | 'the interpolation axis.') 700 | z_src_indexer = [0] * z_src.ndim 701 | z_src_indexer[zp_axis] = slice(0, 2) 702 | first_two = z_src[tuple(z_src_indexer)] 703 | rising = first_two[0] <= first_two[1] 704 | 705 | self.rising = bool(rising) 706 | 707 | # Sometimes we want to add additional constraints on our interpolation 708 | # and extrapolation - for example, linear extrapolation requires there 709 | # to be two coordinates to interpolate from. 710 | if hasattr(self.interpolation, 'validate_data'): 711 | self.interpolation.validate_data(self) 712 | 713 | if hasattr(self.extrapolation, 'validate_data'): 714 | self.extrapolation.validate_data(self) 715 | 716 | self.interpolation = interpolation 717 | self.extrapolation = extrapolation 718 | 719 | def interpolate(self): 720 | # Construct the output array for the interpolation to fill in. 721 | fz_target = np.empty(self._result_working_shape, dtype=np.float64) 722 | 723 | cdef unsigned int i, j, ni, nj 724 | 725 | ni = fz_target.shape[1] 726 | nj = fz_target.shape[3] 727 | 728 | # Pull in our pre-formed z_target, z_src, and fz_src arrays. 729 | cdef double[:] z_target = self.z_target 730 | cdef double[:, :, :] z_src = self._zp_reshaped 731 | cdef double[:, :, :, :] fz_src = self._fp_reshaped 732 | 733 | # Construct a memory view of the fz_target array. 734 | cdef double[:, :, :, :] fz_target_view = fz_target 735 | 736 | # Release the GIL and do the for loop over the left-hand, and 737 | # right-hand dimensions. The loop optimised for row-major data (C). 738 | with nogil: 739 | for j in range(nj): 740 | for i in range(ni): 741 | gridwise_interpolation(z_target, z_src[i, :, j], fz_src[:, i, :, j], 742 | self.rising, 743 | self.interpolation, 744 | self.extrapolation, 745 | fz_target_view[:, i, :, j]) 746 | return fz_target.reshape(self.result_shape).astype(self._target_dtype) 747 | 748 | def interpolate_z_target_nd(self): 749 | """ 750 | Using the exact same functionality as found in interpolate, only without the assumption 751 | that the target z is 1d. 752 | 753 | """ 754 | # Construct the output array for the interpolation to fill in. 755 | fz_target = np.empty(self._result_working_shape, dtype=np.float64) 756 | 757 | cdef unsigned int i, j, ni, nj 758 | 759 | ni = fz_target.shape[1] 760 | nj = fz_target.shape[3] 761 | 762 | z_target_reshaped = self.z_target.reshape(self._result_working_shape[1:]) 763 | cdef double[:, :, :] z_target = z_target_reshaped 764 | 765 | # Pull in our pre-formed z_src, and fz_src arrays. 766 | cdef double[:, :, :] z_src = self._zp_reshaped 767 | cdef double[:, :, :, :] fz_src = self._fp_reshaped 768 | 769 | # Construct a memory view of the fz_target array. 770 | cdef double[:, :, :, :] fz_target_view = fz_target 771 | 772 | # Release the GIL and do the for loop over the left-hand, and 773 | # right-hand dimensions. The loop optimised for row-major data (C). 774 | with nogil: 775 | for j in range(nj): 776 | for i in range(ni): 777 | gridwise_interpolation(z_target[i, :, j], z_src[i, :, j], fz_src[:, i, :, j], 778 | self.rising, 779 | self.interpolation, 780 | self.extrapolation, 781 | fz_target_view[:, i, :, j]) 782 | 783 | return fz_target.reshape(self.result_shape).astype(self._target_dtype) 784 | -------------------------------------------------------------------------------- /src/stratify/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciTools/python-stratify/b09c836f1667d6c05258b42fbba64e2fbf1bd687/src/stratify/tests/__init__.py -------------------------------------------------------------------------------- /src/stratify/tests/performance.py: -------------------------------------------------------------------------------- 1 | """Functions that may be used to measure performance of a component.""" 2 | 3 | import dask.array as da 4 | import numpy as np 5 | 6 | import stratify 7 | 8 | 9 | def src_data(shape=(400, 500, 100), lazy=False): 10 | z = np.tile(np.linspace(0, 100, shape[-1]), np.prod(shape[:2])).reshape(shape) 11 | if lazy: 12 | fz = da.arange(np.prod(shape), dtype=np.float64).reshape(shape) 13 | else: 14 | fz = np.arange(np.prod(shape), dtype=np.float64).reshape(shape) 15 | return z, fz 16 | 17 | 18 | def interp_and_extrap( 19 | shape, 20 | lazy, 21 | interp=stratify.INTERPOLATE_LINEAR, 22 | extrap=stratify.EXTRAPOLATE_NEAREST, 23 | ): 24 | z, fz = src_data(shape, lazy) 25 | tgt = np.linspace(-20, 120, 50) 26 | result = stratify.interpolate( 27 | tgt, 28 | z, 29 | fz, 30 | interpolation=interp, 31 | extrapolation=extrap, 32 | ) 33 | if isinstance(result, da.Array): 34 | print("lazy calculation") 35 | print(result.chunks) 36 | result.compute() 37 | else: 38 | print("non-lazy calculation") 39 | 40 | 41 | if __name__ == "__main__": 42 | import sys 43 | 44 | lazy = "lazy" in sys.argv[1:] 45 | interp_and_extrap(shape=(500, 600, 100), lazy=lazy) 46 | -------------------------------------------------------------------------------- /src/stratify/tests/test_bounded_vinterp.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | from numpy.testing import assert_array_equal 5 | 6 | import stratify._bounded_vinterp as bounded_vinterp 7 | 8 | 9 | class Test1D(unittest.TestCase): 10 | # 1D cases represent the situation where the vertical coordinate is 11 | # 1-dimensional and as such, there are no dimension which the vertical 12 | # coordinates vary over other than the 'axis of interpolation'. 13 | # The common approach in solving these problems is to reshape and 14 | # transpose arrays into the form: 15 | # [broadcasting_dims, axis_interpolation, z_varying]. 16 | # This this will take the form: 17 | # [broadcasting_dims, axis_interpolation, 1]. 18 | def setUp(self): 19 | self.data = np.ones((4, 6)) 20 | self.bounds = self.gen_bounds(0, 7, 1) 21 | 22 | def gen_bounds(self, start, stop, step): 23 | bounds = np.vstack( 24 | [ 25 | np.arange(start, stop - step, step), 26 | np.arange(start + step, stop, step), 27 | ] 28 | ) 29 | bounds = bounds.transpose((1, 0)) 30 | return bounds.copy() 31 | 32 | def test_target_half_resolution(self): 33 | target_bounds = self.gen_bounds(0, 7, 2) 34 | res = bounded_vinterp.interpolate_conservative( 35 | target_bounds, self.bounds, self.data, axis=1 36 | ) 37 | target_data = np.ones((4, 3)) * 2 38 | assert_array_equal(res, target_data) 39 | 40 | def test_target_double_resolution(self): 41 | target_bounds = self.gen_bounds(0, 6.5, 0.5) 42 | res = bounded_vinterp.interpolate_conservative( 43 | target_bounds, self.bounds, self.data, axis=1 44 | ) 45 | target_data = np.ones((4, 12)) * 0.5 46 | assert_array_equal(res, target_data) 47 | 48 | def test_no_broadcasting(self): 49 | # In the case of no broadcasting, transposed and reshaped array of form 50 | # [broadcasting_dims, axis_interpolation, z_varying] should end up 51 | # [1, axis_interpolation, 1]. 52 | data = self.data[0] 53 | target_bounds = self.gen_bounds(0, 7, 2) 54 | res = bounded_vinterp.interpolate_conservative( 55 | target_bounds, self.bounds, data, axis=0 56 | ) 57 | target_data = np.ones(3) * 2 58 | assert_array_equal(res, target_data) 59 | 60 | def test_source_with_nans(self): 61 | # In the case of no broadcasting, transposed and reshaped array of form 62 | # [broadcasting_dims, axis_interpolation, z_varying] should end up 63 | # [1, axis_interpolation, 1]. 64 | data = self.data[0] 65 | data[0] = data[-2:] = np.nan 66 | target_bounds = self.gen_bounds(0, 6.5, 0.5) 67 | res = bounded_vinterp.interpolate_conservative( 68 | target_bounds, self.bounds, data, axis=0 69 | ) 70 | target_data = np.ones(12) * 0.5 71 | target_data[:2] = np.nan 72 | target_data[-4:] = np.nan 73 | assert_array_equal(res, target_data) 74 | 75 | def test_target_extends_above_source(self): 76 | # |-|-|-|-|-|-|-| - Source 77 | # |-|-|-|-|-|-|-|-| - Target 78 | source_bounds = self.gen_bounds(0, 7, 1) 79 | target_bounds = self.gen_bounds(0, 8, 1) 80 | res = bounded_vinterp.interpolate_conservative( 81 | target_bounds, source_bounds, self.data, axis=1 82 | ) 83 | target_data = np.ones((4, 7)) 84 | target_data[:, -1] = np.nan 85 | assert_array_equal(res, target_data) 86 | 87 | def test_target_extends_above_source_non_equally_spaced_coords(self): 88 | # |--|--|-------|| - Source 89 | # |-|-|-|-|-|-|-|-| - Target 90 | source_bounds = np.array([[0, 1.5], [1.5, 2], [2, 6], [6, 6.5]]) 91 | target_bounds = self.gen_bounds(0, 8, 1) 92 | data = np.ones((4, 4)) 93 | res = bounded_vinterp.interpolate_conservative( 94 | target_bounds, source_bounds, data, axis=1 95 | ) 96 | target_data = np.array( 97 | [1 / 1.5, 1 + ((1 / 3.0) / 1), 0.25, 0.25, 0.25, 0.25, 1.0] 98 | )[None] 99 | target_data = np.repeat(target_data, 4, 0) 100 | assert_array_equal(res, target_data) 101 | 102 | def test_target_extends_below_source(self): 103 | # |-|-|-|-|-|-|-| - Source 104 | # |-|-|-|-|-|-|-|-| - Target 105 | source_bounds = self.gen_bounds(0, 7, 1) 106 | target_bounds = self.gen_bounds(-1, 7, 1) 107 | res = bounded_vinterp.interpolate_conservative( 108 | target_bounds, source_bounds, self.data, axis=1 109 | ) 110 | target_data = np.ones((4, 7)) 111 | target_data[:, 0] = np.nan 112 | assert_array_equal(res, target_data) 113 | 114 | 115 | class TestND(unittest.TestCase): 116 | # ND cases represent the situation where the vertical coordinate varies 117 | # over dimensions other than the axis of interpolation. 118 | def setUp(self): 119 | self.data = np.ones((2, 6, 4, 3)) 120 | self.bounds = self.gen_bounds(0, 7, 1) 121 | 122 | def gen_bounds(self, start, stop, step): 123 | bounds = np.vstack( 124 | [ 125 | np.arange(start, stop - step, step), 126 | np.arange(start + step, stop, step), 127 | ] 128 | ) 129 | bounds = bounds.transpose((1, 0)) 130 | bounds = bounds[..., None, :].repeat(4, -2) 131 | bounds = bounds[..., None, :].repeat(3, -2) 132 | return bounds.copy() 133 | 134 | def test_target_half_resolution(self): 135 | target_bounds = self.gen_bounds(0, 7, 2) 136 | res = bounded_vinterp.interpolate_conservative( 137 | target_bounds, self.bounds, self.data, axis=1 138 | ) 139 | 140 | target_data = np.ones((2, 3, 4, 3)) * 2 141 | assert_array_equal(res, target_data) 142 | 143 | def test_target_half_resolution_alt_axis(self): 144 | # Ensure results as expected with an alternative axis of interpolation. 145 | data = self.data.transpose((0, 2, 1, 3)) 146 | bounds = self.bounds.transpose((1, 0, 2, 3)) 147 | target_bounds = self.gen_bounds(0, 7, 2) 148 | target_bounds = target_bounds.transpose((1, 0, 2, 3)) 149 | 150 | res = bounded_vinterp.interpolate_conservative( 151 | target_bounds, bounds, data, axis=2 152 | ) 153 | target_data = np.ones((2, 4, 3, 3)) * 2 154 | assert_array_equal(res, target_data) 155 | 156 | def test_target_double_resolution(self): 157 | target_bounds = self.gen_bounds(0, 6.5, 0.5) 158 | res = bounded_vinterp.interpolate_conservative( 159 | target_bounds, self.bounds, self.data, axis=1 160 | ) 161 | target_data = np.ones((2, 12, 4, 3)) * 0.5 162 | assert_array_equal(res, target_data) 163 | 164 | 165 | class TestExceptions(unittest.TestCase): 166 | def test_mismatch_source_target_level_dimensionality(self): 167 | source_bounds = np.zeros((3, 4, 2)) 168 | target_bounds = np.zeros((4, 2)) 169 | data = np.zeros((3, 4)) 170 | 171 | msg = "Expecting source and target levels dimensionality" 172 | with self.assertRaisesRegex(ValueError, msg): 173 | bounded_vinterp.interpolate_conservative(target_bounds, source_bounds, data) 174 | 175 | def test_mismatch_source_target_level_shape(self): 176 | # The source and target levels should have identical shape, other than 177 | # the axis of interpolation. 178 | source_bounds = np.zeros((3, 4, 2)) 179 | target_bounds = np.zeros((2, 5, 2)) 180 | data = np.zeros((3, 4)) 181 | 182 | msg = ( 183 | "Expecting the shape of the source and target levels except " 184 | "the axis of interpolation to be identical. " 185 | r"\('-', 4, 2\) != \(2, 5, 2\)" 186 | ) 187 | with self.assertRaisesRegex(ValueError, msg): 188 | bounded_vinterp.interpolate_conservative( 189 | target_bounds, source_bounds, data, axis=0 190 | ) 191 | 192 | def test_mismatch_between_source_levels_source_data(self): 193 | # The source levels should reflect the shape of the data. 194 | source_bounds = np.zeros((2, 4, 2)) 195 | target_bounds = np.zeros((2, 4, 2)) 196 | data = np.zeros((3, 4)) 197 | 198 | msg = ( 199 | "The provided data is not of compatible shape with the " 200 | r"provided source bounds. \('-', 3, 4\) != \(2, 4\)" 201 | ) 202 | with self.assertRaisesRegex(ValueError, msg): 203 | bounded_vinterp.interpolate_conservative( 204 | target_bounds, source_bounds, data, axis=0 205 | ) 206 | 207 | def test_unexpected_bounds_shape(self): 208 | # Expecting bounds of size 2 (that is the upper and lower). 209 | # The source levels should reflect the shape of the data. 210 | source_bounds = np.zeros((3, 4, 4)) 211 | target_bounds = np.zeros((4, 4, 4)) 212 | data = np.zeros((3, 4)) 213 | 214 | msg = r"Unexpected source and target bounds shape. shape\[-1\] != 2" 215 | with self.assertRaisesRegex(ValueError, msg): 216 | bounded_vinterp.interpolate_conservative( 217 | target_bounds, source_bounds, data, axis=0 218 | ) 219 | 220 | def test_not_conservative(self): 221 | # Where the target does not cover the full extent of the source. 222 | # |-|-|-|-|-|-| - Source 223 | # |-|-|-|-| - Target 224 | def gen_bounds(start, stop, step): 225 | bounds = np.vstack( 226 | [ 227 | np.arange(start, stop - step, step), 228 | np.arange(start + step, stop, step), 229 | ] 230 | ) 231 | bounds = bounds.transpose((1, 0)) 232 | return bounds.copy() 233 | 234 | source_bounds = gen_bounds(0, 7, 1) 235 | target_bounds = gen_bounds(1, 6, 1) 236 | data = np.ones((4, 6)) 237 | 238 | msg = "Weights calculation yields a less than conservative result." 239 | with self.assertRaisesRegex(ValueError, msg): 240 | bounded_vinterp.interpolate_conservative( 241 | target_bounds, source_bounds, data, axis=1 242 | ) 243 | 244 | 245 | if __name__ == "__main__": 246 | unittest.main() 247 | -------------------------------------------------------------------------------- /src/stratify/tests/test_vinterp.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import dask.array as da 4 | import numpy as np 5 | from numpy.testing import assert_array_almost_equal, assert_array_equal 6 | 7 | import stratify 8 | import stratify._vinterp as vinterp 9 | 10 | 11 | class IndexInterpolator(vinterp.PyFuncInterpolator): 12 | def interp_kernel(self, index, z_src, fz_src, level, output_array): 13 | output_array[:] = index 14 | 15 | 16 | class DirectionExtrapolator(vinterp.PyFuncExtrapolator): 17 | def extrap_kernel(self, direction, z_src, fz_src, level, output_array): 18 | output_array[:] = np.inf if direction > 0 else -np.inf 19 | 20 | 21 | class TestColumnInterpolation(unittest.TestCase): 22 | def interpolate(self, x_target, x_src, rising=None): 23 | x_target = np.array(x_target) 24 | x_src = np.array(x_src) 25 | fx_src = np.empty(x_src.shape) 26 | 27 | index_interp = IndexInterpolator() 28 | extrap_direct = DirectionExtrapolator() 29 | 30 | r1 = stratify.interpolate( 31 | x_target, 32 | x_src, 33 | fx_src, 34 | rising=rising, 35 | interpolation=index_interp, 36 | extrapolation=extrap_direct, 37 | ) 38 | 39 | if rising is not None: 40 | r2 = stratify.interpolate( 41 | -1 * x_target, 42 | -1 * x_src, 43 | fx_src, 44 | rising=not rising, 45 | interpolation=index_interp, 46 | extrapolation=extrap_direct, 47 | ) 48 | assert_array_equal(r1, r2) 49 | 50 | lazy_fx_src = da.asarray(fx_src, chunks=tuple(range(1, x_src.ndim + 1))) 51 | r3 = stratify.interpolate( 52 | x_target, 53 | x_src, 54 | lazy_fx_src, 55 | rising=rising, 56 | interpolation=index_interp, 57 | extrapolation=extrap_direct, 58 | ) 59 | assert_array_equal(r1, r3.compute()) 60 | 61 | return r1 62 | 63 | def test_interp_only(self): 64 | r = self.interpolate([1, 2, 3], [1, 3]) 65 | assert_array_equal(r, [0, 1, 1]) 66 | 67 | def test_interp_multi_level_single_source(self): 68 | r = self.interpolate([1.5, 2, 2.5], [1, 3]) 69 | assert_array_equal(r, [1, 1, 1]) 70 | 71 | def test_interp_single_level_multiple_source(self): 72 | r = self.interpolate([3.5], [1, 2, 3, 3, 4]) 73 | assert_array_equal(r, [4]) 74 | 75 | def test_lower_extrap_only(self): 76 | r = self.interpolate([1, 2, 3], [4, 5]) 77 | assert_array_equal(r, [-np.inf, -np.inf, -np.inf]) 78 | 79 | def test_upper_extrap_only(self): 80 | r = self.interpolate([1, 2, 3], [-4, -5], rising=True) 81 | assert_array_equal(r, [np.inf, np.inf, np.inf]) 82 | 83 | def test_extrap_on_both_sides_only(self): 84 | r = self.interpolate([1, 2, 5, 6], [3, 4]) 85 | assert_array_equal(r, [-np.inf, -np.inf, np.inf, np.inf]) 86 | 87 | def test_interp_and_extrap(self): 88 | r = self.interpolate([1, 2, 3, 5, 6], [2, 4, 5]) 89 | assert_array_equal(r, [-np.inf, 0, 1, 2, np.inf]) 90 | 91 | def test_nan_in_target(self): 92 | msg = "The target coordinate .* NaN" 93 | with self.assertRaisesRegex(ValueError, msg): 94 | self.interpolate([1, np.nan], [2, 4, 5]) 95 | 96 | def test_nan_in_src(self): 97 | msg = "The source coordinate .* NaN" 98 | with self.assertRaisesRegex(ValueError, msg): 99 | self.interpolate([1], [0, np.nan], rising=True) 100 | 101 | def test_all_nan_in_src(self): 102 | r = self.interpolate([1, 2, 3, 4], [np.nan, np.nan, np.nan]) 103 | assert_array_equal(r, [np.nan, np.nan, np.nan, np.nan]) 104 | 105 | def test_nan_in_src_not_a_problem(self): 106 | # If we pick levels low enough, we can get away with having NaNs 107 | # in the source. 108 | r = self.interpolate([1, 3], [2, 4, np.nan]) 109 | assert_array_equal(r, [-np.inf, 1]) 110 | 111 | def test_no_levels(self): 112 | r = self.interpolate([], [2, 4, np.nan]) 113 | assert_array_equal(r, []) 114 | 115 | def test_wrong_rising_target(self): 116 | r = self.interpolate([2, 1], [1, 2]) 117 | assert_array_equal(r, [1, np.inf]) 118 | 119 | def test_wrong_rising_source(self): 120 | r = self.interpolate([1, 2], [2, 1], rising=True) 121 | assert_array_equal(r, [-np.inf, 0]) 122 | 123 | def test_wrong_rising_source_and_target(self): 124 | # If we overshoot the first level, there is no hope, 125 | # so we end up extrapolating. 126 | r = self.interpolate([3, 2, 1, 0], [2, 1], rising=True) 127 | assert_array_equal(r, [np.inf, np.inf, np.inf, np.inf]) 128 | 129 | def test_non_monotonic_coordinate_interp(self): 130 | result = self.interpolate([15, 5, 15.0], [10.0, 20, 0, 20]) 131 | assert_array_equal(result, [1, 2, 3]) 132 | 133 | def test_non_monotonic_coordinate_extrap(self): 134 | result = self.interpolate([0, 15, 16, 17, 5, 15.0, 25], [10.0, 40, 0, 20]) 135 | assert_array_equal(result, [-np.inf, 1, 1, 1, 2, 3, np.inf]) 136 | 137 | def test_length_one_interp(self): 138 | r = self.interpolate([1], [2], rising=True) 139 | assert_array_equal(r, [-np.inf]) 140 | 141 | def test_auto_rising_not_enough_values(self): 142 | with self.assertRaises(ValueError): 143 | _ = self.interpolate([1], [2]) 144 | 145 | def test_auto_rising_equal_values(self): 146 | # The code checks whether the first value is <= or equal to 147 | # the second. If it didn't, we'd end up with +inf, not -inf. 148 | r = self.interpolate([1], [2, 2]) 149 | assert_array_equal(r, [-np.inf]) 150 | 151 | 152 | class Test_INTERPOLATE_LINEAR(unittest.TestCase): 153 | def interpolate(self, x_target): 154 | interpolation = stratify.INTERPOLATE_LINEAR 155 | extrapolation = DirectionExtrapolator() 156 | 157 | x_src = np.arange(5) 158 | fx_src = 10 * x_src 159 | 160 | # Use -2 to test negative number support. 161 | return stratify.interpolate( 162 | np.array(x_target) - 2, 163 | x_src - 2, 164 | fx_src, 165 | interpolation=interpolation, 166 | extrapolation=extrapolation, 167 | ) 168 | 169 | def test_on_the_mark(self): 170 | assert_array_equal(self.interpolate([0, 1, 2, 3, 4]), [0, 10, 20, 30, 40]) 171 | 172 | def test_zero_gradient(self): 173 | assert_array_equal( 174 | stratify.interpolate( 175 | [1], [0, 1, 1, 2], [10, 20, 30, 40], interpolation="linear" 176 | ), 177 | [20], 178 | ) 179 | 180 | def test_inbetween(self): 181 | assert_array_equal( 182 | self.interpolate([0.5, 1.25, 2.5, 3.75]), [5, 12.5, 25, 37.5] 183 | ) 184 | 185 | def test_high_precision(self): 186 | assert_array_almost_equal( 187 | self.interpolate([1.123456789]), [11.23456789], decimal=6 188 | ) 189 | 190 | def test_single_point(self): 191 | # Test that a single input point that falls exactly on the target 192 | # level triggers a shortcut that avoids the expectation of >=2 source 193 | # points. 194 | interpolation = stratify.INTERPOLATE_LINEAR 195 | extrapolation = DirectionExtrapolator() 196 | 197 | r = stratify.interpolate( 198 | [2], 199 | [2], 200 | [20], 201 | interpolation=interpolation, 202 | extrapolation=extrapolation, 203 | rising=True, 204 | ) 205 | self.assertEqual(r, 20) 206 | 207 | 208 | class Test_INTERPOLATE_NEAREST(unittest.TestCase): 209 | def interpolate(self, x_target): 210 | interpolation = stratify.INTERPOLATE_NEAREST 211 | extrapolation = DirectionExtrapolator() 212 | 213 | x_src = np.arange(5) 214 | fx_src = 10 * x_src 215 | 216 | # Use -2 to test negative number support. 217 | return stratify.interpolate( 218 | np.array(x_target) - 2, 219 | x_src - 2, 220 | fx_src, 221 | interpolation=interpolation, 222 | extrapolation=extrapolation, 223 | ) 224 | 225 | def test_on_the_mark(self): 226 | assert_array_equal(self.interpolate([0, 1, 2, 3, 4]), [0, 10, 20, 30, 40]) 227 | 228 | def test_inbetween(self): 229 | # Nearest rounds down for exactly half way. 230 | assert_array_equal(self.interpolate([0.5, 1.25, 2.5, 3.75]), [0, 10, 20, 40]) 231 | 232 | def test_high_precision(self): 233 | assert_array_equal(self.interpolate([1.123456789]), [10]) 234 | 235 | 236 | class Test_EXTRAPOLATE_NAN(unittest.TestCase): 237 | def interpolate(self, x_target): 238 | interpolation = IndexInterpolator() 239 | extrapolation = stratify.EXTRAPOLATE_NAN 240 | 241 | x_src = np.arange(5) 242 | fx_src = 10 * x_src 243 | 244 | # Use -2 to test negative number support. 245 | return stratify.interpolate( 246 | np.array(x_target) - 2, 247 | x_src - 2, 248 | fx_src, 249 | interpolation=interpolation, 250 | extrapolation=extrapolation, 251 | ) 252 | 253 | def test_below(self): 254 | assert_array_equal(self.interpolate([-1]), [np.nan]) 255 | 256 | def test_above(self): 257 | assert_array_equal(self.interpolate([5]), [np.nan]) 258 | 259 | 260 | class Test_EXTRAPOLATE_NEAREST(unittest.TestCase): 261 | def interpolate(self, x_target): 262 | interpolation = IndexInterpolator() 263 | extrapolation = stratify.EXTRAPOLATE_NEAREST 264 | 265 | x_src = np.arange(5) 266 | fx_src = 10 * x_src 267 | 268 | # Use -2 to test negative number support. 269 | return stratify.interpolate( 270 | np.array(x_target) - 2, 271 | x_src - 2, 272 | fx_src, 273 | interpolation=interpolation, 274 | extrapolation=extrapolation, 275 | ) 276 | 277 | def test_below(self): 278 | assert_array_equal(self.interpolate([-1]), [0.0]) 279 | 280 | def test_above(self): 281 | assert_array_equal(self.interpolate([5]), [40]) 282 | 283 | 284 | class Test_EXTRAPOLATE_LINEAR(unittest.TestCase): 285 | def interpolate(self, x_target): 286 | interpolation = IndexInterpolator() 287 | extrapolation = stratify.EXTRAPOLATE_LINEAR 288 | 289 | x_src = np.arange(5) 290 | # To spice things up a bit, let's make x_src non-equal distance. 291 | x_src[4] = 9 292 | fx_src = 10 * x_src 293 | 294 | # Use -2 to test negative number support. 295 | return stratify.interpolate( 296 | np.array(x_target) - 2, 297 | x_src - 2, 298 | fx_src, 299 | interpolation=interpolation, 300 | extrapolation=extrapolation, 301 | ) 302 | 303 | def test_below(self): 304 | assert_array_equal(self.interpolate([-1]), [-10.0]) 305 | 306 | def test_above(self): 307 | assert_array_almost_equal(self.interpolate([15.123]), [151.23]) 308 | 309 | def test_zero_gradient(self): 310 | assert_array_almost_equal( 311 | stratify.interpolate([2], [0, 0], [1, 1], extrapolation="linear"), 312 | [1], 313 | ) 314 | 315 | def test_npts(self): 316 | interpolation = IndexInterpolator() 317 | extrapolation = stratify.EXTRAPOLATE_LINEAR 318 | 319 | msg = r"Linear extrapolation requires at least 2 " r"source points. Got 1." 320 | with self.assertRaisesRegex(ValueError, msg): 321 | stratify.interpolate( 322 | [1, 3.0], 323 | [2], 324 | [20], 325 | interpolation=interpolation, 326 | extrapolation=extrapolation, 327 | rising=True, 328 | ) 329 | 330 | 331 | class Test_custom_extrap_kernel(unittest.TestCase): 332 | class my_kernel(vinterp.PyFuncExtrapolator): 333 | def __init__(self, *args, **kwargs): 334 | super(Test_custom_extrap_kernel.my_kernel, self).__init__(*args, **kwargs) 335 | 336 | def extrap_kernel(self, direction, z_src, fz_src, level, output_array): 337 | output_array[:] = -10 338 | 339 | def test(self): 340 | interpolation = IndexInterpolator() 341 | extrapolation = Test_custom_extrap_kernel.my_kernel() 342 | 343 | r = stratify.interpolate( 344 | [1, 3.0], 345 | [1, 2], 346 | [10, 20], 347 | interpolation=interpolation, 348 | extrapolation=extrapolation, 349 | rising=True, 350 | ) 351 | assert_array_equal(r, [0, -10]) 352 | 353 | 354 | class Test_Interpolation(unittest.TestCase): 355 | def test_axis_m1(self): 356 | data = np.empty([5, 4, 23, 7, 3]) 357 | zdata = np.empty([5, 4, 23, 7, 3]) 358 | i = vinterp._Interpolation([1, 3], zdata, data) 359 | # 1288 == 5 * 4 * 23 * 7 360 | self.assertEqual(i._result_working_shape, (1, 3220, 2, 1)) 361 | self.assertEqual(i.result_shape, (5, 4, 23, 7, 2)) 362 | self.assertEqual(i._zp_reshaped.shape, (3220, 3, 1)) 363 | self.assertEqual(i._fp_reshaped.shape, (1, 3220, 3, 1)) 364 | self.assertEqual(i.axis, -1) 365 | self.assertEqual(i.orig_shape, data.shape) 366 | self.assertIsInstance(i.z_target, np.ndarray) 367 | self.assertEqual(list(i.z_target), [1, 3]) 368 | 369 | def test_axis_0(self): 370 | data = zdata = np.empty([5, 4, 23, 7, 3]) 371 | i = vinterp._Interpolation([1, 3], data, zdata, axis=0) 372 | # 1932 == 4 * 23 * 7 *3 373 | self.assertEqual(i._result_working_shape, (1, 1, 2, 1932)) 374 | self.assertEqual(i.result_shape, (2, 4, 23, 7, 3)) 375 | self.assertEqual(i._zp_reshaped.shape, (1, 5, 1932)) 376 | 377 | def test_axis_2(self): 378 | data = zdata = np.empty([5, 4, 23, 7, 3]) 379 | i = vinterp._Interpolation([1, 3], data, zdata, axis=2) 380 | # 1932 == 4 * 23 * 7 *3 381 | self.assertEqual(i._result_working_shape, (1, 20, 2, 21)) 382 | self.assertEqual(i.result_shape, (5, 4, 2, 7, 3)) 383 | self.assertEqual(i._zp_reshaped.shape, (20, 23, 21)) 384 | 385 | def test_inconsistent_shape(self): 386 | data = np.empty([5, 4, 23, 7, 3]) 387 | zdata = np.empty([5, 4, 3, 7, 3]) 388 | emsg = "z_src .* is not a subset of fz_src" 389 | with self.assertRaisesRegex(ValueError, emsg): 390 | vinterp._Interpolation([1, 3], data, zdata, axis=2) 391 | 392 | def test_axis_out_of_bounds_fz_src_relative(self): 393 | # axis is out of bounds as identified by the absolute axis with z_src. 394 | data = np.empty((5, 4)) 395 | zdata = np.empty((5, 4)) 396 | axis = 4 397 | emsg = "Axis {} out of range" 398 | with self.assertRaisesRegex(ValueError, emsg.format(axis)): 399 | vinterp._Interpolation([1, 3], data, zdata, axis=axis) 400 | 401 | def test_axis_out_of_bounds_z_src_absolute(self): 402 | # axis is out of bounds as identified by the relative axis with fz_src. 403 | data = np.empty((5, 4)) 404 | zdata = np.empty((3, 5, 4)) 405 | axis = 0 406 | emsg = "Axis {} out of range" 407 | with self.assertRaisesRegex(ValueError, emsg.format(axis)): 408 | vinterp._Interpolation([1, 3], data, zdata, axis=axis) 409 | 410 | def test_axis_greater_than_z_src_ndim(self): 411 | # Ensure that axis is not unnecessarily constrained to the dimensions 412 | # of z_src. 413 | data = np.empty(4) 414 | zdata = np.empty((3, 5, 4)) 415 | axis = 2 416 | result = vinterp._Interpolation(data.copy(), data, zdata, axis=axis) 417 | self.assertEqual(result.result_shape, (3, 5, 4)) 418 | 419 | def test_nd_inconsistent_ndims(self): 420 | z_target = np.empty((2, 3, 4)) 421 | z_src = np.empty((3, 4)) 422 | fz_src = np.empty((2, 3, 4)) 423 | emsg = "z_target and z_src must have the same number of dimensions" 424 | with self.assertRaisesRegex(ValueError, emsg): 425 | vinterp._Interpolation(z_target, z_src, fz_src) 426 | 427 | def test_nd_inconsistent_shape(self): 428 | z_target = np.empty((3, 2, 6)) 429 | z_src = np.empty((3, 4, 5)) 430 | fz_src = np.empty((2, 3, 4, 5)) 431 | emsg = ( 432 | "z_target and z_src have different shapes, " 433 | r"got \(3, :, 6\) != \(3, :, 5\)" 434 | ) 435 | with self.assertRaisesRegex(ValueError, emsg): 436 | vinterp._Interpolation(z_target, z_src, fz_src, axis=2) 437 | 438 | def test_result_dtype_f4(self): 439 | interp = vinterp._Interpolation( 440 | [17.5], np.arange(4) * 10, np.arange(4, dtype="f4") 441 | ) 442 | result = interp.interpolate() 443 | 444 | self.assertEqual(interp._target_dtype, np.dtype("f4")) 445 | self.assertEqual(result.dtype, np.dtype("f4")) 446 | 447 | def test_result_dtype_f8(self): 448 | interp = vinterp._Interpolation( 449 | [17.5], np.arange(4) * 10, np.arange(4, dtype="f8") 450 | ) 451 | result = interp.interpolate() 452 | 453 | self.assertEqual(interp._target_dtype, np.dtype("f8")) 454 | self.assertEqual(result.dtype, np.dtype("f8")) 455 | 456 | 457 | class Test__Interpolation_interpolate_z_target_nd(unittest.TestCase): 458 | def test_target_z_3d_on_axis_0(self): 459 | z_target = z_source = f_source = np.arange(3) * np.ones([4, 2, 3]) 460 | interp = vinterp._Interpolation( 461 | z_target, 462 | z_source, 463 | f_source, 464 | axis=0, 465 | extrapolation=stratify.EXTRAPOLATE_NEAREST, 466 | ) 467 | result = interp.interpolate_z_target_nd() 468 | assert_array_equal(result, f_source) 469 | 470 | def test_target_z_3d_on_axis_m1(self): 471 | z_target = z_source = f_source = np.arange(3) * np.ones([4, 2, 3]) 472 | interp = vinterp._Interpolation( 473 | z_target, 474 | z_source, 475 | f_source, 476 | axis=-1, 477 | extrapolation=stratify.EXTRAPOLATE_NEAREST, 478 | ) 479 | result = interp.interpolate_z_target_nd() 480 | assert_array_equal(result, f_source) 481 | 482 | def test_target_z_2d_over_3d_on_axis_1(self): 483 | """Test the case where z_target(2, 4) and z_src(3, 4) are 2d, but the 484 | source data fz_src(3, 3, 4) is 3d. z_target and z_src cover the last 485 | 2 dimensions of fz_src. The axis of interpolation is axis=1 wrt fz_src. 486 | 487 | """ 488 | # Generate the 3d source data fz_src(3, 3, 4) 489 | base = np.arange(3).reshape(1, 3, 1) * 2 490 | data = np.broadcast_to(base, (3, 3, 4)) 491 | fz_src = data * np.arange(1, 4).reshape(3, 1, 1) * 10 492 | # Generate the 2d target coordinate z_target(2, 4) 493 | # The target coordinate is configured to request the interpolated 494 | # mid-points over axis=1 of fz_src. 495 | z_target = np.repeat(np.arange(1, 4, 2).reshape(2, 1), 4, axis=1) * 10 496 | # Generate the 2d source coordinate z_src(3, 4) 497 | z_src = np.repeat(np.arange(3).reshape(3, 1), 4, axis=1) * 20 498 | # Configure the vertical interpolator. 499 | interp = vinterp._Interpolation(z_target, z_src, fz_src, axis=1) 500 | # Perform the vertical interpolation. 501 | result = interp.interpolate_z_target_nd() 502 | # Generate the 3d expected interpolated result(3, 2, 4). 503 | expected = np.repeat(z_target[np.newaxis, ...], 3, axis=0) 504 | expected = expected * np.arange(1, 4).reshape(3, 1, 1) 505 | assert_array_equal(result, expected) 506 | 507 | def test_target_z_2d_over_3d_on_axis_m1(self): 508 | """Test the case where z_target(3, 3) and z_src(3, 4) are 2d, but the 509 | source data fz_src(3, 3, 4) is 3d. z_target and z_src cover the last 510 | 2 dimensions of fz_src. The axis of interpolation is the default last 511 | dimension, axis=-1, wrt fx_src. 512 | 513 | """ 514 | # Generate the 3d source data fz_src(3, 3, 4) 515 | base = np.arange(4) * 2 516 | data = np.broadcast_to(base, (3, 3, 4)) 517 | fz_src = data * np.arange(1, 4).reshape(3, 1, 1) * 10 518 | # Generate the 2d target coordinate z_target(3, 3) 519 | # The target coordinate is configured to request the interpolated 520 | # mid-points over axis=-1 (aka axis=2) of fz_src. 521 | z_target = np.repeat(np.arange(1, 6, 2).reshape(1, 3), 3, axis=0) * 10 522 | # Generate the 2d source coordinate z_src(3, 4) 523 | z_src = np.repeat(np.arange(4).reshape(1, 4), 3, axis=0) * 20 524 | # Configure the vertical interpolator. 525 | interp = vinterp._Interpolation( 526 | z_target, 527 | z_src, 528 | fz_src, 529 | ) 530 | # Perform the vertical interpolation. 531 | result = interp.interpolate_z_target_nd() 532 | # Generate the 3d expected interpolated result(3, 3, 3) 533 | expected = np.repeat(z_target[np.newaxis, ...], 3, axis=0) 534 | expected = expected * np.arange(1, 4).reshape(3, 1, 1) 535 | assert_array_equal(result, expected) 536 | 537 | 538 | class Test_interpolate(unittest.TestCase): 539 | def test_target_z_3d_axis_0(self): 540 | z_target = z_source = f_source = np.arange(3) * np.ones([4, 2, 3]) 541 | result = vinterp.interpolate( 542 | z_target, z_source, f_source, extrapolation="linear" 543 | ) 544 | assert_array_equal(result, f_source) 545 | 546 | def test_dask(self): 547 | z_target = z_source = f_source = np.arange(3) * np.ones([4, 2, 3]) 548 | reference = vinterp.interpolate( 549 | z_target, z_source, f_source, extrapolation="linear" 550 | ) 551 | # Test with various combinations of lazy input 552 | f_src = da.asarray(f_source, chunks=(2, 1, 2)) 553 | for z_tgt in (z_target, z_target.tolist(), da.asarray(z_target)): 554 | for z_src in (z_source, da.asarray(z_source)): 555 | result = vinterp.interpolate( 556 | z_tgt, z_src, f_src, extrapolation="linear" 557 | ) 558 | assert_array_equal(reference, result.compute()) 559 | 560 | def test_dask_1d_target(self): 561 | z_target = np.array([0.5]) 562 | z_source = f_source = np.arange(3) * np.ones([4, 2, 3]) 563 | reference = vinterp.interpolate( 564 | z_target, z_source, f_source, axis=1, extrapolation="linear" 565 | ) 566 | # Test with various combinations of lazy input 567 | f_src = da.asarray(f_source, chunks=(2, 1, 2)) 568 | for z_tgt in (z_target, z_target.tolist(), da.asarray(z_target)): 569 | for z_src in (z_source, da.asarray(z_source)): 570 | result = vinterp.interpolate( 571 | z_tgt, z_src, f_src, axis=1, extrapolation="linear" 572 | ) 573 | assert_array_equal(reference, result.compute()) 574 | 575 | 576 | if __name__ == "__main__": 577 | unittest.main() 578 | -------------------------------------------------------------------------------- /summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciTools/python-stratify/b09c836f1667d6c05258b42fbba64e2fbf1bd687/summary.png --------------------------------------------------------------------------------