├── .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 | [](https://github.com/SciTools/python-stratify/actions/workflows/ci-locks.yml) [](https://github.com/SciTools/python-stratify/actions/workflows/ci-manifest.yml) [](https://github.com/SciTools/python-stratify/actions/workflows/ci-tests.yml) [](https://github.com/SciTools/python-stratify/actions/workflows/ci-wheels.yml) [](https://results.pre-commit.ci/latest/github/SciTools/python-stratify/master) |
8 | | 💬 Community | [](https://github.com/SciTools/python-stratify/discussions) |
9 | | 📖 Documentation | [](http://mybinder.org:/repo/scitools-incubator/python-stratify) |
10 | | 📈 Health | [](https://codecov.io/gh/SciTools/python-stratify) |
11 | | ✨ Meta | [](https://github.com/psf/black) [](https://github.com/python-stratify/python-stratify/blob/main/LICENSE) |
12 | | 📦 Package | [](https://doi.org/10.5281/zenodo.7863857) [](https://anaconda.org/conda-forge/python-stratify) [](https://pypi.org/project/stratify/) [](https://pypi.org/project/stratify/) |
13 | | 🧰 Repo | [](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 | 
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
--------------------------------------------------------------------------------