pax_global_header00006660000000000000000000000064151477562400014525gustar00rootroot0000000000000052 comment=26f91a54a3f2ba4d200e9af3d68df6cf1db49d32 flox-0.11.2/000077500000000000000000000000001514775624000125565ustar00rootroot00000000000000flox-0.11.2/.git_archival.txt000066400000000000000000000001571514775624000160340ustar00rootroot00000000000000node: $Format:%H$ node-date: $Format:%cI$ describe-name: $Format:%(describe:tags=true)$ ref-names: $Format:%D$ flox-0.11.2/.github/000077500000000000000000000000001514775624000141165ustar00rootroot00000000000000flox-0.11.2/.github/dependabot.yml000066400000000000000000000001671514775624000167520ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" flox-0.11.2/.github/release.yml000066400000000000000000000001141514775624000162550ustar00rootroot00000000000000changelog: exclude: authors: - dependabot - pre-commit-ci flox-0.11.2/.github/workflows/000077500000000000000000000000001514775624000161535ustar00rootroot00000000000000flox-0.11.2/.github/workflows/benchmarks.yml000066400000000000000000000051161514775624000210160ustar00rootroot00000000000000name: Benchmark on: pull_request: types: [opened, reopened, synchronize, labeled] workflow_dispatch: jobs: benchmark: # if: ${{ contains( github.event.pull_request.labels.*.name, 'run-benchmark') && github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} # Run if the PR has been labelled correctly. if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} # Always run. name: Linux runs-on: ubuntu-latest env: ASV_DIR: "./asv_bench" steps: # We need the full repo to avoid this issue # https://github.com/actions/checkout/issues/23 - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Python and uv uses: astral-sh/setup-uv@v7 with: python-version: "3.12" enable-cache: true cache-dependency-glob: "pyproject.toml" - name: Install dependencies run: | uv sync --group benchmark --no-dev - name: Run benchmarks id: benchmark env: OPENBLAS_NUM_THREADS: 1 MKL_NUM_THREADS: 1 OMP_NUM_THREADS: 1 ASV_FACTOR: 1.5 ASV_SKIP_SLOW: 1 BASE_SHA: ${{ github.event.pull_request.base.sha }} LAST_HEAD_SHA: ${{ github.event.pull_request.head.sha }} HEAD_LABEL: ${{ github.event.pull_request.head.label }} BASE_LABEL: ${{ github.event.pull_request.base.label }} run: | # set -x # ID this runner uv run --no-dev asv machine --yes echo "Baseline: $LAST_HEAD_SHA ($BASE_LABEL)" echo "Contender: ${GITHUB_SHA} ($HEAD_LABEL)" # Run benchmarks for current commit against base ASV_OPTIONS="--split --show-stderr --factor $ASV_FACTOR" uv run --no-dev asv continuous $ASV_OPTIONS $BASE_SHA ${GITHUB_SHA} \ | sed "/Traceback \|failed$\|PERFORMANCE DECREASED/ s/^/::error::/" \ | tee benchmarks.log # Report and export results for subsequent steps if grep "Traceback \|failed\|PERFORMANCE DECREASED" benchmarks.log > /dev/null ; then exit 1 fi working-directory: ${{ env.ASV_DIR }} - name: Add instructions to artifact if: always() run: | cp benchmarks/README_CI.md benchmarks.log .asv/results/ working-directory: ${{ env.ASV_DIR }} - uses: actions/upload-artifact@v6 if: always() with: name: asv-benchmark-results-${{ runner.os }} path: ${{ env.ASV_DIR }}/.asv/results flox-0.11.2/.github/workflows/ci-additional.yaml000066400000000000000000000057761514775624000215570ustar00rootroot00000000000000name: CI Additional on: push: branches: - "main" pull_request: branches: - "*" workflow_dispatch: # allows you to trigger manually concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: detect-ci-trigger: name: detect ci trigger runs-on: ubuntu-latest if: | github.repository == 'xarray-contrib/flox' && (github.event_name == 'push' || github.event_name == 'pull_request') outputs: triggered: ${{ steps.detect-trigger.outputs.trigger-found }} steps: - uses: actions/checkout@v6 with: fetch-depth: 2 - uses: xarray-contrib/ci-trigger@v1.2 id: detect-trigger with: keyword: "[skip-ci]" doctest: name: Doctests runs-on: "ubuntu-latest" needs: detect-ci-trigger if: needs.detect-ci-trigger.outputs.triggered == 'false' env: PYTHON_VERSION: "3.14" steps: - uses: actions/checkout@v6 with: fetch-depth: 0 # Fetch all history for all branches and tags. - name: Set up Python and uv uses: astral-sh/setup-uv@v7 with: python-version: ${{ env.PYTHON_VERSION }} enable-cache: true cache-dependency-glob: "pyproject.toml" - name: Install dependencies run: | uv sync --group complete --no-dev - name: Version info run: | uv pip list - name: Run doctests run: | uv run --no-dev python -m pytest --doctest-modules \ flox/aggregations.py flox/core.py flox/xarray.py \ --ignore flox/tests \ --cov=./ --cov-report=xml - name: Upload code coverage to Codecov uses: codecov/codecov-action@v5.5.2 with: file: ./coverage.xml flags: unittests env_vars: RUNNER_OS name: codecov-umbrella fail_ci_if_error: false mypy: name: Mypy runs-on: "ubuntu-latest" needs: detect-ci-trigger if: needs.detect-ci-trigger.outputs.triggered == 'false' env: PYTHON_VERSION: "3.14" steps: - uses: actions/checkout@v6 with: fetch-depth: 0 # Fetch all history for all branches and tags. - name: Set up Python and uv uses: astral-sh/setup-uv@v7 with: python-version: ${{ env.PYTHON_VERSION }} enable-cache: true cache-dependency-glob: "pyproject.toml" - name: Install dependencies run: | uv sync --group complete --no-dev --group types - name: Version info run: | uv pip list - name: Run mypy run: | mkdir .mypy_cache uv run --no-dev mypy --cache-dir=.mypy_cache/ --cobertura-xml-report mypy_report - name: Upload mypy coverage to Codecov uses: codecov/codecov-action@v5.5.2 with: file: mypy_report/cobertura.xml flags: mypy env_vars: PYTHON_VERSION name: codecov-umbrella fail_ci_if_error: false flox-0.11.2/.github/workflows/ci.yaml000066400000000000000000000114131514775624000174320ustar00rootroot00000000000000name: CI on: push: branches: - "main" pull_request: branches: - "*" schedule: - cron: "0 0 * * *" # Daily “At 00:00â€� workflow_dispatch: # allows you to trigger manually concurrency: group: ${{ github.ref }} cancel-in-progress: true jobs: test: name: Test (${{matrix.env}}, ${{ matrix.python-version }}, ${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: ["ubuntu-latest"] env: ["complete"] python-version: ["3.11", "3.14"] include: - os: "windows-latest" env: "complete" python-version: "3.14" - os: "ubuntu-latest" env: "no-dask" python-version: "3.14" - os: "ubuntu-latest" env: "minimal" python-version: "3.11" - os: "windows-latest" env: "numpy1" python-version: "3.11" steps: - uses: actions/checkout@v6 with: fetch-depth: 0 # Fetch all history for all branches and tags. - name: Set environment variables run: | echo "PYTHON_VERSION=${{ matrix.python-version }}" >> $GITHUB_ENV - name: Set up Python and uv uses: astral-sh/setup-uv@v7 with: python-version: ${{ matrix.python-version }} enable-cache: true cache-dependency-glob: "pyproject.toml" - name: Install dependencies (with config file) if: matrix.env == 'numpy1' run: uv sync --group ${{ matrix.env }} --config-file uv-numpy1.toml --no-dev - name: Install dependencies (without config file) if: matrix.env != 'numpy1' run: uv sync --group ${{ matrix.env }} --no-dev # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#update-a-cache - name: Restore cached hypothesis directory id: restore-hypothesis-cache uses: actions/cache/restore@v5 with: path: .hypothesis/ key: cache-hypothesis-${{ runner.os }}-${{ matrix.python-version }}-${{ github.run_id }} restore-keys: | cache-hypothesis-${{ runner.os }}-${{ matrix.python-version }}- - name: Run Tests id: status run: | uv run --no-dev python -c "import xarray; xarray.show_versions()" || true uv run --no-dev pytest --durations=20 --durations-min=0.5 -n auto --cov=./ --cov-report=xml --hypothesis-profile ci - name: Upload code coverage to Codecov uses: codecov/codecov-action@v5.5.2 with: file: ./coverage.xml flags: unittests env_vars: RUNNER_OS,PYTHON_VERSION name: codecov-umbrella fail_ci_if_error: false # explicitly save the cache so it gets updated, also do this even if it fails. - name: Save cached hypothesis directory id: save-hypothesis-cache if: always() && steps.status.outcome != 'skipped' uses: actions/cache/save@v5 with: path: .hypothesis/ key: cache-hypothesis-${{ runner.os }}-${{ matrix.python-version }}-${{ github.run_id }} xarray-groupby: name: xarray-groupby runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: repository: "pydata/xarray" fetch-depth: 0 # Fetch all history for all branches and tags. - name: Set up Python and uv uses: astral-sh/setup-uv@v7 with: python-version: "3.14" enable-cache: true cache-dependency-glob: "pyproject.toml" - name: Install xarray and dependencies run: | uv add --dev ".[dev]" "pint>=0.22" uv pip uninstall h5netcdf uv pip install pytz - name: Install upstream flox run: | uv add git+https://github.com/dcherian/flox.git@${{ github.ref }} - name: Version info run: | uv tree uv run --no-dev python xarray/util/print_versions.py - name: import xarray run: | uv run --no-dev python -c 'import xarray' - name: import flox run: | uv run --no-dev python -c 'import flox' - name: Run Tests if: success() id: status run: | set -euo pipefail uv run --no-dev python -m pytest -n auto \ xarray/tests/test_groupby.py \ xarray/tests/test_units.py::TestDataArray::test_computation_objects \ xarray/tests/test_units.py::TestDataArray::test_grouped_operations \ xarray/tests/test_units.py::TestDataArray::test_resample \ xarray/tests/test_units.py::TestDataset::test_computation_objects \ xarray/tests/test_units.py::TestDataset::test_grouped_operations \ xarray/tests/test_units.py::TestDataset::test_resample flox-0.11.2/.github/workflows/pypi.yaml000066400000000000000000000007711514775624000200250ustar00rootroot00000000000000name: Upload Package to PyPI on: release: types: [created] jobs: deploy: runs-on: ubuntu-latest permissions: contents: read id-token: write steps: - uses: actions/checkout@v6 - name: Set up Python and uv uses: astral-sh/setup-uv@v7 with: python-version: "3.14" - name: Build and publish run: | uv venv uv build - name: Publish package distributions to PyPI run: | uv publish flox-0.11.2/.github/workflows/testpypi-release.yaml000066400000000000000000000041701514775624000223400ustar00rootroot00000000000000name: Build and Upload to TestPyPI on: push: branches: - "main" pull_request: types: [opened, reopened, synchronize, labeled] branches: - "*" workflow_dispatch: # no need for concurrency limits jobs: build-artifacts: if: ${{ contains( github.event.pull_request.labels.*.name, 'test-build') && github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Python and uv uses: astral-sh/setup-uv@v7 with: python-version: "3.14" - name: Build tarball and wheels run: | git clean -xdf SETUPTOOLS_SCM_OVERRIDES_FOR_FLOX='local_scheme = "no-local-version"' uv build - name: Check built artifacts run: | uv venv uv pip install twine uv run twine check --strict dist/* if [ -f dist/flox-999.tar.gz ]; then echo "â�Œ INVALID VERSION NUMBER" exit 1 else echo "✅ Looks good" fi - uses: actions/upload-artifact@v6 with: name: releases path: dist test-built-dist: needs: build-artifacts runs-on: ubuntu-latest permissions: # IMPORTANT: this permission is mandatory for Trusted Publishing id-token: write steps: # need pyproject.toml for uv publish --index testpypi - uses: actions/checkout@v6 with: fetch-depth: 0 # Fetch all history for all branches and tags. - name: Set up Python and uv uses: astral-sh/setup-uv@v7 with: python-version: "3.14" - name: Get built artifact uses: actions/download-artifact@v7 with: name: releases path: dist - name: List contents of built dist run: | ls -ltrh ls -ltrh dist - name: Verify the built dist/wheel is valid run: | pip install dist/flox*.whl - name: Publish package to TestPyPI run: | uv publish --index testpypi flox-0.11.2/.github/workflows/upstream-dev-ci.yaml000066400000000000000000000053161514775624000220510ustar00rootroot00000000000000name: CI Upstream on: push: branches: - main pull_request: types: [opened, reopened, synchronize, labeled] branches: - main paths: - ".github/workflows/upstream-dev-ci.yaml" - "ci/upstream-dev-env.yml" - "flox/*" schedule: - cron: "0 0 * * *" # Daily “At 00:00â€� UTC workflow_dispatch: # allows you to trigger the workflow run manually concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: upstream-dev: name: upstream-dev runs-on: ubuntu-latest if: ${{ (contains(github.event.pull_request.labels.*.name, 'test-upstream') && github.event_name == 'pull_request') || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' }} strategy: fail-fast: false matrix: python-version: ["3.14"] steps: - uses: actions/checkout@v6 with: fetch-depth: 0 # Fetch all history for all branches and tags. - name: Set environment variables run: | echo "PYTHON_VERSION=${{ matrix.python-version }}" >> $GITHUB_ENV - name: Set up Python and uv uses: astral-sh/setup-uv@v7 with: python-version: ${{ matrix.python-version }} enable-cache: true cache-dependency-glob: "pyproject.toml" - name: Install upstream dev dependencies run: | # First sync the upstream group without the overridden packages uv sync --group upstream --no-dev # Install upstream development versions from nightly/git uv pip install \ --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple \ --pre \ numpy scipy pandas xarray uv pip install \ "dask[core] @ git+https://github.com/dask/dask" \ "numpy-groupies @ git+https://github.com/ml31415/numpy-groupies" \ "sparse @ git+https://github.com/pydata/sparse" \ "cftime @ git+https://github.com/Unidata/cftime" - name: List deps run: | uv pip list - name: Run Tests if: success() id: status run: | uv run --no-dev pytest -rf -n auto --cov=./ --cov-report=xml \ --report-log output-${{ matrix.python-version }}-log.jsonl \ --hypothesis-profile ci - name: Generate and publish the report if: | failure() && steps.status.outcome == 'failure' && github.event_name == 'schedule' && github.repository_owner == 'xarray-contrib' uses: xarray-contrib/issue-from-pytest-log@v1 with: log-path: output-${{ matrix.python-version }}-log.jsonl flox-0.11.2/.gitignore000066400000000000000000000024361514775624000145530ustar00rootroot00000000000000asv_bench/pkgs/ docs/source/generated/ html/ .asv/ asv_bench/pkgs/ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .DS_Store # Git worktrees worktrees/ flox-0.11.2/.pre-commit-config.yaml000066400000000000000000000027031514775624000170410ustar00rootroot00000000000000ci: autoupdate_schedule: quarterly repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. rev: "v0.14.10" hooks: - id: ruff args: ["--fix", "--show-fixes"] - id: ruff-format - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: check-yaml - id: trailing-whitespace - id: end-of-file-fixer - id: check-docstring-first - repo: https://github.com/executablebooks/mdformat rev: 1.0.0 hooks: - id: mdformat additional_dependencies: - mdformat-black - mdformat-myst - repo: https://github.com/kynan/nbstripout rev: 0.8.2 hooks: - id: nbstripout args: [--extra-keys=metadata.kernelspec metadata.language_info.version] - repo: https://github.com/codespell-project/codespell rev: v2.4.1 hooks: - id: codespell additional_dependencies: - tomli - repo: https://github.com/abravalheri/validate-pyproject rev: v0.24.1 hooks: - id: validate-pyproject - repo: https://github.com/rhysd/actionlint rev: v1.7.10 hooks: - id: actionlint files: ".github/workflows/" args: [ "-ignore", "SC1090", "-ignore", "SC2046", "-ignore", "SC2086", "-ignore", "SC2129", "-ignore", "SC2155", ] flox-0.11.2/.readthedocs.yml000066400000000000000000000005751514775624000156530ustar00rootroot00000000000000version: 2 sphinx: # Path to your Sphinx configuration file. configuration: docs/source/conf.py build: os: "ubuntu-lts-latest" tools: python: "3.14" jobs: build: html: - NOCOLOR=1 sphinx-build -T -b html docs/source $READTHEDOCS_OUTPUT/html python: install: - method: pip path: . extra_requirements: - docs formats: [] flox-0.11.2/CLAUDE.md000066400000000000000000000123131514775624000140350ustar00rootroot00000000000000# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview **flox** is a Python library providing fast GroupBy reduction operations for `dask.array`. It implements parallel-friendly GroupBy reductions using the MapReduce paradigm and integrates with xarray for labeled multidimensional arrays. ## Development Commands ### Environment Setup ```bash # Create and activate development environment mamba env create -f ci/environment.yml conda activate flox-tests python -m pip install --no-deps -e . ``` ### Testing ```bash # Run full test suite (as used in CI) pytest --durations=20 --durations-min=0.5 -n auto --cov=./ --cov-report=xml --hypothesis-profile ci # Run tests without coverage pytest -n auto # Run single test file pytest tests/test_core.py # Run specific test pytest tests/test_core.py::test_function_name ``` ### Code Quality ```bash # Run all pre-commit hooks pre-commit run --all-files # Format code with ruff ruff format . # Lint and fix with ruff ruff check --fix . # Type checking mypy flox/ # Spell checking codespell ``` ### Benchmarking ```bash # Performance benchmarking (from asv_bench/ directory) cd asv_bench asv run asv publish asv preview ``` ## CI Configuration ### GitHub Workflows (`.github/workflows/`) - **`ci.yaml`** - Main CI pipeline with test matrix across Python versions (3.11, 3.13) and operating systems (Ubuntu, Windows) - **`ci-additional.yaml`** - Additional CI jobs including doctests and mypy type checking - **`upstream-dev-ci.yaml`** - Tests against development versions of upstream dependencies - **`pypi.yaml`** - PyPI publishing workflow - **`testpypi-release.yaml`** - Test PyPI release workflow - **`benchmarks.yml`** - Performance benchmarking workflow ### Environment Files (`ci/`) - **`environment.yml`** - Main test environment with all dependencies - **`minimal-requirements.yml`** - Minimal requirements testing (pandas==1.5, numpy==1.22, etc.) - **`no-dask.yml`** - Testing without dask dependency - **`no-numba.yml`** - Testing without numba dependency - **`no-xarray.yml`** - Testing without xarray dependency - **`env-numpy1.yml`** - Testing with numpy\<2 constraint - **`docs.yml`** - Documentation building environment - **`upstream-dev-env.yml`** - Development versions of dependencies - **`benchmark.yml`** - Benchmarking environment ### ReadTheDocs Configuration - **`.readthedocs.yml`** - ReadTheDocs configuration using `ci/docs.yml` environment ## Code Architecture ### Core Modules (`flox/`) - **`core.py`** - Main reduction logic, central orchestrator of groupby operations - **`aggregations.py`** - Defines the `Aggregation` class and built-in aggregation operations - **`xarray.py`** - Primary integration with xarray, provides `xarray_reduce()` API - **`dask_array_ops.py`** - Dask-specific array operations and optimizations ### Aggregation Backends (`flox/aggregate_*.py`) - **`aggregate_flox.py`** - Native flox implementation - **`aggregate_npg.py`** - numpy-groupies backend - **`aggregate_numbagg.py`** - numbagg backend for JIT-compiled operations - **`aggregate_sparse.py`** - Support for sparse arrays ### Utilities - **`cache.py`** - Caching mechanisms for performance - **`visualize.py`** - Tools for visualizing groupby operations - **`lib.py`** - General utility functions - **`xrutils.py`** & **`xrdtypes.py`** - xarray-specific utilities and types ### Main APIs - `flox.groupby_reduce()` - Pure dask array interface - `flox.xarray.xarray_reduce()` - Pure xarray interface ## Key Design Patterns **Engine Selection**: The library supports multiple computation backends ("flox", "numpy", "numbagg") that can be chosen based on data characteristics and performance requirements. **MapReduce Strategy**: Implements groupby reductions using a two-stage approach (blockwise + tree reduction) to avoid expensive sort/shuffle operations in parallel computing. **Chunking Intelligence**: Automatically rechunks data to optimize groupby operations, particularly important for the current `auto-blockwise-rechunk` branch. **Integration Testing**: Extensive testing against xarray's groupby functionality to ensure compatibility with the broader scientific Python ecosystem. ## Testing Configuration - **Framework**: pytest with coverage, parallel execution (pytest-xdist), and property-based testing (hypothesis) - **Coverage Target**: 95% - **Test Environments**: Multiple conda environments test optional dependencies (no-dask, no-numba, no-xarray) - **CI Matrices**: Tests across Python 3.11-3.13, Ubuntu/Windows, multiple dependency configurations ## Dependencies **Core**: pandas>=1.5, numpy>=1.22, numpy_groupies>=0.9.19, scipy>=1.9, toolz, packaging>=21.3 **Optional**: cachey, dask, numba, numbagg, xarray (enable with `pip install flox[all]`) ## Development Notes - Uses `setuptools_scm` for automatic versioning from git tags - Heavy emphasis on performance with ASV benchmarking infrastructure - Type hints throughout with mypy checking - Pre-commit hooks enforce code quality (ruff, prettier, codespell) - Integration testing with xarray upstream development branch - **Python Support**: Minimum version 3.11 (updated from 3.10) - **Git Worktrees**: `worktrees/` directory is ignored for development workflows flox-0.11.2/LICENSE000066400000000000000000000260741514775624000135740ustar00rootroot00000000000000Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright (c) 2021 onwards, Deepak Cherian Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. flox-0.11.2/README.md000066400000000000000000000072531514775624000140440ustar00rootroot00000000000000[![GitHub Workflow CI Status](https://img.shields.io/github/actions/workflow/status/xarray-contrib/flox/ci.yaml?branch=main&logo=github&style=flat)](https://github.com/xarray-contrib/flox/actions) [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/xarray-contrib/flox/main.svg)](https://results.pre-commit.ci/latest/github/xarray-contrib/flox/main) [![image](https://img.shields.io/codecov/c/github/xarray-contrib/flox.svg?style=flat)](https://codecov.io/gh/xarray-contrib/flox) [![Documentation Status](https://readthedocs.org/projects/flox/badge/?version=latest)](https://flox.readthedocs.io/en/latest/?badge=latest) [![PyPI](https://img.shields.io/pypi/v/flox.svg?style=flat)](https://pypi.org/project/flox/) [![Conda-forge](https://img.shields.io/conda/vn/conda-forge/flox.svg?style=flat)](https://anaconda.org/conda-forge/flox) [![NASA-80NSSC18M0156](https://img.shields.io/badge/NASA-80NSSC18M0156-blue)](https://earthdata.nasa.gov/esds/competitive-programs/access/pangeo-ml) [![NASA-80NSSC22K0345](https://img.shields.io/badge/NASA-80NSSC22K0345-blue)](https://science.nasa.gov/open-science-overview) # flox This project explores strategies for fast GroupBy reductions with dask.array. It used to be called `dask_groupby` It was motivated by 1. Dask Dataframe GroupBy [blogpost](https://blog.dask.org/2019/10/08/df-groupby) 1. [numpy_groupies](https://github.com/ml31415/numpy-groupies) in Xarray [issue](https://github.com/pydata/xarray/issues/4473) (See a [presentation](https://docs.google.com/presentation/d/1YubKrwu9zPHC_CzVBhvORuQBW-z148BvX3Ne8XcvWsQ/edit?usp=sharing) about this package, from the Pangeo Showcase). ## Acknowledgements This work was funded in part by 1. NASA-ACCESS 80NSSC18M0156 "Community tools for analysis of NASA Earth Observing System Data in the Cloud" (PI J. Hamman, NCAR), 1. NASA-OSTFL 80NSSC22K0345 "Enhancing analysis of NASA data with the open-source Python Xarray Library" (PIs Scott Henderson, University of Washington; Deepak Cherian, NCAR; Jessica Scheick, University of New Hampshire), and 1. [NCAR's Earth System Data Science Initiative](https://ncar.github.io/esds/). It was motivated by [very](https://github.com/pangeo-data/pangeo/issues/266) [very](https://github.com/pangeo-data/pangeo/issues/271) [many](https://github.com/dask/distributed/issues/2602) [discussions](https://github.com/pydata/xarray/issues/2237) in the [Pangeo](https://pangeo.io) community. ## API There are two main functions 1. `flox.groupby_reduce(dask_array, by_dask_array, "mean")` "pure" dask array interface 1. `flox.xarray.xarray_reduce(xarray_object, by_dataarray, "mean")` "pure" xarray interface; though [work is ongoing](https://github.com/pydata/xarray/pull/5734) to integrate this package in xarray. ## Implementation See [the documentation](https://flox.readthedocs.io/en/latest/implementation.html) for details on the implementation. ## Custom reductions `flox` implements all common reductions provided by `numpy_groupies` in `aggregations.py`. It also allows you to specify a custom Aggregation (again inspired by dask.dataframe), though this might not be fully functional at the moment. See `aggregations.py` for examples. ```python mean = Aggregation( # name used for dask tasks name="mean", # operation to use for pure-numpy inputs numpy="mean", # blockwise reduction chunk=("sum", "count"), # combine intermediate results: sum the sums, sum the counts combine=("sum", "sum"), # generate final result as sum / count finalize=lambda sum_, count: sum_ / count, # Used when "reindexing" at combine-time fill_value=0, # Used when any member of `expected_groups` is not found final_fill_value=np.nan, ) ``` flox-0.11.2/asv_bench/000077500000000000000000000000001514775624000145065ustar00rootroot00000000000000flox-0.11.2/asv_bench/__init__.py000066400000000000000000000000001514775624000166050ustar00rootroot00000000000000flox-0.11.2/asv_bench/asv.conf.json000066400000000000000000000075171514775624000171300ustar00rootroot00000000000000{ // The version of the config file format. Do not change, unless // you know what you are doing. "version": 1, // The name of the project being benchmarked "project": "flox", // The project's homepage "project_url": "http://flox.readthedocs.io/", // The URL or local path of the source code repository for the // project being benchmarked "repo": "..", // The Python project's subdirectory in your repo. If missing or // the empty string, the project is assumed to be located at the root // of the repository. // "repo_subdir": "", // Customizable commands for building, installing, and // uninstalling the project. See asv.conf.json documentation. // // "uninstall_command": ["return-code=any python -mpip uninstall -y {project}"], // "build_command": [ // "python setup.py build", // "PIP_NO_BUILD_ISOLATION=false python -mpip wheel --no-deps --no-index -w {build_cache_dir} {build_dir}" // ], // // List of branches to benchmark. If not provided, defaults to "master" // (for git) or "default" (for mercurial). "branches": ["main"], // for git "dvcs": "git", // timeout in seconds for installing any dependencies in environment // defaults to 10 min "install_timeout": 600, // the base URL to show a commit for the project. "show_commit_url": "http://github.com/xarray-contrib/flox/commit/", // The Pythons you'd like to test against. If not provided, defaults // to the current version of Python used to run `asv`. "pythons": ["3.12"], "environment_type": "rattler", "conda_environment_file": "environment.yml", "build_command": [ "python -m build --wheel --outdir {build_cache_dir} {build_dir}" ], "install_command": [ "python -m pip install --find-links {build_cache_dir} --no-deps --no-index flox" ], // The directory (relative to the current directory) that benchmarks are // stored in. If not provided, defaults to "benchmarks" "benchmark_dir": "benchmarks", // The directory (relative to the current directory) to cache the Python // environments in. If not provided, defaults to "env" "env_dir": ".asv/env", // The directory (relative to the current directory) that raw benchmark // results are stored in. If not provided, defaults to "results". "results_dir": ".asv/results", // The directory (relative to the current directory) that the html tree // should be written to. If not provided, defaults to "html". "html_dir": ".asv/html" // The number of characters to retain in the commit hashes. // "hash_length": 8, // `asv` will cache results of the recent builds in each // environment, making them faster to install next time. This is // the number of builds to keep, per environment. // "build_cache_size": 2, // The commits after which the regression search in `asv publish` // should start looking for regressions. Dictionary whose keys are // regexps matching to benchmark names, and values corresponding to // the commit (exclusive) after which to start looking for // regressions. The default is to start from the first commit // with results. If the commit is `null`, regression detection is // skipped for the matching benchmark. // // "regressions_first_commits": { // "some_benchmark": "352cdf", // Consider regressions only after this commit // "another_benchmark": null, // Skip regression detection altogether // }, // The thresholds for relative change in results, after which `asv // publish` starts reporting regressions. Dictionary of the same // form as in ``regressions_first_commits``, with values // indicating the thresholds. If multiple entries match, the // maximum is taken. If no entry matches, the default is 5%. // // "regressions_thresholds": { // "some_benchmark": 0.01, // Threshold of 1% // "another_benchmark": 0.5, // Threshold of 50% // }, } flox-0.11.2/asv_bench/benchmarks/000077500000000000000000000000001514775624000166235ustar00rootroot00000000000000flox-0.11.2/asv_bench/benchmarks/README_CI.md000066400000000000000000000174601514775624000204650ustar00rootroot00000000000000# Benchmark CI ## How it works The `asv` suite can be run for any PR on GitHub Actions (check workflow `.github/workflows/benchmarks.yml`) by adding a `run-benchmark` label to said PR. This will trigger a job that will run the benchmarking suite for the current PR head (merged commit) against the PR base (usually `main`). We use `asv continuous` to run the job, which runs a relative performance measurement. This means that there's no state to be saved and that regressions are only caught in terms of performance ratio (absolute numbers are available but they are not useful since we do not use stable hardware over time). `asv continuous` will: - Compile `scikit-image` for _both_ commits. We use `ccache` to speed up the process, and `mamba` is used to create the build environments. - Run the benchmark suite for both commits, _twice_ (since `processes=2` by default). - Generate a report table with performance ratios: - `ratio=1.0` -> performance didn't change. - `ratio<1.0` -> PR made it slower. - `ratio>1.0` -> PR made it faster. Due to the sensitivity of the test, we cannot guarantee that false positives are not produced. In practice, values between `(0.7, 1.5)` are to be considered part of the measurement noise. When in doubt, running the benchmark suite one more time will provide more information about the test being a false positive or not. ## Running the benchmarks on GitHub Actions 1. On a PR, add the label `run-benchmark`. 1. The CI job will be started. Checks will appear in the usual dashboard panel above the comment box. 1. If more commits are added, the label checks will be grouped with the last commit checks _before_ you added the label. 1. Alternatively, you can always go to the `Actions` tab in the repo and [filter for `workflow:Benchmark`](https://github.com/scikit-image/scikit-image/actions?query=workflow%3ABenchmark). Your username will be assigned to the `actor` field, so you can also filter the results with that if you need it. ## The artifacts The CI job will also generate an artifact. This is the `.asv/results` directory compressed in a zip file. Its contents include: - `fv-xxxxx-xx/`. A directory for the machine that ran the suite. It contains three files: - `.json`, `.json`: the benchmark results for each commit, with stats. - `machine.json`: details about the hardware. - `benchmarks.json`: metadata about the current benchmark suite. - `benchmarks.log`: the CI logs for this run. - This README. ## Re-running the analysis Although the CI logs should be enough to get an idea of what happened (check the table at the end), one can use `asv` to run the analysis routines again. 1. Uncompress the artifact contents in the repo, under `.asv/results`. This is, you should see `.asv/results/benchmarks.log`, not `.asv/results/something_else/benchmarks.log`. Write down the machine directory name for later. 1. Run `asv show` to see your available results. You will see something like this: ``` $> asv show Commits with results: Machine : Jaimes-MBP Environment: conda-py3.9-cython-numpy1.20-scipy 00875e67 Machine : fv-az95-499 Environment: conda-py3.7-cython-numpy1.17-pooch-scipy 8db28f02 3a305096 ``` 3. We are interested in the commits for `fv-az95-499` (the CI machine for this run). We can compare them with `asv compare` and some extra options. `--sort ratio` will show largest ratios first, instead of alphabetical order. `--split` will produce three tables: improved, worsened, no changes. `--factor 1.5` tells `asv` to only complain if deviations are above a 1.5 ratio. `-m` is used to indicate the machine ID (use the one you wrote down in step 1). Finally, specify your commit hashes: baseline first, then contender! ``` $> asv compare --sort ratio --split --factor 1.5 -m fv-az95-499 8db28f02 3a305096 Benchmarks that have stayed the same: before after ratio [8db28f02] [3a305096] n/a n/a n/a benchmark_restoration.RollingBall.time_rollingball_ndim 1.23±0.04ms 1.37±0.1ms 1.12 benchmark_transform_warp.WarpSuite.time_to_float64(, 128, 3) 5.07±0.1μs 5.59±0.4μs 1.10 benchmark_transform_warp.ResizeLocalMeanSuite.time_resize_local_mean(, (192, 192, 192), (192, 192, 192)) 1.23±0.02ms 1.33±0.1ms 1.08 benchmark_transform_warp.WarpSuite.time_same_type(, 128, 3) 9.45±0.2ms 10.1±0.5ms 1.07 benchmark_rank.Rank3DSuite.time_3d_filters('majority', (32, 32, 32)) 23.0±0.9ms 24.6±1ms 1.07 benchmark_interpolation.InterpolationResize.time_resize((80, 80, 80), 0, 'symmetric', , True) 38.7±1ms 41.1±1ms 1.06 benchmark_transform_warp.ResizeLocalMeanSuite.time_resize_local_mean(, (2048, 2048), (192, 192, 192)) 4.97±0.2μs 5.24±0.2μs 1.05 benchmark_transform_warp.ResizeLocalMeanSuite.time_resize_local_mean(, (2048, 2048), (2048, 2048)) 4.21±0.2ms 4.42±0.3ms 1.05 benchmark_rank.Rank3DSuite.time_3d_filters('gradient', (32, 32, 32)) ... ``` If you want more details on a specific test, you can use `asv show`. Use `-b pattern` to filter which tests to show, and then specify a commit hash to inspect: ``` $> asv show -b time_to_float64 8db28f02 Commit: 8db28f02 benchmark_transform_warp.WarpSuite.time_to_float64 [fv-az95-499/conda-py3.7-cython-numpy1.17-pooch-scipy] ok =============== ============= ========== ============= ========== ============ ========== ============ ========== ============ -- N / order --------------- -------------------------------------------------------------------------------------------------------------- dtype_in 128 / 0 128 / 1 128 / 3 1024 / 0 1024 / 1 1024 / 3 4096 / 0 4096 / 1 4096 / 3 =============== ============= ========== ============= ========== ============ ========== ============ ========== ============ numpy.uint8 2.56±0.09ms 523±30μs 1.28±0.05ms 130±3ms 28.7±2ms 81.9±3ms 2.42±0.01s 659±5ms 1.48±0.01s numpy.uint16 2.48±0.03ms 530±10μs 1.28±0.02ms 130±1ms 30.4±0.7ms 81.1±2ms 2.44±0s 653±3ms 1.47±0.02s numpy.float32 2.59±0.1ms 518±20μs 1.27±0.01ms 127±3ms 26.6±1ms 74.8±2ms 2.50±0.01s 546±10ms 1.33±0.02s numpy.float64 2.48±0.04ms 513±50μs 1.23±0.04ms 134±3ms 30.7±2ms 85.4±2ms 2.55±0.01s 632±4ms 1.45±0.01s =============== ============= ========== ============= ========== ============ ========== ============ ========== ============ started: 2021-07-06 06:14:36, duration: 1.99m ``` ## Other details ### Skipping slow or demanding tests To minimize the time required to run the full suite, we trimmed the parameter matrix in some cases and, in others, directly skipped tests that ran for too long or require too much memory. Unlike `pytest`, `asv` does not have a notion of marks. However, you can `raise NotImplementedError` in the setup step to skip a test. In that vein, a new private function is defined at `benchmarks.__init__`: `_skip_slow`. This will check if the `ASV_SKIP_SLOW` environment variable has been defined. If set to `1`, it will raise `NotImplementedError` and skip the test. To implement this behavior in other tests, you can add the following attribute: ```python from . import _skip_slow # this function is defined in benchmarks.__init__ def time_something_slow(): pass time_something.setup = _skip_slow ``` flox-0.11.2/asv_bench/benchmarks/__init__.py000066400000000000000000000011411514775624000207310ustar00rootroot00000000000000import os def parameterized(names, params): def decorator(func): func.param_names = names func.params = params return func return decorator def _skip_slow(): """ Use this function to skip slow or highly demanding tests. Use it as a `Class.setup` method or a `function.setup` attribute. Examples -------- >>> from . import _skip_slow >>> def time_something_slow(): ... pass >>> time_something.setup = _skip_slow """ if os.environ.get("ASV_SKIP_SLOW", "0") == "1": raise NotImplementedError("Skipping this test...") flox-0.11.2/asv_bench/benchmarks/cohorts.py000066400000000000000000000200551514775624000206600ustar00rootroot00000000000000from functools import cached_property import dask import numpy as np import pandas as pd import flox from flox.factorize import _factorize_multiple from .helpers import codes_for_resampling class Cohorts: """Time the core reduction function.""" def setup(self, *args, **kwargs): raise NotImplementedError @cached_property def result(self): return flox.groupby_reduce(self.array, self.by, func="sum", axis=self.axis)[0] def containment(self): asfloat = self.bitmask().astype(float) chunks_per_label = asfloat.sum(axis=0) containment = (asfloat.T @ asfloat) / chunks_per_label return containment.todense() def chunks_cohorts(self): return flox.cohorts.find_group_cohorts( self.by, [self.array.chunks[ax] for ax in self.axis], expected_groups=self.expected, )[1] def bitmask(self): chunks = [self.array.chunks[ax] for ax in self.axis] return flox.cohorts._compute_label_chunk_bitmask(self.by, chunks, self.expected[-1] + 1) def time_find_group_cohorts(self): flox.cohorts.find_group_cohorts( self.by, [self.array.chunks[ax] for ax in self.axis], expected_groups=self.expected, ) # The cache clear fails dependably in CI # Not sure why try: flox.cache.cache.clear() except AttributeError: pass def track_num_cohorts(self): return len(self.chunks_cohorts()) def time_graph_construct(self): flox.groupby_reduce(self.array, self.by, func="sum", axis=self.axis) def track_num_tasks(self): return len(self.result.dask.to_dict()) def track_num_tasks_optimized(self): (opt,) = dask.optimize(self.result) return len(opt.dask.to_dict()) def track_num_layers(self): return len(self.result.dask.layers) track_num_cohorts.unit = "cohorts" # type: ignore[attr-defined] # Lazy track_num_tasks.unit = "tasks" # type: ignore[attr-defined] # Lazy track_num_tasks_optimized.unit = "tasks" # type: ignore[attr-defined] # Lazy track_num_layers.unit = "layers" # type: ignore[attr-defined] # Lazy for f in [ track_num_tasks, track_num_tasks_optimized, track_num_layers, track_num_cohorts, ]: f.repeat = 1 # type: ignore[attr-defined] # Lazy f.rounds = 1 # type: ignore[attr-defined] # Lazy f.number = 1 # type: ignore[attr-defined] # Lazy class NWMMidwest(Cohorts): """2D labels, ireregular w.r.t chunk size. Mimics National Weather Model, Midwest county groupby.""" def setup(self, *args, **kwargs): x = np.repeat(np.arange(30), 150) y = np.repeat(np.arange(30), 60) by = x[np.newaxis, :] * y[:, np.newaxis] self.by = _factorize_multiple((by,), expected_groups=(None,), any_by_dask=False)[0][0] self.array = dask.array.ones(self.by.shape, chunks=(350, 350)) self.axis = (-2, -1) self.expected = pd.RangeIndex(self.by.max() + 1) class ERA5Dataset: """ERA5""" def __init__(self, *args, **kwargs): self.time = pd.Series(pd.date_range("2016-01-01", "2018-12-31 23:59", freq="h")) self.axis = (-1,) self.array = dask.array.random.random((721, 1440, len(self.time)), chunks=(-1, -1, 48)) def rechunk(self): self.array = flox.rechunk_for_cohorts( self.array, -1, self.by, force_new_chunk_at=[1], chunksize=48, ignore_old_chunks=True, ) class ERA5Resampling(Cohorts): def setup(self, *args, **kwargs): super().__init__() # nyears is number of years, adjust to make bigger, # full dataset is 60-ish years. nyears = 5 shape = (37, 721, 1440, nyears * 365 * 24) chunks = (-1, -1, -1, 1) time = pd.date_range("2001-01-01", periods=shape[-1], freq="h") self.array = dask.array.random.random(shape, chunks=chunks) self.by = codes_for_resampling(time, "D") self.axis = (-1,) self.expected = np.unique(self.by) class ERA5DayOfYear(ERA5Dataset, Cohorts): def setup(self, *args, **kwargs): super().__init__() self.by = self.time.dt.dayofyear.values - 1 self.expected = pd.RangeIndex(self.by.max() + 1) # class ERA5DayOfYearRechunked(ERA5DayOfYear, Cohorts): # def setup(self, *args, **kwargs): # super().setup() # self.array = dask.array.random.random((721, 1440, len(self.time)), chunks=(-1, -1, 24)) # self.expected = pd.RangeIndex(self.by.max() + 1) class ERA5MonthHour(ERA5Dataset, Cohorts): def setup(self, *args, **kwargs): super().__init__() by = (self.time.dt.month.values, self.time.dt.hour.values) ret = _factorize_multiple( by, (pd.Index(np.arange(1, 13)), pd.Index(np.arange(1, 25))), any_by_dask=False, ) # Add one so the rechunk code is simpler and makes sense self.by = ret[0][0] self.expected = pd.RangeIndex(self.by.max() + 1) class ERA5MonthHourRechunked(ERA5MonthHour, Cohorts): def setup(self, *args, **kwargs): super().setup() super().rechunk() class PerfectMonthly(Cohorts): """Perfectly chunked for a "cohorts" monthly mean climatology""" def setup(self, *args, **kwargs): self.time = pd.Series(pd.date_range("1961-01-01", "2018-12-31 23:59", freq="ME")) self.axis = (-1,) self.array = dask.array.random.random((721, 1440, len(self.time)), chunks=(-1, -1, 4)) self.by = self.time.dt.month.values - 1 self.expected = pd.RangeIndex(self.by.max() + 1) def rechunk(self): self.array = flox.rechunk_for_cohorts( self.array, -1, self.by, force_new_chunk_at=[1], chunksize=4, ignore_old_chunks=True, ) # class PerfectMonthlyRechunked(PerfectMonthly): # def setup(self, *args, **kwargs): # super().setup() # super().rechunk() class ERA5Google(Cohorts): def setup(self, *args, **kwargs): TIME = 900 # 92044 in Google ARCO ERA5 self.time = pd.Series(pd.date_range("1959-01-01", freq="6h", periods=TIME)) self.axis = (2,) self.array = dask.array.ones((721, 1440, TIME), chunks=(-1, -1, 1)) self.by = self.time.dt.day.values - 1 self.expected = pd.RangeIndex(self.by.max() + 1) class PerfectBlockwiseResampling(Cohorts): """Perfectly chunked for blockwise resampling.""" def setup(self, *args, **kwargs): index = pd.date_range("1959-01-01", freq="D", end="1962-12-31") self.time = pd.Series(index) TIME = len(self.time) self.axis = (2,) self.array = dask.array.ones((721, 1440, TIME), chunks=(-1, -1, 10)) self.by = codes_for_resampling(index, freq="5D") self.expected = pd.RangeIndex(self.by.max() + 1) class SingleChunk(Cohorts): """Single chunk along reduction axis: always blockwise.""" def setup(self, *args, **kwargs): index = pd.date_range("1959-01-01", freq="D", end="1962-12-31") self.time = pd.Series(index) TIME = len(self.time) self.axis = (2,) self.array = dask.array.ones((721, 1440, TIME), chunks=(-1, -1, -1)) self.by = codes_for_resampling(index, freq="5D") self.expected = pd.RangeIndex(self.by.max() + 1) class OISST(Cohorts): def setup(self, *args, **kwargs): self.array = dask.array.ones((1, 14532), chunks=(1, 10)) self.axis = (1,) index = pd.date_range("1981-09-01 12:00", "2021-06-14 12:00", freq="D") self.time = pd.Series(index) self.by = self.time.dt.dayofyear.values - 1 self.expected = pd.RangeIndex(self.by.max() + 1) class RandomBigArray(Cohorts): def setup(self, *args, **kwargs): M, N = 100_000, 20_000 self.array = dask.array.random.normal(size=(M, N), chunks=(10_000, N // 5)).T self.by = np.random.choice(5_000, size=M) self.expected = pd.RangeIndex(5000) self.axis = (1,) flox-0.11.2/asv_bench/benchmarks/combine.py000066400000000000000000000042231514775624000206120ustar00rootroot00000000000000from functools import partial from typing import Any import numpy as np import flox from flox.dask import _grouped_combine, _simple_combine from . import parameterized N = 1000 def _get_combine(combine): if combine == "grouped": return partial(_grouped_combine, engine="numpy") else: try: reindex = flox.ReindexStrategy(blockwise=False) except AttributeError: reindex = False return partial(_simple_combine, reindex=reindex) class Combine: def setup(self, *args, **kwargs): raise NotImplementedError @parameterized(("kind", "combine"), (("reindexed", "not_reindexed"), ("grouped", "simple"))) def time_combine(self, kind, combine): _get_combine(combine)( getattr(self, f"x_chunk_{kind}"), **self.kwargs, keepdims=True, ) @parameterized(("kind", "combine"), (("reindexed", "not_reindexed"), ("grouped", "simple"))) def peakmem_combine(self, kind, combine): _get_combine(combine)( getattr(self, f"x_chunk_{kind}"), **self.kwargs, keepdims=True, ) class Combine1d(Combine): """ Time the combine step for dask reductions, this is for reducing along a single dimension """ def setup(self, *args, **kwargs) -> None: def construct_member(groups) -> dict[str, Any]: return { "groups": groups, "intermediates": [ np.ones((40, 120, 120, 4), dtype=float), np.ones((40, 120, 120, 4), dtype=int), ], } # motivated by self.x_chunk_not_reindexed = [ construct_member(groups) for groups in [ np.array((1, 2, 3, 4)), np.array((5, 6, 7, 8)), np.array((9, 10, 11, 12)), ] * 2 ] self.x_chunk_reindexed = [construct_member(groups) for groups in [np.array((1, 2, 3, 4))] * 4] self.kwargs = { "agg": flox.aggregations._initialize_aggregation("sum", "float64", np.float64, 0, 0, {}), "axis": (3,), } flox-0.11.2/asv_bench/benchmarks/helpers.py000066400000000000000000000005621514775624000206420ustar00rootroot00000000000000import numpy as np import pandas as pd def codes_for_resampling(group_as_index: pd.Index, freq: str) -> np.ndarray: s = pd.Series(np.arange(group_as_index.size), group_as_index) grouped = s.groupby(pd.Grouper(freq=freq)) first_items = grouped.first() counts = grouped.count() codes = np.repeat(np.arange(len(first_items)), counts) return codes flox-0.11.2/asv_bench/benchmarks/reduce.py000066400000000000000000000115001514775624000204410ustar00rootroot00000000000000import numpy as np import pandas as pd import xarray as xr from asv_runner.benchmarks.mark import parameterize, skip_for_params import flox import flox.aggregations import flox.xarray from .helpers import codes_for_resampling N = 3000 funcs = ["sum", "nansum", "mean", "nanmean", "max", "nanmax", "count"] engines = [ None, "flox", "numpy", ] # numbagg is disabled for now since it takes ages in CI expected_groups = { "None": None, "bins": pd.IntervalIndex.from_breaks([1, 2, 4]), } expected_names = tuple(expected_groups) NUMBAGG_FUNCS = ["nansum", "nanmean", "nanmax", "count", "all"] numbagg_skip = [] for name in expected_names: numbagg_skip.extend(list((func, name, "numbagg") for func in funcs if func not in NUMBAGG_FUNCS)) def setup_jit(): # pre-compile jitted funcs labels = np.ones((N), dtype=int) array1 = np.ones((N), dtype=float) array2 = np.ones((N, N), dtype=float) if "numba" in engines: for func in funcs: method = getattr(flox.aggregate_npg, func) method(labels, array1, engine="numba") if "numbagg" in engines: for func in set(NUMBAGG_FUNCS) & set(funcs): flox.groupby_reduce(array1, labels, func=func, engine="numbagg") flox.groupby_reduce(array2, labels, func=func, engine="numbagg") class ChunkReduce: """Time the core reduction function.""" min_run_count = 5 warmup_time = 0.5 def setup(self, *args, **kwargs): raise NotImplementedError @skip_for_params(numbagg_skip) @parameterize({"func": funcs, "expected_name": expected_names, "engine": engines}) def time_reduce(self, func, expected_name, engine): flox.groupby_reduce( self.array, self.labels, func=func, engine=engine, axis=self.axis, expected_groups=expected_groups[expected_name], ) # @skip_for_params(numbagg_skip) # @parameterize({"func": funcs, "expected_name": expected_names, "engine": engines}) # def peakmem_reduce(self, func, expected_name, engine): # flox.groupby_reduce( # self.array, # self.labels, # func=func, # engine=engine, # axis=self.axis, # expected_groups=expected_groups[expected_name], # ) class ChunkReduce1D(ChunkReduce): def setup(self, *args, **kwargs): self.array = np.ones((N,)) self.labels = np.repeat(np.arange(5), repeats=N // 5) self.axis = -1 if "numbagg" in args: setup_jit() @parameterize( { "func": ["nansum", "nanmean", "nanmax", "count"], "engine": [e for e in engines if e is not None], } ) def time_reduce_bare(self, func, engine): # TODO: migrate to the other test cases, but we'll have to setup labels # appropriately ;( flox.aggregations.generic_aggregate( self.labels, self.array, axis=self.axis, func=func, engine=engine, fill_value=0, ) class ChunkReduce2D(ChunkReduce): def setup(self, *args, **kwargs): self.array = np.ones((N, N)) self.labels = np.repeat(np.arange(N // 5), repeats=5) self.axis = -1 setup_jit() class ChunkReduce2DAllAxes(ChunkReduce): def setup(self, *args, **kwargs): self.array = np.ones((N, N)) self.labels = np.repeat(np.arange(N // 5), repeats=5)[np.newaxis, :] self.axis = None setup_jit() # class ChunkReduce2DUnsorted(ChunkReduce): # def setup(self, *args, **kwargs): # self.array = np.ones((N, N)) # self.labels = np.random.permutation(np.repeat(np.arange(N // 5), repeats=5)) # self.axis = -1 # setup_jit() # class ChunkReduce1DUnsorted(ChunkReduce): # def setup(self, *args, **kwargs): # self.array = np.ones((N,)) # self.labels = np.random.permutation(np.repeat(np.arange(5), repeats=N // 5)) # self.axis = -1 # setup_jit() # class ChunkReduce2DAllAxesUnsorted(ChunkReduce): # def setup(self, *args, **kwargs): # self.array = np.ones((N, N)) # self.labels = np.random.permutation(np.repeat(np.arange(N // 5), repeats=5)) # self.axis = None # setup_jit() class Quantile: def setup(self, *args, **kwargs): shape = (31411, 25, 25, 1) time = pd.date_range("2014-01-01", "2099-12-31", freq="D") self.da = xr.DataArray( np.random.randn(*shape), name="pr", dims=("time", "lat", "lon", "lab"), coords={"time": time}, ) self.codes = xr.DataArray(dims="time", data=codes_for_resampling(time, "YE"), name="time") def time_quantile(self): flox.xarray.xarray_reduce(self.da, self.codes, engine="flox", func="quantile", q=0.9) flox-0.11.2/asv_bench/environment.yml000066400000000000000000000007461514775624000176040ustar00rootroot00000000000000# Conda environment file for ASV benchmarks # This file is used by the rattler backend in ASV to create benchmark environments # It includes all dependencies needed to run flox benchmarks name: asv-bench channels: - conda-forge dependencies: - python=3.12 - pip - setuptools - wheel - dask-core - xarray - numpy>=1.26 - scipy>=1.12 - pandas>=2.1 - toolz - packaging>=21.3 - numbagg>=0.3 - cachey - pyarrow - pip: - build - numpy-groupies>=0.9.19 flox-0.11.2/codecov.yml000066400000000000000000000004371514775624000147270ustar00rootroot00000000000000codecov: require_ci_to_pass: no max_report_age: off comment: false ignore: - "asv_bench/benchmarks/*.py" - "tests/*.py" coverage: precision: 2 round: down status: project: default: target: 95 informational: true patch: off changes: off flox-0.11.2/docs/000077500000000000000000000000001514775624000135065ustar00rootroot00000000000000flox-0.11.2/docs/Makefile000066400000000000000000000152041514775624000151500ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/complexity.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/complexity.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/complexity" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/complexity" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." flox-0.11.2/docs/diagrams/000077500000000000000000000000001514775624000152755ustar00rootroot00000000000000flox-0.11.2/docs/diagrams/bitmask-patterns-perfect.png000066400000000000000000000370221514775624000227250ustar00rootroot00000000000000‰PNG  IHDR°Âÿó0¢9tEXtSoftwareMatplotlib version3.8.0, https://matplotlib.org/5±µ« pHYsgŸÒR=IDATxœíÝ}œ\{^øÏ/&¦Mˆ¯ì‘4¼B]yVap�|AXF£¨Ü5p}¸³>,à\YQã¶{ÅEݠ®"Œ°,O>Ý( «2£¨ 8Й C  d&Ó—„>þQ§I&ÓUÝU]çœß=ý~¿^ýê¤OW}~¿ªSß_�oUŸ*MÓjujèÀ"XTM €ªi`PµÓ«\¨”2Mr.Éóë/[Iî6M³¹Ž+³®YpœQVùÂRÊÏœ9súÂ+^ñëÜÒÞùν$Éùóü�Lþ^^xá^¦Óé ùI²¹9Û—‡ƒüÍœ={f°}0©ãqp’óßüæ·æ…îÝišæ¯ãúúZWúºÝÆ”3¦¹Œ-gLs‘So†œºsÆ4—±åŒi.rêÍc΢ãŒUX7/]Úš¼ùÿÁZ¸¬ÏÝI’}ÝncÊÓ\Æ–3¦¹È©7CNÝ9cšËØrÆ49õfŒ1gÑq†s`P5 ,ª¦�@Õ4°¨šUÓÀ jXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ jXTM €ªi`PµÒ4Íü�¥Üœ³é±‹/ž}úé×t3ªClnn&I¦Ó©ü�ò76ÎäܹáúŸwïî%É`c�?l~ c8éùW®<™Û·Ÿ¿Õ4ÍãË\nѺ²µµuöúõg×0ºùúºÝÆ”3¦¹Œ-gLs‘So†œºsÆ4—±åŒi.rêÍc΢ãŒÓ«^i)%“É¥ã�lEû7œüaó9Ùvwïegg˜&jò ‘;ÔNzþ /Ü$xw}›õuü±è8cakÞ+륔›§NeòÄå ÇÚjn˜Ôñ88ÉùçϯöJÒÐëJ_·Û˜rÆ4—±åŒi.rêÍ�SwΘæ2¶œ1Íe¬9]ÓöuÌÖ×±Y_÷Í¢ã çÀ jXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ jXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ j¥išùK¹9gÓc[[[g¯_¶›Qâîݽ$ɹsÃôßäïew÷^¦Óé ùI²¹¹™$ƒ�Aþf66Î ¶&u<Nrþ•+Oæöíço5Móø2—z]éëvSΘæ2¶œ1ÍEN½rêÎÓ\Æ–3¦¹Œ5§ëcÚ¾ŽÙú:6ëë¾YtœqzÕ+mš&·nÝ>ÞÈV´¿#ìì Û<8Éùg2™\$?yðàj ò÷Ɇ±»{¯ócоŽuû8¦íë˜í$›-l`Í{e½”róþýû“«WŸêfT‡ØÞ¾–$‘?\þdr)O\¾0H~’ÜxîN’ 6ùÃæ×0†“žþüj¯¼,ZWN�ʤëùôu»�)gLs[Θæ"§Þ 9uçŒi.cËÓ\ösvv¦�ƒöu¬ÛÇ1í÷�>rg8UÓÀ jXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ jXTM €ªi`P5 ,ª¦�@Õ4°¨šU+MÓÌßXÊÍ9›»xñâÙ§Ÿ~M7£:Äææf’d:�Ê(cãLÎ�®ÿy÷î^’ 6ùÃæ×0†“žåÊ“¹}ûù[MÓ<¾Ìå­+[[[g¯_v £›¯¯ÛmL9cšËØrÆ49õfÈ©;gLs[Θ沟³»{¯ócоŽuû8¦ã>ÐG΢ãŒÓ«^i)%“É¥ã�lEû7œüaó9Ùvwïegg˜&jò`qj '=ÿ…î ’ ðbÑÇó從î7|º>íëX×1í‹ÓÂÖ¼WÖK)7O�Êä‰ËºÕ!nåØêÍYtœáXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ jXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ jXT­4M3c)7çlzìâÅ‹gŸ~ú5݌ꛛ›I’ét* ü��39wn¸þçÝ»{I2Øä›_ÃNzþ•+Oæöíço5Móø2—[´®lmm�½~ýÙ5Œn¾¾n·1åŒi.cËÓ\äÔ›!§îœ»w÷²»{¯óゾŽ?ú:Îðø”c¨7gÑqÆéU¯´”’ÉäÒñF¶¢ýNþ°ùœl»»÷²³3L5yðDj¨1œôü^¸7H.¬CÏcúX«÷>]ôuüá8XdakÞ+륔›§NeòÄå ÝŒê7ž»“$‘2ókƒü;ÙÙ™æêÕ§ÉO’ííkI2ØNz~r¥K ½®ôõØSΘæ2¶œ1ÍEN½cÍéãyLkõööµL&—Fuߌ%gLs‘SoÆsΟŸÿ/çÀ jXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ jXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ j¥išùK¹9gÓc[[[g¯_¶›Qâîݽ$ɹsÃôßä›_Ãäïew÷^¦Óé ùI²¹¹™$ƒ�á¤ç?óÌë2�No5Móø2—z]éë±3¦œ1Íel9cš‹œz3ÆšÓÇó˜>ÖêÍÍÍllœÕ}3–œ1ÍEN½c̹råÉܾýü�ǧ;Mf´vwïeggøæÅPc�?{²4™\$?yP@‡ÃIÏ?{öÌ ¹ £�ç~}=¿éëyLkõ~ÀI°°�5ï•õRÊÍS§2yâò…nFuˆÏÝI’È.ggš«WŸ$?I¶·¯%É`c�-“É¥ÁöÁ¤ŽÇÁIÎ?~µW^†^WúºÝÆ”3¦¹Œ-gLs‘SoÆ~NÏýúz~Ó×ó˜±írêË�SwΘæÒg΢ã çÀ jXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ jXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ j¥išùK¹9gÓc[[[g¯_¶›Qâîݽ$ɹsÃôßäïew÷^¦Óé ùI²¹¹™$ƒ�Aþf66Î ¶&u<Nrþ•+Oæöíço5Móø2—z]éëvSΘæ2¶œ1ÍEN½û9}<÷ëëùM_ÏcƶÈ©/CNÝ9cšKŸ9‹Ž3N¯z¥MÓäÖ­ÛÇÙŠö·��a›'9cãL&“Kƒä'Õͨ±½}-I"¸üÉäRž¸|a�ü$¹ñÜ�$l ò‡Í¯a '=ÿüùÕ^yY´®œ:•I×óéëvSΘæ2¶œ1Íe¬9;;ÓNŸ¯õõœ°¯ç~cܺÎÓ\Æ–3¦¹È©7cŒ9‹Ž3œ €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ jXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ jXTM €ª•¦iæo,åæœM�]¼xñìÓO¿¦›Qbss3I2�N唿±q&çÎ ×ÿ¼{w/IƒüaókÃIÏ¿råÉܾýü­¦i_ær‹Ö•­­­³×¯?»†ÑÍ××í6¦œ1Íel9cšËXsvwïuú|­¯ç„}=÷ã>ÐuΘæ2¶œ1ÍEN½cÌYtœqzÕ+-¥d2¹t¼‘­hÿ†“?l>'Ûîî½ìì ÓDM™\$?yP@‡ÃIÏ?{öÌ ¹À»ëc=îkÍékmé«~îçë±°�5ï•õRÊÍS§2yâò…nFuˆÏÝI’È.ggš«WŸ$?I¶·¯%É`c�-“É¥ÁöÁ¤ŽÇÁIÎ?~µW^†^WúºÝÆ”3¦¹Œ-§¯õ¸¯5§¯µelû@×rêÎÓ\Æ–3¦¹È©7cŒ9‹Ž3œ €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ jXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ jXTM €ªi`PµÒ4Íü�¥Üœ³é±­­­³×¯?Ûͨq÷î^’äܹaúoò÷²»{/Óét�ü$ÙÜÜL’ÁÆ 3gÛ“:'9ÿÊ•'sûö󷚦y|™Ë ½®ôu»�)gLs[N_ëq_kN_kËØö�®3äÔ�3¦¹Œ-gLs‘SoÆsgœ^õJ›¦É­[·�7²í?‘ÚÙ¶yp’ó76Îd2¹4H~òàÁ3Ôäï ’ °»»÷:_CûX«ûZ�ûZs¬-À" Xó^Y/¥Ü¼ÿþäêÕ§ºÕ!¶·¯%Iä—?™\Ê—/ ’Ÿ$7ž»“$ƒ�Aþ°ù5Œá¤çŸ?¿Ú+/‹Ö•S§2éz>}ÝncÊÓ\ösvv¦�¯¡}¬Õ}­ÇcÜÆ’3¦¹È©7CN½rêÎÓ\úÌYtœáXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ jXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ jXT­4M3c)7çlzìâÅ‹gŸ~ú5݌ꛛ›I’ét* ü��39wn¸þçÝ»{I2Øä›_ÃNzþ•+Oæöíço5Móø2—[´®lmm�½~ýÙ5Œn¾¾n·1åŒi.û9»»÷:_CûX«ûZ�ǸŒ%gLs‘So†œz3äÔ�3¦¹ô™³è8ãôªWZJÉdréx#[Ñþ 'Ø|N¶ÝÝ{ÙÙ¦‰š<88j '=ÿ…î ’Ë0úx¼÷µOï7}º^CûX«­ÇÀI²°�5ï•õRÊÍS§2yâò…nFuˆÏÝI’È?™ù5ŒAþ�ììLsõêSƒä'Éööµ$l '=?¹¿Ò¥†^WúzìŒ)§¯Ç{_ûôööµL&—Fsßt�!§îœ1ÍEN½rêÍ�SwΘæÒgÎùóóßáåXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ jXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ jXT­4M3c)7çlzlkkëìõëÏv3ªCܽ»—$9wn˜þ›üaókƒü½ìîÞËt:$?I677“d°1œôügžy]¦Óé­¦i_ærC¯+}=vÆ”Ó×㽯}zss3gFsßt�!§îœ1ÍEN½rêÍ�SwΘæÒgΕ+Oæöíç<Î8Ýi2£µ»{/;;Ã7/†ƒüÙàdri�üäAj '=ÿìÙ3ƒä¾˜ôQ'û¨}=ÞûÚ§÷sxqYØÀš÷Êz)åæ©S™jA_�÷>ï›±äŒi.rêÍ�SwΘæ2¶œ1ÍEN½cÌYtœáXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ jXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ jXT­4M3c)7çlzlkkëìõëÏv3ªCܽ»—$9wn˜þ›ü½ìîÞËt:$?I677“d°1ÈßÌÆÆ™ÁöÁ¤ŽÇÁIοråÉܾýü­¦i_ærC¯+}Ýn}ÕÉ>jA_�÷>ï›±äŒi.rêÍ�SwΘæ2¶œ1ÍEN½cÌYtœqzÕ+mš&·nÝ>ÞÈV´ÿ„}ggØæÁIÎߨ8“ÉäÒ ùɃÏPc�¿7H.ãÖǺÒWýì«NöQ <Þ¨ÁÂÖ¼WÖK)7ïß¿?¹zõ©nFuˆííkIùÃåO&—òÄå ƒä'É�çî$É`c�?l~ c8éùçϯöÊËÐëJ_õ³¯:ÙÇ~Ð×¾&§Î 9uçŒi.rêÍ�So†œºsÆ4—>sg8UÓÀ jXTM €ªi`P5 ,ª¦�@Õ4°¨šUÓÀ jXTM €ªi`P5 ,ª¦�@Õ4°¨šU+MÓÌßXÊÍ9›>äôéÓ§^úÒ—v3ªCœ>}:Irÿþ}ùå—RrjÀöçÞÞìûPc�?l~ c8éùo}ë�æÞ½ûwš¦ùÅË\nèu¥¯úÙW�ìc?èk_“Sg†œºsÆ49õfÈ©7CNÝ9cšKŸ9‹Ž3N¯x�§îß¿¿7�N¿ï˜c[Õcí÷7Ë?‘ù5ŒAþ°ù5Œá¤ço%¹»Æëëk]éëvSΘæ2¶œ1ÍEN½rêÎÓ\Æ–3¦¹È©7cŒ9s�3¾kžýWЛ¦yüxãZ�ü“�_ÃäÛNzþºõ59ufÈ©7CNÝ9cš‹œz3äÔ›!§îœ1ͥϜEœ €ªi`P5 ,ª¦�@Õ4°¨ÚJŸB}ñ,ª¦�@Õ4°¨šUÓÀ jXTM €ª¹�UJÙ(¥|Q)åûK)»¥”·–Rþ¯RÊÅ.ØfL)å)¥¼¾”ò#¥”¦”²ÛuîCùçJ)ŸVJù{¥”ÿ\Jù™RÊ;K)ßSJùÂRÊ{õ0†?ÙÎÿJ)?]Jy¡”r»”ò÷K)�w�ÀxÞ§”òcí}ñ}=eÞhóæ}ý–žÆ±YJ¹Ö>ÞUJùÉRÊJ)¥ÃÌ'™ûþ×v5†vWJùºRÊ´”r¯�û¿,¥|F—¹�äC)åmmúþRÊ_*¥œ[cÆJõ¦”òÙ¥”_JyG{»|s)åã»Î/¥l•R>¯­ß[JÙk/óqËf¡�µ¥�5¤Ïubˆõ «šßw]ï²~÷Y§ûªÅ몹}ÕÕ¾êç29¥”S¥”O(¥ü•RÊ¿kG/”RÞ\JùÛ¥”ZÓ\>µ�Çiï¯{mÖ7—R>y]·Ùœëøíß›kšÏky}é:çSJ9[JùÓ¥”ï.³úýŽRʛʬ¦Àæs”Úð¯Ž;—RÊ”R¾¼”òƒí~öŽRÊK)¦”rv]·Y)åb»ÿ¾¥Íyk)å+K)/Ÿóû+¯‘e‰:°JNY¡,›SV¨+Îeé:pœûæ‘ëYXVœÏÒuà˜ûÚ‘ëÀŠóYª¬:—²d8FÎRu`mš¦9ô+ÉF’ïLÒ$yk’œäßµÿÿ±$�åzVýJòõmÖÃ_»]f>’ÿ‡Êý¯I¾&É·&ù™ögß›äý;ÃÛ’¼«½Ý_ß~½©Í!ÉoíëöhÇó•IöÚüïë)óF›÷µmþ£_ÖÃ^™ä§ÚqÜl ßœd'Éýs?dΜ¿2É?|hÿ¼Üá~W’ŸksþC’”äß<ô³/íø¶ÿ}Iî·YßÝ>ÞÒþÿ?%¹°¦œ¥ëM’¿ÞþÞÝöòßšä^;ÞOï2?ÉðûM’�ëòþXÓmÝËÚ²Ê}ºBFoëDXÒQÍO�u=×ïôT§ÓS-Îkî*�Á¬PW—ÍÉŠõs™œ$¿ü¡ßùáö²¯oÿÝdV#^µ†¹|mf�Ñÿ’ä›Úýâÿè²_¼®ûæ‘Ë¿º½Ì~}Ø\Ó}óÚöw¾#?¦~×÷µ÷OòŸÛßýÑ<¨«û?[ÇýsÐö¿~¼½ü3ヺ®7·ûÄ·$¹ÓþìÛ“œYÃ\~efkt“ä“|]’ïiÿÿöP·³â™%ëÀ*9Y¡,›“êÀŠsYº¬zß,[VœÏk³dXu>Y²¬8Ÿƒæ°ÿõu`ÅŒ¥ëÀŠ9K×�u}uqúâv0ÿ6É{=ôó?Ùþü_w5À6ç5I¾(ÉoOòK²ä⺆üÏNò·’¼â‘Ÿ¿,ÉÛñüß�á×&Ù8àç¼Íÿ‘$¿ §Ûã×·™ÿgÖ|0sHî�6ïå}Ý÷�äÿÒÌ~îæàEó× 4®ßÚÞ.oIrª£ŒÓ©ßýȶWfv0½—ŽšÙI.¶M’?øÐÏ϶E¶IòåkÊZªÞ$ù¤öwÞöp�ho—Ú"þÞæjfOð~ofO�ö'/†V/kKkHŸëDßëA—5¿¯º>tý^W�»æöUWûªŸËä$y,³�_÷ÈÏÏ&¹Þ^övÞó€bÙ¹|T’÷=àç›Ù�Ë^’Éqo³G.û~í}ôm™5‚¯mçÕKî»ËæœJò]íïý¥$§ÙþË’¼t�·Û#×ó’$»íå]?–�ËëÛßù›yh ÈìÀüûóÈãyÅý¹äÁAêß{øöJòTŸzärK¯‘Y¡¬˜³tX6'+Ô�ç²tX%g•:°â|^›%ëÀŠ9K×�ãÞnG©+Îeé:°Âþ¼RX××QnÐ3yðŠåG°}ðÓÅçŒi¥E¢£±¼r׉u¯]×ü¾êúÐõ{]uº¯ZÜuÍí«®öU?W­)™½ õííå?±‹Œö²§½üç­s.í~ý®ÌÎw² �µÂ>ðÚ¬ÐÀZ!絿óµ]æ,¸Üi/û]k˜ËÛÚßù%lû‚vÛß:NN’WµÛ2¼ 3É¿o·ê·Á�käºêÀa9üÞJu`Ùœ‡~ÿÈu`ÕŒö2G®Ëä§aXK8BÎZêÀ1ö�#×�#Ìe-u`QNu`™¯£œëU™ußÜ4Í<`û×¶ß?å×5FßÓ~?›ä}Ãϵß¶‡¬¿˜Y�ú㙽…÷D(¥¼w’ÏLòÓIþîÀÃùy¥”óI~Gû߯ê0ê…#þÞOv”ÿ1í÷�nhšæÇ“Üʬ!òÛ:Ê?P)e#³w§$jáÃNz}\ä$­-}®ë^^ô5èú½æ:ÝW-¬æž¤ºÚ4Ínf¯ˆ'³w veíÏK)¿9Ég%y]Ó4o^×õà�¶ßÿÚ@ù¿¿ýþ×p]G©ëª ßÝ4Í�¶ÿëöûï8`Û<ï±FvTúZ‹—ÊY±¬2—UêÀœ5Ö�¡ï›u×�eç³J˜—±î:pPNuàÈNáw>¢ýþÆ9ÛßøÈï�4¿¬ý~/ݼÏUJùì$¿"³Â÷ƒg}x’?•äzÓ4ÿ¦ó´Íw¥”ò¾™½ öû“|}Ó4oé8ó×föÀýIî•Ù‰r_•ÙøïKò5MÓü÷ŽÇp�ÿ)Éù$ÿ±iš›æü`ûõ!¥”Ïlšækö7”R^™ä7'ù¡ÌÎÃÒ…óí÷Ÿš³}ÿ±÷YÏ“À£ú�Ìö‹ošæ‡ؾ_?¼¿!½hœ¤µ¥—ubÝëAÏ5¿Ëº>tý^g�î«YsOL]-¥ü‚$—ÚÿN;Êøð$¿;³úó/×t�ç’üíÌ?�}€Më“J)™Ù»T~8É·4Mó†u\q)åB’_•ÙŸVý»ö1ô©IÞ'³?÷ý†¦iþë:²æä`’OÈì¾ùÇk¸Êžäs’üùRÊ4M³×æ¼’ÏËì¼Q_}ÌŒejÃQ´FvQú:f[*gÅ:°lƪu`nΚëÀaóYWx�œŽêÀ‘ïŸcÔ�yë®åtQŽì( ¬l¿T<þùÎÙ>vŸß~ÿÖ¦iŽúÊèÊJ)&Éã™í8Úþû­I>kí(÷Tfo=}{’§»Ê9¢¿ðÈÿÿj)åKš¦ù’3÷?Ùë¿gvò»W>²ý/—Rþ`Ó4ÿ¤Ã1d�¯ÜÍÕ4ÍÏ•R^�ä“üãv?|sfýªÌÞ*úš¦éê]€?Þ~¿4gûþÏ_ÞQþ< ëcÓ4ï,¥¼=É{—R.Ìy•â¤:IkK'ëD—ëÁ5¿Ëº>tý^[�î±YsOR]ý=™�—äÇ3;ౕR>%Éï̬Aû�I>>³�Ïmšæ‡Ö‘‘äK2»ï/w¸îïû�f—R¾.³?)zÇ1¯{’Ù¹oþ[’í$O�õW›¦éªþ¾ÌÎ%ó-MÓüÄ®ïÏfv þ'’|r)å�™­Ÿ�ä'’|ZÓ4ß{ÌŒ.jÃAkdu ¯c¶esV© 3ÖX嬳v›­«”ÓEXfXµÌËXw8(gØã²#üÝãW¤=¡ÙœíûŸªð¦.þÆqNæJgÞÁ8~[f¯ÿl’�è)ó_´óßÿzKŽø÷ÒÇÌýü<ò7ÈíNÙ¤¿s`}qf¿,³ó²|p’?—ÙIy›$Ÿßaö—¶÷’¼#³¿•~i{üµvÛ I>¼Çýo3³.úý,ù·æÇÈüˆÌ^ýxü™Ì±sæ~nœàò>²íãË·u�=·Þdö¶é&Éw,¸üþ'̼lÝùs~ÿF^çÀlmés ér�èr=è«æ÷Qׇ¬ß]Õé®kq×5·¯ºÚWý\¥¦$ÙʃOŠúcëÊȃó—í½+³?�9ÒÉtËIòÑíþü•�ü|'ë=ÖïÏ젓̾.¶ûÆþ}ÿO×°¯ý–‡jC“äËÚºðÒÌ>•ë�ë¾¹ÌÍörŸ±®ý,³?ñùg�ì{™}ÊÙ­á6ûíöŸË{ž ü½òàü;/1ëÀ5r�u`QΜß]©,›ÓþþRuà¨Ç­‡å¬«!g-uà�}mmu`Å}`©:p”Œ¬¡r›­µ,ûu”�ïŸðíKælß?©ë‰j`eöj÷O¦ãÆÉ‚ü—dÖIýçíþ|‡Y[™½­òÆ#?yzl`-ßojÇñö$¿¨£Œ/{¨|îÛÿI»í«zœ÷þ'µ}KOy¿7³øÝHòkÚ…äyðÉdÿ6|<óš²Ïgv ÕdöQ°�'¹Ð.:Ï?´è¬ý¶XTo2{Õ¤Iòí .ÿ#ÑÀ:hœƒ­-}­!}­ë^j¨ùë¬ëCÖï.êtµ¸ëšÛW]í«~®�s>ÉwgM�˜9¿¿‘ÙIÿŸÍìäs´“ /ºo~A’7dvpòè'ríd� ¬—{Y}ü1çóÉÕ†÷ø´°ÌÞ‰Ñ$y¾ƒûç£ÛËüT’³ë¸Í2ûsºçÛûâS3[> ³$îföçi‡~Bér¾¶ý�Hr9³Ö�Ìì¼7ûµá]GÈ™»F®¹,µ£,›³JX6cÕ:°è¾YgXéyÒ u`Ñ|ÖY–½V© 3ÖXËYKXåë(7Ò_oð×çlÿˆvûºàœÌ^>ä_̃'vm¨q´c9Ó¿½$¿º£ŒoÌìÕé}äç/O ¬v,ÿ¡Ë厮ÿ ó Óü&>"ý‡{œóþÇš~VY¯È¬ûþ|’ólÿ3çàp�cø°‡wÝNò¿Í[xÖ�»èÉï§¶Û߸àò?ÕþÎ{|JÇqóçüþ�¼8Xƒ­-}¬!C¬ëZj©ùëªëCÖïu×é>kq—5·¯ºÚWý\&§}œ~k{™oÏ´Ç©[YÓ'7föNˆ&É:`ÛNzh`µ—ÝoJÑ1çóëÚ¯ÃÛϵõ´IòË×¼¯í¯�_±Žý¬Ý¯ÞÔÖ¹ƒ>Ùw¿™~ècö°¹dv@üܵá�IþLûï·’±p�\W8,gÎe–®Ëæ¬RV™Ë#—?R8Â}³–:°†ù©a>k©+îkKÕ�#Ìe-uà(sYGXõë(ŸB¸Õ‹s¶_|ä÷F­”òÒÌ^åþÀ$דüé!ÇÓ4Íþ ßJºûDžßžYÇöËK)7ö¿’ü£vû>ôó÷êh ‡ù�öûË:ºþ�öû´9øo™÷·¿Gù璉ò¡I>*³?‡ùú"OÚŶišw°}ÿDÂOt5€¦iþKf'õüœ$3É—'ùc™½²´¯ËÙda}l?}ì%IÞÞ¼¸ÏÓÒ…Ñ®-C­k\j©ùëªë;í÷^ëwGuº·Z<`Ím]mÏ-÷U™�lÿ{’|JÓ4ïê!zÿÓ/Ç1¯çS2;(ùœ‡kC[6Ûßy}û³W3k‘u׆dv°ön𦹛çzY[}hOÚý{Úÿ®ë¤?.³?ÁþÁæàOö]gmx{’Oʬùÿe™�àÏeög^û'»ž[ޏF»ôµ/›³JXÓ\­GÌ9vXÓ|­GÌÙyèß+Õ�Uæ³l8bƱëÀQçrÜ:pG9‰ûþG'~ôœíû?ÿÏÇNÝÚO)ø–̞н>ÉiÚäÀÞÖ~¿3^’äçlûEm;Ê>Õ…÷n¿÷„žóì�÷.¥”î÷ý�í*ÿQû'3|}[X»¶ÿâgælßÿùût9ˆv�ÿí×Ï+¥ü†öŸ7ºÌ?À›2{§Êû•R.6ïùI9'¦>®`”kKëĺփ—døš¿®º>Týî¢N÷Z‹ª¹c®«+ÉgföI›¿©=èÃ:Ÿ'–ÌÞ±0Ïþ‡$¼t Yó¬¥64Mó–RÊOdVÞã1Ó6^²Ž¬Güú̺ogöî›uè»64™½ƒè[þy)åjûÏ]n‰5òXu ¯µxÅœ¥êÀç²°,™³rXã|Ö�£æ·c>G®Kd«,;—UëÀqåXߙ䧓1³'27›¦ùÎ>³Ûƒ»Õþ÷3ø•QW4ºµ¥’uâØëA 5�u}ˆúÝa�¼w]sÇZWK)Ïdvå·$ù�MÓüX�ñkyžØ4Í êÃþ;^Öþìë�“5OûØúôö¿oXÃU~cûýòÛ>>É/ÌìØß·†¬}ûŸLúUklªì׆_Ñ„>ª�Úð>™½cóg“üý¶y�M䘙û'l’üñî›wdÎߎ¯q yÀcà£ÛÛa/Ý�ÿla½IòÚßy[’W<ôóWfv¢åŸNò>]åðû7ò"8V;ÖAÖ–.Ö�¾Ö‰!׃u×ü>ëzßõ»«:Ýg-î²æöUWûªŸG˜Ï~Mûч糮ŒÌþ¬åO%yÉÛ~c’k/ÿ;×}›=t¹�¬éX™½kã³óÈ9ë2;Iðß~è¶<´Îá¾™döij?‘‡ÎÓÞ¦oh/ÿ¬ëvËì|:wÚßÿ�5îIþ{û;ÿáÛ.É/Íì�JMæ|òï’·Ù'ùÅìƒû�Ÿ¿xÀe–^#W©«ä�{hXq>KÕ�e3V­ë¸Íº®¹u`…ù¬TV¼o–®ǹÝrÄ:°Âm¶RXñ6[º¬ë«´a •R6ÚÁ|l»£|{’Kíÿ"³ø;ôŠVTJùä$ÿëC?úØ•áßfVD÷;ó§išÏí*ÿ¡q|E’?’äËš¦yºë¼‡r¿,þúf’[™ÃWfönίhšæ�v˜#³æ?eöXxyfµ`/Éç5MówÖ”³t½)¥ü�<øt�žÙþð3»]>³iš¯ë*¿”ò²ÌêÒ¾IfŸv3ÞêüMMÓ|ÉQÇЗ¾Ö–>Ö�¾Ö‰!׃u×ü>ëzßõ»Ë:ÝW-^gÍí«®öU?—É)¥|dfïî+I¾+³}ý ·išïX1ãå™=6ß•Ù9üpfŸpöÁ™ý)H’\kšæO>º®úXJÙɬ~¿¬išéÛW™ÏÏ$ùÞÌÞ­ò’̨ï›Ù§“þöæ€w®¸¯ý‰$Û™ýÉÚwevßÿÚÌþDé�Ižh9ÏÒª·[)å³’|u’ÿÐ4ͯytüÇÉ(¥|ZfŸªz:³FówgöÎÓWf¶_¿1É'6MónµBÎk“<Ý^ÿ�döçHŸÐf}e’+MÓì=’±Ò¹lX%g•:°lÎ*u`…Œ—g…:°Îç/‹êÀ1æ³T8ƾ¶T8ÎívÔ:°âþüiY²¬˜óÚ,YÖæ¨�®v0_œä¿evÇNÛÁuþ®Ÿ$¯Î{žáþѯWw˜ÿÚ#ä7I^ÞQþ%y]f¯H¿5³·ä½#³¤mç�OFéðvyyú{Ö‡fö7ãoÈì„{™®ïÊì�c}Ìúã8•äó2{𿳽¾3Éè)ÿl|¤é‡pŸzfo+}[{üdfoñîã“ÿpfÍŽk?’YñÿÈ5ç¬ToÚË}w»_¼=³¿U×ù=}}eßûÊóí|mYõ>]2ãµGÈ8ö:‘׃¬ÿX½ÖõôT¿ÓC�Nµ8k¬¹«>³d]]6'+ÖÏer2ûÕ£Ô†W#ã\fŸúôM™½ânfÍìJòÿdvàµÖûæ€ëÙi÷Àw`-9Ÿ I¾4³ýï‡Û¹¼3³:÷W“|@ûÚoJòÏ2ÛÏv3k^|aæ¼á9ßÜn»ÚÅã&³ŽøêÌ>©t}ø�IþlæÔÔes2{‡é×·/döBÊ·%ùôsyí2š°Ff‰:°JNV¨Ëæd…:°BÆJuà8÷Í2u`…ù¬TŽ3Ÿ,QŽ™s¤:°jF–¬«äd…:°®¯#½ †r”“¸À`4°¨šUÓÀ jXTM €ªi`P5 ,ª¦�@Õþb¨©`pD»IEND®B`‚flox-0.11.2/docs/diagrams/blockwise.png000066400000000000000000001240161514775624000177710ustar00rootroot00000000000000‰PNG  IHDR›€”¼ugAMA±� üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿÿÿ ½§“ pHYs × ×%uÒð€IDATxÚìÝy|Å�>þOu÷œÒè>-ù’oƒ `csÌa“$�l6°$Ù’ÈfÙ,¹6Ç.¿lB6Y‚! H $Yø‡11ØØsØÂ·lÉ–eë¾Gsvwýþh¹tŒjfZ�æy¿²ûjä陚V©»êéªjÆ9'ȵµµe•Jº‹ ‘@ÖA$�u dDBY‘@ÖA$�u dDBY‘@ÖA$�u dDBY‘@ÖA$�u dDBY‘@ÖA$�u dDBYG‹ÿÏœˆñDÞ1Ñ×'½;UÂ$öšr2ëà%ñ¹ø=ñ�#–Ä.óAâ³àxâ§OÆÞ1‰]¬½’Ø%¹Èpœs–\Mæ\úO€Ç\Íå?+©½Þ/•ô^ |.SzLÉuý3è NeV[eŒHˆÅüÿÞ:¹Mà^SNf¼$>¿g¢¤2”äb— û *éT;NÌ.©|)€Œ•dD ý½°aã¶K|/üá¤$SzLIh¦|Á©ŒsNDŒ�rÿÖºÁŒ:©áPXvç^—ˆ Üâœ1Å¡)QÝàÒ{™&÷º4Fl Uùb^gT7åÛjœs‡¦&79—ìôrÎ…1"ÃLà&!çœ)Lad˜²DD&q—ª¨ÁιìG™9s‰1 ÷“¢JŽ#w©.â¦ìovøËL�+a60 ;jÀú\W…z¥—ŒL�ÜÄ™brS#9'E#¦‘”Ýep/'ñhb÷B9'ÅAÜ�?’œ›LuùM#PäŠgýýæ;óz"½ T'næ:r"F4£Jµ]Ñ-jF¹t…çœÆŠ=E”ĽôS/æœsf†ô ü®œs§ê‰šaù¿“s§â$âQ3šÐ·s*Î�Jè  ªµ'@†)°qΙËE†Áu]ºxœ9ŠÛmôõ1E¶¶sÎÕü|£¿Ÿ8—-›i2¯‡LÎCa&y–¶F ¸œšÃÁ�NÔÄ9y<ƒã˜ä÷òÊPˆLSv_ëC�.Ò£dJŸ9'U#…Q$BrŸsbŒ›NwpÀ� Á9W ™Ü48 œsÍ¡‘•?øœ1æòhÁÙ?"29÷x5N&7i*( SÈÔ¥�6 ^÷­¯–Ð^šCS–ØÙiHõãœs¦ë:—»ÞqΚ 葈ü‡2EQ5G4œÈ©†HaLs8£á�ü11MÓ›ë‹FÂzTþLÈ5‡ÓéqGÃa¦H×'NªÓeFÉ´"IQÎçF"¿d®¨3�ÎíĘCsAÙó-qÓT=šâTå/‘DÃÎNœsƒÁˆü‡*.•3CºäyÞªíÌÁÌH"ç^ëlãVÉäò/œÓâÄ Sú·Ì™Â˜ª˜QÙ]?È¡�n&ð·lÕ<…‘É%/(œ¬‹p=*ýb}”×ã †‚òµÝ4¹Ëår¹\Ü”þ5‰áº|½ßß/]N®©š¦iÁPH¶ÏEÄ99]�&xöåN§+ÑH]aŒ3 #�†cšªêºžPx­( ç\ò¬>xS3¹ü'NŒ1Æ¢†éOä n:µ‚¨1@\úKqΗS+à<‘¿ú¾’sÒ�^âÎ'¹F'ÆœÄ87#Ò]!bŒZ~$Ú›Hu2š�‘jò“[-‡gLe¤™<$¹Ë©âiDFBG’W˜ƒsƒ“tÓš¸Â4"Õäaùþ礪Æ”+aOO�ÛíÑFß™³HèÉ·[?¤ó�.¦8d>Ò0ù×VϹs½arÉ€ir¯[=·¦xÛáÎ`ÄP%Î5ŠÂÚúB_¼°&ץݳñPežÛ0Çþã4Lîq©ý»�‘ä�1 FŒsç)�]ÔÞghªTÍ0MžãVrÝÔÚc(Š"Ùˆ7Mîu+eTßl8TEædÈÂ?ŽBÏ|†ù»I•ø)*õuÓ…_!§�6þ”_H¦1ö^Öâ �“V“@bò�w0ˆ©ÔUÏÞùÕï%§GjàŸi�3‡ÖÜMO}™œ© IÕ¨§‹®ý_rhtòræH|£h�JQÙÚóGò—8&Vñ*—QÛ^2�+#BgPwEüR‘SX¨—f_¶!xô¡wþ§Ä[a�>æN†iä8rî>ïî/oý²GóÈDº*iÁö9ë_¶5o{§í�GŽÌ^º©8 /Ùݾ+¤¹�1jè%ž’‡ÎˆR¸—æ¡ìÝc�‡ªÉ¼ž‹šÑûÖî½6Ã2'(…¿ÞnþEa íòïðj¹2ÇÄ0�ÞñÕÛÓ*I—¨í 3Šû›þ¿ËèícšÄW½½£èó7™½ý=ÿ÷”VTHgi"2 £ð#kèÌ3XG›Ô9mp7“>úq¢&pNµ»©½•“Š]F�}øÃtàut�&QTÆ(  ©´Œ^ÝL¾\™cÂ8‘Ê• >úâ}‡ª\$Ä(4f/+íï v�Мr'mFá 1ë´bÓä�ûº\Uæ$mèfn¡û‚OÏyþ¹sRßHaþžðGn]Š„{Z4MꤡG�¢JŸÇç:~ ËéÖ$ïR™œçzŒˆKæéÄ(Ñ篬 ¤ÏNœˆQ$¤w U¦CÎ(4^tnÕ{ž8øÆŸ�K¤“†®ç•”V/>ý½M/¸rrdv!"Ó0<ù³N_¶ïÕWT§SæÂª¨ZgûÕÿúÝÚM/ßûžÓës/ÆXÈß?眕+¯¾îàËsû|\î À9Ÿ{þ‡�¼þª|Þ4¢y%Ä”þŽVÙ˜›1#,ž5WQÔ¶úƒšKª�Á SËqÎ:óÊðC‡¹?R™Ñvܰ€ª™|2>r‘Ÿë 64;]2-=2ú"Þ‹ªÉëý­^ËwËüar“3§â:£,úf3“kîZLÝt|l:õûÉ�‹kQP§Å•ˆÐ±.rËIÃ$Ÿ‡æä«ïµ’ä€E :½’z)!É/ešäq‘×IÝ ÜcÐ *ðîê>øÎŽ·¼9^S®¶ëºþÙøÌ_ÿúº.•e( ëï÷_üá‹N[ºTþŽÎ8ßùÖÛÛ·o÷ùrǬøŒQ0œ?þÒ%KþôäŸ ò CêÛ†qöÙg×Öî–üvÖ —_~ENnN``@Qåš”º^Y^îv»�;ær¹dò骪3gÌ8~ì˜"w$c‘Hdö¬Ù=½½½½½ª\ÙLΚæËÍíëï—N¬¸ir—Ë“Ÿ÷úÑ£÷ªªO¦Çʉ´�qÆÃ'ÿ×?pPU·¶·îxg¡'* å¸ÔÖöyMÍ}ò‘P dÌ.(<Ñlí ;4©ß±arŸÛáó('»Cªt$d˜<Ç­ša÷ñ“~§& …tÞV˜WÖ°Çìï’:u*õ†©¦ž\ytø0ºÈ;_üËom¦Y5d ¤BC†šQ:RKMõĸl$äÊ¥–ãT¿�œš\$ä ®57’ÓAM‡É)s&e =‡x5Õ‘§8�HˆÓÉú#¡0QuÔSX6ІºžE Á£µÇj+r;u. å:s›Ûš÷4îñj^™ CcZK ¥±ªñPÓ¡=-{rRñ‡nê…®B-èÜ×¶/¨(L2Š–yËﬥ0J¨±ëhSO£*—â3ba3ÒJ-�GCfH•¨Æ )}zïLc~ÈínÈÕ|2Ç$jê…ÎâÙ%­G[rÔ\ÉH(l†Ýámß>£¹U:RŒîîÐéK†j÷(9c÷Óˆ1£§7·¬¤°©©ew­£»‹¢gE1ûýîææ–½ûŒž¹HHÕ[[õú£»·»¶V+)‘€cFtáüŠ’"ÞÚÌÒ‘�aÒÀåä$0`‡†åGÍÍÔÔHš*uªQòPK ?N­­ä”‹„”ë#ä£ ”Ÿ'sL8'¦òÞÖ¶£u'$#!Æ(ÐF_{ ½±OsJýU2F¡€®åFL“=Ôîöj2‡ÁÐͼwkKþÑCMŸS¦Û¯¨¬¿+ÔÚ\ tžô;r'�ˆ¡€wÀ}âx«Û#U6"âœû‚^=lúC’í~b ë• |¾¢œîÃÓð§üÄñæH8,¹kÀ*ž]ÔÔPßpp¿7/_&ßÑ£ÑÂî–“×pp¿;W*E"+*(Ôò Ð\.ÉH¨¯½¥¥ùÄñú# ÷»rre"¡`¯³¤|NKˉ£ Þ|©od·üÖ–æÆ£ò—=ESzÛNJÿ–Y4Œ:ÜŠ¢6=êôÈEBº©ùÜåÓ:[ŸPr$#!Åè•5æTWrÝH jöÝŽœìjj—ºÆ*dôD æ8”GgÝ ­È#uª19sj¹Å¼ÿÈ &×ܵ˜Q³¼ÙÉzû¹ä|F<u«| ¬7u0¹|‡¦’çÑ|ijfn‡d$Ä#º³Ü=ÑÂɃÏM“y],×Å;ü’•�‘n(ÁœãMGŽÔçäæ˜rµ]×õÖÖÖúú]nœ¯¢(}}}óæÎ=méR]×5¹T‚h„êÔÐÐÐÔÔ$ 0Æ�€Ûí.).>|øHQQ�aH};Ã0***ä¿�õš››s}¹~¿_2v G" ‘Ûí>qâ„Çã‘�„ òó›››å#¡P(”ãÍéêîêêîvÈ4„NEB>_o__‚‘�7lhjªÕ´™n¿L«<Ñ|ò@_ÿ^U•‰„ÔH´Ó0æTV�iD¹ÁÖ‡}ð`R$ÒÝÜ\«ªE\¢‡BD¦Èw*ª·££V“Ë58犢Ÿ8qb�t$ä…Ž6:�Ñ®îMË•‹„Â/((lii�,qnø|ž`°Q׊„B‘H倿1íR™/Åt} *ryô––‡–/_¼o¿¦ùâ bŠ;Jˆ˜ª0—¦8 …ÉÝ’3WæTƒIßÞaÜ¡*šÂªâR¹L$¤*Ì¡)ªÂT…©šâTE"•4w¨ c¤0Ù!ÌŒ‘¢0k©ü^œ‘Âèƒ$qEa¤0&ö’Œ„kИê$Õ!7JH#u€••4�Ô<’i_X‘“¬#¿ÅàU]¤*Ä4©Hˆ¤ºˆ)¤*¤Jµ\Iu�æ'¦SHÑHQ¥"!E#¦cƒ»ÈM¦ Æ:ò‘W÷²þ'õ1EeªKu9“]¨�âPŒ1‡âp(ÉHÈ¥¸¦hŠæPœ’{1bÅ¡)šCqèŠC2"NÅ‘ÔrtüšLU™ªHGB*S±Á½ä"!kÆ•© S¥Æ[1®0Åú8…©\¦?øb…9Ìé ‡l$Ä\.¦ªLÓ˜ËÉ™Hˆ¹\LÓ÷u8äFÄ(Ìå$Ƭâ1™aYªzªlêàÉ5‘™ª0U%E!UîDBJjqî~ÍÁ•Œ„TÕºHÈ•1²¾Úà†*uð9‘‰1USTéHHÕEeŠÊTMQåzwb/bdí%y8Ualðƒ$#!UScŒ1E‘:¡ îqjÉ_µ5ü^üŸÔ.ìÔ8âÔªSBßîýƆªªšCÕ4™…s®hZB»XS÷ÒTMê^‹¢jªæ`LŸ% ©šCQUƘ¢ªL²¶Ó`{ƒ)*IGBŠjkE•%¤¨*S¦(ƒÅ“j`0¦(Ĉi SÉQBLSˆ%÷ä•ao¥2¦Êœ±‰iÊ`ËUS¬ëžÄ1áLe¤0¦* �b&Ñ©†²ì(!ë•b/™c3ض¦v­dåÔÉ}!ш'.ݺS1«6©ªª&4}IUUÉìÉzÙÜ3.õ™HÈúPƘ¦©ª*ý·|ªÀòñ®õzCr¯SW%�Y·ŒYÿ_rñþÖ%Ev¯S‘Ð7bÌÚKU—¢8$#!Eq1¦0¦)ŠS&ßa¤*Š‹1ÙQ·q?}°¨òK 0¦Ye–ü‚V$Ę¢(N&××`Ìqê˜(ÖÙSr¼cŠUñK ¶‹O‘�„¬ÇOz—Ä‹gÅ8ÿ¦�¹'?õ?ù�’ý�ÒIîû2ùâ¥\á'³„C φV Oª´É}Á©&‰§z%ûþs íh%O= јKhf{¢»|`¯ñ,[¢_ˆ$ž‰0T'°–D€D%Ö"L|/ë†}F5ÅŠßí²!i€Ì‚H ë È:ˆ„²"!€¬ƒH ë È:ˆ„²"!€¬ƒH ëhqÿ•YÿgýO;õÿÚ%ѽb_–Ü^SÎx<|çXTV;Úe bĬÿI¾2vC~—äö’/[Ì^©W§)‡�:�ò/Nh¯Ø—%±×x�õâ¤Ùp‚8%]Õ‰‰«ÞØ/ýàFB-µ÷b‰ï•ÊßòÔƒ³ØÕ)[$Ñ¿K|{ªS�Ar{¡K˜´x¿èQ#!Æ×MÔM2̈nÊ|”arÝäÁ¨a˜œÉµL“«E 3¤Á¨¡*cï¥*,5¢†©܈Á¨¡›c×fÃäŠJœ“aÊÖ}ÆÈ09çdšÜ0¹"ñ)Ö™&797LNÄ%û½†É “›|pCfFƒÅ#=Hz”¸>ö>ŠFQ�Œ(™:E5ÒƒdJìÅ91FÜ$Æ’íÅ['“ôé&± Ôa19©*™EMR‚Rç,®ST#Ó S!#JfTâƒQ2 âæà‘‘9’DdÄ 2£dÒm_6øzS'3J$ñgÅ2¢Ä ÝÔƒz0d„t‰â¦¡šÉÍ�R˜Â%Ž¶Æ´ ÔM=bD‚FPS4™½tS¡¨ ¡�R˜*s¢F4d„(åTÈà†ÎuEn´##¦s�7¸.¹—JŠÎuëZ纵ûØÇ„ë78ñS»Œý[¶Êfrƒ‡B<"Ó�úþŠbƒ<åá° 2Eûo„13ä‘7 3ä¡EeΊ ’iòPˆ‡B¤icf0Èu�G£ƒdJ]DLÃàÑ(éz]Jô¾]J-ëC‰Ë�jÒuâæà^W.bŒt�L“L“t� ]ꘘDÄ9çzÔd&—iß0FzÔ4tÓÐM=jJF±—ir=jêQ©ë�¡›FÔäœëS�š\â*©˜L�Üú»2LS‘úû5 Ó49çÜ4LÓà’¿gÎMóÔ—Ì>È´®Á©U'έϕzvêC ]×Ã!ÝåâuC�FõhÔÔu=Ö#™]ˆÈ4 =1t]�„‰IuUÓÃ!nš†Õ#aÕ¡�¹cL‡ ]眛ºnºdñ8·~Ë:çÒÍHC7 ƒ1n:q¹Áºnš#²Š's¸aš†Fœó¨É£rÍ!“ñ¨I&·! Ó9�šdJ¼�B…1Ã0B|ðT¨s®Ët:8×?¸‹ä•ËàÜ8µ‹ä,ƹιyjGù] ÎM3ÁâY»Ç9Úè;sÆ”B¯cåÂé,ÔËT©£oš|ÞŒÊs… ùk¹É½.õ´YE~ò†¢†" 1ÖÞ^<»*Ç¥ž³d "Ï-s M“»�jE©wvÀ£H7‘ƒ£¢T�(�Ït¨R¿cƒóËq‘+ÇT™B$Õˆ78÷ºXi>E(Ç©*’‘PØàeŌ柫 ô�*ÑOcõ÷ЬÅäÌ¥ÅgP^�Tüa]+¦)R}þáoAD¤¹hî9äô‘Ë# äÌ¡ªÙ´èlrº¥ZçŠF½=T]C)}äðJEBzˆJæPi5ÍZBîârrÓ¤ÊÙ¤…È4¥chFF„*jÈË( ™¾Sá^ªœµ ÄVÎ[Yâ)—‰„LÓô:½ÕåÕËç,÷¨ÉH¨#Ø1·jn§ÚÉs¸WóÊìe˜F¾+Aá"žc†õ cR#º©»‹­ÑE’'Šá¦Ì*ªÑªCîìĈEy´2§rŽ6/ÂÃLâ”­�2`ôWû¦‡)Ôã^èQs¤Ž 7òùÎÊù• ÜJŽd$å‘9³*Ï>Ëlë ‡C®õÄŒ¾¾¼Å‹#.OØ›£xÜRùg¿ß{úéîY³ôU«•å¤KÔv…™�œêêʳÏ6ûú˜T$¤è�… ˜}ýž•+µÂ™> ™†Q°x1ÍžÍòrIî7KDÄMÊÉ!"©¿©ÑLŸN1¹ÛJ £@�*«¨ßOyy¤I•1 iÆ *)¥ùó)7Wæ˜0N¤ò‚Šòù§õjªl$ ê3ç•ú‹ƒyšSê¤Í…ƒúŒùEÜ$Íðº<šd$”[誘V2ÿôî§L[\Q˜¿7\Y]Šs\yš&U<=jUäz|.£_uº¥ÊFDœóœ�5‚þ°"Y7E#z^q±ªSXõŒÊP($ó¹ŒQ(.«ðÌ^°0ÚÛïñåÊ(FT÷•”V͛׿ìC®¯t$dºóòfΛj=©:�R‘�¢ù»:*§O¯Y¸ØÉÉéõÈDB!¿ö‚…e••3æ-pçú8—�„Ê**sæÉGB†®ûJÊSò|9Ò¿e¦‡CÅ3f)Šê0¢šKª�Á SËqz*K*–LWÜrm!…™}wu91¹ª>šœùÓÈ£:\2§2û£îY•äÕ\§…Ô<—Ô·39s¨ÎY¥¹Ä´j¾5ÝÕä÷ÉFBDÖ©²œ‡á$—Ä…ˆ “rÝTžç™U-» Euª(Vƒ £$׆'Ó$�“_®Ä�$ Csjjªªª–,^œŸŸgHÞ 7ŒY³fùûû%¿�õÁàôéÓsr¼�€*÷çÑõÊòr—Ë …\.—d$¤¨jAAÁôéÓ¥»„,‰TT”»=îÜœM•ús69×4Í—““ëó™²Ç�›œœNw~Þüpx¥¦ù$#¡H¤½´lF0x¦ WUÝcïBj4ÚSZz)ªSæëŒHQÈí®œ1c%#Sä.CfÄ—»DQ=99!MË‘Œ„E++›1cƹLîö3cŽpxzEE�ÃQævw©ZŽLßÓ4#nw•ËUE|�*wð‰ˆs#7gV(ìÒõ€dWˆˆ™F8¿`FÐgD¢%Š"óÍ =�—7KQK[(Y7¬âiÅ)ã/‡›dÈ�˜Uö÷û¥ "…(±ûƒMC~€#R´Nñ$Èp"éQÐIì"öŠÝ�ÝK LvÜ'ÒbDQ3±A.•#ó-Ä!‰‰Ý-cD…"¦d°6ø�¬�);B‹ˆ‘ˆò»‘Â$û¹C÷â �Ód¤)\d ’‡�“ÆH'Ùcg}s•‘ÉÉÓFå>h°&%îpJ®=[¯ŒÄ‡„2.;X@…ˆ¬c’ÈŽ,‰](ª$d”ÎÅàGKèû]éo÷þçJ¦øÃ>+¡¯9óé&¬`Œ™‰œi8'‡“ä†<Æ|ŽBššø7úÀçrÓd†AJ"_ÔŠ&äî¼Os�Mä˜94"–؉¿˜K2¡LIø ˆ+xB”ÑG›ôôô¸Ýž±"!°Cooï�wÞimÿ×ýWqqqºK6«««³&•̘1Ããñ¤»80Iû,ÕÔÔ ‚Œ;"Ǧ$Œ‚1a”ØHœs/BÒЀŒ�¦d4Œšò0JÆ„QB`#qÎA¼IC$MÈh±õ‘Д„QB õì‚[e�:DB�1Ðô�Œ[o1qlJÂ(!��zvÁ­2Hš#�1Ðô�Œ†‰cSF€ Ô° ÚE�:DB�10J2&ŽMyý2POÀ.hAê @ÆÀÝ0Èh˜86åaôÈ@=» ]©ÃCè³Ô† ž{î9"Z²dÉm·Ý–îâHÁÝ0Èh˜86åaôÈ@7ì‚v¤w¨²š,�‰Ðô�Œ†QBSF€ \ËÀ.ˆ!uhŽd)4Y ¡é k My¸å2p-» ^„Ô!ÊR¸@&BÓ2š¨·Èƒ¦*Ür¸–�]ЧƒÔ!ÊR¸@&BÓ2š¨·˜56Ua”ÈÀµ ì‚R‡I–Â¥2âlÈh%4å¡{2p-» ††Ô!ÊRˆ„ áÜ ‘Д‡îÈÀµ 삺©C$”¥pú€L„;«�Ñ0qlÊÃ(!��zvA»R‡I–Â],ÈDhFCFÃ(¡)í+��zvÁm~H"¡,…nd"4£!£!šòоèÆƒ]0JR‡H(KáR™MÈh¢ÞbâØT…ØdàZvAŸR‡I–Â],ÈDhú@Fõ£„¦*´¯@®e`Ä‹�:DBY w± ¡é Ǧ<´¯@®e`Ä‹�:DBY §ÈD8wAF3 ÃÚPU5Ýe�q�QB ×2° âEH"¡,…Kd"4} £éºnm8Žt—ÆF � \ËÀ.èÓAê e)4Y ¡éMDB𦥻,0.0Jdˆ3€8'$}:H"¡,…& d"ÜY…Œ†HhÊSÅ$A€áÄ8ADB�"ôé uˆ„²î´@&ÂÝ0ÈhÑhÔÚ@$4U‰®¾ø] 'Ψ'�"´‹ uˆ„²eÈD±ÏiBë2F Myˆ„@†¨'‘H$Ýe�̆>¤‘P–B¢ ™(ö9M˜—GÄX^zªB$2POÀ.˜¬ ©C$”¥0q 2QlG-iÈ8%4å¡«2°–ØçH"¡,…H2QlG­È8ˆ„¦ áÜ™ ‘Д‡®>Ȉ=`´>¤í"H"¡ì…‰c�‰ðzÈPœsÓ4­mDBSF � La椑PöB$™wà CƒA±»â L18GÁ˜\.—ØF=�áV¤‘PöB“2Î]�¡B¡�ØŽíÂ#ÎQýqˆn|8NwY ³¡])B$”½0J2º[�¡b;~n·;ÝÅ�ñ"ÎQèêC�ÇÚˆ?�Lƒ!Ê^¢É‚H2HC†Š%$zƒ0õˆ!`±¿q€! �]Ð.‚!Ê^H” !Ά %ÆŒhšÆKwq`¼ˆ!`èêC"Bt)BŸR„H({aâd"qÝ-È,¢ã‡YcSF€ D‡`ѧÃdUH"¡ì…;í�‰D3MÈ,¢Æbmé© £?@¢C° ꤑPö CÈD¸³  £„²ÎQ Ýx° ꤑPöB$™wà!Ca”P–@÷ d ž€]P— Eˆ„²&Ž@&wà9ç8}AA$”%Ä9 ±5Ä�Û`ŒL„!Ê^h²@&Š}z7Z?�A0q,Kx½^k'(ˆÝx° F AŠ e/Ñd é. €¬Øî4mÈ %”%ÐÕèÆƒ]P— Eˆ„²—ˆ„"‘ç<ÝÅ�‚H2”ˆ„0JhjÃ(l��n<ØEÔ%Î9Æ I@$”½D$DD~¿?ÝÅ�ât:cÖ6z\�ADuÅ(¡© kÄ€ DB`L¨‡!Ê^±‘ÐÀÀ@º‹ -iÈD%”% � QO0¬R{MA»’€H({!Q€ %Z?hICÁ(¡,ûTD¤B0tãÁ.èÓAŠ e/Ƙ8ƒ`…iÈ %™£„²Dl÷ ‘ŒÝx° cLÜi@]‚$ Êjbî&Ž@Áê­�‰0J(K «2POÀF˜¯ ©@$”ÕðzÈDˆ„ á!ôY£„@†Ûí�J@$)í"Ô%H"¡¬&"!œ> ƒ`âd"‘`âØÔÆs:�Ö6ÎQno€]Ð.‚T Êj˜8™ÍhÈ8Ñh”snmc”Д‡îÈ@=» .A* e5L€L„9ó�qbë*F My8G� ÜÞ» ‚T Êjˆ„ ¡ G,$D%”pŽèƃ]P— ˆ„²"!ÈD¸£„² ºg Kƒ]P— ˆ„²"!ÈD¸'¶™ŽQBSºg Ñ!Øu R�H(«!€L„îdñŸÏ—î²À¸ÃHF�‘““cmà1/�"DB� DBYMœ> @Á(!È8¢Ë':�0…‰öºúG^^žµÑßߟî²@fC$©@$”ÕDÃ4 ЇãLr¢é»d/Àd†H(«ˆß2ºú‡3Ø××—î²@fÃèiH…–î@:‰nƒA1�lrâœ?øàƒµµµ+V¬øìg?›îâLM;vìX¿~ýœ9sî¼óÎt—…þû¿ÿûÈ‘#_øÂÎ9çœt—%�=öØ–-[bÂ{à�$w?tèЃ>¨iÚ—¾ô¥™3gNdÉ¿÷½ïµ¶¶ÆþdáÂ…wÜqÇD–ARìÉ*Lòsùý~k‘P6ÈÍ͵6 A¨'`qe×y%”ÕbûQ“ló‰'Þzë­H$²eË–®®®t`dåååóN™={v¢»oÚ´©···³³sóæÍ\òY³f‰’———Oð§'D4£ -iÈâ"[{aªÂ„ �!ê F AŠD]"¤B�8ŒÊj±÷*'l9¡?þñ�qúºÕÕÕßýîwGü§âââÜÜ\¿ß_TT{â›`�>úè¶mÛ¾÷½ïUUU¥« 0™]vÙe—]v™µÝÙÙy×]w%´û¬Y³víÚemÄå]wÝ …~þóŸË¼­L½½ùæ›Åö¶mÛ}ôÑ´@C"¡I`&ŽeqŽÂZ�Gìbó~¿y1$-¶ò .A¢ e5MÓ4MÓu�&¼Õâõzóóó‡ÿ¼´´t´]<Ï÷¿ÿý£G�Ι3GÓÒVu'ÿp*Èhk×®�;w®ªª555ñ_Ev¤çÔ«·V@L¸DzJlW¿··wÄ@l=éïïG7’6¤.UTT¤»D�I e;ŸÏ×ÝÝMÞ­:묳’X(//ïôÓOŸÈr7õºÖ0ÙÌ›7oÌט¦ åû–S¯Þú|>DB�A0q,«ÄŽeîïïG$#нˆ÷÷÷WVV¦»D�Á|>Ÿ5Só!QXK(Û‰VKoooºË’²a¸üØH—DëáÔ«·¢_�H2‚ˆ„°z6ˆý-£{q`Ù)°‹(„º‰Â(¡l'î\MÂHèøñã?þñ�‡ÿ<Îó°>üÓŸþôòË/¿öÚk‰èСC¯¼òÊÑ£G{{{].ײeËnºé¦Ø×÷õõ½üòË{öìioo7 ÃçóÍž=ûì³Ï>óÌ3UU/{á…žzê©ØøÃùèŒõÜsÏmذá«_ýêÒ¥K Ãxë­·¶lÙÒÜÜl= îcûØE]4d—ÚÚÚ-[¶444�ÜÜÜyóæ­^½z´ÙFœó={ölß¾½¡¡¡¯¯ÏétVVVž{î¹ç�w^ì7µ8�N"êìì|þùç÷ìÙÓ××———·lÙ²�üã±ÍÜýû÷ÿâ¿(**ºû¼ÃÎ�;|ðÁ!¿)ëqfŸúÔ§V¯^ÝÑÑñ /Äyó8víÚõ›ßüÆét~ãߘ1cFÒÇ\×õwÞyg×®]V q8ÕÕÕ\pÁŠ+’~Ï$¬[·n÷îÝÃ~ûí·/Z´hÈùË_îÛ·OüçÀÀÀ—¾ô¥!¯ùïÿþoëïzê-õ÷÷¿üò˵µµíí튢”——ŸuÖY_|±Ëå×ㆵ@fÙ%F e‰üü|«e…Øâðù|Vhˆk¤HDB8ç@¢ e; M»X.—kÈ šþþþ–––1w ƒ†aüå/yå•Wˆˆ1¦iš•­Ä¾¬¥¥å§?ý©ßï÷ù|555‡£««k×®]ï¾ûniié7¾ñ�¢¢"ë•gžy¦µ][[kÆ¢E‹Ünw컦þ•ƒÁ`ÿ¯ý뺺:"²Âšþþþ‚‚‚Ø—E"‘õë׿ûî»DTRR2mÚ´ŽŽŽ·Þzë­·Þºæšk®¸âŠ!oë÷ûzè!+Jp»Ý‘HäÈ‘#GŽÙ²eËW¿úÕ!cÚÝn÷{ï½÷ÐCƒÁ¼¼<ÆXww÷ßÿþ÷£G�Þyç�)Ž!صk×Ã? …’xóÚÚÚßüæ7‡ãë_ÿz*yÐo¼ñ׿þÕªöâÖÕÕÕÕÕ?~üºë®Ký·)iÚ´iC†ð444X+| 7oÞ<+j ƒPUuøTJ‡ÃamL@½Ý»wïƒ>\.WuuµišÍÍÍO=õÔ¶mÛn¿ývñ4p7 2H86 ÃÚÆZBY"77׊„pŽ‚8ĵl¶Ã!³ .AÒ e»ÉýÿñgΜiýpß¾}=ôÐÿýßÿMŸ>}ñâÅâź®ÿïÿþï±cÇ ®¿þúÓO?ÝJšºººžyæ™ÆÆF�Ç3¤ÇŽÛµk×òå˯¾úê‚‚Ó4·nÝú‡?ü¡¡¡a÷îÝ"eHÎ;ï¼óüóϯX±"‰7ï½÷xàMÓn»í¶$žï«³³Ó0Œ«¯¾ú¼óγ†m›¦ùòË/ÿõ¯ݸqãÙgŸ=æ¿ìrõÕWùÉw¾ó�ööö_|å•WZÖ:·Û}Ë-·ŒöÎã]oÖ­[§ëúUW]u饗ZƒË�ÀŸþô§;v<øàƒ£�æ³&ŽA‰]Ì ‘P–@l 2POÀ.˜„IC$”íÄéc‚å·ß~ûðáÃC~xî¹ç®Y³&õ7ß»wo0´&(‰–”” yÙÉ“'‰èœsΉ}~Yaaáõ×_?‘‡"Ö3ÏVGGGìÑSå /¬­­}ï½÷öïߟb$ÔÜܜܛïÝ»÷�PUõk_ûÚœ9sR<ÔW\qÅêÕ«c‡Ì(Šrùå—744¼óÎ;;v옰H(s=þøãV$‚*"òz½7ÞxcSSÓ‘#Gêêêd–ÊNHCA$”…ÐÕ¨'`L¨‡¤aÙl—®QB�@ y»Ê /¾øâØ|XÌP ¢-[¶Ñ•W^9â¡áyÍž={øÑ³Rëùt©HîÍ÷íÛ·nÝ:ÆØW¿úU[RMÓ†L¡""Ã0¬ÉhÍÍÍ©ÄÔÖØØØØØèv»/»ì²!ÿ¤ªê‡>ô!":pàÀøMÈ "RUuij.L=˜Ä20²삺IÃ(¡l'"¡ ¾Ó~Á$ñzI^¯÷šk®óeŸþô§[[[�=ú½ï}¯ªªjÞ¼ysçÎ]°`Áð¨eÂ,^¼ØêKÇÑÐÐ@D{÷î=vìØðUU5ûý~ë7Û×××ÙÙIDb‘Œô±K$Iñ;&ñæ°&(•””Ø;x'ïß¿ÿàÁƒ'Nœhnn ÷P(dã§LIV=Ô4í¡‡þ¯Vº7®A3&ŽA‘�ˆ `Êm œ£ D‡`Œ8ƒ¤!Êv±ñGOOÏ�eŒ3”Ûí–¹ [TTôÝï~÷Í7ßܱcG]]݉'6oÞÌ›7oÞå—_¾téÒ‰/ù�ežGd-El­?=ñ1Ñ‘yçøcã÷Åã¼ùÁƒwìØašfMMM}}ýÿøÇ!��KN$yî¹ç6oÞ …JJJjjj-ZTTTÔÜÜüüóÏ�ß7�2¬zè÷û­eÎG4üav6MŸH$bƸ~@ŠD(€YcÙ#Aê ØE´‹b§*È@$”íb3 ÞÞÞ© ÉSUuåÊ•+W®Ôu½±±ñàÁƒo¿ýö¡C‡:tíµ×^~ùåé.à¼^oÿ¿ýÛ¿�ö¼ùXbéèá�-K�•ãpÎÇõûnÙ²EUÕ/}éKóæÍûá¸}ûöÓN;íì³ÏNñmï¿ÿþ}ûö-\¸ðºë®›>}ºøùÎ�;ÇõëØe\:Öóû/^üõ¯=-ˆ}’wooï¸>Ý E¢�ŽH({`ôÈ·f­§åâö$-vjoooê7ƒ!{`-¡l§ªªx4û$|èØ„Ñ4­¦¦fíÚµßùÎw®½öZ"zñÅG|¥ÕMcoÍœ²ž(?¦üü|ë’°gÏžñ8h4Ê}­ØÅŒR¤(Ê¿øÅ3Î8ÃëõþÓ?ýcì±Ç³fÃ%­®®nß¾}eee·Ýv[ld;ÜØœYõ0H¾s¢õÖšßçÍ­zxäÈ‘p8l÷a“;Âó2`’Ã(¡,$ºgápØÆk"L1±Ýx¤‡�ŠØº„v$‘¼?¥(Û.E£=çÛZvÇï÷ëº>ü_­Á#®ã31Î;ï<"Ú´iSWWט/fŒ­X±‚ˆžyæ™#¿T²k½j]×�?>ä=Ÿzê)»¾ï5×\#C¶`Á‚K/½4 >ôÐC¦i&ýžÖ¯¾¤¤døí¸ÆÆF»JN§ºœóÔ—å"??_Qιd�­·VÉ;::F{ÁÌ™3«ªªÂáð3Ï<“háëëëÿó?ÿó»ßýî¶mÛ’>ªªº\.kãía’³&ZÒG·ÁÔ6äŽ}º‹“Tl=Áµ RáñxD³u ‚HÞ¿ÙžUM–÷Þ{ï;ßù΃>ØÐÐ;ÂZb†ˆfÍšûpzÁz¾ûK/½Û»öûý2�-Î<óÌ¥K—�Ÿþô§µµµ±…?tèн÷Þ;ä÷¸víÚâââžžžŸüä'µµµ"Iéïïâ‰'¾ýíoïÝ»7¹’—––Ñã�?nEœówß}÷î»ï¶q‰î!óÝ®¾úêêêê#GŽüíoKú=­b>|øÐ¡Câ‡���¿üå/_zé%»JND.—Ëz°ÝÓO?m%Œò!N|Œ±… Ñ“O>æ¶µµ�8l'Ñz;{ölUU;;;7oÞlý$‰ yÛg>óUU7nÜøØc�ÅÖº@ ðì³ÏƉŠþð‡?466¶µµýîw¿ëééIú `…iÈ%”…pÇd¸ÝntãÁ.¢ù�m·ù!EXKÒöúô*--]²dÉÎ�;wîÜ™““SYYéõzC¡PSSS ÈËË»ñÆGÜñâ‹/~ã�7š››ï¾ûîÊÊJ·ÛÝßßßÑÑqÞyçýã?þãÄþ‹_üâúõëwíÚuß}÷åææVVVQ[[[oo¯ªªuuu±Kíx½Þ;î¸ã�hjjºï¾û<Oqqq4mkkãœWVV¦²ËUW]õàƒÖ××ë[ßòù|áp8‰|èCºþúëï¼óÎñøîš¦}þóŸÿÏÿüÏgŸ}vÑ¢EÖÓë5oÞ¼åË—¿ùæ›÷ÜsOeee^^^GGGggçœ9sî¸ãŽÿùŸÿ±±ÀûØÇî¿ÿþ;vÔÖÖwww�Ÿüä'©Ïñ¾öÚkëëë>|×]wM›6�ˆz{{{zz>ÿùÏ/_¾|È‹­·¹¹¹þð‡7mÚôÇ?þñùçŸÏÉÉioo/**úÁ~ ^3wîÜ[n¹åá‡Þ²eËÖ­[«ªªrss�À‰' Ø1cÆG>ò‘0'ÆßqΟu¢CWâ@7ì‚çÐCrØx?-&¿�7þå/!¢šššû·Kwq +<ýôÓÏ?ÿüÍ7ßœô(¡GydûöíDtþùçOØ=€$Ü~ûíÁ`�ˆ¾úÕ¯.]º4ÝÅ� òãÿØZkïºë®»ì²ËÒ]˜¤î»ï¾ÚÚZ"Z½zõ§>õ©t2ØÃ?üÆoÚE ­¶¶¶¬¢k A–®%éuøða"ª©©Iú°–dÓ4­<ˆ>ø¤<˜òpÇdXë "ÛŸDÙçH"!xâX*ë¼ÈÛ½{w]]ÝâÅ‹­¥¾“#z×húÀdÛÍK}1È âñh_Aˆ„À.hAr°–¼ßB5 # &·2 € ÎùöíÛÿð‡?~ö³ŸMå­Ä¹ Íh˜ÌbGày„"Lmˆ„@†¨'¸–AŠ Ar ÁnZööö"€ñó·¿ýmÆ UUU_ùÊW†/…žьƤW˜ÌÄÒÂX[:Û «2Ä(!D‡�"qÎéêêJwY “ òz½â9©½½½é.LYgŸ}vQQѹ瞛úc×bG8úý~ô·ar‘%f�etõA†¨'DÔÓÓƒ±„�4Q— ÃèïïK 浄€ˆ¨¨¨ÈÚÀ�,Wååå+W®L=¢v°Ñã‚I ‘PÖ}ûH$"–"6B;R1$^Lwq c ¢˜H¨££#Ýe�âñxœN§µ�¹c0i‰‰cxÜX¶A÷ d8�N¯×km£ž@*bëâE�‡HˆˆÄŠ���é. €,,Õ“F e-ŸÏ§ªªµ�sÄ�‡Ž�]°ª=$‘!€Ì$úØ%“–h—c‰�,$ÎQèžAb´>V†!^„$ "\Š 3ánL~¢];�²F2‚ qk 8@аª=$‘a”d&Ü ƒIÎ4Íþþ~k£„²�¸å†öÄ�v8ØE´‹p›ä!¢˜&‹iš• S é“\ì%‘PB$2 �]D»u ä!¢˜Ká ™Ct·0J&§ØHDz�h_!¶†8D=ˆF£é.d0Ä‹�DB@D¤( žCGœ¸�@$Iwq†a%7–�°F Ȉ½5ÛÖÖ–îâ@uÉ4Mºg0ŸÏçt:­m\Ë ˜ùIÐÒ]˜,JKK8ä‡ÿú¯ÿ:wîÜtmÊB½�H¹¹¹N§ÓÔÕÕUYY™î|"¡,'î·QWW‹ÁhŠ‹‹››› Ê 5Š¢X—žÎÎΚššt—2F Á ²²2k£„¦ŸÏ7/†¦!†©+LÃd†H(Ë9�N�Çcmãq`Ž!Ø¥¤¤ÄÚÀ(!�„þ! 7²&f”ÐöíÛyä‘믿þâ‹/N÷WŸÊ.\¸páBñŸÿþïÿŽ&)L1EEE­­­„îLJb-!¬-�µŠ‹‹›ššÝ3ˆ «ƒ]Ä|U´‹@F Á 1JÈï÷OÀÃ�@º¿1L%“™¨–±ë^AVÁã;@†Ù�Ñú�"Œ8ƒD!‚AåååbÛšÌ<®Òý�`*Ài˜ÌÄ(!DBYKœ£Ð=ƒ8&x´>LahA¢ Á ‡Ãáóù¬í ¸A‘P$¤(¨¨�yPo'ÛäÕÓÓcš¦µ�H(k!"ŠF£x8¤ñ"$ k ÁûÊËËûûûiÜÎ õõõ?ùÉObò§?ýéOúSìO.¸à‚Ï~ö³±?q8Œ±p8üòË/¿ùæ›.—kþüùW]uÕˆOÒu}ëÖ­o¾ùæÉ“'£ÑhQQÑi§�vÙe— iŽ[�…:ãŒ3n½õVù7Oˆßïß±cÇ{ï½wòäI¿ßïõzçÌ™³fÍšÙ³gyåw¾ó�¾¾¾ÿýßÿíééyñÅwïÞÝÓÓãóùN;í´�~ô£CV Ø±cÇúõ믻îºË.»ì­·Þzíµ×Ž;Æ9¯ªªºè¢‹Î=÷ܤ |ðàÁŸÿüç%%%?þñ�cÃ_ðÓŸþôðáÃÿò/ÿ2þü¤?% ½ôÒK;wîììì4 cȿΘ1ãÛßþ¶øÏÿùŸÿ9pàÀ½÷Þët:�Àk¯½¶sçÎöövÃ0òòòn¹å–™3gÆîÞÞÞþòË/ïÝ»·»»ÛétΜ9sÕªUçœsÎ�¯óôÓO?ÿüó+W®¼é¦›†à±ÇÛ²eËÚµk¯¾újñÃßýîw¯¿þú·¿ý튊ŠÍ›7ïØ±£µµÕétZ¿Ð9sæ ÿšªª2Æ_z饃Á’’’åË—_vÙeâA³D´aÆçž{nÄ’XÇê_ÿõ_c?ýéO].WÒÇ|jÝ­®®.Ó4‘ÄÁä;™k e-Ñ=Ä ˆ#v´~{{;NIí"Ó4»»»qõ�1!‚÷UTT>|˜ˆZZZÆãýsssÏ<óLk»¾¾¾···ºº:öù¬D4¤‡OD.—«­­í¾ûîkiiÉÉÉÑ4m``àÝwß=xðàw¿ûÝ!AO{{û}÷Ý×ÜܬªjEE…×ëmnn~å•W^ýõ[o½5v¡eËÀÀ@KKË}÷Ý×ÖÖ6æ›Ë3 ãé§Ÿþûßÿ�F5M+))ñù|---»víª­­ýÒ—¾´lÙ²!»„Ãá­[·>ñÄ‘H¤¸¸¸   ³³sëÖ­ï¾ûî¿üË¿TUU y}ssó/~ñ‹ýû÷»\®ÂÂÂîîîúúúúúúÇÉÔä-X°`æÌ™ÇŽÛ½{÷ð¶¶¶>|¸¢¢"•<¨¥¥åç?ÿyoooIIÉÙgŸí÷û÷ïßoš¦Ï竨¨ "ëÿ ›››ï¿ÿ~k †Ãá0 £««kHåyóÍ7}ôQ]×�NgEEE Ø¿ÿþýû·oßþ¥/}Éív']lËÞ½{xà�ÎÎÎÜÜÜ¢¢¢ŽŽŽÚÚÚ÷Þ{ïæ›o^¾|ù�«ªú·¿ýmÆ Š¢XÃXZZZ6lذ{÷îo|ã¢0—\rÉÆ�wîÜyÍ5×äåå y“�;wF"‘ .¸yP¢éCD���Cj@‰H(??aeÖB÷ dX£õ­[³ííísçÎMw‰ S+Šb�QmooÇ9Æ„HÞ'nPX�ï±]YYÙ-·Übm¯[·n÷îÝçŸþ˜OÓuýg?ûYQQÑ]wÝeFuuu<ð€ßï饗®¿þzñÊ@ ð‹_ü¢££ãì³Ïþä'?i=ñ×4Í¿ÿýïO>ùä¯ýëþð‡brœø¦?ûÙÏJJJÆ|ó„¨ªÚØØX\\|å•W.[¶ÌêÏ�Ç|çÎ�¿ÿýï/^;TÄòûßÿþÌ3ÏüÄ'>aý"š››ûÛßž8qbýúõßùÎw†Œsyýõ×sss?÷¹Ï­X±BÓ4]×_yå•¿þõ¯[¶l9í´Ó†:’Ö¬Yóë_ÿúå—_þ[·n%¢ /¼0•:ðè£�öööžþùŸùÌgTU%¢¦¦¦Ÿþô§Ñhôæ›o-ƒkll|衇4M»îºë–/_žŸŸ¯ëú±cǼ^¯xM]]ÝÃ?lšæ•W^¹víZëðÖ××?ôÐCûöí[¿~ý­·ÞšJɉèé§Ÿ®®®¾ãŽ;¬lq``àñÇë­·~ÿûßÏŸ?Ȧ#‘ȳÏ>»fÍšK/½477—ˆöìÙóàƒ;vìÿý¿ÿ÷éOÚzYnnîªU«6oÞ¼yóæ�üãC>qË–-DtÑE¥Xò©­  @4} Á¤‚µ¥�bF QGGºg0šÒÒR ¥»,�ÙJJJ¬9ííí©ÜÊ…,�{Vð>1@c–—– …æÎ�û¯ÿú¯bѼyó®¸â ":pà@ì+Ÿ}öÙŽŽŽÅ‹á _ýsEQV¯^}ÑEYÓŽ†¼yÿüùóeÞ‘E¬ÐÔ××7üQîV Û´²Æ‹‰ÞZ¬D—a²~Aqf½Åêìììììñõ ,˜9sfmmmWW—¢(˜;wneeeªG<;ˆH¨¿¿wVa’°žöb‰?õ¦<Ñ=Ã9 âÈËËs»ÝÖÚmmmˆ„ iâ‘ADÔÖÖ&ÙL…¬…å¥á&ì¡cÖCX�=jתªjeO=õ”ü,žqbÝŒ=["‘Hœ¬møl¯·ß~»­­Íét.]ºt̆ñÒK/Ñ©µ´Gd f‘I±zõjUUÿþ÷¿×××·¶¶._¾<õ; �@`×®]555ÅÅÅÅÅÅK–,Y°`AêyUWWO›6Í4Í 6 ù§––kaik‘i‹uǾ±±qHUyï½÷†¬ñÿ˜wvv¾ù曣óá¯þù牨¦¦fÄ!W\qç|óæÍ6ËʱÏxï8@’•ÿ‘Ãá°åD™kÈè�t&/ñ¤\Ë š¦‰[¤¨K0&DBðbTB{{û¸+‹/&¢÷Þ{/ö9PÑh4• kýèG‹ŠŠêëëñ‹_455‰Ÿ[#ûÍo~“âò@ò¬êöíÛÅB¦iîÚµëÇ?þñ¡C‡FÛkýúõ¯¾úªõxrÎùÛo¿ýè£�ÑW\1|ÔOccã¯~õ«'NXÿÙÛÛûÀ455ù|¾K/½t´�°u~饗ÄJÛÍÍͱD V¬XÑÐÐðä“O‘-ñ„ªªŒ±£G�ŽÇ´ÄO}êSŒ±-[¶lذA|£úúú_ýêW¦i.Y²$6V³îÖßßÿÄOX/‡Ã/¼ðºu몪ªFûˆM›6ýå/ãkllüÅ/~‰D,X°dɒ᯿ûî»÷ìÙcÕºH$òÿ÷[¶laŒ]}õÕ#¾ÿ‡>ô¡²²²-[¶lݺ5''笳βý(MU999"²Ä¼ ˜$Dø@vÊËË«âq »ˆKOì�U€aâ|@l¸¥¥¥ººzœ>¨¦¦fåʕ۷oøá‡7lØPPP::: ô£%÷ž¹¹¹·ß~ûý÷ß_WW÷£ý¨´´´¨¨(�žþøãþóŸKKKûúú¬iMçœsΕW^9üÅUUUMMM?üá‹ŠŠœNgkk+çÜãñ|ùË_Žó¨¸Õ«WoÛ¶íäÉ“wÝuWyyy èîîþâ¿8âÒÎW\qŶmÛf̘aˈS—˵fÍšçŸþûßÿ~EE…XGUU�ÇSQQqÖYg%ýA‹-úÜç>÷‡?üá¹çžÛ¸qcYYY °÷³`Á‚/~ñ‹±/.**ºð _{íµ×^{mË–-yyy~¿ß4ÍO|âååå÷ßÿˆ±`Á‚¿ÿýï/¿üryyy4µ†TUU}á _þâiÓ¦¹Ýî{ï½×ëõtttXÙÓõ×_oRÃ1Æ.¿üòÇ{,\vÙe’«¡ƒ¥´´Ôz¨º[0IˆQBâ‰x�ͦM›f�£Ð=ƒ8D$„z)²žYL¨K ]ø—Ëe=&œˆNž<9~‘Ýxã�3fÌØ¾}{KKK___~~þ‚ N;í4ÎyÒëC———ç;ßyã�7vîÜÙÔÔTWWçñx¦OŸ~æ™g®ZµjÂWóx<ßüæ7_|ñÅ]»v555åää,\¸põêÕ555qF }ô£]½zõÆ�­3øÜ¹s/¼ðÂåË—�x4 ¿ùÍo¾ôÒKo½õV{{{aaáÒ¥K×®]kÍÈMqqñ�wÞùÔSOÕÕÕ577çåå­\¹r´˜¬¢¢bÞ¼yuuu6.r¼`Á‚M›6y½Þ®®®!c…vïÞýâ‹/ÞtÓM+W®LîÍW­Z5oÞ¼�7îÛ·¯¥¥Åét.\¸pÕªU#Ãn¸¡¸¸xûöíííí†a,Y²díÚµ555ÖzL#Z²dÉ?üÃ?üíoÛ³gO0œ6mÚòåËW¯^=âCÁªªªþéŸþiÛ¶m[·nmjjr¹\‹/^³fÍìÙ³ã|…sÏ=÷Oú“®ë˜5–(DB0ÙˆQBXHˆ¨¬¬ ‘ŒIDB˜`)B¼ò ÁP"×bŒ]rÉ%—\rÉh/(,,üõ¯=â?�öOš¦�þùçŸ~üOOîÍååææ^{íµ×^{í�ŸÏŸ?´7‡Ã‹-Z´h‘Ìû‡Ãa¯×{õÕW�6 i4Ó¦MûÊW¾"óÊ@ ÐØØèñxâ,N”�·ß~û·¿ýíW\qõÕWÇf4‘H¤»»ûé§Ÿ~ç�w^}õÕØHèŽ;îHè#JKK?ó™ÏȼRQ”5kÖ¬Y³fÈÏçÍ›çTVVvÓM7ÅçØªuÁ\pÁòå?|ø°®ë .ÄL“Dá�>0Ù`”ÄÂ9 dˆn|(Hñ²�ÍÄ9ñ"Œ k ÁPÓ¦M³6p™` =Ý–¨Ç÷ì³Ï†ÃáøÃ.—Ë–7|î¹çE’‘Óé,//·&¯Mæç°$ú0²$Þÿé§Ÿ&¢áAŒ Ý-˜lÄ(!DB@¸crD#œ°œ¤&6^ñ»"!J¬0�H(k™¦ùÒK/mÚ´)???ÎbÕ‰r¹\†alݺuø?:tè¯ý+­^½:Ýß>=�Àúõë�=ºxñbÉ‘bKDB]]]˜ħëz °¶ Å,õÚÛÛ;üy”MÓòóó­m¤‡�Š!Ï¡Owq`RÃÄ1JDBmmm©,ë™hýúõ'Nœèìì ƒ.—ë–[nþ°³¤]uÕU÷Ýwßc�=öÌ3ÏÔÔÔäæærÎûûû›šš:;;].×�7Þ¸lÙ²tƒ Å9ÿÁ~ (Jkk«®ëeeeŸÿüçÓ]¨Œ$–k1M³³³3ö±ô/v­4¬%ôÁϵµµÅyº%d¹òòrë±°%©°âE«.µµµÕÔÔ¤»D0y!‚¡Ä˜UÎùÉ“'ÑjÉ*'Ožlnnöx<+V¬øÄ'>±êD-\¸ðûßÿþ«¯¾zðàÁƒƒAUUsss«ªª.½ôÒ+VdáœùŽŽ¿ß V¬X±víÚ«†1+Šb�jooG$é%r8^¯7ÝÅ�ôóù|n·; "!ˆ«¼¼Üz Fv@ŠÊÊÊD$”î²À¤†H†ÊÉÉñù|ýýýDÔÔÔ„VËøñ�,ÿâ+V¬X±bœJò�ï|g\¿iIIÉð%·'¿Ï}îsŸûÜçÆã�KKKö³Ÿ¥ûûMåååÖt×¶¶¶Å‹§»8�Õººº¬ ¤“ TVV644Б !æû`HQee¥õ ]œs >¬%#1Ðx?t À.b%E ¶‡´ËœcÖbîÎQ‡ˆ„PO E¢.!‚ø ÁD$tâĉt—@ º[0yˆÇ�a”èêƒ QOt]g€$àœ’ ÁÄrBˆ„ SˆHsæ!íDG£„@#1â())Ñ´Á•=0¸R�x$!‚ˆQB]]]‘H$ÝÅ›ènuttpÎÓ]Èjâ–,F � ÎQ¡PÈï÷§»80y‰ª‚HRQ\\Œxd ‚Ä.)ÝÔÔ”îâŒMŒâœc ¤Q¿®ëÖ6F � îØºg–€»`™E��HFàt:E+sÇ #ˆ»ahú@‰µ¥éƒ)d9MÓŠŠŠ¬mÄÖ"!° `2 ÁȰœdÑôAw ÒH,ÙPPPÀKwq`Á„ ��H삺2 ÁÈðÐ1È8¢»…HÒO ‡Ñ`ÈÝøþþþp8œîâ@Ã9d ‚‘‰Hk @¦Àsèa2‘Ö–†!Ð=b¨>áÖ,¤FÄ‹}}}¡P(ÝÅ�I ‘ŒL\��@oooº‹06Œ‚ÉO ‡ÑÄNâÀƒa4š¦ZÛ˜ï©í"ÂrB0:DB0²iÓ¦‰Nž<™îâŒMŒêêê�|˜`%£Ý3ιˆ†ÃªÀ` ·Û]PP`m#^„Ñhé.LRŒ±ÊÊJ+ :qâÄ¢E‹lyÛßýîw¯¿þúÚµk¯¾úê1_üØc�mÙ²eH©xà�´�§Ÿ~úùçŸ¿à‚ >ûÙÏŽ÷g½üòË6l¨®®þêW¿êõzÓò}2‘ˆ„ˆ¨µµUL€˜0œóžžk‘ Q\\¬išX·´´ †Àh***öïßOèÆCÊÊËË­«êŒ£„`Ti_aº¼¼|Þ)³gÏN÷ñ˜8Ï>ûl8>räÈîÝ»Ó]€LâóùÜn·µ�¹c�±kÄ ÃÃa~+Ȩ¬¬´6Ð�‡‰gX FƒQB0ªêêê�;wRú"¡Ë.»ì²Ë.³¶;;;ïºë®t’ 2kÖ¬ýû÷«ª:}úôt— ÃTTT=z”Ðô�4ý|MÓ|>_º‹“NYY™Õ¬Â„ ˆC¬éÙÞޮ뺦¡ËIÂ$DÎ/0ªêêjk£©©‰s.–‚ñö•¯|åÀ�ååå±³`@†ˆ„pgÒB,$$Zá±bW˜NwY`ò‘ç¼¥¥E´É%êRkk+:t0"LƒQ‰!*†a á2‘ÇÒ¥K‘$Ý-H/1Jçp‘˜„;öGNNŽfˆÇ¼@*Ä9ÇŠÓ]˜Œ Á¨òóóÅÚÆÇ�·ñ�ÆHCz!‚øD÷Ìï÷ ¤»80y!=[äçç»\.ku F„‰cÏÌ™3­ç?~|ùòåv½­ÃáÐuýïÿûo¼ÑÒÒâr¹æÌ™³víÚšššÔß¼½½ýå—_Þ»woww·Óéœ9sæªU«Î9çœÑÆIF£Ñ7Þxã�wÞ9~üx ðz½555]tÑ’%Kd>®­­íg?ûYooï?þã?žþù7nüË_þröÙgñ‹_´^À9oll$¢ªª*1üÉ'ŸÜ´iÓg>ó™‹.ºˆˆn¿ýö`08üÍ×­[§ªêˆŸ{üøñM›6ÕÕÕõôô0Æ çÏŸ¿råʹsçŽøúþþþ—_~¹¶¶¶½½]Q”òòò³Î:ëâ‹/ €©ADB¡P¨¿¿‹¹ÀÃè!>1‰ƒˆNž<9oÞ¼t—&©iÓ¦:tˆÐ�‡”UUUÕ××ꌑÄS]]mEBMMM6¾mww÷�~ô£–––ÜÜÜââ⎎ŽÚÚÚ÷Þ{ïæ›oN1xzóÍ7}ôQ]×�NgEEE Ø¿ÿþýû·oßþ¥/}I<ŠHhllüÍo~cµà}>ß´iÓúûûwïÞ½{÷î+VÜtÓMñ4µ··ßsÏ=½½½ŸùÌgÎ?ÿ|"²žŒvìØ1ñš–––ÿú¯ÿ"¢Ûn»MÄLVH4kÖ,ë?çÎ� …Ä.º®744ÄùÜ·ÞzëÁ䜗••-\¸Ð4ÍÖÖÖ­[·nݺuÉ’%_ûÚ׆ä_{÷î}ðÁ�€Ë媮®6M³¹¹ù©§žÚ¶mÛí·ß^TTdã/ ½***cœs"jiiA$‰sÞÑÑam#‚išV\\ÜÙÙID---ˆ„`4b”&ŽAŠ*++ Aˆ„ ±œ�µ\«]^}õÕêêê;î¸cáÂ…D400ðøã�¿õÖ[¿ÿýïçÏŸ_PP�ÜÛÖÕÕ=üðæi^yå•k×®u:�DT__ÿÐCíÛ·oýúõ·ÞzkìëÛÚÚþçþ'Ì�;÷“Ÿü¤hþüç?;ŽøyPGGÇÏþóžžžOúÓÖ`"š1c†¢(íííÁ`ÐãñÑ‘#GÄÛZ‘缩©IUÕªª*럾úկƾsww÷·¾õ­Ñ>×0ŒÇœsþ©O}jõêÕâçG�}î¹ç.\8$jhhX·n�®ëW]uÕ¥—^j–@ ð§?ýiÇŽ>øà�wÞiã/ ½c%%%V΋îL°ööv+Ž$LƒÑUTTX‘ºg‡ˆ„ÚÚÚ°*0¤“!>,éñˆ�¾¾>»ÞváÂ…ßúÖ·¬<ˆˆrrrn¾ùæ²²²H$òúë¯'ý¶O<ñ„išç�wÞUW]eDTSSó•¯|…1¶{÷î}ûöžþñÇóæÍûÆ7¾!ò "š={ö7¿ùÍn¸!ÎguvvÞsÏ=]]]×]wÝ%—\"~ît:­1áÖ8 ":|øpiii~~¾È†¬À¨ºº:¹GŠöööZ«¬\¹2öç³fÍúÊW¾‰¯iåAW^y¥8,^¯÷Æo¬ªª:räH]]] ¿L€I+LCºˆYc𦦻80I¡{2b:†�B� Q—Ð.‚!‚x¦M›&Ö²±q…éÙ³g;ŽØŸ¨ªzÎ9çÑÁƒ“{Ï“'O?~œ1öñ�|ø·°æ£½ñÆâ‡���Öœ¸n¸aøz=Š¢ÄÉkººº¬<èŸøÄe—]6ä_­tIÌ;räHiiiYYYCCƒu÷xȬ±Dåçç[saþò—¿Œ¹2eccccc£Ûí^NUU?ô¡Ñ�’+ Àä„HÒE¬-�Yc‡ˆ„pŽ‚8|>_NN޵�ôR!Î9º®‹[&ŽA<Œ±ªª*+Âhjj’\q99Ö{± C¢¬BVVVŽ8ïlñâÅ;vì#wˆÈZ¬§¸¸XÌÞ’ÔÓÓóóŸÿ¼³³SQ”ÓN;mø fÏž½uëV+êïïokk[ºti$©««kii©¬¬´þ)éHHUÕ/|á ¿þõ¯_ýõ7Þx£¦¦fΜ9óçÏŸ;wîðµ¢­¯©iÚC=4ü­º»»‰¨··7¹’LN"jmmMwY »ˆ¦6f�A¢{ÖÕÕ¥ëzrC†!L›6ÍÊ�QB�Š¢¢"—ˇ‰¨¹¹7-`\„` ÕÕÕV’bïsè‡ËÍÍ%"]דÛ=‘¸�2„5¬Æz�Åï÷Q~~~BŸÒÓÓsÏ=÷´··ÏŸ?ÿСC>øà]wÝ%fcY¬¬Ç:hÖd±éÓ§G£Q"jhh¨¬¬Lq”-\¸ðG?úÑÖ­[ß~ûíÇ×ÕÕ½ð ‡cÙ²eùÈGD[S|e¿ßÿî»ïŽön£=Ô C•——[íííènÁDÂ(!�!fåщ'fΜ™îÁ$%"!Œ‚UTTX÷¤Ož0‘Ä\EŒ‚ø°Â4È@$vçLB„á ÁÄü&ëÑé¶¼§5‹*V{{û[o½EDÖ"ÓÃY3Â8çÖò7ÃUWWO›6Í4Í 6 ù§––kaik‘iKEE…?ýñ�4Msøvuu ÿáÒ¥K¯»î:kÛçóÝxã�Dô»ßý®§§gøAkjjjll¬©©!"ÆØìÙ³›››>L©ÍÓu}IJišf­m{ˆfΜYUU‡Ÿyæ™D?è7¿ùͺuëî»ï¾õë×']Z€´Íh,'¦»»[èbö"Àˆ°Â4Ȉ]ïòĉé.d0ÅÛ²eˆ "‘ˆõÃúúú_ýêW¦i.Y²déÒ¥±¯¿á†4MÛ·oß/~ñ‹Ø¯ÖÜÜüË_þòÇ?þñðu—‡¬]½téÒ /¼p```ýúõ±CifÏžMD{öìÑuÝŠ„ˆÈšÌõæ›oRj‘ÐÓO?ýýïÿ©§žêììŒýyOOÏæÍ›Å ŸùÌgTUݸqãc�=û��À³Ï>;ZT …ÄòC;wîLz�'€´sÇpg&ŒsÊ+**Jwq`RÃ(!�‘““c­†IÜ©çœp8<â­eÈfXKÆ6cÆ +}°+:÷Üs7oÞ¼iÓ¦²²2]×­7¯ªªú¾g¯�}ìc÷ßÿŽ;jkk‹‹‹»»»�ÀO~ò±Dô¢E‹>÷¹Ïýáxî¹ç6nÜXVV¬³Þ‚ ¾øÅ/yÃÙ³gßzë­ëׯ?xðà�üãüüüüü|¿ßoí²lÙ2‡Ã1æwùä'?yðàÁƒ¾øâ‹kÖ¬±~h%>{öì!¢9sæX?´’šÚÚZ}~œŒE‹½÷Þ{/¼ð /¼P\\\RRâr¹ü~ÿ±cÇ Ã¨©©ùèG?ûú¹sçÞrË-?üð–-[¶nÝZUU•››Nœ8aÆŒ3>ò‘�(ÊÐtØáp8�N+Yóxqâ„Ïç;ûì³/¸à‚… Ê|§ÓùùÏþ'?ùɆ .\h…A%%%¹¹¹~¿Ÿ1&Íž=[QÓ4½^o*ËL,Y²ä?þã?öîÝûî»ï>|øØ±c‘HÄëõΛ7oùòåçž{îðøæôÓOÿáøê«¯ÖÖÖ¶¶¶677çææ.]ºtùòågžyæð<ˆˆTU½ñÆŸxâ ÆØ 7Ü€î d á¶*L amiSì„ “'Ob|Í´iÓ:D¸œAʦM›fuåNœ81Ú´ ÈN ǘjkkï»ï>"r:�÷Þ{oº‹0†ÖÖÖï}ï{ÖöÝwß�»a0î¿ÿ~ëiþð‡o¸á†t&»Ûn»ÍzòÃ?ÿó?ŸuÖYé.LR›7oþãÿHD?øÁÒ]È`ëׯ߱c­\¹ò¦›nJwq`R¨­­-«¨ÄZB06ñбH$’èó¹&^YY™Ú†•abˆ'Ðcmi�a=)•0!âŠ}ZnäC*°„Œ‘Œ­¨¨ÈëõZÛv-'0~c¢[ŽH&ž@ A÷ dˆzÂ9GU�Tàœ£A$RĽ,DB�ÄrBˆ„`ôôôˆ'3b”È@÷ dø|¾œœkË A*Ĉ3�£„@FìCÇ0!â=y¤‡�в²2M|´êÄB$R0J2 F ÁDÂè!Q"Òu],D0"!° žÇ #B$RD$ÔÓÓÒ]€1ˆvOoo¯õX€ñ#ºô"’ ].—µ�®>Ä�9†`/"‚Xˆ„@Ê´iÓTUµ¶Ó]€1ˆva Œ? •••¥»,�1ÐÕ±õs !q#B$Rcâ‚„å„`òs»Ý>ŸÏÚ�‚'"*))IwY c bÐ+çs !✃QB ‘È Ó�Y°Â4L±–&Ž�<ÑÕG$q`Ž!ØBDBáp¸§§'ÝÅ�É‘Ȫªª²6 @F‘F Á¸…BÖ6"!�ûбt—&5Ì÷[”••‰  .�€HdUWW['OžÄLf˜ü ÁĈ�Ê!jÀ˜Ä(¡P(„;öæ‚-cœÃ!Y3gδ6 ÃÀI&?Ñ9Ç) Æ•ˆ„òòò4MKwq cÄÞ±ÇüVˆ‘ØEÔ%œs@@$²¼^¯X«sÇ`ò·Ât]Çx?x=$‡1†%Ï@†¸œ¡ž@Š Ápˆ„ bî"!˜üÊËËÅx̃ñƒÇ�AÒÐÕ±«www§»8�Á0q †C$ @$„1&ºèˆ„`üˆH¨¬¬,Ýe� ƒ;ö £´´TLJEU�TˆsN___8Nwq`R@$ ;qâDºË06LÊ€ €‰c�4ŒIâô‚ª©‘¡C§ ‚ˆQB===‘H$ÝŃˆ„ÚÚÚÒ]˜š8çb¥*LƒD‰H¨»»[×õt&/¤‡` MÓŠ‹‹­mÌ "!HÀ´iÓÄÂÇŽKwqÆ &ò`⌓ش‘$*öŽýÉ“'Ó]˜¼D$„ˤóUaDB�UUÅ-w 5€ÉODBíííœót¦ ŽŽkCÓ´üüüt2ŒËåÕÝ3ˆó Á.qC ‚ÄL›6ÍÚÀ½,˜üDšs.ºî6ÂBB�"qšÂèˆs Á.ˆ„`DB�¬0 ¤¸¸XLwÅrB0DÔˆYc�Œþ±s ± ¤BDB@ DB� 566¦»,cÃú 0® AŠ0Jd¸ÝnŸÏgm£ª@*bP#‰B$‰Ç"‘Hwwwº‹0±œF ÁxÀÄ1H‘ˆ­1ôâÃ|°E^^žÛí¶¶Q—€ A¢ÊËËUUµ¶1w &?DB0®D{Z<Ö !⎽®ë¸Ùq`@Øñ"ÄB$ à Ó�AD‘Ø.ˆu^1J’SZZ*–ÈÐ4MœjPU ""Ä‹€H’PZZªišµ�ц0ù!‚q"ÖB$©‘PGGç<ÝÅ�É Ë �-4M+,,´¶/"!H†˜;†H&?DB0N0Jl!úùœóØ5ˆÀ34Á.ˆA@$ÉÀ Ó�A Á8½²’’’t—2XQQ‘�®>Ä�9†`,a"!HF @‘ÚÐ`£@ �F­mŒ‚¡«20²ì‚sˆ„ âÉ…MMMé. ÀD$�FûúúÒ]˜"b'øˆ¶5@rÐ=¸œ�]Ä(¡–––t—Ò ‘$CDBápO.€I.vRÖ黈gnz½^‡Ã‘îâ@fC$2bÓgTH…¨Kº®÷ôô¤»8�Nˆ„ Œ1ksÇ`’s8ùùùÖ6"!°‹ˆ„ŠŠŠÒ]ÈxX6d(Š"N8XR/bb–C$É`Œ‰óFÀ䇦Ávˆ„ÀFâ…ØâÃå l¡(ŠXu)Ë!‚$‰¦ Àä'採»vO G$©ý|Ó4EÕs Á.ˆÁ‚H’$–ÂÄ1˜üpl'F áqc�º¢¢"El–£{q vñ"êR–C$I‘F Àä':툄À.˜8öƒÑ=ƒ80Jì‚x,ˆ„ I"êìì䜧»8ñˆvOWW—išé.d<Ó4Å  �-0˜dˆè0ƒÁt2F �‘$I¬%D(“^ìsè±N¤.¶ÓŽHl�;ö #ör†ôR!Î9Á`Ðï÷§»8�6ˆ„ In·;''ÇÚÆ“ `’+((ëtˆù>IµHQ”‚‚‚t¦DB Ããñx½^k‘¤"ö9ô8íd3DB�bÐ"@r0q  AJEÉË˳¶±œLr¢ Ý××—î²@ÃÄ1?ˆ„@Fl�ª©çÓ4‘0f'DB�*Lf€L!:ð†a`®+$MTqSÀ.ˆ„@†ôJ÷ ©]9Âi'[!‚Ta*dŠØ1¸IóûýÖ†ÏçKwY`ª‘–—†øD$�ª©PUU¬j�x1;!‚T¡í™Âãñ8�Nk‘$'¶æ`”ØNLBß âCU»ˆº„Þ\vB$©Âúv�AІ†ÅN9D$¶‹]#§)ˆCÜ”E R„H(Ë!‚T‰‰¸ Àä'Ö_À Ó�QsTUu»Ýé.L5X#$åææZ¸œAŠÄi‘PvB$©B$D´¡±¼4$G,$„'ÐÃxPUUœ¦Ð=ƒ8ÄZfˆ„ Eˆ„²"!HUì|DC`rB$) ‡ÃÖ†Á8Ý3Œ‚8ÄÄU\Î E˜SŸå Aªbo“b Lrâ¶*"lHN$±6ÄRåöB$2 �]Ä ~ŒÊNˆ„ UN§ÓårYÛˆ„`’‘ÚÐ�DB0ÞÐ=˜8vñ¢ab$,dDB`,'™Ç E"r8é. LM¢{†ÁŒ‡ˆ„ Ãé.d0Q—­£¬„Hl€H2F AŠ0JÆNS Ýx°‹èÊêRVB$6@$™BŒÂèhH"!o˜2 �]TUL@]ÊBˆ„Àˆ„ S  )ŠF£Ö&ŽÁ8Á(!�„ªvA�Í � ÄCÇ À$'é ´¡!)%ã kÄ€$>Ä'ºñH!Eœ˜µ �=0J2…ˆ° )6$H,$D%ã)vBNS‡Çã±6‚Á`ºË™ Ï¡ÏZˆ„Àˆ„ S¸\.Ƙµ�64$‘L ¬’ �]0q,k!{ˆH÷²`òmèP(”î²@&A$C4«Ý3ˆK\ÎpSR$’hôæ² "!°‡h»D£Qñ4€É ·U!9±‘�¦ié.LeùùùÖ"!ˆC´Àq9ƒ‰iõˆ³ "!°Öæ€ âv»­ Œ‚„ˆHÈår¥»,0ʼn…ðѬ‚8p‡ì‚x1k!{ÄŽpF´ “ÚÐ�ñÄ1ÈÀjž`œs²"!° "!È â”%:ùcB$ Ý3�!–n@óR„sNvB$¶‰]K×$˜äD$„‰® OŒ)C$Ý3�!ZผAŠpÎÉNˆ„À6XK2ÚÐ�¨Ø»ˆ„`ˆîÖõ€8D wd!Eˆ„²"!° &Ž@A$‰Š�cˆH&ºg —3°KloÎ4Ít&"!° "!È hCC¢ÄãÆOƒ !"¡P(”î²Àä…ËØ½¹ì„Hlƒ“d¬Ç ‰Â(!˜`"yŒ�#†@$v‰]Õ){ ;)Ê`�ÂI&9´¡!Qˆ„`‚á42°–Ø‘PvB$vÂ5 2úZ�¨ØHÈår¥»80õáÁˆ —3° "¡ì„Hì„H2ÚÐ�( ÅN”?8M� D‡`,’� ��°6d DØ�( aÖL 4«@¢C° F e'DB`'ܦ€L�64$J,ñ‹Ç�ÁÄÀi d :» ÊNˆ„ÀN¸ë™}-HF ÁÃi d ž€] e'DB`'DB�)І†D!‚ †Á× Ío° ÖÊNˆ„ÀN¹ ™‘$JLC$Í*��ËØEQÞpÚɈ„ÀN¸�™mhHF ÁC³ dàr6Âi' !;aä*d ´¡!QápØÚ@$£„@šß`#´Ž²"!°Ì©A'IDAT®I�)p …'ŽÁCß dÄ. lšfº‹™ §�,„Hì„ÛY�)Ðè�DaâL0œ¦@–áB$vÂ]wȈ°!Qˆ„`‚‰fç<Ýe�É !‰ÎBˆ„ÀN¢í"F×LNhô@¢0q &XlW-+ "!°nðg!DB`'œD S ‚Da”L0tõAê Ø­£,„H섉�)Ðè�D‰HÈår¥»,�b»úhYÁh°–ؽ¹,„Hì„QB�)°€"$JDB˜8#¶«�–Œ£„ÀF¸a–… ��°–d 4z Q˜8 ]}��z6 ³,„Hì„QB�) A¢ ÁÃ(!��Hl„ÖQB$vŠ3û4¤»tïC£…H&ÖXKl„ÖQB$É;~üø� g´QB���»víJwyÞ‡F$ ¡‡ †Ñ CQÞïСž@Š0q, !‚äMŸ>}ݺu±©Ðˆk �uëÖ-X° Ýåx"!HF ÁC$’Ð�» u”… AJ¦OŸ~Ï=÷?~ÜúÏᣄ�À=÷ÜCDÅÅÅé.,ÀûÐè�D‰¾"!˜XK$áŠvA¼˜… AJV­ZÕÔÔ$R¡!'+jjjZµjUºK ðqÖ>.‹mDB0aÄœ œ© DB`Ô¥,„HR2}úôêêê`0h¥B±‘�ȃˆhÙ²eé.)ÀÄÞ~7M3ÝÅ�ÉNÌ#DB0�ð,W��n<Øu) !‚T­^½šˆ¬T¨··×ú¡®ë"ª®®Æ¬1˜l°H$‘¤ºg “}À.8çd!DB�ªeË–y<" ƒ/¼ð眈z{{­<ˆˆ0k &¡8‘�X @ˆ}lž8S\AFœnü�§ćx1 !‚Ty½^1/̺‰Ê9�]s³Æ`2xæ™gbÿs´H(lÚ´)Ý……I£„ -pdž۶mÛ� g´ †O<ñ„×ëMwy!“àœ“… � ¬¹cÆcLü'f�À$áõzyäñŸ±k ‰[aÖ"hóçÏOwaaÒ‰�„b+À¸ÂZB0Ü‚ bøK£tãyä‘`0˜îÂB†A$”… � ¦OŸ^TT4â?aÖLË–-Û¾}û#�JH,Š�±�0œˆ„\.WºËYÝ3κÛ› Ÿ`øÈ#�lß¾—3HTœ‰c˜„8U!{\zé¥#þ—"˜$Š‹‹Ï8ãŒíÛ·ßsÏ=�@`H$$ò •+Wb˜= '"!̃‰„µ„`D«W¯ü¥aÑ¡•y<´Ã!¾ãÇ� zF‹¡;;;wíÚ•îò¸@$öX¹råðbÖL*ָŦ¦¦{î¹'vÐÀÀ€xH"Ð@#µ}ãDB¸q 㣄`DÖÓ]D*;²Ãʃ—3�0}úôuëÖÅNBq”P X·nÝ‚ Ò]^ˆ„À^¯wx*„Yc0©ˆ'$655ýêW¿²ž�È9_¿~½•áž*XŠ‹‹_~ùåØV²xâØ�Ç�mÛ¶Íê}ŒDB0"ñt+'¨ºº:qFÂå dLŸ>}ÄIˆâœc £¦S3aêA$¶ áR“�8S�³�û�D4 AX¶lYl+yx$$ò ,>v9xðà¶mÛb2âZBÖ$´²²\ìÓ]8çCžù‹ËH*..®®®×»ØHHäA„5¥!;Åöµ�%À$T\\ú(�²”@rV­Zõä“OƦBÃG Y�´’’’tÒo´§»:ð�kÄ™• õõõY?ŒD""Â�þ© ‘Ø)öòƒKLN£Ö8kÁV•èìì¼çž{ü~¿õC§ÓùÈ#�¼òÊ+DT]]�å6Á^Ë–-{ôÑGE*4d”�¸i�óÐè‘4îp@BÄJ‹Á`ðÅ_´æÔwuuYyáNÿT‡Hìd=ã™�%À$&š>Þî¢Áä"ÁMMMo¾ù¦ÕJnjj«·Šµ<ìbUªG}tÓ¦MôÁu=D„?XF|º ár «•Ó©g)pÎc'«¢FMmˆ„ÀfVY2LZ±M],.v©Ž��k£££ÃÚ@��ñ0}úôêêj"zòÉ'y䑨HèÉ'ŸÄ¢0Ĉ•5{‡cȺT¸Ó?å!›-[¶¬¨¨k+Àd6|pÐ0¢ØŠÛD¶þ KÃx'¨íÛ·;vÌÚÞ»w/.ÃY ïØŸ ­†$ÄÞãa§}zºKSÓ�bðpqˆoH$„´’3â$�3Î8wú§˜ë qŒxŸ�.WqNJˆ„`ñt"***BZ Éqµrœp²"!ÈF±M¬ˆqŒ¸9ê Œ« Œ8<­¨¨=4NÔ THÅðúƒ•  @–²:¸§ ñ-[¶ÌãñÄþÃÊ`Œ¸®ºg0¢U«VY§)¤Õ�Š!«•cÖX–@$YÊjú ‹cRIF[ƒÀF#žšÐá‡Ñ¬\¹w8 u±'4�²"!È^«V­B ÆÛ,ž7o^qqqºKS_ì1tø!ŽK/½xH"¡,„H²×ªU«ÐÅ‚1ÅÎC†yÅÅÅûØÇÒ] Èx"ŒÆ¬±ì�H²†{€$«7Ž…¥a" Y×uâCla]ï�AgDBc°Çxöxèü ƈ©ŠÕÕHà¯SUÈ49¼ÿ›•ÿÅ1–@sWP’þë,žÂ»lżÌÚ+NàÊ¥( ç<ÑgI3Æc†i°þ<È¡iQ=±f¹æp˜¦i$øËRTEaÊ©³·Ìç1bL±þÞ¥v!"NŠÂLÎIº»jµ�9qžPóŸ1²úh ì¥( c”Ê…s“±ˆiHuá9qâ\QÜœ nꌑ\o×$RS9�JîBƒÆH“ßk°xª››QÎ%ÏNœsΘ¦(“Ö’8„œWˆ81N\þäDŒ98×IöÄní¢Y8É£g�•DvÜQaÆç¼··×íöhÒ{çœEBz§í�uQs ›)™½ “¯YR¾ípgDWä*±ÊXo(òïk?¶ãh[_Ø¡ŽÝŸWÖÚºõÃs|.íîNËwëæØÕÝ4¹Ë©—8ª×sÕüeîå›û_p«n™]ˆÈä†ÏQPè(j 6È"RHé×û.λò„y¬>pЭzd>+ •,8÷mÖyç¿jå$Õübf8Rpýuþçž7ƒ!&qþ$"RT£§·øËÿìyS¤ñ¸â»ªSôÎÎÂOJ›7·ý?~¨UV’1vñ¸i2‡³ð?ÔóÍ;™¦J•�7 µ¨0ÿ�uäv'SU C9~Œvï"·[ªÀEBtÅGiëk �*QNÆh`€ÎYNºAï¾M99RÄ9i*UTQóI2dƒ$ƉT]té 8ª©RM Eeý]¡Õ7.:y¨çÐÎVO®C¦tшQR•;kiÉŽgê=>'—¸ª‘ipož£lV^ýîÍ¡ÈüI)*ó÷„¯üÒÒP$ØÝ6 iR•V�e3 rò]Gv·¹<šÜñ挱Âr_OÛ€iòuÉ0ø¬E¥'ë»ôˆÁ$n:1FѰQ1«ÈW©&\cG¹o¤sÖ×|bÓ½ÿ­8\2µKQÕ¾Žö�Ýñï¶m>ºëWN®Ì^¦¡»}ye5s›î�†ÃŠ"÷»ˆF §U}芫^|àçÞüÓû‚«¨Ê@oÏ5ßúA_S}G»ªI4)3ÂᢳT§³­î &ùçLdº'¿Ð“WÐy¬^u:e÷2õ¹—~äø–M¦®Ëü™¢DÓÏ8Ë;c“;n$FìXè±#2UËÂM®¸Tç¥3£Ï40¹¿2R™Ñö~f«í ìV<ªÌ^fØpÏÊc³ò/W}’; pÃÔ ÝÚ…%ÔÖ/{ bÄÂ:�UIõ½l B2{1b¡(Í.%§Æ´‘Ç!u8'…Q‰�:úÉLäfµÉi~é"SîN#ÒiN)±äZ8áPè™gŸëííÕ¤®•, .=mi0<|äˆÛ-Õ°$bápxÖ¬Yn�{ÿ¾ý’{™&w:�×\ó‰?üáq‡Ã!³‹¢(=½}Ÿºîº}û÷züèCª–#Õ'bj8ÜvÆé¿9vâ·ýý{UÕ#ñ9,ªwW”¼¬ì¦Þ®g4-ŸhìâYõÇ›3#îŽê½Vd#óY¦)(øˆß¿Ù0B2Y‘¢ë}EEwõ¼©i¹r#†ªæäæ.ééÙ®(r]×S;æçšˆc……D”@$Ä#nö…¢;÷QCêS “ÏÎ1w×µö% u# K‹j�45w‡œ­CUam=ÁÃs¼ùnÇ{Û =’‘�Û©4·Î<~2œHRË�N60àhíŒJ†µŒQ dTxó{zãÉ€Û¡È;Ýà~Ÿ–§å66u{\Šdè¯Üż³|ƒwk¥F ™¤¨ÔÖBGvQ_;©êØUŠ)4 SîòÌ¡C¨ÐMfDª|†I-Çéð»d„¥#!“|eÒèð»d„¤öRÔ¦ÊCTv‚Ž$o±t$”CÅ-tüirWSÆ( G5uÖS3©R9)£èQ1…z©ãiR—F]i~ÞÄÝï6¼[‘S¡KŒ™R™Úì<ÑzboãÞ£}G]ªK¢d,b„«|�¬�j›kÃFX2ê ÷TÑ´2*{·áÝi¹ÓdŠÇˆ DZz[gåÏ6MC2[<ì£àœNPÓ‰îã]}�’‘�O+¨(šÑÐrD2¦!"“ÎB¿s Á_/ õé=‹¨ùø@c½ÿˆGõÊEBÁ Ã»´‘5¿ý¶£ºŠ¢‘cf(9{YïîZ3”mŽ«ªÑÕmÔ×÷íÙ©oP\cWR½­=ò¡3=­ùí·Ó§Ë$VÜ4™Ó©55µìÚÅ$›PŒq]WËJ=­­Î™3¹aÈw·Þ¯*Ç�Ó±c$ÙdŒÂ!jm¥£G)0  ùý4­Štƒêë)WªûMœ“¦‘nÒñã¤ËN䜘Ê[ó[6ÉGB}íÁÅMEǺê÷ŸôæÉEBa#ô9 ôúýǽù.ùH(§Àæ Z5§l$Ôßji. †:›ýšCªÉ ë¦Î zš›Ý^©o4x×Ýt6÷›Òã8ˆÈÐMO!�8Þ. EB:¹¢¾ÊjÓ0é®~ü:`D£GöîÑÜ.1TAQµž¶æ–M�GêŽìÛãÎõIEBºî)È?¸/ ÉFB‘p±ß_¹äø‘½{r ‹LC⎅ªú»»ZšOö66ö¶¶¨©HH…Šª9]'ê�l¸£ë9E½Þ‚âÖ†zÍ%¨‘i襭­'Ž6ÈGBá¿£¤¢fÆS×¹Î79S•PKWë¡&ÙÓõ©H¨`‰»ëPsJ…;¤2½+TÞ\äoh êP¼šT$ÒݼУº5©yÎ"¡bOé<Ã8Ù+ ñ°îœž£7µ˜þ°Ô^Œx(ª¹MæÒ¢Ç›™\$Ä9g S¢£¥�I†;§Ž¹–OÆñVÙ!`ŒxH×rLGQU—-ë�9'ÆLÓ¬««�Û��ü¼ü���ºº:�Ç#…„B!ιÇë‘ßË4M·ÛÝÒÒRWWçt:evQU¥«»§¹¹ùèÑ£u‡ê¼^¯Ô@Î5ÍQYQîCÁ äéÈ4M¯×«iZSS“dñc‘H¤¨°°¹¹Ùï÷K&5º®‡B!‡C;ÙÜì�9ƒÅÏëõ¶µ·iªl7\×õŠŠòœœùˆÖ‹9˜Ä¹~üø»¦éà}"ÆÁàñ’âÆŽŽƒ'›ßq8ŠeÃæú"ÁPÁñãï:œEÄǾÐ`$TbÒâãMïjšOæƒÓB¡“•M'Nìíë{WU½Ÿ£D¢�Ü\àr577×9Eò‘PN.EBQ½'‘H(¬i--- †”Ž„zLcŽa†:;�hšO:Ê-((èè8¢(’AðàŽ9Þ~Mó‰1V Ž"¦*Ì­).¦(rƒ® “k*siŠËTä#!·¦¨ s©ŠKS$#!MS4…© S4Å¥)ªä(!MaŒ%�¿/ÎIaÖPP&9­F|„µ!yìN cïï"÷[V¬{ ŒÕ=õ­#ÕEšCj”SHë!ÕAL%M'ÍE¦äÕN'¦�æ"Æ%¤ºHu�æ"Fr‘�FÚ)1…�M.¨f¤hƒsÇM6üõÔ^RG��˜JLMà³�UaŠKu¹T—*qVR™êR] SŠÃÚK¢dŒ9‡¦hNÕED’‘�KuiŠf}¢dñ1ÝÔ%5~;Îq"EeªÊT¹|‡«LeÄÙ…‘u²Q™* ©L³&©É–ÉT…Rs»™ËEŠT6Á81Mc.3LùHˆ¹\LU™ÃÁ\.& 1·‹i©ê`ñdZQ¦ÉœNRærÉGB¤ªÌáHr¨¿µ‹¢�ª’ªÊFBª:øÿ­ÿIî¢(¤ð>Èškf•M~æ)'R9cLÕU:RÊ`ås(ª&u�Á4¸¢)L9µ‹\o�1®jŠ¢2USTM6RŠ5ƺÞÉ|�¢XÓ˜ü%Òºd[»X³%¤8WÞÿ ÉHÈ*ž½'7bLu8T‡C2ÒN¦(ŠªY{Ɇ1¦jEÕTÍa: É>禪iLQ¬’éý*ªª:ÖaUTU*@aLQUë×0¸‹äáåœ)jÂ{·>‘$oþ)ŠU<¹7ÿ@ñˆ1¦)ò‘™œiÊà^re¤²Á]ÔöbšÂTFÊ©]äNĈ©V“—‘dʈ6øÿ%÷Ü%惤®ù§öRØ©&�´„öÅKö$ÀÓ4Mrø‰õbEQTUµö’ŒB4MSU5¡½LÓTUUO2Ò4�1–ÐqÎ5MUEaLQÉÓ‘8Ï+ÖL:™×+§®(ò”Ä.Ë'¹Ëà+S¸ XuVQ\D²‘�¢8SÓÅ­(.¹ÅTcš¢¸Å% )ŠâdLÜK.R7cŠ¢8Ní%ó).Æ4«â3¦&2ŸKaLµþ'{¼™Jƒ'\U2bL³†S$ÙèRÝeÈ·³$ ‰=ù©ÿÉT¢»$ºWìË’Ûkªar—ÆØ×ż1wNî�S:jÄTí¬_&<Žy™ä.Ö+Åf{Éïý½­®YˆŸ:®’/NbñJɽ†ï’Ð^òe£˜fPC¦íš“�ƦðÅì�sÔÔ†ß/Ø%…„‘‹Fú¸÷­Ý%¹½†/¡]Àò~OÈ:ˆ„²"!€¬ƒH ë €ÿ¿½;knãFâþo`†‡¨ƒ¶u�)ÛJ,9¶kïnRq¾ÿ'ÈwØ—Ôf#K¶æº{†:œ£ÔÃ\•¨%«ô0M`0Ó–�sÎ9çܽã)!çœsÎ9çœsι{ÇSBÎ9çœsÎ9çœs÷ާ„œsÎ9çœsÎ9çîO 9çœsÎ9çœsÎÝ;žrÎ9çœsÎ9眻wŠžÇS÷¯û±ôéuû°õ¢î;U¿“ãÏè7ê~ŒG®²^ÔÚÕ»ê~î—ÑU»�ì²^ÔíÃÖ®^ßs_£«xïr¢ß¾ûÑ­ßwKÿaœ=oÿÝkfïU¬#À_Nß‹îcÔß›__÷[ù}‰®éгõö çφ¿ÆM�ê‘""@³h•,mK‹&–:K•8ئÿHT%΢]n€('Î,YTwáwF‰(‚Š*‹ÚW&ªÊ‚.JÕHU]E±˜îiQU¬Blã‹Ú>þ'UTE®�”ow‘_8> àÂHrI¶ú)T�+p².E‘+p»úm‰ RNP'H‚º«0$CœâÝ�е'(C2$™‡§Uqœ SYœ YTª\Õ\gÉwFDŠW¢ÒpSs­†RÔrÓp“$Õ\7ܺ{/!�*®’$V¶W�@ת "K $kΚ-çËšY³Bí!D™•E9k¶½–!kVV¶—•53LRUZ×Hw·*ˆ¤j4g­k­kDÛVÐ¥ª4gm©j²Œ!!HUiJʼª^¾»z*ѺFa›€ˆºÓA×UúêBD�3b0­¨ˆ�»¡€‘³u(È"«‚8› „Wã�@U4'�¨e‰„rË"ÊYr+9™f‰œ„³ë*Ä0«VNÂYr�©ñºê©¨ˆ ‹S§•n¶SaÓ©*DAE…UDÈ0¸Ý”¥*¬ÂBjiU=�­ ŒTsÛ •»g´‹ÜÔ*Â9å¦ÎåÀÒ'%çœZÎ9·mn›`»¹m8%ÉM�ÛV˜ Õ‹]õ„Yr&Kç'’œ¥»x9‹ñ.„³0«°ä,E´G©ªpÎdyHƒä¬"½‡)"¨j˜–Õ ¢IE5‰uÒtµxM¢I,QšD³(ë*Ä6(‹f�(Ø|J`�j�¨.Dª`�ØP ˆBÜçbuÙ£®ÏhÝÔ§ªæœ³ab@D)%ffæ”RY––1±‹Ê=£X$ƨª)¥‚%D5¤”D¤+(çl‹RrÅØnÝÁªzý‡¥n‡Xî÷UAª×QëT�zDu];+¤ @E*VÃ33·ª,’Xª(5 7˜H%Ò¨æUˆÞ=iEjÕ,R‰”–‚ˆ æJUD‘Ê6›–J$©ŠhVÍ–‚ºþ£Êª¬j[uëª SõT³ª¨ÊU”åá«(kÈu`WÏë�)!U% 6Ê·/–T¿£XZ¢DôÕñÁ{]ØSB ó¦}¾œyÚ~wÞ”ÅÝ퉾;¯_<=Üÿ~}1ßeËÒGtX†Åì…#¶ª–%M71wmbX(ª†ç³¸Y�ãö°–+–E§4{„ãåÁhŒƒ@]P— µìÔŠ°?ÇÉ×øð_ÄÒ”º<ÇñçXãÕçØžBM“˜1?Âé7�Æå¸06v1{�xi�¢ïÏðôfK<}�ñÔ– ÆØ�ãéKÄ¡õ90Õ˜?ÅáýDã EÈfÇh.°G–² n°ýôhÿèí§o÷6öŒ)¡ê–³å›goö?ìÂÀP3JÒlÌžïœÔãªåÆ2Îè¼=½x}<;~ûüíÁÆ�5%”/gÓ€¢­éîþÐC,ž<|¶=Þ±ä\<‰Ûóáãçt: #cJHÁ[åÎNù(l�l)¡€ðž/oêËV…±)%”êÝɳÉ3~ó¶<8@6L®iÚ�ÓÓ�/¿Ôª&kJ(ðÙ»‡''“¾I{{a04œåïÿ7}ù2~ò‰~óM9›Áð¨"T–ÛGËÇ_}EÖ”4sxô`0› ¸VWY.ñî Ñ5%Ô6˜Ípü êKX:'.?`± ¾?ÁÆÄT�*ŠˆÙc †ÈÙ8‘Qg‹Ùó×Mm)¡@益³ÐŒÒ»r¼YšRB-?:œ,žîžü£o�ï%„uc»Ü{²¥—£bL)¡@ïß5óÅAÝV“ávQ˜.qjóþb:Ù\>“áØtFÝ”=ÝßÜnÙ×ý˜e6ß•ºàV,É "´Mž>Œ÷ á3‹Áàä‹7±4MO!ß>^�Ÿ¾ M3œ˜ú¤dmmí:&M�5%Ä©�Î�ŽNßükc{*bH …øáül~¸ØD¾ØÚŽ¥a4 ÊMópù$E[#Ûí HÎã�éx{:€Sj €pžÌ–ÇÏ­)! mõa¾XÆÁ  HF󇳗Këp ¨hÄrq0|ù�JÛ�ˆß5ãÅÁä±–I™¢¤áá“-z²=zÅa³46�²Óa\ìb0±¾–ÐfìÄsÅek�j2w1ˆÅ0´µy—£ÙÝÂpÚ3%4Û+?ÄQMÆ|kL[DB§§'çÑÐ�ˆPUõñ³gU]ƒh4约iŽŽžŒÆ£Ô&c”ˆ ƒùlöò³ÏÊ�iì��ÎÞ�//.Ιe<Gì"Æ£££íé´iãS¤¨Ž†Ã½½½år90§ÆÚ”>|˜Rº¬ªhL…3?˜N‹¢ˆEQšoù®zãñx8æ¥ofžL&ÈV·Ÿ;G„P}�“åU/ˆÊº^Ìfσ׃ÁŠrjI 0דɧ;Ӛ߃©9%”ËòÁþì4·o‹bbL 5ÍwûGMóÅÅûaŒ#C9!¥³ƒƒ×Óé¡ê‹¢Ø±§„6&ËÔLS>'2^2i77çóùsæÚš±ÊÓK Nc±iL Å8™L–ey‚íÑõ*°(·\×�¬›On>CÀ¶‡ÿ›f €éuÄÇ-aXUÜ>9Ä„{ ñD¦åþÏ×+ñ}¼=pu$õ›´®£ú"j²½+„Hˆ-߯ #Zî×1>¯_å<‹€H`íQ9"Ä`zî¾]X´ïvNB ¨ö‹"B„‚¯ÏòN û6Å*—Ø5·±#éªFt=ÀÚª§@ѳ·Þy/(À}?S¡kÜ2=_ÿëÕ{é3z¨JAiU?kT¤ÿæ„5¢€L¹‰î”J�mWá Jôß5}ÕÎÒ³Õ×n X–5Æjpë×c‘ÆWö×�� P@ìWŠÐ½À°‡Ü*­_”®çbûH�«û°ï”²Æ‹ÖÐóÚR�S¹ º£Å€}@jþ’V·…"ôÛè¦é^—· ê3»¯¶ÅØ«HWEôºòê?³úSÜsäzì,º é}¿Pß‚º¨^Ûuè1—@ÌSøíï_=ÒÕ-Ò§z}×y7ñ×·º%}òÓ�26]­÷ÌQ×'ÕmZ1¯F?Úä²FA}›qíïLüÞþ˜Rðãq_)­ž>,( ¨ö{ï¾èoÞ5u‚åÅâGÕ+K÷++DÄî“*Ö\5„пÓ6†Eõ ®6¨œ���Fãþ)!çœsÎ9çœsÎ9÷—õí·ßîÏæþ?Ž9çœsÎ9çœsÎÝ#Ý7£=%äœsÎ9çœsÎ9w�tßó”�sÎ9çœsÎ9çܽã)!çœsÎ9çœsι{ÇSBÎ9çœsÎ9çœs÷ާ„œsÎ9çœsÎ9çîO 9çœsÎ9çœsÎÝ;ÿƒ%á @=#%tEXtdate:create2021-11-16T15:54:06-07:00­Ä«H%tEXtdate:modify2021-11-16T15:54:06-07:00Ü™ôIEND®B`‚flox-0.11.2/docs/diagrams/cohorts-month-chunk4.png000066400000000000000000000034251514775624000220050ustar00rootroot00000000000000‰PNG  IHDR ”s0k9tEXtSoftwareMatplotlib version3.4.3, https://matplotlib.org/ÌžDÏ pHYs%%IR$ð‚IDATxœíÜ!ÏVuÇáÏý@p“à¤P˜ÙQìZØìL6Þ_ƒß‚›�$�ÎFÑNafG¡È¸Y|å×h÷óÎíÙuõÿö«ßísÎaY–àlë8 ‚2*ACP@%(`ʨe A•  €!( ”0eT‚2†  €JPÀ”P Ê‚2*ACP@%(`ʨe A•  €!( ª«x»\Ú\¦Ã1�ü¡ €êb(«ÿ¾¤3NÔg׫ZÞ¾Úø�u¾¼UÕòúùÆ—¬ëpóûªÎ_ý²ñ%ë:»õ`ë>rþûÏ[Ÿ°ª³ïVuþôÇ�/Y×ÙÝŸªúïñ½�/YוGOªzÿèÎÆ—¬ëÚãg[Ÿð‘7?|»õ «ºñÛUýùÍW_²®¯_þUÕ‹/>ßø’uÝ~÷OU¿Žú¨ããþr‚?%¶AvÁÙ—SÛ öÇ>Øûb|zöǾز?vÁþØûãÓ²?öÅþØŽý±öǾœäþ81þP@%(`ʨe A•  €!( ”0eT‚2†  €JPÀ”P Ê‚2*ACP@%(`ʨe A•  €!( ”0eT‚2†  €JPÀ”P Ê‚2*ACP@%(`ʨe A•  €!( ”0eT‚2†  €JPÀ”P Ê‚2*ACP@%(`ʨe A•  €!( ªÃ²,Ǿ=ú!«:óÈʨêêÞU°pšü¡ €JPÀ”P Ê‚2*ACP@%(`ʨe A•  €!( ”0eT‚2†  €JPÀ”P Ê‚2*ACP@%(`ʨe AU}ò!qæ�`3fIEND®B`‚flox-0.11.2/docs/diagrams/cohorts-month-chunk5.png000066400000000000000000000034761514775624000220140ustar00rootroot00000000000000‰PNG  IHDR ®száš9tEXtSoftwareMatplotlib version3.4.3, https://matplotlib.org/ÌžDÏ pHYs%%IR$ð«IDATxœíÜ!‹aÇá3× ‚Ñb³l±kìMÁ¯à?ƒa÷+“»`ÑnYÌb±(‹w,n48wßÿëÌê=Â$Û—�{�Ðヤһªúux¿ó$m�9x^UUßîtž¤­ó‡¯ªªêó½�'iëò‹wUUõáúÕΓ´uíýÇÞ#ü³7Îõ¡©[ß~TUÕ³aÒ(³ñ`üs¨Ùî°k󾮨³ '»ŽÝaÖ¶;̉=gNö»Ã2¬mw˜{Î2øF²,kÛæÆž³ ¾‘,ËÚv‡9YÍžÃ_¹¸@”p €(áQÂ5¢„kD ׈®%\ J¸@”p €(áQÂ5¢„kD ׈®%\ J¸@”p €(áQÂ5¢„kD ׈®%\ J¸@”p €(áQÂ5¢„kD ׈®%\ J¸@”p €(áQÂ5¢„kD ׈®%\ J¸@”p €(áQÂ5¢„kD ׈®%\ J¸@”p €(áQÂ5¢„kD ׈®5Œã8õíä‡,Â0å‘‹kDííðvR)Àº¹¸@”p €(áQÂ5¢„kD ׈®%\ J¸@”p €(áQÂ5¢„kD ׈®%\ J¸@”p €(áQÂ5¢„kD ׈®%\ J¸@”p €(áQ¿±qæ¹ð3ìIEND®B`‚flox-0.11.2/docs/diagrams/containment.png000066400000000000000000001040451514775624000203260ustar00rootroot00000000000000‰PNG  IHDR+PôX.9tEXtSoftwareMatplotlib version3.8.4, https://matplotlib.org/%#u pHYsÂÂnÐu>‡’IDATxœíý{œ¤g]çÿ¿¯îê™>MOwOw’ž™Q0ˆ†“Áň   &BTñ¼û]ü‚ãæË/74še㎠¸‹+ˆ‡C äàш²2&B$&˜sÏLwõù<]Ý×ï�»îLͤ«»êº¯êûÓ=¯çãQ�êš©ÏçþÔ}ߟºîºê®*ç½ä­)ï@b²€LV0�ÉJ&0Y À&+˜Àd%˜¬`“•L`²€ LV0�ÉJ&0Y À&+˜Àd%˜¬`“•L`²€ LV0ᢚ¬tÎ=Ï9WrÎ�qε¯ðÿýιrνÇ9w¿s®èœóåË»s¨w·sî8çžtÎÍ–ëyØ9÷ç\´mçœ{£sî�òz™wÎ}Ã9÷!çÜskŒ¶sî€sîKιIçÜ¢snØ9÷9çÜ/;ç:W‰½Â9·ß9÷çι/;çN9çœsÓι'œsqÎ}÷Ëÿdy½½ÞÇŽ¸jè±oqÎýçÜÝιÃι¹òå°sîcιW¯s½ ë1ç\³sî…å\XÞ¿KésJ`Î×–×SºîF�s_qÎ}Ô9÷Æ*1¾ÎË»WÈA�m«õ K¼Ñ9wGùùùëι çÜYçÜ�sîoËÏÇ]ëXo#{°Õ9w�sî÷œsÿ\î—Åòõ?:ç~Í9׿FŽWÔÑ;}UrÐ?ÄZcØ*qR¹/4²Æ –ÛÈþ‰q|f%=¸A¬Õƒå±«–çãß[§zÙƒ1ŽßÃP·zÆBçÜ%ιÿê’ã¬a—Ì%sÉ1å»�s—¯C½�ìCz(OÞû‹æ"é>I^Ò/WùÿÏ•ÿ¥Ë»×¹ÖWH]¥ž¿“Ô™qI_e³’Þ¸FŽŸ–4·J/騤çU‰¿}�Øôò!IÍUr¼PÒ²¤¢¤íyïgóeµ“ôË5në¿–Ôºµ6´ÇÊù«>Î:suW¬Ûj—#UbkYç•—7¬�ƒÛ —5z°µÆ}तï\‡Z݃“5<Ö¢¤\£ÆZ{§¯Júgƒ\VëŸUb^•åù=C­�îŸÇgVrЃä²Vjõ×j•—ß[‡Z݃1Žß^QG#éaIit]+qÎí•t—¤IƒJž$}‹¤ÿ®¤^.éO3.êI?Zþû£’®”t‰¤’ô„¤6IâœûÎ*u¾DÉ:j•4,é%}k9Ç(甤gIºÛ9·u…4SJë/Iú~IÏ•ÔWÎó“’¾T¾ß[$ýúJuxïÿUÒ'%í�ô««>b4L =–ž­õ IïQ²ï”Ô/é”ôœ$]¯sûN£jÝ«õé±Ô7$ý…ÎíÏõÔºMÒg”¬Û%I¿/é»%]*iŸ’¾yŸ’ ¦•l«áòDù¾£’î¹0=¶1ÔЃ^ÒW$ý¶¤“t•¤]åËwKú=I Júòoœs—5°Ö½j|n“´(é’~JÒ’zË׿¢ä�¶’îrν°†|ߦUúÈ{_\)ˆþÙêtÎÝ(éÏ•ôÀ’~FÒ7+y]÷"%ÏÏ(yíÒ¨Z÷j}_ÏÑCë-ïÙÒõº(™U÷’~}•û¼JÉ„]sùö^�›)÷:Öz°¼Ì9IW¬ðÿﮨëšÀe¯¨¨co†ÇKÿ¿„ô�’7}½¤�…<¿g¨µáýSC 5ŸYÉAÚ¿ÔÒƒ:wfåGs®ÕB®yüÆÆ%`[×Ò‡{%M—ï÷€¤­9պǒôPŽ—‹âÌJçÜ·*yQ"%l+òÞÆ{ÿ˜÷¾aﬥü]éwÏ}Ø{ÿä wû-%ï IÉÙŒ!~^ÉäPI+Ìð{¼#!Ißëœ{Á 9^X¾þš÷þŸ«,§ò�Œš¾ó‚:*r´+y'}%/鸒³A²Þå ›ZzÌ{Ð{ÿ×¾üŒ½Âÿ/éü}ñ5q«L¬W�yï½÷ã½]ûÞ+sÎ]!éÊ7ïðÞÿMh®UüTÅß¼Êýè1ÃjçjðW’&Ê_•©¨*Ö±ÿ³÷~p•ÿÿ{I÷–o¾Ê9ײœÑ?†…ô�sîÅJÎö›�´¿1•­¸Üõ:N\UÇgVrЃ†EÃÎJªöã·蟋@}x›¤%ãßO”Ÿ‡×•¡>¬=࢘¬”ôÖòõ¿TÙ‘-ù!%“ˆ’ô—+Ý¡ü„ð©òÍpε,çºòõß{ïOW¹Oåò¯[áÿÓ'¦'ŸÊ*'~‡j¬íB‹ϯt‡òØŸ—o¾u¥û ¡bõØW*þÞ•!ÏjÖ«Çbø$9%o*üNìä邤7—o>î½ÿbµûÒcæEéÁòv.•o®ø|�¥LŸs¶(ùhiCÐ?æÕÕ?ιfI¨d?þ•UŽ£ÁRÿ¬y|f%=h¯ÕêPÏñ[ ôÏEcÍ>tÎíPò•]’ôÇÞûÐ×÷YåÞ‡õ ‡Â\,“•?V¾¾?×*j“žÉ²$iµ�çŸÊ×mªó�hç\¯’Ó·%é Õîç½?¦äû$é;V¸Ë¡òõ·T9óR:÷ŽÇ‚¤‡ê©S’Ê¿à•n¿1I_[åîéYgßV~gë'V�]Zñ÷DÕ{eÓð‹èµi-•ι‚sÎEÊŸþr-ïÊÓcvEéÁòwí(߬vÆ|V–z°ò9g²–çÜ–ÀeÑ?vÕÛ?¿¬ä;¹¾ ä‡]Ö“‰þ©óøÌJzЮ 1¬|<´Þ¯g-ô`½ÇoOc Ã*jéÃk”|ý†tn"P’ÔàO¨\(·>¤‡ÖϦŸ¬tÎ=[ÉwÇI�{áSúQéSÞûÕÞa>¼BL­*›ãkÜ7]ÎJ˸]É÷.4IºÇ9÷“ι�ι6çÜ·:ç(ùˆ”—ôNïýÑZŠs‰Ëœs? ä‡E^Vþ¯_óÞŸ]%ô‹:w&ç÷Õ²,d¹Ç~¬âïªz¯lÖ£Ç2+ÿ°Îå›_tÎmsÎÝæœûº’ÉÿEçÜW�swdø!”Ÿ._/©¶�]ÑceíAç\»sî çÜ-’þºüχ•|çc#˜èÁò;ì¯+ßü²÷~f��O:çæ$-8çæœs�”{r ÆEÒ?ÕÛ?ιoRò=X%I?ï½_n\u+Ê­2ŸYÉA8†½Ú9wLÉ÷y—œs§�sw9ç®�ôfîj,Œaõ¿IŒaXE}ø’Š¿¿èœ»Ú9w¿snZÒYçÜ„sîoœs?Ò¨ZËòèCzh�múÉJ�ûÞicLV¦C;³Æý*O¹®÷£k•÷¯u9ÏX†÷þ°’Fûº¤Ý’þDÉ/ÏJz\ÉDåJ~äkåœûœsÎKZ–tJÉ»WK:-é?yïW}á\~¡™~¤�'€õ¥Çœsý:÷�•Guî»äb[�‹a·Î=G—”ü€À¯Jú¦ò¿7Kzޤÿ"éßœsßSOòòÖéDÍgVûn¿=fVÝ=èœ{…sΗŸsg”ü’ão(ùEÃ{%}w “w¡¬ôà-:wfÊk¸ÿ $¥!jUrfݯJzÒ9÷£kÓ?fÕÛ?Rò݈¿ã½ÿÒZwn€uÇgVrЃf…GHÚ£ä«rœ’³äDÉ™^÷:çº#Öw¡\ǰ�ã·2Æ0¬¦Ö>|Vùz\ÒÛ$}VÉŠv”ÿ½KÒHºË9÷gå¯Mi„<ú�ZgÃdåóÊ×3Þûµvf ÒF_ë{{æ*þî \F=ËYqÞûC’^¯d"e%»uî#ç!敼ˆ¼§Æû½|ýí–‰údî±ò@öç’zËÿôKµža`=z,†íÿ’¤oVrÖ[:Pî’ôÿ)9Ër‡’wûj}gO’Þ¤ä{ú¤ú>BD�ÙsœûGI¿Ûàïà˽�sWKú•òÍC’>R宋’>¡ä»Á¾]I_¶+éÃßRÒÛ$ý…sîU5,šþ±§æþqÎý´¤WJ:¦äìÊ<äÞ?˯çøÌJzОzưÃJzïeJ&M¶JºLÒ�êÜGA_#é üxxÞ=XÏñcjUk¦¯O:tîl~VÉD`›¤ï•ôùò}n”ôÞø¥>½|©ñ}Håèb˜¬LÏšþEÞu–~taµ­‰µŒLËqÎ5;ç~[Ò—•¬çÿ¤db²WÒ‹%}@É߇œs­ác¯QÒì]’öIºAÒ¿IúuI�9ç^¶Jl*ÝÎý«Þ 1Åè±ßQòP’~ß{ÿÉ,­a=z,†Êçç-’î”ôãÞû/{ïÊ¿6~‡ÎýdŸ¤wÖ‘?ýÑ„¤OÖG�ÙÒƒ+y¾ÝVŽÿNIïSòñž¿qÎý±snë*ñYäڃι+”üêy³’ýÿÍÞûÒJ÷õÞÞ{½÷þcÞû¯xï'½÷så>üU%guÍ—sý^ gÐ?öÔÔ?å³ÿ»|óxæñZòèŸÇgVrЃöÔ<†yïÖ{ÿïý?xï�{ïÏzïÏxïï”ôÝJŽ•$éûuîhbËû8²æã7Æ0Ô¡Ö>L_Ÿ´(™ÈûïýG½÷#Þûyïýç•|¯å¿–ï÷ËιKWȓպô!=”/&+í™._·­q¿ÊÿŸ®z¯Õ—QÏrVZÆo+ù’ùaIßå½ÿ°÷þ¨÷~Ì{ÿ¨÷þJN—’�õ?­¶ rãO{ï§¼÷G¼÷!é»”|$±_É÷bî^£Þ‘òõŽuøÎ$2õ˜sî]’~±|ó.Io�QÔ*Ö£Çb¸p™·–Iî<Þû¿’ôXùæëkI\ž¬ùÎòÍ¿\ã»^.D�ÙSwzï—ÊÏ·ÓÞû¢÷þ‹ÞûÿŸ’7 JJ&Á«µJ9ö sn�’¯'Ù¡ä+K~È{_÷�z¤¼÷ÿ¨ä�9Iú�ÿ]N+¡쩵Þ¯d¿¹Ë{ÿ鯖´ªuïŸÇgVrˆ´(Êkµò›NoUòÜ.I?‘%ß*òò¿=c*ÔÚ‡•ûò'½÷ÿrá|ò+Ü¿^¾Ùªä�¦ØL¼ž£‡ëb˜¬ÜhŠåëµÞ�¸¤â÷Z}õ,ç¼e”ß!I'"Ï{¢JüÿÒ¹/¶}K=EJOx¤“W]5ä é7çÜ~�û(Ý’Þä½_ªÅzôX ÕË÷Þ?¾Ê}.__å#?]ñw]¿")zlSóÞÿƒ¤�•oþ‚s®½‹É¥ËãÖg•|tð¬¤×—ÏÈê_¼V–‡uæœ{¹’³ø¦Õø7ÔÖbb 8>³’ƒÜļ÷#J¾ÎDZûù8Tž=˜åø­Æ0Ô£òõÉ߯r¿‡+þnÄGžMŒ…eôPƒ\ “•é—ªö®z/;ž(_”©´š}+ÄÔ» )ù±ŽÕ¤Ë¹pß%©PþûPµàò™`éÿ[­^�ã:÷ĸÖ@º�‹+�…††ê1çÜ[$ý�òÍP2qШ𤋮´=–™÷þ”’/¯–¤±5î^ùÿ]«Ý±üNéÙ_+¿#XzÌžØãÜÊ×­:÷F1­{–�à3JÞõ^RòÆÈYrV¨üòöî5îKÿØSKÿ¤ÇJ�’ާ?NUyQÅ$BÅ¿®õšÃê<>³’ƒ´'ö–æëŽ”ïB¹ô`„ã·jà ÕÞ‡ÿ^ñ÷j¯Oj~mÈÌX(z¨a.†ÉÊôàe£LV¦§R7kõÓˆ¿«|=§óŸ4ÖT~×ñhùæK«ÝÏ9÷,I;/¨+UyJõZ�ã;%jý%±t;¯z/ÄTw�9ç~BÒÿ,ß<¤ä£˜³«„ÄÔð‹(�è_kÝî¨ø{|�û~¿’_ДÂÞ•§Çì‰=ÎÖ¾K&ëڃι.IÿGÒ•JÆ¡ŸñÞßšo—Uü½Ö ô�='fã—^×3=hOìLŸ“×z>•Wf=~«†1 Rí}øÅŠ¿wT½W}¯MBX 项&+ÓŸ‡ïpÎ]¶ê=m¸WÉY’ôÆ•îPþуëÊ7ðÞÏ­t¿5¤§+¿|•õR¹üO]ðƒW}‚(Ãw”o­v¿Õ8ç®Ô¹Æþúj÷Uò‹ÉÒ¹íŽÆ««Çœso�ôQ%Ï?ÿ&é½÷“�+ïÖ«ÇbH'TzË}PÍËË×_­aÒ7=ûÇKúÓ€šè1{b�sßW¾^–t$B¾ ­[–?Æ~Ÿ¤«Êÿô Þû? ɵŠ7TüýÈ÷¥ì©¥î–ô¢5.•ßc™þÛªßÕÈÌVçñ™•ô =ÑÆ°ò×}|wùæZÏÇ¡òêÁ¬ÇoÕ0†Aª½ÿIRúkáß·Êý*ÿïÑ,…Uaf,=Ô8ÞûM}Qrê¯/_®­3voEì»×±æ�•—9+éŠþÿ]u½*pÏSÒà^ÒGVøÿ~%OD^ÒçWøÿVISåÿ?#ig•å¼µ¢Öß¾àÿ:$íY£Î6IW‘ãå«Ü·SÉCxI?Ÿ÷¾w±\êé1I?(i¡|ß'%]šSÍ ï±*Ëýhš·ÆûoWò�,^É‹e·Â}~¢¢Öw­‘¯SÉw®yIÔO�¼ÔÚƒ’®Xiºà>?¬d’ÒKz¨�5¯Ç8·UÉwá¦yö×ß²Ös”’_�¬|NkZå¾ô�ÁK=cØyêz~ÏXsCû'Æñ™•Ü—4x©c ÛµFž­J&0Ò\76°æu=Ž 9~c ãRÏ¥ž±PÒ-åû•$½t…ÿo“ôåò}Æ%moPÍ� 顜/¹°.R:VÞ)~c�ûõ+ùXtzy}Åþ‡üß‹ªäxwEÌ+ëý¦rc{I'%ý˜’/�}¶¤ÿ¦s/$ï^%ÇçÒ:V¹Ï*jýˆ’ï”ìWò‹]ÿ^þ÷³+= •ão©ˆ?*ég”üpA·¤(ù>ÂtBtLLh*™ ž/?ѼYÉjŸ’wÇ¿MÒ/HújÅ2®±Þ^QqßoÍ{¿»˜.µô˜¤ï-&^Ò IÏ-?iW»´Uɳ‘zìEtWE�¿¹ÆóM§¤æ rt+ùØÐŸ*y×þ¹åÞë+÷áû%-–ó/Jºf�ÇLÿ½ÔÒ?5äøèZcBù~¡ö*ãñ™•ä£�^jéAI¿#é1I¿*éeJÆ®íJ&Y~Jç&G¼’S[ñ͹�Ѓ+Ü·îã71†q©óRK–ï×®ssc’þ³’¯(Ø!éÕ’þ¹b_y[•æû�Êÿ’{ëò ¥ß.ï‡Ö¸ßÏTì@k]ŽTÉ‘¹ñÊy®.7µå?,iÛ*ñk€J¾—ìÎU–1'éÍ«Ä;I¬a]�–ô½+Äï­q]/+ùUñ–5ÖÙíåûÿ[ÞûÜÅv©¥ÇTñB®ÆËçªäÙH=v¤ŽÇû3«äù5�›DZéòUIßRÃc~¨|ÿ)IëŒ3z‰ÜƒGVzήÈc¾Uûøâ«=%©µÄUÃyô�ÝK-ýSCާûk�ûm¦þ©z|f%ÇùèA£—ZzPÉde-ûÃ_¯±ï›ïÁî[÷ñ›øÔy©¥+î»WÉÖ¬¶o½w•xó}Hå¹¾³R’þ |ýιç6xYéO*ù>¾ Þû‡”üÀû%}MÉ;Ëc’>¯ä�äWxï§²ê½/yﯗtƒ’w ‡•œÆ|DÉ6/öÞÿù*ñÞ{ÿ6%gÌý‘’SŸg”œÞ<ªäžEÒó¼÷ÿ°BŠJÞ}ùM%áù†’�8,JQò‹´wHz�÷þç½÷‹Õj)7æ›Ë7?TËãGTôXyïCÉw0ý™’w=ÏJš�ô�’þ‹’3ྺZŽòf½¢|ó¯½÷3õÔ@�™WKÞ*é-’þ\É(g”<_Ï(yþý„¤ŸUònïJÏÙ©‹¥§$ý¤’7åþYÒq%oâ-(yî3’~YÒs¼÷wWK"Ñ?cØùbŸYÉ!‰ÜjéÁ?Pòºân%¯9F”Œa“J¾ÿí•| À�®±ïo„|Z†ã7Æ0Ô«æ±Ð{DÉ'Èþ?%û׸’×'G•œ‰øïý­«¤Ø}HåÌ•gy7=çÜCJ~Eí7½÷·4p9Ç”4ß-ÞûßlÔrp>çÜ÷)ygdNÉÇÍÇs-è"D�mnô˜}ô ]ô�}ôÏæFÚGÚEÿ\<èÃÆ ‡Â\,gVJÒo”¯ÿcù×A£+¿±GÉw&hÄ2PÕ;Ê׿Kóç†ÛÜè1ûèA»èûèŸÍ�´�´‹þ¹xЇ�A¸h&+˧ß§äKW¡A‹yUùúVïý\ƒ–� 8ç^(éG”|dÓ¿3c=¶yÑc=hý³1Ð?›=¸1Ѓ6Ñ?ú0>z(ÜEó1pIrÎ=OÉ÷t%}“÷~6ç’�sî“’®“ôïýr.ç¢F�mNôØÆAÚCÿlôÏæDnô =ôÏŇ>Œ‹ wQMV°ë¢ù8Û˜¬`“•L`²€ LV0�ÉJ&0Y À&+˜Àd% ±9ç¶Jº²|sXÒR¬ÜÀ&Ð,©¿ü÷cÞû…zÐcÀª2õý¬Š1 h,z h,Ž�ÆÉ<†­$Úd¥’æýbÄ|ÀfõI‡âè1 6!=Fµa ‹‹ãD qBǰgàcàLˆyfåpúÇó;Þ¨-MAI:w4ëE¯ïÔ›n¸R—\ÒYwüÐдþâc�Ç[Éa¡†9,Ô#GÖøS§FôÝßõ éÍáÕÌ=–ö×£wMkz$ìÓ Y{TÊ{XÉa¡†9,Ô¡Ç¢Ža1z,4‡…�‘ÃB 1rX¨!k+c˜¯?òìÑÊó~e¥†9 ŒaçÅe=N\zHÒ¸ «¢Û«ùjéÛÿe^�Óa)¦;¥ûŽV½atPý¥³uǶè½;õæç”ti[X gæ¤?ÿZaCïW•9b¬‹¬Û#Æ>ºoN•¦ôG'ÿ÷Ó%”`b ‹‘ÃB 1rp¼k'G¤1ìbNV>½‡miêÐÖ¦mAIZ[šµ}{—ú40PŽææVmß¾=8ÞJ 5ÄÈa¡†9bÔP!ô;N2÷XÚ_­-N‹MaedíQÉÆö°�ÃB 1rX¨á!;wÔ1,F�…æ°Ð£1rX¨!F 5ÄÊQ–Û&Åë�<{´2Çž_Y¨!FcØyqY�K[$'+·x¶K}­[´mÑ¥hmuÚ¾½Mg'ti©þ—«…ÂVmß¾];w,j =¨5ÏJÛ‡Z6ü~•戲.2n�ûD¦}óŽ �ƒïæ_C¬eѾϕ��0�ÉJ&0Y À&+˜Àd%˜¬`“•L`²€ LV0�ÉJ&0Y À&+˜Àd% �HÚ¹£Y­-ÍA±í=Éüi±8ŸÆ…Æ[Éa¡†9,Ô#GÖø¡¡é ¸jB{,í¯ô:DÖ­Œe¿Ê¿†9,Ô³ÇbŒa1z,4‡…�‘ÃB 1rX¨!k+c˜¯?òìÑÊØ‹y¿²RCŒ–Æ0)ûq¢º$ɇ-¼+¹šípañ±#…– ø4®8\ÂÓ±y¿:/G„u‘u{ÄØ'‚÷ͳ^:¼øól–ãD+ã Ç»ù×�5Gì1,弈.LäÜnIÇ%iÿþýÚ¾}{”¼Àf011¡¤7÷xïOÔ›ƒªËÚcôPcÐXôÐX'�c [ICά|Ó Wj` /(¶XœÕÝŸzB_¾oZ³cËuÇ·÷4éù¯íÔµ×=W}}í™jÈ3‡…bä°PCŒYãO�*ê\ÿf÷è]Ójm©ÿÝÉ´?Bû«2ÇFÞVrX¨!F 5Äì±Ðþ’âöã`þ5ÄÈa¡†¬9¬ŒaR¼þȳG+s\Ìû••bä°4†IÒÒCRiK@`—Tx™¢ìÛ¥‡%M¥xºŽ+¾4¯ö™úO¬™ípzò­zÍØ)í(-•0RhÑý=zýÞEõµ¥PqNºëH˦é�Ðu‘®‡Û#tŸ(ÎÏ-w%›å8ÑÊ8Èñnþ5dÍ{ K5d²ò’K:50°-SŽÙ±eM -Ç÷õµg®ÁB 5ÄÈa¡†9Bã—–â �’4=²¤Å¦ðþÈÚ_ÒÆÞÖrX¨!FŽ›Æ†ÖP›õqäYCŒjˆ‘#küÐÐtPÜjùBz,溌Ñc¡9,ôhŒjˆ‘ÃB 1{¬sG³Z[šƒb³îÛ1rжrX¨!kŽØcXž=f¡G+c/æýÊJ 1rXÃ$iºSjmuuÇÍv”cº$ɇ-¼+¹ŠÑÁu”kxúñHcG -Á9ÒØâ\X|·iú#p=TƆn�áB)|áØ,ljVÆAŽwó¯!kŽØcXÊy8]˜È¹Ý’ŽKÒþýûµ}ûö(y�Í`bbBHoîñÞŸ¨7=T—µÇè/ :Æ0 ±è1 ±8N'ƶ’†œYù¦®ÔÀ@_Pl±8«»?õ„®½î¹êëk_÷øÊ_¾oZ³cËA9Ú{šôü×všxyæ°PCŒYãO�*ê\ÿfÚcÖú#4GÖþ’6Ç~e%‡…böXžcXŒz4FúÜNŽØcØ£wM«µ%쌧Xû••þ¸˜÷++5ÄÈai “¤7ŒjàìDÝq#…Ýß3 +¾4¯ö™°Zf;œž|A«JKš J!uI…—)sŸÇ¨!ƺxÍØ)í(-ÖŸn�×ï]T_[P *ÎIwiÙÐýQ™#t] Ž”«ÅòÃbä°PCŒ'ÚÉ{ K5d²ò’K:50°-S޾¾öL9²ÆKÒìØ²¦†–2å°ð8,ä°PCŒ¡ñKKóÁË\IÖ³ÒYsXØ'bä°PCŒyÖ³Ç,Œa1rXèÑ9,¬Ë9,Ôš#ö6=²¤Å¦|÷+ û¶tqïWÖjˆ‘ÃÂ&Iý¥³º´þ2¯}ÆkÛTÆOßMJ ýv²ìÌ=¡†ëbGiQ—–‚ãûÚ¤�°ù‡s96pœ—#p],…:ö,Œa1rX¨!F ûvŒjÍ{ Kñ;L`²€ LV0�ÉJ&0Y À&+˜Àd%˜¬`“•L`²€ LV0�ÉJ&0Y À„B#’ M«¹¹5(¶Xœ=ïz½ã+cÛ{ÂçrÓX �#Ïjˆ‘#küÐÐtPÜjùBzÌZ„æÈÚ_•±y¿²’ÃB 1{,Ï1,F =#}n'Gì1¬sG³Z[šƒbcíWVúãbÞ¯¬Ô#‡¥1L’† [T(l­;n¤Ð"IšípÁË~:¶K’|X’®ä*kŸÇ¨!ƺH×k½Ò¸â\p OÇnäþ8/Gàº8“a^(Ï1,F 5ÄÈÁq¢�±Ç°”ó>ð üÂDÎí–t\’öï߯íÛ·GÉ l:pà@zs�÷þD½9è1 º¬=FÕ1†�E��Åq"Ð81ư•4äÌÊ7Ýp¥ú‚b‹ÅYÝý©'tíuÏU__ûºÇÇÎñåû¦5;¶\w|{O“žÿÚN3�c³l�¼Ç©SE�ëßìB{̺Œ‘#kIçz,Fú#ÿböXžcXŒÖúƒq0ÿ²æ°2†I›o{díúÜÆ6µ4†IÒ›ŸSÒ΋uÇç¤»Ž´è5c§´£T¼”œ xÏ€®øÒ¼ÚgÂNŠ™ípzò­*=,i2 A—Tx™¢ìÛÁ5TÔº.Òõc{¼~ï¢úÚ‚R<½_läçݘ=öè]Ójm ;ãÖÒó.ã‡�}ÓB YsÄÃR ™¬¼ä’N lË”£¯¯=Sެñ±rÌŽ-kjh)×,ä°PCŒ¡ñKKóÁË\IÖ³°.cäÈÚ_±rÐù׳Ç,Œa1rXéÆA;5„æ°6†I›g{dí =#‡•í±YŽ/m“Â^¯J’v”uii!S í3^Û¦2~‚oRÒhȤP²Üûvx çêȺ.bl�¾Œû„´±ŸwcöØôÈ’›6þó®…bä°ðÜ#‡…BsÄÃRüÀ˜¬`“•L`²€ LV0�ÉJ&0Y À&+˜Àd%˜¬`“•L`²€ …F$šVsskPl±8{ÞõzÇÇÎÑÞ6œÆYy›e{äõ8††¦ƒâVËÒcÖeŒYû«26Fú#ÿböXžcXŒÖúƒq0ÿ²æ°2†I›o{díúÜÆ6µ4†IÒ™9©9 ”â\r=Rh ^v;Ûá‚s<Û%I¾þ]ÉUŒ};¸†Š:B×Ec{¤Û6D»‘ŸwcöXçŽfµ¶4ÅZzÞeü°±oZ¨!kŽØcXÊyøä{a"çvK:.Iû÷ï×öíÛ£ä6ƒ‰‰ 8p ½¹Ç{¢ÞôP]Ö£¿€êÀƢǀÆâ8hœcØJrfå›n¸R}A±Åâ¬îþÔºöºçª¯¯}Ýã­äHã¿|ß´fÇ–ƒjhïiÒó_Ûiâql–íêTQçú7»Ð³°.cäˆÙyö˜…u#‡…böXžcXŒÖú#4‡…1,F 5dÍae “6ßöÈÚô¹�mji “²'¾~ï¢úÚ–]œ“î:Ң׌�ÒŽÒbPŽ‘B‹îïÐ_šWûLý'ÖÌv8=ù‚V•–4T‚Ô%^¦(ý\G¹†Ðõ �[1¶Gè~‘î›å8ñÑ»¦ÕÚv¶¬¥ç]Æ�Í1~XÈ{ K5d²ò’K:50°-S޾¾öL9²Æ[É1;¶¬©¡¥\kˆ‘ÃB 1r„Æ/-Í/s%Y{̺Œ‘#FXè1 ë2FŽ·±M-�ai¾LljsáËNcG -Á9ÒØÙÿt\—$ù°"º’«ý\G¹†ÐõPc{„îiÜf9NìÜѬ֖æ XKÏ»Œ›cü°�#ö–rÞ>�_˜È¹Ý’ŽKÒþýûµ}ûö(y�Í`bbBHoîñÞŸ¨7=T—µÇè/ :Æ0 ±è1 ±8N'ƶ’†œYù¦®ÔÀ@_Pl±8«»?õ„®½î¹êëk_÷x+9bÖðåû¦5;¶”£½§IÏm§‰Ç±‘·Ç©SE�ëßìB{̺Œ‘ÃB •9B{,í¯<{TÚÛ#f�å9†ÅÈa¡†9bŽayæ°Ð£YsXäÍׯú#ÿýÊJ�Å\—¯ß»¨¾¶ *ÎIwiÑkÆNiGi±îø‘B‹îïÐ_šWûL؉9³NO¾ U¥‡%M¥�º¤ÂË”¹?bÔc]dÝ1ö =Æq¢�›í8q#?ŽùÅÆœYÙ�ÉÊK.éÔÀÀ¶L9úúÚ3åÈo%GŒfÇ–55´”)‡…Ça!GhüÒÒ|ð2W’µÇ,¬Ë9,Ô eï1 =#Gž5Äì1 cXŒjˆ‘#FXÈaa]†æ°6†I›§?,ŒrXÙ'&¢¬Ë6i lãi;J‹º´´ß>ãµm*ã§'%�†~ ;Yvæ‹PCŒu‘u{ÄØ',ô˜…1,F 5ÄÈaa ‹‘ÃB ¡9–³-³~`€ LV0�ÉJ&0Y À&+˜Àd%˜¬`“•L`²€ LV0�ÉJ&0Y À&+˜PhDÒ¡¡i57·Å‹³ç]¯w¼•1khï Ÿ“Nc-<Ž�¼=††¦ƒâVËÒcÖeŒj¨Œ í±4.Ï­ŒÝÈÛ#f�å9†ÅÈa¡†9bŽayæ°Ð£YsXäÍׯú#ÿýÊJ�E]—sÁ)žŽ)´ŧq³.¸†§c»$ɇ%éJ®²öGŒb¬‹¬Û#Æ>a¡Ç8N´‘c³'näÇѲØ,M/º*ç}àß…‰œÛ-é¸$íß¿_Û·o�’Ø &&&tàÀ�ôæïý‰zsÐc@uY{Œþªc ‹‹ãD qbŒa+iÈ™•oºáJ ôÅ‹³ºûSOèÚëž«¾¾öu�·’ÃB •9¾|ß´fÇ–ëŽoïiÒó_Ûiæqäµ=N�*ê\ÿfÚcÖeŒj¨Ì‘µ?Bã+s\ìÛ#f�å9†ÅÈa¡†9²ö—·Ç.æqÐÊ&m¾þ°0~ÐùïWVz̺¬Ìñú½‹êk ˆŸ“î:Ң׌�ÒŽÒbP #…Ýß3 +¾4¯ö™°“{f;œž|A«JKš HÐ%^¦(=\CE¡ë"]1¶Gè>18RR¬ã8ÑFŽÍvœ¸‘ÇübÜO¤2YyÉ%�Ø–)G__{¦Yã­ä°Pƒ$ÍŽ-kjh)×,ä�_Zš^æJ²ö˜…u#‡…¤ìý‘5^²±.ò¬!f�YÃbä°PCŒ1úÃB ë24‡µ1LÚ<ýaaü°�ÃÊöà81aa]JR_›46"IÚQZÔ¥¥…L5´Ïxm›ÊøIÄII£!ÃN–£GÃk8WGÖuc{„îKáŸV~ cXŒjˆ‘ÃÂ#‡…Bs,,g|~ª‚Ø`“•L`²€ LV0�ÉJ&0Y À&+˜Àd%˜¬`“•L`²€ LV0¡Ðˆ¤CCÓjnn Š-gÏ»^ïx+9,ÔPÛÞ6¯�ÆYyym�¡¡é ¸Õò…ô˜…u#‡…*c³öGh|eìž=böXžcXŒjˆ‘#kUÆæ™ÃB�fÍae “6_X?è�ü÷++=fa]ž—c.0¾7Rh ®!��ípÁ9žŽí’$_‚®ä*F�×PQGèºHãbl�Ð}âL`ÜJ8N´‘c³'näÇѲØ,M/º*ç}à“Ö…‰œÛ-é¸$íß¿_Û·o�’Ø &&&tàÀ�ôæïý‰zsÐc@uY{Œþªc ‹‹ãD qbŒa+iÈ™•oºáJ ôÅ‹³ºûSOèÚëž«¾¾öu�·’ÃB 1r¤ñ_¾oZ³cËA5´÷4éù¯í4ñ8BãO�*ê\ÿfÚcö‰9,Ô#GÌbôXh =³ÇòÃbä°PCŒöí9,ôGÖVÆ0‰þ°TCŒ›­ÏCãçãžYùæç”´sÇbÝqÅ9é®#-›f¿zýÞEõµ•ðôºxÍØ)í(Õ¿.¥älÀû{tÅ—æÕ>Sÿ B³NO¾ U¥‡%M• uI…—)J×Q®!t=HçÖEèö85=£XÃlj6r0~ØÉ{ K5d²ò’K:50°-S޾¾öL9²Æ[Éa¡†9fÇ–55´”k 1r„Æ/-Í/s%Y{̺Œ‘ÃB 1rĨ!F�e͑續Ùcư9,Ô#‡…};F ë24‡µ1L¢?,Õ#‡…�‘#4~a9[ݺ´M›?�dcŸˆ‘£/ãz�¤¥E]ZZÈ”£}ÆkÛT†O3NJ ý8z²Üý^GRCæõ ðíQ*�Í´ÜJư9,Ô#‡…çþ9,Ôš#ö–âv˜Àd%˜¬`“•L`²€ LV0�ÉJ&0Y À&+˜Àd%˜¬`“•L`²€ …F$šVsskPl±8{ÞõzÇ[Éa¡†9Ò¸öžðyñ4ÖÂã�šŠ[-_H�YØ'bä°PCŒ1kˆÑc¡9,ôhÌËs ‹‘ÃB 1rXØ·cä°ÐYsXÃ$úÃR 1rl¶>�oYl–¦‚BWtfNjØ$Źòõ&Ù¯ÒÇ”£;Rh ΑÆÎv¸ ø§ãº$ɇÑ•\Åè�à:Ê5„®‡ÊØÐí1\(/ûB'ÚÈÁøa'Gì1,å¼|â»0‘s»%—¤ýû÷kûöíQò›ÁÄÄ„8�ÞÜã½?Qoz ¨.k�Ñ_@uŒa@cÑc@cqœ4NŒ1l% 9³òM7\©��¾ ØbqVwê ]{ÝsÕ××¾îñVrX¨!FŽ˜5|ù¾iÍŽ-åhïiÒó_Û™Ûã8uª¨sý›]h�YØ'bä°PCŒÖú#4GÖþ’lõXžcXŒjˆ‘ÃB 1rXèѬ9æãžYÉq"�ãÂø�Ü1âc÷ØF5pv¢î¸‘B‹îïÐë÷.ª¯-lÙÅ9é®#-&ö«5ÄX¯;¥¥ÅºãÓíqÅ—æÕ>v‚Ñl‡Ó“/hUéaI“A)¤.©ð2eî�5„®‹âü|à‚Ÿ‰ãD9?ìäˆ=†¥2YyÉ%�Ø–)G__{¦Yã­ä°PCŒ1j˜[ÖÔÐR¦y=Ž¥¥x¤”½Ç,ì1rX¨!F+ý‘5Gžë2f�YÃbä°PCŒjˆ‘ÃB�†æXXÎ¶Ì Yè1 ûDŒjˆ‘c#÷GŒøØ=Ö_:«KKá/óúÚ¤�°ù‡s9 ìWQjˆ°.v”uii!8¾}ÆkÛTÆOCNJ ýv²ìÌ=¡†Ðu1¾úŸÁÂ#‡…bä°ðÜ#‡…BsÄÃRüÀ˜¬`“•L`²€ LV0�ÉJ&0Y À&+˜Àd%˜¬`“•L`²€ LV0¡Ðˆ¤CCÓjnn Š-gÏ»^ïx+9,Ô#GÌÚ{ÂçÖÓØ¼ÇÐÐtPÜjùBzÌÂ>#‡…bä°Ö¡9²öWe¬…Ës ‹‘ÃB 1rX¨!F =š5GËb³4¼ègà8‘ÇqaüFî�ñ±{l¸°E…ÂÖºãF -’¤â\ø²ÓX ûU”"¬‹t½Ö+�›ípÁ5<Û%I>,IWr•µ?bÔº.¦Ã6ÁŠ8N´‘ƒñÃNŽØcXÊyø„qa"çvK:.Iû÷ï×öíÛ£ä6ƒ‰‰ 8p ½¹Ç{¢ÞôP]Ö£¿€êÀƢǀÆâ8hœcØJrfå›n¸R}A±Åâ¬îþÔºöºçª¯¯}Ýã­ä°PCŒj¨Ìñåû¦5;¶\w|{O“žÿÚÎàN�*ê\ÿfÚcÖ¶ÇfÙ¯xÙúK:×c¡9æã�Y™ç#‡…bä°PCŒú#kŽ˜ý%qœ#‡…bäØ ý#>v�}û¿Ì«¯uKÝq³NO¾ U¯;¥¥Å e�ZtÏ€^¿wQ}mA)Tœ“î:Ò²iú#t]¤ë!Æö¸âKójŸ ;I)Ý/JKš HÐ%^¦(=ZÃÒÙ Å®ˆãD9?ìäˆ=†¥2YyÉ%�Ø–)G__{¦Yã­ä°PCŒj�¤Ù±eM -­{ KKóÁË\IÖ³²=6Ë~ÅãHdí¯,9–³-·’…1,F 5ÄÈa¡†9òì�¬9bö—d£Ç,ì1rX¨!FŽ�Ü1âc÷Xç´´m1üÓs;J‹º´´�©†¾6i lã\ŽÍÒ×EŒíÑ>ãµm*ã'*'%�†| ;YnŒ ®¡þQú YÃbä°PCŒžûcä°PChŽØcXŠØ`“•L`²€ LV0�ÉJ&0Y À&+˜Àd%˜¬`“•L`²€ LV0¡Ðˆ¤CCÓjnn Š-gÏ»^ïx+9,Ô#‡…*cÛ{ÂæçӸІ†¦ƒâVËÒcÖ¶ÇfÙ¯xÙú«264GËb³4¼øóä9†ÅÈa¡†9,Ô#‡…þÈš#fI'ÆÈa¡†96CĈ�ÝcÓ�Rk««;n¶#‰)´/;�-Χx:vÓôGàºHãbl�tÛ†x:¶K’|ý º’«=\ÃY/ ^üy8N´‘ƒñÃNŽØcXÊyÐì+%rn·¤ã’´ÿ~mß¾=J^`3˜˜˜Ð�Ò›{¼÷'êÍA�Õeí1ú ¨Ž1 h,z h,Ž�Ɖ1†­¤!gV¾é†+50Ð[,ÎêîO=¡k¯{®úúÚ×=ÞJ 5ÄÈa¡†9Òø/ß7­Ù±åºãçãžYÚcÖeŒjˆ‘ÃB 1rdí)yïù¯í4ÑcyŽa1rX¨!F 5ÄÈa¡?²æ°2†IìW–jˆ‘c3ôGŒøØ=¶ô�TÚØ%^&]ñ¥yµÏ„�Ð2Ûáôä Zõš±SÚQZ Ê1RhÑý=zýÞEõµÕ_œ“î:Ò²iú#t=HçÖEŒíº_¤ûDéaI“A%<½oZè1Žmä`ü°“#ö–jÈdå%—tj``[¦}}í™rd�·’ÃB 1rX¨!FŽÙ±eM -Õ·°\Ìj²ö˜…u#‡…bä°PCŒ¡ý#G̳0†ÅÈa¡†9,Ô#Gžý‘5‡µ1Lb¿²TCŒ¹?bÄÇî1�;©ò±ßd"ª}ÆkÛT¶Oßí(-êÒÒB¦}mÒ@ؾʲf%½q•øJZ–T”´=ïíË%ÿËj=VþÿIŸ¯¡¿þ{ƒë¤Ç¸lÈK =v©¤ÿ»F}P’kp�ôs—ú'Ê~+é’FWÉõw’:üXéA.ëzY�þZ¯ýÚJô— ¶gC{LÒ/¯_yùkI­ ~¬ôØ¿pfeœs¯”ôIg$ýÏîò›’¶­qù©Šû´�å¦f$=,é# ʇ¤-ÿýQIWJºDÒIzBR›¤?qÎ}çJÁÞû•ôII;$ýjƒjÄQC�IÒKúîòߟ’ôrI’ž#ég$-ÿßMιÿÔ°bϡǰa¬Õcι‚’¾J÷§Jz¾’ýçÅ:7nýgIÿµÁå¦è1˜Pã• Þo�s{%Ý¥ä͹AIoT2Î}‹¤ÿ®äÅÏË%ýi½¹уh¸õê¯È9b ¿°.֩ǺÊ×ß�ô%cÕNIý’~ œS’®×úÌ…HôØÆ•÷léFº(yÛKúõ 9î.ç(Iº¬�µ¾JI£4—oïUäw$=·ü8¼¤�¬ðÿý’†Êÿÿð*y¾OçÞuèÎ{;sÉï²V�IzvÅ~|O•û|³¤éò}m`­ô— w©¡Ç~¶b?¾µÊ}>PþÿyI—7°VzŒ‹©K-Ç�1ö[IË÷Ÿ“tÅ ÿÿ×4ðñÒƒ\Öí²ŽýÕðýÚJô— ¶cÃ{LÒ�J&îVüô�¤fIS‘ó% |¼ôØ¿pfe�œsߪä�Iú³Àý’~°|óÿxïOǨm%ÞûÏxïóÞ/5j’~^ÉNI+¼ à½Vr€$}¯sîUòü½¤ãJÞuøÉÔ‰  Æ{aÅß+ÞÇ{ÿuIÿX¾¹êw„dA�a£©±ÇÞ\¾“ôߪÜçV%•[%ýB´/@�Á’Z�³î·Î¹>%gRJÒ‡½÷O®p·ßRòqIúÅ�åÔ‚ÄzY¯þŠ•#ú ëi½zÌ{Ð{ÿ×¾<·Âÿ/éü}ñ5!Ë©±zlƒc²²vo-_ÿK•ÇZ¼YRKùï�f®(ו¯ÿ~•‰×¿\áþç)?™ýyùæ[Wº. µôØBÅß+‚eé 4”¹ª|Ñcˆ©–{aùúŸ¼÷ +ÝÁ{?.é_Ë7ß«¸œÐc¨UŒãÀZü�’>ÒùûÞÓʽù©òÍpε6°žF£!­_]lè/¤,õØW*þÞ•[qÐc Ädeí~¬|}†?]¾Wòqð Ë9׫äTjIúBµûyï�)ù¾%IúŽURþMùúÛÊïüàâSK�=ªs‘?¶Òœs»uî;-ï‹SÚú£ÇеôXwùzl�\#åëç8ç¶g)*/ôêã8°W•¯—$}q•ûýSùºMÒ†ÜßèATX¯þºhÐ_¸€¥»´âï‰ÜªÈˆk<&+kàœ{¶’/†•¤Ìñí’^T¾ùÕÎXÙ@*èkÜ÷pùzµ�ä~Qç&¡¾/´(lLµö˜÷þ„¤?(ß¼Þ9÷Çι:ç:œs—8ç~DÒg”|¹óZ¿iz ÑÔ1ŽM–¯{ÖHÙ[ñ÷F= ¢ÇP“Ç�uH÷±SÞûùUîw¸âï†}åIƒÑƒXïþº˜Ð_�d²Ç*O8ù§ª÷²�k0&+kóòŠ¿Cü§+þþhx)fôUü}f�û¦Åí«vïýŒÎ�Ns^|êé±·+ù��’¤ŸRr¶å´’ýð.%“,·KúÞûbüR× =†˜jí±ty©snëJw(ŸIù¢ŠªºßG�¡V1Žk•îcµî“•1 =i}ûëbB!e¦ÇÊ¿á‘~·ãQI÷æXNVôXƒ1YY›ç•¯g¼÷kíˆÏàœkVòËX’ô¤÷þÿF«,?¯öο”üš¥$u®q¿¯—¯¿=¨"ld5÷˜÷¾$éfI·(™°¼P—’w7ê‹·=†˜jí±;Ë×½’nªrŸwKªü޼mÙJË =†Ze:¬Sº_ÖºOJkï—Vу�Ö·¿.&ôR&z¬<'òç:÷éœ_òÞŸÍ«žè±c²²6ýåëÑUïUÝ«% ”ÿþãìå˜à*þ^í‡Nê‘®ßþUï…ͨæ+ÿŠÚJ~©øc’¾KÒI»%½IÉ»t?%éÿ:ç¾³!Õ®z 1ÕÚcÿ[Ò×Êÿ†sîýιowÎõ”¿ráÃ’~Iç”-Ç-uÝÐc¨UÖãÀz¤ûe¬}Ò2zÒúö×Å„þBÊJ�ýޤW–ÿþ}ïý'ó+% z¬Á˜¬¬MÖÿ©òõ²¤?Í^Ž Ó·­qßôÿ§W½×¹lØáœs«Þ›MM=朻\ÒßIz–¤ßòÞÿ”÷þ ÞûQïýIïý_Jz©’É–>Iéœkidá D�!¦šzÌ{?+éu:÷ÎîÛ%=VŽ{TÒÏI:%éÖŠ°ñ˜…®#z µZÏzé>Vë>Y³ÑЃ�ìL¤l6ôR¹÷˜sî]’~±|ó.%Ç—=Ö`LV6Xù»½ÒŸ¨°ü!›Aåw^Zõ^‰KÊ×#«Þëüw'€•Ü$i»¤I¿¾Ò¼÷’~³|s¯’3›7"z ¹ðÞUÒ %½SɯNHZPò&Àû”|4e¶"äø:— =‹Òý²Ö}RZ{¿´Š‡þ‚ ιýJ¾BH’�ô&ïýRõˆ ƒk°BÞlé¢ö®z¯•ý¸Îͤo–�€KÉÇpSß´Æ}÷­³’tý½÷ÃÇŸpN­=ö²òõãÞû¹UîWùåÑߦ�ùåÍôbªkóÞOKúïåË38ç^RþsZkïwVÑc¨U–ãÀz=¡äcrιÖU~|ß1=i}ûëbB!•[�9çÞ"é”oþƒ¤×oð𤋮D�5gVÖf¸|ÒàéGÀ'%}"N9ùóÞ�(ùn@)ùØíŠœsÏRòc'’ô/k¤M×ïðª÷ÂfTk�¥ÿk=y7â;DÖ=†È²Œcç)µÂkË7ÿ�÷~C~g%=†:D럤ûX³¤—¬r¿ï*_ÏIú÷†VÔ ô ÊÖ³¿.ô*äÒcιŸ�ô?Ë7Iú¡ò× m ôXã1YY›ô'ä;œs—ÕäœûfIß[¾ùñ5ÎÛˆî._¿|•õòÆŠ¿?µF¾o._eÕ{a3ªµÇË×ßæœ[í»A*_à­z/ûè1Ä4ŽUñ3:÷ýG�1WÞè1Ô"fÿ¬å^IéÇãÞ¸Òœs[uî+†ØàÇ—ô Ö³¿.6ô¤zÌ9÷IU2ßôo’~Ð{?¹Ë^gôX1YY›¿«ø»ž_þ©Š¿7ÓGÀSÿKÉ�tî;ŸæœëWòƒ’ô�Þû­–È9שäãºÒùë‡Z{ì³åëI¿ºÒœs]’~¥|sAÒC™«Ë=†XBDZó8ç^ ä{+%é~ïý™ªÊ=†ZDéŸZxï‡%}¼|óçœsW¬p·›uîì‹6²žu@bÝúë"DAZçsÎý ¤�)ù„ÀW%½²|âfD�5“•5ðÞÖ¹¨©ÁË¿Þô“å›ßðÞ?\ëòœsïvÎùòåõÔZ‘£ß9÷Òô"éEÿ½»òÿœs/ª’ãsi+ý¿÷þq�;HþYçÜGœsßV^ök$ý½’/“]”ô_Ö(ù*%OhÍyÑ©£Ç~_çN‹ÿ5çÜ9çþƒs®Ç9·Ó9÷c’þIÒ·”ïó~ï}ñÂ$ô=v±©gsÎýµsîw�s¯rÎ}³sn‡sîEι_—ô�’º•œ±üÿ¬’ƒæQgÿdÞo%ݢ䇭Ú$=äœû1çܥιg;çþ›¤w•ï÷iïýgªÔAbCXïþŠ”ƒþ†±ž=æœû^%_}·EÒI%Ÿ˜qÎuV¹¬øI9zŒ“$yï¹Ôp‘ôÛJ¾ûîP�÷yùþ^Ò­u.ëݱ¯¬÷g*r¬u9R%ÇçÒû¬²œ‚¤;WÉ='éÍ5Ô{{ùþÿ–÷¶æ’Ï¥Ö“ôJܵöë?–Ô\%=Æå¢»ÔÑcŸ[eóJ~Àêò5rÐc\6Õ¥Žþɼߖó\-il•؇%m[%žä²a.ëÙ_‘rÐ_\6Ôe½zLÉG¿k�÷’>W¥zŒ gVÖ!ý^®ïpÎ=·†ûÿtùÚKúÓ:—µ§|=©ä;ÌòÞ—¼÷×KºAÉGt‡•|ôöˆ¤?”ôbïýŸ¯–Ã9ç$½¹|óC�«ÆÕÔcÞû‘ôí’~YÒß*Ùç• _—ôg’®ñÞÿ´÷~©Jz £ZDZÿ&éÃ’W2YrVÉ»ã÷Hú I/õÞ¯õ]°ô6›z�3ñÞ?$éJIï—ô5IóJúñó’~AÉ‹·©URЃØHÖµ¿" ¿°ÑÐc B�5Ž+Ïâ¢ι‡$}¿¤ßôÞßÒÀåSÒ ·xïŸñÝ›�sîû”¼k1'i§÷~<ׂ�z¬1è1¤è±Æ Ç.ëÕ?1ЃôàFCÙEmô˜]ôØÊ8³²>¿Q¾þ�ιöF, üNÇ%g±hÄ2 zGùúwiÌ‹=ÖôRôXcÐc‡†÷O ô =¸AÑ_vÑ_›=f=¶&+ëPþHÎ}’.UòœFxUùúVïý\ƒ–a†sî…’~DÒˆVø-\\è±øè1T¢Çâ£Ç.ëÔ?1ЃØpè/›è¯Íƒ³‰«Ž��×É9÷Sw¼¿ìR�ý�?§íû'j™ ªaqû%šøþŸÊ5Gº¤së"ÆöÝ/Ò}â¯÷߯bÀ~9³4¥��þßéÍáÕÌ=–ö×moù¸Ž-¬ 9Òø…åO†ÕàvõkëþsÍa¡†9,Ô04?£7|á®ôfHQÇ0 ýñØ­÷jæèhP —÷êÊ÷þP®9,Ô#‡…²æ[œÒýêGÒ›¹�aR¼þ¸ç¦û5z8ì8±w_�^wÇkrÍa¡†9,Ô#GÖø©Ò”þè¤�ãÄþü§5øTØsÍÎg÷êmÿë‡sÍ‘Æøm÷êôSaûÄeÏîÑüàåšÃB 1rX¨aniJ3þáô&lj›èõ �#ÿg—gô噿Lo†ŽaÏs²òéïmØÒÔ¡­MÛ‚’tlÝ®�;wª«µG3ÍËuÇwµöhçÎ�ÚÑÞ£–-a_%ÑÕžäpÛµ¸5ìãö-Û5°s§¶lëRÓätÝñËÛºtvçNõôv©ÅÍÕ°ØÓ¥Žœs¤ñ¡ëA:·.bl�Ðý"Ý'¶·öh¡Pÿ~y�Ðï8ÉÜciulÝ®­MóAEXÈ‘ÆÏwn—o› ªÁunWkÎ9,Ô#‡….ÒcQÇ0 ýqª½[­[JA5t¶wçžÃB 1rX¨!V޲ܯ0)^t·õ¨8žw·õäžÃB 1rX¨!FŽ5TÈý8±sk·ÚšŸk¶vçž#�ß¶µG“Ía«sÛÖžÜsX¨!F 5\€ãÄMôz�Ça'GY´ïsåcàL`²€ LV0�ÉJ&0Y À&+˜Àd%˜¬`“•L`²€ LV0�ÉJ&0Y À„B#’îyN¿:¶nŠÝµ¯W’Ô··;(>�ëØ_[ØÙœ#�]îïŠOãJ]}Á5¤±yæHãB×CelŒíº_¤q½—‡Åo�o’N…®(´ÇÒþJ¯CXȑƹËv×�Ææ™ÃB 1r˜¨az"xÙŠ1†Yè�¶ ã`›g 5ÄÈa¡†¬9æg ÒW‚ý yöX×ý¬î øÊØM}Ï�çÈ_™#ƺȺ=B÷‰‘Ù±ºcVsÛ[>üŽù;î¸6¸¿¬äHã>t§üé°·IÝe;´õ­×çšÃB 1rX¨a!♕¡ý%Ùê�Çß÷ÍŽam»»õ¼w¾*×jˆ‘ÃB YsgÖYMž=–Æ?ðž‡4~l<¨†îguëÕïº:×jˆ‘ÃB 1rd�Ÿ‹{œøÁŸÿ´:·v×wé¾ýÜû^e ûÈ;ЙÃa�+­#4GðæÏjøÈxP ý{»uãí¯Ì5‡…bä°PÃÔB¼Û,lj›åõ �#ÿ3 ñ^‡UjÈdå™oŒi¦y9SŽ“‡Guøñ3Áñ£GÇ5ôÕlçzÏœ×äSal©Òà¨Ç7 «éä`¦ “EµŒ�Ê5GŒb¬‹¬Û#tŸ˜:;¼Ì•ÿÚ°¶6ÍÇgí/+9üéùc§3Õ`!‡…bäȳ?7�i¹•²ö—d£?æNŒkúëÙÆ0 9,Ô#‡…BsÌäìý1~l\ŌljrX¨!F 5ÄÈ?YŠûBoð©Qµ5•‚ãcŒag�éø¿g{¾ÊšcøÈ¸N>‘­ 9,Ô#Gž5Ì.ÅÇ,Œa1rX¨!F 5ÄÈa¡†Ð ËS™–Y ?°À&+˜Àd%˜¬`“•L`²€ LV0�ÉJ&0Y À&+˜Àd%˜¬`B¡I/ý¦uµöÅöíí–$íÚןÆõ^Þ_Û±;}ëC=:^w|ïåÝúá÷^­÷ßt·N ªa×¾^½ãŽkuç-ªx¤þ¤ä Ñëo»F_¸íAM¯?Ƕ=Ýzé-×èä�ûtöäXP [võh×þ×Jô—Ò™bP]Ú'ýìµíó¥Âdý9J]}šúžޝÌÑõÈ'T˜{K¬Ô¹C“/~ƒZï<¨¦âpÝñË}ýš¿þFM}ðSZ:U “3ñÎú’¤ÛÞòñà3+ßqǵQú#Ïiü‡î”?¶O¸Ëvhë[¯Ï5‡…bä°PÃBÄ3+CûK²Õ�¿ï3š;1TCÛîn=ï�¯Ê5‡…bä°PCÖÅÙ°eVsÏM÷«»-ì8±ûYÝzõ»®ÖïyHãÇê¯+�·2f}¡ñVrX¨!FެñãsaÇúÕ|ðç?|fåϽïÕúÈ;ЙÃa5¥9bôGhi oþ¬†_ËõïíÖ�·¿2×jˆ‘ÃB S ñzl³'Zyù×�5ÇÌBܹŽTC&+§ŽŒ©eËR¦£GÇ5ôÕðsµOÕáÇÏdª¡xd\§žÌv¾øÔñq�?žãìÉ1-®rí8>FXÈáO�È˶OXÈa¡†9ò¬ÁÏÅ뱬ý%Ùè�¹ãšþzàU†rX¨!F 5„æ˜9;™i™=<¦Ra9SŽñcã*æ|œ#GÖÇ‘5ÞJ 5ÄÈ?YŠûBoð©Qµ5•‚ãÏÓñÏö\£?²Ö1|d\'ŸÈö8,ä°PCŒyÖ0»oÛ,ljjˆ‘ÃB 1rX¨!4ÇÂòT¦eVÃì0�ÉJ&0Y À&+˜Àd%˜¬`“•L`²€ LV0�ÉJ&0Y À&+˜Àd% �Hºmo�ºÚ{‚b;vwK’z/ïŠOãvíë Š¯ŒíÛVCeì¶=a9Ò¸-»ÂÖãy±—öçHcK]a9Ò¸ÐøórtîÏQŽ]îëŠOãšÂjhži Š«fÏsúÕ±u{Ýqé¾£?ò̑ƹËÂ÷‰46Ïjˆ‘ÃD ÓÁ˾PhI¶ú£­<ž†HcóÌa¡†9,Ô�5ÇülAúJ𢟡w_�ºÛÂŽoºŸÕ}Þuh¼•q0ëã�·’ÃB 1rd�/Ì5IGƒBW´óÙ½êÜZ-—îë9ï:D£?BëHãú3¼–KcóÌa¡†9,Ô0µÐ,�/þ<›å8ÑÊ8ÈãÈ¿†¬9fZuè‘àEWå¼÷q9·[ÒqI:tè�vîÜ%/° ꪫ®JoîñÞŸ¨7=T—µÇè/ :Æ0 ±è1 ±8N'ƶ’†œY9ô[wÊu„½ÛPØÙ«þ·¿N�Þþ fNŒ×ß±»[/ºùÝy˃*©?^JΊ¼þ¶kôþ›îÖÉãA9víëÕ;î¸6¸Ž´†/Üö ¦Ž×/%gg¾ô–ktòÀ}:{r,(Ç–]=Úµÿµ*ü˜š†‡ëŽ_îïW鯴íó¥Âd1¨†RWŸ¦¾çÇÕõÈ'T˜{K¬Ô¹C“/~CpŽ4¾õ΃j*Ö¿Ú&§êŽYÍmoùxð™•ï¸ãÚ(ûvž9Òø…Ý):lŸp—íÐÖ·^Ÿk 5ÄÈa¡†…ˆgV>vë½:ÕÞÛ¶»[Ï{ç«ôøû>£¹€1,FŽ4~³ôyžë2F 5dÍQœ [f5÷Üt¦3+_ý®«õÀ{Òø±úëJã­ôGÖÇo%‡…bäÈ?>vœ^ÍþÓÁgVþÜû^­�¼ó�9VSÌYÇ�ƒ7VÃ�¯û÷vëÆÛ_™k 5ÄÈa¡†©…x=ú:L²u|eeäqä_CÖ3 ñ^‡UjÈdeéxQ‹[2å˜91®É§Â&·$©xd\§žÌv®÷Éã:üø™L9²Ö1u|\ãOe{gOŽiápýl•š†‡Õtr08¾0YTËØ©L5¦GÔ2q:×MÅa5Ÿ>Yw\óôlð2WrükÃÚÚ4cß¶�ß‘?–mŸ°�ÃB 1räYƒŸ›Î´ÜJ3GGÕº¥”)Ç܉qM=| ‹‘ÃB�ÆÈaa]ÆÈa¡†Ð3g'3-óB£‡ÇT*,gÊ1~l\ů†YØ·¥ì�#k¼•jˆ‘#4~²÷…ÞàS£jk ÇÎÓñÏö\#GÖ>2®“Od«ÁB 5ÄÈ‘g ³KñƱ¬¯Ã$c�…bä°PCŒjͱ°÷Ĭ?°À&+˜Àd%˜¬`“•L`²€ LV0�ÉJ&0Y À&+˜Àd%˜¬`“•L(4$éž>µtl‹ÝÙ+IêØÝŸÆõí ‹¯ŒÝµ¯78GZG·mOX|eì–]=Á9ÒØåþþ ø4®ÔÕ\C[êÜž£š#�[î [K“SAqÕìyN¿:¶Ößcé~cßÎ3Gç. ß'ÒØv ãGžëÂÚöÈëqŒÏ�Õ³š¿í^mÛZ�õïíÖ�·¿Roþ¬†_XÈ‘ÆÇè �c³l��Û¶§;(¾2¶ooxŽ4v×¾°Ç‘ÆÅ¨!ƺȺ=Òí"�-uîΑƆæÈ¿XšŠ«Æíê—ëÜ^Üe;λZ¶�i\hUÆæ™#f ¶G®5LÇ뱎Ë{ÕÙÞÛ¶»û¼ë¡Âtýo«•:whòÅoŽŸ‰{fåÂ�ƒš<³rë[¯×‡î”?öö¢…i|ŒþÈ3GÌ,l�ü¶{ƒÏ¬¼ñöWêàÍŸÕpàk 9bÖ£Ç,<ŽÍ²=BsL-Äë±ÛÞòñLgVZ9¾Ú,lj<ŽüsÌ,Ä�ëH5d²²ebH-n&Sަáa5� Ž?{rL ‡Ã&¶RSÇÇ5þT¶óÅ‹GÆuêÉð'�êðãgr­A’fNŒkò©bp|ipT‹‡‡2ÕÐTVóé“™r¦GÔ2qzÝã[¦¦ƒ—¹rX¾m.<þôˆü±ðõ`%GŒþ°�#F ¶Gž5ø¹x=6stT­[J™rÌ�×ô×ß3cä°°oÇÈaa]ÆÈa¡†Ð3g'3-óB£‡ÇT*,gÊ1~l\ů†Ûd��•ÃÂøaa]X¨!FŽÐøÉÈ_tú©1M6/Ç×É'²=×XÈ£†=fáqXÈ‘g ³KñƱã_ÖÖ¦ùL9,_Y¨!F 5ÄÈa¡†Ð ËS™–Y ?°À&+˜Àd%˜¬`“•L`²€ LV0�ÉJ&0Y À&+˜Àd%˜¬`“•L(4"éâöK´ØÓ[êê“$-÷÷ŧq[võÅWÆnÛÓœ#�íÛ–#�Ûµ¯7¸†46´†ÊØŽÝa9Ò¸ÂÎðÇ‘Æ.÷…핱¥ÎAñi\hübi"(®·«_®s{ýq—í8ï:hÙr¤q1ú#Ï1k°°=r­a:^�u\Þ«Îöî Ø¶òs^[àsfŒiÜfé�<×eŒjÈšc~¶ }%xÑÏл¯GÝmaÇiÝÏê>ïz½ãcç°0~ä¹.¬m�¼Ga®I:º¢ËžÝ£m[ëï±þòq†×rĬ!F�Yx›e{„æ˜Zh–F‚ž=ÏéWÇÖú_‡I¶Ž¯6Ëq"�#ÿ3 ­:ôHð¢«rÞû8‰œÛ-é¸$:tH;wØ uÕUW¥7÷xïOÔ›ƒªËÚcôPcÐXôÐX'�c [ICάÜþ·¢žÞð3+§¾çǵíó¥Âd18^ô—Ò™úã%I—öI?ûF�^wü¶=Ýzé-×èÎ[TñHýñRrVäõ·]£÷ßt·N ʱk_¯Þqǵúô­iôhýuô^Þ­~ïÕzôö5s¢þx)9;óE7_£©~JK§ÂÞkØ¡mo»N­wTSq¸îøå¾~Í_£ºù„ Óõ×0;÷ÌÊ…5xfåÖ·^¯…Ý):l]ZÈ‘ÆÇØ·ó̳ Û#Ï"žYùØ­÷êT†3+Ÿ÷ÎWéñ÷}Fs�ÏyYsX¨!FŽ4~³ôùFÞÅÙ°eVsÏM÷g:³òÕïºZ¼ç!�«¿®¬ñVrĬÁB\ìÛc|.ìõF5~Û½ÁgVÞxû+uðæÏj8ðõ‡…j¨Ì‘µ?¬<Ž�¼=¦âõØmoùx¦3+­—l–ã+Gþ9fâÎu¤2YÙ21¤7“)Ga²¨–±Sá Î¥ãâ%�=9¦…ÃõOlUš:>®ñ§ÂÏ9/ש'³�³~òð¨?~&SŽÑ£ãújx3'Æ5ùTàäqÙÒ©-Éö8šŠÃj>}28¾0=¢–‰ÓuǵLM/s%þä°|Û\xüéùcõ?k9bìÛrĨÁÂöȳ?¯ÇfŽŽªuK)S޹ãšþz¶ç¼¬9,Ô#‡…�‘ú Í1sv2Ó2/4zxL¥Âr¦ãÇÆUÌp\’5ÞJŽ5Xè ë2FŽÐøÉÈ_tú©1M6/Ç×É'²=×XÈa¡){Xyy{Ì.ÅÇŽmX[›æ3å°ð¼k¡†9,Ô#‡…Bs,,OeZf5üÀ˜¬`“•L`²€ LV0�ÉJ&0Y À&+˜Àd%˜¬`“•L`²€ LV0¡Ðˆ¤‹Û/ÑbOWPl©«ï¼ëÐx]_»eWOpŠ4vÛžî ø4®ooX|eì®}½Á9ÒØÞËÃêHã:v‡ÅWÆ6ìΑÆ.÷õŧq¥Î°KAqÕ¸]ýr�Ûë�»lÇy×AË6�#�‹±oç™#f ¶G®5LÇ뱎Ë{ÕÙÞÛV~¾jËðœ—5‡…bäHã6KŸoäí1?[�¾¼ègèÝףc¬îguŸw½ÞñVrĬÁB\ìÛ£0×$ ]ÑeÏîѶ­õ÷XùµC†×rX¨¡26kXyy{L-4K#Á‹?Ïžçô«cký¯Ã$[Ç%›åøŠÇ‘Ž™…Vz$xÑU9ï}œDÎí–t\’:¤�;wFÉ lƒƒƒºêª«Ò›{¼÷'êÍA�Õeí1ú ¨Ž1 h,z h,Ž�Ɖ1†­¤!gVnÿÛ?QOoø™•SßóãÚöù¿Ra²¸îñ•9 ?¦¦áá Ëýý*Ýxƒ†?p�Jƒ£uÇvöªÿí¯Ó£·?¨™ãA5tìîÖ‹n¾FŸ¾õ!� ËÑ{y·~ø½Wëý7Ý­“‡ë»öõêw\«;oyPÅ#a5ôííÖõ·]e]dÝ¡ûÄ–©ÉºcV³pà æϬÜúÖëµð¡;åO‡½½h!Gº_JçöÍvë½:•áÌÊç½óUzü}ŸÑ\àóUÖjˆ‘#�ß,}¾‘·Gq6l™ÕÜsÓý™Î¬|õ»®ÖïyHãÇê¯+k¼•1k°ÐûöŸ«;f5~Û½ÁgVÞxû+uðæÏj8ð¸ÝB 5ÄÈ‘ÆÇèQ �#ϦâõØmoùx¦3+­—l–ã+Gþ9fâ~Š4Õ�ÉÊ–‰!µ¸™L9 “EµŒ�Ê-^’š†‡Õtr0SŽÒà¨ÇÏœ×äSa“®©Ñ£ãúj¶óÞOÕáÇÏÇ�ŒëÔ“Ùjˆ±.²n�Ð}ÂÍfë‡ ù“Ãòmsáñ§Gä��ÎVƒ�Y÷K+9bÔ`a{äYƒŸ›Î´ÜJ3GGÕº¥”)Ç܉qM=ÛóUÖjˆ‘ÃB�ÆÈaa]†æ˜9÷ ·ÑÃc*–3å?6®b†c›¬ñVrĨÁBXX—1r„ÆOFþº ÓO�i²y)8~øÈ¸N>‘í¹ÆB 5ÄÈ£G-<Ž¤Ñ£õ× %gˆþð{¯^ézÝC3ñÎú’¤…5xfåÖ·^¯…Ý):ìíE 9,Ô#GÌbôXhެñ1rÌ,Äë±Çn½W§2œYù¼w¾J�¿ï3š |ÞÍšÃB 1r¤ñyîW1r¤ñy{gÖYÍ=7ÝŸéÌÊW¿ëj=ðž‡4~¬þº²Æ[Éa¡†9Òx+}ž×öŸ«;f5~Û½ÁgVÞxû+uðæÏj8à¸ßJ 5Äȳ†ýaáq„æ˜Zˆ×c·½åã™Î¬´r\båy—Ç‘ YsÄ|V©!“•îô5MNgÊQ˜,ªeìTnñ±r4 «éä`p|ipT‹‡‡2Õ0sb\“O…Mº¦ŠGÆuêÉðsçOÕáÇÏdªaô踆¾šíüý¬ë"t{”¦‚—¹rX¾m.<þôˆü±ÓÙj0�ÃB 1rĨ!F�eÍ‘g Ëñzlæè¨Z·”2å˜;1®é¯g{ÞÍšÃB 1rXØ·cä°°.CsÌœ�ûU&£‡ÇT*,gÊ1~l\Å ÇYã­ä°PCŒzTÊo{L–â¾Ð;ýÔ˜&›—‚ã‡�ŒëäÙžk,ä°PCŒ1jˆÑGhŽÙ¥xãØñ¯ kkÓ|¦žó,Ô#‡…bä°PChŽ˜¯Ã*ñ;L`²€ LV0�ÉJ&0Y À&+˜Àd%˜¬`“•L`²€ LV0�ÉJ&0Y À„B#’úË.Õò¶® Øåþ~IR©«/(>� ���#}<õJã ;{ƒkHc;vwçHcûö†åHãví ilïåa5TƆ®‹4.t{f¶Jÿº"·«_®s{ýq—í8ï:hÙrX¨!FŽ˜5Äè±ÐYãcä˜YhÕ¡G‚žŽË{ÕÙÞÛV~®hËð¼›5‡…bäHãòܯbäHã6òö˜Ÿ-H_ ^ô3ôîëQw[OPl÷³ºÏ»^ïx+9,Ô#Gg¥ÏóÚ…¹&éhPèŠ.{v�¶m­¿ÇúËÇíý�ÇýVrX¨!FŽ˜5Äè �#4ÇÔB³4¼øóìyN¿:¶Öÿ:L²u\båy—Ç‘ YsÄ|VÉyïã$rn·¤ã’tèÐ!íܹ3J^`3ÔUW]•ÞÜã½?Qoz ¨.k�Ñ_@uŒa@cÑc@cqœ4NŒ1l% 9³rè·î”ë{·¡°³Wýo� ?¦¦ááºã—ûûUºñu=ò ¦ÃÞ>)uîÐä‹ß�kŽ4¾õ΃j*Ö¿$i¹¯_ó×ߨ©~JK§ÂGóÀm{Ûuzôö5sb¼îøŽÝÝzÑÍ×èÓ·>¤Ñ£õÇKÉY‘?üÞ«õþ›îÖÉãA9víëÕ;î¸VwÞò ŠGꯣoo·®¿í}á¶5u¼þøÑ¹±ºcV³pà æϬÜúÖëµð¡;åO‡írX¨!FŽ˜5Äè�ÐYãcä˜Y˜ZîJ»õ^�ÊpfåóÞù*=þ¾Ïh.à93F 5ÄÈa¡†9Òø<û#kŽ˜ý%I÷Üt¦3+_ý®«õÀ{Òø±ñu�·’ÃB 1r¤ñVú#¯í1ù8ñÃo»7øÌÊo¥ÞüY /[Éa¡†9,ÔP™#ëqbž�cj!^�Ýö–�g:³2ïcf 5ÄÈa¡†9,Ô�5GìãÄTC&+KÇ‹Zܺ�)GÓð°šNǦGÔ2q:S r4‡Õ|úd¦–N�héÈ™L9fNŒkò©bpüèÑq }5Û¹÷'�êðãÙGñȸN=^ÇÔñq�?UüäbÜö'‡åÛæÂãO�È˶o[Èa¡†9bÔ£?²æÈ³†…å©LË­4stT­[J™rÌ�×ô×ß3cä°PCŒjˆ‘ÃB�†æˆÙ_’4zxL¥Âr¦ãÇÆUÌp\‘5ÞJ 5ÄÈa¥?òÚ“¥¸Ç‰§ŸÓdóRpüð‘q�|"Ûs¦…jˆ‘ÃB RöËóqÌ.MfZn¥ã_ÖÖ¦ùL9,<çY¨!F 5ÄÈa¡†Ð±�SüÀ˜¬`“•L`²€ LV0�ÉJ&0Y À&+˜Àd%˜¬`“•L`²€ LV0¡Ð�¤{úÔÒ±=,vg¯$i¹¿?(>�+uîНŒÍ3G·Ü¶*c›ÂGÛ±»;(>�ë½<,¾2v×¾ÞàilßÞ°:Ò¸m{Ââ—皤¯…®Èíê—묿ÇÜe;λZ¶�jˆ‘#f 1ú#4GÖø9fZuè‘àÅŸ§ãò^u¶wŶ•ŸóÚŸ3cä°PCŒjˆ‘#�˳?²æˆÙ_’Ô»¯GÝm=A±ÝÏê>ïz½ã­ä°PCŒiœ•þÈk{暤£A¡+ºìÙ=Ú¶µþë/ïö/[Éa¡†9,ÔP›õ81ÏÇ1µÐ,�/þ<{žÓ¯Ž­asy�çVjˆ‘ÃB 1rX¨!kŽØÇ‰)ç½�“ȹݒŽKÒ¡C‡´sçÎ(y�Í`ppPW]uUzs�÷þD½9è1 º¬=FÕ1†�E��Åq"Ð81ư•4äÌÊ/þÚ}ÚÑöŽyÇîn½èæk4ü�{T­;¾°³Wýo�Zï<¨¦âpP Ë}ýš¿þFu=ò ¦ÃÞ‚)uîÐä‹ßœ#k|eŽë"ëöxôö5sb<¨†tŸ¸ó–U<–£oo·®¿í½ÿ¦»uòpý�c×¾^½ãŽkƒk˜œ«;f5 j>ðÌÊ­o½^ ºSþtØ~e!‡…bäˆYCè¾-�Û¿³öGž5Ì,L-w%�Ýz¯Ne8³òyï|•ßg4øœ—5‡…bä°PCŒi|žý‘5GÌþ’¤{nº?Ó™•¯~×Õzà=iüØøºÇ[Éa¡†9,ÔP™#¯q0v�}øm÷ŸYyãí¯ÔÁ›?«áÀcn 9,Ô#‡…bäHãcŒa¡5L-Ä{-vÛ[>žéÌʼ�™-Ô#‡…bä°PCֱǰTC&+§ŽŒ©eËR¦¥ÁQ- Žo*«ùôÉL5¦GÔ2q:×1jˆ±.²n�™ãš|ª˜©†â‘q�z2Ûùû'�êðãgÖ½†é¥¸ ìOË·Í…ÇŸ‘?–m¿²�ÃB 1rĨ!ë¾#Gž5,,OeZn¥™££jÝRÊ”cîĸ¦¿ží9/k 5ÄÈa¡†9,ôhhŽ˜ý%I£‡ÇT*,gÊ1~l\ů†d�·’ÃB 1rX¨AÊoŒÝc§ŸÓdsøk±á#ã:ùD¶çL 9,Ô#‡…bäˆ1†…Ö0»4™i¹•ŽmX[›æ3å°pL`¡†9,Ô#‡…BsÄÃRüÀ˜¬`“•L`²€ LV0�ÉJ&0Y À&+˜Àd%˜¬`“•L`²€ …F$ݶ·G]í=A±»»%I…�½AñiÜr_P|el©sGpŽ464GÖøÊØë"ëöH·kˆ4¶ooxŽ4v×¾°Ç‘Æ…Ö09ß$� ]‘ÛÕ/×¹½þ¸Ëvœw´l9,Ô#GÌB÷íÊØ¬ý‘g 3 ­:ôHðâÏÓqy¯:Û»ƒbÛÊÏWmžó²æ°PCŒjˆ‘#�˳?²æˆÙ_’Ô»¯GÝmaljÝÏê>ïz½ã­ä°PCŒj¨ŒÍkŒÝc—=»GÛ¶ÖßcýåãÜþ ÇÜrX¨!F 5ÄÈÑŸñuXelh S ÍÒHðâϳç9ýêØZÿë0)ÿñÜJ 1rX¨!F 5dÍ{ K9ï}œDÎí–t\’:¤�;wFÉ lƒƒƒºêª«Ò›{¼÷'êÍA�Õeí1ú ¨Ž1 h,z h,Ž�Ɖ1†­¤!gVþõþûµ½5ìóÞË»õÃï½Z�Þþ fNŒ×ß±»[/ºùM}ðSZ:ööIóÀm{Ûuj½ó šŠÃA9–ûú5ý�êzä*L×_G©s‡&_ü†àøØ9 ?¦¦áú×År¿J7Þ áÜ£ÒàhP …�½êûëô…ÛÔÔññ Ûöt륷\£;oyPÅ#õçèÛÛ­ëo»Fï¿én�<\ÿã˜Y˜¨;f5 j>ðÌÊ­o½^ ºSþtØ>a!‡…bäˆYCè¾)%ï ½ãŽkƒsd��‘#f�=vë½:•áÌÊç½óUzü}ŸÑ\À#‡…bä°PCŒi|žý‘5Gì1ìž›îÏtfå«ßuµxÏC?6¾îñVrX¨!F 5TæÈkŒÝc~Û½ÁgVÞxû+uðæÏj8àxÙJ 5ÄÈa¡†9bÖ`¡Çn{ËÇ3�Y™÷1³…bä°PCŒjÈš#ö–jÈdeñcZ(,gÊ1sb\“Oƒã—N�héH¶ÏÝ6‡Õ|úd¦…éµLœÎ->Vަáa5� Ž/ ŽjñðP¦¦Ž�kü©lçï�ŒëÔ“á9NÕáÇë߯–§‚—¹rX¾m.<þôˆü±lû„…jˆ‘#F ¡ûfÌyÖ³ÇfŽŽªuK)S޹ãšþzø#‡…bä°PCŒz44Gì1lôð˜J�Ç��«øÕðñS Çjˆ‘ÃB 1rX¨!4Gì1,Åì0�ÉJ&0Y À&+˜Àd%˜¬`“•L`²€ LV0�ÉJ&0Y À&+˜Pˆ˜«9ýcfi*8ÉÖù& jdvLSg'ëŽ_œmÖàà &g&´4?TCóL«fÕ69¥æéÙ K“SšÔìÈ„Z¦ê¯c±4¡ñ ñ±sl™š”›�©;ÞOMêìà †f&TZÛ/ 3[å5:7¦Éʼn ËsÉ~59?¦é¥úsL–÷Ë™… -,×ÿ8Î.Ÿ·îš«Ýo OÇ Í׿-$ÉMOhëà ¦'äçÂö 9,Ô#GÌB÷MIšYhÍ´g��‘#B�=3¶>†ÍÏ488¨âì¸fư9,Ô#‡…bäHãóì�¬9b�aS¥ð+”Çóñ¹1M–êϳÆ[Éa¡†9,ÔP™#¯q0v�;›ZH^GM-Œiv)ì9ÓB 5ÄÈa¡†9bÖ`á8ñ‚\uÉ{<·RCŒjˆ‘ÃB YsDÞÁyïã$rî*I_Œ’ ØÜ^â½?To=Ԭ€š1†�E��Åq"Ð8AcØJø8bžY¹UÒ•å›Ã’–¢$6‡fIýå¿óÞ/Ô›€V•©Çè/`UŒa@cÑc@cqœ4Næ1l%Ñ&+ >À&+˜Àd%˜¬`“•L`²€ LV0�ÉJ&0Y À&+˜Àd%˜¬`“•L`²€ LV0�ÉJ&0Y À„ÿ?ÏfÕïe[ŠIEND®B`‚flox-0.11.2/docs/diagrams/counties-bitmask-containment.png000066400000000000000000000257311514775624000236110ustar00rootroot00000000000000‰PNG  IHDR°Âÿó0¢9tEXtSoftwareMatplotlib version3.8.0, https://matplotlib.org/5±µ« pHYsgŸÒR+FIDATxœíÝ_ˆ,}zð§¦;‰î$$Ñ`B⊂ &d1 q}ÝÐ F^Ô/aÆJôF™Å ¯â(1¢ÄÌDEX /"Mëú²nœ(ñBCÖ›„˜ÔÆH23åÅœ:§OMUwuwuÿêÏçËyÏLuwå&ýœo=ÏóËò<誓Ô7ë°è4�&À ÓXtš €N›¦¾€u²,ûºˆ8�ˆßŸŠˆ_¿ÿ="~>"þuDüˈø8ÏóçD· Ð+Y–}*"þpDÌ#â["â×EÄcD|9"~""þIDüó<ÏŸ’Ý$ÀŠ,ÏóÔ÷P)˲?'^B«Âÿ‰ˆIDœ®üì"âësÿ `­,˾9"þzDü±Ò¯þwD|M¼ÔY…/EÄ÷æyþŸŽswõŒ�”eÙŸˆˆ/áÕ¿ŠˆïŽˆ¯Íóükó<ÿêˆøæx)¼~,"þ­ð `½,˾5"~2^j¨ÿ?ߟÈóüë#â+#â;"â¯FÄÿ�ˆo�ˆO¤¹[€÷éÀ:'˲oŒˆŸ‰ˆ¯ŽˆÉóüû6\ÿ«ó<ÿ¥£Ü@eYö›#â>"¾!"þKDü¡<ÏrÍõ¿>"þQD|6Ïó_>Î]Ô`�“eÙ÷GÄßzó×oÉóüË)ï Ï²,ûŠˆøñˆøñ‹ñyžÿlƒ×�Ø1 t…B ‹~ëÊÿÏdw0 ß/áUDÄŸn^ED¯€.`]ô +ÿý—Þ<5`KY–}UDü…7ý‰<ÏÿAÊûØ• è¢/Ç8GDüňøù,Ë~4˲?žeÙ'Þ@ßüþx9ü&"âo¦¼€}Ø�tR–eß#"ª«ŸŠˆ�ˆ¿ky;@½,Ë~8"¾/^~]žç‰o `', ³²,›FÄwÅË“ÃßßÙÊ%?³<Ï1Áít^–eÿ>">ÿ!Ïóßžøvvf„è¬<Ïó<_äyþçò<ÿöˆøÆxy‚X,ý¶ˆøËÉn û¾éÍŸ?—ô.ö$Àz#Ïóÿ‘çù�ÄK7Ö¯¼ùñïNxK]÷Uoþüå¤w°'Ð;yžÿ\¼{Šxšò^:ùó[’ÞÀžX@ïdYö‰x×ÿ¥”÷ÐqÿæÍŸŸÎ²ì›Ö^ Ða, 3²,ûT–eß¼ùÊøkñ‰7ÿý#¼%€¾û{oþüŠˆøÁ,˲ugYö²,ûÁƒßÀ–X@—üɈøÙ,Ëþ~–eß�eÙ׿Ȳì×dYö²,ûbDü™7?þ¡<Ï—)n ò<ÿwñCoþúG#b™eÙY–}eqM–eß�eÙ̲ìFÄÏDÄ'Ü*ÀZYžç©ï ""²,ûB¼^Êþ‹ñ¶¯îºú¥ˆø+yžÿÀ±î  ¯²,›DÄDÄŸ�ˆ¢ë1"~!^j¬_µry6Ïó¿}Ô›Ø@€tF–e_ßßŸŽˆß_Ïñ¿"â§#â_DÄ]žç_Nt›½”eÙo‹ˆ?¿'"~SD|M¼<ü¯ñRg}?–çùNt‹µXtšXtš €N`Ði,:M€@§ °è4�&À ÓXtš €N`Ði,:mšêƒŸž?ÎS}60““²Ô÷@jMÚ¤¦HOÀùõ]L'óÔ·A‰ `ÅãÓ2õ-P"ÀˆˆÅlÙÕyêÛ ‚ "Îîß�#ì0z‹ÙËØ`~}Æ»F€@§ °€Á*:ªê�WÅø XÝ$À«.�ZÌ–1�ÌßWåBºE€ V] uv?�ǧ¥¬žÈòM'ó·Yùõ]DDdWç­ß›š"=X@r«áUUwU1¸ºkuœ0»:?HxE7°€ƒ):£ÊÖíªZÝ�U„YåXu#„ “B`°´û�—Z ›6� î2Fx jŠôt`G±)œÚôûÕñÁ²ºN/†A€ͺÑÁMŠñÁª÷°ÿjØXÀÑì2"X¬º8fÈa °€ƒYí+Qå‘ÀòïË Ü/KÜ€Á²pu¼ÔšýÕÅEîjŠôt`S·\½nÖjxõpyû¶ «¬îç “,`°<-/µ&Àaä×w½Y–~r÷¹x>ÿžVÞKM‘ž, ‘]«uXåXeûtYµ^Ñ :°€Áò´t¼Ôšiõ©S« 5Ez:°€V­ ¯êv_Á:,`/Ř`ݸ`Ä»qÀ&' ZÐN™B`°´û�—Z€6©)ÒÓ�TZ×Qµï{z‰;Ã"ÀÖZÝ[µ)tjú>U£„åýX§7;Ãb„,íþã¥ÖHc›Ó³eã}X©ƒ,5Ez:°€ZÛŒ®†W›^×$¼ŠÐ…Å P«iGÕ>¯kc×Ã&ÀÞSÞEuè÷Þ5$c<XÀ{Šñ¾¢3jŸ%î«ÝU›ö^28£ßX@c�O»‡L›ö^5Ý‹Åø°è´,ÏÓœ0ìhcàÐy=^jMÚ¤¦HO0ÅNŽmwsÍùž%0ÅNŽ}vsïT-qßö{vu‰;ìÊ!0XÚýÇK­ @›ÔééÀ*Uu`íú°P)»:�üú.Îî×ェ ©ŠŸeWçñpyÛþ 2F€ÁÒî?^jM€Ý,fË�aÕ1Þ£kÔééÀ€)wB­Ž–ƒ§]ÆÿÚx(`Ði,‘Ó›‹˜NÞuI5÷[íÒZ}-‹ ªn|ïñéõ©‚U'  ØWC®ª×ŸSõyÅ{À>X0PEx´˜-+ªªýWåëšì°*>GXÅ¡8…,'�—Z`?C‰puwÖêÎÉ"èZ ¼Ê»²ZìË,`°ì«/µ&mRS¤§ :¦|R`�ÅlY9êWŒøUu>ï]\SÕA]£ ,OKÇK­ ôU~}÷jü®M—·qzsñêç‹ÙòÕÈ ï¨)Ò`½4�ÌßÛ¿QE±9^jM€Ý²ª©)Ò3B$µé£:›Â+ª]FýêF Ë?;»Ÿ¿úY1†Øt¬A0Xž–Ž—Z+T‡¡¦HO ıë“�Gý<ÆK€=P7¸˜-kGò«N!Üæý7yþðlë×À.Œƒ¥Ý¼Ôš@Ÿc€Å‰„‡>™�ÍÔééÀ’[·È½ø]ùOªb °­ÊáÕ¦N©bÙú6ÝWMXâNJ:°€Áò´t¼ÔšÀPè¾ê5Ez:°€Î(wWé¶`ìÖ…W»ì¬‚¾`�ñø´¬ü» Þ)‚«mN,–½C_!’šN毂«¶h÷/µ&0Æ»CM‘ž, ©C…WÐwUáÕ.‹Ùu^1,蘺“«N$¬ ¨ŠñûüúîÕ¨¡Óé##„À`i÷/µ&0TÆ ÓPS¤§ z¢IxU×½}&À’sÊ Cµ.Dj²›ªîõë¾;Oo."¢YØ}!ÀZ±Oe‘;C´˜-kC¤Ålùv7Õb¶|õ=ZtQÕ½¾øîl²«Ì,úÈ,ààŠ"ºTM'óF?Û•}ã¥Ö†l5üâ8ÔééÀîñiÙ8¨*~f¬€![íšj2J¸j5¼*:°ê¾7íÁb(t`ƒåiéx©5�Ôö9-°è°râ`w¨)ÒÓ�b<^`@Ëöíœz¸¼�ìêÜ ¼!À€#ٴ窱No.""„Xð† ŽdÓx`ѹõpyûíÒ‚!`@Ëöíš:½¹^Á ´¬�à©î=6�!®voÁP°€½L'›OJjr ŒÉjµ)�*_·i qu E–çy’~zþ8Í[›NæñøÔ¬¸î’ÉÉYê{ µ&mRS¤§ ¨UtNÕ…W:« ÞºÎªrVù;Õ ¼O0Xž–Ž—ZèªMc€U‹Û-sOOM‘ž ,Åæx©5�ÔÚ�„Xi©)Ò3B-[6Õ�®Í^1v,X£í=Tuãƒ}<0ŽE€5N>ÿÝOÐ,¨ñüÙÏ8 :@€#·nߌÙÃåmDtcÿ”�±`ÀÈÙ·ÕNo.ZϺîUס•!"Ëó4' ;Úc:™7 ¥ŠÎ«!X޼/µ&°�®F‹Ù²v<‡¥¦HOô̦‘¿¦�ÔãÓríµF ‹òxÞºðj›ªÕ÷/Æ÷!¼bÌt`ƒåiéx©5�ÔºÚÅÅnÔééÀ€ ê:“ë:²ªÂ«&Ý[@5¬¨:ñ¯��‘Faw,X±ÍèßÙý¼2ð*¬ûМXÐaMO¤š}ã¥Ö MjŠôt`@‡”÷kÔ…WÛžèDAØ]UUù»Õw-– :äñiù¶^WoÛ•¥‹ ¶÷pyÕ#…åïVßµpX,蘢.þÜæ‰®§¿ÐžÓ›‹Ô·¼!À€Ž+?Ñ] ©šŽ@Ÿ °àHÚêŽZ ©Vp8Åa8Í�±s !0XN /µ&Цüú®r㡦HO½´˜Õw"Wý®ü³éd^Ùe•_ß½wm9¼jrà Ð.XÀ`yZ:^jM +to ƒš"=XôÞ¶;¢Öuoú³�í °àêF Œ@3›§Õ.§&áÔÙývßÁåïìâï‹Ù2²«óÚïômï ¨&À€«…è¾ASÝi�U?jÀku�S ­vA5 §Väâµë¦òwvñ÷³ûyä×wkO.ÞÛÐ xÇ,`°ì«/µ&mRS¤§ v4�ÌuKÀ°`G�O˵ãeÂ.8¬Ål—·ïý}—÷ºÇ!ƒ3�Ì· –.íþã¥Öê”넇ËÛ8½¹ˆÅliGµÔé °€ÁRlŽ—Z€6©)Ò3BÀ !„á`ÐKå°éì~§7oW ,_?�Ìß ¼Š÷ȯïYÐ1F­ÕûîͲw«›´û�—Z†g›UöYÑ65Ez:°´u'ÿ­N�OËW×nsj ð «*�ªë’:»ŸWþ®ª«J~}·Ã‡¤ ,OKÇK­ tIÓ5Ez:°`Å6]WÅõÛ¾8ž}ë“Ï¡ùµ-öú, ž,`°<-/µ&mRS¤§ €Á9TG”N+èžb¯Õ¶{«V÷a¯uò t—,`°<-/µ& _~}ÙÕyêÛ`$ÔééÀàhö=áo1[Æb¶>5?òZ ý²Í÷<0\,:¡ª8=DÁª€þXÌ|o/XÌÃåíίݴÿj]ÀUõ»òÏê:³-u‡î±Ä€£šN溠8 WÇK­ ã’_ßEvu¾öš]Â?\ÞÆéÍÅ>·Æ@¨)ÒÓ�ÀQ ¯`víbÚv_åb¶Œìê|ãç­ ¯ê^+¼‚îÐ� –§¥ã¥Ö„q9TÔééÀ`kMžŒN'޽Ž£¯ÖíÄ^A¿ °ØZ“1Àǧ¥qA ‘¶NõoÝkœ ìãôæbc¸TÕ�µ©YÌ–oßwÓû·½HÏ,ÖšNæN¤·ì«/µ&´g58‚±RS¤§ ¡Cu;­{ߦŸY\'¼€ñ¨êŒÚ5¼jkÏ•îp B0`ž–Ž—ZÒÙ´kºÁHEM‘ž,öê˜ZÝ9ÕdÖ®Ÿ¹ú¾N2„q`4¤}²]OLõ™)îHG€ÐPÊ]PÂ3 ËŠÎ¨Ô§ÿ¥þ|àpX=`‘:p(»„>Óɼòu©öSã„öcÁpYâ –…«ã¥Ö MjŠôt`t˜ÑA`ŒŒe,€=m2m{í¦ÑAЦ&'û£þ0 ” °ö´2m*è¶ÙeÕäZ»±€1`´H ôYvu^ùóÕ‘¾6ê�&�^«X¼µÏé‚MwWÕeuXDÄKgÔ>û§ì®E€@c‘R`ï�ö=\ÞV^SÞ�Õtl°‰r8V¼·Ð `ƒ¶Zì(|¶³O-Uµ¨}Û›‹‘DåYžçI>øéùã4 ôÊt2OR°¤ú\ ]““²Ô÷@jMh&¿¾Ûk¡úb¶|µ÷êáò6¾ø¥OڇЍ)ÒÓ�tÚ!B¤&O…WÀì{à.!UUgÀ&,`tNS†h›=W§7ƒ-!°-0(©¤Ç§eò{hÛ>〗·ï`ÙÕydWç­.†Ï, UvG]b_Åx©5a7Mvb©÷#5Ez,`°›ã¥Ö MjŠôŒ­1:ÐoMvS©ù€X@k´’p,€‘)¨—;®6í¿ŠØý¡åÃåíÚ{XG€¬Un×2�N[aOqª`]`Õäs¶½—Ó›‹µ÷°Ž X«ü„íc‚B1€z«]R‡ {Šz¬ø¬uŸS\+xŽÉ)„À`91h¼ÔšŒÑt2O¾“t1[ ¶$5Ez:°€½u©ƒªK÷°«üú®vOU�&áÕ¦Z©égÕ^‡¢ ¨µËS¼.<ù(xZ:^jMÆ®¨ÉŠ,e°ôpy_üÒ'_݃n-úDM‘ž, V9ˆjÒÝ$¼H¯¨Éª¢¢¦Û¥s}Ýkt‡¤ ,OKÇK­ @›ÔééÀ�m;¤.o]WŒ)’  cvYâ¾ÉãÓr«%î§7�Þ×+àŒBb¥ŽvÿñRk2duËÏ»P#ZÌÎP©)Ò`ƒ¥Ø/µ&´G(jŠ.0B=Ðt_�“_ú¯­‘Á¶l^=\ÞÚ‹´NtPÚ¿·Qg}ºg`<-/µ&¼(‚$T°5Ez,`°›ã¥Ö MjŠôŒPiuEŶë*º6 ô› ` ³å«}TEÕ$�Z]±íºˆìê|«ëÖ`ÁHÔ(¿ôË6 ÒÏîç¯ö_A”ý¥@Ÿ°`$ê … @7<\Þ®ý}y!ût2oü0Ò©€@ß °µ”ÝGUŸ½ËýlS¸Ð]§7[]ÿø´ô0 £–ò ¿ê³w¹… À°Ô-?/�š<̬{­Eë@_° ÚíÄ:V7”®+€aÚvùùêÃÌmG›|–ñC  ²<Ï“|ðÓóÇi>šNæ•�Mu??Äg°ŸÉÉYê{ µ&¼¦æ„Ý©)Ò`ƒ¥Ø/µ&mRS¤g„ cì¦xŸ  c¶ÝƒUVìK]Ý_e‡*Ðg,航‚B‘ÐOÛ,>/®}¸¼Ýê=§“ym½Xìº*N\Ìœ\ ô›XÀ`ÙW1^jMúf1[¾ ›ÚÒtiû!>†FM‘ž,FGWÇФî,®i mÓÙÕ´ãªê³·ù€cÐ� –§¥ã¥Ö„E¥Ã ö£¦HO½Öv7•î,†@] �‹ÞYý2ÞÔ½n±e‹-ØE›#wùõÝÞ׬.q×} �‹ÞÙ&dz|zwÚŠ§PJ[!Ñt2�ìêüÕÏ÷ Èì´úN€Åhè®àгe£§u�RQ³æ×wo¯É¯ï^dU!×:º°€¾³Ä, WÇK­ @›ÔééÀ蹓ÿ‰Ô·Péä?þTê[B€ÐsÏ¿ëw¦¾…JÏŸúöÔ· „‹ÖY–¯=\Þ¦¾€Þ`Ѻªeéu¡Öº°kSVõ{á]uzsQû;§¬g‰;0X®Ž—Z“.[Ì–N„žQS¤§‹½µÑõ4�̽�+úNx°½iê ÿªFõm|Ð/:°è4É ¯ö9QÐÂv€í °HÆ8 t×>!@ÛX[:½¹h|myòàì~¾± +¿¾Ûé¾Ú´Íÿ�‡–åyš†mÜ ÓÉ\'ƒåÈëñRkÐ&5Ez:°h›ÝRåðjŸ½Të^kß}Ó….(^èÀËÓÒñRkÐ&5Ez:°F¨I7”Ž)¨çB€ã`�P“�WmŽÀÐlZp¾.¤:»¿¶.®uê@=�Xô›-f˘Næï…TUƒW®âÚÕP,¿¾{{�]\¬AIÑ%¥3 €>©ërÚ§®ÝÔAåa0Àþ,qßÁt2÷%=`áêxõ¹Öä°.o+ÇÿÚªñ³å«A ÿÔé °€ÁRlŽ—Zs\„FÀ¡©)Ò3B˜P›ãwFù«&á•=Rý&ÀJh›媀ªøY]»óº×¬{/šìê¼òçëN ÜÇ6ï»zm]=¾˜-ß»N Œ�«'ªªâguAØãÓòÕàºk×ý8¼ºz¼ÜeVÈ •X#QÕ¥e=Cg_Åx©5iÂî, )5Ez:°z®éÈߺ.£&áÕÉG‹#Ü ›°+PÛî ª ¡ì²`ìÚØoõüáY«ï·nÇõX‰•¨¶º¢tW0vUV—·•×6yܤck1{½‡vUQ§—«ò{ç×wo¯±°@€°³ª°ªj÷ì.ŠPËÂvKÜ;Éruh‡…«ã¥Öd_jr`•š"=X Õ=‰ñE i5©É�ö�«›Z‚UpåPé�ËыѾâ3êökíKP`„0íþã¥Öd�édÿì;¯*—²/fËFËÚ�qQS¤§«v]ê4WtJ=>½„TUÝY›Â«CvtPOV‡Õ-Ž´Pšñ´t¼Ôšã¢k 845Ez:°hÚqUR ¯à�}«®uTÕý[¡|Ÿöbc£ ,OKÇK­I�‡ËÛ8½¹Øx]Ó©Ó0jŠôXÀ`)6ÇK­É6„PÀ&jŠôŒv�¥î°ŸòˆÝº‘»¦�V«.ow»1v¢ ,OKÇK­9n}Z꾩û+¿¾‹ìêüˆwTQS¤'À:0íÈ�Žbs¼ÆRkÒ5;°‰š"=#„拺mSÍnÅ@z, 7Öí²:¥Ò`�'5ЮcîƒJ–PM€u@žÔÀa-f‡«¹³«ó8ùhq°÷ ¹ÑXº£ ÿ}Úàó‡g}šm€µ®;J¸éu¥.ïÊ}ŒÙh¬uŒþ@»vÙ'u¬º|S@åßé °€ƒk²|=ÕÒt@÷ °BK0tÁ¾' ®Öõå0lßp¬üo†‡ËÛ½Þ€ídyž'ùà§ç�Ó|00““²Ô÷@jMŽ!¿¾Û;túAM‘Þ(;°t\ôÇ(¬&3îB.h×bÖŸ]SMþ= û àxF`5±r ³`g÷›ëêÅlùö«?[uŒúÜbw€nm€µÍ—ž//8Œr8uv?û¿â÷åàk]}žê$Ck4V9°ªúÒÓiǵ©+kÓïËXÛc}'-Z}?v3¸«.„jÒEõø´b@‡lÚ›U¸ÊXûîÝzþðl¯×ÐŽ,ÏÓœ0ìhcàÐy=^jÍþy¸¼�Ó›‹Ô·PIM‘Þà:°€þ© ¯ÊÓûvSõéDÞ§ ,OKÇK­ @›ÔééÀ’jÒUu�Ž*€ñÐ� –§¥ã¥Öì;°€.SS¤7˜,§@•Ã+ÝU¬L€õøä ºâáòv¯×ŸÝ7@-ì¾^XMº­td@:mŒ6=…°iØ%èè¯^X뺭Š/9YÐojz ½ °ÖYý’Ó…¶K [z`íFybŒôWo,a CñpzÛ@iߪ³ûùÞËåH£70 ÅÃé6GúÖMl¬em,—àøz`ÙoÇчѻuv_ô_/¬ª°ÊH!GªÈCk ½°„U0>þ@¡SVÕ–¦?çXc„‹Ù²#‹W§¬ª',MNyŒ0¿¾‹ˆwÁÖêé~M8¯+‡Ug÷ó÷>kÛ0«¸/†%Ëó<É?=¼ñƒ§“ù{aUùïëLN>ÈRßi4©59Œºš=¿¾‹ìê<ÁìOM‘^§,€}(6ÇK­ @›Ôéuj„ÊXtš €N`Ði,`kùõ]DD,f/'>\Þ¾ýÝt2ßøúâuÅŸ°NçN!,¾ìªŽÞ؆ƒÆË)„i,fË8»ß^Uy¸¼�Ó›‹–ï jŠô:×�õø´^@ûvCm ¯Š÷/Z¯~žð €u:Ñ�5�Ì…V@ë<-/X´IM‘^':°„W�NyŸÕ6¯)¬îÀÚ¤ª ÖéD€O]p´ëþªm=>-ßîËbÐD'FA»ÿx©5ëå×w‘]�¿ý³lŸEìC¥¦HO€ –bs¼Ôší°«à…š"=#„0bëFøªÂ«M#Å~«²òÎ,؆,`°<-/µæþŒ¼£¦HO LÝNû†WÛœJXÐ¥@XÀ`yZ:^jÍãª[0jŠô:Ñ�U7'^Ñù´i¿UÕk""²«ó�:® ©NXN6€ãÙ&¨€.H:BèX^à�´û�—BÚ¤¦H/i–ð Ž«ý«[˜®; €.²Ä,OKÇk(µæÉG‹xþð,õmŒžš"½NìÀ^^m¦c `XÀVº�Ý;Ñ` X0—·•?ïRØ»² ,û*ÆK­ùb1[îÕ¡äÔp€jŠôt`@�«£j1[ ¯è ôH¹£*¿¾«¼®I÷ÕÉÝçÞ»~ÝçT™NæF8 #„À`i÷¯>ךùõ]dWçqòÑÂI„ ¦HOÉÉç¿°ñš"¼Šá¼!À€#yþìgò¾‹Ù²Ñ)„ÓÉú±@ã€t•B`°´û�×ÐkÍÕ1COM‘ž,虪ðªIô•,`°<-/µ&mRS¤§ €N`Àˆ1 XÐ3ùõ]kïuv¿þdBèôÌ>'¶~À±° ƒ4í~@*,è 6‚¦&û­ìÀ X�ÀÃåíÁ?£É~+;°è$pzsñêgÓ‰0 ª°`O'-ZyŸÇ§vÆù,j`hX°§çÏRßÂ{ŠýYö[0,HhS·Ôt2¯ ¢³åÆ×®î·fÐgYžçI>øéùã4 ŒÆääƒ,õ=�ÆÐkÍüú®•S hFM‘ž,è uÝUuáÕ¶KàOî>·Õõ�Š :àáòö½¿W…Tåk ‹Ù2.o+—À¯Ž–®çóï‰Kßè>#„À`i÷/µ&mRS¤§ €N`@�ÿ` XÐcN#` XÐ!«U'-Þ t‡ :¤è¨Ê¯ïâùóÄwÝ À€#ªÚYµ˜-_ýÌh ¼#À€#*‚©‡ËÛ·?;»Ÿo|Ýt²ù*´d5”ÚäôæbíïË�Z�O¯»´`,XÐ’M¡TYÕè`¡éaÕH" M–çy’~zþ8Í£19ù K}¤¡Ö MjŠôt`@ÛŒFØ�À¸ ° eùõÝÚñÀÅl¹õ¸¡XŒ™  ÉÉ‚@3,hÙ¦ì«á–%ì°™ öÐ4€*F Ë×7=mÆÌ)„À`91h¼úPk>\Þn½ €4ÔééÀ€„WМ €N`@ Nî>×躪�YÅ~, šXÀ`ÙW1^jMÚ¤¦HOì¡è¨ÚÔ�Uw áêï€j:°€Áò´t¼ŽUk.fË8»ŸÇÉG‹xþðì @jŠôt`ÀŽÎîç몮+ 9Xvu'ŸÿBâ;€~`@�rçTÓ“ë<ö3?xÍ,`°ì«/µ&mRS¤§ €N`Ði,ØÑb¶Œˆˆ“�‰ï†Í,`°ì«/µ&mRS¤§ V8ºG€+²«óÖÞëáò¶µ÷€1`0jÛv\­ ¥Ê¿;½¹Øéž€÷Ù� –}ãuèZ3¿¾kµS €nSS¤§ *' V^Àq ° ^�žÝÏ÷~O;° ,F­®švUmJÙ�í`0JEU®Ê�XÓÉû�XB)8>£pr÷¹÷þ^¢ê:±ŸêwaÇ!À`žÏ¿gíïËÁÕº%îÀq °´òH`Sm,qÚ!À`К.g�x=fX¿Ê¿¯ºh_–çy’~zþ8Í£19ù K}¤¡Ö MjŠôt`@IqB!Ð ,()ŸP¤%À ÓXŒžEìÐm,Fo›“ €ã`0x'wŸK} À¦©oåáò6>ñ[¦ñ¬Ã zM€À`9M†Á!ƒópyûöO Ú ÿt`0X:°`²<ÏSßÔ2B@§ °è4�&À ÓXtÚÿ§Àœ»ðÑIEND®B`‚flox-0.11.2/docs/diagrams/map-reduce.png000066400000000000000000001316661514775624000200420ustar00rootroot00000000000000‰PNG  IHDRË' YI6gAMA±� üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿÿÿ ½§“ pHYs × ×%uÒð€IDATxÚìÝw@SçÞðßIB€0e)" C "CQÜ÷@{õj«µõ­Ý­£õ:Z­×Ööv·Z[µu¯¢¸q´N@EEA@œ€¨ Y2Îûǃiš¢ÄÄïç�÷=œœœçyx›ožÅñ}º\.¯®®nds”JåºuëÂÂÂüüüüýý.\¨}Myyy@@€ŸŸß«¯¾š––ÖÀ­þûßÿvéÒÅÇÇç_ÿú×Ù³gë¼lñâÅAAAW®\Ñ8çÎ�¼öÚkUUUêçwìØôꫯ&$$4ð{ùá‡z÷îíããóþûï—––6¦ùiii~~~cǎݶm[�_@äççO›6mðàÁr¹Üßßÿ×_Õ¸àÇôôôìÓ§ÏÏ?ÿ¬P(ù䩸¸¸G�¯¼òJ×$'':ÔËË+,,ì‹/¾¨¬¬làâëׯ/X° ÿþÞÞÞ~~~o¾ù¦êÏIuAPPÐ’%KÔO>xðॗ^zûí·³³³uÛ@€g—æ;ï¼3|øðøøø:_�‰‰)((¨/\1………_|ñEyyùÕ«Wµ_MNNÎÎÎÖø|Ïçååååå©âÓ­[·222bbbØ�—/_Þ¿II‰D"iL[”Jåÿýßÿýç?ÿQņššíËJKKïß¿_RR;bĈsçÎÕy·�?þxÅŠ………eee§N�?~ü‰'´/ûóÏ?óòò¶lÙ¢qþÂ… W®\9pàÀíÛ·ÕÏïÞ½ûþýûyyy±±±ãÆ�ûòË/µïÉóü{ï½·lÙ²›7o–••mß¾}ýúõ�y'Nœ¸qãFIIÉ™3g>øàƒÉ“'«ÇufÑ¢ELMM-...**Z¹r¥ÆYYYÕÕÕׯ__²dÉo¼Ñ˜r™ôôô®]»úùù�1â—_~шÖÌýû÷oß¾}èС:_e¥GEE]ºt©ªª*;;ûû￟:uj�¿G"Z³fÍÀ�ûí·ŒŒŒòòò’’’]»v½óÎ;êל9s&//oõêÕê�bùòå999...mÛ¶m|Z6$LÖáóõ×_×ùj~~>5<wçÎ�åååDtÿþ}íWËÊʈèĉÚ½|ŒT*uvvfÇÖÖÖD´{÷nöãñãÇÙIŽãÓ–ÿýï±±±&&&?üðCjjêµk×êìÃdÌÌ̾ùæ›ŠŠŠ™3gj§�ëׯ¯[·Žˆ^ýõ­[·öèÑC¡P|øá‡ÚW‘vW˜��;hÓ¦�úy"êß¿¿�� }÷ÝwÚéqçÎ�,f/Y²äܹs+V¬˜8qbcž»¹���ŸŸ;vlæÌ™×ܸqƒˆXw߬Y³V¯^]ç­Þ{ï=ooïýû÷ÿñÇ�)šˆbcc JJJ.\¸°xñâáÃ‡çææj\#ÕþÑ*•Ê:oòå—_–••999mܸñÓO?522:qâ„v€'¢;v,\¸°ººÚÚÚzÊ”)‹/~õÕW;tèàéé©~™¡¡!UWWgee±3%%%¬Õ“&MjäŸÀó@7£d‰(..N£«�a ¡á¡’{öìa÷îÝÓ~U5ÀuëÖ­u¾ÝÀÀ@õ)ßÍÍ�ˆNŸ>ÍÞuéÒ%"rpphLC²³³YwÜܹs(•J¾^,GFFúúú^½zu×®]¯îÝ»·¦¦ÆÅÅå½÷ÞëÒ¥ËÏ?ÿìââ’™™ùûï¿k\É’ËØêLMM‰ÈÎÎÎÈÈHý|ûöí‰ÈÏÏo×®]]ºt!¢Ï>û¬¸¸Xý6r5*****Ê¢oß¾ÆÆÆ�yìæ­ZµÚ´iÓ‹/¾HD»víÒ‹ëääDD>>>o¿ýö«¯¾ÊB©6333Öø¿ÿý¯1EÑÝ»w‰H.—óÍ7�;wNMM}ã�74ºU ³Î‘ÏÕÕÕ ¢wß}7((hܸqì;‚/¾øB£3–çù¥K—Q‡:ôÑG�?~Ö¬Y˜;w®ú•ìàÁƒì`ݺu¥¥¥£G�ndÓž:H˜ª®¤ƒj¿Êº}êën"¢âââ3gΰc04¨&ÑÅÄÄÔÙªŠDäííMDååå/^$¢›7oÒÃØùH7n¬ªª²´´1bDc®g#oûôéCD,Õ¨;uêõë×�ýhhh8uêT"ÒîÐcG;„›››“Z¼QaÃ2+++íìì~ûí7ggçâââèèhÕ%%%¬ùÆ kLCÔµiÓ†ã¸ÊÊJ©TºhÑ¢!C†Ñš5kÔ¯éСi�žÕ~>=zô�J¥YYY©©©�)��v ŒŒŒÜ°aƒ««kBB‚Æß•ªk·ÎÙ•.\(++“H$į̀Q£Ú´i“——Ç:´UÒÒÒîܹCDï¼ó{Ôõ±µµeb±˜ˆjjjX§ñĉe2Ùã>a€L— óèÑ£/UTT°>ÌÚ9uêTMM ‹ 쿆’’"222ºsçN�3Ùç~ÆÝÝÝÊÊŠˆØÌO6ôÔÝݽ1 a]©Ã† {dï%ÃêÌ2mbb¢Æ«éééDÔ£GÕ™ˆÅâ”””œœõ+Y�ö#211¡ºÂyëÖ­éaŸ§L&{íµ×èŸñ^õUƒ‡O"‘X[[«Ò#넬+•ÑV­Z‘Ö ÒÊÊJ­ÕgôYYY±VÔ™“µó×ªÝ·ÉæšªFÕQrr²ê‚†Ç‘>’µµ5›ûJDíÛ··µµ­¨¨¸víšê}5Æå>Öó©Ë–ªžêÀÀ@z8ÔYE•ÿµÇÓÃ?9��»ýû÷'­”Ë!·jÕŠ4€5�þ¥±éµQQQì*:H˜ª:›Æ\Ì:ÙXjb£aÙ²®*,}©ú¾TX—¦Æ¤Döµ“$ª}žã8SSSUÀSlÕrG–––öööDÄfc>.SSSõÍ3ÙýµWLm`&{>FFF555l�çSV–ꯅÍËÕø�°/5êL˜,޲@¨bffæààPSS£úƒTÝ\}oØ|K‰D²iÓ¦šššÁƒ7rù(€çŠ&Kááᤕ0UQª¾-ï/_¾¬P(\\\ZµjÅ‚„jCÕPÏnݺÑ_ý¥q�Fÿ¡©©i@@ÏólóÉÆt`[×Ñѱñ g岉Ã2U©Xã-lÍ!�%sYÈÑlìþuæ(SSSÕõ¬¿”ulª.`Cgcccß333õ¨ªûk\Ö@¿4c``pëÖ-ö(ê[oVû²@õg30]\\4.cپΈˊÓH˜DäêêJÿ\JŠõÓª÷Ö6€•ÈqܦM›ˆhòäÉOð`Z<��’íÕ«i-x£Z_Âd£j}||èaW•v¥jV›`©½ž�F&õîÝ›ˆöîÝK>}$6º²‘S¬J,ѱ6ªV.eXFÒÎ9lX�§õ%L‘Hd``PUU¥½Ø�‘‘‘êú:×Ë}á…ˆèôéÓÚÏó‘  …j•{÷îq§óI$±‡cgg×ȜϺmU£dÙº»ƒÖn>Õ“0Ù{óäÙ•�\—acÇŽ»{÷®»»{ã‡RlÝÔÈÈHíËxV¬usçÎe³jÇ�×È¢Ù¯’%̯¿þšˆfΜÉz,Õ±3uþE±¸®ý¶’zVg “Ín$ÖEßÀ— Ï9�Íä‡ó$“’’Ø�l™ÕxBíœÃó<[&ÇËË‹®Yª>SNë>:uê”êTW&©Mükä3l\k#—Ìêêj�ÞNõŒáïïobb’˜˜ÈÒK˜ûa2ªµO5v¤lë¾{$–0ÍÍÍ;wîLW[­ó™h¿—ÕV½»’õÈÕy=‹gÚë�ŠD¢Â™3gŠD¢/¾øBc™•×^{í º1Yåß}÷Ý’’’É“'�9²ÎËŽ;6|øðwÞyG;ß²çÃ:œµN#Ÿ~~þ„ ŠŠŠ´/k aÖ÷äÕgx>Ö¢×_}ذa2™Œ-"t6J–}¦÷÷÷/--½yófvvöÝ»w}}}UëÁh'L6DV5„’%Lí+ÕS‡D" )//ONN¦‡}eu.Ö¢Šm�øÊ}ˆHûùÔ‡¥>ÕŸÊœ9slmmW¬X¡‘Y¹uþfÙ“¯o½(õÇÞÀ¾u¶HõM‡¿¿¿µµõ�Ù(€ç‡ÎFɲTо}{"ºtéÒÙ³g‰Èßß_5„U#aVUU±Á¥ª(HÊjL½cãlU½O...lßU’ѦêmdÂl ´hSUF$õêÕ+==]cX¦¥¥¥H$º{÷n}Ã2Õ³™j§“ÌÌLÍ_�HDu¥ v“FÖvذaD”žž~õêÕÆ\ÏnÞÈá£uRý^X«j›GRqMD2™lôèÑeee;vìP¿ŒuTÖÙ‡É:„µ7Ñ~ìõ¥÷:UWW«ˆH$êÝ»÷áÇë¬ÀóLg£dUûIZZZ^¸p!==½]»vVVVª…^5fFF†R©‰Dªá¬ô0kåçç«_ÉúŽÔG3öìÙ3??ÿÆ� ¤ W9 ’e�:gOªk£–-™0#""ØèÓ'N4æzvÛ¦Ä'Õói×®�«««jíßGb‰Z½]Ý»w'¢ØØXõËø:€ukgu–$Õ·-e äj/„[§ŠŠ õ?¤>}ú”””ÄÅÅ=ñ#h‘t܇ID–––………555>>>¤¶Ð«FÂd›šp·páÂÍ›7ÿõ×_ ,Õh-¶½‡zö '"ÕŠ²õÕŠ%·:wMÔÆ…j$Ìû÷ï:tèçŸÞ¸q£zYê ³gÏž"‘H;¼ÙÛÛ‘ö:7………666£dYéÚ³í�Ì¢u#C L&c£—ÙòH�Ôô„©>Ý1""ââÅ‹u. «M»o–-5|îÜ9õ{²>Ì:¿_hÛ¶-ݼySã<«€*ÌÓÃ,ÚÈ„YZZªÞ“&‘H0P@ƒ&ûè¯JAªÅcY6¨/a²e~”JåÚµkgÏžýÒK/�7ŽMDÔH˜¬S}OÈÐÐP‰DÒp¤‡¡±‘}˜,{¨v��� yå•W–,Yòá‡~÷ÝwMfÌÍÍýýýÿüóO�:::R] ³  @5ñR¥C‡TÿˆMí&°ø×ÈðLD�:u"¢k×®5æböTsmêϧgÏž<Ï;v¬ñE«GGkkk™LVQQ¡ÞÌú0ëL˜lTíÇÎ:ÆÕŸ<[§7''ç‘kA•——+ ¥R©>|×××766¶‘ëH<'t�0YþQUÂd«¾y˜¬sÒ¤IÑÑÑ?ýôÓÛo¿È^ÒØA‘ÝP½¯ÉÔÔ´K—.‰‰‰ Ì了\6€õ}åææªÎXXXØÙÙY[[³M)OŸ>­zI£ÜîÝ»_»vMcÏ6¿4##CýdyyyYY™ê©°„YßÖ‘Ú “5ªñ�°Ñ¡õõ‘6ñæÚÔŸO`` T*ÕæZŸ:§€²NìÂÂBÕÖ‡YgºsrrbOR£×”=[ÕÆ0ô0‹–——?r©[UÑê_ß»w/))鉟@Ë£ƒ„É>v«‚œª›ˆ�’å8ŽMÔˆI,aFEEuîܹÿþo¾ùæ¦M›HZó0YœPDÔ§OŸôôô†‡8²*5œBUØÊ@ê£+½¼¼Ž;vúôéŸ~ú‰Œg¡¡¡D´ÿ~õ“lŸO�Â:âÔg2¬�1--­ÎÚjŸdMÓžŸYõ¹²�ô¸7צžý »téräÈ‘Æt&×ù+cSyU�áôpS“:¦‰‰ ‹ŽOž­.«1“}wpùòå†k¥ «ê©»K—.DtðàÁ'~J-�&ûØ­êwò÷÷'"+++Ö/GÿÜ0“¹ÿ~nn®L&c1L…¥�,ÇFÉj„ɾ}ûÖÔÔœ?þ‘kd¦———X,¾råŠv²òöö¦v0jD ???�„éëëKDÉÉÉê¹.;;›öîªcý·%%%u¶H{G®¿Ü+K¶æææ�¹˜µN'kÉ2]»v---=yòd#ߨþv¥R™ŸŸ/‰œ��U'Ù¦2õ�Píܹ3±ÕŒU²²²Hëɳ+¹`�êoOýo˜ý�7²oà9¡³Q²ª@ò¯ýëå—_þþûïU¹ˆ¨¯ÖœéÔ©“j ­:�0Éâ„Æv ...nnn)Bë\mdR222ò÷÷¯ªªú믿4^244433ËËËSÍÕHP†††žžž.\Pß$ÃÝÝÝÚÚºªªJ=Y]¹r…þ9V“ñòòbáçСCÚuÓ¾ž5ªñS%ÙƒRíàÒ0Ö:]ÍÃ$"???"Ú·oß#ßÈþHÔßž˜˜XYYÙ½{wõ�FX¯f} 3$$„ˆ4fÆÖùä»uëFD±±± ÿ‘¨ÖRÿ½[[[;88¤§§k¯[ ðÜÒYÂTuÊd²¹sçÿ]†Ö|HÖSÇz�´i,èʆDj$L"4h[.¨>,#5²“ˆ†JD›7oÖ~‰ ýUÍ´ÔÎ6l˜«z�Çq}ûö%¢?þøCu’íÛ¡¾¢©Ê¨Q£ˆhÏž=ê1†� Ö^ˆ%¢Fd½uë«9Ûùã‘ëæuÒx>ìá|ØÕÕõ…^P¿¬Î®o•6mÚôìÙ“ˆ¾ýö[v†ýBµ»——ûÅ-Y²„ £%¢’’’ ôèÑCõÛ¬oÑ#6Ó @E “­Âª>ˆQ³ ‘ˆÔ&ò<_YYÙªU+Ô±±líPöù^{'/////¯*Æf#P%"ccã?ü�ˆÞyç�/¿üòÚµk÷îÝÛ½{÷èÑ£Y_¢j<¤vï[]&!!A}E"¹\®T*_yå•Ìš5«¬¬,((H;1‘µµõ[o½EDóçÏW¥�””¶¨Œ:í}#Õ]¿~}ݺul‰ÔãÇ�ÿúë¯D4mÚ´FÎÃløæ*¥¥¥üñÇü±gÏ�År´8{> \%[]]ý×_½òÊ+Ÿ}ö™D"ùüóÏÕ—ùQýN333cbbbbb´—Ûyûí·‰hçÎ�K–,Ù¹sç/¿üBD&LÐ.‘]™——÷ /|ôÑGŸ|òIDDÄo¿ývûömÕ¯[Õõª1!–MÐMHH¨o›€ç�¤é·xã�7Š‹‹Ù2°u—!‘‘««+û‘ã¸èèh…BÁΫ›1cF‡ÂÂÂÔO²NÈ:»ÔÆ�·`Á33³:Ëeq¥ñ£d‰høðáW¯^ýþûï¿ûî;õ 0� ¢êy£uëÖDTSS“žž¤:¿hÑ¢áÇ߼yóµ×^#"33³O>ù¤¾Ò§L™rúôéƒ>|ܸq·oßNMM�:uªvYh×~€ÌŠ+6mÚôŸÿüÇÞÞžåÌ^½zMŸ>½‘¡á›«^ÊÍÍ}ï½÷T'׬Y£ñ‹SïilÓ¦MJJÊ#Wme¿¬ÌÌÌ—^z‰ˆ ¿úê+¶¦Ž:¶±gBBBBB‚ê9¿øâ‹ª ärù´iÓV¬XñóÏ?³3QQQlÖ¥†~ýúMž^^^#FŒ°¶¶ViÆÁÁaÓ¦Maaa2™ÌÏÏoÆ îîîõþ2D¢üqΜ9–––+W®Œ��÷ÝwgÍš¥}eÏž=���ëmKD¯¼òÊàÁƒmllòóóÝÜÜæÌ™óóÏ?7�5ôèÑÃÌ̬�¯ är¹\.gÎDdmm=yòdõ™·���&&&žžžª3Ó¦MkÓ¦�vg¬†nݺY[[‹Åâ¶mÛFEEíÙ³gÀ€Ú—õë×O•úˆÈÀÀ@{jëìÙ³çÌ™coooeeõÚk¯-Z´¨¾BçÍ›÷Å_têÔI*•Êd2¹\>kÖ¬]»v©º|ƒƒƒ† ¢^(ÙÙÙ¹¸¸ØÛÛ»¸¸4òÙ´l\}Ë¥<�­ôÏ9$LÐ $LÐ $LÐ Vú�¨{‹k×®%''ë»n�-$$ÄÖÖVßµxNÕ�0÷îÝ›””¤ïº<6…B1bÄ}×à9Uw¬©© :t¨¾«ðV®\©T*õ] €çVúÝ@ÂÝ@ÂÝ@ÂÝ@ÂÝ@ÂÝ@ÂÝ�4ñýb±XßM€ç¶»hΚ”0Åb±©©)Çqún³êz¡À [Y t¸PÒAÂä¼»““§Ð7·aÿ_níhmd*pá2‰��HLDŽm̺Q[AÛm*e†.­D†BAÀŠ9C ÙÛštó´á2ãç÷Û>7“²Ò„.ÔPÆ Š*²î–\ze•¢†ç‰è^yIzÑ=!‹6–ÔþSæ—We ÜpI…BàtKŸÚEÞÝõع�“ÜÆI_¥;µ1sjc¦—¢ ]Zº´ÒWÃímMím…öÐÌé aVU+Yoƒ�D'5QµB©¬ºtŽ8©�ˆã8…²F¡¬ÑKë” /hѬá†b‰ˆãxE5)ª-[$â¤F·‹柧3ïæ=¸Þ­mLú‡¶'¢Óï\Í,¸t™42ÜUj NÎÈKJËÕKÃÿÌI�¿wCà†[Je݃, eü•³ü™}B͵jÃE¾&p{à±è aWæÞ/¸Þ†ÒÚš?¨¾ôʪÚnÛ²r…À¥«~¿²<ëA¡À /WT×vœ–—�°kÏÝO �»•€n a€nˆ8ŽÓw %@&è&è&è&è&è&€@ÒÒÒô]€§K¢ï }úX[[ë»:º‡„ œâââÇ:t¨uëÖáááݺu“Édú®€Î a‡çyöïܹ³uëÖÍ›7{{{vëÖMßUÐ$Lx†?~¼¢¢BõcAAAMMMLLŒ¾ëõÇQMM %'''''¯[·N.—‡……yzzê»vO ža™™™%%%ªËËËyžOOO×w½ÏóR©´ºº:;;;++ËÆÆS4àÙ…„ ϰñãÇ«ÿ¸råJ—Ñ£Gë»^u{õÕWUÇ<Ï( KKË€€€ÐÐP'''}W ©�0„ÃÆÇŠD"‰DÒ¥K¹\®ïJè &€ |||är9V‘€– @ cÇŽ•Ëå˜f -&€@úôé£ï*<]"}WZ$LÐ Ïóú®´èÃh.JªÊô]€&ÁJ?ÍŚ˻s÷wëÖÍÆÆFßÕxlH˜ÍÅ÷>—íî;vl×®]®®®!!!���Ø:ž!H˜ÍEk™•÷è>£F�º|ùr\\ÜÖ­[7mÚäëëâëë+‘à± ¹Ãç€æ…ã¸N�:uêÔ©²²òüùóqqqË—/766îÚµkHHˆ««+Çqú®#@Ý�0š)CCÃàààààࢢ¢“'OÆÅÅ=zÔÊÊ*(((44´uëÖú® €&$L€æÎÂÂbРAƒ ºyóf\\܉'öíÛçììÌò§©©©¾+P à™áìììììÈ×^_¥÷nëÑ»­‡^Š6 v¢`'}5¼‰Äb±\.—Ëåeee§OŸŽ‹‹[µjÕºuëüýýCBB:v숉šð” a´X2™,<<<<<>ÞÂÂ"((¨[·nú® ´4H˜-Ÿ��MdddddäµkרDÍØØX‡���àà`LÔ]ÑAÂthmj( \o›VÆì µµ¬²J)pé2#‰˜ãˆ¨•…‘›“¥^î`bégí(pÃÍ ¥" q¶¼›¿�Es67 Eruuuuu}ñÅÏž={úôémÛ¶mÛ¶�MÔìÒ¥ &j@q555Úr¾ýö[ssó¡C‡6üf±Xlff†ù< ˜’’…BQß«+W®tqq=z´¾« ðÌ(++KLLŒ‹‹»råŠT*e5===Åb¡¿7€–£dž_2™¬G�=zô(((`³4ãããMMMƒ‚‚‚ƒƒÛ·o¯ï À3FÒôÈ’c7y„®·�‰Y˜3ÎNK-º+péf†c] Å’Ó¹7OçÞ²h™Å°ö~DtüÎÕ¤‚lá>ÌÙÏ\jT‘‘_~QÐÇ.¶02�p¸½Ï++«Áƒ<8333>>>!!áðáÃmÚ´a5­¬¬ô]Ax6è ³:»¸*³Hàzó•µs/o•\ÈϸtkC%_CDwÊŠ.½\QŲê¥áƒœ¼‰HYXQ™Q dÑ[� ð|rrrrrr5jTJJJ||üž={¢££ÝÝÝCBBºvíjdÔÔ; eÃ(YÐÄqœ·····weeå… âãã×­[·aÃ??¿��oooLÔ€:!a@½ ƒ‚‚‚‚‚JKKããããââ¾ÿþû§1Q³æÄ|ö¡›g`(ê?™3±¸ž]túâ¡K'’{Ùz´·*ª*ÿýJ|¥RÑô6^/{�ÛvÊU%‡¯Õ<¨¸á;L|h©�0àÑLMMûôéÓ§OŸ»wïjLÔ ´±iê~B|^6e¥ Ý*CcRTQYyuÖÝ¡K'êÐÎ’ˆªk”E¹åJAcžœíw¥¨©¾]¢,®¾íÐR!aÀchݺõСC‡š‘‘wàÀ�èèhWWW5e2™¾+ú„„ O¢C‡:t7n\JJJ\\ÜæÍ›7mÚäëëâëë+‘Ôñß—˜˜kkëÐÐP}מ$Lxr‰¤sçÎ�;w®¨¨8sæL\\ÜO?ý$“ɺvíâææ¦~ñÑ£G‹‹‹333£¢¢ô]qx*�0@ŒŒŒzôèÑ£G�‚‚‚¸¸¸øøø£G�ÚÚÚwëÖÍÆÆ&33³¸¸˜ˆþüóÏÜÜÜ—_~CjZ$LÐ%++«Áƒ<8333>>þرc»víruuå8N$ÕÔÔ(•Êäää¥K—Κ5 ! …é»Ð2999�=zéÒ¥o¾ù¦­­íµk×”J%q§T*ïÞ½;kÖ¬ÌÌL}Wt ž"Žã¼½½»téÂó¼úIžç«ªª–.]zöìY}×t žºãÇ�Çqç«««·mÛV^^®ï €n`&<]eee/^”H$Ú¯rWVV&Õw%@'�0àé*//�ŒŒ¬ïUccc¬÷Ðb aÀÓemm=dÈ�†¯Qê»’ ˜‡ º�„ º�„ º�„ º!Rßà‰a-Y¨9²�Ï͸PÎÁ]Ô}%\̹‘],pé–æ†½�¤â#·Óón YtsÛaíýˆ¨4>³êz¡À [Y t¸P€g&€ð¹™”•&t¡†µÛÉUdÝ-¸ôÊ*E Ïѽò’ô¢{Bm,‘²e~yUf‘À —T(.à‚y˜ H˜ H˜ H˜ H˜ H˜ H˜ H˜ H˜ H˜ H˜ H˜ H˜ H˜õ*++Ówž%}W ù:tèÐÉ“'ýýýûôécmm­ïê4wH˜ )((øë¯¿:Ô¶mÛ=ztëÖM&“é»RÍ”„ã¸&ÞBli$©P\o±¥;°22q4±¸tK©LD™K�.ÝÖÈŒ´24ÖKÃ%œˆˆD2‰­‰�EK¬Œn,ÊØŒÌï24"NDD‘™‰TøFK DD$â¸V†2c¥ ÿ%5Kˆˆ8Ndª‡†‹dÂÚÇ) žçsrr¶nݺyóæÎ�;wéÒ¥[·nú®@³£ƒ>L‹�îzlÀPçÎC�;ë«ôp{÷p{ý4¿¿c§þŽ�ôÕpãNvÆ�ìôU:´<âÞã¨F)x±I�ˆ¨C»VÎm-„oµD""¢V†&3åýyž²h©XBDb3C«1>$lÑD”qýÚ±˜� }biiiì€ã8žçÙo*999))iýúõ]ºt õôôÔw5š‹&%L¥RYZZªï&ÀsD©þ(¡&aŸ}EèR Eý's&7sŠO_¼#|«å^¶í­Š«Ê¿_)lf/{�Ûvʲª’Ã×jT Üð伤½� \hShøR*•<ÏWUUÅÅÅåççB&ÓÔ>L…Bèñ±ÐòðyÙ”•&t©†Æ¤¨&¢²òꬻ%·ºC;K"ª®Qfå–+�yrkG""EMõíeq¥À ïéÔyøò \苉‰Ù½{7;f˜,pZXX 8P.—cùuXéàxžç8Žã8©TÚ£G'''}W  9BÂh‹—]»v ”Ëåú®@³†„ P/''§I“&ÉårìPÐH˜õB§%Àcé»ÐB a€n a€n a€n a€n a€n a€n a€n a€nHšø~±X¬ï&ÀsD©Tê» P¯&%L±XljjÊqœ¾[Ï‹ÒÒR…B¡ïZ@Ý$<Ï7%"rÇÿ¹‘Ï͸ޜ­“¨÷x"*�Ϭº^(pé"óî"©øäÝk§î^²hG“VQnDtîò½+7ï ÜpcI¯ v&Æiyeçr„,Zlid1Ð]àöÀÓ“ŸŸODÖÖÖú®èRSGÉŸ›IYi×›x Ì/¯Ê,¸t±¹!ÕðD”_ñ ½èžÀ¥3…%YwK.ÔÜDªPÖ‘²¤RàÇ.©@×%À³-333+++//ïâÅ‹999b±ø½÷ÞCÂhat�0´¥¥¥eeeåææ^½zõÖ­[ªólìÌôéÓ�œœô]G]âl�ø¦ßåq µq`VFŽ­Í.ÝÒÜPÄqDdglæaa'dÑ2 v ¶6–:YÜp±¥‘À%üðÃòòr‘H¤T*Õçb°x9vìX¹\®ïj蛿£/A¾öA¾öú*½w[�Þm=ôR´i°ãë*€f»•€ŽÉd²±cÇŠÅâšš�x)‹CBBúôé£ï:ÀS�„ ºêìì,ýã¿2b±¸M›6QQQú®<-H˜ðTÔÔÔð|íÄ4Žã >øà™L¦ïªÀÓ‚y˜ c÷ïßß´iÓ¹sçrrrjjjˆˆçyÄK€ tF¡P8p`ïÞ½...‹-211™5kVUUMš4 ‹Ç´T¹¹¹UUUú®è&èFrròúõë•JåäÉ“ØÉáÇoÞ¼9"""44T߀§åüùó[·n 6lö¹xÎ!a@Såæænذ!--­_¿~ƒ–J¥ª—úôé“——‡Õ}Z¶~ýúuêÔ)::ú?ÿùOxxø�!C0$ๅ„ O®ªªjÏž=±±±;v\°`�­­­ö5ˆ—χ×_ýêÕ«Û¶mûè£� ¡þ}<'�0à �={vË–-‰äÕW_íܹ³¾«úçææ6sæÌ¤¤¤èèèÇ2$44T,ë»^ $LxlwîÜY»víÍ›7ܯ_?‰ÿ5€¿uîÜÙ××7>>~çÎ�>|x—.]8ŽÓw½@øL�¡¢¢bçÎ�GŽñ÷÷_¸p¡•••¾kÍÇq!!!]»v=zôèúõë÷ïß?räH///}× ž:$Lhžçãââ¶mÛfddôÖ[oá“"<’D"aKIïÙ³ç»ï¾sss9r¤³³³¾ëO&lذ֭[ë»^ðTà#4äÁƒÑÑÑÇŽëҥ˴iÓ0,žŒ¥¥å„ úõë·cÇŽ… †††FFFZZZê»^ cH˜P7žç�=maañöÛocX,4]ëÖ­§M›vóæÍíÛ·Ï�;7""bàÀ�Ø< %‘`]/Ðvýúõµk׿åå :´W¯^ØltÈÙÙù�wÞIMMݾ}û±cǰy&@K‚>Lø‡’’’­[·ÆÇLJ„„¼ùæ›ú®´L^^^sæÌILLÄæ™- &@sWsdŸ›)p¡œƒ»¨û"J¸˜s#»XàÒ-Í {:I ÄGn§'æÝ²èæ¶ÃÚûQi|fÕõB�.¶4²è.p¡ê”Jå‘#Gbbbììì>øà777=VžÇÈåò“'OÆÄÄ`óL€  ¹ãs3)+MèB k§ÄUdÝ-¸ôÊ*E Ïѽò’ô¢{Bm,©£¥Ì/¯Ê,¸á’ …À%ªKOO_·n]aaáСC#""þxÇÙ8ð•eB·ÙÀ�$D$36plm&téD¦2"2‰;XØV*ýí[JeDD‘A[3±…‘À —Ø™\"4sb±8,,,88øðáÃk׮ݿTT¾äxF!a<ïîß¿¿eË–³gÏvëÖmĈzËúÌõÅÅÁÂÅAoƒ�-¤Æ3¼{é¥h±‰Ôrp‚æB*•8°gÏžûöíûßÿþ×±cÇáÇ;88è»^ðx�0ž_ …âÀ�{÷îmÓ¦ÍÌ™3Ñcz'“ÉT›g.^¼800pèСÖÖÖú®4&Às*99yýúõåååcÆŒ ì'h>Ô7Ïœ7o^Ïž=lf¦‡Ñìð¸�0ž;¹¹¹6lHII 6l˜©©©¾kPõÍ3?úè£þýû÷íÛ×ÈHè™ÃðX�0ž#UUUû÷ïßµk—‹‹Ëœ9sœ��õ]#€GPmž¹iÓ¦#G޼ð ={ö”Hð! ™Â?N€çÅÙ³g·lÙ¢P(&Ož‚a±ð ñòòš7o^||üÎ�;<8tèÐàà`üï@3„„ ÐòݹsgíÚµW¯^íÝ»÷СC1Æ žEÇ…„„tíÚõèÑ£[¶la›gvîÜYßõ€@ÂhÉ***vîÜyäÈ77·ùóç·iÓFß5h‰DzðàÁ•+W:::Ž5 Ka4H˜-ÏóqqqÛ¶m“H$S¦LéÚµ«¾k 3FFF‘‘‘ááá{öìùâ‹/¼½½±y&@3�„ Ðeee­]»633³_¿~ƒ–J¥ú®€î™™™EEEõíÛwçÎ�‹- Âæ™z‡„ ТB� 0 hkÐV_¥Ú:Úêçk0_w[_w½mB`ÔÁÚ¨†z> ŽãjjjˆˆçùÓ§OÇÅÅöèÑ£[·nNNNú®4w …âÀ�{÷îuvvž?~›6mô]#€g†jóÌmÛ¶-\¸044422ÒÒÒRßõhi0žaŸ}öYAA�ú™ŒŒŒØØX}׫Q8Žc]š‡:tè�»»ûÿýßÿaè,Ô'99yýúõJ¥rÒ¤I]»vÕwužIÖÖÖÓ¦M»yóæöíÛçÎ�1pà@üÇ@‡t�0Kã3•ùå×[lmlìDDeIwª³Š.�“˜uoLjÏçežÏϲè62ó�NÞD”pïFÊý�nj`8Щ“©�Qå�û)¹B-274ë¡Ù]›9s&¶0�–  ¹õ¯ÇÒƒ|íƒ|íõUzï¶½Ûzè¥hÓ`' Æ„x@È„ @7<==çÌ™CDeee™™™ùùùùùùW¯^-..ÎÎÎæyþöíÛ›7ožÿüs33³ùóç;;ëí3à™¦T*�?ÎŽËÊÊNŸ>ýÈ„ù×_©?2a^¸p�ÅK"JJJ*,,´´´Ôw»á¹€•~%‹Õ‡­ººº>ò-ê׸¹¹=òúöíÛ‹Dµõmll,,,ôÝhx^ amõêÕîîî­ZµúôÓO;wîüÈë?ù䓞={šššFEE5ffûöí¿þúkggçßÿ�ã8}·ž:XéKÏž=ÓÓÓ½ƒƒƒú@ÙÆ˜1cÆŒ3ôÝPxî t t£d ^œ­/|¡6µ‹ëZY9¶6¸tKsCÇ‘�±™‡… ›À;Èj×d[K�„^ŸIliÔô› a@½D½Çë±ô _û _{}•Þ»­Gï¶z)Ú4؉‚�ôÕð¦À(YÐ $LÐ $LÐ $LÐ Çqú®´èÃÝhên%"2*§¦¦FßU€z5)aŠÅbSSSŒ³Á”––* }×êÖÔ>LŽãø?7ò¹™×›³ub{¿&\̹‘],pé&Æ’~¡í¥â䌼äŒ|!‹¶µ2îÔŽˆÎ]¾wåæ}áÞ+¨�‰±Ÿ~¦æüa!‹æ,íDý' Ü^x,MM˜DÄçfRVšÀõæUdÝ-¸tsi ÏQqi•ð¥3…%úi¸BYCD|é}�é|e™À�€Ç¥ƒ„ õIKK{ä´2ccc'''}×@�0@jNüÁg_ºTCQ¯q\«Ö׳‹N_¼#|«å^¶í­„/ž-«V­º¿¡ÉMFFFï¿ÿ¾¾«  H˜ |^¶ð£åÉИª+‰¨¬¼Z/ãÕ;´³¾Pxæôë×oÛ¶mJ¥Rã<Ïó¶¶¶o¼ñ†µµµ¾«  Økà)’ËåJ¥’çyõ“<Ï‹Åbggç™3g"^BK‚>L€§èþýû­Zµ*,,T…Lžç9Ž |ùå—õ];CÂн‚‚‚'N?~¼°°�õR²`IDÇ�;¶OŸ>ú®#€î!aèLeeebbâ©S§ÒÒÒÌÌÌ‚‚‚ÂÃÃ[·nýÆoTVVrg``0~üøÐÐP}×à©@Âh*žçÓÓÓO�:uöìY¥RÙ¹sç×_ÝÛÛ[,³ ºté'•J?øàlL-&À“ËËË;yòä©S§ œ��G�$“É4.ëÛ·ï­[·f̘�u} eCÂxl•••§OŸ>yòäÕ«W-,,BBBºwïÞºuëú®wrrúàƒ´“'@ ƒ„ ÐXuކõõõeKø4 ñžH˜�ÖÈѰÏ9$L€zUTTœ9s¦ñ£ažsÕ¶< €ÌÌL™L†)þÍÏó—/_>yòäùóç‰H.—GFFvìØŸœ†>LA�?~×®]mÛ¶íß¿¿ŸŸ†Ö47£aG�bdd¤ïz<�0„ÆqÜ�;w~ÿý÷Õ«Wûùù…††Êår}W ày‡Ñ°:�„  555ìàÒ¥K.\044ìÒ¥KŸ>}°ÿ2€À0@·�0á™wîܹӧOQNNN~~þÍ›7õ]£†äçç«ÿ¨T*yž¯¨¨ˆ��?uꔥ¥eXXX·nÝ0QàiËËË;zôh\\\QQ‘››Û¸qãºvíŠÑ°M„„  DZ^Íššžçõ]€–L{4lÏž=mllô]/€ žyþþþþþþD´råJ—Ñ£Gë»F ‰‰‰Ù½{·êG¶˜³T*õ÷÷ïׯFÉ<% $L}òññéÙ³'Vúi©òóó333;vìhhh¨ïº<¿îÞ½{âÄ Œ†&€Ðxž·³³4h�\.Çn%-Û•+WV­ZEDîîîÞÞÞ�:urvvÖw¥žqqq'Ož¼y󦕕U�=BCC1ö‘¦N�ú믿–””¼ñÆ[·nmÓ¦Íï¿ÿîëë;uêÔ˜˜ggç+V„††ª¿åÞ½{_}õUttôõë× ýüü¦L™2qâDíþጌŒU«Víß¿ÿêÕ«ååå...cÆŒ™3gޱ±±úe!!!9997oÞ,**úïÿ»iÓ¦ììl;;»‘#G~úé§&&&ú~HÐ$LAÉåòÐÐP,äóœ ñ÷÷¿|ùrJJʉ'¢££ÍÌÌ:uêÄÒ¦™™™¾+Ði�†>|8FÃ>žç'L˜püøqww÷ÄÄÄ)S¦téÒåèÑ£íÚµKNNŽŠŠºvíš��»øøñã#FŒÈËËóööŽŠŠ*++;zôè¤I“öìÙ³aÃÕcçyþ¥—^úí·ßˆÈÏϯOŸ><8zôè'Ÿ|rôèÑC‡‰Åbõ:äç秤¤ <øÞ½{r¹ÜÄÄäòåËß|óMFF†ú„#h†�0…™–ÏCCC¹\ÎBççç_ºt)99yݺu•••ŽŽŽ>>>�:uêСƒÆVà `4¬�9s&%%ÅÖÖ¶gϞǎ«®®¾|ù²L&óññIMMMLL &¢[·n 2¤´´ô÷ߟ0a{oIIÉ‹/¾¸iÓ¦ðððéÓ§³“ÇYXXL™2eΜ9®®®ìä�7 ð×_­[·îßÿþ·z}úìÙ³'))©sçÎú~BP/$L�X[[‡‡‡‡‡‡+•Êk×®%''§¤¤ìß¿_*•vìØ±S§N>>>èßx\ û4Ìž=ÛÖÖ–ˆärù±cÇ.\È†Þøûû§¦¦fgg³Ë.\XXX¸hÑ"U¼$"33³U«V988üðê„ID_ýµF)íÛ·Ÿ7oÞ„ bbb4&Mž™˜˜Qvv6K›+V¬àyÞÕÕ•ÍØl×®>0¨h�† Ä·rÂËÏÏ/,,$¢Ö­[×wMQQ‘z¼xñâÏ?ÿ¼oß¾+W®p×¶mÛV­ZQeeecJdý¨ …BßM‡† a4ýúõ«®®¾råJrrrBBB ^¨æÈ>7SàB9wQ÷D”p1çFv±À¥[šö t’ˆ�ÜNOÌ»Õô6^sÛaíýˆ¨4>³êz¡À [Y t¸PhyÊÊÊ0¶¹111yíµ×ê{•M¡dæÎ�»dÉ’víÚM›6­OŸ>>>>2™,55•õLB‹�„ ÐìtêÔ©S§NDTTT”œœœœœ¼iÓ¦´¤õ�øÜLÊJºPÃÚ-‚ Š*²î–\ze•¢†ç‰è^yIzÑ=!‹6–HÙ�2¿¼*³Hà†K*ÐáOŽçù‹/žzô(M˜0A#^–––ê» cH˜ÏŽãœ�����\YYɶٌ��U_¨cÇŽl €g—öhØ=z¨¶„æcÁ‚ÑÑÑü±��ÁÛo¿­šrYZZºbÅŠI“&©¾e¿Á˜˜˜—_~Y$Ïóëׯç�wôÝÐ1$L€g�Æ6›.\HIIY¹r¥R©ìСƒ······ƒƒ†�À3D©T&''c4ì³ÅÙÙ9&&fÔ¨Q~øáÿþ÷¿€€€¶mÛÞ½{÷øñãÅÅÅ­Zµz饗ؕ/½ôÒ?ü°sçNww÷ÐÐÐêêê'N”––~úé§sçÎÕw;@—$øG ðL³¶¶ŽˆˆˆˆˆP())) Û·oo©ë@Ës÷îÝ¿þú+!!¡¤¤£aŸ9=zôHIIùî»ïvîÜyüøñêêj{{û‘#G¾ûî»>>>ªË¬¬¬Ž=ºpáÂlÙ²ÅÉÉiìØ±³fͲ³³ûæ›oôÝÐ%ôa´‰ÄËËËËËkäÈ‘%%%)))ÉÉÉ[¶l)))iIë@‹¡>ÖÖÖ¶wïÞݺuÃhX½[¹råÊ•+U?.[¶lÙ²eªW¯^½zõj�·X[[ÏŸ?þüù ßÙÁÁaÅŠÚç/_¾¬q&..®Î;¼ÿþûï¿ÿ¾¾Ÿ<&@ dffÌó|VVVrr2›´)‘H¿>P~~þ©S§"##õÝhQê ëéé©ïz€n a´dÇ999999 8°ªª*---%%åСCëׯ·¶¶f}žžžžÚïMKK‹‰‰III™2e –«€¦ËÎÎ>qâFôlH˜Ï ©TêëëëëëKD©©©)))›7o.))iÛ¶­§§'K›ª•ÓÒÒ8Ž»~ýú‚ Æ�ªïÀ3©¬¬ìÔ©S'OžÌÊÊÂhX€O “³uâ¯7gëĬ,Œ[ ½‚…‰±DÄqDdn*¸t[«ÚO~–fúi¸D,""δï(èhÎÒNàÆ´lVVVªm6³³³/_¾|ùòå'NTWW·k×®cÇŽžžž‰‰‰555ÇUVV®Y³æÜ¹s/½ô’L&ÓwÝàÙ >V*•¼øâ‹nnnú®<]:H˜¢ÞãõØ€ _û _{}•îÝÁÆ»ƒ�^ŠöïhçßQo¡‹óè*ö誯Ò@‡8ŽstttttìׯŸB¡¸víZJJJjjêþýûyžgë�sÇóü¥K—,X0eÊL—€†i�†íÚµ«���¾ëBhRÂT*•%%%únL|â"â,lø‡c×…#5"‰”ˆŒ %¶­Œ…oµ±QKžÊ~áÂ…B¡¾g2ëÉäy~×®]gΜyóÍ7±ü0û÷ïOJJ244 ?~|‡ô])Ð�–üñ#ê5N�¥»9Yº9Yêû´4×®]c‘’ã8‰DÂq\uu5±ÌyçÎ�O>ùdúôé1 D”““£P(¦N�*—Ë1à9‡„ šÎŸ?ïââRSSÃöÏd'ÙèY"rrrÂz? .,,,,,Lßµ€f 4Éår¹\®ïZÀ³G¤ï @ �„ º�„ º�„ ëС×  &軎ÍËÔ©S~bŽŽŽú®£p�0àoªUÄ ë!•Jõ]G[´hÇq»wï~²·«ž˜��A�OÌÈÈHßM&ümÕªUíÛ·'¢E‹UÔå×_Õwuì·ß~kݺõ€žìíï¾ûîСC‰hàÀ�åååÚO,##CßMlªêêêFv_c·�©9²�Ï͸PÎÁ]Ô}%\̹‘],pé–æ†½�¤â#·Óón YtsÛaíýˆ¨4>³êz¡À [Y t¸PaØØØDGG‡††Îž=[.—÷íÛWß5zºNœ8qåÊ•÷Þ{O"yÂpÄqÜÚµkƒƒƒcbb.\¸`Á}·I÷îß¿ßÈ+‘0t†Ïͤ¬4¡ 5”±ƒ‚¢Š¬»%—^Y¥¨áy"ºW^’^tOÈ¢�%µc5•ùåU™E7\R¡¸D!ùùùýúë¯ãÆ�7nÜ™3gX—fKµzõj"š4iRSnbffôñÇûûû6LßÍÒ±Æ'LÏóú®-4/QQQ³gÏÎÏÏ1bDYY™¾«ó´”——oÞ¼¹K—.¾¾¾M¼•‡‡Ç† 8Žû÷¿ÿ�ššªï–éØc$Ì&–ÄHßÿ²ž#‹/ùdõêÕûöíëÔ©ÓW5//oÖ¬YŸ}ö™«««��Ojjê‰' tôèQ–'‰¨_¿~¦¦¦D”žžÓ±cÇÁƒ«ßÄËËKã¶MJ˜ljÅâýY)wÊ„žSÞFf>ÐÉ›ˆ.¦çfß+¸tc#I¨¼­�DÌg$òç-ÛÊ^4˜ˆ._Ë¿y[èÇnl$ òicldpãvQ굂¦ß°ñÌM¤¡þø:@H;vì˜5kVppp�=´¯9sæÌöíÛÛ·oŸ””dffÆNVUUýöÛoÆÆÆO/UwNII±µµíÙ³ç±cǪ««/_¾,“ÉXJLL nJë²³³:4lØ0>´_ýõÒ¥K;wî\ºtéìÙ³ë¼fñâÅK—.µ³³[½zõ AƒØÉòòòÏ>ûL"‘¨¯9ôþûïÇÄÄtíÚuÇŽmÛ¶%¢êêê�>úèóÏ?�ŒŒLNN666~²z.]ºT,ÇÄÄDFFQEEÅøñ㣣£?ÿüóíÛ·³kF�5jÔ("ÚºukLLL—.]–-[Öðmu°ÒOòý�gu‘‡…K˜Ù÷JS®æ \º¹‰4¸³½Ÿ›É§è¦ƒ¾±=)h0ÝÉ{ —†Ë½ìŒ‰ +.ݶ•q¨¿ƒÀí€äää²²2'''íÞ*æÞ½{Däàà Š—D$•J9¶ö‘fÏžmkkKDr¹üرc .dEøûû§¦¦fgg7ñþ¿ýö[MMM×øÑ–““sûöm‰DRg '¢»wï.Z´ˆˆÖ­[§¾T¯±±ñüùóÕ¯ÌÊÊúñÇ%ÉÚµkY¼$"ƒ¥K—þù矧OŸþõ×__ýõ'«§��ÁÑ£GÝÝk—D622Z¼xqttô©S§šÒ|ì‡ uKII™…Bééé©~žã86hyÇŽO\Õàà`U¼d<<þøã˜˜__ßåË—7ò-ŽŽŽ¯¼òÊš5k®^½š˜˜Ø½{÷#GŽ|ôÑGª Ø6¥¥B¯Ó©­¢¢bÓ¦M~~~r¹\W÷<|øðìÙ³MMM·oßÎÖ_­�››�>}ú‘÷lß¾=%''k¿ÄNº¸¸¨Î<ÕÇkbbžÛ#¯D€صk×Â… -,,¶mÛ&“ɾ˜õÈið÷÷ÿâ‹/ˆèüùóª“l"ejjªÆÅlcF!íØ±£°°PWÛ`ÑÍ›7£¢¢”Jå/¿üÒ±cdž/2dˆD"Ù±cDZcÇ´_Už‹Å;vìÈÈÈиŒ=Þ¡C‡ªÎÔùxoܸñçŸ6½�DtõêÕG^‰„ P·ü|¡—mh®\¹2ažçW¯^­±L�Þ}÷Ý¡C‡ž9sFãüüADª3ÁÁÁ"‘(>>ž½DD<˜1cÆòåËu;ò‘V¯^-‘H^|ñE�Ü­¢¢bäÈ‘yyyo¿ýöرcy½““Ó[o½ÅóüرcU�‚ˆ”Jå×_ ê‡trrzíµ×ª««ÇŒsãÆ v²ªªê�7Þ8uꔳ³³ú‚½lËO?ý´¸¸vSÃãÇ�‡……éd?yOOOKKËóçÏ«W˜ˆ´vÒÁn%-Ò‡~hgg׫W¯nݺ=² Å3fLQQ�1¢¾k–,Y¢ÚéÑÒÒòàÁƒ111®®®���¶¶¶ IIIíÚµ[¼x±ê]ööö'N\³fͨQ£¼¼¼lll’’’Œ��ccc‡ "Xsrrbcc_xá;;;�ÜpöìÙ‰‰‰DôÕW_}õÕWu^3`À€}ûö©~\ºtéýû÷ýõב#Gzyyy{{WUUÅÇÇß»wO.—ß¿_5Îö‹/¾¸qãÆîÝ»½½½ÃÂÂLMMããã³²²wíÚ¥¾æ[o½µzõêcÇŽ9;;{{{ß¿?%%åµ×^ó÷÷õÕW›ØFCCÃE‹͘1cäÈ‘={ötrr*((¸xñâ¨Q£4šŒ„ z"3'3k¡ 5®Ý«ÍÈPbf"¸pc¶,ƒL"µ24²hSƒÚÏ9c‰ØüÉw?2"Áµݽ{wëÖ­›7oöõõ `]-›ö€Ì†ÍŸ?ÿõ×__µjÕ¾}ûþüóÏüü|###ww÷ ¼õÖ[–––ê¯X±ÂÞÞ~ãÆ�%%%&L˜7ož���µµµNºÚã÷ßW*•:\ãçqŸ‰Åâ_~ùåßÿþ÷òåË�?#“Éärù§Ÿ~:iÒ$6£’144Œ‰‰Ù°aÃÊ•+ãââ*++ÝÜÜ^zé¥wÞy§U«Vê÷ôòò:|øðܹs.\¸Ð¹sçmÛ¶�9R£×ñ‰½þúë666_}õUbbbBBBÛ¶m»ví®qWSS£½ Ñ·ß~knn®>¨·¾çbfföåÅCéE÷tõëi$ »÷:÷%¢}ǯ§\z‹¹‰tÂÐNFRIÍ©ü©�‚–íè);“ˆŽ$Ü:wYèÇnn"3ÐÓÂÔ01å3…,Ú¶•ñÄ¡Þ%%% …¢¾kV®\éââ2zôh� <¡êJªèÃÄßDb20$"…¢FYÃ7ùv�‡ã8©�ˆˆªk”Õ¶],Š$DÄ+jHY#pÉã8©XèBuáÕW_åyž}Râ8Žçy©TÚ¥K—ÐÐP�½éT”›?£¬4¡+êæ/6ƒôô¹È¶•ñ˜�žFRɦ«gß´í~ÖŽÿש'íI/Oús‘ÄÖÄf²¿À…‚Nx{{ß½{÷öíÛl_MÐ-ôa€~ÔßÎç ú]qî¢î#ˆ(ñòÝÙÅ—ninØ+ÐIj >~çjbÞ-!‹î`n;¬½=8›]u½Pà†‹-�,ÖNå:|øðƒ®€Nð<Ïó|eeeBBB\\œ¥¥e@@@Ÿ>}T;ÂÀ3áôéÓ)))o¼ñâåS‚„ úÁçf ßÕÃÖN¥+(ªÈº["pé•UŠž'¢{å%ÿ1–Ô~�Ræ—We ÜpIÅßÃO®_¿^XX(pt…uf*•JŽã �?Îóü�!C0Eàò4¶ÁuH˜ œ)S¦è» �A}m 6\V$ÕÔÔèp=FUUÕÆ�}||ô]—  !<ÏK$¥Réêê&—ËÑi ðŒ’J¥Ø…èiC¨Çq–––ýû÷—Ëå˜r ðHí…d€ˆ"""BCC�œœô]€gú0ê¥ï*³êz¡À [Y t¸PBÂhîøÜLÊJºPÃÚÍÄ Š*²î–\ze•¢†ç‰è^yIzÑ=!‹6–HÙ�2¿¼*³Hà†K*— [% º�„ º�„ º�„ º�„ º�„ º�ÝJ n›6m211aÇìÀÚÚÚÚÚZßU€f êV^^~øða±XÌó|LLŒönnnÎÎÎC† ‘Édú®,4 % ucý–J¥²¦¦Fu’çyžçÙ±³³sTTâ%¨ a@Ýäry�çÅb±¡¡áôéÓ£¢¢ô]Gh^0J4%'''''K¥ÒÊÊJŽãØyžç%Iûöíg̘�®K€:q¶N¼ð…Ú8°+ #ÇÖf—nin(â8"²36ó°°²h™;[K�,n¸ØÒHàž žçUà¹UUU•žžž’’rùòåÛ·oxxxx{{'%%)•JžçÅbqMMÍ Aƒ† ¢ïÊ4_¢ÞãõXz�¯}�¯½¾JïÝÖ£w[½mìDÁNúj8¨C&Àó‹çùÌÌÌ””””””ŒŒ ¥Rikkëãã3zôhwww©Tš™™yîÜ9"’H$¦¦¦o¼ñ†“>Ã@½�0uèСÌÌL¹\^ß 7”––&''_¾|ùâÅ‹¥¥¥‰ÄÓÓsôèÑ>>>vvÿáæäädnn^\\ìííýÒK/ad,4 @Peeeñññ§N�²´´ èÖ­z„@<Ïß¼yóÒ¥K—.]ºqãÏó¶¶¶���¾¾¾¬»²¾7víÚÕÉÉ)44Tß-€g&€ÐØN………G�=tè�½½}XX˜\.Ç.öð4”••±¾Êäääââb6»rìØ±ÚÝ•õÁ‚±Ðx:H˜Ãœ;—VW \oSCvàßÑ®C;K�K—HDqžAœ­°PƵ Äù¸Û8µz±8‰D$3’‘«“…¹©´É÷{ †âú^*,,ÌÏÏ'¢²²²‚‚‚´´4�ËcÉËËSWWWó<Ÿ““³}ûöÍ›7ûúúøùùa "4]ffæ¥K—.^¼xíÚ5žçÛ´ièããÓ¡C‡º+šH ³ƒ°ËRkhmmÒÚÚD_¥sVöd¥ŸÛl[Él[é-‡XšYš5—ºÏ�;·ÿ~v|ûöí³gÏê»F� ¾z3;V(DtñâÅ‹/ZYYMž<ÙÓÓSßÕ„gOyyyZZÚÅ‹/]ºTXX(•J½¼¼Æ�çëë‹r†fõ½R¾B)p½9#±��)ñ9ô HàÒIb@­Ûs"1_”GÅyM¿ßc0”qv툈¿—Jïë¡á¶í8‰�²¤Ry¿BÈ’9©È ž>[ÿvíÚÑîÝ»íííÃÃÃ…~,�ãäÉ“ñññªÙvA‰D¡PøøøÈårôaÂcÉÎÎNNN¾xñâÕ«W•J¥ƒƒCPP����›››D‚© (|ø(9|½*SèŒ'u²°çKD|Â>å¤À¥“¹µhÂ2’ñ)'øS;-ÚÑS>>æææú®À? aªOŸ>ú®<îÝ»ÇfW¦§§+ ggç��ŸöíÛ‹D"}× nH˜ÍÎòåËMMM;uê4qâDoooSSS}W àÑ�0š�Þ½{÷ë×ÏÕÕUßx$6üÇe)?RÆz"¢ê:4ž&å÷”±»{’NÏÓT”NÄQñU2w#âéÏ—)}5QêJòÿ°ŽwÝØA‡'�¢ô'/|Nÿ¾§™0Ëïý}\ULÊJ’?Ý•ߣۇ4OJ1 £"·¡Ÿ’¼³ôG0ñJ"¢Œu4äOªQÐΞtÿÑùÿÒˆ²p¢&OKÞÀC| EEéddKCŽ�¹Ñù¥”¾šˆ£®Ÿ�ßÌ:ÞUz“�ÓŒ—DdÓ…Œl4Oz¾LÆ­k�}ßzêñ²)ì{‘]·Úc»nd.D¡×·×ÆK"Êù‹nÿI…—kã%UÒ¥¯õý\"á¸&¤€–áF4å&q4p'Ù…)йO‰ˆ:¿G]æÖý®ôßHYQ{ÜíKò|‰ÄÆTv›ªŠë¸ØÂ�ÆePþy2¶# A¦ ´NÓÎý?1ƒ’¿oìņ4ì8å�%âȦËSÍËhdò‚$rE$"ª©=“Ÿ$D5žú0à¡[»‰ˆlkã%å¥ê""ï×ë}WÑ•Úã6äûI-IlHf.díW÷õ¦Ô¦‡@ñ²‰8Ù’mW�â%u|•Ú'‰Ií¢JUEdâ@¡ÿ##â$µgš1ÌÀ‡J3‰ˆòiu+ê4�‚>­=CD[¼É.„úlø{¥Ÿí])ïì?Þ^~‡V<#)µ É…µÇM¥´_ê(Î-ŠúlüÇ™Ì}´w‘Ëhê·…®n¢KßPÁEâ•dHÁKÉ.ø×WSújºµ›ò“¨2Ÿ$2jåCî¨ã+ĉŸä \YKG&ÖqÞ¸5M¼£yòÚV:8¦Þ[qbzEñ�3ù”ü-ÝØQ;ÍÕÜ•Ú� ߷ɰÕß×HdÔÿª*¦Õ­ˆx2mGDäó&ù¼I{RÖþÚ3Í&<ÄÖ¿áTUHç—�ëè¿WÄQ”ÑíÔ´Œ‚?¢&ùçé`]Ûü÷™œ¿hW_—N²‡‹�_øŒÎ~ò�ùŸUEt÷Ý=A×·Ñ Ý$’êóajDÜ;ÇéÀ(ªP[è¨à"\¤´UyP³G7ù{¢â$ä8 öLáeº}˜ˆÈ9RŸ�z$LxÈFN×·þýce�æH×Êû; ‹DDwOQé-""‰)9¿PûªÄäï+­|þNJDtç)ÊQ“â *Î "â$Ä?ì T”Ò¥o(hIí�ÖòÚxi`NfÎĉ©è )e¤¤ÿ=É–&mÿQÕûÉô «Þ‹emþq1_CÙ‰ÎùT�âÅ×hï T]LD$’RÛ^$2¤œ¿¨º˜dÒÁ(•X;,–WÒ¥oéô\""ß·HÖ†ˆè^<ÅŽ¦šj²ð �ÉOô«&<Ôq%ÿHeÙDD¦í¨u7’˜P‡kw°$ŽÜ¢þ¾8pqíÁ¡qµ SÖZsÔ+ãû6ù¾ý÷�;PñÕGWÆÌ•z­"û0*¸D;º×N½sòï ûSÄ:jÕ‰¬åµghߺ}„ˆ(cÝ“$̶Ô6âïëßË´éAƒ÷ýýcÜû”[{ìõ ù¾õ÷K§?ª�—ÆmhرÚ=-dÑV?ª, üótûOjÛ›ˆ(ý7:õQûáµYº²€v†SM%™8Ò€�š»¿43H˜ð�‘-�:K×¶’²‚Ü'ÖöCöþ�œ‡Rq9ôÕœùT…ÿBö=‰ˆ¬|É¡݈&"z�ù�k:¼H÷â(~6å&PѪ¼ÿwïèý"¾¶cPi«(é‹Úcû^ÔCmÅZe%ÝØQ{ܦ;å�§¼óµ?Zv¤»'ˆˆîœ¨M˜^¤‹_“×Ëä=£vy!C+êø U—PȲ:vif�0š;ÎÖ‰oú]·Pv`eaäØÚLàÒ-Í EGDvÆfvM¾ßcp�ÕN5[K�±³N‰-�. Æ­5—�åDÿ躌H-­Zר¯}�M‡'PΟu¿�W’¢ìƒuŸž;ÇèØkµÇænÔo+‰ Ôê™IÊòÚãëÛèú¶:îP™_{ 6¤Ñç5_íþ­­Ð$L€æNÔ{¼Kòµòµoú}žLï¶½ÛêgsÓ`' vÒWàQö¥¼D""i+ò}‹ú�™ ]^N‰ŸÔ^À òE}Éu:0’jªˆˆ¤4pYÿã‚ÆTÃÐúÑ×< �0à”¾6^Q¿ÍäзöX"kè]ªÕeÄ×è`—˪bÚ7„*òˆˆ81õÝD–^š×˜:‘È�j*‰ˆäs(èS}?»§H¨mCt¨<÷ïc©eíÁÝ“”¶ª¡w™>£TSI×¶Ô³5„ž_C‡ÆÓýäÚ»}ù�¥eUÄFÔnpíñ¥o(ë€ú-(ûPmÿg‹€>LxYù'&^ID´«µîFå÷(ÿÜ#ÞÕî:õ^í†"‡ÆÑ™yÄ+©ª„&æÔ¦M}Wç©4h�µµµL&1b„¾ëM2wî\Žã®]»FD7nÜxóÍ7;tè`hhhaa1iÒ$�‹ïÝ»÷á‡vêÔÉØØØÒÒ2<<ü·ß~ÓX#}ãÆ�u."RçDÄ��ggg"***š3gŽ«««¡¡¡““Ó[o½õàÁíë÷îÝÛ·o_sss ‹^x!!!A&“ÕÙ®ÊÊÊ~ø!44ÔÜÜÜÄĤsçΟ|òIii©ú5YYYÖÖÖFFF,ªœ={ÖÀÀÀËËë‰c¡‰‰ {\DtòäÉ‹/Ž9RÈ_+h‹ŠŠš={v~~þˆ#žçÀß²Ÿ&<Ÿ�0 %ðóó»{÷naaaÿþýõ]Ð�¢¢¢˜˜??¿o¿ý¶¢¢Â××W,ÛÚÚª_süøqooï%K–ˆD¢¨¨¨þýû§¥¥Mš4iüøñê!ÓÍÍmÊ?I$’ŠÎÏÏOIIñóóûúë¯Û´iãáá‘““óÍ7ߌ;VãÊÙ³g<øÐ¡CžžžéééÝ»wß¿¿ö=srrºuëöúë¯ß¸qcàÀ�cÆŒQ(óæÍ V_ÒÖÑÑqùòå•••'N¬®®f'+++Y´þý÷ß닯�Ô£G":pàÀ™3g† &—ËçÌ™£¿_/ÔZ¼xñàÁƒÏŸ??uêÔ†¯T*•Ë—/ïÙ³§•••T*mÓ¦ÍСC×®][UU¥~ÙO?ýÄqÜŒ34Þž‘‘Áqœ���êÌÔ©SE"у^~ùesss�øøø²²²_|ÑÌÌÌÇÇçäÉ“Íð9Ïó;vì9r¤ƒƒƒ¡¡¡­­í�!C8 }åÍ›7U_QÙÚÚ6,66VãšÔÔT�'£âèèÈqœú¿ÐÙ³gswýúõšššüÑßß_&“Y[[�5êêÕ«êïõòòbßd±h¯¼òŠÆ7\wîÜìñOÒô[€=½Á~ϱX,‹õ] Ð�Ì›7¯oß¾K–,éܹ3ÕÔÔ¨÷øÝºukÈ�!¥¥¥¿ÿþ»jKƒ’’’_|qÓ¦MáááÓ§Og'Õo¾qãÆVR}ðàAÏž= ôõ×_[YYѱcÇúôé³gÏž¤¤$V"Ú´iÓÒ¥KÍÍÍ·oßÞ§OvrëÖ­¯¾úªÆ •Jå°aÃÎ�;÷öÛo/Y²ÄÈȈ5gÑ¢EóçÏýõ×7mÚ¤ºxôèÑ“'O^½zõÇüÉ'ŸѼyó’““.\¨ÑŠÇ2`À€víÚ}ýõ׋/îÒ¥ËîÝ» õù ""‰Dëׯ Ú°aC@@À{ï½Wß•cǎݾ}{›6m† bii™““sôèј˜˜%K–$&&>Ùo“çù &?~ÜÝÝ=11qÊ”)]ºt9zôh»ví’““£¢¢®]»f``ЬžCqqñèÑ£cccE"‘¿¿÷îÝóóó8°k×®™3g.]ºTuå�F�]RRâáá1tèлwïîÞ½{çÎ�ï¾ûn—¹º~ýú+¯¼røða???__ßK—.mß¾=...%%E5åå—_fC:tþüùþýûûúúªß„ +h©t�0 Ì9C¡?ÕIlþËlíL•å—ÎÉ̈}�meOnþ‚mãP{dã(pѵ 7�‘ØÒȰƒ•�E‹-Œnì³ÂÈÈèĉŸþùÑ£G+++}||^ýõÿûßM¹çÍ›7¿øâ‹={ödff𛛇††Î˜1£_¿~u^\VV¶jÕªmÛ¶]¸p¡´´´uëÖýúõ›9s¦§§ç# Z¹rå´iÓ<˜––þ矲W ÙÖ…ìÌ� ðÞ{ï-[¶ìÎ�;öööÚ÷Œ��íÛ·¯öy¥R¹råÊuëÖ]ºt©´´ÔÊÊ*((hìØ±cÇŽÕ^¿²²ò—_~Y»ví¥K—”J¥››Û˜1cÞyçSSÓ§ý fΜ9“'Oþå—_Tß¡ˆD"sssÕ .,,,\´h‘úŽyfff«V­rppøá‡T ó Lž}º_¿~_~ù¥zsþóŸÿìܹsëÖ­¹¹¹êݳß|óͱcÇþûßÿŽ1B$}ùå—ÁÁÁ}ôQSžgQQ‘••Õ­[·ÂÂÂ<¨þ7¿xñâÌÌÌo¿ýV˜,,,,vìØ0kÖ¬àà`ÖÛ¬áÌ™3Û·ooß¾}RR’™™;YUUõÛo¿7åË‚3gΤ¤¤ØÚÚöìÙóرcÕÕÕ—/_–Éd>>>©©©‰‰‰ª¹»Íá9‹—���¿ýö›——;™“<Àô¿2¬IDAT“óî»ï0@uÙÍ›7G�]ZZúÓO?©¾ô¹xñâ /¼ðå—_zxxhÔxcÇŽmß¾}rr2Ûm%++«wïÞkÖ¬yóÍ7Ù53gÎd3fÌ8þü˜1cÓ= Ðb4)aò<_SScÒÝI/U¯©©!"ò‹àü"„/�'âkjÈ£+çÑU? ÷îÎyw×WÃ¥®­¤®­„o¸Æô* ¢Ï?ÿü—_~iß¾}Ïž=oß¾������pñâÅÏ?ÿüÉnøXßû^ºtiøðáW¯^522 ±µµ½víÚ¯¿þºvíÚÍ›76¬�‚V­Z5mÚ4¹\~àÀ ¹\.•JÏ�;Çó<û¾iÓ¦ÿû¿ÿsttÌÌÌdoILL$"Ö“#“ɦL™¢QsÕ•Úß�““ó /œ;wÎÞÞ~àÀ�2™,!!aÞ¼y7nü믿lllô÷Û~Žx{{³ñ~u¾ÊóüöíÛÅbñ+¯¼¢ñ’���ŸŸßéÓ§ËËË���Ÿ¬tí}ÞÙGÉÜÜ\öãÕ«WSSS­­­_{íµGÞmëÖ­Dôꫯj4‡ã¸ˆˆˆ³gÏž={vàÀ�ªófffk×® ›0a‚‰‰‰T*ýý÷ß›Ò?ûöí^½zݸqÃÑÑ1...111$$„½”——·`Á‚ˆˆÄK=JNN.++srrRE& ¬7ÌÁÁA/‰H*•6=´Ìž=›}»!—Ë�;¶páBV„¿¿jjjvvv³z;vìˆ��µ¶¶Þµk—���ê¼½½ý† Ô¯üïÿ[RR2aÂõ$éëëûÃ? 2dþüùS§N}âSÞÞÞ{÷îU Ywtt|ç�w^ýõS§N©&Às®I ScÌÀÓV›®AͶmÛ6nÜÅ~ܲeË„ –-[6zôè'øîù±¾÷ÍËËëß¿NNÎäÉ“¿øâ 6ž�ˆÎž=ûÑG5PК5k¦N�êëëÛªU+"’J¥,\¹rÅÃÈŽ?.“ɲ²²²²²Ù�éaÂ477רa,22²¾„Ùø€ÇÐOÏ¿ÿýï6[ÏÏÏ/,,$¢Ö­[×wMQQÑ'LmìÏF5¶–-Däííݘ©l‚Öèѣ뻀µE]HHÈûï¿ÿßÿþ—ˆ~üñGww÷'®9Ïó#GŽÌÈÈØ²e‹»»{ppðرcÙw%Û¶mS(Ú‰“’’2yòd©TºeË–ú¾À c[1�9ò•W^ ⹬­­Ùû禪ûQc’§ÞŸûŸß9sæ¨ÇË:ÅÄÄÑû￯q>22ÒËË+555>>>44ôɪ:bÄ�ç¯ñ 4u”,>ñè×Ö­[Õé�3æÀ�+W®\½zõ$ÌÇúÞwñâÅ999={öüõ×_Õ;göíÛ×@)¿ÿþûË/¿Ü±cǃª>ßQPPÐéÓ§YÂ}bbbN�:5fÌ"bƒf]]]·]�ïxÜ�ð”4f‚±‰‰I]ˆª¯<ž¥RI�ùùûå—_f_¦hóóóÓ8£P(>ÌŽÙ_ï‹�����9rä¨Q£ˆè믿~õÕWûöíkaañù矷nÝëÊêKQQш#JKKøá‡þÛÌÌìØ±co¿ývttôü!‘HüýýÃÃßì›Äf¨‘Ï!==�ˆT=ðõ©®®ÎÎήoýžÎ�;§¦¦^»v퉦6�o +ý<ÛÔ3f̘•+W&$$<ÁÝë{_öu²zkŒuëÖMž<ÙÖÖöСCQ-((èûï¿?{öì¸qã²³³oÞ¼9sæÌ]»v�ø >>^ãJÕ€FííÙÔVí� <Žã†^SSóí·ßê¥ÞÞÞ–––wïÞÕÞü ??_ã ë!üúë¯9“œ-ó:{öìuëÖ‰Åâ_|Qû¶�ÄÒcJJŠêÌ’%K>üðÃË—/ñž¾¾/½ô’^ž!|üñÇ111¾¾¾KC5ÀÑÑñ•W^Y³fÍÕ«W»wï~äÈõU ØN<ÏÖ ¦Æ?777":}út×I¥Ò¶mÛò<ùòeíWÙf³...Â<1ö°¢¢â©>C€æ  ¥aŸbŸ`Úc}ï«!ßøïƒ“’’&NœèâârèÐ!ccãÉ“'ß½{WýOOO ¶ØÏ‰'ŒŒŒ|}}ƒ‚‚Î�;WQQ¡¾ÌÏ`=·nÝZ±bû¼¾lÙ²���ˆˆõíΘ—_~ù½zhh½X°`�¥¥åǼdÉ’òò¿W/--ýòË/Ÿ8�5’X,f#Éß|óÍ[·n±“eeeo½õëW7a„®]»>|ø_ÿú×íÛ·Uçkjj6oÞ¬±ë`QQÑĉ W­Z%‰ärù’%K²³³'OžüdU}á…Œ��×­[§¾a`§N�ØD»Ë—/Ï�;÷Ù $-î]».\haa±mÛ¶GNª¬ó» ¶úÚùóçU'YgfjjªÆÅ«V­ÒUÍwìØ1}úô�7 ÿFŒADK–,©ó¸úSŠŒŒ$"íÕéöïßéÒ%;;;U¯>{b·oß.))Q¿rïÞ½ÿ…z2ôð»K€ç&@KsñâE"êСC�¯6ë{_333[[Ûêêê .4²b÷ïßoß¾ý‘#G"""–-[vïÞ½I“&©&à8®k×®………·nÝ:qâD@@€T*íÞ½{UUÕ™3gXA]»6iõæGö°ïȇ ¶¬lEÐ;gg瘘kkë?üÐÙÙyРAS¦L‰ŒŒttt|ÿý÷wîÜù´+0oÞ¼   ÔÔT�ˆˆˆAƒµmÛvãÆ�ÚËÛŠÅâ�;wlذÁÍÍ-<<|òäÉl訨ŸþYýâéÓ§ß¼yó³Ï>c³‘‰è�wÞ>ž½DD<˜1cÆòåËu²\pRRÒˆ#~úé§ñãÇïÙ³Gàçt÷îÝ�²ÿÒ1………Ó§OW_°jΜ9¦¦¦kÖ¬ùßÿþ§:ÏæÞ/X°€u]‘¥¥¥——Wuuõ¼yóØ�jjj~þùçÑ£Gëd>›5úÛo¿edd¨NVWWWWW7ýæÍ&@K³bÅ zø ®6ö}mAAA�¯6þ{_zøuò|ÐÈŠµiÓæÈ‘#lïôéÓ###÷ï߯ñ¡™ ”=}úô… ºuëFDÝ»w'¢ãÇ�''';88°ùo�«ñ=�; ô¨G�))) ,prr:~üøºuë’““GŽ™””$ÀÈO™LöçŸ~üñÇ...'Ožd]ô—.]ªs²¥½½ý©S§~þùçnݺ]ºtiݺuG�•Ëå»víRï\úý÷ß7lØÐ·oß×_]u’ã¸5kÖ´mÛvÖ¬Yl¬øãš:uêÉ“'ûôésþüù�7>xð`Μ9ÉÉÉS¦L¹páÂgŸ}æéé©úò0f̘¢¢""1bW¶’0ciiyðàÁÀÀ@77·qãÆ½ñƯ¼òŠŸŸßÒ¥KÛµk·xñbÕ•ööö'N$¢Q£FuêÔ©gÏžÛ¶m‹��}äú«�qûömÕÿ6nß¾]àç ‰vîÜtæÌÿnݺEEEõëׯmÛ¶?ýôSMM�*¶µoß~óæÍ&&&ï¾ûnÇŽÇŽÛ«W¯=zdee½õÖ[³=gÍšED_}õU»víÂÃÃÛµk7cÆŒ~ø¡ÿþMbAAA£G�.((ðó󋌌|ñÅûöíkcc³{÷î¦ß ÙBÂx¶½ýöÛiii츺ºzúôéGŽqrrš4iR�׳}ÆV¯^­:SVV¦:nü÷¾D4wî\KKËØØØ &¨4MKK‹ˆˆÐžQãéééäô÷ö¹¿üò‹��ÝìÙ³ÕK˜l–0;wîlnn~úôéôôô'"Ûø€ÇÐOâE‹xž×^nªNÖÖÖóçÏ?{ölIIIEEÅõë×ýõ×:'«+--åy^ý�Y%..Žçy¹\®qþý÷ßçy~Á‚ê'���ÿóŸÿ\¾|¹¢¢";;ûÛo¿µµµ�ŒŒäy^{P«��ÁÔ©S>œŸŸ_]]}çÎ��;w¾ð ê×Lœ8‘çùØØX�±666ÙÙÙ•••OöTCBBbcc‹ŠŠ***RSS?ýôSÖ?cddÄ&$‹Dø< õî¬Æ˜?þ­[·>ûì³öíÛÿùçŸ?ýôÓÆ�Åbñ‚ .\¸Ð®];õ‹W¬X1{ölgg猌ŒëׯO˜0áÂ… þþþÚËÂ=�Þ½{GFF²!ÖM\åø žµnÝúĉkÖ¬éÛ·ïÕ«Wÿøã�K—. <866vÆ êý´ƒ ºtéÒŒ3ªªªvìØ‘œœÌ.Ó 0yòäU«VuîÜ9777999000..nÒ¤I:ybD´nݺO>ùÄÙÙùàÁƒ»wï¾}ûöèÑ£���urs€æ‰«ó{úo¿ýÖÜܼñ«Þ4+W®tqqi`Ó¹&$$$)))00ðøñãþþþöööçÎ�ËÎζ´´Ü¿}ÛQ¦¦¦úúú* ??¿:°•{ÔÝÙ»woTTTII‰———¯¯ï½{÷Nœ8¡P(Þzë-íÿ0Ÿ-‰>ûì³wÞy‡]×­[·ððð?ÿüSýí»ví2dˆ——×Ù³gÙ œœœœ¶mÛ———ß¾}ÛÞÞžˆ pìØ±òòòÅ‹øá‡õ=�ÈÈÈÝ»wÇÆÆöíÛW㥅 .]º´¼¼ÜÕÕ500ÐÖÖ¶¢¢"!!!))©]»vÇŽSÿˆ–““3dÈ�³gϹ¸¸”••ÅÅÅeff²"úþÍèÓ�7\\\ð¿‡P|g ð srr:|øð÷ßÏú=”JåË/¿|îܹúâ%yyyíÙ³'444--m×®] …bÈ�!ls?¦ñßûQhhèåË—?ýôSŸ„„„èèèÜÜÜiÓ¦%''«âe"##§OŸžššúÖ[o±3öööŽŽŽåååÎÎÎ,^Q÷îÝÙR.O< ó±z9 àùtðàA" ÓwE ™B&´Ï[&€Ànܸѽ{w‘H”ššŠ�Táÿۻ󸚳ÿà¯OÝö(mB*%‰RÖ2˜,É [’åW†,_¾cÌ`¾Æšføò32ƒÆšdkc0Èž¦mbd*E”–Û½¿?îuïíÊVWW¼žœÏùœ{îûóiqß�ó9‡¨BÃ$"""¢ûùçŸß{ï½âââ½{÷2½$¢ga†IDDDD/îååuêÔ©V­Zi;"zs‰ªÞ½õŽ;V»vmmGADo:ŽaÑ‹1½$¢—Á “ˆˆˆˆˆˆ4ƒ&i3L""""""Ò f˜DDDDDD¤Ì0‰ˆˆˆˆˆH3˜aU‡Ã‡ ‚¡í@^¬´´T„��mBDD53L""¢ê�——§íÞÂP‰ˆèMà “ˆˆ¨:Ô ´­…JDDof˜DDUÂù„ô’jPÚVƒB%"¢7 3L"¢*ágq�+,,\³fM×®]--- ìííG�uóæÍ§[ž>}zðàÁ¶¶¶cÆŒyºYll¬ ?þø#€Ã‡ûùùÕ©SÇÔÔ´cÇŽGŽ©bñññãÆ�suu511©U«–��φ ÔÚ4mÚTAfÍš`̘1BywïÞU{É/¿ü`eee``ШQ£ñãǧ§§«µ™9s¦ iii‰äÛo¿mÙ²¥±±±¥¥åÀ�SRRž599yöìÙmÚ´©S§Ž¡¡¡››Ûܹs?~¬Úfܸq²�Ú·o 22R-ÔmÛ¶©u{åÊ•°°0{{{}}ýºuëž88ØÑÑñÚµknnnn߾ݥK—äää�7Nš4IÖF33³Q£FÍš5ËÉÉIV™žžÞ£G�ãÇ�GFF>\V9pàÀ�عsgLLL«V­¾úê«g…=qâÄzõêEGG·nÝZV™ššêçç7yòd___wwwí~;‘Vp–,Q –——÷ùçŸ{zzšššÊfÓ�1"..N­™­­­ 999jõ!!!‚ ìܹSvxûömAæÎ�çææfjj:vìX©Tºoß¾Æ�XŒ?¾´´´z.íÂ… cÇŽuqq122233kß¾ýwß}WVV¦ÖìÑ£GK—.mÕª•‰‰IíÚµ;uê´nÝ:±X\¹;€W™QY‰ù„ôB‹-ÊÌÌ|ÿý÷øáEv uëÖ±±±ŠŒ ÀìÙ³„‡‡«ëOœ8±¸¸xÁ‚j=ëéé�þøã§¯kåÊ•ëÖ­S¤—çÎ� àé>_†T*�:uªT*ݾ}»"½àää´råʲ²²ï¾û®ÝÑ[@$•JAÐvDôÊòòòÚ¶m›’’âáá1tèPƒÔÔÔ;vlܸ1,,lýúõ•ëöôéÓ+V¬hÖ¬™¾¾þºuëììì–.]êéé™™™¹víÚ-ZŒ?þu_ÚÌ™3—-[&•J���{öì)‹Ož<9nܸM›6?~\$’O¾øë¯¿üýý“’’¬¬¬üýýKJJâããOž<ùË/¿È&ûUÎĘ̈|Õù„ô2~þùg+V¬xþLÉÉɉ‰‰VVV#FŒP;5}úôåË—ïß¿¿´´TOOOQïíííââ¢Ú²I“&:::ÙÙÙ•ài·nݺ~ýzFFÆßÿ ##£—%‹ƒƒƒëÔ©£vª[·n+V¬8wîœlŒQaÀ€ÆÆÆª5²lSíºTåææ^»v---í�?þ¨t¨ )))îîî:tP;Õ¥K�sçÎU¢[""z ~üxÙ²e"‘H‘^–•• 0 ))),,lÍš5†††²Ë2dÈ‘#GÆŒ³uëÖJÇð23*_i>!½Œ‚‚‚ÌÌL==½V­Z=¿eZZ€fÍš) T°µµµ±±ÉÊÊúçŸìììžÓ‰H$222*((¨D ±±±7n+™P‰ˆè­Tq†éäätýúõ¿þúKÛá½KKËç |ËÈÖquuU» _|ñEUºµµµ�>}:///M›6;v,€¶mÛ��ϼ>ÿüó�l‰”ÈÈH???E½‘‘ѼyóT[îܹóâÅ‹NNNk×®U UYZZnÞ¼ÙÙÙyÛ¶m³gÏ®ôc`zzzñññŠ!/ٌʨ¨(µ™‡¤Y‰DVxÉñÃg5«ôÄœW  °°000ðàÁƒíÛ·_´hQÇŽ]\\D"ÑÚµk«øW˜îÝ»«=DªÐ¹sçÊõ9gΜ%K–ØÛÛ�;¶[·nîîîÆÆÆ‰‰‰Šéµ•Ó´iÓÞ½{WxªaÆU陈ˆj®Š3ÌÞ½{?ëÿ "zC­X±"<<üñãÇÁÁÁmÚ´yz<§ÌÌÌdㄲE,--eŸ¶e‡%%%¯õ¢öîÝ[TT š^VHöðؤI“TgB¨[·nhhèÚµk£££+�a¾äŒJÒ¬ZµjY[[ggg_ºtÉÓÓó9-ܸqC"‘èè”[P '''++ËÈȨnݺ¯/ ,8xðàŒ3–.]ú’ýËÖŒ-**zVgggîîîš?xðà¢E‹|}}ccce£ýš ÕÈȈC÷DD¤†+ýÕTíÚµ;tèPóæÍW¬Xáããcnnîçç·téÒ§÷Í«A’’’øøø¼°¥lžd…C=²ÊÔÔT &›QùôRC¤Y ðÉ'Ÿ<¿™‹‹K“&M²²²6oÞ¬vjùòåR©´gÏžjzÐlâãã<½*ì£G�žõ’ àÉüÒ õë×OWW÷§Ÿ~ºwïžïª,Ô��µô²*¡zyy999]¼xQÖ9‘3L¢¬k×® _~ù¥ŸŸ_BB¬Y³œ��'Ož¬˜ïW³Èža{ùY޶äêe5ל9sÌÍÍ>¢ºöïÍ›7»víªº<©l%Ø©S§=zTQùã�?~ýõ×úúú²UR_k²•f£¢¢5óæÍ›5kÖ³:—ýédÓ¦MÉÉÉŠÊÒÒRÅÍNNN'N”í˜rþüyÕמ9s&22²r% 5&&FñkA*•FFFª­M¥ÊÕÕÕÜÜ}Ïž=YYYÑÑѶ¶¶«V­’-‰)#›õúœñŠ7‡lêÝˬB)›'yíÚµ§OÉ*»¾î;ðÂù„ôò6l¸oß>[[ÛÈÈHGGÇîÝ»<ØÇǧY³f'NœP¤7‚‚‚¾øâ‹ÜÜ\ÿ:{yy………ظqã ç¸V=€9sæ|òÉ'ÞÞÞ#GŽìÛ·o½zõ~þùgYÞU¡víÚåææzzz 6ÌÏÏÏÊÊjß¾}Š6_~ùåðáÃ/\¸Ð¦M›6mÚ 6,44ÔËËËÇÇgþüù•Û.häÈ‘NNNÑÑÑ...¡¡¡C† ±··Ÿ0aÂüùóUwdQ%Ûê@`` ¯¯oHHH¯^½6løÙgŸ)ÚôïßõêÕ< rqq ëܹsãÆ�ÿóŸÿTø³IDDïf˜D5•ê �ŒŽŽNŸ>}dÛ¬'$$(ê­¬¬$&&ª6NOO?vì˜F"‹Å+W®œ0a™3gªØUŸ>}D"ÑÞ½{Oœ8ñüK–­òºjÕ*µ53³³³7mÚ$ëªzîÀ çÒ+yï½÷nܸ±xñbww÷³gÏFEEegg�;öÚµk²�>ÿüóS§N 80%%%***//oôèÑ—.]2dH5àííß»wïôôôíÛ·ß¹sgþüù—/_Ú¶m›��Ï… víÚuóæÍ6mÚøúúªµ¹|ù²loØlß¾=77wúôé©©©Í›7×Ì7Õ8‰DJD5MIII‡¦L™’‘‘¡Z_TTôþûˆPTŽ7@§N�|øPQ#{Þrذa²ÊÌÌLÙå«öù’w@*•8p@¿~ýžL6Vùt}QQ‘¹¹9Õ€¥R©"*"""¢wÇ0‰j¤G�ÙÛÛ¯\¹ÒÑѱuëÖ#FŒ˜€DŒß§ËËö½a×ýe_xåkœž*/ë›ãÃ{ªeH#= ÙçaÝŽýä5yבº†6p �>W¤#ª&"m@DDDô.)¸�_ºáA ?µ�)Ž…!i$F ågÚ ãêJyÙÐê2ÌÇYÊrI>ÊŠ!2zíÑ^Y‰ÓSäåVsÑf>òS±§-Ä…pã{þ~î%ªœ%KDDDT]¤ˆI0´FŸ_QÛ‘´Ðf`jºlDçð£Ä0µ×öמè]Á “ˆˆˆ¨ºè›€TŒ’ûHX§ y q!îÄáòWð^¦lÿ0 ‡!ç¼²¦¤wOàî \û‚.BG_^}-~›I©ü°øníÇ­ýp€n[ kX.Œ[q}-ÊŠàæzßS>¦xv&ê¶G½÷˵¿¸…™òòãpóGüƒ¾'aîZù[‘±‰¸— ?,+BÊVV¶ü} Ck]ÆÐT|xO9j—‰ìsê‘´ü �gåeC+„ü »ä‡·Ô ø†¦bø]X·•WÞ‰C~ª¼ì1½b•ÿžžéZ¡Ý1$£‹Ñy£²òîoò‚´ ¿M‘—ëùbX:zìEÀQôüE^yã;åj±§&áÏMà‡>�qÇGBR‚z�ÑqM5}}‰ˆ³d‰ˆˆˆª�¡5žGêN”Á%"èòú"? ü`ã-o)•àÏ-ò²¹ºmQŽXZ´€E eŸ9‘Ÿ"/»O‚…;ˆŒðÞ7ò……¤îRf†2Nƒ`d+/7èc[?ÙbDüX=l§`˜»€¾9¼fàð“EYÿ9�ÚN•¼‚.zî—?KÙd8~›‚’<@%¿½{ …ËËõ|‘q@ùZ}s”ÜGYr.À¶#xNGÎy´ÿZ¹ogÃ`Óðü”;aU'þ¼U#£ºêËÆ :å†.eв倆=”éåÓé%«VÊríÆÐ«%_¨6?Ycñ›¹¨™U¥®T?C ùõ*†%$)Ï^ø¢âŠîÉ tµÜ)AýU;Î’%"""zóH%*ÏÿÀ&}öÅ)Ac�©Žpª- ¤á; }qCË×U Ç0‰ˆˆˆÞ -aÝÎCáü$¥4ÿ ºâò׸}…w W 6íà6ŽªC�fè{!y+gÁÌMG¡ù„jÚÄ}ì{ãÚÿð÷aä§B"†I}Ôm�ÆÃ”OœÑ›D�H$‚ ¹åňˆˆˆˆˆè]ÅY²DDDDDD¤Ì0‰ˆˆˆˆˆH3˜a‘f0Ã$""""""Í`†IDDDDDDšÁ “ˆˆˆˆˆˆ4ƒ&i3L""""""Ò f˜DDDDDD¤Ì0‰ˆˆˆˆˆH3˜a‘f0Ã$z H_¢†ˆˆˆˆèµi;"ª‚’|œ�‰ÂLøïQVÞü¡ÿiÙ¼r‡W¾ÁÃt0wE³ñZ¸¢ÄõȽÆõà5ã�sÎ#=&vh ]C(ÎCb¤8 DíÆZˆŸˆˆˆèÝ&H$A´½º‚Ûø¥$†ü‰Ú΀ÇÂ�´Ú.BËÏ^¹Ï(d�€úÝpD u°?þÚ ušcÐÕçµÌ9�=Þ�–@=_ô9‰»Z"ï*è›cÀY˜¹hለˆˆÞaœ%KT3I%84’`h�>¿¢¶3$„#i  Íx~ªí_³´Ýòô@æqÜ9†û7äé%€’û¸ºRÛ!½s˜aÕLéQÈ> ø õ|@\€‹‹ Å4´š�·}¼¡U¹ÃÜË0°(÷;íÞem‡HDDDôÎa†IT3eìë¶°ñ‘×dÆ£ô!4ÿHýëèjû _Äí_pì‘ @ɘ4À{_ÃÐ ‚H^CDDDDÕëmå z[=º9°¡š�G»Åò;šÃÆݶª¯ô#-Cê.$oAö9å@d w8†kDFêýX¢è..DZ ïÀÈ�‡¢íBèèËœ›ƒ‹‹äå±*K×FBR -¦Áç+(+Âz#0´Âðldÿ� ñÏ)”<„Ycx~Š&Ã_p±YgñKWˆ ÀyºFBÐ�Èþ{P’� u)LíÀ}Ü'aÿ¸}P^CDDDDÕHÄe~ˆj$}3�ŠQr Kà$¯ .Ä�8\þ ÞË”í¦áð äœWÖ””àî Ü=�kÿCÐEeê(S˜‰�(Ì”ÜÆ¥/QV‚÷¾©|ÌE98ó). ©X^“w Ç>„A8ôyæ«$!¶·<½´û]6AP™|qm � ‚]yÍý¸ZùʽË8K–¨f²ò*wXœ KÏò5yåÎÆtVI/˜:ÀÐZ~ä6Z=½�yLž^ *Óeo|�âÜ*…}éKHÅåúä��V¨0ûz (lÚÃtô䧤e¸ò ÎÍ�É0¶€¬3ØçI)Ìš Éˆ×tˆˆèY˜aÕLncaÜ@^6µGÝö0oŠÆÃžœàö÷Ä£t¨ãŽžû 2VžMÚ„Ó8öG»%Pœ‹h_܆‰zDC× ú¿,DDDDï8>‡IT3Zcày¤îDY\B!2€.?Á¡/ò“ÑÀ6Þò–R þÜ"/›»¡Û別E X´¨¸ÿz¾h¿\^nöoÜø^^V<íY9žŸÀ5 ­à*V•–¡ðŽúc“eÅ8Ô¹—ÀÔ½Á N¹�‡áÊJ4 Có òy³pƒÒ‡ðùJ}¥Y""""ªÌ0‰j,£ºêËÆ :å†.eвQòdÆlÃLˆ­� òËÁÐRY?®RÌÏì¶P½e~2ò“ååÒGt¥k€ õÊÿ­RxDDDDT5œ%Kô¶“JTjæ�|q.Ž�ÖvDDDDôb5óã&½<#èÕ’—oªø‰ÇJPÝàD±ªPY1 ©TwÑ5B‡5òaÏ[ûqc]5Ü-""""ª f˜Do;AŽýå弫8>R¹ìýDÄýî]ªL·&vÊò�ï!-Ca&BRª™°uôà¿ Íÿ�Óä5§§âaš6î ½,>‡IôhóþŠFÉøs3’·¢–#ÊJPp î'¢ÿè¼âoÅJBÎÎĹ9Ê].5¬ ö€Öó�¶ù)?¯¢Ï±rûaÑ›„ÔˆÞµÑ+V9ê(-C~Š<½ )EQÖ+÷iÞö}”‡²ô²Ž;\B5¼È�ÖÊËwOàòŠj½uDDDDô*˜a½l||í¿†íû0°€ ‚Q]Ø÷F—Í<ãú•é³ËF¸Ž‚¡5tôQÛ­çaÀYÔï¬ùàøÁe¸¼|nò®iï>ÑóR©TÛ1ÑÛ€c˜DDDDDD¤Ì0‰ˆˆˆˆˆH3˜a‘f0Ã$""""""Í`†IDDDDDDšÁ “ˆˆˆˆˆˆ4CtùòemÇ@DDDDDDo‘¥•µD*官DDDDDDTi‚ è‚H¤§Y†)Ú‰ˆˆˆˆˆˆj ©T‚H_OOÊ “ˆˆˆˆˆˆ*M*A„ÿGEæÎ¸+!9%tEXtdate:create2021-11-16T15:52:56-07:00è:Õk%tEXtdate:modify2021-11-16T15:52:56-07:00™gm×IEND®B`‚flox-0.11.2/docs/diagrams/new-blockwise-annotated.svg000066400000000000000000002330151514775624000225460ustar00rootroot00000000000000 2022-11-14T16:09:56.206507image/svg+xmlMatplotlib v3.6.0, https://matplotlib.org/ flox-0.11.2/docs/diagrams/new-blockwise.svg000066400000000000000000001137761514775624000206060ustar00rootroot00000000000000 2022-11-15T16:33:54.164631 image/svg+xml Matplotlib v3.6.0, https://matplotlib.org/ flox-0.11.2/docs/diagrams/new-cohorts-annotated.svg000066400000000000000000003073701514775624000222530ustar00rootroot00000000000000 flox-0.11.2/docs/diagrams/new-cohorts.svg000066400000000000000000001657211514775624000203020ustar00rootroot00000000000000 2022-11-14T16:28:28.725615 image/svg+xml Matplotlib v3.6.0, https://matplotlib.org/ flox-0.11.2/docs/diagrams/new-map-reduce-reindex-False-annotated.svg000066400000000000000000003156761514775624000253100ustar00rootroot00000000000000 flox-0.11.2/docs/diagrams/new-map-reduce-reindex-False.svg000066400000000000000000001717421514775624000233270ustar00rootroot00000000000000 2022-11-15T21:26:39.966187 image/svg+xml Matplotlib v3.6.0, https://matplotlib.org/ flox-0.11.2/docs/diagrams/new-map-reduce-reindex-True-annotated.svg000066400000000000000000003056361514775624000251700ustar00rootroot00000000000000 flox-0.11.2/docs/diagrams/new-map-reduce-reindex-True.svg000066400000000000000000001713171514775624000232120ustar00rootroot00000000000000 2022-11-15T16:09:02.384660 image/svg+xml Matplotlib v3.6.0, https://matplotlib.org/ flox-0.11.2/docs/diagrams/new-split-apply-combine-annotated.svg000066400000000000000000005327521514775624000244660ustar00rootroot00000000000000 flox-0.11.2/docs/diagrams/nwm-cohorts.png000066400000000000000000004527431514775624000203020ustar00rootroot00000000000000‰PNG  IHDR�;fNA9tEXtSoftwareMatplotlib version3.8.0, https://matplotlib.org/5±µ« pHYsaa¨?§iIDATxœì�wxTE‡ßII=Ô@€Qz‘º¢‚(( ‚ (Šú Ø@ý+X@é 4ñé"¡¤7!”PB‚´„”ùþ˜»›Ýd7‰€œ7Ï<»{gîÜsïîæî™9s~Jk� ‚ ‚ ‚ d‡Ç­6@AAA¸ýçQAAA¸*â< ‚ ‚ ‚ WEœGAAA᪈ó(‚ ‚ ‚ \qAAA„«"Σ ‚ ‚ ‚pUÄyAAA®Š8�‚ ‚ ‚ ÂUçQ¸«QJ WJi¥T‘ø¸~J©Ÿ”Rû”Rç•R•R{”Rï(¥ü2µ-­”úJ)®”J´ìí™Mß­”RJ©KJ©3J©©J©b.ÚURJÍQJ%Xm7*¥É�ÓAAþˆó(·/@#�ÎÀ£Àà=`~¦¶�îÀ`qv�*¥šK€“VŸ¯­€•J)o‡v倠2Ðx8 ÌSJuþ{§&‚ ÜM(¥j)¥æ)¥Ž[ƒ‘{•Rï)¥òdj7P)µÁØLVJ±RïsÑg ¥Ôh¥Ô!¥Ôe¥TŒRj’RªÌ?wfæ~i Ú¾ñïA¥ÔðâX‚p#äºÕÂ݈Ö:èšis¨åཀྵ”*¯µ>dm_­µ.  ”ª <•Mןû�ǵ֩Ö>ÑÀ: 7ð½Õn�h«µ>fµ[ ìF)¥æj­ÓÿæiºD)¥­õåœè_AøçPJÝ ¬ö¯g€f˜ÁÐ:˜�Lþ˜Î@Ps?Ú¨”ª£µÞgõé ¬ À?1ƒ�ïm•RU´Öçsüän €á·ØAp‰Ì< ‚!P)õ«Rê/¥Ô9¥Ôt¥TQ[¥5Úy6ó(ªU¦”Ús“ì8m=¦Ú6\«§”*Ô¦ÙGkÿõ‡²“CóÆÀ›ãhµKÃÜÔ�û¯áx�*¥vZ£Ç‡”R¯ØÂ€3µÓÖèq¥T$� ­í=­¾Ë9l;¬”Z¨”êdÙ–dÙ60Ó¾Vˆð>kD;ÑjÿÊÕÎ[Ap¢àtÖZÿ¬µÓZ&�(¥ Ùj­‡i­ß×ZÏÓZ‡k­§?L„��¦@00Dký�Öz•Öz0(…‰¨ùWáê÷… ÜŽˆó(†¹ÀàqÌh_G`™RÊ˪ÿ3ÚÍq'kĵ0Æz½Ê•£ãeÈ¥”ʯ”j¼ÌÒZ¹�s¨j=îtQ·Ó¡ 7Ɖˌm[õìdÙú+�™A}3#ú¬›]:/m�5VˆmPxÎÚÿ<°@)•yVöz¨ |ŒÂ8Ìë�¯3…½‰yŸgYç0 (ø7Ž+‚p7’b=žË´=HÇ,¹ÈŽ,ƒ¦Wé éjF)¥J)¥Æ+¥b•RW¬�Ú_”RÅÚ”±‹OY¡‘J©×•R.+¥^SJE+¥.(“[ �‹6�¨Œ¼ç•R+”R 3µ±å[¨mÙ”TJMÅÌ:Ú^m¥œµí eòœ³ú?¤”š|µk!7 [ïZë7­çË•R'�@`†Öz§R*óO}¢Ã~/?Z¯Ó¬r­tÅ806¦Ï߀ý`Â�κ¨;ëP&è¥T^­õ‡íM2õ厀c˜°×+`{=ì¦}^ šÖ:Á¶A)� [zÀfƒRj!°øB)õ³Öúšq€ZZëÖë%Ê$ zW)õ�Öúfæu—5:ncÙ KánçL¸ê÷J©Ág°9Уµ¾˜y¥”'æ7hð p sÿ³±Ø WJÅ‘@%àc`+š�AV$Î&L~��1¨þ˜ÁËBÀI+ºh=f0õ]Ìý«ðPx1S·€½Ö¹|,VJi­ÏYÇí†ùí°3 ê�¬\¥”j©µ^›©Ï_�Ÿ€±˜Ù×ÝÖã〣ÃyÂr@g[e8Æ�. „dw-áf#Σ fdzý3æ†ØÂ¡îkàW¥Tc­õ:¥T~ài`ŠÍùÑZ·¼Îã.ÄšæÃÜ(þJ©NcÍ¡;‡ËqûhÌ:”­¹‹G¸‘UïöØÊdƒ­ Œ¶9ŽZë J©@O»…erý€úÀ÷ŽÎ«Ö:M)5 ø³¾e¯;;²a�ƒãhc&Ш ¬þRJ}‡IP¡µþëŽ%‚pW£µ>l96s�ƒUß�áheæ"Ʊ³¬â­u¬CŸ©J)Ûý÷‡ýVaÂcSÈž€"@ ­u¤ÃöŸž¿† �­¯µ¶c™åØöWJ}¥µÞïÐþ<ÐÁZâ�Rê¸e[{à'k¶òsLî€ö¶{¸Rj1æº|Џtä­õ0Ç Öà5Zë ™¶7Â$ÚëosV-¦^åZÂMEÂVÁçøÂZ3�ó Ü|ÌÈäëuOÌá˜=¨Ö:Ak½Yký»Öúc̬ã#8'¸Vâ­GW³†…q˜‘ÔZ¯za’Äœÿc˜ÑW0³Šî(„¹��tQçjÀ 7}dÞpÜz¼Úì§;â²ÙfësðгÎ3ÞZ{Y÷�)‚pWb…T.À܃ÇÌ:¾‰¹GNt³[#Ì€iŒSö»rȸj-™�Y†Ðs¯zãì­PJ¸ŠYí�ß39Ž™ þtpmLÅÜŸ2Ïè-²9޶%"e­ÇʘȗiŽƒ¿Öé �‹u�s®rŽl²VJu±fWáGœGA0”p|¡”Ê…q4l™-qÍàq¥TILHËJ[v¸›„í&VéöÝm=VsQWÍ¡­õ˜ó¾ÖZÛnÜX“Íq¬6Å]Ô•p±ÍÖgæ>Ò�’.ÚX�g¬Ç$°gßsÄ�6§+lÛâÁ h­Gj­kcë§0‰‚–IÒA„ëâ ?fíõj­õç˜YÇÞÖúv'´Ö[µÖ´Ö30> ^jã9Œø˜Öz¢Öz�ÖúG &‚äÕ«ØT8z•6þ\ßf¼ã ­µ-G€o¦öîúôÀ œ:⪭K´Ö«1ùra–ÊUJíVJe—�]n:â< ‚¡{¦×]0ÿ WeÚ>³øf”qôM¶£…õxàzw´2§þô°Ân°ôWƬ­È¼OªÖ:Rk}ÀÉ}˜¯µŽÉæ8�Í@G¥Tn‡ãäŬ¹[/�Ç”R¶/VØOÌMß.tØzÌœÄça7Ýß§”ª‘i[7ÌèöV¶$j­Á Ê]Ë9‚ €™üÓÅÚFÛLYU²Á’ÜØ‹ó iMLþ€­™ÚÂ8qÙö‰YwYú*mâ¹¶ÌkÅæ\ºë33pêÈu­ë×ZÏ·–ÇÀÜ+gfNÈ#9‰¬yÃcJ©T`pf!üœ×G µNTJýˆÉƒ Õ±£”Z 4×ZgûÝRJõä"_ÄbÂ_›/cðÏÏÔþqëiyë±®RʶÎò‡¦ƒ­søŸµž¯fTx7ɬ2¯c’œîÁ„¥“–›ï‹03u_žÀ€ ìZjÙú»Rê ŒSþ"æGÁSÉrcBn')¥ÞÃdä뉙)tÅqà7eD–O`œÑÖÀ`+YÖÚÌÝ'ø4&ìèUÌ{u�ö ‚ ænU ØlM¶3€J©"˜è˜u™úôÄäØèжf†ïj³ŠK€§•R•³‰Z UJÕÖZ;:©Ï`œºß¯rŒÌìÃ,ù覔úÂv³ÖøwƬ­¿t ý$[ûùºÓC¶f=ÕR‰˜$@µ€ˆë´Wn ­µ)wmÁd,Ó˜0˜ß0ŽÔ_˜+ÅÜìÓÜÚg°‹ºUækuÕã6Â8žÇ07Š‹˜,£ïy\´×¶­17‘˘‘Ð2Ÿ ÆÁ[†Épwã4}¹Žk׳æ#ÙÚ0&©ÐY¶�vÓGÌ üpɲ»ƒ‹võ0?,.`~4 Ç„5i œC»ÃÀBÌ�z·e[40(S¯Yý�v°"PöV&¥H‘"åN*˜uúéÖÿï.˜µ‚oY÷Ó=@n«]LtÌ«‰¤ ?&“êE ®CŸ�˜Yº£V›ÖÿüƒÖ} òUl*…q@O¯XÇz Ücµ±…¶žÀ¬«lcÝÃÒ1Ybm}•³î5o¸8ކ;¼îfm[d]—'¬sNš8´nµËrÏÅ Žj«M}L‚ºÜ˜$@“1‘RÍ1¹Â0÷ðûnõç@ÊÝS”Ö7’ _î^”R_bfµÖñWk·`%8ØÓZ·¹E6vk­¯)|VAøûX™Q‡`–ÀDÔ,FØî“ÖºõјŒ£�€&™Ù*«ÝŸ™ú¬ˆ‰riŠ =‰qP?ÈÜÖ�M¥�÷�1³•§1Ù¶_ÖZŸ²Ú”Á$Pk‹Y·y3�8RgdK-‡„ü�Öú‹LÇÐÀûÚAöI)õ(&²¦&ôvðžÖz½C›áÀ0 ¨ÖúL¦>sc–Q<ŠYÛ¯0’&÷a"ƒªcßDLôÌ�UDr qá±ÖVÆã´Ö¯ÞZ‹n-J©I˜°Ó˜„4ý1£¡m´ÖÙjpå M‡çQA!G�5�‚píD`B+bÂKïvòaÄ”‹)˜ÄÞ*ÇQAAÈYdæQAAA¸*"Õ!‚ ‚ ‚ \qAAA„«"Σ ‚ ‚ ‚pUîê„9J)`´ˆAL2¨ãZ’‚ ‚wµóˆq�Þj#A„ÛŽÒÀ±[mDN!ƒ§‚ ‚ ®>xªµ¾k FV»+>>>zæÌ™ÚÇÇç¦ÕÝŽýŠMb“Ø$6Ýmçê®ÞÛÛÛö<ÿ­¾G]Ã=ìEŒxy°hzû–ruM¤H‘"EÊ]_Jewÿ�5�Ù ”"Ož<˜Ú›Sw;ö+6‰Mb“Ø”“ýÞI6¹k{»¡”ê ||ÔÖK”Re®± ™qA\‘íýá–8�J©”R;•RY%B)ÕÞ¡~ªRJg*2õá­”úV)uF)uQ)õ›Rªô?6‚ ‚ð�ó0Ik=Qk©µ~ˆ^¸µf ‚ ÿfnÕÌãQ`P×*aÀ|¥Ô}m–%ʃ™úø è< 4ò •Rž9j¹ ‚ ÜB”R¹�:ÀòLUË�FnöñVJå·̺AA¸.nI­õ‚L›ÞVJ½4öXÛ’µÖq®öWJžžÖZ‡ZÛz`F][ËrÄpAA¸õ<�“™¶ŸJ¸Ùg(0,'�AþýÜò5�J)O¥Ô“€áPõ€Rê”Rj¿Rj‚Rª˜C]À ‡QW­õq`7nF]Aá_†ÎôZ¹ØfcPÀ¡È2AẹeRJ©jgѸtÒZÿiU/þÄAÀ‡@˜RªŽÖ:3²zEk��©ÛìF]QJyÞ›òøøø¸L’àëëëôx3ênÇ~Å&±Il›r²ß;É&’’’\¶¿�8¤‘õ~WŒ¬³‘X÷ÎdÛë;%1� ‚p{¡ô-Ò@¶Öl” ��>@sÒ±mIŒ#ù¤ÖúW¥T7`ŠÖÚ;S»ÀA­u7ÇŽ‹°�™3g’'Ož¿wB‚ ÂÏ¥K—èÖ­@­õ_·Úw(¥6[´Ö/:lû˜¯µz ûçÎå ‰‚ Â�Iö÷¿[­Så 9 ŒË¦> l=Á„æÊÔfð~6}xc´m¥­/__ß,¥páÂzÞ¼yºpáÂ7­îvìWl›Ä&±én;Wwõ… ²é\ÝÖ:�@Wà ШŒÂDñ”½Æý³Õ9–"EŠ)wmÉöþwËÂV] p)ͨPÊNX›¶)@kàg«MI *𦻸 Û¹ZˆÒåË—¹|ùòM­»û›Ä¦Ûe_±éßÙï�`Ó­ŠÆ¹^´Ö³­{ã{˜Œä»�µÖ1·Ö2AáßÌ-q•R‰˜û™ùM)•Ž g}ØzL�¹ZësJ©)À$¥Ô 7fÄu/fSAþÕh­¿¾»Õv‚ w·jæqÐ ³¸ÿ¯{[å 'ð&LõM [û½M†³.±.p˜Œ‘í˜ôB€e–TG`)0ÒÚ/h«”ª,¤ ‚ ‚ ÂÍ%'tW‘!Á¡qȶj¡€ZÀkZ믕R‡ÉX f=¤Êh­ì;)U“pgRí�}5©AAAáÆø§æ ÆÌ8~㦾pÅÑq´8iÕÙÚœr±ï)‡6Y�G±Il›Ä&9×ìêEçQAÜóOë<Öa$7Ž[Û_i­¿²^wÃh5zgêkpPkÝßÒy|Vk]9S›(`’Öú7ö GtA7ˆÎ£ ‚p—“ýý/‡u¤4Î:�¯阙G[Ñ@pØjbm+”©¯ÀûÖóÞ@¢‹ã%½²±GtÅ&±Ilº-ú½mº›ÎÕ]½è·BªÃŽRª8ðð¬‹êîJ©‰Ž%˜d9ç­:›ÎãF[c­õ¥Ô9²Ñy©±Il›Ä&9×ìêEªCAÜó�Ju¸¨h­“¶÷¢�8L¢œÀ­uk«þ- §ÖºR¦þöcd>F¸9ÞpDªCApÃ�"Õáæ~vRkíVë8Óþ"Õ!‚ ¸"ûû_§×8Hu¸¨ß |{ ýÔ±úªm½~ Øç¢]0$›~DªCl›Ä¦Û¢ßÛѦ»é\ÝÕß)RÀp`7P¡½ŽýEªCŠ)R¤¸*·•T‡¥TS 2ÐõšoÅdS ¶žÇÅ]´+Š su‰©±Il›Ä¦[Öï�`SNFãä©™ó‚ BNrÓ³­*¥†*¥6)¥lë‡*¥*gjóð� lWJÕtÑ�·Rê[KÚã"àeµ‡ �Ç–J©iJ©sJ© �Ç]7ûœAá6$X)u\)­”úI)UþV$‚ ü»É ©Ž`Ipò¿gr ý1³„?eÞY)UA)õ0x‰™i¼¼«”òÔZGK�¹˜ä9¯[mÎãbM£ ‚ üËØ<´úbÂV×+¥ü]5¶dóÛ VÂ8AA¸rÂyüxŸ ÝÆ{1zŽ_9´Q˜YÄÏ\ìhtŠ`t"õ�j@+«Ýû˜›_IŒƒ¹x è�y¦SAþMh­—h­çh­wi­C1™ËÁuör€¡˜9¶rô0SAø—‘:�«0Î!J©Š˜D6ÿqh3¯”*çbÿXkæq%PLk�àÐ×nŒÇ2ŒSzNk]Ðqÿìä:DªCl›Ä&±IÎ5»ú;UªCk}Q)µ “À#0­6ò!¤ ‚p�ä´T‡æ…´ÖM]Ô—ÃHrÔÒZowØÞ #¹á�©ýr ZkÝïFä:DªCAÈŽ;Eª#3ÖàèA`¼Öúƒkh/R‚ ‚+²¿ÿåp*ñ1Àa ´›úr˜”°53mï$»h¿«oP®‘ê›Ä&±Il’sͦþ’êøha–u,þÊ^ãþ"Õ!EŠ)R\•[#Õ¡”úxh¦µ¾ÞИ8 ·Rª�cØ*P XïÐæºä:´HuˆMb“Ø$6ݲ~ï›r2ç&S˜…É p³î¿�Ö:æ–Z%‚ ü«¹éΣªú-Ð x@k}ÝlÁè:¶~¶ú- TÞ´ÚØä:î×Zÿaµ©�‘ëXŸ¥GAAø— µ~òVÛ ‚ Ü}äD¶Õ ?PˆPJ-QJ5VJÙ3(¥ [ÚŽ÷Z›¶)¥>WJ•ÐZŸÃŒ¤ÎVJi¥”ŽcBrB­6‘Öó¥J© –ÎãR`©Ö:K²AAAáÆÉ ç±>à øbôÛk�§Ú<l9l{ãtÚ8ì€Ë˜õŽ�µÖimT¦"‚ ‚ ‚ ä7ÝyÔZ+Ç‚Y§°×¡ÍTÌz�c˜PÔ`�Öz¸CWé@¨Öº°Ö:�Öº�ÖúO[¥Rª Ðh§µöÓZûí�v¢ó(‚ ‚ ‚psɱ„9°ÏÚ6(¥<€iÀçZë=®4-º+¥z`à,Þ×ZŸ·êbt7Úk­7ˆÎ£Ø$6‰MbÓíÕï�dÓ�ªó(‚ ÿ·DçQ)5h´ÕZk¥Ôaà+­õWmúb4 ã0³“#€ZëÖV½è< ‚ 7•;Uçñz�GAÁ ÙßÿrX‡*‹Î#Pã8l; ¼z•¾ê`´GjkÑy¼k5ØÄ&±Ilºóú½“lºStoÂýù–è<Û–m™¼ÿXŽ”[q®R¤H‘r‡–ÛNç±)fä‡PQOàK¥Ô«ZërnºÜŠ‘ï¶ž‹ÎãMîWl›n—}Ŧg¿w‚M9�#‚ w:7=aŽRj¨Rê$0ȌʔÀf&#ëÏÀ Õ*¡@[‡~¼•Rß*¥Î(¥.a€pÂjbÓy\¬”:g•ňΣ ‚ ‚ ÂM''¤:^À$¢é<ø¡J)­u<ðï[�gfçU�7­×{0R¬}Ã$é¹c¤:&êôPf)èУ§k�ã¾­.Û¯×�S˜Œº˜#cìÏ×–�´4Å^ÊóþñÓ$)æ9,óÉã;‘‹N_Ÿy¾I\ŒmJèÊò”-3€5úIHKs{\AAA„œÈ¶ÚðžÚ�ùJ©ÚÇ<ŒÉ°  ”ê‰Yÿ8×Ú–Db’é<´ »03“í>N`2¹6´ö_x§Hu4HžEÿðyÌóMb†î ÀûÇOƉœ¼ÿ˜½œ ©eßoýº'ñË3鯘#cˆ=:€ÝIžÌóÍ~6Õ/Ï üòÌàÌ™F”‰mJÈ´h~[PîªkRAAA¸ûÈ çñAŒ¶ãt`F§Ñ `%ÅiHÆìà¯Ààm`¬C?+­ºq˜YÅóÀÃZë4K¦£fs#fcIÌÆ÷¹ƒR¿35üX³úiú‡Ïc¢î'rèÑS”‰mJSõ“÷³¯‹|6ùj&øÑ8ykV?�Å‘?c8K—.½füòÌàH…ÝxÇ¼ÄæeçY;q4—ï$teyŠ-\{“ÎVáæ0yÿ1ÖNÍ©_"nµ)‚ ‚p×pÓ�G­µ²LXìSÀàK‡6qÀ¬— ´Ö÷h­GjçÅ&OZûÆßÝ´Ö±V�M¦c…Öº‡Ö:¿ÖÚ“9®ÚÍ>§œ"~]F¢»!õ;3¤~g$Ï ÷Ú…ÌóMbÍê§ 6™Ž²gÂ9žHŸ¤–vGÒ¶¯­¿k%s‚˜à�¨"-ØzÅ“íãª0pÒ‡DZJDD„ÌL ‚ð�³lôöçQQ_rö�çÙ¾© ?.>À�!Cn•i‚ ‚p×�# s”RÕ€LøêLrœ?¯£‹@4Æq¬ŠÉ’Z¾ Fò”‹ýNYuî캭tÏ­ø±÷ODǯ¡V‰ÆlOñ `s"Ô…�ufóÅîxx<É—Ý:2 FEŽÿoá@š‡sB‡“kMN¡4 uAåòbÓ‰5x¨io“žžîôèȦkÐÖùk¥HiÔï6‚]a'�‹-ɲ¢øè5î{6äJ'üøYÀµfåͺN·».œØ$6ý[lº�Ïu÷ŽJìÝ;’ٳϷ§5¾Ekåiþ�o”eÿ±ItAÁ=9¢óhé8–Á¬Sì ôš;:�J©rÑ–0'»þê›�:Zë­–Æã³ZëÊ™ÚE“´ÖŸ¸ég8¢ó(‚ ¸Atÿ>_tyÈmÝëUÖÜp¿‘?¸­ë24û±ð*»W;>uO¶û¨å>�¸¶ÜÖõ;p6Û~³£\hf…³ BûdŸÖÁ­$ÂíMèÊòÙÖwWsn¨_Ç<)7È?¯ó¨µ¾°^nVJÕì}ìwƒ]nR€`ëùuËtXÜV:�§ªD{f8ï½%�¢=Xq¹A}L•>½Ø7f,÷Gí'pÐÏ|Ò¾:ñÛø4ñS“ŸáÛðet.7€9‡G¡ryQþ±îúu:5…z-[ãyOMÀÌ8ÆÇÇãïï�‡‡s´rÚÞíh¥H(@¡³Çñ(ì|Óª}j=+Š• õ©8¼N¼Gò�öºÜäfpÁÁ¤û×O:„š¼H ËÒ{Ó§T­³Ÿ„¼™·±;ëÎü[táÄ&±éßbÓíx®»__®º‰óC§eý?­<5!íõoL~øoÙ$:�‚ ‚àžÓyÌ„Â9\ôz¹“tç„õ:#Óq¿Öú¥T}®"Óq»é”èÄ=]úR§§IrÖ·zT–ýb¯þGìA„»�]ó¨”òžü0¡§6PJÙfÃ�·µÖ6é�:˜õ�Ëm�µÖÇ•R»�FÀ22t7:´Ù ”:gµq9Uu»HuÄ–XLá;™þ¹Ž× ×¢³ä«4œ ¥/±!Ì�jÕªÑ1¶#÷6˜IZš"<àwÚÆ6gX·ªp`Þ–T‡¯Ï4ÒÓÓñõ™ÆøŠgéÔÌ@ª\^äy5Ÿã§íÇôÖfUãôÜ—¨y'Ru:%\!Ü3̓2ì­‡àÀÒÉXƒéQô<åòÕçÏÔyp¹$)YIض%H¡ ‹¼}C,›IKËR—–nɃh×Ëa�Äþ '÷‘—)¢çpæÍ—)s°*þ%}9µ¨6ÔMdÆò½œ.ù?.¼·– •k1¹I†/ vÜ–rb“Øt;Ût»�«oÑAô¬q€”à�wqáp ¶y�`ǤÀ§\¨\‹¯¢vÉ€ß.\Á××W¤:Aá&“SR™u»i­[u]­m1@ð!Ɖ­£µNVJu¦èL…Rj9­µîgIuôÔZWÊÔf¿µï7v G¤:A7ÜRJ©fÀ0ƒ©%1ZÉóêæ^ö &:�?(¥šk­ÿÔZÏvh·[)µãH>üšMŸ pôt]y½™Ûdæ–KuŒÙº�®G&°âr Q<ø³êrƯ�ÅóMž¢ÁS?ýö[:{œU±§éÚ5Ÿ}ß´4ETTÁÁÑ<³i:«ŠNbgÁ‹öz›Ç©*é¹*á¾aýº'ððð Zµj4ÏU†\ʃøuοÒ<4»ê&RmsA–&¤’”0š6»MN£too¢ß~‹Ö¾[ð:qÃH®»œñ3†ÓÿÙj¬¸\‡Ö»²,$k¢ÛÄ)>°ƒ³k€§§seZ…ì �ßp”ÊšÁUko.]N¡³ÇQ™9ê¤yÚåC¦‡8ß¼Uš¢Xd‚>ú˜ÞÒˆˆ‰eJ÷7¹wwÂvÌ%°ó3>°ƒB”£¶lð~ŠØRËP»ò]%� 6‰M·û¹þ\‰òŸ?ŠËuìDYú-2€€�‹œªr�”ojØ·Wo]ŒG{?p§Juø;€)€«_o¯=�ýÀ;À ¥Te­õùÊHwøzÍmݘ8÷uI #ÝÖuí8ØmÝ’Èìmڔ͂–Ó%²s~Â}Ýœp÷?îFgo§ËLs[çƒûõ¾­&f¿:çòb÷‰ð¶^ñt[w5vÕXä¶îªŽòÚïÜVÍm·ßm]¢Ö¨%GšªŸÜÖ�9Ó({›²áâ¥înëZF½á¶Nõñ†�)™™¡;»­ x1·û+ß“m¿÷îÛ{£&·�ΣÖú„R*£áFÃ1·Rª�Ö:Á¡i12d8nHçñv�êèuô;Fþ°— ]ê°{Åj¶.É·k§ðíÚ©ŒàÍ»ÀÞf—xâ¡YÄטÃòm?Ó£G~{Ák}8<‘ªËŽòSGÿ,Ç Š�HLHšDͦI=³-U§³2å0¹”¹”ž.ôÒ<Ó-B_rY·,o?¨ž‹ã[+ñùê£ >¯Yµ Æx¼Ò“ÀÃùã”èŸiÖ�ÊÓ3«óhÑðÊTv¾lÿgÝ8y>^3Ùýú~Jk<29�Žò!Žr'³”wì.¼}ºÁ‘›aÀ Þ3wÉ“¢E¨úÛv·zÂRéÛÏ)Òä {÷Bxé@–ÝSšóUêÒ£G~Öè'é]©”ýÿ „¿ÓûDfå6Ø÷Në÷z÷ o8’Êʬ}¶IeƦýxþý5xç«iß¾cÉi¾X·ömå‹uÛé]ÅU®¶¬Ç½¤:´ÖK€%@–�MkÖñUà#­õ¯Ö¶g1÷¾nÀ¸ÒVAáî"'¤:^°¤7þ²J&Ž·UŸW)5Z)uT)uÙ 5-G††ãÌìáY¥”¶LFV›ó�Ñy\¬”:g•Å\Eçñv`øZof¶‰ R§—éÍùÈOØ}Ä©Ýøµ³è24ý»R²§˾¨Iùp³.gz3”Ô¶zö"mϧ}óøNdút÷ÑVÙ�<©°;˶õ¿>íô: ç}ö>U“'Ù·Ûó ‰þiŸºçª£…Žü¯|ažœOËô.´Œzƒr>i¬ôø™¸‘Wh�êü1ݾ©Ê5÷=hÞf·uCm#´Ï>”+Ê�–—)ÞË>£ÃdlÃW²dghñý,ö7ë”EvcH}÷SÝúÌïíèŦޕ(´ÛIë1³£ÐkÆgnûòtF˜XãØ·¹¾-Jt¢õ¤ƒT‰Ïpˆ{¬^ÄSâ)Û”¡GO9õûóFu¬Ë†¼�²2ø‹,Óô‡š_æÔ}�Û_;®?Ù7f¬[Û®FrK÷ëgBCCY³úiú‡ÏcÇÒï91õ"ç«ÔåÙäØPzG£«R' íÚµ£IlÃLâŽ/z/ vðj¢¢¾ä׈8ªN­Ê½0dÈBW–§xض;ZÓòøª6·Úá.ãÕÄ^.·ÿñó{öç=ïàî…é鳉*րۆ؀«®-¹ƒ(a=f޲9éP犡˜5޶r㽂 Â]KN8�ç�ÀR`2�dmÓG2³òGàS` ° ¨êÐÇALØë8à7LVÖ‡´Öi`—ê(¬Â¬a‰u-~»Ku4NžGëÄ3¼V÷QÖ�ú/î8ˆ*Ò‚6µº0pÒ‡<ó*Op¹Èÿ}‡,ªC�žbÅs uŸÀÆøÃÙ¾© [¦^âøÔ=¬/Ñœ²QÕIzÆš½ŒÆê¥½�öYØjë5ŸŸmµt«Å4¸0ŸŠëØë&Í?MÀ‹¹™çë&Ø¥ÕŠÌ @Ï^´Œzßó ð‰ÎX{tù/5¢ðçYW�¬ÿõiŠÅ=ãVë2;–• ¿î}~ð^i“ÙÑŸ2fêPÖ�ú/ÿû=V›õ }«GQ:h7˾¨Éñ©{Ø•”±¢Eé"¬Yý4k­P¼aùýËg™ÄÁ¿-(Gñ°mü‡^1„?+ßcw<#-eذa„®,OTÔ—DE}Iñ°m·Tó¿�Ük� Âße\ÅÂöçíx=Ä}"€â'ã9]b5ïÎ>Kןf;ÕíýÙÈ ÝɃ6™È_{-kþ 8”Ò9d— ‚ð/榯yÔZ?g{î ÕшÐZ_VJÍÀd�ëÀ$ÕYæÐM:P s#ŒÆ$àIt¨·Iu´w<¶R*‘ÛXª#rÐRRu:©–lÆ©ˆDÞ· îƒ +NP½u1êvN‡y Æ£œH/B‡Ðêt(ñ>#âNñKJ-¨›n—ÝpäýØ8F…ô„øm¤žö#Õ˹ÍÿÊ ð¨Ûª »·dü¾Ø¾âÛõ]5B.’Ò ½—çãxçq4o™—¨´_)ÖˆÕ¾¡Â«�ñ×Û‰ª\Ð €]‚#‹ŒÇúÉ쎟A@ÈE"'N¡yr2iÞÞäí×f/È�ïHKc×Ù®|4t&0 'A.X}èVË�òð˜ß—"\an|>�z‚Á ¦ËÞê¯ßÄnïSì{tË«qÿ ¯O<*Máí™õ·”Mê$ÍźOÛ6×ã*¶í*—W–ºë3d{|¾¶I0¾å%¼Zì€È ìm’D€g>ÂÐŒ_÷=¿Lï¨s¤øþ�—ëÐ(íq „eGá¡—`òKô kÄ ¾á©JC?ò«ÏŒÇ»u1tüþÇzª]jH80ÿ·Ê” |†õúq>™¶ƒOªEÛ°F¬j~_“+\aêÈT»½kø¸È—}£Ï}ÈLŠR¬¾Þþ_´§füÚYD´#9å NE$pÿ ¯ã;wY–}ÿ-Rw›M·Û¹^|{=)ÏV E{0aW%T®åNmlßEå©Ù¾âÅh ¹4¿·Í vq6 ´RÔzƇ/Ïw$ÅcëÖ­£U«VwªTGœõX‚Œå`ò\÷šAA¸n…TGnLú’g€TŒ£ØGk=Íaÿ¾§13#98 µnmÕ‹T‡ ‚pÓ¹¤:±ÖüÛ¥:¬„9Ç�QZëϬm¹�SÀ`­õ5-ðÌI©ŽÑýVÞð¾Ùf[ rŸmõjlêXÉmÝŽ¥ßßp¿s‚n,•>\%Û겟!ßÛÜ}r(ɶzmH¶UáŸàï,§È6ÛêU¸†l«·—T0h<‚‘èh|§”:¡µ¶­iœàÐ×n¥T&kkm­µ-†òŽ“ê8÷ÕH&ì ¦]¡\쪛ˆ¿¿?§=‚èZ€ðÆþøx�'>>žÖ¾[¨~¿³A!%øìÂ1ÞÌ[Šd•ufÌ[§Ûë']ð¦¦W:3+‡2~í,&�ñ$úí·8[±)>sHUÓißž%z³¢Ú7¤û×�Œ›ËGQ�2¢T—.·KjÄ–r˜i²ä6l²#ÏýÃnoûä3)>¬¨ö A}ŒGr2 ¯>^=øj–óÉMņ‰ŸÒ³‰YÙÜZ“é([â™ivÑV÷îf’Ó³¾¯Þšë¦g»ï®]»HOÏ:Ãk“<ù4ñS®p…盢”ú x˺7Fo—€™ÿ´­®¨ÕÞýÒK}æ÷«ìÝÞ}M.÷?Ìw•ê�m¯u/g“¨yÇl÷ݼ̽úIëÔ¢nëT‘ÙöK6C�µ-å¶.»ôý>Ù87Ù¹SIeÜkKxÄ5u[÷å„ÿf»¯ @s̓?=à¶.ì�1ÙöºŒšÙÔ^r[“çõì¥ ðÏF�tµû¤}z¬{'àhtÕlëÝ1Ñ'û™©IõÜÖÅ|šýwC¸u(ßÿ†÷]óäÅ«7rÇûïßø¾üÃRJ©W��1£¨¶!¬�ÖMñ ¬„8.Ø ¤`ä<¶rJuLÞ ¯°ù¼Xm?)J±‹’ô¸’‡Qyr1ý©mœ}çy¾½ôÃz–¥p|<µÂï'Ù!¬²§Ï&šð3A²ò É…óhã·Òkx|ƒ å´¹©¦t˜L4ÐãøDjŸÏ¸Ö³ñ(þzš$Ë&¯Çãôfã8:Hnœ‰|Ÿ’#ßæàPé÷¯éPi¯•É:ré�;–œf yÏ,”èä\ŸœŒgr2O5b„G1^,R…Ý‚ˆÿà}jþ•�£e&ò½oIÞÛ×Êiß+\áÛµS9ù !™Ä‘==uÐFrºrº†™qµo||}à4ééé.�Gá!x©4ˆ>_¶§*¡¤hO_ªG³ÜèÛ¼+ÍÚM¦ÊèŽ=ý*ËîKÅãôfÊõÅV‡ë{àå9„=0†äóßR¡Kªt8Ì�Á]ˆZ`nÃK˜ðÞ}ÇØ½î!¼õ¦Ú±…ŒMíOçò��Âk=<<œlv”cñLWö×™åtj d}OmQ ™¥RÆýð6�W|“„ûðLWhÙ‹/æfý++ kÈ ‚è@Ì´0V$ææÒ¨/)Q©!ïÎ>Ë~47²;A*ânµév8×Еåy5ªÀK¥ã¥ÒhÐå#"f¾™eßÍ?Ç;ßËö×¹†laošÅwWt)÷Ó¸É|òÉå,ǽ¤:€º€£—eôü£íøà |6mn�GAáßMN$Ìq…¬5ô²Jæ_åiW±å>k?Ûú›T‡}jN)UŸÛXª£òŸ‰öçSZšÑ‚e¥Â©VdOˆçÛ ìò®è“Ô’a&ëiÔ�?²$�qÄçÇ:î ‰0Ix<ŠÿE‘"ëÙ;u{§®ÊÖöÎ̈âÖ‡*°$ò»,ÙR¢*´ -×j_‚ í–Q-ÈuèÌУ§XÙ¬}Ÿ1Bw8É“K¿gÇÒï³È– ¨?—*‡r¸ÕZ–}QŸ‰îGØþ.×’tèjL™J¿ey {`ŒSÂ�̼pùù:ŽçÑ‚^t.7€äºf=WÌŒñT‰?B™Ø¦3IiF0€ák½éÝ™Îå³60Œ"EÖÓ-ÉÌ”ì+èÔþbëCð~Âd×õé@ùA“(?hAL¢—+¯§[Dt–ræó£ýÌ=�rê¾Çiû§î{œ=�£Â+/Ÿ @™Ø¦\¼Ô�ü—qnˆå¯ê“FþK�Xü‡“< èy»»Yü‹’¢Üµøå™ÁØÔ¬£²ý»ºlï(åñaW÷ß;03B¶ÏííˆÖz•ÖZ¹(=­z­µ®µ.©µöÑZ7×ZgÕYA„›LNè<~¬”jª”*§”ª¦”úò[ñ³«�•R‡”R—”R Às@˜µ¥Ô{J©†J©©J©sÀNÌÚŒÃ`—êX LVJ-QJ]À8�ÇÈfæñVÒ(.#³g£Çœ×9l/t‘µäwé”Ù˜ý)ýMÅã1÷é_†9‡ÖQôˆ‘“p¤s¥×œwδ֠øÉx ôìEÅ•¾T+2ƒü—QœSg=‚ÑmtdÈ¢mœŸ÷<ó|“¸ôå^ Æ{:eSk…þl[Ç–©—x­Ñ2ub¯wÌâê—gF–sR¿3Cêwf8ƒ˜Û¸É}¨¸~ǧîaÌ/­©Õ¾¥[-v{�À6{h¤8†öÿ“—£Íz–¤¶¥Hj[*[‰w o’LŸ¤–NÛææ}Õå:ŽÊ_t€ Gðø ŠÅ=ã¶OÛL«“íë̤®?ͦÁŽm´Œzƒæfi×?|WúRq¥/Qþ ^ׇiX”Ö�Eé“Ô’¡ýÿätq³dßœ’¼·¯Õ‚ÊðÞ¾V¼·¯Eç°aÙ@ûtËRláÔBkÓii%Æõ|‡S%~ä‡*Kí¡¯s�âHCŽwÅÉ>Ûû>Q÷pÚÞ>á”}0bÙè/ˆŠúÒíõ)¶Ím]v„††ÒkƵk� ·''0³×DLpÚ¾«T—YWw®ÈøÿÞ<۾˅ÀO›ý}#Aá.#'f‹ct÷aä3‡ÛtRà f=¤zS6!¿+@KLÈγ˜„;?aÈ߬ ®Ý1á:m03›+¬¾Ü¯@¿EüGä¨o(]†ÈQßàõó³„ç2°EЬgQÿ üúWnû,S¿Ã“˜|˜¤¶¥8ìÓ�†Ý2´çÅÙwžgÃò%ŒMíO�ù‰Û�÷Êã® hò"Ó»làÛ}�(4ØZ/qbMbCè®æÐk‘YìÞkÑY:]ø €îQå²tµaÁJ"ýË�çõ{4o3‘ÍM¨W½®&TÌÆÈõmi{Ìüˆ[ÝôK'9�죩ߙÒQ­™°3€][3ÖËm[ÇÑÐ)î:£#ÀÀ(ãlM;¼�aÑ­ù6èBûìcm`kî[¢£ëO³™RÖ„ÃUkeÖsî{c!ƒæm†&/ùÒ<¾œ°Ëþ#wÂüYœÊ]¡“ìÏ«™Á —O\5@ä¨oˆõÍ5kYþõýwìoÖ‰îQå˜ý©SÝú_ŸfW©N%" `$æ¶ÛŸ¥Ø$Z¶ÚÊÜvûéwà,£Ãdtø�4ðžï>œ2ñlçì¬ÅuþÁïÈ–Æf†´V‰Æ”ŽjÍ]"""©Í½—6›Ë‹wº²<Æ cݼ=ÙÊ’Øêö¤Dóë—anÖNÍ —M É¾9YY¨`n'y?mŽO¡—²T�ëùŽÓëAž&Œ¿N@3¶oªÂÉ÷Q‚ ‚ ¸ç¦;�Zëç´Öåà ð0ëP¥µ.¥µ.l…Û”ÃÈyÔQJ•ÑZÇb’éxOZm»OÕÛ‚¸â@ÐHkí§µn‡™Áìp;i=ÛFI¢Iøt¡}öQetG{]ùÓX™+�&±!TÛñµs§ÑGM2B°æZÇѤ…è3¿óreçˆÜ†Ý>cÃò%Ì;Œ/Ã1±‚s¸§�jÇÚ5ÛìŽvÙÆ67äévGiØsµøO¢/S2!`ËÉï´Ï°çjá•Ò€M³à³ìUWfd­}­QFb�Ï+ä”Y®»ÊÈPçíõ?—6¹c~b ‹7`Ñ«™>ý/bŽŒaþÛaœ ÷æ›àoð¼§&ó|“ø©£?1GÌ¢ûìB^mmÞ$™Å5*3c<ûÚô$ßåÖÄ�¼Â¤6Yéñ3³ŸìÊù*uÁÎ[ÇÜvû©vl!/\>AþK�©aÖ€¶N<ÃÉpo6/;ÏSâ¯:‹z=TOôã©ñìm^†¼Ÿ:ϼx¥t¡„gW§ìùÁž�‘蟖µ6ƒ‰…Óyáò Ê…f$S°9èœ~ŽöU^dêƒ1\/#7Ï`@ÏÔLð#*êK»Ø·X7ކ>hoÛ'©%»j,²kœþ\‰Éû�Ùg.G÷[IÈ4óÙ™-ÎÁÊòY‰Šú’Æe�Úgæ^y€ ±Nƒ2SŒ¡ß�³L®çœ�sÀã+xhl§mSG¦ây_¶^ñäÑ‚YevAA¸6r*Û*À`‘Ö:T)õÎUÚÀdIM´^×Á¬q´ zi­�+¥vc’…-#Cëq£C› V˜«K­Ç[¡ó8¢¤?/mìÄóMž¢èÉ'){hœ볗#¼w´ áéçH›Ïv`;0ØSCl>˱ cÓá†ù³jùž*5 cùW¹ôÛ+TÞÑ‚ü{KRy@ˆFÕ²ÃùìÂ1¾ªð|ÿ% ü‡z¡�XØj+)ÆÁå’¤{gœþî¹AŒxÒßžÑ`ÂÆ®\á oÏgÌúGyþ?OñÆÚ9 .8˜°ne2´m8h5ëY�Sgkò|Ú&ì æýØ8{¿®´)mÉ]6ú¶£æ_Yë“Ï�º£\$Äy.o2Ÿ]€_Úœäñå;8øô‹|Û¯ ­ì£ŽýøúLãpL†T™ÉȽ“€Ãœ\ïCñFI”÷åÛðeÔkù:!!Õ‰��ç �IÌî²–õgcQ¹öÒ+%„(ý³]sÒö6–uÞ«€ Hv-Ç¿�½”Gvy ‚ƒ_϶ßzÅ�«7rCRJ7·uŸlÌ^'öFud;Gg/çóþÕ¥:þY�G¥T ð5&mx¶Ã·J)/ÌzFàZ”U3ë8^¯Öã?ªóxªÊAzƘµ„á¿ÚëJ>OTT�]SñÄÌ ±Nå© ¹Èñ0?úÜw€�ÅógU3«ÒÅ"+PmsAŽí¡ÌAg½ _�޶ëöéÔ§µ@Žý^>3†ê½÷1äh"Úq|UÚ„äá³ Çœ´óUÎ#1�P4¾!i}*Ží÷Uc_á@©_Ï•n·éT•ƒh‡B›Žä×� ðÙ…c¤û×¢ö’Ä=DÈ´hüà«­ ¨V­éþµ(Žš^γ�©:�ðÔ#}ô1ó:<Ää&x«ì癩ªIÕ:ûI(`×>P”±ãHNy‚íùÏÛugëª'ú±-n›ÃWÙuáj{ÁøÃ3®“ƒö¡£¤EõÞæ‚ÖÞNš—ÍdÂÎ`š­y�5|A@ÈE'�È¡%Š�¢+Š•È¢ÙÐ{"½7}Êôzï2¸à` QŽU;æÓ¨ñOÄ–ZF�Ÿë2õÉÁ‹¬@ÐG³öþ�é[=ŠoÃÍÍÖ§ÐK$ŸGùǺÓÚw ÍN,¶kO”éV+ÁÙŠ5À3CŽ£‘ú%‹Vf©ƒ!öú™•Céqðœ]_oz…Ä͸ÈoecTZo{N·kp^Á$Ë9¿8É-˜¨{ØõJ Qކ'#Ø7f,åFüàô¾6ÏU†\’3ÛSë–¡¢¯æ{ß’¼·/™'fÎbÍ"?Ø]‘é!(zº­§îg~b >/xoOÍg÷§ñÅ•œ5ó&‘Զῳwoy†,ÙÉ”ûË3rãƒôjaF¹žx¢{÷B¾äv$çK&Wò<—×Ñ{Ú“|w¼#JãŠg:ß×{•6}E©Öìš—COÄ34×ãô_7—6mÚ˜«åááä<æRxhMÝæp£ ·)“æ¡ý:¥¦89�J%;Õ+•L¹²}ð: y±Ú~ƤŽD¥™¶Ž‘^™fö½´¶o³­2}‡t%w£*ôM› û¶_´ÕÚôã<ý(˜ Ûì¤È™T`3¿ú•’¹Ø‚5*~ÀšÕkð(f¶çmÇèʲtÚR.Œ3Iv¶§§ÃéÓÆqtp=U†]6½KGGîÁ ÷3½ïß Ó+àÁ ÷S¤yAþwê¼èù=ÞVô÷®�l}¥’ÓÃKK(¤¥Q8>žgò¥‘”¿|Õ€ÃIÎa·¹”‡Ó1µ&=ðÀÃÿ¦ù™>«[R·íãœìW ___fÍšuÍßÉÑýVR«} "ƽIpŸšDtGkÞéýf®¿¼x'õNuã|àp>¯ô$»ó虜̥9ýhØí,‹ �wÒj¬Òб§¿µ^-MVÞÀ%éœx8•g §\ÒLf„´ÂËúN?1þšmºMtAá¶$'²­®Ä$¶©i•ÑÖö}@MËqü“Uµ#P8£”ÚàÐÇÌìáY¥”¶«_›óhÓz\¬”:g•ÅܦZ�óßãÂ`ΰaŒùá?&äM:íø•*‡2¶á+„¬@ßêQ4[“}8@Ù¨êN¯7,_âô:bæ›Ôj_Âíþjíc]®tŠÆ5£ø<~íl’§ì,x‘µ�atZZ‰*‡‘û“OPôä“ š·™&üÅçÝÿ³±60Ìþ|÷hä7�]¥2Â#}`t!óøNÄ/Ï |¼fºì'öèfèΔ‰mJÅCÆ)Ê5h»Ë¶Ù…nl½â‰*ÒåßÀ®­x-¸Ò„«¸2c�Ô€Zûìï��ÌÒŽL y”<¯ßCî笳ƒ’̸Ǡy›]†Ù2´úå™�_ž ©ß™×êÉ�ê‰~ÔLð£ORKJÏK¡ô¼J-J Gì8zÅ|K¯˜o¹¾-§¿Où#&”¥ü‘x*>Í’BÅìÇ)Ò¼ Ýö™ÜTÝöµ¢Hó‚nÏÅFRÛR =zŠ¡GOÙÛüô™ß™4‡ÀO›ÝPœ_#âHJI£m iØí3FDä¾úNBŽsî«‘ü‡–Ÿ{Â�0¡ò¦Þ�¥�=¹X ^“\v4ušÑ=ªÛ7U¡dO?§ìÎvš¼HhŸ}ôÏ5Ö®©ûÈÇsèlAáî"'²­ž×Zï¶‹}�G�‹@’Öz·R*ÐHÂHrT·JO¥Tn«�sÀ k¿'€Ö˜u‘»�P«M$&›k àu«´Nj­o|ÂM¦\hÖÅ”¶¿.´Ûžñ´Äk¹‰õ ù/5bWÒ‹ì�º âÌ’ÌÊ_¸�Aÿýèb‚w:mkЦ}–v3ßtÛÇÑèª4Nõ`@-çKÊôé1œA¼·¯åB›0d‘Ù·=^ ImKe2&«tƒMîÁÆJ�Ÿ9žÈÖ+žøxÍ´‡“zÇ8§âÿ*ð½VúeéoDébY¶ÝLöþ<�<¾ì�®Úð ]šMLðNJTjÈÜ�AÐQö�Ë�–îï"±ÏSüdùä¦Ù&\;QQ_R ü€Éòvˆ&ŵ֫öOJaÂW£1a¯‰õ6©ŽöZëüVigõëè„ÚQJy+¥òÛ R¾¾¾. pÝu³*šLišê^)P4‘ñû“ïã¤y{Û¥Ò½½I³ÊîïÆQïñI=mžˆËùå/ON®M rÊ ªoÌ=«MFØ~ »Ñáü·¼´ï%~o5šîA ù9v$*—Ñ/S¹¼¬¢í¥[{3Röƒ¸oG;R”"E)¾ÛUÉim í¹‡‡ëÖ>‹·§ÆÛ’‘ðöÐæµcqS—–¦ì ¥A?R´')Ú“}�ÅØýúú/1a“éééì,ºŽbÇ»QíxcÞ¸¿ÝÿlÃó¯™óÍMn|}¦¡µ·S˜Z¾7u–?ÊóOQ?éWŸ;�‹—ŒÞbïòöëO¡B¤žô£[¾$êíhAd3£·¸Äj ¿ÿ=¾EñxÅ7)üþ÷üÒÆ„�Öí2Üá:z±1,”«}¨±ä`d<æ?o¿Ž¶�r8�´4Åᘉ¼¸æ7zçÙBØwÕxnë'œ˜¾ÛÈdd.@Šö E{òÁz_Öçy˜ÓE§S4ÝÒæLOãñ¹ó¨³s;©'ýXÿí§l¾[Oš(íT�NªNç`Ù9XöEŠÊG±¡ùL¿>öBzªUÒ²¼waJÛ÷ßSÄ„š�¼ýú“·_¼­¿òŸ?@�ƒç¸øözÖÖZËw»*Ỏéé݈5³ÅÞ:«¤:G{m%�4Ò1uÓ·»áïd¾aMQUj‘žžÎ'O³nݺ¿õ}¶ÕÍX¾—¡UhÓ¦ÍMù?q3lº�úݼùc6T­Æ©S (T(‚çžâÉu3iÒÆ�OŽ™ïVàÿ¾bJÃ7Ù¾âé¤ým&ÌþÈ|×�¼ìôYÝs*ËÿËÁ±…Ö³:)>Ĥ,à“öfàL¥)¯ø&�Ògãëë˳ ÎÜðù‚ ‚àš["Õ¡”ê \b0 s>Ä$ï©c%µA)Õã4ÆU1™Rh­[[õ"Õ!‚ ÜTDªãÚ©ƒHuDªÃ RÿNDªÃ™›îMü”ñ#/Qy@6äJÇóžšÔ»¸˜”Ã�¬(V x‰„Wž·K±h”�ðÔ#äñŽ_ž1Nö–Z”Jš‡fWÝDªm.ÈñºÎkÁlÒ™¥:>9v’Ï.³Ë�ŒòÉH`¬›n¿Æ [m¥ö¢ƒÌ9<ŠBo�¢ð�œ­Xƒë–’›4É¡_gPï±wÙ¹âÏO«V­(µ(Õ.�s<Ì�àGÒüà@zO$"&–ªÉ“xãÙý‹¬À›yKûp3·ýÞ ’×Úo¡³Ç){à>ÊþÉñ˜{ (û'‘§ýö[¼»Ùƒ÷Û×¶ïÓáâ÷lò{�}ìÿ“•3}’5�nËíß»SUÒL+}°šBï~ÃåÑûñ}¡…ì Úæ‚\úíâF^¡Äk¹Ù4{�S½¿¿¿=Óòß¹N>>>$$$€8�ÙòE—‡ÜÖ½⾎7þ#,;V6+’m}c7Ù¼ê�rÿãîï°$2{»nÛ|në²[ú±2WÚ ÛT#Îýºù-S³_ªR³^¤ÛºÆ©7È6%äQ·u½b¾Í~çµ78ˆÑó�lë¯6ᎰÆd[Ÿ]²Áž>›nè˜W£M­.9ÒïÕhÜñ¾[rÜåÏÊ÷ÜÐ~U ¼ác~¶(ÛúóU꺭Ëα¼Ñ|d{ÿˉ°UG©ŽT¥T*Ðh½ö̼ƒÖúÆy Φ߭@ŠC›’êÐZÿe+Ày0R¶Tí™ pÍu—ÞôÂÿx‚§aÒo~9ðžiÊ^Wÿ…§åd-H/@õÓ�9>í<­¾šJÔ¸e`ÂýúþK¹ˆDšøÍdÇ’Ó$çëÈižx°ra¼TZ–bsÞÚ§Ÿ'øP oW^Ä’»ã3çûüÓ‡«p`ä×Loñˆ]6âÍz�ì£MfÂoÏ´�bä7ìNaº2¯‹‹ºÐ2«ìò¿üb>{~3jSj€æÏ4ÞKÿ’V5çëgÍ�ˆßJ¯±Ë6ØÊáÕ-ìú� Ò ˜ëqîY1ŽAG=Éë7™¡ÇÌÇ(ö‰W ¾4Ùžâß+=‰e!æ:ÙlðÐÎŦ§´¦ý–½xç{™]>r’ç°=ÿcÙbÆýð6¥¿À¬Yç8ñÓû¾ZÓ²”?õCZ‘¸ú/*ýþµý|<=µýZ¦§§£­ðÝñ#/ᙜÌ�‘_Ó4EÑh×¼<‡C£¿`çOÉT}±MS ãSùåÀgì:iÖÐúåãVìááa>WÖ{à™®ðÀ3S±¾â6©‡bsmr'IʃN»~³—-�ÇY›oöÔãÐ×ýøåÀgæÙQOOj{�±– @Ýæ°qæP.ŸÅŒ{—;És8¾ß‰«ÿâü[¿ðDcfÿ\œ!øP MËÄ¿·-/v uúÞ}øp—æôcÉ‹Ýiš¢˜bÞ›¢E7áááA2ÉŒÜø µ½Ç0mÄE»M:5…ÐY›íß[Ûs€ 0kÖ9ZÕxœm?&Q¶Å¼óõ3û¥)”Jfz‹G˜6â"ÓŸÚÆ¹V¥ Š�h¿Ž×û?äzÿÇÜ.ýo”„gšâä¡û(Ûb F~�G²Yãû]Ýrà‘Ë^¾™¸�ÚçòñDŒñSv¯{ˆ|±Ï²|ùr¾Níåô½ Š�ÈÑc£©ñÜNðôäÅŽ¡ô8n®o\{/¢Æ-ã¼ßïæebnJö¨jÿßÖ¦M››v�AApÍ­’ê(®”šª”:®”º¤”Z ”ÁdXìë¿UJ�QJ]Â/‡6·�TǺÀ�Ûðv®03k;WœâóþÿeQÿ¬ièm”ž—BŸ¤–l^vžzóö³þW“åõÞû8?ïyÖ_|šÓ%VóWžõÌH6£ù�ÇÚf=¶CFWƒæm¦}Ç/h5±2U­ú°§ƒhñý,F®oËð'©æóå|ÒèÚÕyôÁÉWekÉMç>äéôôÙÄÍz¡3g±2W�]zйÒktš‹í›ªP-¨ e"âí³“ŽØ2zg& cëCøÞ·$›:Vâ|ä'Dú—±·_¯'_•!¬ ü˜ÍÙÛn¿]îâjhô0^ û’çõ{hä7íšö±1Ès²}¤uÃò%,ê¿�ó ÍèSJ—˜ÿöÕEŽú&KÒ�¾Åº™‘ùCP¢¯WYCƒ£W /¸iŒÙvý¹¦>¨j>®íªù|GCoóƒ¿Aàq^ï[�×ûV㡱 ØzÅ“™Vû:ëbˆÔ…¤>›xãçEœ›o’<‡�¥kÐ`f6 bÌ/­¹œïa§:ÿíÔ–©ưzioV/íí —y¦œG_nÞ–Ú¹Óˆˆˆ`ݼ=,ßö3¡¡Æ¦}…©ÑîöÝ[�<¯g…ô=¿€2±M9þÝÊÄ6e†îlϼk“›˜¼ÿ“÷»îëx'áócFò¤Ì!9ß{Ú.)Ó>á…ÿ;Ÿ�MìõÇ“ÿÇØÔþ´,jú*ôõZfM´”9³põD?�zÎi[¢^ÍšLA„œáVHuä¶5€þ@?à>ÀX ”ª¬º¯ƒ�û�˧ñ¶’꘼ÿ—ïdû¦*Œ+8Û©îÝÙg©ºòÚÖSfÖÒ[½´7'‹û35©ƒæm¶o/ñZn6ä}f8QÝ£ÊeÛwrËÀ8ž3tg¾ ¼ÄÔ¤z|x‰µ�a<ò£™Ð}lίÔh÷gÿócSûs~Þót÷6Y9_9ç›U‚Ã"dZ4%{úQ³^$Ó§ÿEÍ?ª®ÌCÈ´h§vc¾@û*/R>Ü8šå�ij¤P1|¼f²¿Y'†—,NÕU ²'´Ï>šªŸ˜ÑÀd,}âÐY6›I�€fNò$ÿ¥FìN2³b+¿­ÎôéÙ50 >š²QÕI.;Ú~-ΜiDëI9ú zfY&ë–OJ_¢D¥†lßT…×ûV£F\S?0è¯�ãÑ�BèúÓlŠÆ5cxÉâ$%˜cþõýwx-ìK‹ïg±aÁJöN]E•'�gH³”xŸ1qs]Ó¦š“d–n±Ñ°ÛgN¥àYó¯ãýã§9UâGÀ„U<4– •'ðHÌ#YúH곉nÑ´�\€|•†s4x ŸÎád¸7c£?³Ë‡ÄÞ_ˆ# M†Ú<¯ßÃ_yÖ36ú3–ÏÚÀ7Ͻ Àèðéwà,Å➱ǖ%vW©ìŠ>Âk�–Q¢’óúŸ#kI?™ŸFqá´k׎�Qƹì±z�–f,£žRöeªÕ6×ä³Ç Q;w>ѯÑ]ÍÁ'ú5”ïÏ¥Ë}31yÿ1ž:ÏУ§]Yþš®ù�ÄÁ•²l;žèôzý¯Oóþ¤m´O8Å=áGzôSºGÚ æeokËHÜÀEÈÛ_yœÇÿW¾°=²Z‘?OïJ®ÿ ‚ ‚�3Ü ©Ž  $f¦ñà¿§ñ<ð°Õƨ ä&¯³¬í!Vÿ·�TÇSâí©ç·-‰Ë"¿}S•«öQ¤yA~¨’uíX§ _eÙÖ¹Ãyr^¼Ñ¼Æxÿ"±ÏgÙ6©�ùÁsdŒ=E~ÜÈ+Ûó‹½Í†+™ÜÄ86}«G—6›¡ýÿäpÐòU”‡Ì»6üÅ…ÁálX¾„!õ;3fêP6,_âôC13¶Ù©…~/˜ìËðþ¤møllB¹è2|òÐõK,(ߟc̬_Én÷0œQäý´9ªH Úwü‚£½Üj5fþœë×=Iÿðyœ.±šu1¥©™�! R£§ö´#ì�1Ԭɔ�G)ô®™aÜW8�V+3Ï7‰2±MÉ©z #Øó1‚+Þχ] ›õ%�—í¶ÙMÍ?¶L½ÄºQÿ¥f‚EšäØC&QAØÓA»o)ûî-H·ˆhºEDsº¨ybžBÌŒñT‰?b/½×šu3}’˜çkJ5ŸïìÅöþ¼Üó>mkH£m 9YÜŸnÑÜëÿ3ËJ…Óo™‘<éó-åYÏ ‚¨ÖÊy† âô÷ÉWe�+½æ´mW�EöYÜ)ݳj“6x¸%�–V¢ß�³DU.@ïµ éÝ™ó‘Ÿ¯È {û c>§ÁÒ¶Nƒ+Žë‡lçÕ(.cæû›`óþj~™)Ý�×õ4<À[egËñÕÔ hÆ<ß$ǾŅÁá4?h¾Gõº>Lƒ‡[=¨ qi³)[f@–s¹“ ]Yžr#~pYçµ°¯ýù—“+3ì¹Z”óI³6 =zŠñkgp—ØÝò�S=¨¸Ò×í€EæÄ=V/"rÔ7ÜÓóæ¿và -AA¸qþq©î×ZçÖZ—ÕZ? $¶Ø¦€Êh­½µÖµÖ}€ÝdÈp\·TGN0fë>'Í2wi^�Ò­»¬‹ ÞIô × š«[èr;˜°Ô/#›fÛÆFïMŸ^µ À€°Ï(üùhûëJ«çÚÃK7U­Î†¼�R#®)Z^fW©øû›™ |•†Û÷3uè5ËÆ�EÛ(—4“rÑæÇ~¹¤™¦Ïl4 Ý‘;¥;±G'Ø÷ßÐn5âšâQü/~ê蟥}éy)N¯mNž# »}–í1 >š­W²,å%Ïë÷ØgRö\-–.]ÊšÕO³fõÓLŸþ—Ý‘³·‰n�e¶¬rç̪èORÂH´iÏ–ã«Ùr|5i{·fö×gb=ž:ÏZò³–ü´N<ÀkuMB‚¥K—Ú‹�ê‰~ÔL0eljz..KÏÅeí³‡bXø1ë?¦Øž_˜|˜RC²^gÍ%M^tzÐΞ´ » ŒUž4Î[RJ7ž8d´/ŸM~€!õ;ÓÓg13Æ3}ú_¤Žª @çrƒ8š´�£ÑU9š´�Ö“â3±všÙõOJ;$…81„ô“ùí3�®B^ú½@ÜŒ‹xÞW‡1S‡Rz^ sÛí§ñ w è(›f/àÞ}{¹wß^TëOþu™è'Ï#~�ëü*>ëÙŸÏYøC�žÊÒÆ†^?ÏlF‡ÿŽ@…#N޼´–a㥮€qìm<¸ã Iõ×þ­óAáú¹ñÜÊÙ`IuÔ깨ދIŽ3B)ÕÒúÆa,iµ)\ÑZ'dÚ÷¤Ugk“õŠÙVÂÅvwÙVÉ.۪㣫:ÒÒP¹œ³—NÜS‘€âQžÛ'/:ERÂ^êµ ¤T´É@•æ¡Yt.…Ï‚òÖ¢$TšÑ*Û£/7éI¯€ª®jऩhc|Åþ ©_ƒOöïpYFxÛ–�ÔÇ{?ŽÂ�u§ÓÁ3äRŒ�ÚaÏä:ró|{Þ™Îg}›%F·ò"D.,Gp�xþ°t'mÔí28LÌýðÀƒÃW ~ù=Ɔc Š•ÀãØÞ0_‹}cÆ;½½‰®Qƒ6§_°''"Dô1?òSg&;mæ«x*"êš÷!Åñ»“n¤gèV:²5y€••ÓÔ�üñ2%ºgÌÚÚ>ÿéGLñðÀsS+’ê§7¶½ËJ.'ÿô<ô^3ʉgâBó9JsóyêY¢7+ø†FI)¦¬ëÄî§—Ð/­DÂÞ&IäJK#dZ4ÇÊžÂ�Txõq¢ß~‹|wSrÁ¼» Œo~@ø‚}x?A�”‰|X7�7×Ìò¿"»ÿ!W«¿Ñºœê÷äÚûµuu�Ë?‘ÀAëÑGgýÞÙ> )§ñBÜd*ï¿Ì'ÕbkÌdRôcÅeìßÝ�“+ãS¨6ßÉë?¤ücÝ ŒIà�wÆ¿ïÍW2ôoä|ÜÕûøø�””ä²½ ‚ ÜíÜ©KvcfÝc æ×¢ÖúA¥T7ŒV£w¦¾WµÖýoPªc8¢ó(‚ ¸At ÙIqt |CÇ-íã^Sîo‘)²!3+=~v[·÷ç 7|XÛ:~W\Mª£t�ûtú¶Hœ›�«/ûÜp¿ÙI‹LŸîþ«4¤¾{M:[½Á–ïàFh<Ƚ”GúI÷‰Ow}¯†-ÑØ�pé˽nëjµw9ŸrM\Î÷°Ûº€³N \+÷îsoï­bíÄÑWoäÛÚ}wT\ézðòZÈîÿ@vúœ7�lï91óè(ÕaÛæ 4SJ½xk­·5•R€ÜZëÓJ©�§Œ Gn¥T¡L³�ÅÈȤzÝRÀÌÚHù€£½{÷¾a�ÇtÿZœø)c�c³5¯³æ�/¹Èñ0?»Dƒ#ÊSr‘7ó–╹÷Ѵ{»9QQAœ­XƒF¹æÒ}à žob„yUš¢Xd…,z‹[²-nê?Há;xw³ƒº×ä“i;èj>ž]»v1¸~ßNÝãd³í3ófÞR )e¾�ÇÚXº¯ý:¹Òy$-�Âv¸|o"ÚÑüxƒ ¶kZ:bÓÊlí»/eÍ2EL ¸‰¹Y/.±š¨¨ >Mü”›>dr½Á¼<Ÿ|—[Ûߟ<~ÃQ*ï#OÓ ÕƒæÞ?2¸à`އù1©ö"bb�Žk›å³éG½'cÖv®­Oűì¾t¸ø=‡Êøóg|>Šz”Õ±§YQí'mPÛKÛ{w<Ì�O-Ì©ÙAmßõO?eû ÛÍû\‰„WÌ þæðU”¬;Õ6$´çfºíkÅï§Èã;‘‡~øŽ×yüjׇôšñ [måL‘ÙöÏxÏUy è(á¿Ò|]<5–“Àaüýýéq%c0.|å�KÁkàŠEVpÒ÷ü_ù¶7ÏèÐúnátx+'›l:§‡~�ÁØÒϰû� LìÇ€Ú•sä:ùøÜøhAAø·óOHuÔÄ8…3°¤:l µÖç,Ç1“ g¾Uµ£éh_ô¦”* T%Ãy´IuÜïЦ>ÙHuä„Îã�%;Ñ© �ª¨ÝÃ�ð†#í£NSö:§bÕ¿2÷œÓvï|/³ê»j¤øÌaÖ¬sÌ‹eׂa־߾6ÉiŠysþÃk}#É»ž’Í’h”ËdéÜÒl#xz²¯Å:ÆçỦ�ðZ¢I`r…+$“ÌS¯¥q°B/Ê•íÃÇî˱ððda¾—yÑó{þ ¹hלô]Z@ÓŹõÆAÒ©)4èò5žÛi×msÔo³×ñÏæ°¸Ô�LSôOÈc·é…äÓvÝF[8·þ<'G�gµç,ö&{ÑgÅç\ÈcÖz¥'ñõÇY´]éÚʹþÏÚÏG§¦à�ïeÞKÿ’Þ—Zð^ú—40?vçΣU�Çé}©Ú3Ý*Ʀ{Öúà�'e~KãrÒÓì+ZŽúafdµþŸ»iâ7“=5–â?¸ ¯5kƒw¾—ñ(lœ•Ý7³7Ù‹µË/’/öY¼î­MX˜ÑMOO盉۳\_›–f÷u‹‰ùi1?MbÇøò,|o%K?2ëU>ÄÙa/pvØ TÞwžÊûÎÛõH]½7^*�ð³®ñJ™o¹RîK¼î­ÍÊ#§¨W²)e˜Ðk/•Ž—Jcz¹ñJObÉ‹ÝZ~%ÏlšÀ§i=ЩŠÍÑQtZ~/J%£”¥óË3†µkºÐôÈPš¦(¼´&<ÙdM}ºmQÖ›BZ½P¦?µ�´z¡¬n’×h/5ÁOß0™¶S*áé©)s¸÷V¨Ç ɧ Ìõ8 )éËèÊ£9}ÙdíuyŒ]T{¦S!æ;ο¿Æ|×ÏŒ!É…^#À‚ÿgÝê«©”l–„‡ÖöÏø¼C_Q!æ;V.�RÉtû-�Ù?ÏöÈí®óxiÔ—mïv†ã>¯�§ÄP¶ÌûLã¡æ—)vßRæ6îŤ6‰92† ÞOeÙ·H‘õ4XÚ–Ñãdï~ Cwutø�´Œzƒê½Í¿Œ6»£9â:Ëo÷VÁöç›—�'¥ƒëP·ü—á—g{›—açä4ñóMg]®t·á€!«PÚ§sÛíg}‰æöíõO®cxÉâ,®Q�1qsYöEM§ýÆÏn~æLÖÜf›:Vâü¼ç)Ýj1I}6±õ!ó‹hG¿gi\ö(í;~�ïƒÕÿþ ‚ ‚p]äT¶Õ=˜ä7¶²ÉÚž“H' H c2£z™ùM¶JFßqð°ÃÌåLÌz�˜Ì¬m€‚ÀÓ9p>Y°‰™ƒù±sd {žÀ…ÁáL®gÖ‚ø¾TÉž†~@‰N (щÓ%VÓ÷ÑßçQ6\IjŒÿ‘þ¹Æòjb/û¶q=ß!ï§Í³´㼀щ,ÙÓU¤…Ûóþ#Õ|¾ãÄL^œVÄžæ¯<ë�$(l¨"-²¤ÑßTµ:åBÍìÙcù¯dÙ'9å ¾ ¼”e;˜¤<Ùqyô~·ë%ŠÄ>Ï�òýé“Ô’oƒ^`мÍTMžd¯ß±ô{ê¶ÍÀØæù¼öÎã $çˆçG?ÊÈ*ê—g•Ïš0ÊMU«Ó®];®xÍÀ;–~ïôÚ&B¿~Ý“ièO›Z]H?™ŸS{ÚQùÏDŠž6YD'ì fû¦*Lšš¢qÍ8µ§Áûøvê’FrjO;z…ÍçøÔ=|9a—]#ïüÂ�”Úͤù§Ù¶$ŽmKâh³Ûèl¾Ü¼->…^£a·Ïìu6¦ÞÀ®è#N%_•!ŒÙh2ÅÎ9<ŠÙÑŸÚ³»ÆïäH×kuÊÄ6¥JüûuìÒù/9; ޾ï²�k%æÈbŽŒÉ²}¢îaÞ$6„á ¢}Â)¶4°Ë¢¼ÔüTÿyäáÃÜåìàº"*êKÎÏ{žsS§°}Sf¶1çú|÷á \ÚÕOÓ`Ç6|ެ%ïæ;áJñN lTuJxvµ¿v ˆüÉ„·»ZçãnðâBåZÔ;Õ €1r}[zÄŽ`öìóä¿Ôˆ*ƒ²þâÓ,û¢¦ý»ÀÚïXñif6 Ì{!‚ Â?GN9�©Zë8‡ÒXkýª¦ÚZkÝKk]Rkí…‘ç(IÖ ©çµÖÏi­ k­óX’±`×xlôÒZ·ÓZû��R¸^yÓix@ÀÚïhª~¢J|Æ ²ÍY³�¿ÁÃ-ÙûóZF½AeBZih´Ë%Í„C ÂV53»Þ^ÿcI¡b”óIã“�s\&MØ>® {žÀ�òýi¹úŒ}û€0g§exIç Þ+�Ûå`F‡ÿ@±…W—}ضí#ZM¬Ì꥽i°´-v-lŸ¹Ý·Ž¦ê'zôÈÏ�–—¡Âz2K¶7Í^pÕþoWâöGØŸ»”h±°½w6mkÈØ†¯�”`–™x?˜ò×-c{¡‹ìXú=ç#?±;ô�]zq$p ‘£¾¡Q\8jí£þI9±ÐïÖ׊àdqN÷gݼ=¬ÑOÞÔsA!{rDªVJ’��À[ZëCnÚ4f&Ò‘îJ©˜ä7K€÷µÖ¶_ÿ6�Ç�¶ÆZë J)›Æ£Ë°Õ›)ÕqåÀ0¨›Ž·‡fM™DV]‚J3¾xd•‹¬@Çò¯â™¢ b,¸Ô¥P|kq–ò3S™{£É^•�´EnrÛ�åt~™d>lÔ=¶ÂißÌ ¨?—˜ô…@ŸG=Á,ÿOØýúôÓHüæiZy{ \¬³”­Ç6BøT4!²6i‰Ì´)I¿¤�˜Uq%DfHvx;\~G›êúL"b},oõ-B¯C _Õ†­–ÜÃÄ… ÓZ»)Úº5'¼ÖàÿøPˆ‚��æzñVߟ¸wwjz¥‘»'—.ÇÛCóДJ@%>y¦2?�y ý®{›¶ÙìÍ,õa{m{t|¯¿cb!p–øH'kF5›4EæÏ„ã6Ûcßbݨœlf"ß^ÁéY…(½v ƒàí€vJów’jxß&¶ªÌ� —Èýônº®YÌŠb°ÍwÓË äËÉ•é”ÅHÕçè_écfíÿ„”Æ/óZ`?øVfðü³âú»¾|ùrâããybzE|}})ReÅŽÏg}›Æ¼9æ}óYy¬;›ÃWñ`ã mΔõ“í ~ºþÒ„7Q6㟒ê¨ôM[f„F1èÒ1–• çÐÀŸàí·H·$3~íü�.¶à¼ï Ú†5bEµo2¤[>å…MÁ”|åZ'ž¡ùé ÆÈ|ÄΚH,ðÔS˜z¬dÆw1ͼÿ;Îneô3#'N�·ß¢hz4õ ßÅÇ9ﻂê^)´ªQQ¤:Aá$'¤:ÚcÂS÷cfßîîÓZÇgjë¬öj�k¦”ê DcÖ5VÅdI= µnmÕ¿ôÔZWÊÔß~ŒÄÇ7¶ G¤:A7ÜRJ©fÀ0ÙËK�´Öóê§ÏfÚm£ÖºÁu#[©Ž±ÑÙ¯™®×ôZu]4XÚöÆv¼ŠTÇ�rEo¬_àÔ÷köÿµsߘ4Ã=á®óØÕ±®Ûºö ®ä±¯�£Á+nx_wkÄf?ÙÕmÝÕhÚìê‘U®è®æd[ÿîì³ÙÖ»cÀã7~�¾œ°ËmÝÔcn¸ß]IÙ7¦„<šm½;ÞŸ´-ÛúÁ‡ÝÖ5ésc‘wW#vð�K»LôYyCû]KîwMZ˜m}LðÎê÷&\ßVªCkí(¬³K)ÄÜèì2J)/à'ÌÙ‹™úpü/³ÛÒnܬ”ª­µÞjkæâðÊÍv7Mª#pÁj>»pÌ.3ñzïŒÉÎϱârªm.ˆg& Š6üÅ«|\Jy4[ó:Ï¿–‡Ág+mqªÊA{fO§“w�а՗?<‰!GóØ÷ÍÜïÛÁóé°{÷T$ ä"­OÅáuâ=öÍ)io“îíMôÛoÑÚw ë=a@ØgüöLm�5Ç[§»•½ìõÇÃü¸|f *×¢Z«çø(êQ~¬×ƒ¨¨ އùÑÇ¿;J£ÏÁ2¼Þ{©£jÒä�·ˆ~û-‚ƒ£ þe”xŸ ;Mқ܉+9Ǻ]óp’²&‰N‰÷I.Ý�T�Nxꂃ£‰ß�›Až“™Ñ ;)]~ Ï#_³«n"g+Ö à9/ûPûµÈ$[²»xFHŸãõï¹*‹7&ð�üà«­ ìTËú�:�t8l—¯Èã;€K—û0€ퟵ-^}XØj+Á/dh°ÙÞWŸ‹ èéN²±¥–™JK!Ý¿­ÿ2á•3+‡òqL –• ·K_TÛ\�cA{øý¨ }­î£ök˜Ço8¬?îtÌäºËiq„ë¦Óz×@¦?5�ñkgKÕäIv –ÌŸSÀ.ãa“ª‰�¯OõD?ꬋ哇j1lÉV>¬›nÿ¼ ÈøAØ|Æ!vÕM´Û»iå ^nn~ˆ�¹—¢¾O³¢Ú74ÏU†š¾Ú.ÇñßNméÝí=ÂS�dù®2„ÀÎÏPøÀ‚>ú˜­?'*¤?;'Wff›Xԟ˨Ûù°n:»ví¢ØS¾Œ_käa¾*8Åþ¾¢ížÊ˜•´q'HuŒÙjþ·U\¿À.Ѳk×.J=þ¬}mk¯Egù¤}u<â·ÑÚw _lÈEH�NÔÏ3›…~V»5C~ªÊA¾ø¡ݽ·ÒªU+º%ù0!ý9.]ΛyK‘ïh»¬K`龉ý‘K‡g‘ø¸Áåyxj´gÖ�K[¨ªcý £žŽ§œ¥ßz¿bu›ÏéÃXLI¼´Æ«øp6ýò( néÔûì2}iš« çZ•æñ Cù©rC.\ìÍEFØe/Üá9pA§ÇRlÏ/œòÈÅÛ•±/16S¢kzìä½}­È] ªîžÍ¶Ô8þz{"pˆ³Q-ÙT£ Ý"¢Yúad$IÉ| �®SšBGncPTƒ˜ ‡€CG™ÞЈ®®ú®:UñTB fEŸ£Æ¸e=y H¤âÆÅ4-Ëú‹&ÿR™ˆxV†vËxrÞQ¢Zndñ¥z¬oèÇóÓ>À–jQ¯ýœ:Õ€Ó§OÓ3æ,^ë¾¥\ÒLf@iA½“«Y\Æl~œ^¥ZÃÚöÌm·Ÿs­4/$ŸÆ§E)8K²òàðêPò¨p„)e_¶Ô¯[˜"ƒ¿åµÑ™›·�–Vâ—M¹¿­Idu²â*þ3ª/]¾Ì°aÃhP« OÔeÖ¬YY¾ëÆ cÌ©ÆÐ.|5Š § ²vMºn™Mû-móæËœ¥Í óâÚŒDI;–œFåÒ”j ñE‹d+‘�DÄ�ÖÝŒ~ÿ¬|Aщö5§¶OWHH‰ÇÀ Ç^Ôk?»N™,µ^*�÷]fnÞbl¡ǧî£NÏ<œÀ$,ÒžšÜ•†òĈT‚êuâ?óO£ž0ŸÍ×Ë…£+teÇÔ @à�¨QÃÈòoRˆYý¹â5ƒ‹üŽÀüÉk‰ç—/_æÍ7ßdxÔF’æltÊÆz=×éfGãÜÖ ìÀåÀ¦E²Ö:Î]¥ ‚ ä7=aŽRj¸M^ÃAf#8aÕ{‹�–çñ°RjƒRªŒCÞJ©o•Rg”R1ÙY½l}�¡ñ¸X)uÎ*‹ÉFãñfryñNŠì5’”•;Ÿà«ÀKÔˆkʳ‘íþ#ñë\GÙ²~º#õ“:\}õl�7“Ò­Ó2½K–ígßyž*Og•v1ôÀcm‰92†y¾Iì*Õ�š ~öä'Ž3@ŽÌô1ŽùÇ1&ãkÿ\®å?úG|Mÿˆ¯í™@m‰bì}5 "áÓìCM2Ó=ª\–m=ò¥8½ö)ôEãšq|êû1wm­Ì˜_ŒÌh¡Á�)´›³ïÂs�¥R§—8]ü§ìßÐÛ�Ë‹w²zio»ãØ?×Xû5¬·Û9”Æ–Ì `z`?ææ}•ö §H?™Ÿµö±nÔépÑ8Ö¶™ÙÙOvÅçÈZjÖ‹äP¹çìûÛ’e&à£äôÔ�xž�ldžv˘Ûn?Iy3Þ¿±Í;þŸ½3�Ë)ïÿÿó\•R2Q*­¢$4Ù©¥0c¬3Œ0²Û=˜{0›e6faÙ—Æ’eÆ2Æ’JD)BˆIŠV)‘6Õu~œë:•3fÜßûûý]/�ó(çsöåêz>ï÷ëÉvÏ!²›ëÿqõá� ·AØ,‚i} «þ®6VO¨jþ5ÒH#�4Òè¯èU¸­ú Õ+vÞ@Bi? ‚ �8z"¥â G "×€ä""B+àR·ó¿€ªm#�ˆ¢ä}‘¸’óU¿gÿ'�þ‹äߣ­ãu6«ÃeDêå�û=§ÔÀ¬oØœÿ¤ªŽJ½PYÕKAç{¾Éö1ñôöRj»¶Z›‰ÉyN)ö“áÂ�iËMøµÖÍú–T¦€9›ì¦ ~9fÙyÌ=|‰didë«Ä!´ñëó§õïÔ€ä¶ ðºw½ßµÉítä"mœõÖðF*Gl�઻«ÃeØDåÑõ°ð«K9M¯ÅY⇰c´9·XW¢?’ù…F©× ŽÝO�S—j9úÑþßqu£¿}ÆÃO§2;y=‰IØü[_¦GýDXžéìfBØoŒ={ €®£ÞÂþTCœæÎaàÐïùfC[:L‹ç£ƒR*«›û^v!OÞÖÕ; ÎM”§cÓ£«µ%zNÇ%«‹2ðïü†Õð2Ûz¢KC†7~†…_;¹ §YvÝáùt¹Lþ¿`ÒÁ¤o $„×ìëŒh鎙G)I-§óAº>bîéj˸ººr-h=ǦG3"¹ö` ‡Ï@~i)18-�•sÞ܃®|y°�G+àôn&“ïHý^kÂOÒ,«7=|r¾cWžiá)•y«;þ·Èzî~üá|íMõ³¡|:³¥¾¹5á'Qf7®¶Ža±ÔÙbôPÁ° Öt}àK±á[l÷B‡iñ”–½SmùQ{÷¿j5M¿«ü|™p÷#’GÔ 5xŸä¹#É Ï§ë_LL¤c)hXùy�íÙ‘‰­-Ùì¸1dá?p%þkuƒô·t>ÐS™ÂÕ¥EH5Žê)ãU¤Fi¤‘Fÿ÷ô*‚ÇÆ€5 l €î¢(¦V@?Õ~ý�Ë À.*ÓZu�.H¦;Û€€=ªùž £:L€3H5Œ+�pÀL„WNåþpÿ±jÿ�ßkAÁá©XÌh@٠͘ϓFwlî´§dr ]ú²/y™7ÿ‘ý«{î_¤�C¿¯·}žÛIùwW[ëí6é½jåèÕ6ÏòŽ'ÿÞð)‡V¦Wý$ÄAŠž/Á±ûÙ;Ô˜&Ÿ­$øwÕTÔ©¤ìŸUHt4bæÛ!”Çö1ñ4ýrçÅ·«-“1T§ÎâíúFoëÒ”×™Ù1�™?€›ž”ZI�á Õd$·'#¹=¡“�œÀ¹³•¸T�;sÈpáj“B®6)D«]çZ÷á’Õ‹û…¤ô‹àF)ˆT×nnX‚þü6x•WO™�°#Â:¬Æ¶ìÏÿŽãùLû� ©khîgÀ±éÑüÑ^}ªDÖ¦ÝârsÝ?€Cî0ó(•™‹cú9zª%%]iJŸ¥KëæMÚ&¾.3 ŽÎÁ7*™ÍŽ›119�i» Nõ6áÈ{‰ÌökÇÌ·Cjåªv-<^íÿQQϧ<ÿçtä÷ü÷â†ulHþ–ÄÄX;플#ñ÷üˆ–s·Ò©A�T0]{Óµ7ÈŸ³Ïãf°“) S˜’0…»«&Ɉ”NÇî°„¹rŸ€cpNyiè%Ï#6KBn|âð[µã94QÎLh¦¶c¦²ÇÞ˜Q{÷U[®Ó±;„õñÇJo96;±OÉ©q^fa±ŒÊbJÂ6Ú7ýg/Ü‘DQÜ'Šâ1Qoˆ¢ø;0h ¼YÏjß eç¨'«W¤i¤‘Fý_Ó«E*ÜÏUý,ÔQDðør€‹HNrgT˘#ߨˆ¢¨+Š¢½(Š“�H¨Du E±±j€Ô›Z'˜¯®´===6lXëÔ:_TÕ¡t¶Ê¢BW·Ú¤¶±òoJFìàBèºû~ƒGÔ<<¢æ¡3çšt÷¯*Úbsø>„ ÅsS%*B¨P`â´¸ÖuÕóº¾ýe¢e¢g›¿@™ P&D˜÷Duå $3“Ȉñò¼”Ô-2þ \T2müW4ÿý¡ø‘^þ+MšDUÇbø4§EïÓ( ‚ƒƒ¹÷ëÏ´W %Ï"�¨yX¼çÈjã"¦vºÍ†&E€d&£¤Bž.=«Ž¯xþVmÓ­mR!-Ví¾J‚µ)†_÷¥wÒ¿1ùãvÉRšgÏ‹3~°1ú8Ôy*´Äê“j»{ìO¡T*kL ÂcTTÔ>©ÚÕ“��OµíÌXKŠ«WJgÒ¯ã'^cB™§ŒéÒχnýß`Úø¯(l× ×},cÔ÷µLdl…úÞU(*ÏÁ6p¶�+øÄÂ\¾Ž[nÚÓ/­�¦MG©TÒhÚt™a–Ô‡‰‰�1Íôå¾ÒŸ)ÒŸEÙɯ°h&í6$88Ï€$âŒÂÏ% €Z؆éS2b(ËU“t^Ûð3sòߣ¬å=éϪñŒOjTJèµ_‰Þÿ WCîÓU?�I�¤º¹ì[ž˜Í5¤sÁQöØa¸‚såïÐæF?.]úûSÛùè©·Ý×û®çŸ˜MYŠ0=\ö3»\m(Ï6àÈâs¤¤nAuIK—Ò,Ë.ìä�þ,º^ëË„‹w¡Y>gÜ,0w½¼Ís‡¯Ó¹à(Ï–šÖûówÚjk�{Ý…ƒ‘ÄíhÀذF´pèÎÄßc›ü�AÆÚü‘#�øè‘oôŒ˜ûç(•5žÿ2…eç·q%õ³{úÑ­ÿLÏØ!?§�¯qÿ¬É­ãhÿ°!JAàÆ5É{añ}®”Δ?/݇ìæÈBi´þ„Â�âŸâ)þ)ýùĽœUœÎ+[0µÓmÊ,VnqškSîïº�£Ñ/Ò;™6›õºÍÈi¶‹£·ý™¸ãû—ºNua=þ›%Šâ} •*Þµ,S*Šâõ„Ô±«‘Fi¤‘FIÿQT•u‹Eªù§�À×@_QÃAðEÂmè>·Ý` YÅiT‡Fi¤‘F¯Bÿ ¨ŽªRùTCuÔ²Œ1p˜*ŠbíE£5×Ñ :þ¤4¨ITÇ‹¥AuTJƒêx±4¨•^€êP»Fü&Š¢új_Á ˜Ž”zZ—žÇpü� :L�Fvº€¸.ùØ}õ5ŠÒꮨjtBmmUÛ?jdÉö†’»ºå±r,úã‘öBT‡ºmjÏÑÕO¾ *Âõ�øÍÓ®s]�ÒEÕ*„wfEþ ¾wøž¢âÉr›Ÿ o°„)Š­ÌÙú=fþ›¢âÉ$[OÆ4¾U5}€özÒŪ(�ÊÃD§[�äöž;“etBZp#$—ÞçæÈ067#ÄÔœ&´@ñÜ y{írÂËÓxàt“ÜQܼ%·©v_}�îÎw9¶ì,>7’qqŸ»£b–Ô‡ðò4š<ÌÄÌ­„–i•øQõuðnxAÉ]cÆDï®õ:î¹½¼Ú1U(Dâºäãý ‹»k%s�ƒ¾dù›e€úg—ÌÓ¡Æ3¡ÆfxhÛÐýü=nô‰&\qÝÔY2ÁÁ!™ ¯j¾"‚¶-‡�Á»áeN6šV­M½_õù¸fq¦™„ÅPã6ª>ƒSŽãî’©òuŒÞ#™*•¯êÀ¶® ø±Õ�äååɨšåov$.t+óº A÷’G=cPäÅVÀD뎮ö<)�;Ò,ï1bÞ9bN…ðæç½ÉÉéJ^^Þ²èÝ`³œâ˜ì?‹FËzÊÏøC{Ù5vìþ.\p •±jŒ Àëù:Jõ¤êu½ãæÐÖí�z%Dûǧ‡NÖ‰tø~âïø '¤¸3ÆÆÆ\ß[Šs§z”+ØîUÈ„S¬� £åð1Ü=¸›ŸÆKuvº§29pôSòý×’——Çĉ±[iÇªŠ‰ÕîÏ­ž%˜Æ·z¥¨Žo.~Ì„ÝR P•²"ˬòÑ{ºE³¢ý¿£K?5µ  -0kFÿ{r]s^äc Ç\!1Ñ}ƒ%ØX¿Gz†ô…ðú6Gù9Ô7X¬Ó_p£O4¥eï­;º.Hý¹xþ ”íQ®àø×Q´>mö· “kDo˜EQ±Ú ÏB>»¤ ðÈgZͽ7µ±%ò·1Dþ6†Çý¬Èo&°ïm©Óaî˜RÐSerL(Àÿ’Tóøø|ë¼þ…Î]Kú%ÍF!Šªì€Ìû›h²Ä»ÙØî“ÄÀË·˜×Û±¼L>fÚÕ¦ ñ� óU¤6+¬aßP¤Qƒ½48 ŸËL·#Ìt;ÂçfÍøÜ¬®9c«=Çêg¬ùØö|âxŒ?š_àI_KJ+>~Ⱥ^ƒÉj}ŠŠ®¡rÀØéD ×îGpí~mB6Ò0e&®ÏÈ7èiH/›t¶û$ñvô6>¸ó�t.¢È„¨oÉÜ‘@×#wq›½€÷nûP¶ÚEz™«\¿]£cUÏ…ô¬uxbH§Ç†ˆñ±ü|$�×sÜic ®ïH2ÊNGHQþ‚[Ñïèήó]Ýs‰æcÛWäy¯ÑÁ»9:m;¡“¹€©;—¡“¹@¾þby¡6gð;{„›tÏ™B³f1òv×:®åâÉã˜|»†Ü¶#¸xò8_eô©±ßú>þl[Õö‚�OsÔç:g{6¢AëEü2VJÞÞãZØN&ÇÌ”Ø%t÷쇢©ôÇõŽcSlïö#Ôæ �Òϳ»m0-ãPŽ–î]óù"÷ÖÊŸózû0ËÝSzž[yÜÏ kí·ùÅ¡s3´(¥TúlQ½—Z¥¥ôzs ™; °½Û� Vê¨ûù{|½¹ Ó§¡P(x=Ç-U*ÿôGúd¯*àh^9nE¿“ä5�¼[ýI·•þT–àñœ)ÓøØyF'çsyǯú:=?ïX]€XÕR§g,° É\Îø )ÃçgÕOWQ5©¨i¤‘F½R½òàQåþæÜEñ<0ß!»¢~tð~z1Dêezøaô¶tåræY®–I-ºïàn›�{¹‚ž<ÁÌ£Tv/©×¾EhOâJfp"~�<_�ìPËêp¹áù¼ùšÔlþw&¤®aQƼ·Þ!8v?÷Hùåp5Æ €OÃ7*YéÒÕùEÞ®þü6u^;·¬pæ¾ÄÂcÕSs”Ù�iQˆ¹G8%Ý#è1óß¼ùyo|:Žä�ËpÜËœZ#±ƒ÷\Àêp-çnåÑŠ\ ZOŸ�2zäUjÈWž<0ßÁ¹³ã‘<½’áQ^^wìO5¤9ÉtöÓ¯fôS¦³=�@Ì[»²8Ù›¦ß­Egÿx׫Öëé[»OY/a/1¦�rŸÀæß¤íGY ÀnÕ~œÖeƒÇPZèUPêU}´© ~97/sÍüœìbüÓkÅŒÐÿël´oÊáîCÛѱã'¤[ž”{¬³=;Òð�×™Ùé•û§½‰¢xFE¡–ÉOÅbQû‹¢h*ŠbQmUóÓÿ§�[#�4ÒH£ÿûzœÇóÏ1K ¤ÞQ€M€¯ ù‚  ‚ðð°@ÅÇHF:ûªl#x‚*1E…ê‚Ax*ÂS z•¨ŽÅ‹k”Kb¡ûNµÄpè&ò—I).ê/ZuI�ª¨M÷ÎÃO§’c~–%Ì%߸‚€•R ºÜªèe¿šRúE¼p§µC«åïŸ�|·F0iô¹ô…¼läÏœ8ü!ÓN6"2ÕŠ6~}èðÈÝTéü+B÷SÎþoÊIøP•ëí:En›ð%Ü_ÈoùÕƒôªªÊTT딃”δÇÞ¸ÚüèßO±¤g)Ͳz¿ð|ÿŠnZCûSúlÑ;Å7V¦ÄÏ:,»—fí.”Y‹7¼ŠðÞzGX—fæ°(£25¨aÁïºÚáaØŸÚ¯^ò<ž.Ç4ë=œæÎálÐD9(ú³j3r KîgóM–Šiº•[6Üò°!Æ4�ÓÀ:ñ·Î`šõžüÿŸ^+ÆÛºW7:áÓq$¿å—ÉA›¬)(L±K«‘ödÚ.ˆ­>s8åð=CuˆJ•¾ÿº ¯ž&–æj̱éÑ\ySJ}Š9ÂùÂq|¶ï!¿å—±¤¹#ZÌÀsg2“K¼b¤Ã£šb®5Š’þ–²KqU”ÊÄÖ–u^'aú ö5& Iqqÿç¸ì¤Øð-B''pàn$ziTܺ @έ~ò99›ì†Vir°{qÿçd Õ¡9É(Ìž`‰èàµî÷Ÿ–ƒÃ|¦%UÖï¨y‰ÑþßñCXeGŒ[V8Ýõ%‡Ó§ŸG@Ä:ô¶tåÒÉôNÞcàÐï¼Còcétì%v+iXð;ªÔ;{¹/�oõ¶teëo9œü¾ƒ<ÿ¨ÁûœhbÊ¡FÔz¼‹'u$¥_ÑÁ'?@î Z8ÎEvæ]¾ƒlÏŽÕÖûß(j¤‘Fi¤Ñÿ&½*TG9Òèap è%Šbª N’�‚A)õ!•é9 ÕHÞ!ñCwQ«V— ÏM¯T£²8`W³Àº´K0¶c¦â´v( å[‹yR-H^^÷¿½_ÃøK¸ x£<-Gܤöû…4(ÃÚðÕ¾¾ ©Ñ#£FÊ#5ŽÁ<Ø^cYg½uL×ÞÀ£ÒµÊmóâBìí-›Ô0;°ðk'cÔŠ=‘H5zO«ÛøºÚÑã-/ŒV¬·ÛÎF~–47çãÈÏŸ‘öÜ«Üð*¢¹ŸÏtvs6h¢\[×(!–ß> £õÙCŒ�€•Þ †8ÀIËp²Â=pü#ŸÔÝ•õ Q¥“kÝOÕ£Ôv-·{ã�kw0Ë΃ûÒè²zdfMÀM̲ó¸»j¦7¥ëáÛòàÕ2þ‹ðX„{éa&—xÉh‘ñ¥}¸œy¶Ú4¹Ä‹¸+Ò—ñônMHs5æ·ü2¹Ñ¸ÈMfú-'¥‘öðˆ˜{š6#§°Áõ_ä4“F/¸„âŸuˆÂŽáëHüªÕŒÝ/Õ㸠¿’šæ�—r$©&¯ªfš£'OÈ63fq²·¼ß©c–ÐdÁ<ÏÌdfG©¿¨à讼ي�I­(™C„u»ÅL?,o3.t+I-šÕ[«¤V¼±�Ìm}}b§û°ßµ÷ô�áþBãšÆ!¹¹n´í)³^Ÿ:v¬±ÌRÑ�*ßÃçy‹AAAòïÛ[J) O;²ÑïS¬ôÑ©f¾¤¿%[»ÙRꥉMTS¦Ð¡k|µmîGà•ø!û’WÔÝÚIþ=¥¤z­ÍewkF'åa®5Šï¦)�~¤ïߎý©†äæºcˆFi¤‘FiôŸ×«BuÜT¥Ó4Eq�(ŠêÜ= 0\EQõ�jµ€ªî/J TŦ¢(ê‹¢è#Š¢œ¨âLgIs39ø8ÔèÖ†ïÀ£\º�-S¶Ö¹  Öó¨OŽß¤ÑÅEp/=LȤVÜÇŽ˜}¿È£_jW/·¬p–_8@³œÊÀvàÐï1º‰�C¿çlP¥qˆÛð�ÄÏ:\mŸÍ©LÅì £¤É‚h/¼ŒÂì Î&»ù B�ã,6|‹Ìuψ •ÎùÎkÕÚ·{!¡­󺼜³ØŸ•^“Y¸ú~‹`Ò—+Ï´8ß1J�ιÕ�ó¥âêª�lL{)½ñhû· Uû?ecÿ§¼qíãZô$ú„Ô5ô]¿‡„¶F´( Ä4ë=ydFÍÞ x#wÛ :Í`¬¡4‚[ÒhC¾òä»é_’ûœQÀ�”UDŸ¨¨îK^�×lÉåk¬aÃ?£ÿ‡W#`Œp€É‚„41z¨ÀÌ£”»•„¤ç`1£q%3d£fmBéÿáU¾›þ%_ŒjJßõ•ŒÒ1Ñ»ù ]Ÿ�öM¹òL‹ËEüë±42³ïw:ûéãlgÃâI¹´žK' p“F°ó"kšDŽô%sK²1ÊÓ".w Ùáº4,ø�%ÍÍdƒ(ç~“è·ÅߨºÓ}Õ�”G�ÒÚ¹ª‡Üfû˜xº_“F/îÿûS é™îIPP³“×sJ¹ßÖs�¼ÞÐb=:L“‚-5¯òËa/é0ù'5´¸¦ ÌêIŸ¡×dKš›olÃ’ûÙrG€W¹Ic¥l‹½C�I)Ñ¢YUú|””�`Õï8ñ˱M|]NÁ=iNRËélìÿ”F+<ðÞz§úŽÍ+¹š{ì�‰°㣃R�l^äc.gž­Tª9›‹ÌM¦ÏÀÕÕ•HÝ¡ï‚h¤‘Fi¤‘F/¥WUóè B¦ É‚ ì¡¥j¾¿!ÓãU£‰Ï€çm�Æ‚�+ÂMA¾Á°J›šóx¡Êv¢yEœÇ–ïÜÚʹc“É÷—\¦m�Z§ªms&w¨ÁÀëp3Ž2Aà s®”ÎdÐõC¼ó#Ö¿ü€[T!ïÇüÈ‹!ltØXƒO˜6XKæ;VNÒ£P&*êlË÷_ËAÝ"N¼Ö”®o¿ÁÂßcd>a¿€.üâ;š.z[ÑE—§Ön|ØmJ$š–³Þ'pp¼´3'} ›Ã÷qøÉ$æ\Ÿï¸n7Že»%~œB�FÓ¦Óc´4ʳódÖ+÷2`swšÿþ �í]ØŸôƒê¾ëÖ˜\ݤúÁ n–t}û üÏflÊ:ÆîéÈò€+üxE x ïÜ`ôè×ê½u±6Õ,ÍÚ8›¯¿«Kv³×hü¹/¯ë”ákXÂGù’éÊ™¸ßð5,©Æ­TRA‡0©ïÄåD¡ÂoLÜñ=ƒ 9Ýo-“ÏK€ù›aÛYv¾!u‹pÌI!ppÌ”w°Ê�RÓËö³rû-&ÛSv×–+¥3ùãNŒüLØ® ý\BüZâׂ0_©žÓyB .Sïâ2õ.cì\cçÊšiËdæŸEú,¬Ò&ónd Vi“YfsŠO¬ÎÐÿæQŸ1ž» £BW—ë6ÒóâǤ¥ï %u á§Ñ•è‰JÊzò³¨‹.›Ã÷1'ÿ=mQf@v~ïm:_¿ÊºŠ÷y'1GÚv•÷®B!Êœ@5¿RÍ%Ñ+ŠY ³X·ës™ïåL—{!¤žȪÁ0êݘ"'©–ò—_ «½iƒµèøž¯¿+}üŸi¹‘¹E÷@YŽÃyÜ?«Gów¥tØTû›ÜvÌ¥gðÑ–¥„ ¿ÉÌÐ3-7Rpì* 6ä­A÷_ó#çQ#�4ÒH#�þSúOsŸ‰ÀE¤ôÕB`B#XÅþªmL’‘Ò^Û«Ú“DQôVµk8�i¤‘Fýãúoã<¾*½ˆóØ> ýKo;ÇæåØ{ßXÕ�’¨O/bÎýTG}²OÉ©»1â¯eùTUô€“u¶™˜Ôï ˜®[o{]“Ø¢Þö[uã .gÖÏ×kú]ÝhuÙÏ˨jJþóŠ7®ûx«zÔ&u}õ_•ý®¥õ¶ûgÕMÓé\RcJÂËg¯ù·­·½åÜú³Õ^Vs_z©õRW ª·=ôTË:ÛüþRû°‰ªíò² H€æ~/½®ýÝ u¶õóºûÒÛýúïá<Š¢¸R„ÀV¤:Ç $ã›Ïm£ª+Ë A�K‚ tEñŠz±ZvÿJ8�ΗŒd ƒZjFaUÞHu\a뜱ð,$3̱¢æv- ÏBŒ��éþÇ ¾ÉzÀðiñÍÏ(îEbjÎ݃»Ë˰›YåWY�"/ïYèÔô— !¦æµ¶Géˆ2+Ð&¥/±Y‘hµé€o‰‰kð «mWÕV•Ÿ§Öµ}EXxÒäa&ÏlÖp}›#³=ú³Ý«·á;9×ç{©]Åj »vˆy]†°i÷¦�w®uŸ®™A¬,—y˜µ]5SQß`‰ŒQ«*—òù6=ÝMäååñQ#K%²Ø¢cÓ7²åh,< ñ~�Å.ϧ´LÙŠµÕ΋o³43GæV6y˜É™ô¶õÄ*=3®—�œîÈØùž«ƒMfÒôÇ�jdɘÈãòüñ¥}äë”f€³§÷Ž^ÉÄÈÏ%IÞ§šKé8SªïKðß ß;õ³¨N±¼¸ÿs™é—f@¯3ÊiËmoø–�…g!+òW^úžl˜tþà8 —]�¹ˆ]‡FI£JÃ)õ½spHF«Ê5ÌËëNQñd¢£|qvv¦ÉÃL.…è“N®õ&&Fel™‡|®“ÛI#˜êt`€Â¢™.á�Óùñá6)ż.nbhh(žUc ú–èqtîDüæió£Ñvù\í¾úš;?$.t+nîÒ6ñÈjéyŠ›ƒŽRJ¸¸;j!yyÝ©¸uãÏHL´£ -pÍŽâµ*Lÿ Îã݃»i1MBêªS‰ÕŸ?™aüب¤Úzã^ÅÙÙ™ù+XQ1›;í1v�#ò›*eéC{ì/T>k‚q/>ÊŸÆ£ÄÅÅñÎ;„k+åg¢¤Ñ(>Ï0’©SºïÃña:Ê)¡Dù0‚GM-¸{p7Ÿü"iù_I ë¨·x¸ìg™ßéããSç¹þ]æçQ#�4ÒH#�þ[õ��ÏKÅBAÔœGDQ¼ tá5 �(Š9‚ \ê뢸‚dÀã úý¥9�€M¨Æ’’’ºV@K)qüªjßÛ4M¸RÉ›Cª�Úú› ‚¶Š·X! –×íåÓá‰!Öî|d Ë•J–4ØË&OÌn€÷ÕÛôû1€õŠš·HGk ëkw-88üú «o†ÒaZ[ãÞýš$�Ý7Äw¢•3Ämo�ˤëx� #ãH_2†êÐá‘–ǺpµÛ#JJee^õšåŸ'4V .Œ,‘Ro%Žæ]®˜ºqÍUJ Ww͸®äu—[<Ä’)£>A[¨¼ï¥“®#Šº>‡Ásˆ™ŠŽj·‰m¥ÝF"ßy·?¦SëER~@D뇒Ç]ºÿ ZµÞ£Ìû›„R ôwSqó2KCfâa!ð¸Õ»;§ dK&Rëz f›¨ä§‚|z�ù�¤“*FeS'}Š s0ÉÅ0ç#l÷B±õ€âyΟ»»;‘‘‘PœÃ2«|â,"IIz? ä`®UÁ/.·yˆ%Ñ{FRö]ŸwlI³áþB<ŒÇpœMè(K¨èJòÜ‘8¹= #´‚}ÉÇ1ôè‹piÿš]¾Uk€S{°®6u e‹i«$æ'Ôø,+J+Jú[réd1C[3¨ šãEïõ¬ÖÜo»›û“o, •¦«¤¥E¯´ED8É}츴žM‡Š¸³ ”J%gt˱¿»¯Ä)±½s÷Xh'õª‡[xÒéÂL�ú (¡ÿ‡W¹V ؜߻Áå ¶ÝDBf \M`³BÈŸzŒ/s�jkÿ§³q4ÒH#�4Òèÿ’^IÍ£ –‚ ì!O„"Àã¹ö%À E„Ç@7àÖsËè ‚°F„\¤ôV$W¨ä‹2à°è¥·¹°ûJÊ|ñM�F”­ÚFVEMGÑ¿+W[k®ntâß>凸^üöI÷Þ¬ RØ‡ó½£øÿêMI™¯lŽñ¼:Íà†Y”üÿª&8�VxÈ#Q´&öD–lÖbâaÄ{o˜à 5s­ÊѧçÍsꓚ�øW4âË¥/EX‡ÑÖXªÃÜÝc ·{ãœø.óÜN2©•¼ÜéŒ\ù÷Å“$—Mµ‹eUůZ�ç™™¸e…ÿéc¸ºÑ‰â{aÕï8=žþ†žN Œ^øèà#vÁÂîµc6^•e<¨6•ô·”¹}GÞK¤ ~9óÜN’c³“Cî˜tQBº–4…Âì cÉ™×½ô0×·I~Ww[Lâƒt}zõçA»·ÑÒ­v�±¹#¥ÒµLÙJã¢êåͺ§2pqŸ[gª™ó€4Š’¡Î!—áõb:ªJ=Êë¡ÎâdïjmîFÊŽÂͲߕ�_ÕR›\­Ip#Ã!�£SHjÑŒ’É1Ìw:'ãCº�\†Ýªýêx^$³°ØzÙŸÚs¯Ê¿¯Ú…oø”ž<¡9É|%u,ļ!#8 fŠ—õ½þlOaÑš“ÌVÉnÐ&$ö«Ú4çj“Bè9CF ežñ‘žs©Ãä”vM~’Ì¿¬­¦pÜ¥©×iXð;³“×ST\»±Fi¤‘FiôŸ×«à<®AEÔ>Î#�ªÚßAêÂþ˜ <R€/Ah&B+A>vo#¥™f©–ûL-ç18„dž3_µLµÔ4þ]Yô –íê¾3ªÞƒíëJ„uC¾ò¤ã@szŸ›_ïö¦¼žøÂ}Þ(Ѫ“ƒöwäl]ëühÿï8¶LúB7"yCŒtÐK‹À¡§”U\T<evcÙÚ_ ª¯OjWÔ§ŸGð�Q1 >~rÛŽãR�uÒ2œÏö=ä‡Íq±î/Õ?xîLÆ18àO/ðujß/ô74¯Ë:<2À*Ñ›ÑIy”t��;jÓ!—áòïá­VsÈe8û?e”ÝÜç~ÊêIŸ’›êü)Î4ZáQëv´ç^•ƒó�àãÇϺgé ½OÇ‘ŒÚ»�cÓ£96=ZF¡|�?� ®ÿbæÛ!ò¤½ð²ü…>eÑx’çŽ$yîH~v ÂbF<îHXŒ»-&ñÙ¾‡dÝŽâôû£YjÂR»+$4IË´PBÝœ šˆvÿËT|_ÉTKÌ=-�joŠØÃgFŸ¡÷t ´¸•Gšëε5Âèµ!(r.ÑÜ· ·ÆžaÖç&ÿÎèÍu#)ý1Æà †—ê!ìýK÷è¤e8�¹bu¸ŒøÃ‡Ñ9:ûS $üÎq³æ ÞáÀ± {I-òçr槪×kNúU2†žêáìiÆÊí•™åC[³|ÖF†1e”+y‘�Iku#QJYTŸoyŽ®ÙQ”›ðyH½ÏÍçÌP(¡E¬W›IÅ÷Žô>7Ÿ–³‚9»º~Fï•EøÍÓ&*5�›ñuj_t­úÂÓ{Ä;…àïMŠ«P�uZ? tU#gÊÊû¢®?äzа°0zzRQ!°->�™�«!ž—zÞàŒ^\.ÕaÇÓBzëêb¦Ï�u¤{úÉÇ\p³Dû¶5æx+Hh[QõÞu>Û� �1®¸ÀƒH]fÜù‰-­Îax縸ðÓO?ÕØ÷Ÿ9¦† rä÷xìéÈ7̤QB,Ý}¿¡ñW“IüÌ'JF=¾P†Ý*´ˆR=ÿñN!øí]Afé/L~Ö=#×4�-óàÚ{ö$•eŠùøhȇ=Ê )†>ÖÍh:­¿ «ð,Š=~�99]Q(<ð9Á•ÐalŸr‘M{³±%-‡�Á"}SºïãÈâsØÍÅùÅçhòñ*à“•r¤O ;þüT§Q›þìuú³ëêéé½°^#�4ÒH#�þÕ«@u¨ÿê®~Aªgü˜&ŠâŽ*Ë ö"a=îCEQŒQµù"!7ª}Ó!HEqÚËà:4¨�4ÒH#�ê“Õ!IƒêøsÒ :$iP’4¨ŽJiP’þ/¢:^Eðø ¸$Š¢[•y«‘RW]«Ì3@Ja5¦ž@wQÔ<†wDQœ® Ç‹¢èøÜ2‰ÀVQ—×rlµ�A�êð~�EÀ5{Z›�‡r8 %ÑÿмXã‘è9]^¿>TGAÃßçÛ§÷ª¡:äãU¡8>»¤à—±I<¯Š �ÄD»ˆU#M“®UÃLèí�R•ŸGfˆyçèhîN´îhnZCiÁFZƒ¾Á2¬Ž`ôPÁÂc±ÜèM€mS óA „¼Hlî´çäÄÚÞ𡃎’¨~.!..Ž®�X[M‘R�-šÉǤF¥T•1‘f€Ãà9è7Ü"·Eû‡áâ^Õžã §*?·Ü´—×-έLw�:f Pù«ñ.¾%zœ´”Œ�²ÏëIϱê˜Ä¼sÄœ A¯É,šGáììL\\;‹;ÈÛU›U}·´•ïàø0Ëcå{\FÃ÷[IçªÂ¨4JˆE¯É,¦¼žH´¶QxÔÔ}ƒ%4,ÜA÷ìH°Ëàµ!+ÿîÁ»áet¢7Ò¾t+GÞKÄòŽ'WËtzpžSsŒ��!ï5®b|i¤¬’ß»¯¾FQ*=‹jcóŸÊ_ 3ñÓÕ/1�o%?/ ù|�?�¨ÔtÊz„8¯fEþ –Yå³0CŸÁ©ƒYdnÊ™–)*\"ß›llT(¡ZÚ+†ÊïÆ´m_ÑhYO²vKõ‹£F¢³«q]ò‰‹‹C©TrÄöS{ŽÆ£\�õY}BLÍÉ 3À|”Þù¹„™�ì?‹@Ÿt¾±¬ ¨p +òWðŒg,·*bfØ·8ú)‡ßyggç ·ñwÖÕÓÓãÑ£GðÿyðX|üúKo[O'°Î¶ªæhµéeƒG§¼´—Z^ŒÕ§zƒÇ;u//RüªÕ/^¨ úýK­§6x{¥îÞTo»·u³—Úî ¯¢z۽ʵêm¯K=Ó=ëmϺUo{]:o^»'�ZQ�ÕÙöƵ;u¶9½›YgÛ ÕsFýí/ÙÉq¾cý×(öDV�mÚ /¿Ô>ֆ勉íGëúŸ—úôD¿îÏÓ›¿¾ôvŸ/ ©ª–Óíê~^^±êýû÷*ÜVï£JQ­¢x Ú§¨(Š…¢(&‰¢-Šâ$¤úÆIªæ, � Ï[TšR‰âøË¸QKEQ|¢ž� v())‘­ÚŸŸ÷ZBõI(¹i¢YÎXD-%_onƒ¨¥”FQKDÔRÒîŒ_onCëÈ#(Ð’¿ÄgÛŸá^76þ,¡Ô Ž myjÐz‚* D…(âeiLóÞ%( Â]Wʬ¾}½Þ�‘U'±¼¬Î©¾öºÚÔc©R²ý¯1©Ú¿ÿ¹5ãnÛBð}zÆöÄÇLJ¹Ï$Ü1‘Ç™~”•Þ--´JKÑ*-•¿lß]»ž¾¡³h²‘³Z{h?c_Ý‘ÌK´”e.Q`pšk›ZÒóÙ>šÏW]'ª£ê”ßLàÀ<)Ý·TPðïýùø�=B¨ÍJ…Lþ26 --±Æô0Ñ €±‘AŒNÎÇ&³�„ù¸Ÿ'“ÕÖyîy™Ü.‰žYô*h”~ž~I³ù¶Q ¬$ó¡Y§¿`iÀuæ¹Ji…€Ã)W¾ÿY\Ÿr1�q·mñ¨(`j¨>×Näp½L‡¸í-Ð�¿ŒþÓ_q<£Çì�ŸÓ:òž�Òªyù“0^0Z-™A*V42؆B¡ 88˜àà`ž8¸0:I:×ÑI^¼wÛ‡¯2³¨°v'ûn;Þh$¹rçú#–—ÑcäWÌë탶 `ãÏŸxB2Šé^„ï‘G¬Ò·ä¼b÷ÖrFõAzi¿„*¹xò8º†³é:KN-U*•Õž£‚�O×x·F'ç#ÆÇòkÒ·tðnÎØÝpM_Â;�{xù}Cg‘´ò'¢(¿?Oõ�"äžeõ©£$Í>Pí]¯ïsàJñ;õ¹Îh�Kq<†¯o#bÌzãú,@úˆQ¡wš+Òq8åÊ®30Ì�:aŽX�ãÛÉ‹ÑQޯ׷¿Øþ‚rÈ&ÊÆJî± LMP*•ìü¦�²ï:â˜þ€Ž)’{í¸þÍøø!Ú‚‚÷Ks¼´+¬ÑG|€cY븖É!߱ЯÎׯñõ×_ÿåëðgÛ_f]�4ÒH#�4Ò¨v½Šà1p~Õ±ÉG– N‚ Qa6 k¤àà2 AÕàŒäÞ •¸Žãªm<á8®ãå»k‘`ܫƼ´V7¼Câã­ ßAèä¾ÙÐV^Æ)¼!öw70]{%ý-Lª»|æÜ’8‡|UÀ†òéµÎÏ×¥âæe:t�Çp�ÔS9v—Ë•ZIƒ¾zMf±Æî}ÖØ½�«ï·/Üo}ú3Ç]«zΠÃ#n”HÁV¿~ýXr?›ÍŽ›y¦³»ÞUƒÈHnÏ�Û+åyz[ºâl²½&óÐK‹ Å7?0YØ…{éa’ZNG/yŽÁ¬<ß_¾NGçND~ @n®—N°«@ª1§BPf7ÆÐiá_>�4ÒH#�4ÒèV¯"xÜ‚Tçè„”Žº)=4¤tUAÖ!¹°fÓ�ãªc9 Šâc¤ÌBàÀˆn¡ªeâ‘Ò¾H¨Žùªß³ÿI§Uë‹�jÌ3u—FŦ¥H¹âËC‰Ëê¡]dvÛ¸=# Çf'z'ëÆ<¸9 ^F¤³Iý•Zê€6Új1%6=å à^+)êàÛ�[6ìvH!ÎåØß ËÕÎ|‘–DÔ^�DïÛ^j›z[º2Dåú©þr~J:ÿecLúò[~�ö‘ÞM šò—íÄñ�|”Ù�Qf7&ÍÕXÞžçÎdÚ„§ñÃæ89Hvš;ó1R�µ½eŽM�fr‰'âבž/¥ÿ mð*§é”�ÀãÎÜm3˜–ô�ùSœñRŽ”Ó=�¼—ˆÎÑ)8Í�CÉä’Æ.–ÓHw¦DÓ&<�kæçð¶nFOžàôn&vKÏÆ„Ýß2-àKœõÖ1r‘6#i“à/åá'øo`fÇ™oØh…�Vxè“þÂsXš™Ã¢Œ,a.‘Ö_“p“ΑÒz#ZÌ­Æ«lJŸ}É+8Ûë@âqø rª —ô·„zªgfúIþ>!F&(rRªµÙ§ä��Õ_>ÆiIIòÒ¢ßG&f_ÆÅE²àÏö™MÞÆ4 ¢'½xCuhàÐïÑå¯×Ì.éYJI•ÒI >ò¥�£>å†çã­bÚéðg¿ªžœÃ"-%àAê]¼Îæ²ÑïSÙY¤Ñf5† .YÙݠĦ'.©ü½qÞšZ”ñlvÒuÔ[ÜY¶”ÿ ´ÿa`µe&Ü}Äñ*ÿOòª=%o·C œÒÇÝ6ƒ[½iÈÉ ·á„»#¥ä;e�~Ö}ø© �ýãIÒ*–G—ž.pàGßíä!s_Î<‹ó$v_V8›úav#âP´ WéÞwþ�Þ¦ðé7$zNÇöÜ=ÜUÇw5ëzMœ@õ¼é¦ÎB¯I¦�L`š]?„#.´...ü¬{kûÉ4ýrÑ€Sê¦%ÁÊSçi5ÒO>çÜð|¬xñèŸéÑ(HgÒÅT"x�cÓ£YœìÍV±½³‡ØÛKo‰¡yüKÞH¶ƒ+8fT•Þ]i®ÆÄžÈ¢ÍÈ),ݺÿ+ �p¥Þý·]ß–K–ÞDÞ�d_ò ÖØ¢«Jß-µrÅÄZ�IéahÁ€æ $”+¸8ØwcÕëÃjÝîj‡ê�÷±Ã„ÊcÉ×åRbC݈kbÊ�¡XØœÂìô(ÝC‹rÉ@@oKW,7 fÛP”Câúch[²‘Oq¾&þ=l#XõYcð‘«¾¤À©f–„Fi¤‘Fiôß«W1ò8n!…KÏ{ž?Œ�¾Q�A `ÍËç–Sªæ‰Hh�_¥Ý Õ1PÅÆªi’€¯PVv7äß!}iöôG¹ý©QBE³‚ó®<Ó’�üÜm‘©=p¼œyÿ_½IMó¯×!í?)õHÙ)Å~y„ð|ä»ò2†C¥¢ûG+�¸þØøkWRËé*–gu=ï(Xò^ÍðížCþòÓ«�åO¬½«M–wj¦G¦Z¡w¡'‹'u”ÛÂ6àÀôðÃäæº±k×¶ˆcÈËë.Žú»™?Å™F+<ø£}0×� Ñj×™»• oü óÖ®|�*¥oíw…UCkôn¡EI ÛÇÄcîÎLóa|gTLsß6t4wÇóÌL@2–xãÚÆ—öÀÓe?�J�·¼°Iï…¹Ö(–Dèòºjv¶GôÒ"°ÒD†CÈ �Aælý�Fo­ô1-àK²Â=°˜Ñ€¦ß­¥÷¹ù€ÄMtõý–‚xÉ«+ÎrwæÈÛIj9�N $Œˆ×Ù\RúE�¾;_ë_ÍA®}@{ŽüÞ€\ëMD7ÂÕ'ÖØ½ÏâIù×ãÚÑñ«Vã¬·Ž–û¤cØ"Ž¥q‘s_bà£,ÊxÀÒÌÚ‹÷MoþJÌ©@Êš¸áUÄ@§LI˜Bã"7>i@ùªüÒ²)z[ºbª!!“Zñ8`»¼3Rñ*×â› m‰ üˆíÞ†;6,2—ªº†£—°—ÓâéÕûå]95ÒH#�4ÒH£ÿ¼^ÅÈcKà}¤ÑÀ¯‘RXW ‚PªBu˜��ÒY?€ƒ‚ ôE1\µ�ÝHAcÐ)Øty¨À¨Íkù�ª­†^–óø�yç—0'Ýš§~R¹àf å�[æÁ.�p%Ä3s †Þ‘}iï~Œo�–ß fŸÍÏy¬ªúÚ”T T]=e-WQ=O¨¨­oEP™ÌBŸPQÅè-'§+�'sÓ36ƒå;,ômI×jepêj‰òuªí:ÖÚVåÙRVai–‰Õ�çÔ È«� ()� »ª*•Jȃ™ÉÖ”*¶´’Æe[ô>�nÀÊÇ߯4¾Áã/!j‰X`Hù úu�Å™¾ÄuÉÇüD¦®¥�•Àö–3*öË×Im€ÓPo'z%Õùš.'’°îBÂçGqü~DmV¹„‚‘ÑE¶xo¢Gâ¬u}Y¦˜ÏÎÖ©XžhD\‰9Ú}ÛVR�•3tÄwy/‘ôäÆ�)l× }û�x¢×Š]ÅÞô趇+if6… mÛ3ö™>�Î'è8 WÛ@ücþ€¤k5x€þWX~/›oŸÞ£¢Bà�Ï,<K©—ô†‰Gù°¹w€Nqƒ8ïj@àà-DûÛ³ý³é‰(òb±h>îlÂ%‰i[YYàIWkv»wÃåé=&ú~Î�mlO•q£D‹¼ÈÇ4KGˆ3„�.À³¯![Þ™Hxخ䈽ëy±(îEã’bÉ´”­ô˜ùoºú>ä× IOêä“xd5ãú7ãã)¡¬û| éý‰ÒyÔÔ‚l¥q¡4JˆÅnæZU\¥ãxgBŠá¸ùYÞ ßÇ•Ôà:…£zïce¹¿ú„`ΰ;¹òk�ÑÖ؟;Ì»-zpµX HxÍ‚ì£œŠ±±yywHèSÂYAÄ4:—má÷í¯!zJÏmë�'wÃxé9ù‡X�g] çQ#�4ÒH#�êÖÕ!‚pØ#Š¢o•eŽ…¢(Ž®c»��K@gQ¯¼$ªc ΣFi¤‘FuHÃy”$.n\ïúõqëSaј—Z¤ì†—Õ倗·ðÿbTÓ—ZozøázÛ—ô,­·½.9ß;úRërfDmzQ–’Þ…ž/½ßzqõ Mªz7üUµ?õòßëêãR†õñ¯³­C×øz·[¶¡>ùt|ùÒžúø…=ë©©J±{ùºôŒäºY±]úÖÙ¶¾aó:ÛàÏû€<¯sâ»õ¶;þ‘ÿRÛ­¿ò"}¸ÿ¿#îÕû÷ïUŒ<Ö…êP»_ä"a9j[¦¾O¦+@à úý/£:�F/WVù¿!�1qâÄz9�J㎖ÀãÎÂ[­ÆãÎÚ·ê"óÐnÜ©^߸âA.ÎÎÎxhÛ°-p³=ú㪻…åVEô(ÝCZ‹Ó$&Úá|ÉSW#@J8‡OZD‘[�ó8(´PÉn»{p7ŸwÇmøN×cPázÖܤK?5µ 3Ì—Qú„††ÊǤP(pvv®Á�T›XüUΣÚáSÍݳûêkžþ¸ŠÐÐPv—v"˜Æ„�³“Gm<´mˆËŽ¢Ôv-·­A{îUÆÞyLHqgÚ»°<ðct«×~©�9..ŽqŽ«µ©¹‰jÎc}m‰žÓI·<ÉØý]¸à*s6kcªÛžgtÊ×¢ gÐççê)—3ÖÒrøš<Ì$Ïq0Þù¹DëŽÆ#2�-y»±ð,ÄÁ!™Ý |úbdBŽÙ^܆ïdÄÊ-|ûôúKè#¾ItºInoQ¼ö¶Ì>Ì 3`\ÿfÜkFË´<><ŠãÌéò³¨fSÊŒG`Õî«|ÑEYí¾·5–Ò†ÕÌJ5£ó†î$¾a¦œþ™jSæ" B)ç#ßeÔ(CÒ36Ë,McccÂÂTu�ýúÉÏœúÞ©Ÿquв‡W#™¥ùQ#K¶¤^”¯_©íZy»U9še:ûÙ±‡¨ÔtÊš/#ÄÔœ‡ö.Xg½A�Ò=ÄfEÒ£\AY‹LBŠ;Gç!ˆyçhòÓ&×£YÎCùÞªyŒ×CÐÜ· ƒB;åÌ#RdÞ_ðž (šPT<™–gÖËLW´´›¾±ò¦'[Éûõnx™tÛ&<ˆÔåtF.ž.ÃèÔðBŠ;£o°}™I à5û:äŒ@‘‹‡¶ «/ÿζžƒÔ+!44” Û -�¹­™©m9�² €éã� )îÌ݃»Ù`õ^µ{gÑ'˜øOBHþäc2à ˜Ü.I68z²~7M¢1�o…wÜz«ØkêúÇ„×,hštM~'oèNâXÞñ$Ð1”±a�$–lÃËìjõ¾ ýØq\Jµ´D,< eäøÒ>”¼w½�IïÖ„x§Lã[UÛv¸»1×·9òUÐÙ”ÕøwÖÕÓÓ«±œFi¤‘Fi$éU�‘€ãsóZ©¢(>!¦¾eêP;$“5a[�êè&ŠâEAºSªCÅR@îêSŒ/JQjÛìŽgfóCØQægÞ#Òº‚w.qܬ9¥‚‚‹Z¸ÛfȽgtz‹Se)l \†X^Ʀ+­É5ZÊXKh(¢—Õ›ŸHg—N8^÷�™®½� ÷§óI»c J)ít"…$¯ Œ0°khµ¿Y­ÂÑÙy~˜�Ñ‘»¤NnŠÛì<-œO¿á�F Ø\ûɨù‘êkò\ X•MWãúUe>j©Ó&¥à+zÏHD­HÌÞkÈ<â àó[(à†=ûÛ„!¶S²6\‹ý¡³Øè{²ô%7íC$Øè±ä~mɸRê£VEÍà$Îã‹ÚôŸþŠcB1.§«…Š (¨0¬ftÖ”Bn~¿³Æ¶æx¸ZÝC§h-4€FÛ¸ì.J%99…hi‰´¿1í¼_šCÖÎelÿ5ž…�p� «#d¤`tmžÏr“s˜fõç§Œëߌ zñ՚ÉY­=¤ýzŽÄ<-–+Ëáé=fùÝ&ïÆ@vçž&äl àI_K(H¯vß ¶wa‹Þ) ..pÙú:¢Hüª¦|¾¶8wl(øq#�–-eÖé/(U�ëç{|qDêÈ8îÜù€R[é•ú<®nfR0aÞ“<î’Ù¥)÷’Ù�½ú�y·HbD– U2¡ý†[(*ÌÁMû÷Öb ¿›¡Å^LO.à¢u3®ÅÚcéý”âõwH(_KyLÒ‹ìð|’µ•` [ÜÞà]©v�ÞïáÌ>ä �÷†¯o#„\]ÌL¯¡£,ÁõšÇ]6ñÍÅ�ù<Í‹žá=ñÏ:DÉ£–\×þ‘V]üp|œ‰Ë®Þ3X”ñ€”-.k�E,~¸KÇðn,«+´…uv÷ñiXHÎïý Û# ôýQ(„ùÙÓ÷-/nœaÏžsôÎå'à’ÞÜ{–¢<«$ —2þv®v{Äe­ z¥-¬gMüüñÞz‡€#ϰô~ŠÑ¢˜ºø}VÓ•~¾o“døÛÏŽÃçÇU�“Ã�pùõtX¾“Ÿ�‚€HÙôIÇt¥ªšm9­ý@wŽ»lÂñq&?¿eB’_SFÚn#Æ´eßuänÔ@s—±ªB)bKÆ ‚0 ˆB�Xû¨–q¶m�\[¯ƪe#UÛËBB}D ‚P œCbEý§�V;vü„U,¥Q6âÑmÇ÷`¶‰-âX¦kkwgI^Å” ÚÌÀuÝù©L2Ð0^0’‘‹þE‰ÍNv‹#ð:›Ë�CvÜY¶”Q£ ɻ՗ym‡ÁÙCÕö™Þ­ ~çß�áÌwªåÀ"Öa¥·v�=ÐÅÎ�ÏŒàgÝ3ÔZù?¨ w±Íá5Nįcà¢Ûzâ§‚tN­y�Ðr�æ ŸÒþ”>�-z³üÂ�d¿©×èûâÿ„ÞÜЃäÖq<4–Îçùáí E£€»„+2VˆÞÉ{ìZ}™œ[ýè+17³^°¿Üð|&ã…Ù�“/ꊮÎ/x)GÒb‹#°éu|šóðuj_¶¾†ÐË`�z%äH£×:Êè9ƒó®ãða$J*Èã.›¯; – Ìœ;Gò8üczý€%OY8®rðPÞmie9ŠœKD¥¦££,áЀÛðôG©-bÝ[ÎæxQW&]L¥´¢%ý-Ù-Ž€"8i9‹[·Z2g댘¼˜”-ôÒ"ðß¿™©#Žsœ® ÞáÀ:/°:\FÄäó0¶1PJïÕó¹ã½”Ÿ^+æq?+ôNÞ£CæY))ˆQ#HRÓü¹µ3Ú /s?v?ã 1»a�ù¬n{³+ý.¹ïcß¼!G�£È¹Ä¦ˆ=d%/ã‡ø^¸ÏÕ'rô˜ùorrrPæò¾öNl}�¢ü2ôÞÁÝ6ÙcvÞG?Eë@):G§p8× evc,úrµ¨+EÅ“±œ)ÒëèìßÍÄððm>w:@ ž9p}›#ó&´!ÉÏ€o6´ÅÛá:JA@í#öC|/ˆ—p>VžoÒ{ÝFîx/ÅMø•3+FÊq¶¶šÂô¨Ÿ8ä2œ¸ÜêµbÞ9Âúø3Ïí$—Nàu6—’î•{Ë;ž(Ìž0a·Wc¤èpá1) P4+ÀÍ`'o¤ÒrîÖ?uŒQ©/fë=/Wßoé0-žÙ~íþô:M¿Üô—÷³&à&ÓN6ªßqâJf°húì*¨nÎs? �íÞæræY–°JNŸ~à 1C[Wã0þYÝî];¦ 65Y0‚îÙ‘u¶žJ“Ÿ¤sÏ<ãCOž`âaÄ@§˜xѬ�”²é™Çé÷GèjWca×՘ȩ�öç'ÍÕ#|ü™ÔŠo¢¤‘ºpÅA’Z4c«ÏB'':9�’þ–²óéIËp ôwËSPP�œFºkä%¶�‰§E蟫Y™i>Œ)¦RIò;w2:)�ÖgazóWLoþJ³œ‡l~Hü^ Úøõ©¬Ãé9¢$2O©—…¼M[›™Œ*;^Ÿ˜@V¸‡4ª~!š«pØþfS ôw“1TzFRÓüIMó'™äÎiØz ÍÒÆAcBÒsÑÒ�ŽæØ£exCârÇà^z˜ ®ÿbZÀ—¸ xŸ¶IÉ_§öåj“B®­gÉýl®—λû!¶�‘jV"W}‰«ï·d#ñ�˜çv’�I­øbTS̵FñÃæ8™ë 0r‘6ö)9D®ú’¨À�Xpóy 84‘SÚ$�]LÉä~´.’ïO£e=9Ôè”�‘°)U¤v@uõýVú,¸¿�'ë×2hÚÉF¸Åºâyf&Kš›áÓq$S^O$§ç™™,ì>‚Ó@Š ß’]�Ü–2ø÷SΛã(´™wîJ÷ÕÂöb†¶FO'�77ôàVÀ¦%=ÄÚjŠ|\‡}À¡F3´5\ÿÅO¯I#w‹\ŸolC‹Ðž4Y0‚¢1ÜÚ¿™ w+y¹]ߦëáÛ²c®Fi¤‘Fiô¿Cÿxð(ŠbŽ(Š»DQtEQ8„4ž®uüøJÅY¢(Ú@!Rà‡ ¯ïï©ð �Öš"šê‘ÉÀQˆ¢h¸#uÓ×Vù·uàníAÅ©5¯Wûÿ­€3|l{€Éwl˜4¤£“ò(8<•ícâñX$/›ù˜� (éÁ#ò�+ð÷”‚Ò®…Çå/ãÂZ¿ŠS Îåc„8h `c‹I8ua¾Ó¹:×IÝý׃Çn#—I¿¨R{¿ÙжÆ2Ͳz“p“«1NøgÂÂV*‹}²~s_BÌ=ͱéÑtéo(×�¥µºÁ@§ìK^Që~ÿJ´ÓÜ9\0s¯µÍùÚ›Õþ_ptÿÎoÈå̳üh]„žN ûöI–o˜É¾wG»¿ÆvúõëWcÞÒ­±´?#ñ"'ß±Á´]-ô*H³>‡QžÎý$ç̰o±ßµ¯³¹ ‹ÜNÙ ÍdÝŽBïé>Dê¹êK*n^¦Ã#v!ïÏsg2Þ[ïAcfO¤I…Œi=l6mFN!Ú¥£ýoe0§] ÓÇ;Ðeäm,ÆÖ¼B•yŠí—R&” ›ú±Åûß(FT»†ßÓc�±6ß�4¢ã{z\h+9u]ÛWÄ.W®Å¶ab°!¿$ŸÇw Dt®Ðb£ÃFºû~Ãñ) ñkAª½4Ú“j“ìf¯Ñ5û¬ ÑQQÕQuþ¦~EÄ;…Ôz>.Þ¥BW·Ã#j¯­ÞŒÙ\CnÌ?A•ùˆõˆ÷øD±Ÿ)”gФI&~(='òjÜ·çQ)©[Ø¥ôÅ:­†…}iT$$›íÓ9gó #XoHÞŠýD˜÷”ÏG4vgÏYüK{;=L@K{žJX…B¤BKš:¡sð:„IÆÆºA<Ñ;Ë}¥5ãÏeüï¹xi=&á5iÔ°LTP&jÉSÕûªÔÕ¥BW—ë6ÊÓÔN·é2r‰ôî˜ÌäÒ3Øvìëv}Ny¶nÆða⇔Y¬ Ì}6‡|þ «~ „¯AªKì×qí6Äÿü`®7‹Äðë¾Ü “µ1a-Éýh6fÊ;˜)ï8¸‰ •RùŒ›)ï°4=Kº¾U®ñäóljŒ�in/ ÛuÃÞ÷!‡Æ$Ð]y€¬›ÞDÆ6LŸsÇ&3ïŒÔ1‘hoHh/K‚ƒƒ9¨[Äk¡ôŽxJ\˜”ÀÜ^1”§ÏÞåÆü´º³**Hk!u$DÀÄi1¡×~å\ù;$ÜD‚£!=/~,Ý[å$ŠŠ'îÁ(îù \ %kŸ4Ú¬E<Óúcøu_6Ù™�šÖ�]’‰Vƒ´Ùt.8J¸ÅiôD%‘ãy?æG¶Ü´ Í�~ \·›˜ûçÈŽxDÌýs”«P)Ý•H°6%Tø­Êg‚”رз=Ë÷ÜÅg æ·½8«ªFˆõ¸ˆÙ\CVYUp³O Zé‘Lï#Õ•>KZÌT‹Ñèj‰yúŒÆ‰×˜ÝÓ�ãOŸpÁÍ’Íáûx½Û-î¼§Ægm}ŸÃ/Óþ2ëj¤‘Fi¤‘FµëGuTÛ¸ ŒDJ/µE1S7¤šEKU@¨^n`+ŠbA|�í¢(ê>·­` YÅi*L‡Ÿ(Š­Ÿ[æ¶jÝoê8ž%hPi¤‘FÕ¡ÿT‡ ‹€áHuÿÅH&p ªÖó«2yS�&À`¦(Š7ÿä>êGu„,¬wý�öuã+žÏÔø+ª+PW†Éß•^“yõ¶[ü…²ŽJÃÔ5äµèï :6í«³Í7*¹Î¶éUa>ü³j/õø»ú;)ó�¶ÿƒGR©x㺯áè¤úq—3ÏÖÙæžþqý;n^7¾¥>%yÕmB`ªîޏSß¿Ô>¼”ucK̵FÕÙö"5K«»ê­>Ä͉ÃÖ»Ý}ïÖ}LK—.}ñ�ýÏè?Žê¨ªIÀ‰ª�¢JÏG¬B-óž×óËÔ¶ü‹¶óR¨Ž‰'rø»/˜«µ €U¹Õ³ÓøV’¾ñþжLËãhé(òbñŽ›ƒŽ²DnëÏ ª TPÈ5k3þEW!òE%™alí´�(‹loÙ„öÙ®²�¾z»ÕOlf5´…ήև=Ê )î\Õá�/Ùì—‹JÂËÓðжA[¨> ]µí�&ÅÜ0‹ª¼àUÐõ¡-”Æq˜9Ç÷9Úï �Åëq‘�âάÈ_A¸…'G Þ¯¾²²E^,É8DT·Ï/  Âü™¶Í×xÝÛ”I�Jùöé=îÜMA«ö˜�1@§l$cÓ7R–bMˆ©9MfÒôÇ�Æî«¯Q”Vgtéþ©"$­Õ ¹M5µàîÁÝÌjæKÉLm‹…íl÷*¤Çèý2ZÁÙÓŒë!(y´–-æ²³áYœ��ù¨‘%Ë6—ë åËT=r«}(NÈçsð Z#£Rã^\y@߆”'\ãQS Vä¯`@êL¸óº·)Šf_ û¼�šZÔz®êë�f€X!0åõÄj)¿íµË«=3jM8ö°Ú3.ªF0ß{äڳæà�Œ–êyJÏ�ê'uSgqãZk,< å{»Ý«�¶9âïù‘Œù°ûêktw¾Ë©5¯ÓÁ·!]C‡‘Yú W¶�Ÿ§ ¯Â™íxž]#/á¹3™àñ—dŒ„ÚàÑsd,†ƒC2yÑ*§XÛµrÚmXXÎÎÎòº�¾˜À›Ÿ÷æø×Q¾…œ¹öãKûp e«'}F ^‰¼®ó%#2»4Åèóqœ?8޶7|äûÇ�Þ’q'#ZÏcf—_išt çKF=*gÊ�ÓļÁœÄ9LŒY�ŸK!¦æs}o)Ús¯Êç¤FäT½ÆçÅ·±?ÿ;á™qvvÆûA—µ*èhîN^äcÂÆÙ1vÞ]€i|+Vä¯à[£�ˆyç×¢;S¢)±ÙLQáùÝyô¯©$¹½ÅÒÌvè0dbùótß~3¿•"Ó?�Ûø;ëþ— :<�|b�þN ‚ÐVÅBÕ2ó?à6ð)"‚£(ŠÿùCÖH#�4Òèÿ½²´UAl�~@ÕÂ&µÁ¤ùs‹›RÉfÌ‚ÐäËüUÆ#¢(–Š¢øD= ¡:ÔVíÏO Ù¸ÛÝv&7~)¥”bòíüR¥/ðΞf8mCKKDKKDç®%m›I¦ :Êt”%œÑ-Ç`{NÌC¨ÍŽXIõ„[R/js†FÛ«äç·L(U1 ':C)¥dŸêÏšˆ¦%N£x½d…©Ó}-?ç¨Ïut”%¤ú.�íñ¡Ò*¿êt²‘”Ò*#Ú´™ØmA!O@µÿ×Öæ<�V©ëµ”|½¹ £¥k«B[ÔœDy¿‰OrÔç:(´IèIº­t‹Ã-<Ñ*ä㪜¤/ñzèˆb� ¨u~]m3œoÓÓ �ÊÀ„Ö°=™V©ëè¥ý ÖÚoËë^ &Üu%±;Jèzä.íÎèÑþ‡�Dtû€ˆn_£wà=´JK±_¾½ï‘ÙEꉿgw“Œ–q(DQžÕvßøØ•Õ7»rÖþ;lûž “®?k"˜8SJÜÚi!ïçûÒ|l{ìfoàÊ[Ž´óœÀò†ÍA¡�­ç&éþ(P05T_>Wã#ñ¾*¹£48À¸Û¶¼Ý¸‚Nc p+úES©'x™U>>>><09G‹¨|lŽT`{·óR ›ÓWÞôû1€È߯ÈSô©çor»$f8ßFG«SÕgF�6ÌB¿Nü2VJ�l<†o•°l„1¿8H’Õ— è«p”C6á¸g ¿¬¥MÈF¢ÈäAgØ×ë R=‹(û®# }#)08Í¢{Òó2xùzBW¹àÒñb|,-?Ƕï ž¸À°Ýö¬¶žÌ®Ñ±¤îÝŠV…PÉìÔÒ’§•Þ@,/cÃÏqª&‘æ½KHê5A(%88·¸kX¾=^^wÑý<æMhƒË¤ëœWŒÂhÑœ¹ö7/4åM–ø“r¶/ ÍZÐ4KrÞµÖI©áPÏ™ÂÔä\ÆF¥qæÚo´óœÀµûÜëf Ö3ù©|þ—ÞÀÔÕ—Ž·ˆ)ò…#>q<Æ “áä¤H\ÝÃFÐÁ»yµw²÷ýãÒ{u/å�Mýü‹îçñ¬Å,è.uJµmÕ•^i‹ijNFË8&ìm�޲¿½ÒÈÏ3žÑ"*Ÿ¤^C91c v·�I¶[íÝé•¶ˆE÷ó(ôŒíYíó´aÊÌ:?këû~™ö—Y÷ZªZþQoŠ¢x ˜ØPY÷_Õ?à (Š7€ñ€>àû?tØi¤‘Fý [�þü6èÏoCȤVÜî=Œ¡=eÒÞçæóàælGïBOÜÀ¤‹¦4æTîé#˜ôE«]g´{›åÒ«¶o_o\»Ã­ý›9_8ŽeŽ¡Ä%§e1 ö«E±'²ˆ=Q;è#~Õjy:4Q6LqÍ ÂÐi!YûèÔŸC�>�ÍgÎG¾+›Ó¨•žÏÄ)PØe=�”)0›–ô�i_Ò,û]@2§9ä2œížCÞøÃ?£Øð-¢u¥zÊØ¬H^ó›À5ós°;À„Ô5œß–÷³xRGÖ7lHn¬‹\¥º8»™k94‘�~Ÿ²Áõ_ tšÁƒvos×FrÜu~%5ÍŸÎ~úD7Bs’eãšUzf䘟¥éwkyÐîmJìVr¾°f:ÊÃO§âmÝŒT‡ëò(qúþí¬<ߟ•çû³Ñh®¾ßÊfO-Ã¥›ô^è%Ï£YVoÆî—Oûó¿³Ô.„øY‡™\ýY3yàλ÷Yœì�÷Ö;d”Å{ë¶u]@´Õbníߌ`Òÿ_½1tZÈîR©®qnI6‚I_Üb]9ä>��ô§–·}5ÆI~&˜ïà‡ø^»¿@i—`:5¨ AÙœ¯½Éý€B|ƒ­ésÄiíP¾›þ%KíB°˜Ñ€í-U:§žr¾c™7ÑÒ�%R'ÑÑ~W(¸½„�N3pÊKÃiíPb†¶æëTé3ñõ‰ Ì였Ö!.�T €õœÁãß*S{NžUã~hT¯^SýT§üÿ€jª«æ_#�4ÒH#�þŠ^ÅÈcWÀÈÖjÆÅ/*Óœ£H½£ó‘ÒmÔŽ«'DQ|Œ„ð(ÞQ­�d˜ªZ&Èúª¶3_õ{ö?Íx ebkKzNž%€Éir çé2 ÃøKäæº‘šæÏfÇÍlSúR~àè§DX‡1r‘6I^Å,¹ŸM×÷™pÊ€¶ÆûY”ñ@ÞÏé÷GË몕¢W³Ù‡'rM‰^rå²�-&É¿[ÙÝÀÛº<†2vlc¢Öç ýÿçµâ’Ó°Ò„•Þ œÞÍÄqÄ}Z¦lexãgt=|›ññ?€Þ¶¡SVwnºú‹(ëþò±üUé�RcžMz¯jÿ/µ][çú‹'u”�9qý1²ÍŒ¹�‡Ü&Û̘åov¤À'iìb†7~ÆÐb=ò�+hŸjÀ"sSÂúøÓ¡k<6Qy4.rãЀÛò—öիɱÙYïyÌ|;„™èÔ ‚„¶F²ó)ÀÀ¡ßãôn&Nk‡2-àK#e1€‚øåü°9g;ÚøõÁá}é5Ê�±c3vlc2†êÈÓ·Ã¥c*ÓÙOòÜ‘”/ï,¹å6_.§ÎÞ¼ÅÀG�”GJ‰ÖaÕŽu\‹ô]¿‡ÅÉÞ,s åP£0z(}ì\p Åñ�|y}½-]e×_3RY¾ƒµá’ãg\É †];(»ëíèŒcp@µ}}.ˆï¿ÉVýû)†µÆël.Á±û¹a›Qmy÷¹ŸÖ¸¶»K;qÈ}?ZáëJ±á[rۚ𓤦ùóAº>·{£C×xÌ[»âÐó„|®.Y½x°�½C�åk0ÅÔ—ÐÉ DX‡qlz4=yÂ@§ò¶'ß±aˆ‘MŒ@~~´.’�—·ˆcsOiý5ÙáºÆ_b‘¹))‹Æ3Ó|9ægåžùSœé“#}\-S°ÇÞ˜¥[cñ�JfÀ€4ùL lÃÆI(™¬Š}LKzˆÓÜ9¸ —ž½c�Ëh²`mFNazøaNþ�ó£¸(¹¨ÎŸ˜@Œi S¤wjØÓe¾æõmŽ8ë­cfÇ a“6ç{GeG[Ó£j"“FFªQÆ•@„j„*³wžÏ´É¦ff�Z‹�jÕSFËi¤‘Fi¤Q�z%¨¤QB+` 0ªCµˆ/ð=R¡ÿ>¤ThUe3w€$`#pÈÞT3U¨à ÒÕ•ªí› ‚àøOŸÓëùòïÂôœêmB0�‰åØÃg 7/“Ôr:AAA(s¤Ýüe;ypsʧ3,r;KXEÌÐÖ2J@=ò–U!°;­JÅ­«Õö½„¹µ“[V8±'²¸ãÄÖßr¸µ3ÿz,�ŒÜ>´ÿXGl_'¥D û]KÙÚMbÕ ºñ;ÌwÈH�¿ª’î˜{„טï™GV¸VzƒÐÓ $yîHtŽNaìÙcò2ï߯s»‡Ü~©ã!kq½#ŠUeŸ’órûxIEX‡aF/a/GÞ“¸{d-fæÛ!¸Ûf`Õï8¶63q6Ù�‡jdÒØøB�ìy•”ùRb#¥œ¦îÞDÖîB¹-ÐÕŽâ{ñÃæ8â÷ZU*¹÷Þµ1¦¤¿%o\»C\É œæÎÁqæt@ÂÍœü¾'¿ïÀk~äib„dÊP¾ªa}ü™i> î/ä�ËpºëKÏìS_’çŽ$yîHº>ðÅþTC¼?Ä|žTø±íi²*ö±¾as†µ&߸‚¶ÆR@5Ö°ŒàØý\mRHnx>i®ÆìL‰à†Yq–ƒ˜çv’ÍŽ›qÖ«½sá5¿ ôê�‰Iõ„ƒø½LH]HÏÖÂî#(µ’RDƒiÌ¿óòîáJ5KñÈ{‰d‡ërkÿfè9£Z€ÚÕË›1¹O è‡[¸Ûf�u;JšŽÎ�H�§¿ÑãéoñLg7-UøŠ˜~‡¹�~[ysCv;¤°¡|:«*¤Ñu§ÑÃÏâá§SÑKž'_Ãç‘&£ì°ÝKºçOÖ¯#zÀI¢?À’]¦ö”F~ÿh,wPÝò°¡Ã#™) HÏɬÃpÇg½uøÍ“ÊàÎ’¼È’ZNǧãH2×I#Êê‘÷M{h¡W�ßq[5ú€1Ñ» ËW2g½uè¥I�â<·“,ç@bÒÅZï¥Fuj-ëxt-mÅ?à¤LõdõO Fi¤‘Fÿÿè•戢ŒÔaªFu¬T�:¢ú¹X¢jŸ´®UÙ„‰Ù("¥º& �j©Q«îW„|¤”�ZGAЪº¸Ê¨Žº slÇyV³o·²œ¥ÂAX ´~åm©–ªm˜í:¨†‚ÈqÊ2†rÈP–cœ“Ë# °s3ΡŒm¤»j‘zf —2ÖÒrø ¬6pCy˜ö·» «U‰qPÿtL³æ_ÚÕ¿#ªåÔ?�<}Dz±ð„ôt'ú”^a»Õ ÈöýêŸU¥ž§¸2-ÓE$Y¹Rf±ì2Øe1™¦Iר5âcHü>ÎoÃÞ\!áEÌVó]â;¤[xBðf¢­wÅ‘>3âH̱#ÑÒòËkìW� («åÞ¨çµw?Æò~Í9g¶—¶7$Ž\¼S¦ñPÖ"“„Àož6Q¢4ÒZ&*䟓øE6,�M~+*j€R¬vlµïͰí´}Ç ZS…Êæ¨g 9Í¡|Uœ;% öBÌ;‡er;è’ONNWJJ§’oùËw^“F3ÕûRý¬z�>ì6L6™‰ ËF¬hØLêp·h ØÁ`–Ô‡Ð�ý¨ÐÕ¥¬å½ÊëPÜ?ó‰²ÓÑm•£ÁC?N²‹ =/~Lôž‘´H=ÍIËpÚÈÑò©(Šbé\¶…¥Û¤ôÊgÇb9¤}]<ØŠ/P2vÏjÊ”%Ðo]ìàü`6Ó€F øãN :æDÿq…äÖ@”Øl†Â%Ø%ï¢ìî4ÆŠë j3Q]‚)»äÆ&[ø sóë¯OèûÑlÖ]qD[¸J[ÿµ�—G¢ÿÌœÞ�ÁÒÌ{tŠxJ…B„.°³áY<º5eΩnû­ÆMø•¡)G>Ögõi)ŠtÆv«t=ñ�ÃtÉûäc:Whú�YËv"h? ì®-kΞD8¯CËá-‰ Ž`õ¤Ï(/‘ž›’g#€4Ê¿Ðtézfæ`’>]ti€°.¿—MéºÝ캦C×쳄7’Þ‹Îe?òJ&]YÎ�·3è+ƒüì”ék1ÍÕâ¡Øà=6õË„Ïí¹qc#WfÎdWE…üNír@ÊÇá8­ªÌ|¨ŠÌx^õµ½Šuõôô())©uùÿ´AX z‹¢Xu¤°ª@Õ^¹ªÞÕ¤Jk•�¯jû›§‘Fi¤‘F/ÒÕQeþ `/Rúê}`¨(Š1UÚ§ �YH�å7@’(ŠÞªv ªC#�4ÒH£\ÿ%¨)sgÐGÅÄZÚ3�U¢(~«š×x€„ôØÈ ¤AuTJƒê�¤Auü9iP’4¨Iÿ?¢:^uðxx&Šâ[ÏÍ7š#¥žNA2Ìé.Šâƒš[A:—€Î¢(^Q�ãEQt|n¹D`«(еÞå:F3êy|ÞÆ=~nGçÈX†Ï.)d‡ÔËîÖ\-SÈû+òW°ie‰ë�aXìÍda7J´jÅb¤,Op{;mZƒó%#*«»­«U÷©NUiÄѳ°¡¾¶7_ÓÁ¢O0¥eïü)TGUkµ**íä¶þ÷<Ð=•Éü‰ ´Ïvå¦I4¦ñ­d¼H¸»±œ†7ýD_tQâÝð2G<äT¹wî>DWçV\Г±uá8þì¹¾îmJwý}džñ¡™G(!Å�ñnxYvžœÚs4NЧÓøVØ}õ5w~ÑD§[Ðä§M5�¯{›òAþ¦ö]+fEm„�k²¯Z{ûlW|K$S§?Ê׸†²éOÔf:—má‹.J’ÉÏï†å±rÒ»5ázÈü‡RÜ™¸¸8ôã/ÐhYOúoëÊAÕÈû݃»é:ü3ºëïã¨Áû˜Ü’œGÕÈõsz¯U˜lÀ3*m3!Å�166†¼×访�pÅA<îÌ» -MHL´Ã;n»FÏ‘ëAÆ„µ”÷ëüÞ ôn!44”m=vº€¸.ùòñÎöèÏšp)U´Žãa&7.·fŠ©/¥]‚Ñ͈"=݉¸.ùd†PœëÏl�þDk+éhîŽî%¢\‚yD J㎖à|ä»Ìë2„ëF…(•JòòòäçéÛáMH¬8ÈQƒ÷IÙ8WF€¸fG᪻…ÕRúvÅ­«L¶íÆ·OïñÐÞ…G_Ïåõ‰ X[MáA¤®ŒÔ066¦Y³ùYdÄÇŠü7[Àu#)Eöõ||rÞg�Ñš<Ìä™Íz”î‘—ÉVZ¡È‹•8‹ðçh¿+˜‘бñ¬ù‘çÕØ}õ5¡|z褼ßÿ6TÇ£G�à6x\‡Tâ1„ê™4�EQ,V-³©Žq�| ôþªãEÁã†äoë]ß%«W½íu©GPÿ—Z$öºäwÜö¥·;ÊnÁK¯3´u�mÕú•FÖò ^jŸj³®ºôŨºûúôÙ¾‡/^¨ÍìX¿ÝÄÆþO_j»/2áT÷õ¯¯fîáK/u<)viu¶ÕUbñg´ÿ›Z2N-W%IDAT°Tzøï—7‹>Qoûü)Î/µÝSŠýõ¶ÛÚÔ ïÚU÷GëÂî#êÝ®À¢ú¬5ýrS½íŽä¿Ôv«z#üUõóºûÒë¾býÏp« :†?ߦâT%©¦hUÐ7 i„±6]ÊÕï/�ê –´�¥(Uµo/:0 - èQ9–À/c“Ȼ՟N *Øú[ß3'} ­Fú±¢b,ƒÚ‘«¤ô­g:REŠJU1öËw2ú�/‚í8|÷G¶�üªÖžÀR¥@©*(r>UªÏªzžbyí)IUÛŽæ•ÓÙpU‹U�éy©q$õµ…Úœ‘¾Êˆ˜F“Pþp…“žç1ËyL#ƒÊgqú#}à):Ñ™aò“4]E¦ã>‡TÃoÔw>õµ];‘Câü<›²›‰‰RúžŽ ¤Tõ8¬‰`�.èFéò™ÑgLœYÁ¥¢µ”<öe—½1©‘›p™½”ªw5òp6‘j½ŽX�ãÖ­–ü26 ÇôlÔmÊ´Óø!ì[¦¥|E")”*†­…»6ü¤=�{ÝLˆ üˆnýß,ÑR ¶¤Š¡Tþ™qO2ô™á¬îéòá�ó`ȹÄÒ��h®Hç2ƒÐi 1¿‡Š š&\ÁaðA:ÛA±ãÈL.@¡P`꣓¼ä{¾üBå³´§¬ ßv«àûŸ[³Ð¯�ç±Uw(é¶³à´ÕJ‰�ôŠï²’L\i¿ÍzÝf�s‰ Š<³ØKÜX¾ƒ8甥X€ ýåWÚϘFFr{Ø �f ÛZ—Ï]Ãi\;‘ƒb /1�—2Ô »Û—¡k8›Î~®`,ñÊò@ ×ÿÀHëytrú�yn¡ì²ï­wPž˜ Ë–rÒó–Ÿ“¾Ô¥(¡¬·qE]1Ý�5IÌKÃnömš“Œã™XÂ?ï7 é�é[ZÊ®ßcYº5°&±eLvÎZùŒ!_yÒÆ¯—3ÏRX4€{oJ&Ö÷úóÃæ8:4'߸÷¹ŸÊ?å«:HFCÍ—óyB?ž.ÇÖf¦Œ4ÑèÅEQ¨c ¨²Œ(ŠâQ›‹¢¨'Š¢G7V�4ÒH#�4z%z•œÇÅHÅüejΣ UÙ�UŽ­€Fªõ[ ‚°T„}‚ <¡)X¼D‚Œê‚Ax*ÂS z¨ŽªZß°9ö§ʼ¹M{ä4…¨À�8´2�{šÊiÑÐi!Ìw°ÇÞ˜%=KñA};91†=öÆjôA�9éW›Ö˜—Ûfs_b�Ýû´( D~›¿uN™7e7Ö«eÿì#al|�¾t*Ìþ{g—Sþþÿç¹+•D´HZ„dKvZˆ6 Ÿ±Ž%Ë”ÝXf3˜Íò™ËÌ0cßÄ0–ÌØUDб IÑ*QÑH›ê>¿?Þݧ¢šŸñY~ßûåñ~Tç:ﳟÛ}½ß×u=Kgëz”Îúx:÷gßýsüÔÏ”-ãq²·%¢M¤Â�»Ø²N«+¬ªÚ»–“-û³Öå}Ò-Ï0yÐIz×ÒSìsÇ´¡kÏÍ|*Æ0ŽÛšÆb–eëÒ">ZûQñIÌp=^n»Ù†'ÙšßãèKŒÍ÷·Í`®[CT°¸§“z“eZLêÖ[ìØñŒÄ¤U¯u}òŸ.åü‰£ £sEŒ+Ã¥}ÌIyLºívªØ”›ÅtÔøáó�ÌÄuIt¸ÁØ|/:xù(ÛkÓË’È�#gœÂ8úÝ­Í1P‘–€™‡ f& Ðö^Kìb[aÛJ©nJãˆ_áøÏc:ù�™÷ðç�Ûp±¥uÔà14ÊÈp%#£æsÂÜLÉË*—r­vŽRQà¬ýŸ��ÞN¼Ã1ÐÛIþS1Ëì5õ†Ù¿b˜ý+$Wƒ %h—û¶¦œÿ5„IyÉ2-æ“<og±á±ˆ‘L»p.Ñš¹cÚ`mЇ-ž}‰u4n Î7+I \Ï–áÑʹt.ØEˆn1gŽ�Æ­H…aö¯Ôò ÃMQlvJu†ØÏ"rçÇÌßt•œÜá\Ü-ž—Ö~†Ø,îʼ‡�ˆŠO¢³o/“V±e¸@�œéòZi¥•VZi¥Õÿ¦ÞçQ“8åFÎ#P A„�¾@„˜^ETUÕŠ¿@äAB8”�)_iU#é¥öo‘“Y ù¶î¤ßñ~Åö¡gù"¾7ãÙä;�u&»éÚs3/ôi\š4þÝfGæoºÊô KD´‰ärê:Oþ¨Ü6Î-û’M¾Óø(Ë���Ä—ì—™��ÍþåóºqRäfŒù-‘Eöa ·“kµs0ÐÛ©Ì�þ�:þmërû¾v±—·æ’ûÝ&Fþ€Y²øâ}~Õ7¬3ÙÍï[·`ÙÄË&.ì(B%÷%,ãœÍ×ø±Ã8úç­ùQÿ´ØîfGž|6ž#Î¥˜ÉmbT€ãä‰|èÙ›�®f$®ç{›\e=Ÿ¬  üÊÝÓyî Ø·œGŸM÷˜Ü&ãèK —ö)³qKYí\q~…‹_ÕyEsR+û«içð±1§ö¬�¼uýu¾YIvÐxe6®[úbš›PçËõ‚C8!ZáV>è­KJ?½ #’Ý$Ñá‰7Ê-×Ìœ‡Ž´Ç¹ç$š†%±5¿Ѧ¶<ÂN¹�êšâOLs¥of¦¨¼k°­�¯‹ç5ú'«Wö} ç]äïdËðhZ?5"88+7½r©±Øƒ:_®ç€[Nf�ØX ®à2ƒºJ®‘úQMŽ;ÆŽl1h`9£î<£�UWš†%a�Nçc=˜ê(‚6Ê#—‰Å­½xoiÓÆ}ÄÀ„æ™s+bv§�¸)ǺÅ+‡èeËI‰oIñ­Ë<Äž©ÃW±}Ë�Ó�QèÇño[+3€?êŸfÑ…}Ìs/ÀÕh;Öö71¨=Cat¾Ì´ÒJ+­´ÒJ«ÿ=½Σ,Ë»KBl"(Ãy”e9_–åÞ%a6ú²,[IÀ)Y–5Y£Ï…tüdY®&˲-Ð hŽÈ¡Ôp½€ž²,ɲlôz¾ ÎcY98|È•:¤›‹„ôÈÄÒpÏKæŽi£„ûEìI\ÉD9æÌ±Ñ•nó£,C®MC2ëN‡œ#¯Ø¯­k†çéÉåÂ`˪AþNtg_®ÐöW5!a>6澚ÖO�àž-ù£^ÛGêvœ[ö%ÖA…ÔòøÃ~W�¦ØY„Êõþ¢+-Žaf�Áñ|´ö3,‡ ÆNßde&(7‹÷gå;“ž={×p"~'lпäËwÑ]ˆ°ô ÉÅ”«GÓÈ/ôS؈ %p@Ãé›XcXOáwV$Í ÓË:quu¾\ÏÔ’j~/àÔ¤a|´ö3òu”b{¯%nÜíÚŸÔÕ/è¾fç ¡W¿o™ÜIÌ~}êpÛä.t‘~¢‹ôFÕ•âD§ì¯HÃ2õÜÏõckð7¸ˆ¿ÁE†ÅeR—D²ê¨yl¹ ãf³ H\Aë§FߺLýÃ"7Ôëq^yœ96šÁstiÓËR ùnígH‡ »|”eÈ ½@~lv W7‘øº>|âžP­p8¶É¯æhnº‡š¹®˜y˜¹ócæÕ««„r6Xø#e²é`: öI¯jH®œU[ç Ó¢þigÕ+ýwñ>;§uÁ@OÌÜ]yQZ) p ©«_prL#·Ä1Þ˜'�T 5Žø“Lе£qˆ!“Ûij¯¿Øý‚sO‘ƶj¯N«™<è¤âúde=%K�!œ³ùš9o+…€4×Ë ö Þ�îIôOVÊ mÀý§Ô#ž5> øÖe¦]bjüå½›êу� ÝDn‰Bˇ|{é¼b[zé âž°2lOïã{›\¼7:pX„¼né]‡»]EÄ@T}ÁvÕ8ÑÝÒÇ0\ÚÇÊ !L´ÿ˜¹sçNM TÎQÖJ+­´ÒJ+­þ§ô&s5¥ÃG›5œÇ—ìu�ÞÀ¦2‹Û!òOh”`>n"ŽPÊy¼Pf�óˆÊqåcæÊïO_’¤ššFΣ¡¡a… xe™÷°ö˜Æ~ ‡]#ft:Â:‡uLË…czEªŸÅþŠ%¤bU¹¦¯#+¼Æ«†“9tôSô>ºŠÞGW¹Ùò(ÉgÄÈ¿¤«W®�=<¶çQ_§|“ŠUå8�’îK­*[Y»®BƒYëÉê_°ôÌ Ž| À‘¯#ñNê†å]/Š‹%¥åþÖ´�;Ÿi3€·!éêqñí†ôZø‡Ç4~`h…Û>p—> ¼(^.ô1WÑ¡úNÖ7žˆóøû jü1Þ±‚ ³×÷†æÓË]ÇBIR‘šß %‰ç9£é¹¡í‹ãmév˜õ�'¢¦5ÅxO¿Î•ZÙ¬<*&u1z`v{Æ9Ý™ýëE2Ì„óR¨2x¥E…>ªð\­FS¨ÚÅV;1‘g4 ÕÁñôYàESýBÆð3æ Čѣ;ž4>DÌþõ|¼q>�Ï1ºúe<ŸK�Ü>Ȳ> ‰).–š[�€<‘‡¶·° íNôåÝ_38Z«GkÕAuåm&ÇÛ0ú¾£ïÛá�ÔMiÅ%Ï[±J& Ð“™ûPèÉŇg¹°î{¬SÞ&çÓVOâl’ —^Àõ«M9òü;ªåòøqgÝÛ¡»«øí>©âÎŒ�É/Ï_ éÊÛ–âoÛÒð´¨ð9<´!ÁÒAl’¼14ØŽ]huvî|ŽÞÁ”|a@Ìþõè\ôf»_±Å£;¬Æ°ýxºr}o®^ÇèmßÞ&œäB‘‡9äì¦yõ¡Æ„‰|ªZCSýB¶Õy^$Ú¤>g“lˆ›=’àë{¹]@¤Íi쌫î&eBäy_¡G¨+'�–+Ç;]g3‘‰É´,ØÄM»rêw*ÇMNÙ@ç‚]´4(flÄœœœÎããBbÃ{)ç³càåY]ÛËSÓ lkÇÀ&3Xj²‰§$P›ÌÈC†ÍzväÓ*ˈ]�C°ˆn„Ú´ éõ~f}ø.–‹0ô‘ :£/«Yòü¦¦¦t ˆ‹�}>m¾Ié›jÄ÷5òÙØ(IÉQ]V\ÏÕmù„ú7Æ`ß(µêÏ(cÁÿÓp˶¿5U0 ß°\+±mÞ¹€Í qÈÝLÛdZö�G.’ÈË#'rQá+Mï¡pôÔùåÚøí ±OF¹¬7=u>)¶ ?‘Ãr“mèpï{Q`FUP€NIk›z°Ü1Ut¼–-N*£æÚjZFxµR‡QÃ[,i“ ÒÉ2Û�_ó>KÆÎåR}¾ÞнÔYÜr>Æíz"Úùƒ{°3Û€‹Î§Ýۂ𳃠!•jµµZ� z]¾SzÞç×ÐÜõ)¶ñþ~+ š²dì\6º¾À”FaèIÅÌi‚Á¾Qd�B=l+oOã ×<¦®û‚ X鸒OãòŽ@Yt»?÷»i`7ﳨ?Yf·­(³£Q-.æúvˆÜïcy2wOæN"jK ’Æ!G_Å«¾)^õM�ÛUQ éRð ~;~„ѽ-x»F5›�%Ý4â•{#먕¶.RäýÝ_¹†¸¥?ÛÜ”NžÞ ¶™A{�nØýö;ç9p—Ãw±Ÿº–ôºX#³¿ªkI^þHäè«|´'‹®ÅhäªKƒt~3›jÑÖÎTº¨ÛŠpð;]s¹í™Ãb›§œq¯Á–Ž 9Óøö4÷gïÞgØÝ÷F’ øö·øŸù…ž"—vÌÑ,&eùqô½ál5‡ÜÍ8änñÌLÞšó8îšG¨cRl7¢k. Õùâ]"—�p¦;ç l~‚®²xž¾º7€á[) •,óžÎvf°È°žòYpÙ÷ —êûð©ãa²Îþøcòòò¸ýÔ�N·Eá§¿òYûglo¢¯VZi¥•VZiU±ÞªCW’¤/�k%Û_&IÒ’TJž—Ä4ß  8/IÒiI’Z”˜/—, çÑ ð(YGÃyô’$i»$I¿—à:j¯óòä–ü ËJ±FÕ•ßÃmBñÜ.Êþkx��ï¯eBÜ&Ä=)·‡˜,¶¾•ˆÁñWÃ4U%s¿»Ãño[ $DI5Iã&óþ¥ã?â(Sã×ð݆(¾‹îBþÓ¥U®¯)û0�éUòÓÂ<°ö>‚«ÑvB»­¢O°ÈQËZ°ûe{Ø=tëLv3yÐÉJ·ñgŽÿeMºÄõckØÿ¬šÂ6?|Â�KYçÿÙ+÷ Êù0W�¦±è¾ ÷¥ªûŒfC+›8/¯ãõ˄턯Æ"m;Ð!è®RÍÓ!t-)Áo1Ä~^êÁt~~�þÏ¿ÇñÛ>X»°cÇ3æ1�ß·nQ�~1Þ¨ê>cÞÃG´ó¯N߯<ùfâ—JUα÷lqKþ„á± xÜbÖÞG”"25¸óÖõ{¬Ú:‡Ýñ‹1޾¤òŸ¡Ëà9¢iФJ!ÇoûÐ}Í.šœ9€]l+òÇ^ÄÂMÌT&w¬�÷FGÜ“=Q�ØÉ,P)¨ó£þiìb[ñ¬z�[ ÂÊ¿…R©¬¦xŒâ±å6v:ŠYÓõá»X¶ £ê�UDU÷FÕiùÈEés´¶ñ3ð±1ÇÇÆ¼\â%ÛqºÞ›�² £¼—®Ò^r¿»£¬›�¯Ãµ�ÂaH[ú‚µ.ïC½E41dÂÖ/y{› *ŒNŒü�ìèEØÅ¶"ª~\�¶—Ûo-ÿ"rF2)ï!»ã£Ÿ"ò…³ê¨y¡Ȫ´œÓïG‚}‰I«lŒ_¾ϪGpÝò,?6;Æ:“Ý<ýç4@§ioK¿<1#ö(¬4¤'ÖÑ„j…ÃÙ2<š©ÃϾXÜÚ«Ø53Å­Ú:‡˜™‡èzöC¢â“pçÙAã©óÍÊr³ÚG£Wc˜ý+wölà{›\b|ýÑÛó.úz?“ÒO�$S&ê®å¬<”–"uý¼õ\:Ýeßýsté*f,¿ˆñ&¬Ñ«¨­´ÒJ+­´ÒêKobæq0á®>>¦–YçsÀØŒ@{¤'%I2–eùwD�FÀaê#¸ô…óx 8€(žóaÉ6²© §ñïVEÎS٘痫“Z½W�¸愨öеçf…Û7æ·D²£_Å:¤ßñ¦M/ËrË48 ëN£©ñkþúIüixs ™Ì<÷6ùN#¿S8ßEwQØme+(:=8á«™lÙ¿ÜvVm�SîoMÉþq~ô5ÑcL_sñÅg¤"5œ¾é•ewo×±ÓÅþ•eeg_V¯~ßâ½Ñ‘1'–Ó«ß·øòÇ5Æü–ˆmdf…¶»V0wLåï:_¾~BóÉ ö å÷1WùnC×Ö5cÕÖ9töí¥ÌMÏÄ<¦³Îÿ3¦xŒªp†ÒÅo ¾m„£‘3’ Îbv(ÌÍ”D‡\«�£0/ÏVœ¢†I™XǾʟ+«súýø"Æ›+/tÊq)¼ÃË­×le?ôõD5ÜõOÓ«Ù{ÌcÆÑåYŒÇŽ£©7Þº~�Æ;æ3!î ¿Ø‰°J¿6¨ê>£iXia nC#ë ò¹£/«A°;j|á¯_àeiœ&�Ê:Ò X¤zöÆ ö jO©pU/XëÑ�–"„úJïF<«¡ØšGˆÃ·Ê ˆ" ÷—�áú±5tº«,wuû‰ï6Dq·kê>Êähôj¦mú'×væq0«��² ñ:“�Áw¬ƒ , ÀРLÖõxN³¡©Ì קǘ0{¤3ÁccèÕï[º¯ÙÅÓÅûhì®ÌŒDÅ'±pmóWÎí—Q±äw ç€óŒû­g̉åˆÙóEÖ¹4šª8—‰+ØwÿïKç­çnÊFƒêz0ßþ$;}…s§qâ‡KûTÍìN¥ù‘íÎ%“V¼'3ñ9â–ü ÷=Älè “¢Mm9øi('“Ó‰ÊOA¡tÈ9B“3âšäÛº“ß)3ÆŸ”>“ÍVöceØ6æÛ—ÎøkfÃmB1H '*>I9×1®ä?]ÊÔø5ø±#nÄ\Rò)¨šÓæâZnMãó±lâB~�!dšFõ›réx6ãbDE´©-Ù†¯m •VZi¥•VZýwèM8�á@SÀQ–å»’$9ì’="¼´SÉ,#²,a”`6J èÄÏ�\ !ð¾$IÆeöÓ�ë0’e¹¦,Ë#dY¦ \Ç߉ê(й”æÆ©ÕêWð!5Ê¢:^¶¸[Îæ¥ó{¥¸�¼¢•~y?Dÿ!úBoš8Þ— e[Evç^æ•Ú.„+Ȇ÷\DÍØë,¼ï…Eª]ˆ­>ZÁ#ä�¢ÃõîÔüÂ�k'– 0ÔT{¾ Ù¬+E�ŒpîeŽs/sNú7PЧ¥Ã­U‡‡g ˆz<„ø1MÏàVè²L^�duš]KuÀ<‹ßOŠY™+�"(”u*hⱟٹˆ©îþÈjt=…óøû¨T¦R©P©TëëS¬¯/�›9“œÀÄfWhw¢/±ÕGc «1�Õ^!z¡ {9ŸFÐ<Ôˆá·}y´-�îO+×âfWk‡l¡qÈ:åïçþ<±ÝíÇÓY½ã "œœ‰prÆñ�)8ô¸‡ÈŸGqöðXFŸ0F*V±Þ;IWVö+•œÇIÿœôo€�¬F_÷u1¨‹(”uXaHÌþõ\ßÔŠÄÆ·Hî¥âüžO™æÕ‡€BOLºÖD6uSî»óøûØ$y“[cßnˆý$1xÐj¨>}LuqîeN„ïQ¨>äŽÏKÒðôÂtŒ~Û—B·©zòËlÁQ\x�‡g 8p—…õL1mz}™Ù~‚—¹­Ãb†ÍãfWk¶xôá¹�+ú"T7àüÏ$6 ¦®úGÆýÆ[&¦ÛùH? ­³4Ö“¾·)ŒØLÇ ð·ÅsÆÚuÄnçb>µ²Ä.´:Ãoû*øŠ™�‹˜žû€ä¢½ŒœcDµü¡ÄÍI¶µ(VóÁsÂsüˆz<„wrÖv!EbæÞòh!Sgþ�v‘$ßR(«¸Õ-Ÿ¼¢ÑÑÝM‘¬VšÆ®yî¯Xõeô c ;‹ÐT›3Õ).–HHÜÈÍÕë¸yÀž …3Û¯áÊ{>¨ñÇKüòü‘z²ònýAã³A$þJ\ž„Åññ¬ÚåOã³AüòüÙ6?bhhÈdçÆÌÎ3žÿ.܆Õ¡•VZi¥•V¿þvTGÉÌâ׈ðÕb@øTƒÏ�$É8Ô/[…U’¤õ€�,Ë=JþÄ#ÂQ["*¥ÆÉ²ìSbÿ˸-ªC+­´ÒJ«ª¤Euýªã…^åÑ6ÏgUŽmøpœÓ\%ª ãÑ÷«ÊSþUöy3a×OWœkжZñko÷å‘?+¿%UÚ]�¶¿ÖvÖ5®8=ä�4aë—UÚ«ªÁ°ÉwÚkíàΞ׻ç­;DWi×iÑ®R›&*åuÚíõqcúšWj»V;çµ·ÛùúÕ?^©Í{XiÝKà�‘2•©}ã*íšç>¡jÄ�[¿•ÚþÃú·£:†#!¨·€ÖÀ÷’$¥Ê²üc™õ^öZËa6dY.ûæÞ”$)¸$IR[Y–5 v×ñ· :ÊÚLMM•Ù*�rr'“›3ï¤Æ¢Þm0Îó¡Úó]<%�ÔP#t¦]WJÿO×Ù\Ù0åÔ?yÿ÷ÒQðŠÐ›;ÌbôÅÅ•Ú5*È^GÃÃ+Dq@)záe»ýä•J þ'��yúõô ûùDM£MÞªrygÅÅR¥¸�·Sº°äù| /s%ïòk avJuöÝ]J¬çD&³�%Ïðq�ú”Ö[âf¾Ž‚1ðеE·Ä¶íHNmcpªë¢ØŒª•þç|Èh}rÖP(«ôEsÝ~Š=6¼…õpÒÂ’ÔP#Qy2rX·41æP¨2à¤ÓòW°%Ù4!‰Õ¨Æ,“Y,ÎZÌ ^�aSn¬Á=¼|ï~¨•§à[4ÏÓ–Þåÿ¾°î{åÞMqóûÅýéIâ?ýDA6Ü÷ÈSÂ;5û|ÒØtt˜ŸšÎ'v§(ZÖšü§+™ØìŠr>;†MSp Y ¶óÞ¥)sA5�wî‹BDú—|Ë] û¯¾æò¶½tò"ö�¤Çæ«d¢Úg)÷½Ó£sl¸á€×Ô ‚¥ì= •C“ÝTp4Õ�æÑäÔä�ºL˜®š–�\hýü˜‚ÑœÏö„óL×Ù,°¦n ŽãôõƒÔˆ¹ŠïÍxôö¼KÁÈŸˆÿôg-&Õf%±á½Ø1ø6zðEŠ ³Lfácx™®�°¬x4QWiígÈ´Øi,¬_LnÎõp5 &¢zkH£ÐkX:zûØj'¯û|}/ÎΰÛvs ~¢hQ;ªØ”¶vºÛ ,žòV�j< éAJ¿>ääǨÕA¢Mm1I—Q¥_RÐIc+(}Ô½à…²ÜÿÌ/téº;ÛÉKܹÓð•{WPÆ‘,PKÌöo‹ª €»õS#Η¹·›}³™WæœfÃsØÝå-áPmºÊ²~õ…Q]Ï„…Ž êŸi°Ájä³lÄZôN´RÎ'u{6}z}EðØ,9‰ÿž³ÐÞ£À¶4iø ×Kög—+*}ëÈÀSž}º‘Îý|i64•–Û€í" %‰(ê¡w~îj¦ê8À »ûAÜ®¥Sîž'äëСÉdÖ™ìF•!3¶EG¨G“S? S,qZ¿ç´. (“º7Á!.w£�ti`Ž»ä ¸ò°÷™x†nžÌÀ«�)uvÞað]<õ£ÏW™ÄçBXÁ(¾:ó ›ýgât=›Ws¢cB ØÑ¨ãš !®°oß½G¯Ø LŒü�X]™ú>ÏIWÙãí<ˆ<÷™”ìG”ëhnªƒ8B.è¡ë^ƒ î'Û’4ó´® ² œé0às¦íd‹Ý{Ì&Dô`ª»?u3ƒJ‡CÆS™r ã³Ù3ºˆ~¿aIGhá@LÔ/0žŠ“/\?¶†yõê²:ª 8=çÒžyDï‚}€‹'�{Ybpò!ø<çØWalñÝ$ŠvÕ€`ÛÓJÈ}·ûHHíNJñ!b–e~"‡ö=º“|¶Gœ×ã4êfé;™?‚¡PºÉ‘ºõP«ÕìÅõ0޾„Aíxu0%ó¼¦Už¾®íïìûwGãh¥•VZi¥ÕÿOz9�Õ�Þ’$ÅK’”'IÒ}bC³/M(ª&üt] ŠÃP*o”ä'®�$)C’¤ УÔáÔà:Ž” :~—$é"Ÿ²´‚Ç–©éZ?5âØ±cÚêgJ~�úØ ¯,+[™´*9ÙÛþá:W�¦ášVyˆÀ7&¯Ç-;qµ|uÎì»óˆžîïÑzB4Cìga?y¥b|«g¹ª³¯£ªª¬jd˜ý«2k7°É v56eÑ…}LÈBDÎH¥˜Èy]u¹~N×{”«Dù|Vcúš³4¢G£_-\cTˆã‰­t~~�ÄÀõô¹)°+bDZmJþ!,=Âéj&f(�˜}õÈïÎÄÈ?�ìèEDŒÄlcTý>Då¿Çˆ5ñ:“Áño[²B8j£Þ2£ÇÌkL¶ìÏä61$Ø')³Ï«§& #ôºˆY2 6u= 8ÿk3\�“eZL˜JðH]ô7b} ¯",ZÃÇÆœ«iç¸ÿ”®=7+Ç·¶hb¹ó1O‚kZ^êÁ¾ZÙ®~J$—SÏ7BÌ`G_"ÿéR\ÝÄÛí–'8—hÍÒˆÌIyÌÄȸ/ ñ<Üy‡^%y­ç Qž ³;±l⤼‡LÊ{Ȉ5Y×ã9ãúžÀ öZwˆfò “èξLÛjÅlî0 €^>åª='&­"9E|84îõ‘ÒO�këš±;~1iw#•¢C÷¦nJZ˜‡Ò_ƒâ˜ãò‚Œ W|lÌéìÛ‹É–ýyÜbµïìﳫÒJ+­´ÒJ+­þ{õ&œÇD b–ÑØ‹¨šš ‹aÝï�O$IZˆp,ó€ÀNI’g�ÁÀûˆüÉŽ%ëE–l'Q‘µ;ÕñaÉï�dY®º\äß¨ä” ­m�yZWjùPË?@q^†ë_¡ó󃜚4 “ÌR¤ƒÃ¤Þë1¬ºêˆçéÉ•Ú7ùN£Õè¿~Iæ±ì•e½ú}Kƒ`w>H®Žµ÷e¹Á¶v¸¦…aT=�³òPï˜ÏYy(éw¼ÿԾܓ=ÿ”ãxrL#ü"ãÑé²Ùwç1ÓUœ{Šb)²†(뎽g˼p}Ö5®Ãù¥UG;^Φ‹ß®¼Ða�ÉnvÇ/VÖÉ>4�Ýñ‹It¸@Jð[ŒÍ÷"5QTèØ`:ù�ÂqçÇà;“…iâ ¾ã·}Hq8ÉäA'™ç^@‚w8.ú84}4«öúð]èa~ªp65_軥�Á{£#[†GÓ Þ–ñ¶dXœSŽëQ˜>—ŽgsýØBºšQ3×UAIh} ­-lV®Òl̪µD/[ŽyZW|Û Æ·Í`<�EAÏíñølº'®_ �tBÜå:­ˆqÅ$S‡3ÇF+M£ßö|AÛjÅô5Ñøßz…é?C5çùP3וzÄsýXy¤ÌƒÞºJ>ÅÙ3#©ço¤Tì~ˆ� ݨëQÀBk ªç@�XÔ[Tί1„5†õè—gÀÉ1�(¾u™} åŸ×þç¶aéAçëWi6}f�E¡ž�÷?%*c8¶É]ðÙtÇÛYxœ+­lìâ·„;žÑÿœÀVŒlÐYÜ;£IÜÙzš€ÀfX{áq‹Aèξ¬`>4LMÂW3Qw-[†G3VÚ~§dÖ]a4vöíŇ㜘�"r­&lb`C7.Zìdÿ³jüsHtàÔ6†Ë©g˜¿†)£ðÙtOqmïµd¡µ­ýŒÏw?¡ñŽù<Üš£„°¶ö3Ä.¶9#±HEŠÃIüNˆpêé:›•gdî˜6Œ9±\áËîÈÖƒFIt¾~UTèmvÎæ¦¾¨¸P×�ƒŸ†béÆϾœwnÃ�ÍŽÊu:·ìK&ê®åü‰£¬J;@–i1ûGŠç“Ê?«´ÒJ+­´ÒJ«ÿn½ çñ '��@ åCI— XŽ#ª¯ª€5²,g—Øõh�êäÀ®’åž’$5Ì€Óˆ<Æ¥ˆŠ­uß4ª£¬ô§0=èߘä1xŽ.ÆýJóØf´ïK\sîl=]®�ÿwÊ~Ù‘ U�Ξ©|±ÁI4¨=ƒ=«vb«JDÏŽ^Ä»Rtç•Þ�8à<€‡Øc’©Ã:ÿÏ—Éí–'(ü#Ç¿m-8€ élì Ìrü9ÞÎ*‡)ø(Ë�éA—ÈŽ^Dvô"ʼnØÔÑß6ƒiR3³?žŒvñ[ÂñÑÅ~>dˆý,ÜìRp¯­’–Å[îxØ’oë^‹Ñ8!½Üº‘V=Åv¿íÃäA'«,¡9¿¹cÚ0wL…•躖‰ºkéÕì=öÙïãƒäêâ ÿàÒ$ûp›PºH?7b.q J“à³lçéâ}xžžŒkZ®iatz$œRS·Z˜y˜aéADÎH¥i4Õ1BAHh¤aOÔžÂåÔ3$:Ü ÑáÜøÞ&—ì»ó”c6ˆŸÁ¹e_bžÖ•y)3ÂÁÁÁ;vŒ:߬dv§�Lˆ+�u2Ęp›ÐJ¯‘û��¯Ã°¸LÚYuUf`�ÚÆ0¹M {‰Á„FIp 8y©[oaT=�$S"w~¬Ìk¤IŽÏ·_ÊFyuI$ËT„GäŒ$Ë´˜j…Õ�â­ qøK�0ŽF¯æ…^ ‚¹švŽ(çÃlv–©C@`3…¯jöØ�º¡¯WHA+­´ÒJ+­´úÏêM8�a@ ÀG–eC{±p¨Ì:И!˲>"Œ5­ŒÝ²d[Y–õeYn,ËòXà&¥ª£W ª£¦,Ë=©Õñ&ÔcÊL…û˜½wž)GýK¾4NHðï[{ét—éA—pI­8Äõ_Ѧƒé•ÚÖzôcù˜ÏQ§ÿ±&´î�“ÁjÞº~�­o%VÈü[Ö¯= ~ÊÌÖËêü�Ê«OY¤�3²�†»æ°Š©qj4€¦€ËŸQ”óa^èòaÉLêM»…Gw4H '4!•IfÝibø§*Å„ý!ï+\ìׄ5> œš•®_¶ªÝúÀy´<Ý™è)AœK´&DµG°K”zÚ—yáúÄ50ç­ë÷0¨=¿%Šsù²æ¤<®p¹FÁccp•DžáüÔWŸ��}D¥ºtó:<ªkŠý²=‚hëN�µ`t^+Tqå…rÆ)ü"ã•6æ·De;ýÏm¡hQ;ºöÜÌʰmNEŽ— ¨ÍÝ®ý•YÂþç¶à¥¬ãc='’o¿”Ö¢i:xæi]ÇS£ÝC‡`pÁ�-vS¹_â6LÊÄ=Ùƒ�p¼�†åìÃÂé0Îóye&S£‹!'q2XÍçFø±ƒ{¶ôþ½òšÿt%“-ûcf�E‹c‘/)2”}w~‘ñ$:Ü`mÑDΞIuCá>“LâFÌÅÕh;�ÿáUîHMlŽ—z0æI#éÕì=z¯í¬Ø:§ÌgÂñ¤n½…㉭ÊòrIg3,.“pjbÛŠ��Dn«fF»¬4|ÕÍfñÖõ{LnƒU ÓÑ )\)\4²AgÜ’?! qÓƒ.Ñÿù÷ŒØÓ^ÙNFXÆýÖón´xþ4!Ý[¼rˆk8kû›Lº„Õ{ÕØ³°ˆ˜™âcüÆÉÇD9f…ý$еÃÎv2Ë„S:æ·Dòí—’âp’üNá$äë�’Haƒ.P›‰aA¬œRáýÓJ+­´ÒJ+­þ{õ& æ,FäÞ‘$©,ªcW™ufEÀòJ¶a ¼�eùéKË•Ø4ëTôÍúq™uÊI’$}Äì¥F ç±²j«eVf‹q4Fÿ·Ò" Vc»Â�L ªÎdâü–;W¥sĦÌ'9nQí³x7ýÆåg<ªQ­ÜOI·t»e9Ž)mw4Vžåíõüšb�ÿHáòÍÎ{Èô®§J¬.œ2gªÞšªïò+ì'ý@]U 5'½ÇÀ_/bÖh!ÝJªT^5œÌýŸk±$ 6qçí`ÁùƒÒŸ/«Œý€ïmñûc0kr�¬Z6ÔI§”ø’4ËËÚÝÛÂ�ÍŽ8$�Ë< ¤²ë­4Æ»ã,Ý™öÅFœº|AXQ’‘pL²�$66:M¾íÈ™WήQd] �¹G¯°´Û,’ë/)þ"T\ro5?WLXÀ„|5 sÞeÝÒ�,yþ€E½Zq§w0}‚€Ë bë›A,˜ŽÄjØ4ÒnúÀb2¯ à·¶.ìc®,"ïb4½ké‘?ê2µ‚07œ ,'±c-¦¶ô' ĈaÇØ`:¡�ì²×�;NËùeö$,ç­'óÜ FãÊîþäæÌ£Fn$©€¢2¯¡†¨\³®Œ~PZ I‚r¢PeÀêâIXL¿Ódz,erÊÛä2�ÕrÉOòf¿áF?îÌÂsJ·i ¿¾\A)I7Oy>U*5b®Rc�; ?Ä;=µº™dRxa; MàŽ�w¬ ϧºOÃwžr’å4<½›h¶49�_Œ7™ç~çƒç@{}¡�>»ÞJcßÝ¥$&I-¸àjLXQµ?YÆl{PÝp#g­m°NyèÀÏ pR�ó«6Òqð¬º�@ýPœ‹¹:“ISxøþxìlth¼b ¥Ã俀yƒh ïÛ¡¯¯Ïx÷aät¾Äº”OYòü‡QÓhîz€OìNÑ,Ì�‘ :ÿåc:4}4gº|W鵨Üaã݇1"´'-,©MT†ÝµJïߢ^­Pe^eqÖbXga9£þ3tùÔá Ž&?kOí'©H%+q®ÿÀ䉊>9kH>ãCTû,RC�x«F5¬ôßQ¶«aªMÛ€ª´@ÒìÃWix '''¢¢¢P«Ås\^(뜖“›3�êFóˆÚÒ@YÞqðÚ>ŽPØ“]NÏ,w�ôö¼«°÷4û�ž_úøß¾w‘“–˜ššrã§z×3KI�nR?¾Qí³Ê�«¦8Ì2ƒºåX‚uÒ\Êî©7´Ñ�Xœµ˜õKs‰Ø?’õá»8m.ªqj8œU=3šû£Q~�!œ�ôÃÉɉ'��qˆËVrf‹–µ¡²ÅÅÔ‰»ŽýW_£*(P®ÁÛÛ8ª2&ª}º¶xgˆÜ¼ïM¶(ÛW£æ) ø).³¯í@+ÄŒ¤¦¥"œÎ%ë\F0•ä5I’ê-)u5¨ŽŽeÖéD¨Y– dY~¦i@6Σ†óõrþÐVmîczýþ„‚bÁu,P g¦@-1öä7tž‚\$•¶gGÃô3MuÇ4Õ�ëGÓYb²㔉dDϧí#–ÔÈgto‹Wú½ÒJìïø�™W�‹$òKxyçLÃÏ{¹½l×pËò_^VÖæ°w!_¨¿Ãq×<úŸhÎ8�!|¥žD7'Q¸CÝöîè1ê®/¿?ÂG{²XŨ¿tL{û÷«ðê̼ÂÛ5ªQ@+·ҵš(Ä3ʸ˜IY~ÔÑR¹7ããÖ*ŽÝG?“mt ƒ}£Xn²�ºé¿sì+á4×uÍçÆoMQÉ2MÎý‚Et¿%Õ#èþ÷t9=“NÞ5¦c9î£JTºJ[íõ¾â0ªÕjÔj5£s»£'+M’ Jžá…µ)¢[7½Éʹöéõ±ëŽÓ²<-¿ëŠ󛱯m�&pÿ‡ å®arW1Zfn~ß®Sp‰Â€Kè5oKZ/ñå_×ѳ%+0[²‚9 C˜Ó0„,s‰,3I9fÇãñõõUZÓpñ{‰É:Ž7µ¦ð›6l_˜C`ó6?Áž¦¢ØMVVGž>uQž•àeΜ^-ŠÕmJ�q?tëæ|uÕŸïÅb˜,4؇ûUwÆo_€yúê�h‰mj7Á¤Tèpîàpž>uápÀ]RÛ×àf‘.AÑË·Ø‚ûUwTè”4ñ<oч¯74åúÃpnêñ[ý/HޤwÆ8.î=ÂÅ\?qmU;™°ù+jù|ÃÕmù\?šŽmj7ìî{n±…¶uEŠu˜•'ïìÜÅøí ˜;¯ú¦J_I*ÀqÀxÆy ¡Z“9ölºžÄ4Õ�]»~Ç[îËøø 2¢ç*®ÿ ^ G_E%Ë< éÁWêIŒÌë @ýAïòdî$l†�¥Kï�4ˆÌ¢Y´øÈíñ¿_ž2"2Iy Š%ÞyGäØ ]IÅõ‡áœ¬c‰KúeBÖ‘Y¾YGMÓÑÞlñ�Ã<}™wÅ,‹®ún9Ãè+WºØ&³¥cCÒ~#ã¯ó9üg?§ÿŠ]+­´ÒJ+­´ªXoÂy<,–$)M’¤|I’Ҁπ²,gQ/5+DäÛ%ëü¤»%I’K8�©À3 ¸d�è’ß�I’ô\’¤çÀ1àØ¿Õ¡ÑåÔÊCô�«Ôö²nœiœ£/.æ£,CŠo]&œš\«�C“þ¢º©á”&üsH�*·37Þ§JûߥÊP¶~É<–ÑézéìÎ�ÍŽq­v­'D“ny¦´´ÿôÑœš4Œ‰aA|´ö³J«lš§u­x_ÇkЫÙ{¯,×Ie`ü@¦]bjüæŽiÃdÿ…´=,°YuÔLºÄã[¥Åj¦zˆñ‹0]5­;¼âðÜQTBÕÛó.ÇÐ 'ëü?ã»è. ‚`zþ#²L‹™t‰éA—8{fä+Û8—hÍù¢"¯CèZ"Î ÅÅo .~K3Ž‚B1»9ê-3¼Ãyl¹ ê-""g$ ƒ•íYé¿Ãdÿ…$äë01,ˆ‰aAî óõ«t¸)0$nÞ ûš]t_³‹¹ñ>Ì�÷aX\&É{Jgí ³Eý¨¦Ò.†ˆbG¿íù‚!ö³TǵÍY¸¶9ß•ÜÛi±Ó3÷dO¬c}øç�:ÊløÛÛH²9ËCìqî9 ÛÈL*R=ⱸµÇ[É[)X‘Æ}–ótñ>ªçܲ/ib¨ÌRXÛßÄ )œuþŸá�kZéæâ�‰_5…ïpÜŠT¸mÇ-ù¥ÈÕr‡å$® ¦¹ -vrjÒ0¦–¢Ñ¬ãyz2$W'#,‹‡“l;’Àùd+ž|4…-ãYàL�oV*¸ËÕ¸¶®™r|$WgUZiHÛ׉ݱlâÂϾX¤�bÂÖ/Q™‹Ê§�K1¢&ÍVöc÷Ð!ì³ß§à€ÂTûiç_�ž={b©3„ï6D1wL…ùvâÛ lè†dÖkƒ>ÌîT–fòDEv³ö$®g÷Ð!X{Áä ñ¬®òü˜�.ö¯T6ÖðHBŒˆŸ>˜:߬¤ÿõý Œ¨p›ßôe†ëq¼ŠtÐ;4ލú}è¬ «T+μp}Üúµ`ÃÁîÞ{­´ÒJ+­´Òê¿KoÂyLô^Ú¶.åCIë½Ô2ØWf�{ˆœÉ§¾ãIÀM–å²ÁÐÒKí?¦›^b†'Á;\©Nz¢¤J§÷FÇ ‘?£k›q0«�â[—ÑO,­ˆ:1,ˆ1}Íø7&åGË?ßý„¦ƒÿ¼ÓZV ù:¼R‰4ÎáŸÑ„­_ÒùX¼Îdàcc®8jûàÉGS°>ž†Ó7qðÓPÌ“F²=á<fÉã™o’Ém^×÷N«+ÜŸæ>ø\TbÝ/§žQœ›=84“˜æ&¸m§ö¬Ò/Ö�Kp³K)·=ɬ;C†”ÎƒÇÆ0wL,ÒF±õ­D…#x¼~&™:ŠÃZÏßHA'Ôˆ¹Êˆ5qç�[ ¢–�Âô¼H`A[bš›å|€¨øò¹ù…~¼Ý“|[wÚV+Žd fbEŒ+Gk[`pÁ�}öû¨ço¤8÷‡œáQ˜>�Âô±Žõa‹g_vt¨ÍϲZtaß+Ë&d ÁÕh;®FÛ‡Ê~òÊ ¯½F[—±ga‘xÃ’”*è‹-½ëÐ,3IÜ·ïaL³01ópÿ)–a„t5Cý¨&’Yw’\Lõ–?êŸFÎ8ÅÒˆ´žÍç|�(�tó:„8|ËϾ4¾¿ãf³±lâBsS‘;Sc�;ë×!%¾%)Áoa鯄n{mgù…~<ùl<ñÓKòZâE•Vã&ó˜<è$͆¦U¿û5!*c¸‚ÈÉhÚ—“Éé˜dêP÷Q&OïSØ�ŽÒ÷+Ïr8’oLò”Á –ã…^ Y¦Å|Ý…GˆŠµîGñÞèÈ�`} ÿ#v$wxĹ¡X24(“@y }¿òäÉgãñ¸7MÙÏ©IÔJÃÜÉ%f;››îav§�øØ˜3ä§ÝXê ¡ÁÂ�É3þ¶É]øpœnÓ?cÎÄÛÊ6·xå”c~ΫWWá8Žù-‘Îÿð¢ÿ¹-<¾Õ“ó5ú2�éÊç—~H*Ç0ÓUì†�JÃëµÒJ+­´ÒJ«ÿn½ ç±°S–e Y– dY¶DÌF*Cز,§•m@8pJ–åûe¶£‚eY®#ËruY–}eYV¾½”p½€ž²,ɲlôzþ;9�i¾Î ×ç»_Ø—°Œû$ò{Ôçn×þÛ¾GüŠ��9—SÏ”K¸×äõmî0‹ê6åΞ På¶ÖÖ#È0ŸCÓKÁïÜÿ¶c­LnE¥�ÞK ÜŠT„¤ÿ±&|Øì,Qõû(H‡Ñ³2lœPýæô˜yMÉ¿ Sí';zQõ«fgž=3’ÄÀõlv¶t§%ÏÄ"ë\Á¿t³ÜMý»aqk/ñ«„SÔÜt={ö¤f®+1ÍMð‹Œç�‘Ç\Èd'¤s.Ñš‹;™ùÁcKC~%”]{nfiDNß„qžˆ yN‚�ueòÖõ{|8Î ó¦ÁÊupçSã×àVÄŽϘì¿PØìÞG°gÃWs ç]¶ �¦p°pÃtÕ\y¡ÃðØØ/ÛÃ统(8�eÅ£1ˆŸ�Qõ@Ö5®Cñ­Ë8§uQ-+öát½7q^yÊu1(`¿lé¶ÛPSäõú¶L´Géײ~íI\܇ÑMê0:¼,ÉI+­´ÒJ+­´úoÕ›pÃ/I’šH’ä ¸G*ZY’¤º@o`Sæá’$eH’tK’¤o%I2.cÓp/hȲ|ž*8�’$éK’TSÓ(ƒê044¬°ÊÖþÁIôudv¶çEœø²¹µáh8½MrÑ^(.FÒ•E+ƒÜP–UaÓûè*jIB.)ê#Kê’vÖv!g“lØÞ$‘ �EnÜ?\Õ»)È^�¾ŽŒ�¬.‡½0(Óš×ÞIíÚ‘ \*XvÛ²uˆ6�âb©\Qè$!q#–w½8ñ½3z[ÚS¨¹o…*ƒJ[…vI¢°ä|4¿ß\½®Ô±KáÐú[ŽæJâc¢N¬#y—@Bº½@Ô¶–,{»5Ï_ åJÁd^Ü]H°™ÈyMoŠÚ«¿Qï×ð½�ÍÒŸ8Ú®)í Å»ñEŠ Å™§9P} " Q�™û Ö�þ€ŸÎáĉȲ>Žé :ú)fbæqZ»,$¯Á*c5Ê‘¨ÇC0¾w€ˆ¢þ\~ÆÈ9Fè£OŸì¸Þ€Z®Æ,P}È´¬Q<ݼ‰›Å&QÔkב¹þ­04ŸŽûã =‚¥äÞ^Ë“Ø\ý|oÓçÆæ'§‘µJ T4=S�š_øqèè§Ä͉Gä åsBs}[>1¤Åõžt|°€v'ú2d¯p²Ý tXç°ŽVz…XÞõ"¦[>[‡Š*™·ÌÎsñáY’Ïö ùl o_¡8ó4+· ÇójƒD�X§|v&|†‹«á}�Ïá¿ò9ýgíÿiI’4G’¤‹’$eK’ôX’¤ —E%IÚªIë(ÓÎÿ§ŽY+­´ÒJ«ÿz¨ ø‘ÃX–ó¸°’õ?fV²,ç—Y>ˆGTUm‰¨”'˲O‰ýÀ_–å&/mï.óñÊþ´¨­´ÒJ+­ªÒªC’¤cÀOÀEDÚÇW€Ð\–園u¶"*Ž”éúB–å'ü ½ITGÿçßWj‹k`þº»Ä`c‡×î[•b|ý_»o’ÍÙJmÿ ª#¿SåøŠŠÒ þ¬¶æ¿þ5Üä;­R[e¹ýFU¡/4)•iÂÖ/_kŸ„;)[OàïTÿëû+µE/« }þDz_V9Ò$Åád•}32*œoùC ª¸N�FÆÍfWj;]qº@ûÆ•ÚþHiÅ»_»ïª½•× ©þaÓ×ÞnUÒDßüêߎêŒü(Ãy”$)U–å+X4XÖq�eyC™?o–ð/I’ÔV–å+šÕ*ØžTÉrxC¨ŽÝ{êÒ¹`íΉ™¿²¨MåU€��’è\¤bå¹P^!r£"…S[Þ&KOëX•C+”UevÛ{-©UH‹¡Arü‡OÔ´ÒŠ¢%Òà)*²Ui·œO¡$qÒŸÇièÉr¹�ÑŠúj Ҁ̿Œ¾Ð¨›�ù+È�\lçl·o±òÌÁC×–Ö†2s­ÌÉ[y— ‹s̬WW`>J®aFÓ¾<Üy‡ÞµôxÐ[—œÜÉäæÌûCl‰ŽŽL„<¨¬‘:q×yÒØtt˜ŸZž1µ£Z.™™™ RC£z~Mù&öf™ÌbqÖb>rø¹\¿>7eã­ÆXyæp Ù�Ê3™Êž�]œÈSLj$f»Ê>/—‚Oð²r›µS®ñȼ®J¿‹!'iïí+úÒ—ë¾ò¾BV5#Îfà å9¾¿?}ã ŒkK̪µ4Xø£‚ñðеE·äY\zé ®n?!ËúäæÌÃÔÔ•êÕÀµZMff&Ÿ_R±Ê>™ÎE*bV‰bCj}}â?ýD¹NéuRB)cöÕãdë&40\yÞ4ý>dëÐYXD7¢ö“TîtÍå»ÍŽ,ê]i_‚,©M¤L.=]g3ã݇á:`; ¿y›“yíH 5"Ý4B ÷õå+»NnÎ<| /¡³W)Œt|ôE&l¢ZÒTžÖ±Âþ«¯y{Ñ6Ê#°±Wâ5�6y«XÔ» “z“¶ô…r�4Ïø×‰ÝÙ‘­‡É#1î³œÄÆ·”w p¹3MúOÅãÞ4Î몕ûîó8MTò�ÜÀB&3ÇÒ‚X÷|bcíQ›¶QªíŽØÓ@àˆò²äùâ¹ ˆCL&_ŒÄñÛ>Ê*NæµCmÚ†ø5ïÓÁË�¦­y„fw*ûÕ|ž^½ú à ©õÁŒ¿·ñ¯ôýo@uȲܳìß’$�i!e«³”¤~h¥•VZi¥Õ¿Eo"lUá<ʲ%Ëòv`‚óXN’$u��b»WøM‚×_æ<¾)T‡�w4׆S­Ébô†sÙFä¨%±èÐ1íÆåV­i0AGñ|/ ÇãÝÛ‚÷œîVˆñ�‹$T²ŒJ–‡P*ùûåV™=¥aFDtoY,F°íé¿„ãxKQOQ™­"»CîfôdY8Œ ü^U߯ԓÌ…¦¯,S«ÕÜ)ÁSÈ:2²Žº\;wp8:Ó®°§i(s†à³5�·kTctnwÒDE̶+Ð1íÆ¤‚tä"‰PÿÆÈÑW™ó@:½Ú‰[šàxÚ€”+Q÷]¯à ˆP Á6µsf ¼D bBóûËX�£¤R©ÊáWúç®ä"oì/Ê!@Pér¦ñ78¼-Fƒ�FÝ|åZ|ûP¼ —‚O°7n {ã–Tø¼”ŃÈE…ÌèêË,µà“ÎR¯àÊ?•~�<½fÞýTºŒ®N¢ß,ôÔù4^´“9‚ºÃó9¿é£SP@ËïzQÜ!˜o .…zêaÌöo‹EtPÉu.Áƒ B%Ëüvü)¶IÝž�I¯lýÅCÜÚi×qsÿ‘8¯êº×fKdžÄ} @ׇG¡ÚIaÀ%r÷M u{6ág3.΀f}?‘CÌÏ+q0­´ÒJ+­þoéMqõ$Iú^’¤DI’ò€q€’H"IÒI’Ž#*¨B•RKòW”ä<案*®KVÑp�H’ô{I;BœÇ7)÷±S˜ázœ-ã•´/+eÚÞâÖ^Ô�jbž.þÿofH’ÍYÎé÷#%¾%íü«3®•(œ¡ùéyzòË»z-�×}½ Ç¿mý·lû¯ê£¬ÿ|NÑÉàùnbš›°ÐÚB W¸Ûµ?“;•äfMiÂÜ1mˆ^¶Ãì_±_¶‡€Ãâ¾.L{Ì“ÏÆãxb+k ë1,.“EÛ…Ãjó K#z`püiÅ»95iŸï~¢`3ίúÝÙ—Ñ�}YÜûðÕDZ‰IˆH«žLºôo9Ë&.œ·Ïñyë¹ÊóûGŠr>L”óa>È‘tSý[Ð ' òw2=è'Ç4Â뱞qç—SÏp0«�Îׯ2-V8 sÞÅÚ �[ ¢ïWž¯ìçî�8ÞÎâêÑ4š­ìGêê÷!?7íR”РǷz²*íñÓÓaÈ?�™¸é•ËçF|·! @)ð³Å+‡f™IÌIyŒdÖ�ëÇÖðpç@TqØÐ ƒÚ3Øÿ¬õü�°ö>¸¾§1«×p"^±3ù±Ù1l#3ézöC@ @‚ óÉÈpåˆs#™¡)”Ó²`f&¤[ž�FI40(&õ´/VÝN”V~¼Ôƒ•*ÇS#è½¶3�ò@îl=Mãûk‰óÊ㤠U†Å¹rUŠ�ÍÄAg€2óZV-O‹‚Ey+ïröÌH\Ó˜<è$/ôÙwÿœR½73³Ó+¡fšBaæMƒ¹¼5—cÇŽá½Ñ‘‹;ék¢GŸà¶Êuªz•Gžå1 ZU¬’T�¥@¸,Ë7˘ŽÃOàC *I’~%Û©0ç_+­´ÒJ+­þŠÞ„óø+"ç±?ð00,%IÒ÷—¨¯!IR#à,0x‘?Ù�숅ó˜tGüÇùaÉï�þœG±Ë£›Ô§Ö"1³r3_‡¨ú}¸ãa‹dV1Ç, ô ^±3±¶¿‰úQÍÒ/tisÑ�}¹Ê8öב“°Öåý?Õçím¼Ò_PEÈ�¿Ke9vÿŠ4LÀª”ûÝš­ì‡«Ñv ôvr³›¨U1ÇåŽs"Æ×Ÿš¹®œÓï§0îl¬Ç‘n»ãf³‰žD³éÓhÓËRqî;Oþç´.´ ©Žý²=èy—TQåõBî°¸µ—zÄ3)ï!^±3¹{@Ì:„®¥ž¿óêÕÅ·Í`Œ£/Q#æ* ª­ºMÿ ·éŸÑΪ+í¬ºÒÆR EN¥d0Ù¡Ò†ØÏâÔ¤aŒ½'rBâ\ÿÁÝ®ýÑiÑŽ“Éé´³*åmfž)S+ö1!î ]{nfeØ6Åáø©Ÿ)?õ3Åßà"sR+Ë;ì⎇-:-ÚÑο:ç�Û°ÜA8€ÏÖ¬&nÄ\œÌ8·ìK®[ž%¦Ž˜©3œÒ„˜æ&üsHò ý0ÌþUÁW”•kšË´Â믜rÈŒU[ç°u©à&ºØN-ÿZ[ ªû »áãé]K€¯bnåŸCê0 æ ª§A°»âp�hiÏ„ã5ˆ°ô eHuÌ*MÜ/°vÁÆP&-ÌCD\½CÚÝHÂlîbDÊýޏø�äêÑ4ŸÆÂ/‡“[{Sã—–»n÷‡Ìf¡Ê‚8›<&Cœt��{žSÇÙ™Ù#�yÒp “ÝHkÉÚ¤Õ4T“ÃBÂ-Ç;n*œ0=ß�9oc6›]wp¯0�§ï�'%Ø Ùáii*`&‘’ÄÀÉ{ ‰I]y¾®nþA8®ÍûE´=|�kaKÃÚ$úV�ÖÑJH’¤ÀÛ@WY–«tödY~(IR"¥©/«Âœÿ¿å@µÒJ+­´ú?£7á<æ#f‡É²¢Y(I’Ù€,Ëë%I:�¨¨ú²,K¶a+ËòÓ2Ûè€Àp§ÕÑ«lGI’²JÖyÅy, ç)Ò£ :*+˜Söge¶Æ![>äODÜ·x©HJÞFnNé:Ïz»²ðJ ê˜,Ô%>saû,‹¯øåù œÊ¬SÕñJϪíÛ†ÆÉ\Ú¸‰åc>Ç´éñrx‰²?˪ߡÃÄ;;+‹rÇ[mQ‘*´—9GÍÏ…i�™óÒ6Êö�Ù¾?K/TlesËÊ&É›jÏwñ”FìZNWsÆ»—°G*9G©Xb}ønzêtRlœƒ‚±xYeð…ryç²PV•û)Ë¥�šæwÍÏ—¯×ËשH.½ƒšß5?Ï[áù¼ˆ›Ëh«§k…˜�•M~&6ÝG“ŸÉJïX®ŸZ­&¸àZ¿\᱓ËâÚ:9ñóÏ9tvñf£ŒÍÝÀܺu¨“‡ ǃJG Iœfw’ÈëÉJ±£ÖÏ�qèî*ï–«îá<g’Ó9à+Э_Åö%ÒJG]¡6?Oò;”+Å%?Å5Øa5Vä�}x�ù£b¢Baå Û�§óE±ÄÙ¶ y:o2µ§psõ:Ú~ó�“yí8Ôü-f'ÞàæõAä¾cÊÑì¦x0€øO?Á52‡û#�é±¹ÇG_dKáME‚:ÕaÐF õ¹q@ákΜv¢Q�½ŽÀ°ûHîÃ(î–Ï£my<²1‡:°,ð«{5 Sõ">¥/.©Ç8àô6=B]¡îr~'1þ¾¸H-ïÛñ¾®LRò6`j}}öù’-¾u˜ýëEv¾]ËÀûDµ‡Ä޵¨3R W–›æ‚éD>ƒ÷ÖöD‘Eq±DãsA„zÈ<Ô“™Ô�m6x 9{=Y‡j?“– ioVŸ0’�e}VG5!ÿÌDbý[p>ÔŠ€�Æl7KÇÊS|žÄ7‰¢s‘…%o5ƪnßľC˜Õ07öeóáÇüP+�Ù}XôàKž?å»�W>SÿìgmEú»û�Ÿÿjñ¯§JBUW "xºÉ²\Ñÿ“/÷1l(Mí('Y– €‚2ëÿ=«•VZi¥Õÿ)ýí¨I’"€ˆŠ«�á6Û€XY–ˬ×á<¶‘eùZ™å~ÜF¹Ü �³)Ëò-ªC+­´ÒJ«¿[ÿ%¨ŽÕˆÿ?ûR~ ôwY–ó$IªÌö!œÅˆt[ ™,ËÙbÿªC>YyþÿFGUúWPUÉ ~F•ö×Euü‘ªByÌcÙko—’(‘×Qô” ×ê÷tqÕX’«G_¯Øð¿”>Ó(é�שDNUj›ùC¥¶1}_ÿ�ºœúúl:-ÚUj;vì�#×*Ó>û×ÇÍìYXôZýÎÿR¥ýÉgã_k»ßL¬ó?šßÿoGuŒD„›>@°¯;�¶ÿâv_ÆpüW :46‹Cáš>�”d6oÞŒƒC<íÛÂìÙ³Y´h�÷Fr­PEKÝ"Š’H 5bl‹¸rÛ-‹¯¸¬S~öëuQÙ,Ü ÊÙ+Cu$§lP°רÏÍs½)hýK¢˜Ç_Eu\p¦Ó£s¯œ«ÞÃ/*í[›èšç�›7–É¡KxÿwCÖÖÎåŸíÕ88ÄÓ?°±RðäS‡ƒôu-L.ãØÒ°4N*–°ˆnÄãf÷ðO|ñÖ¤&6`ÊJçмQ>Þÿ”&ý§2V*3ãV‚Ü‘Z¾H°Uàcx™Ë)–´±tã†I~ùÜŽèÏI§åÜßH‡Ÿzý6ƒX´ý:3ß½‹Et#¢¢¢ðôô¤øÎ5N¥d°Ù½�²OÃËt}x„ÀÎÉ�)…ÂÎ8™×ŽÚ4àæÉ ¥€ @l}3從^í@+Q�Q�š§$P›¨Pzýöígÿ@QP¥,¥³ËN,gT òçQXD7RÐ#�#~UöÙÙ&• ÛKï�ÅbV­Å†.��‡“žÞ�ÌÌLì¿úšÑ“‹‰LL—q´<Ý™E½Z¡Ê¼Ju£y¤XÿBòž-x:÷Ǽi0»wg3­Ý?ÌGÔ£H.†œ¤óä�Äõ UŽ×ÓÓ“k;ógáGAûìt &gÁ®ÜƒÐë¥ 6›�£¨w]Ù¯CL7N>Æ©m µÖþHü§Ÿ”¯UOFVc«r¦A*'óÚñù%?�(ÿ>‹(„yØõ5úÛ‡Pÿž'7Lrh•eÄ•G 6cw—·˜ŸšÎ\+s: ù §L˜�inˆðÖ�}¾Tp@>†—ùö¼.f�ÝÐ�~�€ûOiyº³b�ŠŠâƒ¶¢PÐÕ4ñÞ™v~Al¬=QQQlt}‹°�ç l2ƒÓæ›x™ETû,îïÄçÚ]ÎtùŽV>´ÖS³íHõ†: Ê¼Zîú·ƒåŒjTû¢Úg)ïÝŠW6˜N³e"ÿõu‘ÿQˆ€Ó/-¶"þ_uB¤ˆ˜ ÈSÀ�?ã8j¥•VZi¥Õëê�8�²,ß<$I2j–äbì¦âÕŠ”T“$©vÙ°UÀ‚ÒJª¯…ê ‚°�? Qªª|{Y[¢W;œ( �jß^”¤·6–¼¹s1îÝšà�†…?çõx¾…Þ銿¨¬=}ŒNžÞå–i5(Ž—U•ýe›ãiâ¼^=§²ho¹/Á’e\Å(ýfa°ñ‰_”5-‹Ö¨H»{Ú«£©z²L¢ß,ï˜_a_]óT*5Œ6ó£øÌZµ!ééédeuäǨÈÉ}ʘË©iÓ•#x^ºÏUû._G‚÷ÏäúQ”ø;!€MÚ[ìhŒI¦Î+@l=IM—B ’#pM† ÐB�j¦¸y²£¹ÈË´i.ÂO¯»ÛQgC ¨Õj®nËšâÝ˯øSÔš¾�{ æ³öÇ(~ð  Ç–À æ³™�.ö¨óŠ�û¨P‘—¾Œ ÖØ £Â_¡ãS²²:âÜFÏ!CT•$ LësiÏ<ä¢BFÛÏâ ‹;£sõØhP:B§V«1LU/ÅrÄ© !�ù�ž°‰wøíx3eýµg0¾­5÷®Í³]‰lzˆå&Kȼ��áHÝz¨ ؾ°€‹‹OA.lé¹=Éa!’T@@Êj€ÎNH†/\¡0¢'8¯çf‘.:¦Ýè<¸®'\h�¿|ßâ‡ìd:»ìD¥òF.’XŸº‹vuòhqÚ€áïÎæ‡ìd:Mø9ú 6ÃÆâ³éùc/’�ø9,äyõC #a:k0Ý‚¡×ê@~ÈNæò¶½mMà«øöÙÿÌèè½d/ÏÙÃc©{üïoæ´¾+Õ ‡‹ëŸ¸‚†roŽª‚2«qþÄQ:ûVcø™:\Å¥X‡#%ïÀœ‡™ ©˜ó0“´ïzq6I Ýs¬Cç~ÞÜÙzšØuÇvl ÆÕâÎ�†è%XáëÛšhS[âƒA‚‚2¡ç´ý7 õ¨=k ¿97¢³o/ö.$Öy=õ½Ë²¬j\ù\:Kô¡d|<¨O§ «jÄ{Ï¿ç|�¾ÜxT“5>`Y�ȉ¼];�.)¿â-÷¥A/wènÌÙY\óÜÅ„Ø ,ð]KËWÞç?ûyúWlgß7�óW%Ër•1¥²,ç=þM‡£•VZi¥•VŠÞDµUE²,ç”8޵ÿÑü£>%ºŒ`:úhH’ThI©ó¨Aut,³N'þC¨Žª4ºI}åwo¯ûJÕÁ�­*ícP{Ê=&'ƒÕj½ÁîåþNLZEŒ¯?nv)Ä�x%øµÕ8ä_ÃwUä§~¦U¤ºaùÁɃN2.f®ia:$0Õ¿‘c9—hÍÑhq-òÇ^ÄñI2ÁÁÁœ=3’‰aAl4a^¸>‹.ìcÛÁÁÓOœÂ ½@ê>Êd‹ÝTQì%rÃk{FX–RµôÝ‚n ª#Êù07½ré³l3ËÇ|ÎÀ†nUnë­ë÷—I“3h�¿“õO+¶ó'Ž–kCDÁŸŽƒàâ·„ÃÏó�Ií{Ó³g9F9u= ¨ëQ~¶`tø!ΞYnY�™×¸ï!¾ˆëíy‹ÇÈnÖžW÷ð¨®)‡ZŠÿº ž£‹kZW�¦qõhç†*Û9Pã¶ØM%®�9óÂõ!¼ô™M7¯Ã„¬!¸^ua�ÿgÌ™x³dr¢ŸXþÝ13‹À.¶Ó6ý€Ü¼±äÛ/eNÊc’\LILZ¥<3¢PRi¨Ôï[·÷ ðí{c7|<3\�srL#Î­ì§Æw&lý’'Ÿ�' ô «öúPPø *±JfÝéû•'nv):$�o_q¡®ûîŸãÔ¤a˜'�$®�9Ñ%×Ð!&‹fÓ§Q3וéA—ÊÝ›˜UkiýÔŸM÷ˆžDDޏ'¨ @Kƒbä 1½Â~nÉŸàà~Wi¯‚~9Ðó.w¶ž&ꊒU@=â9—hMçëWqM cbäʵ-¾s�|[w>ôì�¥Îò{”~ÆÝ¬Éž…E ¨ÀqÔJ+­´ÒJ+­þ·õFœGI’úI’t@’¤I’ ÝR€-%ö:’$µš—t¹*IÒ7’$YȲü;�ì–$I–$IFTm}—¬]òû1I’žK’ôQ:ôØ Õñg5Ü»¤ÐÆæ”[~Ày.úâK¬SÛ&ê¾Êaû»´¶hâŸZo“ï´ —‡¨ö¢[IQ™ÿ6…¯&*> ÎÆ-ù.äÄö|û¥Äøú31,ˆãß¶æÚΪáþfç"Ž×`@Í$® CÐ]p§¬wÞ¹ –M\8à<%±Å+ç��Þ�“¥ø‹;[O“–…]l+©ɻݨ¸ê‹ÇpNë€ÁñÔò`ŽË Å~VÊYyheÝ+UÈŠV4NHgl¾có½^áTFÕïSn ÃÓ¹?Õ 7Ò!è.>›îa°±C•y:âž0!él“Š™YùO—²Öå}ßêÉÁ¬BvúŠ��lCá4ÊÉ3þFÕ•m9ÄdØ ÛÈLš M¥ó±´L³¨?ÔÊcbX&™:8ÞÎÂgÓ=еã»ÍÂá 1"%ÿŽs"zÙrÈ `¢îZô�ã€[®.7q]�çX½W�?:ë¡ >>dÑ…}l4Á¸ßzÚV+f“ï4b=Åû½Ì .�[ âÎÖÓ,ë'œ�×ø’Ý»³ÙébÏ1ÞÔ#žÇ–Û¨ç'˜¦¹yciýÔ·¢Òÿ"^κœz†|ÛÒÁ¥ØbÁÖün³#ÍcîTz¿´ÒJ+­´ÒJ«ÿ]½©™Ç�¬) DBþ-D8‹ûÛÀUàp™>3�²ͽ’~O|Ç“€›,Ëe=é¥ö?§Kæ=|Ä9]5Y¦ÅJeP�¦­™áz�혨»·"•2Sôwé_�ík|­â0¥üÌ9›¯ÿŽÃzãó["ù5†(Ëì†�ÇÊ¿›;Ìn`v§�È�L» ÷l±¶¿Éùd+eyNîpZ‹Ç}]ã:4¿)rDï{äÑäÌl“»0ÓU^èâÎ3Ž�¾¨ô�p¼-CªóntOœÓº°=Að#G‡"ÈðÕÐàÖ~â>~�@SÿnX{ÁÚûû5ÁJ_Ì|Yé¿Ãdÿ…d7k¯4M®`§GçÈÈp ÁÀ�eýÚótñ>ÅÉÖ8  1\N=£Ì”Îhß—­ùÈnÖžÉþ¢FÕÁOCéñÀ@É¿Lt¸A¢Ã ‚ÇÆÐ'g òS»“½ˆÉƒNbåßB¹7‚ûcmPŠsˆþÉŠ=ï*77Ý÷âFÌeÕ^¢§)<Ì »ÓÿÜVØOb†ëqü"ãi:xû¾ôº?áu&ƒ¸†yV=‚œÜátÈ9@¬£ òwr±_äï¤AþN ¼Ä}®…yõêÒÿú~®M£}cþ9¤û}Vò0Ì¥W³÷X׸O>šB¯~ßÒª„›¨;ý=f^c‹ÝTæ…ëSýCá¤ÍOMWÎ)ª~ ûl D·˜¡A™„¹‰ð憻a‘6JÌrº¿'ŽÏè-böÕcÈO»ñm3˜$S®¼ÐÁñÄVBKž¦]bRÞC:Ý%ú'+23Euaooo®McŠÇ(ÆÅŒceØ6& gÖàùnŽÖ¶à¼s™DÿFIL׳à#tæü‰£\«-Eòu”™÷äeƒÑJ+­´ÒJ+­þÿÔßîcĈšäÛ/% ô !Ft¸yƒÎÿðBý¨&-vrÞ¹ ÏK¿È¹Ù¥P盕ÿÒ1mñì[¥}ÇŽ?.4è #Š«}±Êª\–a•Ú8øÃ}ý]º©?æ•e]¤Ÿp2 $°³ÈW˪£&#,‹Eö1bDMœ{NÂ<­+a�Ä^3UVe�ûkëšñä³ñÜ_6†ç³Âøm�(¦«f`C7†KûÈïQ¯"vºØ+ÎeR£›HfÝYµuÖA…Ê6½½½13‹à§~¥,Ë YCˆ©cÀäN8ÿk[즲Ån*×�­a!"W1µàg2²”@ãèKœ_õ ¸ˆÖA…X’ß)œ^OKg2¬ì”×sÚYuUf¶®¦�ã§~¦ôìÙ“‡“ôýÊ“­o%^BЉÜP.ÜôQ˜>‡ŒD-�Ù‡Ef‚w8NñEŒ77» Gù¼õ\âFÌåb¿&d�ǸßzÁr,¬(ü#�oõ$*CܯÐn«}q1KÔ&Ýv;ù=êÓEú‰�.öfÿŠ/ây~þE8¸¿Gïµ�©û(“;{6(• õö°ÉwY¦‚á˜`ŸÄNá°b¬œÇä61L "È0ŸgkÄù]ô>@Tý>Lˆ{B]�‚r³ö_'v'1i‰�ë1޾„I¦óíO*¡´ÙwçáôàöËöÐ{mg¼Ã¹{`‡¼¯Ðÿù÷LØú%„¯&xlL•3ÉI.¦ìK3µmß#ÁÀ� Ã|’\Lyºx­²ÄgL«,#šN£F±kÄxžf6ûºåYâ¼òX×ã9N)ƒ[fÉ㩱؃q1ãH óÀà‚;©?WzLZi¥•VZi¥Õÿz3�ººúËS$y”p%IRÛ�odY¾UŶ†K’”!IÒ-I’¾•$ɸŒMÃyThâ²,ŸGÌtºV´1I’ô%Iª©i”á<VØ€¿Å¦�~…vOOO7r}S+–ž9¡°£7n¡X_Ÿš_ø7{$5&Ldf=QÈ´ó Oõâh»¦Üu{›ëW›2ÜÞ…vÖ¢dµ,I¨_jå�%mÄ©_xxÆ€âb©çQÓT*²¬_ŽQørÛ Ž˜}üÚe"±ðW UåˆÊ© ë™R(I刪«}²W¼ÒOÓW­V—kµkG @ë[Q ͭƉ'¨÷ë eÍ>+k/ŸOÙóè¤ÞGNÀ%† «Eq±DóÚ;ñž~�É”|Ñö´¤½Á¦rç¢á^ØŽ¤«÷JX¹µ ²Ziz[ÚsíäC¢BE�§Zk¤½Á&¦e�b×[iôk6�Œ&bî‘Úš¨ÇCxž3šãwÙèó Ž•cO–m*•Já`«dŠuJ[ÙcRéîS–? Ê£ð§¨)V˜Š‘u]”mª%‰‹ÏR-iªx§š´R®¹æú­.žT Ê=‹s�Šœ_}•Œ¾ŽL¡¬Ã«¾\±ê+ø�%ÏéÃ3˜ÝÞG±¾>9—0mzœ–ª~X½…܃ø*¦7ㆲÜ4—nïE‰í–¹¾¿Ì=Ë#óZt¨¾“™ïŠ™KûI?ðüÅP²ŽN¥æ~xDÎ`Ä.1( KȲ>ó“ÓHÞµ‘†h*¦·SºðÜÆ•õÞ¹¬÷ÎE¥R¡V«™;€[fçI<ՋˆÍ$$nD–õ•çåZžD§üýÌìØŸõ�'2=÷Õò‡â� Þé&_s¥`2:Éç¸øvCDâ׫ޏ¯êbÎ&ÙPhµ˜˜aó°‹‚¹G0Aï ¢X_Ÿk'r³åÑ’ûVLíO„ó8¬ÉlÖ�ü‚æç¸Ùò(7[Ux¡'kÖÆÐ`;úès%ñ1«‹'�Z%v-çQ+­´ÒJ«¿Uÿ œÇ‡þ[9�Ö±>¼RÊ˪ÒÞ¾‡q¥¶„|�×ÚçÉ ©jVcJ|ÅI¤Ýñ‹«´·žýZÛý ¹êïAëLv¿ÖvòŒÿQ©ÍñÄÖ×Þ®µ÷‘×ê§).V™,Z¼ÃÐ{cÕ�oe‹‹ý}¾ûI•öÖ^ïžœLN¯ÔVU½�蟬*µqnT©Ímúg|`•è!ö•Ú[n«Ô¦©ëð::·¬r–ãÌ=‡+µýë¿‹ó(IR;à} ­\…ç*ËrÙÒ•7%IŠ.I’ÔV–å+šÕ*èúã<þ[Y»�áeÌú}[ÎÖzMkf™ÌâIcg:øõ+gSëë+¼9UÁ«•/5v§K&ޏBáàqœ<‘…i� t{‹%Ïð¤±3Éû¶áä=‡I½é³l3a!ÏùýäGÄú ©¡F¼U£›Ì`‘u.€Ây¬n4Qÿ¨TÙ<Îe*örœÇ\…bû“œÇÅY‹t¢.ÏÛð‹Ý/l]ZTåµÐØ7»ÇºÈ�‚#h)0 [¶¢(æ:OëX)Çlð|7æMÅLNe¼K�^¶7 +�¥8Ôü-T™WQ›¶!ËLÂ䉊¯bûY0–•çBi8`8¦¦¦ÄšÙ1"yÄ[Ó2Ñš›ÝÎýéIâ?ý„Ú4Àî·ß1u«ÅyýaT7܈Z­&33“�kÔ§@Rò¼Ü1™{s2¯‹³ó‚Ò"7o'¾­°Ë2/•ë[Â¥\œµ˜Ì»åaÕ›:Ú)HŸÇi蕼ªn8ФÿT¢¶µ¤á€áÔ¦mÅ&K/dF{ ½«qÑ�ð‰šÆqÏú— ^]ÆQ˜`ÃI K7»Ç·?6aöHg<· Š�†³YûI*wºŠço}ø.N›o"óÜïä ¿Bl¬=º¶èJ*Zˆ™²ãõÔ{£±µ;—Ì/£bÉÌìDnÞX,æ+ÏŰ8/®ªètÝ›CÞWø&öf™ÌR�~!”>ãO;³hçM%´6fæ!ö \ãÇÍî!—yfBŒ”gÜÁ!‡„©öã¡@êi_ÿ^HK3ž’Àâ¬Å„Yy3S¤'OTú.ÎZLÏÄžx{—¢{rr'“›3�ÚOR©óý:Œû,çGýÓÌhß—“5k£Ê¼ŠOÔ4ÖF·¥ãà¥7×ôw233¹¿?¹¨ß›ñ\Üý+�#~Ux°�›Ý£Y´*s� œ+Bp«Q�Y&³ðеÅèrOî™MÈŠVÔókJÚîh¬OÿξZi¥•VZi¥UÅzS¨cI’¾n÷%Iº¼…àmþgå{3ž˜æ&t‘~¢ÇÌkÊò_F Eþ¨Ëp  ÿ¹-yÛnAØÙNf�ÿgPo‘·ÿ ŬªI3óVïU£Î7+‰Ÿþç+9v¼É¬;†Ù¿rå…W^èaéÁ;÷Eø‡ÁóݸnªôÑÌ$ÓÿÜvìxFˆn1®„èSÿž§è»­�C 9â܈´0¢ê—V�ž_áØE¥:sl4ûEÍŽÁ—Øb7•®Ç©V8œïmri˜° (­{xây²ƒÆcm“Ç-±á`wÎ'[‘üù…~Ì ×'ÝòŒ2sô²¢ê÷QБV=¹ãa[®5õïÆM»Ò– ‡á€ó&¯Aמ›ËmËÇÆœD‡È§hÝ!š›u#”ŸÊµ ºDÛÃb€$9åU.¦…Rûóå�^Mºív¾·Ée`ü@æ…ë³Êócn˜äp9õ ¾mÄó°Îd7ß›”>³ã¢Ó¢ªºÏ8_£/ߘä‘}w- 6)ë�Í÷bßýs^#üÐλÁy H\á« Qí!"g$óXƃFâù±²»­8Zg²›Ïw?Q¸‰^SopÀ-‹´Q¬ºêH›^‚�˜\ÿ8+b\q²·%*ÿ=>Ê2$1i•RXÈÊî6VïUã» Qœ[ö%×-ϒܱ¶rŒó7]壵Ÿ±Ñ D¹¦nµñnöÙ€md&rÆ)šÇ¡é‚M9Õ¿ÿR‡´0öÝ?ǵÍ+í(wþ—·ærìØ±rÏ7@ѲÖ4¾¿æÏŸ_éõÓJ+­´ÒJ+­þÿÒ›Buú#�3‹†ÀqY–3�ñˆŠ¬C�¦ü?öÎ;.ŠëëÃÏP¤Ø(ŠˆQ‰ì ¨šMD�š€FìÝØ5ÆXR,1š{Å^¢ J‰(ˆÆŠ¨ˆ X�"Ø(;ï³;°"š˜äßd?óYvÎÜ;wÊ®{æžs¾RÞ…·úoA¨ú …¸NQoŸ�ä4jt³�r''©—·€{¯³ÎcJê²ß½í´[ÌLö!á F[ö଻TUóH{":.ãä/áZ"å¿—öÂŽ—ns(~9î…Á\Xåô»ûm™J\VÃWÁ²¡+W=lÙÒGÒõ{Q%ÆÜ9›÷>þ‰Ž&¸�wexn_üc’q«,íÇà–äÐDÄî%yB¬}(ê¶﵎ü)ý˜_ß®™¶›YéáG¥¢âÍm±Hÿ�Ž™R� ´Ö¦¬êô˜Iž]ÙÛù{«Œ—÷;nÝԬ̬ڵðmÖ›šZr†Y,fiä&ùºÇ†¬  Gªº{:<”‡‹œ½sŒ!^ôëW ssiÖrkÛÖ8®á;›<âêtãÛ5qü$Õªú!è2gï“—eAÓpšPZ)Ôãú8’¼òɰÜD¸ÃBî,*;´ iê6n�Pã-N9ÄÉ#‡äê±_§HŽmÕ›,Ith*�–•“Щõ��Æœnò& 9j�‚‡lT ¥Ãx"ZÒš—8Žz+9(·ôHL§ôâJ“#òvÏJ¬¡nÁ6ÞÝ$C4I±f_n†¦9H*^¥7á‚Ö>�klÅ*°1÷j™sÈÔ‚y›cØæ›Æw6yÔM¶eô{¡xË"×L=£\ w–?•äU|»Ð$ÜX>Ö ºë©ê4•INÇ8yäCNËá¼�õ82ˆTWszÕs'©ÞùAEaQoæZ[°! žŒÆïhxš-�¤dÿoº®lK¦å1:wî ×my?O>Ž,‹h¼½n”;§ ÿnþ.©Žv@%¤"9ó€ÝH³�š„+W`£(ŠGEQ¼ <î-Õvõ߯H¹“ã�íêõžêý85€£HyŒ‹€H Öë(Õ¡ÁÛëßÅ}ñ‡ÚL»•�¥G¤üƒÐtÆF[öÀgÝuÄwæjÐQªú­¦îÜ�Ù8׆màG_û)nsu—öÌÑV‡›´ ¾Æ^—ž¬0ª�á©v¤ízù f›Xïç®×Ì%®8Àƒ  8MÇ�åOqzÿŽüøEô;v€ÍbæZ[ȳëëZÛ±/·ˆz+éþ•'']šáDz0s§ðGI¿ð´k ÃY¹‰e§zH9�w§²/·ˆ“G•óóðO�îí+ÙÒCŠÑ˜|†Ú$²BžIýÎdƒ\4âÛ5qXzDÊy '¢ßç¡ñ ªÆŸÁ½X÷bÙ)�)B\rª¼¿Ñ�sÉÊrã’ºÈt‡}4 7¦gµ§Ìµ¶ x^©œÎ6Ç0ùï\ófÕ®%Ïn·ˆN#tp}\:K³Ÿ{]zE59zÞ¼RGRAAAAAAá¿ÃßQ0GÉ)í'Šb¸f¥ 1¨¥:€(à]AÖ#9��Ÿ�Ãj»%RásÊôÑ I†ã0¥R]Êî\„\õ6åfA0@r@5ÈRÌ)ûúWØú]@èKÚþ!µþ%œk¹YœJjÝ_Ñ9#=ï™Æ2pʆz¦, Êã ÐrúmJ�ê%æòõ[l†3¹õ}�ÙÇ4×§�lÍÒäU@‹TƒÑ1kG¦¯UÊgêðE-Å¢Š{W5Îæ¸¾H\°7oúdc“aÌU 8·••ŠG6ñN…Ĩ.Üy²ÏsdÀÚÄÏÅæ·Ì[÷À×Ä—¦j•RÇò¸µÙgöÑw‚™W½éÖ(ŒÞ[¤é;ã™ÐÝCQÅì´tècBµ †|ÑRÅ·7=˜ZGºÖËNõÀ¦Až,˳®‹ :™7¥[¨quÉ ÁÐ`5Og¿Ò÷Þßõ}úW·544¤ àY¥)øç¤:*k€‘òUÀQ7«Ûû#Ém<Óï YÅáŠT‡‚‚‚‚Â_�"ÕñÏ’ðâ+µ3Ôÿ}9ö¯ÄuÛWj¿xÉ+ﲪßê m/“êx¯*ãðиâr=÷¶uÃÚ½Ð^št‡ŠxQtÔZÃð mϦ6ü.˜>©Ðv6(ï•ûÕäÂ?�—I‡¼H"䦡ÿ+�iCÀ«ß/ �í­Ð6B¯âZc<>|a¿é/¶WÄ‹d<*ThÔðÕ$VþózIu¨í〶À»@ ÐX.Â]QÃÊw'ó¬ Çÿk©Žµ5¥.VÕžÝ2U,x|[–lØ?aW,—íIsssY^ÃäÄÉL1™¢%AQoç<~Hp£ëç´$(B¾Šdl`c¹­FÒÁÇè¬\Ù³"ÛA—±½´tU :Ùçe™ƒá7×16°1M޶åÇÐ/Hžþi©ŒÄ3u³ú]!o ×S �Ì›NÜ¹ù)®d÷Ñ“ʼYi…T5SóïÊçxmÊoüz+ 7÷xDgénNšåAÌ’bå¶ÉËÆðر[ ›ËÇczÿGÓ2qsß��õPNˆïAI fI±²Œ„|MÕù—eeÊÊmLwا5Þª…�±€IƒJ'Öƒ¢öÈ2e¯Ï [s­ëªú H¶�Þù‹<&£³¼¡çÇ'?åð¦�C30Ó�¾'Ë·8{j6NlP³¤X’¹sw56ÖCåbBn1©|ÑR%]Û2³¡$[ËÇz'¢2ž£âȼ*…vþ-…ó¿šßB–ñØÐÕLn:ðÀ}õ̤ Sꢃ‰Ž&¥}«�ç“*uøÙú8á?¼ÉP  [aÑ™}øí? KÙܶ¿Œíõ&¤µ6åJ“#Ô¼Û[>Ç5³p14½ ¨yï}ùž(+ÅñøóRý6AO_–a‰ˆ�Šçhr\5Ò#z¶ÄÝÓ.ÈÒ÷¸ÐïÎZ~ºL•9Ò�¬~×ÈçÂ)Þ‡š�ˆÔSñuÊ[̬e†YR¬¼O�ôαöß²¾ÕYRÃ'no¸íe±a-jfÞGÌ>N�íòžÌ"Ãé:¾»kéM‡HŸç¸qœµš‚n£¦”\½ Ûý>žñ?—ÔP¤:^þçRêœÈ¯�‰¢(þ"ŠâEQ—;�Éê.Ò�J‚ ˜>Óµ¥2ÿï¥:^d›˜;˜AGk)T;…ê÷ݺ|%—Ò×,@¹u:::|ì ¬ÑHL¼wr=Íí,°»’ÊZ²{·ô€áÎÝÕ¸��"ËH‹ä�è ªgÖ—·MØtôÊ,’sÖïÎZ>Ð?ƒýØ•ì¯:–�ïÔ�Ãr+’éø½!/’±³½Œ[\,v7¼Ë� Ýgƪ§uޝx>¡òåß„B¾R�¤ÝÓ�ôN‘ÔwË_Mµ‚TþÊ�OZõ xÏÇ¥rÖ·©õ¡ ?.%+ª);Ö1ûžTáõWS ªüÖ�­oáb‘>«Ï5D_YyTzªx¤æ º%wc©ãRv¸ûS­ oÔÜ@n �*•׳ñ�ˆº*D]?D±¡mo­k�4vú7êàxÔ�û‰^dŸ¬¤%gÒ0úg<·I¹rªâ^ì0~Ê(çkœÚ6 ±X ½Þ�œÜ.9Ûb‰ K=h›ô·åûE ±»á-Ëgè :œÕRzm£@?úöW‹þ�)²TÇ�nG¹’ã�eãPÚ�oǼwZ‘)9’ 76¤°Dк‡ K UÒCtÐA—’sñ ºÉý™#é"_;»Þ4õ©Íê;ÛI›ø>½·mçÁˆõ;‡®yGòö gd®?53ûqw‡”ÙüÐMß0G,(ú¦w6%ðÑ^éš‹º"¢®ŠaÉYTþÊ�‰±Äfoê @›ˆ^¨T*T*%�Ø�´Ÿ REe=AQä·Ãio›Æ(Ýd½Ñ =Gz§<`½Cu ªŽ¥ñQCš\êBÐÏÔÈêK¿˜T®äøsgó#�ýLÚé ªÍ!ƒòÞÂ">˜Ã�¬å¦%©‘Ö{ø�Ijü]ý*R Œ?ì< ‚ÐA„_A¸#‚(‚ß3vA„Y‚ ÜAª†º]W$Ž}HN¤>0W„‡ê>L�f(5ã9«~½¯¶‹‚ ˆ€3¥2©Žã‚ VuzÌJ×�ЛzV+âÀKÕ‡›ÞQtÿÊ“E':ñÈ©e¹>þ[¼ÊÌce S�ýàc`-à‡Bz HDÊE|\GšEÔhFø#å?îEñÒ¬àC 7àƒTdç’$À5¤*­M�‘Àl 'póu–êø½ŒwžAÍT)Ü劉•ˆ²‰`­( ™'–üô§ún°E[—­Ï4=Y‚Bó£üÏP›djÝË~évýŽ(·.©ßLöv¾ö§âì+bH�)©Ëdy�C²‚ ËMäš—ÐÖæE}^\¹vƒ×ªÌ÷øÓcšÚ¦quºÉÕV±¿ER¿™$Õ­‰W±.I^ùì¹&EZw­®�_¾R—•åFhZ&½’{Véý횸çî'³¦uÃÚIy'QËÙëÒSÖTÔ›p�î&úÌj§<—ÑœÊë˃  P?•žÕžbÑ8„›Ó¤Y¼^ 'bí}°Ü~¯í•ÄæOÕr'gþìlG3¸{MZXu ©¾ä¼',[)çÝ´OÕ’Ó°¹Ý‰›ö©T™ïAHHMs*süX{I…b¾3Ù@Â&8MGŸiz,�Ü$;»‘á�i°e6ßÙäÞ¡ë|Ç‘i»¹Ô¡JŸI¸^ mC:Ñ(2Ãä‰ò~[÷™ƒ«ÿnÚ§’~-†ê�‰÷�¼“iV²&ä­ä&œ½sŒ‡‹¤Ö¿HúžqÉ©ÌÜŒC¦ìuéÉÖ¶REÇÌÁP›d rW0 CÓÒýÄ� æñ”Hî,Ê‘ó»äpÞ-}Î@íy¤Ú§GHC M'ÊÚ¨çºÖçü¡tzDo 6d';¦ gí´«Áî›ÁÛ±×i{ž˜BiV¸IŠ5«:=¦ëʶ´ãnÐóóx²²Ü˜èvøOå)((((((ü;øÃΣ(ЇDQüLÅrŒ %Ž‚�¤ ª�� âlEQ£Ð8„¤‰ºÍt lm’“ºJÝO.ÐUEM" /’”Ç`)ðp¨­.ðÿž½î™±Sš¥ ×+ÁC%ÉBì¯<’3‡¡ºW�XËãÄZ§iNeZ…õ¨°¯¦þR5Á�;ñm|û¿|¬º=9ñ¤¿,dn:åå?45?€öv¾Æ‰f1x¯u¤ZžÛ_>>kûKx%Næ’Ý-@Òª‹ YAçÎ�ñ*ÖeÚ­ æÆTà§^å%8ž¥x^ ªú­æAÐô÷•ò�-�ô9sø#"ƒ 6* +Ë�´[åå(^…š©ýåkþ,k2¶É Z÷™@ó׉¢QTãVÁ~Z_#²¾ä¸yëp`ÄIN<éÏ� Ëòâé"ÝOC3(¹|–p�]¤GzÐ`ËlYæhÍuùK2¦Døi�ébhç¥c�èSn¼'~êϱ�Aô™¦ÇªN� 6*�å4nÔLüâ%t輞¾;v²Ñ)»€aÜè;U:�ÂXt¢I^’cW§¸JJ@çºÖgoçkZú�[Å^²ôË~ïs4¸±Rv,[XuЯé”^8.ÇÙp¹–ŽêéðPYÆãQð0y½é÷¥…-,="±‹Ë»i|MvƒÓ6ö<ª{Õ˜äÙ•øÅK° lÌÐîRÕä{Øq,d“<»2>Í›š²–£F²c`„ð�I>½ºI3²µH‘œÑ:ݨX™¦9•i:<žm†¥9ÄEÝÖðvìu@*àáР5— ÓàÆJÚñ�ìGTЊ&GÛj]£ø1Á4Í©ŒSv*Fo¿Yî*((((((ü·ø«sí‘d6Š¢X_EQk!”ËAÅtQ"Í*´Eq‘¨]úUÔ_Cr Ëæ2º—DQì%Šb5Q«!à1Zð/ÀÁacVyáîטöŸP½»4àí@ ŽœßE¥¢æ®|ƒh?y¶!,,Œ’Ëg‰7·ålPÑ‹¿”g§~¶û™ ·S*Ü燗W‡›eÀªf„ëì’C%7ßMèiQŸ�,�ÜDœËw¯)/5I÷ŒÑ© ±aòD ÚDQÐ&ª\_UýVÓ%'÷Â`­õ^c/Ò¨ÏPE¦²½�9u K¸]_**ãiÄã)‘´cf²~ù†rQ£o×;ÒÅo!NïßÁéý; úÛ´öZNø^÷�„ë•ð�Mη÷³ÅF»pRõÀ�{¨l¼• «œäâ;o˜ïÂñHWw­!©Þ™Z°Ñà(C-ü¥ÛêØbÕ»¥zˆªº…5'ÒÝœ”zC™�æA›v�¢¹;æ�“�½•J iEYsîÅ­u†«).i�W£ùœk Ô1‹xÊU¤…Òu�§{�¢FQŸ�üÔ«§\ÍRe`Ó?%c{>qü€t;'ý·b!¾>“k×B?B$±^MÂDÙŠÁ»"_ Â¹–+Í,u 1“9gŒðœ¿‹E»>§åx_šgœ ʲ¿íšÎ{ >¡ÿ.fD?fÕ éÌS³×÷ ݶ5g¯÷9¦[ýBë£_A] ôBè]Æ iJëäˆÞ¾äOÎqJ§t6xÞæXFìü�z=0~÷{ö_(h{–3¥…^9úãœ�¡~Ì~TêsY¬Ö¹,ûšýtá^”,É*�Åæ˜1õÄ�ùä±!ý~ý™~@Q’¤·X`»†íoë’e³CQÅá:‘œ(î�Y¦¤™8¯k3ŠÂJ+_^·%k¤jô++¿Ï”´�ÄÔ\ǹ„ûäæ™Y˜}>€þ÷9ö�s]ëSï£UÜS• C&'Š{ÐÏk-Ù1•°¦#�@•·ö’÷Ä»ú<�ÞF–MG>‡-±úŒnÙ’b¹±�ÏÓ]9œã 9Ç8!ùš|/Ö¬yš’È™3ûÑK©$EâSoÌHŠÔÏÆœï¸¶ ²RÈr—-¥MD/°½Kõ«·`x'ÖvÜŠO04XMdÍ_áƒê¤ÝÄ#£PÞü±/W�—P¤cHÕ¯ßbùâ¦4ì1œ€+Û¹èû�>&�í uÑ�KF´–×ÜÑ�nÉRuØ&zÅ‹:$åKçrQãÖÄïÏI s¼M(A{‹i�x­Ü÷’¢ó¨     ðßäOé<ª‹ØôE1XýÞ ˆ¬DQ¼[f»5HUW;?Ó¾#ð+`*ŠbîKöÕ Ø ÔE1[„Õ€�(Š�žÙî)ð¡(Š;žÓÇ,�G… Ptÿ"†N}å¶{«ŒÿëR†F�ÿ–~_ÆÉ_*Ö7|&âÇñHÐ+·µ6ìöò�*`*¶=“ÇÿGˆN±®Ðf—XqŠÃ«é”‚TTíUI¿V^ JêN�_¹ßŠrá�;w®ÐV£FÅu-Ÿ—æò{9÷T÷åU@Â&Ú�Gmþê™Çtõ«%p·Ìú²¯ÊIõk [½¯6e7PK{è¿`_ÿotÿª~§N�ÊÄ–ÝÙÞ ‹øúåt5Ⱥp¿CË1R§4ݵ¬Þâ³ZŒSoó4i¦¤÷—‘NZmñܲ„»w?ÄÛÛ[¶ååyaßÓª’<ýSfœÑáUu|yȤA ¯¬¥ó¨_æáÈé&ora[>÷-cpvv&..NžéÔ ££ƒ³³3ö_}-Ko”Ee`@òôO177çJíSZ6¡DÀ"¾¾Ö~›¨ÿÒè>«ó¸é`–ÔVWÄÊóI9ÍË&…ëä¶3ÎèpÖæ6 ZóÉO9 µðgCÀ'ò~3œ®ãÚ{½º}Éð›ëXUw°¬›è7Ž•ñÍ)˪AÓµtC–*ßL=pžy]ÞD'û¼tÝO®â”‹Æz14ƒ@—$B-,e­Àg92àŒ<¦ÀóÁu([l†ÓoWK<ìê3ÅdŠ–n¢~QZõ}G>¿>Fgép÷ 1V�!f�\¼Gs}îDTÆáÝq´-ÜÎEé?7�Ω)u¹*�×Ç'áØë.M ×1yÀ5,âës¿� ³ïÝgÿ„A$®8ÀÔçÁÓ’�o£2o†ÏÚ–j¬j4ãââhë*U•õ¸>WƒµZ:œÉôØÚ€ ]ÍdmÄ3‘G©×3Ÿ¸qÜøÑ”À‰z¼›ò®T-ÕþOžö"²8=[®˜æ3.q1)i$ì©ÍñŽ ±ò|Â�Ÿ¶²ÒúC.ÙÝbÍEjû7¢ÓãU„æ·�õ. ªôÅ!BÊ!¾ÝUO>÷¸Ð;å›fѵº>i­M1s'1Ñ^Ö#ýÙîgŽÖ”òA¿;÷ ÎÎÎø�EUÜ ‹÷´ÃUÿ¿|ïý™¶ŠÎ£‚‚‚‚‚BÅüÕ9�ÉHN�üØ@„J€^>£™úUã”ÆMA¨]f_ �R©-þ?é<þUýΜ9×Ì~-‘¤žò”ÂçüÓ8”¿G˱"½Åg—ù69œí 9X'ê}/kL–]@ÒžôõõÕÒôkºŠdû~öí4DÊ%û±_y{†SX"ðf¦;%6î¨êHE?ÎY¸¡_/…wlÚräÈ{“F�&ß³ €NMIJ‹Æ¡ÔÑÑ‘µKéxV áì›MÙòÖ»<ð¶&A?€m?ÏÀ;k(iß§JÚ 6þœ�îäs¼6†öG%‰ÓÞ'´´.ËjªôE‘›‰§å|�Õw¶kíWÔù`b •NcƒoÁ{>æBéÉ_ZïñLtˆ@,.¢ËÙ«ˆÅELª+IoLªÉ´záÜÙ”@µ‚T+èÀúz)Ô¸&åaž½eÉou>'ñ sß0Çè#'v¶—tgµewÒtK­Å>m­<¦Ó.¿¢/”0ðÖrVßÙ.ëR¦Ü—Ï�OÐM­ó«rYñ³qwÅ!o=ƒòÞ«Ž9VRÞâÛU*Ñ(t¹Ç–Ó9µûíù™‹iêSÃ=r/o7ëë¥ðh¶Td¨ßž6¬£7Ñû°ˆ&¶�g %©Žnù«‰½Åã§û™­É148u¿ws½þ@¼“Ʋú\C¦;ÐÒ ½ŸèņÖõ¸§SQDGù~€4’™¿™&=’9“œÈçªo±qhKн{ë×àR±nq±œIND_U€na¡|O<ªß„aI+9]«ïV©Dµ‚t¸+�I£wiüx·¬-9z åcA*"|Ò¥<»ãyt4ÓîË]Þ¸äK¯ä^¬q\ÃÇ5ÜY^:Ü�݆Íb˜ätœA04�HúVé>þ< ¯b]Vº~„Õ()÷õä‘R©‡ÓᡸÛÝböºóÜÿl)[K«‹Ö9PÌèÀ¹XÛkç�j¤:¾]ïˆ[eé:Zêö勾ftº­–8I–ô»®lË�'ykÅvôwI×n.£)èT‡°! ¬ó•Š/ 5Þ"îœö}´Ö°|xVZkS¾ñ%÷j™sxaSŒ8‰»Ý- M%u¡-}Î ìa@|g„o‘™+Ïÿt™ §�x?8›~ýªÑªƒ¼OÇ…Ýžtê§¢7µôùTóR%Ó6÷¢qK�Äâòn@ AŠ /.T·`{nDÓ.Í“'¤ ½Uúî°�YL  Mw–?Åq¡ÊeP™·c¯KUk »ag;Z–o°h‚Eã®zØòÈ©%+‹Gp1TúznÝgVe´ûµ$ROz@8Q�ªNSeûA—ú̳–¢Òkk—*(((((((À«…­ºʼ¤^6�êuÂ3˳X=g]'¤j©AH³‡Ÿ …•#Í6UЗPæõùRÝÜ‘íÛ·Y ¾µvÓ¾êfƧ•Ïóüðí49&IYœ Ê£úQ+²›õnpºòÛ¸M¨Ããf<É øCû_2xïr´B»ê^5tjI>ÿJ×�X L4`°ìT?á¾Ãæ4£š�±Ÿ¿I­> ^¸ÏGûÇÑv�4“Õ¬ËÞ0¡añ^¬l�p!¯ž.=;¿çGYjx˜`x8•ÇÉ‘Lr:ÎãÖÇç¦lŸÚ¦çšI?ÂÓj”9´‰õ–ÂR an_ƒöö¥€ê\÷䪇Eþ ˆ—â÷U>%dó˜´Ö¦ì?äHAÄ—ŒœËÆ�ADÌkÁw]üÉæböqöØï¡¶eeõFà|§t¼GÎï¢y›(J.£i+hmû”ƒy0֣ͣ—1Ç1 §÷rÕø®þÙ´Š}‹ƒæ«1�±„èèŸÙæjÏ=¿uäš—@‰ÖyàAâ¾L r0ɸ;•[ûúf"©ÍÚú©œ¬Ò]}¿ôÕ:sÓ3P©T´÷]H“ð‰ì!šŒd0—ªÝžÉ{‹�Éš®Ánòi,¯FW¯ÖãÑþqè2uñ.Î^>†Pã-ª}îOæœÙ´‰õ&Ê #ûzB²3ua΄p¸Ô€Þ7îó� K« Ö8®á–Ÿ>MUºdf>ÆÐt b1ÜôŽ‚›€þ6†v×å`^+­±OºOtÊnN9ÄÝšÐdCÜJnj¼ÅŒ�éЩ˜»WçÛ�í©7aWs28dÚ’�æ´ž>õûÊýV�?CòŽ�ŒŸḟ‘èä�aàÖÜóÛM§!§!|—=¨ƒäì6ÕW‘»¶ ¾�l9õ�¾;vÐÁÀ€ë>³Ñ©ùˆ‹§ñƒýH®zزlñ`=ýrŸ‹Ë»Éýr5×¾�§ŽÏ«çÇ(((((((ü»y�Ç¢( ¢(jµê÷�et?EÑT”~Û•ÿ2Ýܾ×ôSf RÛó�ꀛZî£.Ðè"‚fêÁIÊãMQ�ÔRC€Áÿ�Ç¿ƒwß¹‰»_cŽëÏ®¹Å’>^²£E5Î~Dw}ªú­–EÝAšõ*¹|£G¿`5ª£#¤j¯ÆFk_¸ßÃu"_h·hòB{ŸizŒÐ[Éç ÞŒO3f›oÚ ·^ÿÓneЫž;º’†ãøÜ�ôëW ›šØ {Iå¹`ú„~æ¸OøŒð5´òäþ.F7K`’gW S£~¸ +]?’å &è®g¢Ûa*ÈKY†xqöÎ1ù½FÓ¯IŠ5�âç’Þ&À½ZæòvùK¯qá´ç¥KŽãs04�È Knì'tp}HˆC†â•8™ôH�ç·Mž(ÿ}:CJ'qEñÑïÜÇ¿Jªãe¼HÊãï’êø3¼Hæã KýWî×ìËÕÚf¯;ÿ¶;üÌ+´5Í©\¡íe, šV¡ÍÕÁ+÷;<·o…¶g£®þη÷¿rÛCñË+´ 9ýÊý¦¤.«Ðæ•8ù•ûÝàÙ½â}n­ø^ä4Š?ʳ4„âyÏ�;ú]ŒYåõÊmÿ…üO¥:4<ë‘ e׉¢¸¦Œí’ ‰ÀAš‹¢x®‚>Êõó;·)ËNªã÷¶=sækí±ÿêk�.Ñ*ÈQ¢#×2ç3&誴ÏÓ‹leíö_}̓ÐmÔ”7s+ssÚ‚{¿‡³³3ÉèêŠDê©hrÏ•j#GḰiÇ|ˆk™û\y�´HYI ÇÑ#˜›žÁî¢f|ÑRÅ'UêPõÖ¦;ìcBA©z‹�¨Ò’¨Ø?Aú²’¥"âÆ±åƒqrÑ »qåYT6Öþ²ÖÈ#hÎEjýKr±YŠã)Mè¨FŽCs.4d^õF…Šnrã§­TΛ>²ýbhM|jÈö1¹è os4nŸ,KÒÖu›\ÈæM ¹_Sêrf×,Zyùp:<”V^>Ø^o¢uí 6¿OæUoúU-âR�.7f #yú§ÌÏ�Ïökó0w—ª{®Þ:‹Ö}æ�Ø *fI±¨Ì›‘YûGÞÈÀA·'Ó­�–“�ÑjÑȳxèÙrëÓ�ä|4Œf–îÜ’4¬42,w"*#ªgІ¾™ö·(:µ™Pç%˜R!; wm©•|¢%Ñ2�eì÷–¾nf:Ç-UÜoà‚MúÛ<2 Å'WÊ+,UD§bnnŽŽŽu³Ñà(oY×`‚îz†»úc_Ÿgt˜Дy›cÙ³ÿ3zuû’Snuˆ,N%..³tWjû7"×LÅì;™Ò~Õ2 ÎgL¾ñKÏà“ŸrhêoDqfeùú\w”ªÌº »©iÄþ7ÞF'û<¦÷ï`—Ô˜ˆþö,;Õƒ=בZÿu’×2—?mehßé¬Þ: ÓKè·«%iE»d[K�ŽX¸Ò¬Ùtà/‹ñOö[‘ýu�êEñ—gVMa$ÐV„+Hé!_‰¢ø€ �$ªü�Uÿ˱*((((ü·x]tÏEH9ŒçÔýÔzÎv5Ëôó‡uÕ!´…e¶$©Žñ¢òí¯j{ÝúmÙòS¶oߎÁæ÷™{)n ðYw]k]•$Ãð<^dx0böלI¨Û×Lc&î>L�’H8‡êƒ 5²¦¯ýB×¥ma!ú7ê’SLJåEæ8†Ñãñw$Õ­‰wjGx”ÆãïsOGz ûA‚7#î|Ï{VQ€ óŒjƒ£”W ”�Ð.täõºÏh:ÞøÑý¾Ð`ž/Éúpò—p|Ö]ÇÏi3€ÊÆËä׊΅®yGÄât-›FîDÃ3第„ €¥‚·­zΠ]z¤_“×]žü˜«%:ԺԀʟ·afj¿ ™j»*¶4l5êxú†IG ƒræÌfbî` } Ù@{g (êpF +¾Z�Þ>3(Ü ©,ökÉÂc+˜†û„ϰ6ìFÞ·Wˆ_¼„Ç'iµŸ|†‚"â²Øf(9ü%W/Ðãñw4 ìÈÇ0¬ãì=øO�5qÅL²u9djÁÕ £ Üꄵ÷A‚¿´­•Aoãü¡tº›èÓ"Я±IºÄR·/ìÆ²Án,�ê0sp3¦öwùÓcþ_Ð%'ƒ©mÊK½$Õ­)ÿW0жÅÒWË4Ki´nX; ÚDaؘoÕ²-[Ûð�Mk×`5ªIõFà¡’òüFG, @ØÃ:ßqìuéI’W>³îÞã¤Zf"ç£a ?\€¹Ö´}¼«ŽÒoé6Æ;)°_ÄÈü»¦FÑ`ËlâÍmY½u É–¬5 §©›lË£øy¸ÚÙÐ1sð C�@º�…oÉáT?Ûý HyÒ#=°6”¡lÓÚËmv䏸>õ&¬cV»Bªä´_Cü ¤Y.“Ïûãyt4Ç�õgæàfŒn³W;:M¾@ áiF7K v`eù<¬ÉØFJê2b-�“âp‘}¹E˜}³TKÒää‘C®mÅðܾ< Æ£ý’ôImÿF>¥Åy^Apá1Òÿi+‘RD® 9ŽPþ!é½2¶ç1 )LU³ÜúkG¬    ð_àU æ˜#9ˆ Õï-žÑy<x›�€ºHOL¡>4†c�꾞ˆ¢�Tq5èª^â�C¢(&¨· Cš­l æ>@‚¢óøç³Ê‹¦­â¸ÝõïŠn.¥äòY¢m¾ÖÒÏ{–­79{ç—wSrõ‚¼>:Åš«AG+l÷y‚÷ŸßÞÎר[e¼\åóE¸ÚÙÐgšzSÏò(xmcÏpr+×ÇñÓÃJ<ÕßJÞ·WÙs#š’²q¼/U�=!¾Gƒ›™ôˆÞÀá…M‰s9 köÙü–C�m;Nº4#Üa!I^ùxëâ¡vÔô‹úP-Ï ç[™—\óYÓÒÛÛ›ö6sÿã1Ø/ÞÅÎ÷ûòp…”{ñnÊ»²>dÆåÎì›Á¾é˜sL븢¨ÆF§–M“±AQûL\V€–ù,;üÌÉŠÌ¥GìOL$}„NnÅ+QÒçì3M�¥‘›ØPÏ€ª gQ3µ?^ª>4 ìHQ·5ôݱÓ屢¶Å:,;ïȲô½ÜÿL»R®F[t«Ø‹p‡…|ßž§ú[å*£aaa/¼~quºQÙXº^�C8ið'—}CHHB�·8(·ôHâ\°ÄAÒðÜï}NÒènuÂjT%ÜIó1¢¿=¹æ%l°Ë[+¶32¿48ãáŠåt©Ï,3¤À‹†Çöâ¹9€=×a�èÃ�/–�—œBÍyhêo„Ñ£_pŽíŠnã´4Æi©z.0Öñ“œŽÐ+¹WƒŽâØKÚ¯OnÖŸÊ-QøÛIšm�ÀFAÞ(cazÈs˜‹ôV³Xÿe#UPPPPøÏð*Îc’$Æ·ê÷ƒ�Æ9j©ŽfHŽÝ‡À É)|W½ýS¤°;`ð)°]ÝoÓ2ûÉn«ûØ�ÎZVîÃi¶ò,°˜ªÞ¯£"Õñ×ÐnÈ.^âÍ\)ÞÊîÊß¾Ï�×ÿå}®2yqÒö¯#?@ÿPºø-,•{xZ]º¨õ~iä&úLÓÃÙp¹<‹´¥CWÞ0¡ëʶt7ÑÇ.ñM²"se©ˆœ/Æñíš8â F1ºY‚<»pxÐi6:…P=p ^‰“ ×+¡¨ÛêEJÕ!‹ôw‘ToEÝÖ0cç}Ú ;äY¦V—.2>͘z+ÙèBíÀÊ\ª%åöÜó}wìdhÂP†çö%èíªÌ÷`¬£4É?µ¿ 37£‹Ó(†'ݧ¯ý;6`bËîÔ¬Ìç Þ4¸™)ënðz" :}&u“m)¹|–‡‹ضÃ_íÌϳÎc¯KO®eŽcsèyï}b¬:Ë œ–ú½üäG-‡˜5Z«ŽëÏ…UNT™/I„h¤:ª$œ§fz¶¹Ú3mÄûµäѵYø±aU3¬ƒ¥PÕ”Ôe¤H¡¾­ûÌ‘ tš|�p‡…¸¤—Î.&/áàÉ$õ›IÝ‚mtñ[È»›èwìOõ%‡ÔgÝu‚� 6* —WeVÅÐt"߯·'tp}zÕsÇÃKºöçºÖgݾL¬½âyt4þ”@U¦yxþP:ñæ¶ÌŠ2 }«tþO¹„mó5¡ƒëãcS“VÁ¥!ÏÛôƯðz!ŠâSQ“DQ<#Šâ4 øíô�²¼0=DÅBQjàwÖQPPPPP(ËžREñ(ÒÎçU[­‡ôÚÛ¢(Êe»A؇:CÅ4AB�b$3èƒ4{x´Ì®T@¤ÙÅ4¤™Ç¼2v�T‡O™ý˜÷‘¤:~}vìå|VX0§ìë_a{û}™Ý®¿'ëׯ'±]§:ÔÅä¾ÍHù�%j¹‡‘s]ëó†ù."õT8EÉEWDA@õL¿¢úœ—µ•”^�,F%*1Ýaª«û*n«*Nª*Ñzõ¯Z€a™çñ©Í«_¯oøè;#"«ë³ÅjfI±¥}<‹z½F£,Mê·dÁãÛ²t†ji Ík‰®öAY‰ � ÃhtI"Kú] Ûù¡EÜfÑš¶,êõ i"_ÔRñÆÝ6èèè�³Þ�f@„ "üø!ñÔ1�s;¤L6IA’Çy—ý :ºL4+sL°6»V<¡XçGV�ØÆ “mÌ9±—õ¾•ùyê’]\¸1kO¬XÑgQOÀ.-ïOÉ•…àù„ ÷§ð©îUTjW#ARqœ©¿œf^WÉ­qug"�R¯gG…t»BÌÚbË|L>ïO½Þ9\G:á¢tÇyuã±õWøfŽd в`+q÷bxoïjNüÔJ ÞÍu,½îA=äÿ9ë·Íáä.0 C·7÷bÞ@$+Zzû"Dˆ$7ŒC’©…wæj¾3‡ptö ÜÃdd´ÅȰ­,ÕàWo<ºuغRðƒF%-͉7".s:|4»€ÇG¤c6p€¸ˆ{zmüçrñûx’Þ®Á¹{§È1³U Þ5gÎ #&.ÚÁ^“äV/Â,)–Qgn2"Ç«·}-ž…±ë?˜ú ÷Éá&MôŠñÖÛ'6°X„ŸÓÂj„@q*}®zbÕÛ]ë3ÿO}ǼNß{†††/̓ÿ‡�þÿÒ¤‡ø =¸E„J€0å�‚‚‚‚‚Jª£:°è„äDæZ¦Í¤ÙÆ 5RÈÍ>Q‡”éS‘êPPPPPøËxM¤:¾!=8­ ¼�]ÓYÅPµTÇ4` �ˆÁÓEªão᧘ô—oôóg¤:š�¯Ðvuך m£ß ­Ðö2^&w2!øL…¶™ƒ›Uhk/ìxÕ!Ñ#zÃ+·}ÿj¹àQ¼8ðM“ÿ<,="_iŸ7 t+´ÝrxñuÍÊzV†½”’˯^BÄ웥¯ÔîEiH/£Z^ÅÇò2¼½n¼rÛ!¯ŸTð%`Šzšø? ‚Ð^Å8Q—Ùþ¢ 9ÀnA¦ˆ¢˜]Á~ž·¯²(RQÛø !|x–ˆåÎXy>áÆO[iÕsF¹~›Çu“äîßAxæA…(ä˜YiÙ4¹\P*‹1ãŒ#rŒqnžPaÛ¬Fe´ˆT%èdŸGeÞ ttµd:àùRÏÚ4mË¡î[#ÛP–{*ky¿Ý"Z�ëP\ï„0¬Ý, jÈ-UÌÏ�Odá‡$,[IîœÍØü–Ãí®z²ÌÇŒ3:Tj0›«Î�lÍ©Zî¨îGÉÇÚ¼–‹ÎìÃÛ»4�óIÞhòžÌ’%B4²ú?âq'‚)&S´$5�‘fpǵxG–‘ðôô,— hÓëCÌ’b¹ßÀ…Ù÷îkÙ¬ÒÆ0Åd w"*3¤ÛQ\ï„�e#]»m†òñhΓ&GÕözª»U%²8ãʳ„B…2õÀy¦öwaÞí{,x|û¯¾¦·Oéý4µ¿ ³ïd2äÄAœ��™Ÿ;ŸÀvRážâÅMùðí¤Öý•ÄD{­û©í­Ùlø„�7r(uÍoA†ÓuDõÌïÀ9ì¯<’©ùwYðø6Æ•ga˜:” ºë¥kr;�ŒJ½�óHØS›‡+–ÓêÉAZkÃ-U8Ÿ1á¶ýeâÎ9ò¦�ãs2ºånÌ’b™qF‡÷ôÏÓsÏON,ýÚÕHÎÌÏ�OdŠ4›Ê%Œßv}Ž §O½žxèÙRùö)NÕrÇæ·<(’f£Õ’%–}�ȬUú«xqSî[ÆàììŒ�ÑY:Ü=È<ë<,'V"ç£a´°N'4¿z¶Xõv׺®¯ÓwÌ?5¦×Aª©Úøf 6’ƒwµã¨¶/Œ€åHÿ—ž|¯ã¨     ðªüÏ¥:ÔsÆMDQ¼¬¶Ç ‚Ð Œ¨ ï“ê×@6ŠTÇ_Þïi[÷køìÂÏòý£'<ªßDKVBÃY+)ŠJEtžq5A’[ «$þP®�•]ÌãÏp­.‡‚'s5è(5®ìÑîW§ü­üÕõžŒvû™ÅÙu¨M2±–ÇinL~Ýec”å&ò¸��M<«Ï5¤Y‘>1Û>atà\:§TØÐ¶7v—! Ìï´Ãóèhníù æÌfŠE Ö[EÓ{›ödýÉ>Õ‡¥[îËýû’Í º¨[»=‹3)¬êÇÒZ[yœþ¶t¯©Öçµàlj?"i¿ÃÇ0ºy“ƒ&Òñè'¿ÅÞÎ×È5/aâÀFl° çX¿m;øÒ.=ŠeÙŽ¬tÏÐB©2kÿN5I·Ø‰ášÖ€$2ÝÀùÇõ͸œÐÝM·ä>4Š÷bŸkÆí‘TR‚YÂ9–œýåµþŽù§Æô:Huˆ¢øÂ’Ñêât³Ô‹‚‚‚‚‚ÂÿŒ¿Cª#Ø!B–Zb£%R.†FbC“ä¿_„Ëè©ÐNÁ‡gén"å¤ „ÓR?êMXGÍFÒŒíÔþ.lo`Žñ¤Fx©úÐ4§2'¢ß×êk¯û@â’S™èv˜! 9i-EÑ®ɪÀϨM2 �í%:Å›Û�äâD£›%0Ç1ŒýMÞ`õÖYlÙò��ÉóÙ™<ëDâ/‘gð"V2«v-¬í/áyt4ÓneÈ×ušëÓ �YAAAAAAAáyüR‡[¤œF€9H9�ÛÔY�ò6><�`A\‘œÍ¶H9 €öÀ=QSÕý(R¯WF^aÙnºø-dVíZ¬,–&�ÝÜ¥pºB»¥d4~� §�èUÏ�ûEåú°õœB3jRæwcò®³b3¿ƒ"é8z·ÂÞ¦F�Ü‹4`H�sÓ%gk¬ã R.âx%—f],Ùæj�ÛyWHW§½¦Ï„ú© ?\…(ª:¸¾¬÷7nÝ,ök‰ÅåÝôiH—œ ªå¹Éá�­.]äI^mïãlPÖÁEÄ…­“Çûy‚7=bâÆâÁ´ ¾F� )<ð-ë )ð"Åá")ÚÕYÿ*¦¶éÅû=åÖ× kQËË塆k[i­³Nô‘õt{bé‰s�­8רJØ�×)Ÿ›‘a¹‰§ú[YÕÀL–&éÕp"‹Nt"4-w�ö£Úiiõä .�G¢ºW�¶±ç©8�DGtj>¢y¥¾1É'¨ NƱn_¦¼¯Ž™ƒÉhüµ< µòxâÎ9ânw‹^õ¤sžÔo&­‚¯ñуçM)Kë>s˜4Ô™ø¥Î±¦]QŸ�< ÆÕ £<žɪfôxü�\Qµ,šüŸ‰-Ké¯zØ—À­‚ý Lù�h›¯e[¤žŠ™É>´}¼÷´O¹Ö¡�Œ¤ÅõíºaÙЕ[ÉMˆÖSÑãñwZûª† ·S˜ätœº†%8½/§›ËÕv¿�8@Ÿiz̵¶@¿¨'Ó´((((((((¼Œ¿TªCýþ$G®·ú½er1DQ¼€$ç‘€$|¼É‘,ލÛÔ!ÍRnú�Z‚ hÔ¯©Ž×Œ1«¼H™ß aÄ(Ú ƒ»_cÞ}ç¦l¿±x0ÝMôùxågT6Þ*ç)š~¿šûEtñ[H¿…ô®�fƳúø‰ØÌÃ$Ú ƒÍüÌ›'%ºoøDv0*$j9Ü�еa7nuâÝIQæ±c3bCVš–É,&pÁT’>Ðh*ÎMÏ`|š1á:»hh̾Ü"œ½¥H³ªù>ìuˆeCW̾\MËNUù¾º:WÔ#Ÿ.N£dGª†‡ ¾�ô¯ÛV¶_8íD³.–4mO¦å1ö HC½Šu©TÀÍ]FDlTÀ6C)ìz@aGfծŲ iLt;Ì£`mýÃ(ªAíy¤Ú'lHgÝ%GÙëŠ4C‡”8ß#zóNí‘탢ö³UìEJê2yI»%9kkÅ~¼0Y`�ý,º’3 @²5=bÂ?&™$¯|ÞZ±]ÖE Óš•¬›lK§Éä÷ÃÚ} ÿÝ`ËlF¿J[Éiêp|«˜1Û>”i#®ÈŽq×êúr›¬È\–Mc¯û@Ú“"élFÄîÅùö~úLÓco•ñl°‹×Xm'¿Q`Gºå‰W±.Iuk²¶¾ôV™ìÄgÝuÙÉmêÿò™X…²ü]ÕV7‚ ˜3€UeV[ÏÙ<ƒÒœIKž)Œ#ŠbŽ O)/ž¬Ù—¢óø�é«�c¬ïHó•=´ŠYX ŸÃ Mûqö „ìóljÕ�–FW^yLa±»y˺úçS´Õ�"©š¢æµ,š"8å‹áhÛKtDRë_Âöz“R�É 4 U‚ ¥KYô̽Vb` kZª 0 „5‘;Y3­2°ŽéwB£Çh¥£K‚™ cǪ$f³WQR‚YROÎñÉrg¨uñ­�¯¨bÁãF pFÛ|ŽdlCГ ‚gV2e=ðöuðHG¡µÔ^£ÇxîÞ š×nGÃÖV)ÁØ �´.:� Çj¿-iO®‘šHud­Ëˆ_qÛ~‚^(ÕÃnqÖ݆Œ˜Gĵ„Uƒ¦³DÐaÜ_ èéc}ÙbK©¯5;¿Â êp>\´ƒ”_© ^<À\w S˜Â[9<8ñˆ3­M?‘ff�kU…âô zᦷ—N·=ØòHóx/%Óæsë”ðøÉ ¬úæ2\„éšhtêêôzc0H]�W}�NüÔ�biû�;QÉN:ÖNåiôSbRÒ˜®DAQïb®Sz¯©�f–R8­�y“Ûêû©èÔfD×Ê,8½—‰¾â¤Á|pË„)LáÔªïÓ¯!LüÒgr²à}©ª�ütk)�ê7 [�^U˜iU£‘õ!)–ÌÌV´‰èÅ�¾S9¹k:oÌ"´¸fÄ¢R©Ê}~þmß1ÿ2�G…œ¿Tçñ[]¤:ÍÔ¡ªÏk_ )T5xWÅ"õúO�¢(:>³}"°NÅyÏjG–Ùæ)ð¡(ŠåÄ��G…ñ:è<þ/Ptÿ½„…׫Ðögt-uû¾ò˜>^ùY…¶INÇ+´…w¨ñÂ~+o}¥ñ MúBûD·Ã¯Ô¯Ïºë/´ŸökøJýƆ¬x¥v¿‡¾;v¾|£çà´Ôï…ö¤º5_©_ëDŸWj°Ñ)¤B[“ðÿÎo7dÌ+ï÷_È?¢óøRA¨ „�‘Т2æt$�«g©Iélã–ê@ÑyüÏŽiÙ¹LîëP5ß·˜'„:/Á'nú*í†ù:cqvv&..®Ü "H3�{ÿüZ¶‘¸–¹Ü‰¨Œ³g-~Ûõ9½êN ­µ©¬Õ¨Ñ=47?Å ñ=úíjIžڲ¦¥ó¶èGÒ·oUNˆïar_§œne®™ö¸4Úˆ´[k"l!2ü1OΑ˜h/kÖ»)õ¹¶÷Y+ÐÁ!™û‰^\ir„o×;òÑ# r–b?ò{t²Ïãct}AEÂäýüÔ«'ÞÞÞ²Ž£Ê¼Ýò¥âEe °ht85zŒn=¥bÉW,—Ûú�eåÆ8¹M×Ï;Ð{K¾h©’Ï¿¦Z,€/9o4šPç%88$sçn©&¨áã�ÄEÜÃÊóI9�Ȫ g°Èd9ÜdÆfwi.ŸGÇûiDDDàì쌇ž-ë·Ía¬G'H‘÷íOÓûw¸t¶!YÑLs}Ê [sŠúlÄÿÝ/ø¢¥Š?meŒ»giA&¤ÇPç%xèÙ¢'è�-ýVßss1Uæ´£æÝÞèdŸÇôþtÌÚñÛ®Ï1�±D}C•`–+_÷naÍ¥õ–³)YçQ,XÐÓ”Ãu"å\TCƒÕdggsã§­˜Lû7a7i·Ö`øx'MôЉ,NåNDeÖ5ŸÊ•‘¥³úÿ_>ÏgÛ×DçQAAAAAáµäqÕO<#Éj¼+Šâ³1B1@uAZ‹¢ø›ºM :¥’1ÀtAj‹¢¨‘ïðåRŠÎãwLƒœlC§R¤’rÔôUåœG•Z}R¥Ri9�³j×bÖÝÒg*•Š$¯�x%Næ¤K3šæTæ^TC'¡ŸžH»¾ÀeìÎO¥HÇ�ƒf«iÑ‹-œê°åƒó|Ð*›sÕAf&7:ŽÄßnÞk"é‚C¢W iÅÔCIÐ ~‡' —Çò³õq®^­‡®®HØbZ®'0ägº5ÜÃXƒ@j¢®HÍÌ~\¯¯b@|gÒ>2§YP> ,J¸Ÿè…º4>jHÂ[Ñê^ÑòzOf˜Ì@_P¡/”ФG2»U*tttx3·2ádóÕõž´Š_Œµ÷AÚ§N“+ˆjÎÜB‡…Xˆ' ßÀð¤ûDÇŸGÇIÊÍ»P¥3öcGsc±”Ó¹ÿópÎ6žÇAVËç¿�w5fÛ‡Òue[ 8缸�®®ˆ HåÊÆ[©>ºí ¸î9›“Ûûð Õš­b/ŽëO{›Žë�ÙÞäÌ™Í W[YCçÎ�y’@>ÐØódžaW£*åÆzV|€’�«Wëa—Ô˜>½ª1×z«¿½ÊJ×�`"0˜ÁVÏL¨SKé‰õˆÈ`ª%ÄRßÚœ¸Ma‰@Ø�¶lyÈ›‰„ªê3ái:ßÚµ�#©Œ-.]íâCS]dj`s¶|p^�S™Ç¶m�q±°D,x+l F…s+m)QÇû3¤À “U 'ç/Q%÷6·ò–2á–1qu¶±<Ø›:> –lžû„7žóùyÝ?ÏgÛ×AçQAAAAAáuåUt; ‚)‚&'ñ�T‡Úþ¡ '€XµÝWm·TÛ«·�d8<€'j=È`AtDQŒò�Sj›œ’DQLP÷{H. ‚�¯Ö‚\‹Öú¯ 5Røs>ód9Ž?BYÇQÃñcý)°mÇÙ <E¦’ÖÚ@Öàû½2µàRA�èlÜÌÔ²kô / †v£è3M�§ú[˜òÓnePo§T]¶ÎuOZÓ4§2SÛôâ´Å6YnãñçQƬàñ”H–MÃ$[—u­µ‹Ø\8íÄ·kJgc¬:ËŸ¬Ò�¤~3é»c'Ñ‹¿”gЦ;ìÃÚ°'«tÇÙÞ–Z…˜}³Á¼=ªÌª47bøá*¬j`Æ…ÓN„……ÉýVËsÃÕ®þ 04�(®ÑÈyü:ò–Fnb_®˜ )“vk V9a˜<‘¦9•±_¼Kî³æ½÷I�ôà¡ñ Vzø‘jsœ ‚VTí&ÍèiÆRqx @Jê2â²ÈΖ6%Ê&‚ö¬“i»™5ŽkØsM fÖîöVÏÇ+?ãב0‹Åø^J–®]Ç“lsµ'ãrg|›õ¡‹Ó(î¦] · S M'2p«Óne�¶k I…,:щ�)?Ðàf&qYLcë[M¡Ï4=ò¾½JR½̪]‹}¹E²^¦±ÑZYÂÊâ,Û­Ñq‰‰ß¢       ð{x© R)”—êh‡T U#—1_m¡~ßu¡šgè”ýUŸ…ä€>V/{€ŽÏi'”y-{ª ð �ÈßË�/­ê£e™wj�ú %Šjü¶ësš¸Nüâ%¬êô˜øÅKþоö{Ÿƒë¶oµœ¸äT–FnâÄ“þ’D†ål¶9†ñ$/€eAÓäÍËjV�?#ÿ}cñ`.†–Ö¤ò�I¦Óä Lê,kjhr´-mcÏSÔM ‰4ûr5^U˜PpK�Hb-�³Êd' Â�¨ê·š‚*RnLÙ}èM- ¨s ˜‡Æ'³~Å-]Ò�Ô8»M޶%¨ ]ü²kn1™–ǸiŸ*WµœX‰¾öS0ûf)Éúpî©®ì6?p�[û‰ YÁˆÈ`lÓÚ³Îwñ2ÞÈÁ"ýCf&ûPÙXÒµ47?À4K y»Z÷²™mÊЄ¡òCƒ7.ùRë^6î>ãAÐNv>ŒãÂnr›ó‡Ò9(�ZÙ5·˜·cKsc¶pæð#š¶ŠgYú^fì¼/kƒ~øv N[l#\gìÆ’k^RîH£>CžtŸeçÉ´<&ß‹vø™š–IÓVñ µ�*ã6õ7¢QŸ¡„땼ô¼((((((((À«Iu,x‰TÇ0µÍ^mo¦¶ÏRÛ�‘À÷š~Ê,7ËìJ©å:ªŠ¢øž(Š·ËØ}àMµœG5$ýÉÁŠÎ£Â‹ÐhD:´;¤µ~oçkZï‡xɉäëöeâcS>ùÛ.`˜$á‘<_^·çæbºÔgîÊ782¨\¿/ãy³œÏcxn_Zeøãj Í.Õ¼÷>�O0ɳ+gï#ÚÀ£1Rr¾œG§ÆÐt¢¬_8>w ÓF\‘g<[_ƒ¨ål¨'9E{ö¦åÜê¤ÕW”MK#7aÑXšÉ³¶¿„ÇuIœ^oÂ…rãî¹GÒ@LkmJµ<7 ìî°�F}ž_Ààä/áØH³tC»ÿ À±ößjéXšN)ïÜOmÓ‹©m¤ÐU÷Â`>|[*¼`eЛ ‚VÌjWÈñcý©l¼µ\)À“¼­÷¼žÈ7¯TÂð /‰KN}ÇJ×�ܽ&±–Ç92ˆc!ƒ¨‘&C‚™ ¾�è6n¡å`—%Õ¦´¨Ät‡}Ĥ¤1Ñí0ƒ�,¡nX;¾1É'¨ ­¼¤Æ:Љ7·¥äòYöØï¡–G!7úNàä²o0LžH“pcbbb~÷q(((((((üwù'¥:Aè‡TÜæ0[ÅGÏl3E„@ð#ð�(ŠOÕ6�Îã�2Û¦Tçñ×gw¨Hu(c*»Þ@T‘ðÁ,ê¥fCÌÞ0ÝÆeµt‚ßþ”øt$gý:²®^àM`Ðu;Ö´nG7ÑÑÑaË8�éKNb,èé#èIº|š×qiki;‡+M¶°6{+V<ᨛ³ÓÒYlX‹¯»´¨˜ºs7òݹ_pvværÄy¢c|oooòò‡`U{‰‰öì÷< :R>œAbwùx4²©�½ÎïÒíÒ/8ð#·ï±©¨Îp=}Z÷™C¿ªE¬ûQØ¿*Lÿ”éõ¢[âjŠ�úöÒ}À¤þŸódÎ)èÙ@–¨ÐP·Ã¯¨e<Ö¨ÓuÃl躊Œ °«w�„Éû©7f$O朢¨¾4«(”|BÍ{]!¿ò‡¢vßSý›@R¬–d%Òqmðè&‘ÙÒjC-îÓ¤pSû»à&ì&¢Ø³¤ÒºèÐôaUNß=Žíõ&T»Ëú¬ö´ûíS˜þ)k³·òI‰Àò`o\üo¢ÂúÖ»ä1‹Ä:5ð¹™NÙÑ(øð,%jý‚^”èí ªR_h_�èìÒ ’—?€Ñ 0Ðù¢¥ =[.ëÉy‘CßL”%(<ôlÙvè>CßLä‡ÈÃlóM#ƪ3-Žµá‹–*YÒÀÛÛ[:'j¹ �dI§Ûòx Âï0¯Ë›’ôˆú\�>“&…ë08GÍl7¬<ŸÈÒ¼Æ^¤¤D 1Ñžû \pSϬ÷ B`_KV¾¡}\éd¾ÓŠÑoüÌÀ” j9E)­8貚‚Âa$ü¸”LË †”>Ö¤3iåÉz«hÜ�>v…e³½õk�²cS»JOLSb¥�Ê«°«QoMZKRtÜÆnà,°²‹ŠÌÌLnÛ_fе·°>Ò… ñ´uÝÆ“Çs9ºÜ™·ÂÆP°B²д�çtn‘ è‹"ú¢HA«0Â#s ý`ˆ›pŽ!款S´¶Ø«Ñá ŽîàxdIýJƒrV­äÆðà V-a±ñXî¸zbؘ{Á×øqà®^­ÇY›Û,9hIJò:¹míÀÊhÒÎëÍZÍÂß$Çtj›^œ½sŒ3†ðx.ÛÝg”_\·e‰ù&ÎJ§Y£;dOyJó;ûØë?õ­°¾q oü«ðý£4ôU4é‘ ©Óh_œímzs4Ô’ÎÃ(g)·nòz†$ Ï^.�óÿþóügÚ*R ó‡�GA:#å‚äÀ—±÷†­Ô«� ÏôQXˆT™ÕI»Q¸[fS` ð®z•¦Š„f�ÎãÀIÚã4/ÐyTPxá:»h�²jåà5ö"a‹]0žÔˆ¼o¯Ò´U<§ÃC©ß'€�s�øbÄ—|¼ò³ ûl:<žãÇúãekÁ} Ußw8½û`…Û ˜Å%³|žä`g;švižÌ­m çü[ óÞ‘>V=BÒ¼MŹ*ÂÉdš¥ú‰“å¾¾]5 Œ�ßç}{•ª®SY Ü,ÅÞ{ïg¸ÒäˆÖ¦�¸‚sìÌæêU08G�ÇѲ\*þÓy<¨ŠÑÉ<ƒåÄJÄ6ZÄ“)Óð¤ûœ¬²µ\š'~¡¡MMÎÞ9@�Á2DјKÍ{ï#)ïTLŠÃEÜíná|Û–1ú[¹Qw0µ.AmÿFt©²PÞÎ:¸ˆu¹™z"u| aÙJt 9èr3J«ïºÚÙ°Ôט¶±ç)°m‡¡þ6V×:O­Ìx{{ó͇ûñì6š;Ë¥Ôk¯c³Ù°óf çHLú�JÃ¥\OVI/FKŒØ¾};ÕÇOT;'È?xÃÔ(N¦7ãbMIWóB‘>65q·»E¸ŽŽ8Qõkéxšú!¸ÏÓ:v£MR¿þGlØà›Dƒp#œ�FW§uC µãsÏY\r*-í×2ƒ$,[I“Qé›lKØIi£ÁQ\pyáyWPPPPPPøïò*RæH³~š_geu à:°YýÞî�Ç&HN^u 0ÉùËCr"ÁUýw[` °hÜEQSÞ0 i¶²%0IÄHø7ç©(üo0ÜÔ‚î&úø¬»N§É¸pÚ‰ûE,<Ç^wmÙƒ_G~@Óáñö3øÈ\:�¤ÐNšL_ô”i·2*Üþ¢‰Tѳ²ñV²²Ü6*Àñ~ 9q±!+ˆ YñÂü es6–y~Âè÷Bå÷ΆËùªLÑ sW¾ÁÒÈM¸�weçÎÒúU{«ŒgoçkTËs+׿íØë$ø’âpQ^÷CÐe­mª–†¼nèjV®�V¨l¼U~?¡àÇBáôþNþ^nû!×méUÏ� vcy¯íXM=p€‘Á´ì¤­ t¬ý·äÌß#¿ÿ )›~»¤\‡˜Â!œ ÊcU§Çlo`HòF[öÀ~ñ.ŸÀèÑ/³2º¹Z}üÄrã|Fo¿‰0bïgËÒ$B�·Ø`7¯cYtÿÊSÞï…mϘõª;�LÛÍP?•¸:’,ˆáÚVìŸ0€“Ö3±ôˆädçÃDÛ|�¥G$£ÛH3œŽ£G°ªÓcÖùŽ#%u)©Ë¨’pþwƒ‚‚‚‚‚‚“Wqs�$14ÊÒÏê<#�qê÷Ïê<º¦@Sàg`°0¦TDZ6аTÛú�Z‚ h~my#ÍVžÖS‘JGEªCáUðöºAÁ‡å'­ ×¶¢»‰>ŽG‚HŒê¢eszÿIõF0:pîsû\ç;®œÃ5×Úâ¹Û¾Œ‰-»ËòMs*sî©.†©Ql:˜€«ÁZœ —ËËðܾLí/Í"¤ŒáÛ5qr_G$‰�áA_2üpvø™s(~9m¢pö\n v¶£É°ÜÄæ»´Ö[�ªDhZ&]œFam ßKÉ$LÞϪfÜÿxŒ¼­*Sr沕:¯óNíÁ:¸ƒi»Å†µ˜»ò Nv>,Kƒ4Í©Lµ‘R±�á7×ññÊÏpÊN%lHK#7©g+K©X™=7¢ùéa¥rÇqþP:“<»böåjÎ=Õ¥°¥4ûZ}üDƬòânÐ5¬Ã‰'ýI˜¼_n7‹ ½ý&=]-q÷küœ«ôûI™ß�B/+­1Ù¦µ'¼C RR—q2M²M^ÿN…}8-îL°QìÆÊënùé“óQi‘ �š–ÉV±i»607=‹t)GÖKÕ¯cY²¤KÆnEºCAAAAAA¡<¯¢óxô%:�A/Òy%@mQ DQlL@J'Ó”I2¨õ�DQl¤®²úÐü×Huøˆ¢h,Š¢9ð¥R ˜¢>ÿp›áA_Ò*Ã_kÝ$Ï®nŸ²u5NÙ©´vÐ^ØÁ´[̾“ù‡÷{äü.ê&ÛRÛ¿ iÿÅ%§—œÊ£øy<ŠŸGï÷h[¬Ã$Ï®dÚnæ´Å6n…½ÍÖ¶Réæ»8Ñ,†½.=)¹,9ÏY‘¹4? ‰ØkŠàx¯u¤x^ 2¢%‰�n‹×ÐgšW§ñ‹—�;G <œ¨ÇÒÈM¸§}*÷¡°ÿ*±;º�[š–IÕø3ìË-¢m±ô•”Yk‡‚'S=p ñ;¬ˆµ<Ž¡þ6¹ßÖ}¤gUÑ‹¿Äpm+N[l£[XsÖÖO媇-.éí¹ä•‡[åÍ4ëb)kD.èiÊhËìué‰I¶.né‘ls Ó:·³gÏÀݯ1­¯•[ÿW¡Ñ�ˆÝËhËØÙŽÆ+q2^ª>t3ù%­%\]]™v+ƒèkùjð±©IÓœÊ?Ö_˲«Q•8Y¥;Iýfâ&ì ;úâL圂‚‚‚‚‚‚6ÿDÁœ“À`¾ Ÿ"UG��äÈÖVoc ²çžÊЕ!ס¸Þ ¡•˜b2…í ÂuEÜznÆqô,²ÚCOS¦æße05ÿ.:H’f¿1¢Í"~휆 $-Qª;U«xœü‘)&S8QÜ“šú *¦õí9ü�憡éß�„üøz3OU Àâw›rwÛU.Å6ÄÊó SöÝGgß0ÎíœÇ�ª“èìÆ''‡3ÅdЬA™Rô ÂaÛ»z4�8G(�«ý6ú�öÓÂpoŠë�O¨äê�E¼T…´D­/yâ§þÔ¿¾ŽÆkQ=)ÕTlã?—ßv}ή´E°‚ê­å T ßäÞ¡âûÛ3÷†o¤žÄözGß:´âʉ·� ªbš¸N]¯6XE�aŠÉŒŒŒÈ؃Å{¥eÑÿ÷¸ßþ8úlå£ïŒ8>¦!£›;þ¡~vuC £n�+›…“²Î£(¨€bQ…ŽZÃôÙ×þ÷qMÃÕLL~àIÀ9ŽŸœËìÙYÌ›7¯Âý¾Ê±¾îß1ŠÎ£‚Âÿ–YµkUhÛ›]q�9€«AG+´�iXq×(/¬RçÄ?—ÈŠM/ã„¥G…6Ó)ËmüÊ’öѱbI�—qá²%“Ê”ñ™W±íoD �Z¡-ŠWèY#§â”"mþ)�G_`Òì¤ Ø¼œEq”Ú© Š¢ã3í�u¢(ÎSë<Ú‰¢Øé™mžŠ¢¸ã9cš…¢ó¨    PŠÎ£Â¿™×Qç±mlŹÖ{]z¾ò>{¼Èy| «+.„·4rÓ«÷k²³BÛ‹œÇ—ñ"MË—ñ"ÍËÉ»^àDÿC¼Èy¼öö+÷[ÃäB›ÑÛo¾r¿ÿOy-u�õA¨‹¢˜+B:’³ RAžç=ŠªIélã–ê@ÑyTÆô»Í/ÇXðø6ÎgLÐUiß©õ/ÑâÎ|B�—à7}•öìD‘Ž¡l{Ãm¯<ûÒŒcÞ“YÏÕqm�HŸ ×´l=F•y3ÐÑÕ*tó�ÉT¨Èá&*óft‹h®C),êÍIƒ04XMvv6N×y4û8Uæ´Ã­çfôw àÞ Cr̬0½‡æµÜh�Æ%»[Üðȧ^¤‘¬¹è“‘ÎÎöoË¡µáwÿØ+Ï'Øõ5'·÷A¿¨�òp¢¸fI±˜Þ¿Ã‡vÒÇ41ª ¸¥èÔfB�—Çøæïp>]ª8j{½‰¬Y©2oÆW×{S8„&êpÌKƒñ°«Ï“)òù?å†NÍGÜÃT%’ŽcÜ8:ØÔÄÿˆ�Þ*f§NrcâZæâ¡gË­OòS/éG‰³÷`†¼ó¦|Oó…VÈèÿ·{üðÒ…Ø®!ïÉ,TæÍ¸»#‘ï«ç3üæ:zÕ�ÀƒÐ�Ižþ©|÷{Ÿã ó]ÔÛ9¯ôü�åp•áL(¸Ç–Gú´¹Mõñ_»cý;Ú*:� ó�ê<Š¢˜ ‚'R•ÖŸÕ¦ º ­EQüM½M¤ ­'Êl3]„Ú¢(jä;|y�T‡¢ó¨Œéeö´w:ÀöíèªtK´�Ç‹¿5¢­•t¯è« Ê9�ôÛôç¦êG¸>–o#Ðtx<6ÖC¹zõ%�HvÝÂÂçÚº]9ˆ¾(ò¹a©}d®?0ÃdÉ+>bIq+¸|�Ñ�}©Ry=¦¦1dg×CÔ©ü•óa÷Aò¾½Ê›­¥pAÙúÆùðÛ�ˆªD–n b�À V Á´ã;Ì´±äƒ¤læéŸ¡§„=áì¦Ý|žšÍÍ‚\¢ –bg;7ör•zÔr+ L÷(6ícxø6‘º¾@Æñg¹èÜ]óŽÜ6%@.5³�?‡2Çï�·žý‹¦s}dæo&oÏpô|Ÿ ºg‚EV0N~¥¯gà-�f},q;ïʉf1`Þ‘ÛêpÆ0ï·Ù­R±Ç~Þ™ïÉ÷A~~>½íÝž{ßü¹Ç; �‘‘Û·Ã5÷P»Ÿ+© LaCH:ݺâBé=\['�ì«�Èñue¡JÒÑ\xR•j ~¼W­„31�µöóºëßÑVÑyTPPPPP¨˜?\0G„N‚ D ‚  ~G#Õ!‚¾ ß«ÃK5�Òß ‚àSFªÃL„0ARAÈ! 8,E1@Åx$ÝÆS‚ ˆêðØ“@’fà�\T÷óInÝ¿9ÔHáÃ󪮖•ºx7lÍ ×‘ª‘¾{�ÁG–pB|€±�øfÄŸ ê•Ü‹Àƒv¬,Á¡øårᛲœ{ªKHHõvJ9 «£$áûh=S~ÀóèhâÎIQáñù4 7fÏ�h¹}Fã÷hc,…×lóMÓê{j›ÒКÞ7îs³@ÊI1zô EÝÖÐ#zõR³ȼê-oeÁ?sÒZ›ÐÊˇüªï Së!祿ð˜Ã†$06yU»Iy�öKÅœ«$Îå¡i¥‡†Zø3ºY祳,}/=«=¥gµ§Ø^o€CÄJnìÇǦ&™¶›ËUM½çÙì…cùÿD½H)—/$$¤ÂmîbOœËÚÆž—eA,*S;°²¼Í³r' ÿM^EªÃè€B ÚRÆ€ЀÒÂ4�=�T‡`Tt‘œÄG”ËÑ�Ä�ÕËJ¥<Ê"”y-{ª ð Ô¹îI;žyµüwµítÛo¯#FñFÂU†$`s[JÍu5XKz¤}í§0i¨ó+�mVíZ˜}¹škz:¸¾–cêê¿CÓ‰ Ïí‹Kç‘r›˜Â!<ž)W=uZêG‡ã“¤¿#µ‹‰˜}³” ÁgØ_¹´}ÊÖÕL»•ÁÙ;ÇÈŠÌ}î¸üc’92ˆÀƒv8èJ!¡ýª–β6Ø2›÷ƒ³ü[ InïðÐøÖÁEŒ~/ÿ˜dMžÛw\Vf_®ÆÜ½:ú»pÐ¥>:¯§v`ej\ÝÀ~ïspw*žG¥YÐ;ï3cç}¹/�óê^¬ó¯rŸGdý%ÜÅžYí ‰µ<ŽKç‘rõÜ&…ë€Ò¼ U�£7á‚Üviä&Î~ôl— ÿa^EªcAER¢(>E±‰Æ®Þ¦µz»õêö—DQtEÑTÅJ¢(Ú£‘f0ˆѪ€ µ\GUQßEñv»/༩–󨆤?9XÑyTø³X¼çÊÌÁÍØ—[qˆé³lø(•_Ðàà0‰wß¹ À•‘Wf?Äf~ˆZN[ß.ÏvÃ�åOÑß?”·Vlçä/áôê&ÍTnðzªN�YÕé1ÓneÈ‹&qK‡®ô¬ö”�ÆôJîElÈ ¹Ï-ºïqÁIU8IDATÒý+O,="%'8j¹,³Ñ¶X‡“GqòÈ!.œv¢‹ßB¦�ŠÄXuf�ýÒ#=(°_„µa79O±,U�¦R<¯…ÖÌêá:‘de¹á½ÖÚ�"lHË<¥ó4º¹#=]-±™ßÁgc“WÐoWKÚÄzSÕi*[nRЩ¹æ%8^ÉÅ@ÿGš5›N½ ëxkÅvŽ… Â"ýC-ÝȽ�¯q5è¨ì@øª4ÕWqËO»)û‰Z»‹ýQ]Öÿ×¼ûÎMjÝË&\g n¬¤Zž›<«>¯«ä8ïí|�¥‘›žtŸ�[¥b_§¼Åw6y¬5 —ûZívù(((((((ü§ø_äÆï~/ŸƒõëËŸÿ¿Dðu˜d‹ß_—í……äÎéÀÛ#8Þs…š“P̤¶žö^º +=ê=ú�ýFäŽT%rŸWׇ1 7‹M³ÿØxÂ�vÕqÓ‹ %µû�£ªî¶8æ‘ œ9ó5-[~ª5®ÿï÷¸Õ¶n&,,Œ‰-»³æë¯¨×³.‡’ØÛå 5®ìaU£UÌ c\‹(NåB¾€žðmZC�(�Ï~7—3ù5?Ö¿¢íë Õ!ÂH`$PW½ê20GÅCj{0à™f§DQlû¿£‚‚‚‚“¿MªCm7¢€«¢X¦ô¤ö6æÀ9`³(ŠŸ•Y?A½>iör.°OÅ!jûj ®(оÏôWŠ¢¸ý9ûš…"Õ¡    P¯ƒT‡ ï%@’zÕàc$é«Ëjç±0°L³§¢(Þçw¢Hu(<‹8óÕƒ¶„Ù/þ¨ÄÄÄTh{QNvÙû?J]Ã’ m[Å÷[Ùxë+íshÂÐÚ/^z¥~¯86z¡ý¿$Õñ"^!ã³îú+÷[0ät…6‡I¯ÜïkÌ?#Õ!‚>°)4vTÛTC*–s˜]Ö&Šââ2o/ ‚�ìaŠ(ŠÙšÍž×mëA‘êPÆô'Ú>Ø7‘˜[uÈ1³Ò’êHØ#¥ëzÜJ{å1e}¿˜P KfœÑay˺Z¶¸ˆ{Xy>áNDeÄ2`�›Kµ£DA�%7ì’´‹¿h¤/4Ò# ŽÊ6œ��ñ1:K‡»µÇc³QłǷ±ÿêkt Éùhº�š¦ÕV_P±¡ž”GøuÊ[„ßa]²³³ÿ'×nÙ¹<7'óAéL1™‚ýW_Õúkô&\ ÓúV$zŽ RêXr̬Êõý]ÜôÛ¾„Pç%d8]磦Ÿ½tŸçñüÕý¦¤Ç n²l‰æ~òÉHGÿ™‡‡K£#¨×3=[ô)«aÑ™}¬o× €+×OjaùÚë¿EªCÅ_žY5]=Ùi PÅWšRPPPPPø‹ù[œGµã¸ °<Ÿç½ ‚PA*†ÓCÅ—%—�T¿6²y�GEªCÓŸi[É÷+ìÂÏòý£4Òz�GÕ}5]üRuâTÖ8®!ßÓó•ÇT㣠lß¾�å-뢃®–Mã0Š%bq©ó¨£þá¯R¿D‘Û­k fýŠ]¢$h{àAuý>è« ðv‰áÈy©ì€ÂŽ\ ‡ÃU†SÈ^¹ß¸:ݸuä µê]æ`­Úè¢[XH�?pÿã1ôÞ¶�ã†À%ÐTè %¼½¦5§ýrµ0ƒeI+ñõ�`ûöíÿ“k7ÈÉ€oýý <1€w¯U¦Ádš5áRà%DÛå,:v„ú}Ëõ=¼Ád®’òC?júÙ¿îÿJõ {|÷0±ò6ˆZNfß+�y†-ž�¦q1ÆÝ“ƒÀƒ�¨ÕN]X¨ÝFjÕ3bf²guK^ëcý7Ju‚  ô*#ITi訮zž DÓEQÌ(߃ÜÏsÓ6þ¯"ÕÑA„_AÐä¶)cÓá¤J©Ý‘Âj ‚`õLÕ�ô��rÓA8*‚Q™mLAØ,µ Çfµé®ú5h"ÂAž¨%?¶ñ�G…?‹¦Î ñ=v¾ß—›†þ\ ¼„««ë_ÒA•¾˜Né%/né‘rUÔÇ'1ºY‚¼hŠÜœ—$DN‡‡Ò³ÚSÜÓ>e_n;“ç3ÔÂ_«ÿžÕžR3½UãÏp®«T­tê�óÌq  .9•¤º59í×�„e+µÚö™¦Çµ=pZêÇÀ9Z¶Ó~ éñø;¢ üþ±0—Ïö Ó˜ÉXÛK¡Aš!aÄ(Ùþ<ª�Ÿø÷ðb‹Ùë>�Oú³·ó5y}Í{ïWØæÀƒ"¹ª®{a0•Š˜kmA’Û;ÄO¨8ÌLá¯AgA#ýŸ¶é!ëµùx“€V@„ÚA¬ˆiHaªšåÖß5v…/¯2óhŽ4ë·ø°¡)piqPøÅÀÀAAZ‹¢øT=ã¨q¿ö«·—ãíApEªÎª‡”ÓáÌ˜ªÞ,LÝ®%0Iþc.pYÑyTø»ÝÜ‘üü|ì®Ý†Ùuþ²~‡[¸ºXŸy§öH+îÞC§WO\€Ÿzõd÷Ý{tîÜw—f<É @ xòº~Þ�wu#Hê7“N\ุ¶K5÷{ŸÃÙb'#¶Ó¨ÏfY‚Á�ó,P×B™—¹n‡ ·i©Ë¨Æ2zuûž”pâ§þÔÌìÇ’!;µÜ˜ÃuvÑ(0˜Yï÷eöìÙ¹ø3#ž)ÿŸd°ßgvÌyÆ&B½�V½´M‡ã“°5œeûYéú§ÙF]ö¨üyÂ\: _ÿÿß&h ˜½€�‚ xˆ¢xEÅ�e¶»$ è üTAÏMÛø«­    ðïæUœÇ$I ƒÔËF$O£ÿ¸ï™v~H¡¬-(u?V/€›Hš���œÑ­Hÿ)nD’ápE1ðF Q=‹ähæ«Ûw¡šâ@*ü/Ôð¯sAšÙ<‘¬U$à^TÈa@aGtK–"¼S.bÈDT‚ÀsȈ6 v‡Òìö¶ô9ƒYÂ9ªæû°eKRmŽç)ôú¿öÎ;ÌŽªüãŸw7ÉfI# ¡„ÐBD‰tB3��€* ""E@A¥Y‚ú£‰ JˆÒQéÝ„– ª„ é}³Ù–Ýóûã=“;{swoÊ–»»ßÏóÌ3÷Î)óžwÎÌœwNy7‡²™<|ÂTýå�,Fo3–)ŸÀ #FùŠË±ªˆ%L[�ïíƒ~Äñ÷ì ;ïÉ�Ogñ¯ÞbÔ”ÇàÒzÓ–E±Ã ÒcÇùÊC«ø u|ò¯¼WùÝiŸrç±ïy}9ü:~ÿüUÀcÜ´ñ}Lª†ŸŽ½ˆûý‰-F¯`Õwáúӟ㬛j•òtâÊâÉ‚9¯›ÙžÀ9Àé9âÎ1³¸ûª†òË9mC!„XÖÙx !ŒÃ¥ihµÕzo$3<�Ïo_àlàÛÀvÀø|�é1lc`Ya㬼ŽöÅ¿È&®:F§Â{ã= rÕ!™Ú¬L>ò£÷¸:¬6ºÌHö u±Î†Ô¾¶¶~=V÷SÙ†‰Ž£¨ÈgGY0�§Nú��/Ìâ™þxü² X'÷{xÿÌ?°w×;XYñ}Bð[eÁ‚=W§MÒO8€Á gpð#ϯQ®Žzí Q¦KŠþ[�Ê$¾Î”’G¹°bW.™ÏuÇ}›·Ü„¾6„÷^<�Ý?ši—\L¿­'sö ÃyuÓý¨Ãç9;îrÞ}Þ§“[qàÔþÇñ½ÇÞ¢ÿÑûLY›"m!¸êh£þû+à«–$3­C!„hZÜU‡™í�ÏW\ œ¼œ€¯ÈºSaª™]Œ»ÛØ!+¿�ÛC—ËU‡Bˆ¦¦@\u\†Ïkœ‰ä<¸ø2þþ<€‹[ト·v !”­å9äªCWë°õN{ÞÈõKûr§ºFË¿°{ƒaMµÆ�hzsÕ±ò÷4–�]Ð`Ø~G|¡Á°6LÁ¹êHé¹)„p{üý¦™„½([7rÕ!™Ú¥LϽŸ7bïã3={S´èMw2ì»ì3ûI&lîsGÌ~ž 6¾€+—^ÉøªVçyË;ƒ±âànÞ=›§F¾Â‚Mï]nµFÿÉÛ1Ç�)»ôE~4âØÆ§AÕ„"ž©Ø�¾}ûRTTÄÙSÏæ�µ'ÓûÚ›uKÒ’zj‰´íA¦«O~„¡»MáÜâÛèB—Õõ¥šjNÛÿÛÜüÒ=LØüË•[íßèP!!„B´ZÜU°Q<ïvÀøb9vŽ!L6³ |¸NÒ꽘WZ¹ê휓fü‰Yã¿ÛlÜäyo]y7Oíý!Ä)3¿Ã á§qã ýØjÂ"vûz?^ï`ñ {X£½6¢m�¬ ¼ûØÝÙ™�y÷Ù[9±kÝ6º €'Ÿ|’ó÷:’ç˜Nçû¿ÇöŸ\}iuú×úßͪOó°wÕ=ì?,óíñ¯¨#L!„è´†«Žý€ÞÀ¯�s�>À{ø\Å­É,M¾˜�Ï›x8+•Ÿ\uˆvÍïoy—]N¿Ÿ‘wÜÎ[{Á7?YL'óû¯>#ŽX§ü¦w=ŽÛ¿3yõÿ®c{À^Kèó»ëaø‘ì7h/Ïl$ûÖÝÇ¢¶mªâˆaìVññ¯ý÷ñÇ÷¤æð“x|çíèÜ<õm¶ûÖ‰,Z4ŒníÓO>¹:Ý–•±ñðž9ó|­ÿÝô©?‹@!„í�ÖpÕQ‚/hsEáÒ§ïµÜ7ê€-ðÞÅ™øœÈ•©¬×ÙU‡m‰óïŒAÛ< wçÚ²%üýñ…„U~{õc8/ |€ÚZヶå©-Ư1üðÝQ“(Zð:�ŽšÄÐþ÷1äKÙjÂ"î›v%têÌv{�Hå o°cçÙÜw×ÍÜtb.vkf| JKK¹çž5-mœ½¦~È=ÀÐQ§ðÔÕS8óØÙ<>y;ö9î*ê¨eŸðÅ¥ÝèdElYy3¿À Þacöo]á…BѪ4Ûj«°ÚUÇÀÝ©žÀ‰@9petÉaÀ•øüËÍRɯ&á=�{áCR·!Óë9€¬…qBK̬:†å’G~%S›’iþußäâîÃСXqý…<þ•¯�ºÇ·ÆÓ™÷ÁH†Ï«Ÿþ¯ÿ›Ìæ#aî}“Ypv`ß_Ǧgþ€³·=œ3žÎXBMQW¾}L/¶x$ç^·¥¥¥mJO’iÝÓþnê7¿Û üù�QÚöœ÷“ÿz;‹.¹˜Ió^ÁBðÉ©ï�«B]½}[)뺤-`?�B´Kοÿ±Ãv»S£iÇòçÃÎqBƒa;=׸{6¹ãh›$Ó3rñþ�Ï­ÆãÖ?i{¤Ùü<ÆWÿ�¯ z@z©™ ü7ë€{ðs^ !ä\‘Ã̾üØ$„°(úyB8$+^5pBáÞyŒA~…B4@!øyl äçQ´ò��±!Æãþß?«ÑpÑöØ ã±>?eýýG0-ïç1�«BOÛ™Ù&ÀªÂR3› Lk$Û‰q¿=°ˆõpÕ�ürõñ}f?IÙ–7rÕŠÏ~Ûõ¶ù¿Ë8ùÌZ÷5:á\»ª Ëº¶i ÄÏ£BQ�4¹ñ˜2†57„°0¦ ôÇÅiˆ]ã>ñ9¸ÄÌ6 !$ǦWòó(™Z;í†äûÅ©çÃNÏmÄ[¯í¸Fx¨5BqöpB_`güæ#éüòŸ`ïØ}‹ÑÔÔ=ÄãìÉàÏ2r›£ JÇííÚªL§o>eß:Œ±%ÅÜxö©ôºÎ ÁŸþ�ñ×ßzœ}Ž»Š7Ÿ˜Ë]#oãÀއOçõi•0-ó1ãœsÎ+è²®mÚBðó(„B*ël<šÙ!ÀÅ@Òzýª™MǪ™ ¼‹»áXL5³wð¿ç¢«ÌlÞƒ¸ P tÆ&n8Ìlà@`_`xJÎÇCŸÆßOS�w̬{̧¸µ=5—÷ø>ýŸ?Û$éx_{v·7W|õGù䙃E]aç=¹sàéÌÛq«¦V´Î<ñröžýo½¶#ß»üo|\3½^øy§åÁî}Ùüľ\R÷oXðúy<øåÙ·ü@ÊWìÜBR !„¢5XŸžÇ�qƒ.!ÛUÇ�TÞ]�/Åí2î;*€RÜØ[”£Ì¬8„P‹÷þ Vº ïm,%k%׈¥ö¹Â…h7µ�¯uýéÏ­sÚsz�s¹{õÿ’âÀUÔrænCI%Ú;¥_ùâêß¿ûÖ\[S?üÁî?^#Í££&AQ'Î}È É_õ­åð§ñ¸lG!„¢]S”?J}BW…,„�jGÆÿ'†¦'a©8½b¼©< !ô !t ! Žâ~Á�Ë^À¾!„’ÂÖÀ×�CÍ,iéŒ�ýb¡4„Ð_‰õ”¸€í’ &pæ®S8s×)k„�þÑbNÿhq£éŸxèüFÃEÇeþákçŠciŸ:–ö­åW§ìʯNñ�ŽšÔœ¢ !„¢hnW]€ÓðÝÞn N7à$|±œ™ñð>À²«I¼ÂD3[†e�BÆÏãìTvOá®8rúy”«ÉÔdšS7�!Ÿ.äŠÃvå´y»QSÔ•g¸Ž¿6ž­‹¾ Àgo À!ÏïË3C¯ƒ‘øÕ›réì<1ósÜýµÞ̫۽Ùnõ$™Ö?_êjé·`1 úõàÂÇÞ ¤(ð›=€ÚÚ5Òνo ›�l;em(\®:„Bˆ†iWfv8p/°>äôˆÂkYqήº‡‡>Ža'†vÈJó!p{áòèªcëÂÁYqªbÚ5¼›ËU‡BˆÆ�«! ‡ðÌ…�Gx©a?�~ùÃõ>o2EDtÖו‡\u4ÿvÁÄ9¸ß̆…æ§âÜ<lœãìBH>ùæ²j-ëøÚÄI#W’©]Ètä)ìkÿ ¶Ö˜:u~Ö} ªÌG¢?µÅx-F]]‹-¢oß¾TV�V/ß3ù;W­ø¬]ëI2­_¾?ë¾3¿:œ‡Ùš�[žÊÌY·ðÊËÇRTTÄСCY¼ýÎP\\/íùûíÒ¦ÊÚP¸\u!„ Ó,Æc¡ø(nÍl*p n¼%q–á_=§šÙD|áœ#�{ð¥$7Í‘u?2>×ÙÏ£\uH¦ÖNÛTùž4ëÏÜ>èG|Éî]^eETFãqÄìy¨4S¯‹ŠŠðNù n¾)Lù¬]ëI2­_Ú™_NEE£GM~ÂûC>Ç/Ïíǯç-ðÅÅk�çœsN³ÊÔRú—«!„¢aÖyÁ3nf�˜Y2×0Û€cf˜Y¹™-1³gñUWK²âícfÏ›Y90?Æéƒ'½Ì,¤7|�WRqv2³§ã¹wÓˆŸG!Ú 6ú .½õÍzÇžÚbdu˸= `fÛšÙ_p?�¯Æ8¯+�'B“ñWç‡Åm2ðDâ 2æWì�÷j^ Œ¦´çy*B$̸òpfÎrÇî÷ÝWÆ�w._½]8ìüd�¯¬Þ§™0cæÇ„hˆ³n:hõï»»z¯ö⟟Fuç»°Mä°^�[K4!„B´ë3lu î#!íçñ@p!>ßqð&îû1éy¬ŽÅç&ž‡1}Ø#„0+•ïB¼G2—÷0pV*|>Dõ à6ÜØ|8ÀÌzÊ€�šÃoá•ã¾ÍÎ;»�ØÉ2߃Ž_Jmq€½`ÑËËèöջꥭڣ² Ñ èÏãxOvßÙû³í¹·²´¦– wŸÆ/yž{îYc�2!„B´#ÖÙx !ŒÃ ¿†V[=*ù]uœ ìGÆUÇ*`ãxüÛÀvxÏd?ÜGB°Þ»8ïy\™ O\uŒN�¯7°˜\uÑÞ˜øÈsüdêžË2Ž_Zïÿ§Û½GÑÿêÏåºùùgÙî['6¯€¢]rÔÿ¢¸êv<úž[y?'í¸�_<ÒÚR !„¢¹i–sr¸êBXƒ·�û1ø*«o'Ï™ÙN!„©1üZ`ÞÓ¹¾ØÎ6dz=�µ0Na‰™Uǰ\rÉÏ£djW2]:s.W”ÔqU Mú«v{À{�Ú"ß3ê²Ò=i:+Ë;†ž$SÓä;à¢K¸í¶Û1k&¿ùöý~1³ñf6?újÚU‡™]ff{›Ù 3{ mü ø›ùÀÏÌl¹™­4³)Àç€[ã9ö1³‹ÍìÑ$p?ðxáÓxÞ§�©À;fVafË€¿·¶ç¡FBäãä¶`à•Ã9릃˜PåS„v7Ê:�²÷àw?øm½øÿ½ÿ—­!¦hlúü›QÑ•YƒŸaìWf0¸Ø×CûùƒOµ²dB!„h ÖÇÏãÎÀp|uTp7o¿jq#ð|Èê×p××…þ—Êcûx¼ï•ÌÂ{%Á{Š÷XÎÊbÜl,µÏ.D‡¥×�âû+Êøü”ø~åAÌ?‚c¶¹€î¿öá†]{ŸÕX¢ÓïÓïòŸ~›®Ý“w+ÏX=DU!„“u6CW…,„�jGÆÿ'†*CGá«£Îv‰ûÕÞÈͬ>ÿñ´BÿB)°#¾ 먭èìB( !l |8Ô̒ᮃ�/†JC=ñ•XO‰ !²xåpìÒåüµësœô\7Î¿í«­,•(4n˜ä^“ž˜üg–�½ÝëÍÎhe©„BÑÚ4ùœG3+î~Bø_Ž…hvǵy:9B˜mfïûOá>—…^MÅ™‡¦î‹ûƒLü<ÎNåýÞS™ÓÏ£\uH&Éä\uÕUü).R(25g¾’iÝÂ*ÿ2…ÒÒRv¸î¸nÍ4í¡¬ …š«3»¸ ¸6„ðãxÌð•ÃOz¯gf�ðB!šœ&wÕ_t‡„‚™M® !\Ã�n!”dåõ40-„pº™] œBØ!+·1íåÑUÇÖ!„ƒ³âTÅ´÷ä�w rÕ!„¢ ÉU‡™í‰Ï÷_ü'e<^\œ|üŸN2$„P¶–yËU‡Bˆ\4þþ !¬÷àˆÔÿÝq›§ŽM~œúP•#¯g€ãï‹�)9âL.Œ¿ožÊ§8¶yK€ž©m tíÚ5”––®±õéÓ'<ôÐC¡OŸ>MVˆùJ&É$™$SG+kCá½{÷ñÝÖsCÞ�ºÝqÃp0ÿ >·pAÖ»m)pú:äß3–S›6mÚ´iKo�¾ÿšzØê—€þÀ§©a ÅÀïÍìÇqîâ\ ‹™õ!,I¥í¼Ï6Í‘?2>×ÙÏc�«É$™$“djµ|Û‚L2§‰¹x,„ð¬™ý�ã�¬éÃÍì, qW5Ÿû˜ÐŸ¦l@ÃÓ6„Bˆua��ÇÂ8ü gC«­f»ê˜ŒÏÊ£øð#3ëü7g¦¢Õá ÚÔÄã“�•©ðÄUÇèÔ¹{‹‘«É$™$“d*˜|Û’Làªã9`hÖ±Û�ð6Ÿà£rFo˜Yüì-'¦BˆŽH‹»êHÅ;¸ 膿!|œ ?˜„÷tî…¹ùwáû1\®:„B4)…äª#ÁÌÆoe¹ê¸8 _…übàäªC!ĆSX®:RÇ{ƒqßTãÃO»6r®oÄóõ•«ŽŽ»Œ¾d’L’©íåÛ–d*WYï®qDW!ã®c >lµݳÓ:æ)WÚ´iÓ¦-×Vp®:’…s–SÍl"ÞÃx$°F�adbÜo,B®:š<_É$™ %­djŸù¶™6d4NsB8 ëÀ�Ç1­ ŽBˆL‹»ê0³­s¸à¨6öO�§·™ÝafËÌlYÌ2 ÈU‡B!„B´-îª_¤ç2ܘÜ8Ÿó¾+f¶îÏqo|NÇUx¯æ¼§1®\u!„B!D Ñ®:V»Æ«­vÃWA=«‘8éÕV?�¯‚šøb|¨Æâî>ºC€‰Ifö 2þ!w.ŠAI>¯Æô½�ý�c�oƼsúyB!„B±~¬Ï‚9/’1àú›Ù.Àâ¸í�÷ v¶.ÃÝn<BXff·à+£¾ÓlóúÀÌönÅ]z<ì‰÷@>Bø4Kžº¸©ßB!„B!š�õéyÜ÷ëøfüŸöóX‹÷~ ˜†ÏcÜ8<„P–Êã\|¨ëýÀ˸ÏÈOñEvÀÚéü ø¸XI}_’ÀÉÀò˜ÇýÀ¿€b2Cj…B!„B4ëÜóB‡CmhµÕŸ°æj«wšÙîqÎ!!„J3›Œ/`SŠÏC<3û^ [ƒ†\u”””ätÕѵkWV®\I×®]ט³¾a…˜¯d’L’I2u´²6.WB!DÃ4¹«Žq6à ÉcCÿJïôÇçEž�¯|º_4,�îN!Üã—³€Ÿ‡nŠ®:…É:_5pBáÞ²Œ!‡«!„"‹mBÓ[[ˆæÂ̶Àß©B!Dš-CŸ5¸>s׉Â3›AfµÕäø2ÜÇÔT3›ˆûs<¸‡ÌœÊ÷Sñ«Ìì`«xh.>tu5fÖ›FV[Å}Eþ!ëX|Þe.zà/×-�²& +Ä|%“d’L’©9ómK2%Çz/´f“»Ü éJHGk‹ô”éhí�žòÓÔ:ê�¿¤Ù�G3닻ᘓ/*™!¥oàCY‡à‹ê`f�ñx’y‘€KÌl³B’÷Á1]ÎÕV³]uDt‚™ÊZ²œe®oX!æ+™$“d’LÍ™o[’)׆öHðaG«¿,çÓ•�ŽÖé)?ÒÑÚ!=å§t”7�õñóØ÷µ˜°MÖj«cp‹sȱڪ™m <�Ïiܸ÷Óø8@a¹™Ý\jf3qƒñ§ñ|ÿˆû§ñžÉ;Ìì§xâÕÀ-ª`B!„BÑ´¬OÏãÀRÿ“a ~ NÀÝỏq� ™ÕV+ñÕXŒ¯Ì:xØ7„0?•ïO�U¸¯ÇRܯãÈ€B­™ü_mµ_•õüõ(“B!„BˆFØ ÕVà�FÂ!̾²ç©Á Á�Áà>o(¼ ¨.eÍ¡®VˆùJ&É$™$SsæÛ–dÊ—¦½ÒQ˽.HGk‡ô”éhí�žòÓâ:Ú ÕV…B!„Bt ŠZ[!„B!„…�ŒG!„B!„y‘ñ(„B!„"/2…B!„Bä'„Ða6Ü•È;¸ÌåÀàÐTø 4°�¯2û›â½|(Áý\V5�ÏÛ�œ£÷•Yg‡¯Â}f–óc’°Z`Y Ë•6É? { w›‰€ê˜ï”¬r×Åóß \Ó€N¦ãîSrå[ÛÈ9óÉÔXÚꨃ\z¨‰×æú¨ËÆÎŸKž•ë˜&Ä4ËóÄ™üX’CÞåx��Ñ@¹§çÑÓ¿�'H[ L>ÅëkZ'«€‡€›c¼\úxèušKŸƒ€áñü�]ÏòTxú\µ1ßÆÒ®mX¾x59t°6õ´&KÆÆÎ“èµ¡çLuÔECù$ºY™ŠS·çl¬ÌùÒ6Vþ:üYÕXÚDŸä®Gë²¥ókl[Š»‰j¨îTs{¯¯‡ßÉ5Nž-•1ÿpÃåøsùnà>`f,ïdàœÖ~ï­å»ñ `Z,ÛÀ—Z[¦VÖÇpà`v¬ Gd…îÓzv¼Öã€/´¶Ü-¬£‹€×È´G†HOkè)_»³Ãë¨�º€k¤§Õå“ã}5·µôÓÑzgâ¾*÷žþmf_ˆá�Äc_Ä_¦ÿÊJÿ3àüû™ xƒåàüÅóàãV�7V~ ,À}\ŽÀßéÆ_9ЉÏÁ IpápàM¼R¼|ôˆá÷Ÿ]ñ‡S5ðQÌsaÌÿå˜ß”˜f¿(ˉxc¶.–áßÀ-1í'¸[•qÀö@—˜÷*¼¢–»ßÁýuÖÆð%ñüå¸OÏ'�GÉ4¶ÿã' ½åÀ±\K£ÌóbÜ—�ÇÈ4žOL¥ Àÿ€oÿů喝ßXÓ¬Œ2½Ësj<çÓÀ[±üÀÉÀøµ­Š:¸¿–�cÙO‰é*cøà8üš-À_¤¿�a¥±eQ¾¥ñ:$ò?Æ9¿öKpcލãjàóxýy-ÊMÔí-q?¯O¦Ê ~­ÃëÚ¼¨#ây¿¼lƒ7v§Åd˜Ž×¹ïÄðy)}< Œ�2}Å�œ·&îoºá÷ZâÞ§ ×m5^wþLŠºÿPñ)þ,"ó!%a9®÷÷ðúR™ ›†ÙïÆs,M¥M �ÅÀgq¿*•®¿¾ñøœVeªÂÕ/ãõ#ÉwEÔÛrü­¤þ3arü]üøgJÉ=÷~Ýç“ ’<CåÁx®D¦UQ®¤>TÆÿIJ-ÄõX÷‰¼ÓÈ 5±¼•QðFNyü}'C `jÌo™:Ý5†UD½€_—«£ŒI]XJ¦~ý;ÊZ— {š ·Æ8I}Jöàõ01 “²&á7Æ}2õqa<–œë“Ö?¦]Ëÿ"þ |.ÆÝ¿ŸßÆu–¼”ノ×ûaÀþÀ±øýð9àxüCâÿ—›ÙY0fv þðÿð{òEà 3Ûª5åjeºá×½¡k÷3à'1|OüÞÆÌz4¿=2oïì ŒÆ]¿=mfÝRq¤§üíNé(…™í œ†¿‹ÒHOÞÞÝ,µ M…µ¬~ZÛšní o@œ�J…]CÆ;"^ŒUÀ1xæb¼€ßâ� UÀ11ý+d ̹À�Sy÷¢þ—ø7€ÍÉ|é7.ð›Tº½YóëÃuYigKÉõÞcš�ö·1ÏûRÇþì˜þ>õ{ ÞÀUuÀ9ñXU*ìžøûëqÿBVÚkpã*9öNJÞ}ðÆéøA”œIÜ­²Ò¦åÜŸœŠ“äyCýÕÔ9ŒûƒR:JâÍÄû›§ò‚bI¼Ó£žŠÇ~œÊ;ÙžÌÒÿ`NL—|ˆX…ßüI¯GR?&Äýû)ÙW�ùâuHâ�ÇöJ�ïUÜp ¸�uEÖù’-ÑõIYiÓ½®÷ãzÒƒ–쇑©ïIÜCã~VÜÏÇëk~™ª×IþÆô›“¹·ÎMåW‡¿XªðÆÊ±©°ß¥Òf_Dz¸÷£²Ò&uª ÿ°’ÖIrîe=˜5{­}ý(ul>p[êÿvÀdzÏ’�6iy“°¤·:)Ó!øì$¯Å1íA©c‰ž–ưwqCì®,yó¬�×6�¶"uýŽÀ ¥ôý[›Ê÷™¬²&zú\LwG–ŽÓùî�•vZª\Éóø'©ð$ß$]ò\^÷/Æ4�Ìý””5àÏât}ª#Óã�Ô§Ýpc3y–ÝÊšõé\ÜØüF «:ÇsÃz¦êõ Àó­ýžËó|øKÖ±ÉÀå­-[!lI�Mý7üýAêXI¬‹§·¶¼­¨§~QWÃ¥§¼ºZŒ„–Žêë¥;Þî…·�®Q]Z]Þ1À[ „µ¸~:ZÏãj̬ØÌŽÅ¿0NH`fóÍìCà¸a°iÜŠñ^šxoÙ¸¾=þU ÿúV„5 ý€¯¥¾ìŽ÷t$½ ÷…fã½U¸qyP ;ÑÌ®ŽiÿK¦g#I»2•¶Ø7pÁ{ê†/„&’ùJ>Ì ê÷î�7¶ˆçüÿZ_ãGù>úàGbt|ïíÁãÄsl†÷´%�y¢žŽ‹ÿÿŠWú­ñ†vÀ�„¤Q½4Un€p£ |¨ÃÁñ÷éq€�pÃjhÜׯs~7Æy×ùGdzç’Þ¡ÞÀDà«ñ5°/Ð3U¦+Íìj2½0uÀQdz‡æá�ϤÁ~�ºÄt¯ÅcÅd†ÆíˆÕñwn|ü«RWàkøƒÜ@îã틬Ø%†Õà×ëËñÿ¦øu.ŽçHêÓòÔï%¸ÁbüR2×kÞ ývüß XBx5þŸŸ:ïIñ÷ܸï‹A˜Ù%fÖ…Lok/¼�ò•E�$Ôˆaà_qΊçü ~/&C@‰ò‚Ðàu%I[‰÷D”…^&Ó£™ú<ÎÌnŽåM®cÒCøLÜ÷M¥íŒ×kbŽÇŸ'I\¢Ó{ð:^ž ë÷gÄ´¤òªÄïǹxïpr?ü:î»ãzÚ ïùß“L}ø=ó®ç’¬´]ÈèìèXöSº¨ŽùÞ†×éÄ)qß”žþ‰÷Öÿ uÞäŸEIïar¿üú>Ö‰äºo÷scºt¯e¯¸ffÉ—êÍã¾–Ì�A¸ŽËâo‹[º>=‰×�¤®tÞ !ÌN�·:Ê{tÌë•BÒ»úT Û=¥¯^dF�QÏ»S¿ç—øß–—¨M° þ \­³BþÑ®#ë,¹“ú.=e‘£Ý)Õçà±³YÇ¥'g°™Í6³ifv¯™m�·¸~:œñhfCÍlÞè¹82„�OàÃôFâ= %øf€�ã¾Ø(þž7p#£!„%xcov K†{íLf(ì¼áÒoD]—ʳ7ˆ–Æc×G9þóM¾à[,GÒ0OŸ]É4 7�ùü:–;‘}~V¹“á¬FÆP< žsf +Ƽ_ÄÞŸ�é¥I†¾Mæ«þßÈ Mz_{ƲLÂ�ÔÄ L •ÉøƒuÞÈMŽDzvNÉJŒŸôL|=¦Ø2¥“?ãúÞ,¦O†&†ROܨþ+#"áH¼.ü%þ¯Œy%CŸÀ{ò¾�÷æ%½XÝcøhÜ@›†7Ø“{® ïùãàZ|ØÝ5QG+cÚx]1¼®,ÁëÓߣL#cyªâ¶],˲xÎO€Çã9º¤Ân&SŸV’©7àäW‘Ñib€ ÁëEzÈèüÔïĘI§½7îÿ \Fæ£À�ñëód ?7òoÂuŸÔ™„�ðk÷dL{Y*ìEܘNî•dès"ûb¼�þ@MÒVà=¤µd†>&×7)ÇRüe*~&Fžá½éâzÞ,Uþ�q}Vá_  ì�Ê·¿o&ÏÅc‰á¶f\�_»/á/ˆ$]¯x®/—à÷Þ­©p‹é®Âï…ÄØ*Ç{ï·Â¯Ù˜vZª¬‰Ñö5¼ÿ8þ¯&c,ö�a‰¡•z<1¿$^‹ˆifÄ}úC̵ø}ÿçÔñ�•n>nøB¦÷v>™k’Ôáb¼7üãÝFø}tV,ç,ü´ÊÜÿh‘<6&ó|OÎû­ø;É÷ëI`|îWãÏÌlŸÿ& —Mp=ÍË:>�X±‰^¤³ˆ™þæ¥BroJO‘FÚ�ÒQ$Õ»áv³‘ž|„È ø(¤Sñr¿bf}iýt8ã7nhtÁ�µ:¼‡r.™ÓÛ£ÌKâÿb¼1XŽ÷*×’idÏnðίÍ@2FéÆdæ»�~ÅdŒÎdþ؃ñ¼i£.ÝÀ·ÇïŠû¿‡~…×Õ.xý<ÿP~-&â ‘àu<�÷–øŠ#ñ^­SRaed®Å(üCAÒ»Þ¿f«pCg|*m÷¨‹¥À¥9ÊP¼B�_×adæB~ýúÇ|“Þyð9ÔŸÆsŸÏÝ/•oPB87ÆÛˆŒ‘—ÌUžCfñ§-Rò¬Œi¦â÷D™�Hã×íÅÂ_ñºž<ëkðëß-Êü™aÙIÚ¤ýޏ"îCL÷~<'d åñdæ¨l÷�ðûš¨—ô³ 2=Šeñ¼Óñáæ—áצ4žÆÈŒèŒ×å¯ãsƒ!3Ä|Þ ®Ÿr\Ÿ�EÙ*ðçûüùò}ê×§¤ÌéóŽÇuøV û{l8§ã„8ŸéßÀ¯CÏPødè–㘨�t–ázüÝùíaÒS#íÎH‡Ö‘™ ÄÛcǧÚ¹è°z !<Bx „ðnì™MFú|/-+Y³é§Ã�!„êÂG!„×Cá ÑsÒqÌlÞø¼�Ì×ñ¥qß…ÌÐÏdzª–â ]ÇNö'3Lkg¼�znü?_p!it\›:}òE‡xþ„¤aÚ‡L´Þ˜þ½™M�iKð¹›ÄœýÈÌA,%Ó²ÊÝ/†MÂ{àÀ¿Â¯"c\ôÀàÛã�¶¡1Ï­b™wÀ‡¯m‚÷L®ÂëØ!xcv|xenô�ù\B¦7à?ñX>Ç)i`Vã�ÓÁdŒÿ¬Ì$3OŒøÿäø»ï솣ÉP¹Yx#¸þâ+&3ä7!Ý(L¡]cÛÇÿý£ “RyÂç� Á‡ ßõU„÷]Ó%áÆ@)~ýv�rO�a{àunã(gRŸzá†dÂÁ¸“ËU¸¡õÛÞ ï±­¥þ"9]ãùÀ{$·�qƧÂöÂÐiãqÓÔï$�Ndî�ì¯_‰Q˜;Çý, [áR|á•Ô7Šð¯¶5¸‘™faŒ?)Ê×ÿ’IŒû¿Âwq#ìµTºÎxÏÓìÔÄ^©ðÇÉÜs‰�”ô~'=uoÆ|§¥âþï}_…)i}Aü0ïÝx}K®ÇP\߿ǯ{—TºÞ¸¡¹Þ`ûn°%zJ^‰îÓÃòK£Œ5QÎy¸žúÄðÏ‘©÷É�Ó$ߤ×þ9¼Þ'ÏÈ,EÌ7¹’ÞÒ2zyÀÌ&’Ñq�˜g9~Íw�Ç·‹ûdñ¢„~)™Jð�sÉ5O°øÿáøû#¼ô%3´çs±<¯Äsý-O®Áhê� HôÕ¯sð�d{˜YïÖþÝBø-…ÍBüϾ?“çšX“ä#�t˜ÙŸðQ †f¥‚¤§H#íNéÈÙ/óf¶ÊÌVáë9œ'ºèèzZM¡93˜V¨GÎxÌAÒƒ—æ$¼WlÞ3™!ªµ¸42v@ ŸŠ¯°Wã}‘Ìœ³:|øeÒÀ?ï½HzJÌ,Y=©ÿ‚— eûBÜŠñÆdg ðE5’þÀ � xã} ¼Ñšô,%ÃV‹cXrÞ�¢¯�™CØ+晡\à†G2Ì트Oæ¿}ˆ÷2�J{lJÞÉôˆ¦ÏùbŒßo,áÃüŠcÚ¸Nçà @büW¢N:‘iœ¦ ÀR¼�×9uޤÁ8!–ñKd ©ôÊT™Ù^ñw²ò`Ü8\„I#c^_H•· 7¾zÅ8EÀGf6ŒL]K†'× 2+~ö¯ÍF1Nb¨œ÷÷“éÉ&Æ»Ÿúó)Á{h�#cî�OÅøBJ�¹>‰áñç(cg¼e!™žð�âñäÞ(º§t•|P霒11æ1³ýp]õ�´$¯�b�L†EvO…wÅëdŒŒ„e±¼#â¹7"cÔ¾€Ï¥;:Ê7;+íõÀÌl33Û—L=¨Ã?t$2%×+~Ú)ʸkÌw:#ñc¼Ž%óïL•2‹Ÿ*crŸ%zû®§ës”5àsš‹É\R²g÷ô�ë¯"Ê4¯Ç]ðá­�é!†ÌG¤ä yQ”eûXÎÉdêóÇd>°ÌÇ�5ð<³9—L�0øˆ�‡ðkWL¦þ$çMŒàMã=Ô‹LYçFyF¤Ž%”’ùè×™5ëS2áüÙrv<ž¡¥±N¤Ï[…¥Cb¼DÇ<¯þB¸$GÙ ŠB5>ìztVÐhü¹&Öd^ïVë,ÎAÒ™9×ãóûG†¦eE‘ž&iwJGÎsxÛw—Ôö:þ>ÚGHO)̬_c­Q�Z{¡–ÜðáP_‡VÅ6µQáÝñåå÷Å{?îLJ&Í#³ÊÞ5xÃa!>§*½PÌ$¼ÑtOL1™ULçâ�´ùxƒn)>Œ°olLLJK%s÷ÞÂ�—Äÿóðž‘¥QÞ—ñÞÅŠ˜Ç=xc¸ oT.Â{Kß�‹ñðê¯rxÞëQ�÷²^ŠWÆ�}@¦W­*Ê–¬F8/æ· ž÷M¼A<oðUà�Ê}ð†s÷sãÿðá}ÉB@5xãº:þ~wó‘¬lx~”/ÑûmQÉPÐ91xãSeüÿ_|Üø'ñ2ìõ�”œ‰NþNf\MÌû�Xî|¾Ût2þàÊð{æ’é]›¯a2qþÀœ–Ê·&^§1^-×+oáC9'ÇøQŽ ¼Gå¨TU±¼¯ÇßïàCoˆòÔDY>‹e~¯/Éâ/!^¿Úx-þŽ7–Ós#Ëb>3Ȭ2¼€L½ QGûàu3É7Y¨ŽŒKš×ÉÔýeñwRg¤®aˆeþŸ÷¹4u®¤>.$3Ç6Y|(à†óâ˜×KYyN‹×ï³(ßÊT¾Éç™džÓȬ†šìkâõ,#³¸Sòa#©; âu\žÊ{Ÿƒ“É”/Ç4•QKÈÜwIÏá²x¼.^ïD‡IÞoà áT¤dJæT'ó’ã¾ÇðÏRºYJæþH¶$슨ëäø2«œÎ"3Ô61rç¤âÞã$uâCü+ü‚ÔyßI…/IÅû4î“ëú~LW™*]Ôc¢ÛDÿ!–')Ãñÿ«QOIÝùSJÏÉ"ZŸ’yA�Å3+�çÙ+þŸÿ߉æÉÖ¯µß}yÞ‹Çàõýd¼1òGü™5¨µekE�t'ÓˆMÞÿ»[Åð b==ÿèq7þœëÑÚ²· Žþu0"«¾—¦âHO�´;¥£Fõ6Žú~;´žpûd>šgÞn^ž<§[Z?­®�Vþ­x#± oh<›º�Kñ¹…KãË"iÈdoo“YÅ1×vnXæ ›ÛHº Ý’Æ`5õ]%$FWÝ:æ7o¤/'3'©<ë|eø×ù†ò�7¨çS¿!Ÿ¤Oùé4É‚/ùÊZ™u¬o�&ÆVRÞÚÔ9jðŽ7³ô‘ =\å ¸O¾eYqþ…/ü’ÈP“Òo™:“K†[”e•+}Mªb¾!S÷’­&¦�Œ?jSi™îÇ{Ÿ’†óʬò¾JfÑ¡ì-É¿ª‘83hعûL¼g¯¡úžï~H}· Ù[vÝX—¼7$¼1yJÛXžÉ½¹*OÞë#oc÷v¾°šL†T�#ó‘$ùÀSeœ†?ëŸÅï¹EdÜždoÓ[ûÝ·ïÆ3ȼß º[è¨>¢(×µà _kN¬ã��Z[îÖQC÷щ©8ÒS#íNé¨Q½�£¾ñØ¡õ„·=gãí±Ï€€Ï·–~,žT!„B!„hÍyB!„B‘�B!„B!ò"ãQ!„B!D^d< !„B!„È‹ŒG!„B!„y‘ñ(„B!„"/2…B!„BäEÆ£B!„Bˆ¼ÈxB!„B‘�B!„B!ò"ãQ!„B!D^d< !„B!„È‹ŒG!„B!„y‘ñ(„B!„"/2…B!„BäEÆ£B!„Bˆ¼ÈxB!„B‘�B!„B!ò"ãQ!„B!D^d< !„B!„È‹ŒG!„B!„y‘ñ(„B!„"/2…B!„BäEÆ£B!„Bˆ¼ÈxB!„B‘�B!„B!ò"ãQ!„B!D^d< !„B!„È‹ŒG!„B!„y‘ñ(„B!„"/�Z[€ÖÆÌº]Z[!„Cu¡²µ…hnôþB‘EÞ÷Ÿ…ZJ˜‚Ã̺öéS\±xqmk‹"„¢p˜ lÓž H3ëÚ«_ÿŠe æ·¶(B! ‡¼ï¿Žn<ö–ýæ;÷rs¿"ž¢ßò®Ø¢‚¼ŽëzU6zì®adzðƒQÌï߇ÑKp÷Ï2bUq‡?vÙŒ9ç¶ßð£!¸œ39êŸpØoëÐÇ.Ú´wŽ8Œí_~„½Îæµî‡±ç»owècãŠ\«û¬#{êä× ê^.„cï={k‹Þ·§ßô+~ûèó½BË[ù5Õl$ï¿^øÕÕ#ØüÑþ5ã�”l|#^<�ï�׉‰3fqç·^c`ñâa ]ºoÍyë;œaó^bï®eâŒYìTy+•mÆ_Ãw™ðʱ¸EßÕqn;¨œ“?YÂ#ÝÏ`YŸZ¶œõe&¼r,'Tލw¾!W¾:NöùÞ™4¤žLÉùî)­ZC¦ä|;�Û§žLÉù~4ü�5dJÎ'´Œέ˜»†LùtpË]cÖ�)¯îþÝ2åÓÁý÷—­!S>lüËã×�I:hŸ:¸dðÃ9ïÏÆt°wõÝ9ïÏÆtÐéÜ·rÞŸ�éàß'LÍy6¦ƒY{õ©'Ó}ö]Nûún�çý×ᇭtí²E%Eô Å¥ÅtëVDi—ny�õèÑ•Š�º³Q·ô¬® ´GWz¬*îðÇŠºu§kçÎô,1J(¡{qq‡?Ö³´”Òî=èVZJÏn%tëÖMÇŠÖî>ëHÇ í^.„c%%-}ßv¬×b÷î]©ªêF�’ºvîL×.Ýè^\Lqi1=KŒÒî=èa§gu7º•–bu§gi)Å]=NQ؈¢nÝéŠ())©§´G-=»•°Q·Tw¯¥[7�Ó#t«w¾tœìó•fÉ´ú|¥�Ö�)9_QI}™’óå’)9ŸtÐ2:(*Z±†Lùt�K¦¼:È!S>””T¯!S>ä”I:h—:hèþlL=;u_gtêÑu�uУGîû³1tË–Éz¬Õ{C æ!„B!„È‹ŒG!„B!„y‘ñ(„B!„"/2…B!„BäEÆ£B!„Bˆ¼ÈxB!„B‘�B!„B!ò"ãQ!„B!D^d< !„B!„È‹ŒG!„B!„y‘ñ(„B!„"/2…B!„BäEÆ£B!„Bˆ¼ÈxB!„B‘�B!„B!òÒ©µ(*«WRWUDÅÔVÔR^^GEu9uU•�++«¤|å V–waùÊT”UR¶ª¸Ã«+_AeM Ë«UT±¢¶¶Ã[^QAÅŠ2Ê+*X^^E¹•ëXÑÚÝgéX¡ÝË…p¬ªª¥ïÛU­ýJjQV¬¨¤ººœ²ª*kjÕ嬨­¥¶ÂX^¨XQF•”——³|e ¼¢[¹‚åÔ†Z–WêªVRW¾‚òPGUUU½8e•,/¯b¥•QQâu¾ªªŠ²ªòzçKÇÉ>_E–L«ÏWWµ†LÉù²eJΗK¦ä|ÒAËè ®bM™òé —Lyu�C¦|:È%S>tÎ%“tÐ.u°²<÷ýÙ˜–W¯XgtjàþlLee¹ïÏÆtP¾²d ™Ö !4ó«©p1³žÀg@÷Ö–E!DÁ0Ø&„PÙÚ‚4zÿ !„ÈAÞ÷ŸzýŹ%°vævû¡0‹ŽWöŽZnPÙ;bÙ;j¹aÃÊ^Ýž Çmõý×Vëu[•Ú®ìmUnh»²·U¹¡íÊÞ”rç}ÿÉxtÊBË[[ˆ–ÄÌ’ŸªìµÜ ²G:TÙ;j¹¡c—}isúi«×¶­Ê mWö¶*7´]ÙÛªÜÐveoi¹µ`ŽB!„Bˆ¼ÈxB!„B‘—Žn¤çy3+‰ám¾ìffÀ€—BïÅÃâº7Pvh§×Ý̆šÙ   ¸82„ð>äz7mñý×&Þ_ÍüljZàk¢¡»pQŽàB–½­¶Ó¶~LÅe¿¸ÎÌNHÉ•ÈÑ�\­~�Gã£u™Ò‚ÜW÷˜Y ð&Þ»~OJ®DކäjÙ;­oÂvBÈúo9޵BO¤þ¾kf€��ïÉâëS涨—¦(g®ø­‹Â}©¿ï™Ùëøü›Ã€5’´-•ýzà‹ø¸lÚûuÏYöv|ݧ»à/ûo3³©ðö~½›“¶øþ+ô÷Ws?›šƒ–¸Çš3\ B¨l$jÁÉÞ†ÛiEÀë!„‹ãÿ7ãÂ,?þžŠWˆ²§9x"ö¾¥)D¹�Á{I�þ‡ß§×˜ÙìÂßRñZ\öŽÚó¸ï›mu÷gM ¾ÍB(Ç Ì�‡+ó\ ‹™õn$N¡ÓTåœ lš#ÿ~´]B˜ƒƒã¡6]v3û>|æÀ¬TP»¿î�”} ÚËu!T‡> !¼B¸_ã:ÀõnFÚâû¯àß_-ðljZàkv�çxÃÌV™Ù*`pvü�œ»e¯Gj§ÍÞÏ:6Ø*%¦ì˜Ù |~ì_S‡ YîßW„î !¼B¸ø#™ÞöV“½C�!„jà `tVÐhà•–—¨yˆCÖvÄoúix%� ï‚?p“2¿ÔdÅÙ _Á©­è¥©Ê9èef{¥â Ãç³´]‡Â Äë´Ñ²Ç娯ÇWB˜–¥Ý^÷µ({®4íâºçÀðÚíõnnÚèû¯`ß_-ølj)šúkž†â=1Éö:pWüýIË^�6ÔN{’ulü#%¶ì 'áSK+d¹7겎Ւ±ÝZOö Y ¨-od–*?¿qÿˆ/U>¨µeÛ€2]+Í6À0à| ßA1ü|¦#cŹ›ÜKúÎÄ—`ÞH¿Ea¹êèNæ…€sãï­š²œø<²·ñÕ÷Þ¡õ]u4Xöv5¾ÈÖÀøÃaV[/;ðçxMGPÉíÒTœvyÝó•½½^wà2àK±LCq7µÀèö|½[H·÷þ£…žëÍ w‹=›šAö¹ÇZ¨þŒcMW';m´�†Ï£¯.¶Ç‡R–ß)t�Çóá†î9 Rn|^æ,2®:ŽW¶¶ìÍv¡Úœ�û­©Â­óá­-Ó–çÞXiª�Ïð%}?Ÿ 7|™è9@%¾"ÓNYytþ,VÆÛÀÖ.[–Œà�‹ìmlS–è܉?Ø—ÇßjÙ�Rà)üËZu|PŽÍQ®6WöÊ€›º~·µ²·×ëÜJæù<x–بmÏ×»õ[Pï?Zè¹Þ r·Ø³©do‘{¬…êÏ8ê�);m¸�ޱ­Ä‡¬žš^ȲïËr„¤Ü@Ü­È  Ÿû[ KkËn1c!„B!„¢A:äœG!„B!„놌G!„B!„y‘ñ(„B!„"/2…B!„BäEÆ£B!„Bˆ¼ÈxB!„B‘�B!„B!ò"ãQ!„B!D^d< !„B!„È‹ŒG!„B!„y‘ñ(„B!„"/2…B!„BäåÿùÏ çÎ<;IEND®B`‚flox-0.11.2/docs/diagrams/split-apply-combine.svg000066400000000000000000001676461514775624000217320ustar00rootroot00000000000000 2022-11-26T15:23:21.390398 image/svg+xml Matplotlib v3.6.2, https://matplotlib.org/ flox-0.11.2/docs/diagrams/split-reduce.png000066400000000000000000001542221514775624000204110ustar00rootroot00000000000000‰PNG  IHDRQ! ‰² gAMA±� üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿÿÿ ½§“ pHYs × ×%uÒð€IDATxÚìÝw\TWÚð3…ÚÀЇ®”@D¥ *ÅÅ5Öl4šhLV£›bÊš¸»Æ¬»éu1 1o¬14€‚ E $ ‚¨4�¦¼œäî,CÊÌEø}?ïgß™;wî) ¹Ï=ç<‡£Ñh@?Åe»„˜ú3Ä<П!æ€þ 1ôgˆy ?CÌýbèÏó@†˜ú3Ä<П!æ€þ 1ôgˆy ?CÌýbèÏó@†˜ú3Ä<ПñÙ® t%%%2™ŒRPPÀv�ˆ™™™««+‡Ã‘H$nnnÁÁÁ‰„íJ@7q4 Ûu€�¨ªª*555''§ªªÊÆÆÆÛÛ[,B¤R©©©)Ûµƒ�®¦¦¦ººšr÷îÝòòòÚÚZWW×���¸¸8‘HÄví kó€±Éd²ƒž>}Z,ÇÄÄØØØ°])€ŽÜ½{7777;;»¹¹9!!!11‘í@ æ£ÊÌÌLII …cÇŽ e»:]sâĉ³gÏš››/[¶ÌÍÍ�íê€^ó€ñ>|øÐ¡C“'O9r$Ûuè&¹\~øðáììì DEE±]èb0™L–’’råÊ•Y³f°]€ž:{öì¡C‡âããgÍšÅv] ÈÛưwïÞœœœ%K–¸¸¸°]€^0räH›””SSÓ)S¦°]èöçƒ;tèè&Ož|äÈ‘ÌÌL¶ëÁ8VffæáÇŸzê)<Ðÿ„††VWW§¤¤¸¹¹!¥@Ÿ…q0 ºŒ'>>)Ú ¿;v¬——×Þ½{Ù®´ 1Pjjª©©itt4Û0 É“'߸q3Üú,ÌmC‘Éd©©©‰‰‰fffz~…ËѨÕên|‹Ãáp8¶ëÎ�F£�õÔÆÆfäÈ‘‡Fêj€¾ 1Ê¡C‡„B¡þ³Úx<ž……Å€½�fQCCƒR©ìÒW8Ž……�Çc»îìP«Õ Ú±bBBÂÙ³gsrr‚ƒƒÙ®´†˜ ¥   «Ëx8Nýé"å½F#W•ï`nçMi8_¢(ª5ré\sË8ož¹ )ÿ�ìJ¹1‹æ‰M­'øtï»§ñB©ñ»‹bí.pµn¹×PªÈø¥›H­ÌG¶NW`ff€˜ oBÌQUUUZZ:}úô®~Qy¯QQRÇVµUUrã—γ¥š¢ªo6réü¦® ï´ÂJwBÔr%!DÓ¤b¥tްíÑ­ÀÀÀÇ¿>Ð)L�ƒÈÏÏ755E~j8¼½½åryII Û€Öó€ATUU9;;³] ã±±±155EÌÐ!æƒ())±±±a»Fåìì\UUÅv- 5Ä<`2™ 1ô.橯¯¯«cmy4ô}d»ý�öî%„�� æ¶ôAíÆ~àÀ�ÐÐа°°¥K—^¸p¡ƒŸË_|ûøã�¯Zµª¡¡AŸæçççÇÅÅ :tÖ¬Yßÿ}›7£UUUK–,™8qbpppHHÈöíÛ[�ðå—_úùùÅÇÇýõ×]ÝÊ£S>ŒŽŽ~þùç;8'77wÊ”)þþþ111üqsss'½÷Þ{ãÆ� :tèŠ+˜_'æ„°°°¿ýíoÚŸ}öÙW^y¥¬¬¬w����L&c»ÐZ»1ÏÊ•+§M›vþüù6?=tèPuuu{·ûTmmíÇ,—Ëoݺ¥ûinnnYYY«;NêáÇùä•+WÚ¼Ú_ÿú×-[¶ÔÖÖÊd²¬¬¬¹sçž={V÷´ŸþùÁƒß}÷]«ãW¯^½qãÆñãÇïÞ½«}üÈ‘#555<8qâÄœ9sþùÏê^S£Ñ¼öÚk7n,..–Édÿ÷ÿ·k×.}zàìÙ³·oß®¯¯¿téÒ믿¾páBí’úàƒNž<ùÛo¿=|ø°®®nëÖ­­N(--mii)**úÛßþ¶|ùr}Ê¥ †>tèÐ'Ÿ|rÛ¶m­‚=ª¦¦æîÝ»©©©m~JKŸ={ö/¿ü¢P(ÊÊÊ>ÿüóçž{®½ÍãwìØ1a„o¾ùææÍ›r¹¼¾¾þðáÃ+W®Ô>çÒ¥Kù¤©©iõêÕº÷ßEEEÉÉÉ„�—^ziß¾}ÑÑÑJ¥òÍ7ßÔ=3<<œ¢;\`bbB_899i÷õõ%„Œ7ÎÎÎŽòÙgŸéÆ3¤�ßßþö·+W®lÙ²åOú“>=@/þøã�:”’‘‘±zõêVçܾ}›B‡DÖ¬Y“””Ôæ¥^{íµÀÀÀcÇŽýðÃúM9qâDuuu}}ýÕ«W?üðÃiӦݿ¿Õ9\îᅧ*•ªÍ‹üóŸÿ”Édnnn{öìY¿~½©©éÙ³guCJBÈ�Þÿý––‰D²xñâ?üpéÒ¥ƒöóóÓ>M(BZZZJKKé‘úúzÚê èù«}_GsÛ!çÎ�k5AÑ{ÖŽ'8ýøã�ôŽ{÷t?e¦¥íÛ·¯Í¯›˜˜0÷�ƒ "„\¼x‘~ë—_~!„H¥R}ZXVVF‡,Þ~ûí &‚ŽÏçñx‰‰‰C† ¹uë–îîrG�U«Õ^^^¯½öÚ°aþþúk//¯’’’o¿ý¶Õ™ôÞ�F}Ú,,,!¦¦¦ÚÇ=== !C‡=|øð°aÃ!6lxøð¡ö9t¾ÙìÙ³gÏžmmm���`ff¦O'Ð‹ÛØØìÝ»wÞ¼y„�Ç·šAçææFyüñÇ_yå•¥K—Ò0I—¥¥%0Ù´i“>EB*++ !ÁÁÁŸ|òIPPÐo¿ý¶|ùòVC4LÌÓæ|Å–––ãÇ�B^}õÕ°°°9sæÐ¨õã�?n5`¥Ñhþñ�BœššúÖ[oÍ�;wÍš5Ç�ûí·µÏ´¶¶¦/é‹ää䆆kkë™3gêÙ4èûÚ�y˜Çí'OžÔý”>oï‘�/O¡÷ÙÚ²²²!cÇŽ¥o…BásÏ=GÑô �£ZYY­n�LÕÜÜìààðÍ7ßxxx<|øpÿþýÌ õõõ´ùS§NÕ§!Úœœœ8Nss³@ øàƒ&OžLÙ±c‡ö9ƒ&„èÎyÓíŸèèh�@PZZúÛo¿éS:�£8bĈÄÄÄÝ»w{{{_¸p¡Õï3üÕæ*�«W¯Êd2>ŸG�̘1ÃÉÉéÁƒtÐ�‘ŸŸ_QQAY¹r%íêöØÛÛÓ<�¢V«éÀÚŸþô'‘HÔÕ€>«ó˜'==½ÕGMMMtœ§ƒYYYjµšÞÈÒ{ÐVêëë !¦¦¦m®œ¡w¢”���­­-!„® ¢Æ|||ôi!nš:uj§#<­3�²._¾ÜêÓ‚‚BHtt4sdüøñ<///¯¼¼\ûL:Ž¡ÛEæææ¤­pÑÑÑ‘ü1.$‰^xáò¿'Ó�Ì”?ýñù|‰DÂÄ3t æôéÓÚ!çé4©ŸÏ´ó;^ÐÅ ãuôÍÌÌ^{í5¢5SQ»ÛI;1�9tèPKKKz„ËåN˜0�’ššª}fuu5-+&&¦ãZ9880-"„¤§§—–– … tµ{ /ëÍãñ:N– �¢vcúlžæ»xñ¢öG�Æ<ÍÍÍt¥GXX]S¡óÐûTkkëÇ{¬°°Pw·Vy¨ÇŒÃçóoß¾Mïøél·ŽÑq ;;;}N¦ô>žÎa£)Ô4ÐÝZžû´ZÜB;P7¶¡“¸t�s8 &ä`B>&‘ƒX,vvv&„ÐU=]eaa¡½™½¾nv²ÖóÐþ155U«Õ4ÿx«þi-‹ùm¡ë»ZýDLLLh˜ÝfÌC$¢0,--¥R©Z­f~!™‹k/ë]·Ãçó÷îÝ«V«'Nœ¨gb x„´{kHïSG�Mtbææ¾½M!ýõW¥RéååeccCom™tÀ f‚Vdd$!$--­Õ ­ÆX,,,BCC5 Ý GŸABM:çêêª�Ðré’V“©˜8­ÕWh6…Vééèm·nA¯ßæ�½……s>S¢ƒ?Ì tÂÛ‰'ôoÃÒÒR{ÚsýV§u0vG™˜˜Ü¹s‡vE{¹ÝZ¡á+ó§+y¼¼¼Z�F£Í6ƒ.Z\«˜‡âííMþ7IËÒÑê-‘ÃáìÝ»—²páÂnt,ôq�Ìm3f ÑYÊÏLâj/æ¡sáüqòÇã|Ýafu]¨£›)Aw¿ÑØØXBÈÑ£GÉIÆ:EçDéùðžV‰Æ´�L–0ŠÞµëÞyÓ”k­æ‰µóp¹\…B¡›ÆÀÔÔ”9¿ÍÜt“&M"„\¼xQ·?;% •J%“AûÞ½{G7ðè—Ë¥�ãàà gäI‡¶˜¹m4ÇÝĉu›OÚ‰yèwõéyzf§Ùç(ú;–‘‘QYYéãã£ÿHx„t󸺺4¨ººšÞ‚Bêëë‹‹‹™m¦«¦«hhÌCW¡´¹ÉÆçóu3%èÆàïÙнWn†}õÕW<�."jE7²��—Ëmµ ŸvQ›a!mšîP�vÌC3¶Ñ�Mµ{€Æ�úïÊ ëpèõÓÒÒ”Jepp°î‚šÉ ƒþ¹ÿþúõë !'NÔsãNÚ^úóÍÊʺyó¦���nßÒ™fíÒmö�n—¹sçÎ6ó›u@»>4GYbb¢îiômÝÛo¿MWgÍ™3GÏ¢é�’Æ<ÿþ÷¿ !«W¯nµ%+ùcœ§Íß(@ê~…fzÐŽiÌCסé‰cv–À#­“õ<ä�õ6×®]£oézf�î�·F£¡ üýýÉùÁÚÜ–”A±Óí>É÷ֺ㵢W0hChµ‡ ¢çbòÇ�òáÇï½÷Þ… Æ�7kÖ,ÝÓ:ç¡¿�º=OGð´cúË£ç0 ÃÂÂ"--­Óœuð(êdœG­VÓéULÞ:à3|øpúVw£¸¸X.—ÛÛÛÓüfôI¼nNdmt(éüùóô-½·n3æa†wôIFLþcôÜ�”žliiI§léVi×Gû~� t³Ð+kgQcú¼©©éàÁƒIIIB¡ðÝwßÕ-ÈÞÞž°lÙ²EÏU+ÌÅ !¹¹¹¯¿þ:!äí·ßnsH‡Ëå^»víÀ�ºõÐp‚†]êú£ûlÑ¢E×®][´h‘n·hgYhó º=OItg¶™ZCý‰ÇÅÅ™››'&&¦§§·™Ci�ìIJçeB~ýõ×K—.B˜Q݉[tœ‡£ ™µtÏl• €†.\ ܰ¶9ý‰¹šv¶€è™XŒbn”i†nºE�6hµ9O�Þ”k׊ٺT7¶¡£Uº·×ôëG�¥Y¹;ðÜsÏq8œ¤¤¤èÙ:zñ�7þùÏîà4©Tú믿޺uë¹çžkõ�ÌÌÌÂÂÂêëëéæ°ú ñ­­­íĉß~ûíùóç_¹råË/¿luý•hs�Nl•�ü…jw;�‚:Ýeˆi‘¹¹9�NŸzê©ææfÝ}¢àQ×É8])ñØc�BòòòhÌ3bÄfâ™îR :ÎÃlübmmM‡YZÝú·¨¡1½~éÝÝÝé =§3µ·ö½MÌ8Opp°��M«>ž¢Û?í¡qó«òÆoØÛÛoÙ²¥U CËmó'K{¾½LÚÝÞAº¼6[ÄÄÞ!!!‰äøñãz6 zÅ<žžž„�_~ù%;;›ÂL»Õ­?Mî¬�Œ=šÂdok¯V4–hs]t*W«˜§¦¦&55õ믿޳g�vYÚ1ϨQ£¸\®n8áììLÑ]Á_[[kgg×jn-]w ¥¶Ñ`OϰD$Ñ9‡4ñC§zóh/›‰‹‹»~ýz›é×téŽ_Ñ´~W®\Ѿ&çi3âuqq!„·:N+À„—ä�èHϘ§¡¡A{´-&&†Ïçcz@?ÓnÌCoF™ûr&Q½[m/æ¡ T*ÕÎ�;ÿò—¿<ûì³sæÌ¡ ZZÅŸßqÌCþcôç¡wÃÚSÔΟ?ñüóÏÿío{óÍ7?ûì³VM¦¬¬¬BBB~þùçVtuu%mÅ<ÕÕÕÌÆàÁƒIûó¬t›@=Ã9Bݱ´°°PŸ“i¯êq]Úý3jÔ(�F“‘‘¡ÑÚÁŒD"‰DMMMÚ#ftœ§Í˜‡îˤÛítðP»çiN¼òòòN³\Èår¥R©R©´'Ý 2äĉzfÈ€GB»1½#gn‘™˜‡Þô··ž‡Žó,X°`ÿþý_}õÕ+¯¼2bÄúQ«]赟Ç[XX 6ìòåˬç!mM”ê¸ÿ>sÄÚÚÚÁÁA"‘ÐMr.^¼È|Ԫܑ#G¶ÊXM×)ݼySû \.—ÉdL1hÌÓÞV6º1m”þé§éœ®öÆ‘zxq]Úý3bÄ�@ÐjrZ{Ú\JDújkk™#tœ§ÍxÃÍÍ�öd«‘%Ú·LZpòGt$—Ë;M+Ç­”†‡‡ß»wïÚµkÝî%èkÚ�yè� Z0�ÒéÜ6‡C“´ºq§1ÏìÙ³ƒ‚‚Æ�·bÅŠ½{÷N˜0�è¬ç¡7¸Ú·¼„�øøø‚‚‚Ž'&Ñ*u1hÎí9Qþþþ/^üꫯH‡Ý|óرcÚé¾C­î‰é`…öªŠŽÃäçç·Y[݃´iºë|Ú£½æªS]½¸.íhD(6ìôéÓú ¸µù#£K´wž¥¿QmÆ<æææ4˜iÕó4“[«õ<4šýõ×_;®>iÇ�Æ #„œ¼¡¡!33SÏ/j]¥RUUUq¹\æ M)ÞÞ¼²   BÍÈ(--%:=OÏì4ó»§ý;LÏõ¿€GB'sÛ˜[äùóç/Z´èóÏ?gîÔé í5ñt1}@@3óM[«ð†Þà¶Jíåå5hРV÷µ­Ð(=ïÝMMMCBB …î^“B¡ÐÒÒòÁƒÌš¢V÷ôB¡ÐÏÏïêÕ«Ú)’}||$‰B¡Ð¾×§»—jϰ¢üýýéíxjjªnÝtϧ�ÒÉ í(&wÇhëzk=!dèС„�Ÿ~ú©Ó/Ò_í¯_¾|¹¹¹yäÈ‘Úi¦u7ÕAiµÂªÍž�ŒŒ$„œ8q¢ã_&³…öÏ]"‘H¥Ò‚‚Ýqðˆê$æa†SD"ÑÛo¿þßoꬫ¡£ô)»®VÉÓèD¦V1!ä‰'ž ‰ÚCïÚõç!„L™2…’’’¢û�°Ç¬ØÑ½Û¦“Ó´Ÿús8œ„„BÈ?üÀ¤Y›µ³‡1f̘Aùñǵo¬éŒ>Ýœô]ÏégwîÜ¡5§yŸ;Õ¥‹·©UÿÐÎ9yòd§ñ'ýai}óæÍLç0:˜ÛF;v,!äÚµkÌ~D¿ýö[}}½��]«›vÈ�;w<ØA­˜e<­J¤Cym†©ð(âvüqËfè8�ö 4‹}¯«ÕÂúuí¼mÔĉ;^«Co uƒ¥ö̘1ÃÎÎîçŸþñǵ�WVVÒÌjÝri2èVÓÛfÏžM9zô(ÍdP__æÌòÇL¶Vž~úi‘HT\\|ôèQí¢ÉKƒt{[Ï”t»ví"„˜››?ñÄúœß¥‹wp†³³³µµuuuuÇãrä�p‹ -¾úê«S§Ny{{Oš4Iû´6‡NNN£F�"„|úé§ôý�êv»¿¿?ýÁýío£“ß!õõõï½÷^tt4óÓl/�]±†˜ ßh÷.“f<ÓžzÔú›\.ÑZ”¢Ñhš››mllè�©6:��æébÐ;NÝ<ÎþþþþþþԘƟÿÑGi'0`~¦%%%‡:tè�n"�W^y…rðàÁ¿ýíoܶm!äé§ŸÖ-‘žùàÁƒI“&½õÖ[ëÖ­‹‹‹ûæ›oîÞ½Ëü¸™á©V «èB¯ .´—d-üö>X¾|ùÇiʵ¶¿ÉçB¼½½é[‡³ÿ~¥RI�kûóŸÿÿüóÏ>ûL{CªÕB#í;`GGGBˆZ­.(( cŽðÁÓ¦M+..~á…!–––ëÖ­k¯ôÅ‹_¼xñäɓӦM›3gÎÝ»wûí·çž{N·�4ŒÔí@jË–-{÷î]»v­³³3�|ÆŒ³lÙ2=;¡ã‹3Ý¿ÿµ×^cîØ±£ÕN{4ÆÉÉ)//¯Ó iô‡URRòì³ÏB„Bá¿þõ/š-@ÝhèÂ… .\`úyÞ¼yÌ ÁÁÁK–,Ù²eË×_M�Ìž=›®ÞieìØ± .LJJª¯¯ß½{7sÜÓÓsÖ¬Yô5Ýè‰"‹µ¿KLjZZZ.]º¤ÀÀ#§Ýqž   ½{÷2YÚt…‡‡GDD´šXÕæ-µ——ײeËè”!†H$²°°=z´îù³fÍ4hМ9sÚ,wذannnÇïR;_{íµ¤¤¤˜˜KKKSSÓ¨¨¨Ï>ûìÃ?´²²¢ë7!ÞÞÞvvvÚ÷âþþþO>ù¤D"aB;J*•îÝ»7&&F$ :t÷îÝ>>>ív1—ûå—_¾ñÆb±xëÖ­çÏŸõÕW׬Y£{æ¨Q£<<<Úœ#Gyþùç'NœhggWUU5hР7Þxãë¯¿î †i%::ÚÒÒ²ƒ 688888˜ÊB$ÉÂ… µWp�1ÂÜÜœnJK-Y²ÄÉÉIwÀª•ÈÈH‰DÂãñ\\\fÏžýã�?Ž?^÷´±cÇ2q!ÄÄÄDw‰Ô_þò—7ÞxÃÙÙÙÖÖö…^øàƒÚ+ô�wÞùøã��H$ ^³fÍáÇ™a±ððp©T:yòdíB !^^^ÎÎÎ^^^zö-ôel9†ðÑGyxxÐäzâñx–––õ§‹”÷õÿV¯à;˜[ÅyBΗ(Šj�\:×ÜÄ2Λg.hÊ »RÞó ê�'6µžàS__ߥÁsB‡Ã±´´”_ºküî"„XD» \­[î5ÔŸ*êùÕºÊDje>Ò­¾¾^wÆï‰'îܹ³jÕ*ã× : ï(€qÐØƒ-án$Ü�­ÒMýìLýìXl~W±Û]&¶s†°R´žûb@�˜ú •J¥JFèEÝØ­X£Ñ444èî­<@h4„=�Ä<ЇtãæØ‚›~xTp{~ €> 1ôgˆy ?CÌýbèÏó@†\Õ}ÔéÓ§«ªª<===<<ìííÙ®À£ª 1O^^Þ­[·Ø®0@×p8œ˜˜kkk¶+Ðe�æÖ­[?ÿüsKK‹¹¹¹‡‡‡»»»‡‡‡§§§­­-Ûµxdt!æùá‡êëëmllØ®3@”––ŠD¢¸¸8¶+Ðeqqqqqqjµº¼¼¼¸¸øöíÛ¿þúë‰'T*•¥¥%ÿñðððòò²´´d»²}Wb�FËv�º`Ó¦M*•ŠíZ@¸\¬'ãQ«ÕlW¡û¸\®T*•J¥QQQ„¥RYRRBC ììì#GŽh4:þCC 333¶k Ї`=°€ÇãYXXp8¶+ECCƒR©d»½ƒÏç{yyyyyÑ·ÍÍÍwîܹ}ûvqqñÙ³g÷ïßO±··gB wwwSSS¶k À&Ä<À‡SºHy¯ÑÈåòÌ­â¼ ! çKEµF.�knb5Þ‡+àÉ©”ÿr�•†7^¾Û\Peü†[ÆyóÌMùdWÊ�Y4Olj=ÁÇÈí5&¡Pèããããó{år9º}ûvjjjMM !ÄÑÑQ;l×À¨ók”÷%ul•®ª’¿tž•�¨5„U]3[mWÕ6±Óp¥š¢ª7vÃùMýdxGOfffþþþþþþôm}}}QQ �~üñdž†‡ãââ„@®®®|>n ŸÃŸ9€~ËÒÒ2(((((ˆ¾­ªª¢ñOqqñåË—›ššx<ž««+Í…àéééâ₵vÐÿ æ($‰D" %„h4šŠŠ &ÊÌÌT*•�ÀÕÕÕÓÓ“Ž9::bÝôˆy"‡ãìììììIQ©TwïÞ¥k�nܸ‘––¦R©„B!݈Ž988°]k€î@Ì„Çã¹¹¹¹¹¹EGGB”J%MwûöíëׯŸßËËËËË‹¾mnn¦±‹‹‹óóóÏœ9CÏqsscB gggŸÉˆíéééä䄇P‚˜ ÿc¶Ü©««k•„zQMM ÛU€¾ËÞÞÞÞÞ~øðá„�FSQQAs!ܾ};33S¡P…Bwwwf"œ½½=ÛUè?óôstË�ÒÒÒ1cÆ<ñÄÈ© `8555^^^l×ÇÙÙÙÙÙ9""‚¢V«ËËËitóæÍÓ§O+•Jl †vãÆ�+W®TVVÞ»wïÞ½{lW翬­­Ù+×DÌÐoÑ-wòóóÃÃ×.]Ú^jáp8r¹œí>06‘HÄvàÑÃår¥R©T*�ŠŠ"„¨Tª»wïÒ! ¼¼¼'N¨T*l ½èÚµkG�-,,tww÷òòòóó³±±ár¹l×ëw>¬­­-..¾xñâ�Æ�mbbÒ“k"æè‡è–;ÙÙÙAAAl%¡öóóËÍÍe»'Œª¼¼|„ l×y<ÏÍÍÍÍÍ-::š¢T*™èÊ•+GŽQ«Õ666Ì�——6=555mݺõúõësçÎíûÁó¹sçŽ9’ššúâ‹/º¸¸tû:ˆy _¹~ýºH$È™—™-w¼¼¼V¯^Ý“®�Éd=ybíæævøða¶ûÀxîÞ½ÛÔÔäææÆvE ¿áóùîîîîîî111„¥RIsÁŸ?þÀ��†ÙÈÃÃÃÝÝÝÔÔ”íZC_ôðáÃ�?þ¸¡¡aùòå�ʦ|ÁÁÁß|óÍßþö·—^zÉßß¿{×AÌýJ^^Þõë×çÏŸÿØc�±]cc¶Ü±±±yî¹ç˜LAÝ“““SRR2yòän_Á××—RXXèííÍvßCQQ‘­­­‘'‘ÂÄçó½½½™?­ÍÍÍ%%%t(##cß¾}„GGGíyk€ÒÜÜüïÿ»¹¹yÅŠVVVlW§ LMM—,Yòí·ß~þùç«W¯îÞ£%Ä<Я̚5ËÜÜüÛo¿�?~`` ÛÕ1f˓ٳgGEEõ0 õÁƒ�9²lÙ²ž\D$ùøø\ºt 1 —.]b6f0¡P8xðàÁƒÓ·MMM·oߦ£@©©©UUU‡ÙÈÃÃÃÕÕû¢LÉÉÉË—/´Æœ9s>ÿüóÏ?ÿü¯ýk7ÂxüÒC¿Âáp-,,’““Ÿzê©��¶kdXÌ–;---&Lˆ��íáÃ<™L¶mÛ¶_~ù…Òó):QQQ;v옺åNIIÉÈ‘#Åbqϯ™““³mÛ6¥R©R©!½µ´qòäÉ;vì>|¸�� «}`@yyy¹¹¹o¿ý6Ûè„¥¥ePPPPP}[[[ËL„»xñbcc£P(tssÓÞ!P? Ñh.\¸ðÄOôƒŸæ¨Q£Îœ9S\\ìááÑ¥/"æ�~‹NlÛ»wï/¿üÒoÆè–;¿ýö[hhèâÅ‹{k„zïÞ½§N�Òh4‡Ãáðx¼ÞZ •™™ùÝwß-Y²D÷SžØ”ߤ4fÒBé ®•�oonäÒ¹Â%„®¹‰‘Kgγ°Óp‡Â»á|[ÃN­”Ë凊‹‹CÆ6xäˆÅâàà`fÚýû÷‹ÿpæÌ™¦¦&SSS-ööölWº#??_.—÷�uÎVVV...—.]BÌð_!!!?ýôÓ�7víÚ5oÞ<¶«Ó#Ì–;þþþo¾ùfWÿ©·G&“}ñÅ·nÝ"„0�T*U/Þ½=ûì³ëÖ­KII™5kV«�¬'ø©ûÚbíaÝ;ÝØ ¢`gQ°3+E›‡¹š‡¹²Õp³³¶J7„o¿ýÖÜܼ'Iú{{{{{{:ýI£ÑÜ¿Ÿ¦¥¥577›››Óà‡.ê•Y`%%%VVVææÆ~Úe ...%%%]ýbèçLMM‡š••E™;wî£8ªËl¹ãææ¶råÊng¦×URRòÙgŸ566ªT*¦gèhO/Æ<‰äµ×^ûàƒ ÄdÐV©TõõõÆíHÐè¤Í^—’’R^^¾víÚž/¨èS8ŽƒƒƒƒƒCXX!D£Ñ”——Óè·ß~;qâ„R©´´´¤³à辨}wË«©©©?Mõ·°°¨®®îê·ó@ÿçàà°råÊM›6Éåò… >B j˜-w¬¬¬,XÞ»1›H$²±±©­­åp84Ô¡ÇAï.Åvss[°`ÁŽ;jkkãããéA݃‡\.?|øð¯¿þúÚk¯!uô{4ᵋ‹KTT!D¥R1!Ðõë×�=ªR©lll˜ŒØýfT¡P©TB¡�íZô33³n$©BÌ‚··÷Ê•+?ùä“Í›7/Y²¤ïoMÀl¹Ãår§OŸ>jÔ(CÔY"‘üå/ÉÉÉùæ›oär¹Z­&„p8W×ÞŸø%‰’’’ª««‘½ir¹|Ë–-µµµ¯½ö–ñÀÄãñ\]]]]]£££ !J¥²´´”fľxñâ�4�D"ÑžgjjÊv­.íÇšV_¿óè-ÞÞÞ«V­ú׿þõé§Ÿ._¾¼Ï†=Ì–;MMMãÆ�KHH0ô'‚ƒƒ/\¸�››ÛÔÔDáñxLVÓ^/èµ×^ûâ‹/¶lÙ2yòdìU �¨Ë—/}ÿþ}‡ãääÄŒ¹»»÷Ù‘Ðoà7 ‡×_ý£�>Ú´iÓŠ+úÎ- ÝrçÎ�;áá᯼òŠÑ–(•Êäää¡C‡Ò\¥o¼ñFff¦AçêˆD¢… ÆÇÇïÝ»÷“O>qvvööö Eð}V^^Þ­[·òòòjjj"##—/_Ž<ú …¾¾¾¾¾¾ô­\.§ñOqqñÉ“'«ªª¸\®‹‹ )¢û¢"‚^‡_)plmm_ýõM›6mܸqåÊ•¬ç™a¶Ü Z»vmomª§“'OÖÖÖ®\¹’9B×§š››ÛªU«JJJrrr®\¹röìYzÞ O),,¤/|||ÆŽŒh ‡ÌÌÌüýý™¤���EEE4 :tèP]]],ÄŒ¹¸¸p¹\¶k �<Ä<0ÙÚÚÒIn6lX¹r¥­­-+Õ¨ªªúþûï³³³ ´zõêAƒ¹555?þøãÔ©SmllXé77777·É“'WUU=x𠪪ªªªŠ•š´iüøñfffnnn}gL Ÿ177üñÇüqú¶®®ŽÙõÊ•+õõõ�ÀÕÕ•rttÄr|Ãa7¼¤¹” 1 P–––«V­úä“O>úè£×_ÝÈaO}}ý�?þ˜––&‘H–,YÂìZcd»wï‹Åqqq¬”®M"‘àñ9X[[Ñ·ÕÕÕtÜíÛ·Ï�;'“É„B¡››�<==úÕÇìâr¹–––,†”õõõ†ÛÇ 1'p$qó3`—´ÉÊŽþÿ`‰«ÄÔØû.‰ø&&\!ÄÕÉ2’uY‚•…€¾zÙp…Æb9BGÈ'„8Û›G5jÃEf=m¬H$Z¹rå_|ñÑG­\¹Ò87›ššNž|8!D£ÑTVVÒ! ‚‚‚S§N) sssf!�———µµ5ÛU~ô´”×7߬6r¡|{ƒï`k�˜GÑ¢Rk4†®z+\G`Â#„´(U*µ±KçŽÀ„Ëáp”*µReÀɈ4\¡R*5F-š6\Èãs9�²…([ŒZ6—ËôÂÆ5|>ùòå[¶lÙ¸qãŠ+ ±†^£Ñœ?þàÁƒ>Œ‹‹›0aëC"û÷ïoii™9sfÏ/URR²qãÆ„„v[ MxíääNQ«Õt"Üo¿ývâÄ ¥RimmÍl äááÁzÖ"`‘AbžŸ/–T>h4rKíÌÇEyB.^¯¸URkäÒÍE‚ÄÑÞ^îÍ×òï³ÒðŸË Îß»m䆋¢?ù„‰…"Í�lÍ¥ŸŒY4ÇÆ‰“øB¯\ŠÏç/]º4))iÓ¦M+W®ìݰçÚµkû÷ï///9rdbb¢X,6f/µ©²²òôéÓÏ<óŒ…EO‡æhÀC73` MxíââB³�ªTª²²2: tùòåÇ«Õj&òòò233c»Ö`<‰yj6߯‘¹%BÁïmyب0~é͊߇¶dr¥‘Kg^Ó,/m¬5rÃåÊ–ß—äõä~‰1‹îݱ<�·hѢݻwoڴ饗^brhöÄ­[·¾ÿþû[·n…††.]ºÔÑÑјýÓ�ääd__ߞ礖Éd[·nmii!„0/°ŽÇã¹»»»»»ÇÄÄB”Jeii) �.^¼xàÀ�FcooÏ„@îî½0yú,ämø‡Ã™7o!äÓO?}þùçé�ÝSVVöÃ?\¿~ÝßßÿÍ7ßôðð`»qÿ•™™YPPðþûï÷ð:2™lÆ ÷ïßW*•H }ŸÏ§Ù®G�MQ(wîÜ¡!PFFƾ}û!ŽŽŽÚ!‹knÁóü�yóæ™™™mÞ¼yáÂ…tŠp—TUU8pàÂ… îîî+W®ì•ñ¢^ÔÐÐðý÷ßÇÆÆö|Ðé£�>ª¬¬T©TxàÑ"}ÿþ}‡ãââ„@®®®|>î™møù´öä“Oš™™mß¾½©©‰>Ò³åŽX,¦ñR öíÛgbb2mÚ´^gûöíx 055õõõe&iËåò¢¢"=z´¦¦†.böE•J¥lí3݆˜  &L033Ûµk!¤Ó°‡ÙrÇÄÄdæÌ™£F�ê›OƒŠ‹‹³²²–-[& {r�ÿüç?/^DÀý’™™Y@@@@@}[__OC âââÔ××óù|777�?8;;s¹\¶k �è‹wf}ÁèÑ£MMM·oß.—Ë'L˜Ðæ9Ì–;---ãÆ�KHHè³+ 5MrròСC{²N‰rðàÁsçÎi4<0XZZÑ·µµµ·oߦI±/^¼ØØØ(\]]™Q GGGü'²BÌÐ.ºž'))I.—?ùä“Ú1[îÔÖÖŽ=zâĉ}<ëÿ©S§***–-[Ö“‹TUU�={–Âãñ˜qÿ¸¹±¶0€ÑˆÅâàà`æâýû÷‹ÿ�™™ÙÔÔdjjê¡ÅÞÞží*!ˆy:. ¿þúk¹\>wî\z—O·Ü¹{÷nxxø”)S$ ÛÕìDMMÍ�&NœhccÓ“ëH$’üã999¿üò —Ëe"ÖwY0>{{{{{ûáÇB4Íýû÷éPaaaZZZss³H$rww§C@žžž¶¶¶lWy€BÌЉàààåË—þùçr¹|Ô¨Q?üðí[·‚‚‚/^,•JÙ®�^vïÞ-‹z«CÖ¬Yãíí}ãÆ�ææf¶ÛÀ>‡ãàààààFÑh4ååå4ÊÏÏ?yò¤R©´´´¤ñemmÍv­ Ä<�ó÷÷úé§“’’.\¸àíí½zõêAƒ±])}åää\½zõÕW_íÅÌ —.]R*•K—.™™™™™™l· o¡ ¯]\\è&à*•Š �®_¿~ôèQ•JemmÍdÄöòò277×çÊ{÷î577�‹‹Ã$ ý!æè³åŽ��]]]�¥¥eŸÚc´cJ¥rÏž=‘‘‘~~~½xÙôôôððpº_[TTýkÞ=%%%999„�ÆÆÆ²²2–» Àˆ|||8ŽH$òõõ îûe Ûx<ž«««««ktt4!D©T–––ÒŒØÙÙÙ‡Òh4‰„ŽÿÐ(ÈÌ̬ÍKåååUTTœ9sfΜ9=LM4p æh³åŽ……Åüùó£££ËÊÊþõ¯}úé§/¾øb3>ÇÑ£G[ZZfΜً×,,,,**š?~O."“ÉN�:•™™YUU%‹è_öI“&±ÛcFÓÔÔDƒüšššãÇ�§¤¤H$’¨¨(<»ø|>MõF·ÄP(¥¥¥tèܹsû÷ï×h4öööLüãááÁÜxTTTBjkk¿üòË¡C‡Îž=OL:…˜  Ì–;<oêÔ©±±±tLÃÕÕuõêÕ›6mÚ´iÓŠ+úø}IeeåO?ý4þ| ‹^¼lzzº——W·µÑhçäÉ“B¡0&&&  ‡™]Ì „�»wïeddœBˆ³³33ø£Ñhèÿæåå½÷Þ{ãÇ�OLLd»}b€ÿÁl¹ÓÜÜ7a„V��ƒƒÃ믿¾iÓ¦�7®Zµª/‡=ÉÉÉR©´'Ït566fggÏ�;·{_/))ùòË/ÇŽ;räH¶{ ¡óþ‡ væÌ™ƒfff¾øâ‹xv 00 …BúV.—ÓYpt-Ý1‚ÂápZZZ!?þøã™3gž}öÙÞ�ÊÞŸ`×X€ßi4šsçνóÎ;ß}÷]HHÈ|0}úô6C[[ÛÕ«W·´´lܸ±¾¾žíŠ·-33³   ‡3Ðt�;wÎÄÄ„&å쪜œœ�?þØÁÁaÍš5xÚdff6vìØ5kÖ¨TªuëÖ•””°]#`Ÿ™™™¿¿ÿ„ –.]Dy´)•ʇþóŸÿLJJ’Édl×·/BÌ@!×®][·nÝþóOOÏ÷Þ{ïé§Ÿ‹Åœoii¹zõjBȆ ª««Ù®~kÍÍÍßÿ}lll¯§[ÈÈÈˆŽŽ¦3ýº$33óË/¿ Y°`A{‹2€233{ùå—{ì±>øy@[QQ‘Z­&„hþ@ár¹t»¼¬¬¬7ß|3??Ÿíjö9˜ÛÝ­[·¾ÿþû[·nùûû¿ùæ›ú –––«V­úä“O>ú裕+W:88°Ý”ÿÚ¿¿‰‰É´iÓz÷²ùùùååå+V¬èêsrrvìØñÔSO…††²Ý7�ŒY³f™šš¦¤¤¸¹¹u{ô3tn r,--%‰··7Íigg‡9±mBÌWeeåwß}wýúu�•+Wúûûwõ "‘håÊ•_|ñņ ^yåWWW¶ÛD!ÅÅŧOŸ~á…z=³\zzz@@@W÷�.))IJJŠŠŠBÀÐUS¦Linnþøã�ׯ_ß—W€q”””DFFJ$777333¬ÞÑæ¶Á@T[[»sçÎ÷Þ{ïîÝ»Ï>ûìo¼Ñ�€‡ …Ë—/—J¥7n,--e»eD£Ñ$''õzÂþêêêË—/Ó”š]òå—_zyyM™2…í¾x$%&&ŠÅâ/¿ü’íŠûÜÜÜ.\8yòäàà`<]‚˜…BñÎ;ï\¹reöìÙëׯ�ˆˆ £ÃÝÆçó—/_îëë»qãÆÂÂBv[wêÔ©²²²nçUëÀÙ³gÅbqPPP—¾•ššÚØØøÔSO±Û-�.33³gžy†Ùºº1 8·oßnnn 3fLo]“Ïç/]ºtÈ�!›6múí·ßØjZMMÍ�&L˜Ðë;Þ¨ÕêÌĮ̀¨(.· 4d2Ù¡C‡Fމ¤=acc3lذ””¶+ð¨BÌޝ¯¯ƒƒCzzúíÛ·{ñ²<oÑ¢E‘‘‘Ÿ~ú)[�c÷íÛ'‹Ÿxâ‰^¿òµk×jkk»š`:55ÕÔÔ4::š•ÞèO&OžÜØØˆn݃˜"º¶ä«¯¾’Ëå½xY‡3oÞ¼‘#GnÞ¼ÙøaO~~þ¥K—æÏŸÏç÷~n’´´´aÆu5{ANNÎc�=†A€ž333 Äô6€îAÌQhh¨µµµ\.OJJêõ‹Ï›7oܸq›7o>þ¼ÑZ¤T*“““### ±¢±ºº://oÔ¨Q]úVUUUiii÷v/]���W¯^e»�$䪆�ˆË寯ÆþôÓOׯ_OKKëF.²Ž=ùä“fffÛ·oojjêõ‹·éèÑ£���3gÎ4ÄÅþùgggç®FSùùù¦¦¦...m~Êáp 1Ð¥R©»sy—Ö§õ:º« þ¼¼¼!999½ž• ßÃ= P£F�:räȈ#öíÛçëëëììܻן0a‚™™Ù®]»ärù„  Ú–ÊÊÊŸ~úiÖ¬Y½~qµZ}æÌ™I“&uõ‹UUUô*—Ë577—5)•ª®ÝöõŸÇ573!„È›ZJc—Îåp,D&§I¡lV¨Øix³RÑbÔ¢iÃÍÍL¸\N³BÕ¤P³h—c!Ô××+•ÿS.—˵´´ìaÚÆž¨¯¯W©ºðƒ033óòò*))AÌÐUˆy`€‰Dááᥥ¥›7o~óÍ7Aï1zôhSSÓ¤¤$¹\þä“O®-{öì‘J¥]�{¦§ììì–––ˆˆˆ®~1??¿ÓHòxæí»• †ë™6¹8Z<ïCÉ̹û[aµ‘K·´Ìžà'ð¯åß¿x½‚•†_ú¥âZþ}ã7|ZÜ`+ áoEUg²ËŒY´��Ùì'ÚÞ€‹Ãá\ü¥¢ü~£‘{ÃÚB0&ÌÝÈ… dˆy`àJHHx÷Ýw_yå•o¿ýv×®] .ìõ"ÂÃÃ…BáæÍ›årù¼yó ÑŠœœœ_ýõ�7Þ0ÐãêÔÔÔÐÐPssó®~Q£Ñtš½ ¥EÝlô‡––ßÇvZ”,”.T¨èü*•JcäÒ™†+U«áj•ßo¼URkäÞ°·éfb›’’#× @¸œœœΜ9³dÉ’ .( lppðÒ¥KÏž=»k×.Ýå=ÔÜܼgÏžØØXCT¾¤¤¤¨¨È@#HÐU666��Æ•èóÀ€–��pùòe±X€ æ�-00ÐÁÁáÔ©SãÇ�÷ööÞ¼y³B¡0DAþþþ+W®¼~ýúæÍ›[-¤î¶âââÓ§OÏ™3G(¢Î …âüùóä€Gbèâââ222ZZZ/^ÜØØ¸k×.äíí½jÕª‚‚‚O?ý´¹¹¹‡WÓh4ÉÉÉ�=ö˜á28eee™˜˜DFFèúÆ�˜ºÈÈH¥R™••eeeµxñâsçÎ]¼xÑ@e¹ºº®Zµª¬¬lÓ¦M= {ÒÓÓËÊÊæÌ™c¸žIOOïõtvF†˜:�@~êÔ)Bˆ¿¿ÿ¤I“’““«ªª Tœ««ëêÕ«ëêê6mÚ$“ɺw‘†††ƒN˜0ÁÑÑÑ@õ,,,,--ÅÄ6èó�„„„ŠŠŠÜÜ\BHbb¢‡‡Ç–-[z7Ù€6‡×_½±±qãÆ�õõõݸ¾}ûÌÍÍŸxâ ÃõIzzº��O¯oÕ `|ØŸà÷¤Õiii���gñâÅëÖ­Û·oßìÙ³ T¢­­íêÕ«7lذaÆ•+WÚÚÚêÿÝüüü¬¬¬W_}•Ï7Ô¿ßÆÆÆìììgžyÆ@×€¾‰og®i6ön<±©ÁÛeä&ôM Ÿ}öYmm­X,¦ {6mÚäëëb ---W¯^½iÓ¦�>úèõ×_×3ìQ*•ÉÉÉ‘‘‘~~~†ë�ŒŒ “ÐÐPÃ}�eŒAvücbB´’VOŸ>�âïï·cÇwww‰Db B---W­ZõÉ'Ÿ|ôÑG+W®tppèô+'Ož¬­­�9s¦A{#---::šËÅÜW€�B­VwoÊ}o1ܲb ˜Gêh!ð Wé6Ù٘юQ³ÂØCr"S>�Ã!„ØX›r³Òp©¹x¨ÄÕÈ ·4 ¸|BÇÚ^3ÈPC"mâXÛõîãââöïߟ˜˜H3•Íœ9³°°pË–-«W¯æñ õû,‰V®\ùÅ_lذá•W^quíè'XSSóã�?N�:ÕÂÂÂ@õ!„äææÖÖÖŽ3ÆpE@dШƒ]‰yF†HYlRÈcŽ!�*™U§ü½lý½º°6£E; ŠvÄVÃ9ƒ‚yƒ‚Ù*½WDFFîß¿?++kôèÑ„�·dÉ’uëÖ8p€þˆP(\¾|ù–-[6nܸbÅ ooïöÎܽ{·““S\\œAû!--mÈ�!]ZbЗaî ÀïALL MZMI$’ ?~ü·ß~3hÑ|>éÒ¥¾¾¾›6mj¯¬œœœk׮͟?ŸÃá®&ÕÕÕׯ_§Q@ÿ`�qžúŒbåƒFc·ÄΜ.º:U–ÿ[]¥‘K·4Îòòøï_¼_lÌ¢¥"멞C !g*n]«.3~çz µ˜6ݬ’_7j·ó¬M­â¼{~mqqq'NœÈÍÍ ¤GBBBbcc·mÛ¶víZ+++6‡Ç[ºtiRRÒ§Ÿ~º|ùríO›››÷ìÙëáaØ•…gÏž‹ÅLóúƒÄ<-e%uFn “VïNCõÕªR#—.š«4jBH…ì¡‘K—+ôEYc-+ Â-�¢ªmj¾YmÌ¢ùöæ½~M±XÿüóÁÁÁÌGû÷ï'„L›6Íp¥BÔjuff&y ŸÁÜ6€ÿ1jÔ¨¼¼¼ŠŠ æ]ØS\\|øðaC—ÎápæÍ›—��°yóæóçÏÓƒ•••§OŸž1c†P(4héÙÙÙµµµ111†n&€1!æø~~~NNN'OžÔ>(‘HæÏŸäÈC/졞|òɉ'nß¾=--�’œœìëë;bÄC—›––6lØ0sóÞ@`b€ÖâââΟ?/“É´Ž1"""bÛ¶m>4B&Ožüä“OîÚµë?ÿùÏ­[·æÏŸoèËËËoܸ1jÔ(#´À˜ó´ÉçóÓÓÓ[Ÿ7ož¹¹ù¶mÛŒS� &̘1ãܹsƒvt4xúõôôtggg???ã´Àhó´F“VŸ>}Z÷øÒ¥K SSS�S“»wïZYYüðÃ-H¡Pœ?>66Ö8í0&Ä<mˆ‹‹{øðannn«ãÎÎÎsçÎýþûïoß¾mè:gee=÷ÜsK—.=~üø®]»4��ʺtéRKKKdd¤¡`|ˆyÚÀ$­Öý(***,,lË–-r¹ÜpÐh4ÉÉÉÇ÷óó ^ºtéÙ³g·oß®R© Q\zzzxx¸@ 0\‹Ø‚˜ mñññ­’V3æÍ›'’’’ Wú©S§***fΜIß/_¾<''góæÍ½ö”””a[€΄UÝ1@Û¼½½½¼¼Úê¡ {®_¿n …=555˜:uª�� sÐßßåÊ•›7oV*•½X\ZZš———›››!Ú�.—knnÎå UÄÄÈÿG¸ .×€� Ÿíîè»âãã¿ùæ›éÓ§‹D¢V9;;Ϙ1ãûï¿4h�§§gï–»{÷n''§¸¸¸Vǽ½½W­ZµqãÆO?ýôÅ_ì•-JÏŸ??wî\ö#<"~¾XRTZgäBílÌf?áoÐ"0ÎЮÐÐP‘H¤›´šŠ��2dÈ–-[ E/š““síÚµùóç·9ÂëêêºjÕª²²²M›6µÚA¨{Î�;gbb2|øpÃt!}Z­V·yÂÂ… !»víê­•Jåž={bcc=<<Ú;ÇÕÕuõêÕuuu7n¬¯¯ïa‰È^ýb€ŽŒ5êáÇÙÙÙm~jff¶dÉ’ .dfföJqG�mii™6mZǧ988¼þúë---6lèIØ“ŸŸ_^^>jÔ(Cu@€˜ #"‘hذaä*ðôôœ1cÆîÝ»ËËË{XVeeåO?ý4cÆ }êØÚÚ®^½ÚÄÄdÆ ÕÕÕÝ+1=== ÀÙÙÙ�]À2Ä<�ˆ��/***,,ìà„€€€Í›7÷paOrrò Aƒ¢¢¢ô<ßÒÒrÕªU&&&}ôѽ{÷ºZ\ccãåË—‘¢ú=Ä<� I«ÛËd@-\¸°±±±' {233oݺ5þü.}K$­ZµÊÚÚzÆ ¥¥¥]únFF†X, 2dç@RVVvôèÑ“'O¦¥¥eff^¼x1'''77÷Æ�EEEeee÷îÝ«©©ihhhnnf»²½ ¹ª:G“VÏš5K7i5eff¶xñâýë_¾¾¾úÔ0š››¿ÿþû &8::võ»"‘håÊ•_|ñÅÆ�W­ZåêêªçÓÒÒ¢¢¢ š ú”ÚÚÚ«W¯¶hQ(‡7¦¦¦t¯@�@ÀçóA›; vpœÏç·úz¯äÙèÄ<� Ý·o_zzú„ Ú;ÇßßÒ¤I»wïöóó“H$]ºþþýûÍÍÍŸxâ‰îUO(._¾|Ë–-7n\±b…··w§_ÉÍÍ­­­9r¤q;ب{\­V·´C¡P(•J…BÑÞq™LÖÁ·Tª¶óÏòx¼Ž#¨6?í4îâóù<�ín€¾1@瘤ÕãÆ�ë``$11ñæÍ›[¶lY½zµþÿÝ-..>}úôòåËùüîÿ{äóùK—.ݼyó¦M›^zé%ÿN6ö:yòä�!ClmmÙèNè[¸\®P(4Äð‹F£Ñ'‚b>Õ~ûðáþ¥T*Ûk‹îÐSǯµC©ö^÷äï3ôø7  —Q£F9r$;;{Ĉí�Ãáp/^¼nݺ}ûöÍž=[ŸËj4šäää   6¾v �Ç[ºtiRRÒ§Ÿ~úüóÏ·wfuuu^^ÞŠ+ØéJ08Ž@ 0Ð`íÅNº}ÍÄN���Úq”öëfú1ÁR§Ñ”þá–‰‰ Û?"€�1€^D"Qxxxjjj1!ÄÊÊŠYØÒéeO�:UQQ±lÙ²^©$�Ç[´h‘™™ÙæÍ›.\Þæi?ÿü³³³sÏ£,(šR©TíEM¼V(ÚSº'´7ͯÓ!¦V¯õŒ¦8Û?€¾1€¾Þ}÷ÝÂÂÂŽÌÐ…=;vìpwwïxaOMMÍ�¦N�jccÓ[•äp8óæÍ333Û¾};!D7ìQ«ÕgΜ™4i›] ÐWñx<�gjjÚ»—Õh4ºÃJ¼f¢¦†††­öæøñx¼. 7éùË¥àÑ…˜@_NNN©©©�& HLLüå—_:]سoß>''§¸¸¸^¯ê“O>Éçó·oßÞÔÔÔjžììì–––ˆˆ–zñŒ�òhQª�\¨ ÿ÷YQÁ.úœ(¯‡x\Ž€Ï#„ùÙr³Òðá�N�ƒíŒßp 3Bˆ¿·­«“¥1‹æó:JNhgcÖ¬Pé}±Þ!¶BⲇÃábÑ”îŠ)=_755Õ××·wNKK‹F£Ñ-Ë¥àÑ…_2€.=zôæÍ›kkkÅbq§q8œ%K–t¼°'???;;û�7Þ0Ð „É“'›˜˜ìÚµK.—k§›KKK 577g³ Q«Õ2™LÀ#ª !<1gáÎS#—Ëè+£—þ{ù,MÑ45ÉYlx{3‹F†H�ß½Åp+¦”Je7F¥èr©‚.µº�ç\§U ¾^y=çø%%%Õ××ÇÄÄt°¦wÀBÌÐAAAVVV§N�š>}zÇgJ$’ |õÕWm.ìQ*•ÉÉÉÑÑц«í„ ÌÌÌhØóä“OBÊËËoܸÁv/òÇ4¶kšZ­®¯¯g±í…a}ŸÏçóùfff½{Yf¹TWWL544tpNË¥z}TJ ôÙPêÞ½{7oÞüå—_¬­­G�×Þ¾‚b€. I«�;–˜˜Øésµ���¸¸¸;v 4ÈÊÊJû££G�666N›6ÍÐ=z´©©iRR’\.Ÿ;wî±cÇø|þ�7233»±wjïÂv¨¬hó!뀅¨ÀÈ ·\ªKA”öS�eµ× C$ñëùr)�FC㱺ºº#GŽ:t(,,,::Ú(?Û¾1@×Ф՗.]Ò'f˜9sfaaá¶mÛ^yåæ±PeeåO?ý4þ| #T8<<\(~ýõ×uuuW¯^Õh4�fÿþýìÆ<<Ï¢Ï>*ëÇÚû¯¸6D¤¬@D Ð=†›ã×½$~t¹T'´WœP(ìI?íUXôïÉ¥K—.\¸  -,,äry¯Ü=Bót MZ�žž®OÌÀãñèžÇOž<™ܳgÏ AƒŒr¿ôÒKŸ|ò x!õõõ¬õp8œŸ/–ܯ–¹\{[³Ø0wBÈ…ëå·Ë¹ts3þ˜0ws3“‚ÛÕ9¿Ý7fÑb+á¸(O}ÎDDÊ=#R0Kôú 1-—Òý»M#Ÿææææææ÷ß?44täÈ‘...l÷+ ót™žI«)º°góæÍ>>>þþþ999ï¼óŽ‘ëœ••EÿðÑ?ˆ*•Šõ¡BÈýjyi%k *ªëšŒ_º•¹@©RBd-F.½YÑ…›iD¤}6"€~À@Ë¥Ö¯__\\̼¥SÝx<žJ¥âñx¡¡¡3à!ˆyºAÿ¤ÕTHHHttô¶mÛV¯^½gÏž &8:5Eò¶mÛ.^¼Hþx¨¾0Ô}"RcÝ¥ˆ Môá¦v¨cmmÚÔÔTQQÁÌ7˜ótGBBÂgŸ}ÖiÒjƬY³nÞ¼¹qãF‡óÄO³ª999×®]£¯™Õ�§� õ@¯hjj"„p8gg瘘___777BÈ?üPQQÁvíX†UªÝèààpêÔ)=Ï“'O®­­4h�‘7_ ^»v-—Ë577çp8\.—YãH‡zŒÜu`B¡pÖ¬Yëׯï½÷âããiÀb€nŠ‹‹ËÈÈГ™cÇŽ¹ººfggß¾}ÛÈU½zõª©©éǼ`Á///‡Cã.:ÔcäÊ€!¬]»6>>^"‘°]‘¾1@7EFFB²²²ô9933³¢¢âÏþsDDÄ–-[är£. ÏÈÈˆŽŽær¹QQQ«W¯~ûí·‡N'ûÖÕÕa¨ú7Ä<Ý$"""ô™ÞÖÐÐðý÷ßO�:ÕÆÆfÞ¼y� ))ÉhõÌÍÍ-//3f sÄÍÍíÙgŸ]¿~ýĉ…Bá¡C‡Xè>cAÌÐ}£G�®¨¨ÈÍÍíø´}ûöI$’¸¸8Bˆ@ Xºti^^^jjªq*™––`kkÛê¸D"™>|øåË—éÞÌÝŸ‡ÃáÐízX •Œ ÷Çy”JcÏj`Wddäþýû³²²Z….»wïöôôŒŠŠjžž3fÌØ·oŸ¯¯¯³³s¯W,--mذaÓ§O¿té’F£¡ƒ<̆¤ô…Z­öóóc»  yÛzJ ÄÄÄ´JZ�“““››;þüŽ¿°yófý÷6ÕSyyy^^ž••ÕÆ�5 �Ç#„0‘ùcœ‡Ìvb€^wïÞ=&iussóž={âãã;ýîÂ…  Å®]»z±>*•j÷îÝ<ïôéÓÞÞÞ«V­¢ h�ÃD;�ÆÛÛ›íÎ0,äªèb±xذaLÒê#GŽB&Mš¤Ïw™…=¾¾¾L„Ó“J¥ºpáÂáÇøðálw€aaœ wÄÇÇçååUTTTVV¦¦¦Î™3G(êù]OOÏ)S¦ìÞ½›I°Ö *•*++ëÝwßݱc‡………‰‰É+¯¼B— ‰D¢÷ߟï0ëyèW|}}Ùî9ÃBÌÐ;¼½½]]]Ož<™œœØÕE2ãÇ�÷ööîÞÂ�FÃD;ï¾û.‡Ã‰ˆˆ07ÿïŽIvvv~øá´iÓø|>—ËÕh4�F ¸¹¹±Ýs†…˜ ×Œ5*++ëöíÛsçÎíêw9ÎâÅ‹SRRôÿ–F£¹téÒû￟””$•Jß}÷ÝçŸ^©Téæ¿–H$¥¥¥–––ŽŽŽ\.—Ëå6Œí>08¬çè5C† Ù½{÷c�=fccÓ�¯[YY-^¼ø_ÿúW```HHHÇ'k4šìììÇ———/Z´ÈÝÝ�~”––æåå¥;€“™™™��½fÍ//¯C‡>|Yª` @ÌÐk.´;v¬¬¬Œæ6`»« �êõÒûä0è§N�ªªªš9s&ÑJZÝí«Ñ…=§N�ºråŠF£¹zõê‡~øÅ_XYY­Y³fÅŠí<ùùùååå£F�Ò>XTTtðàÁ™3gb& Lç詚ššÌ™3Ç‚âíííååuòäɧŸ~ºÛ×ô÷÷�‹‹ûÏþckk[^^¸fÍoo•žžîããC÷ä¡ ÅÖ­[ƒƒƒuÓ¸ çè©Ý»w{zzFEE1GâããÏŸ?/“ɺ}Í«W¯477×ÔÔб�NžÆÆÆË—/·ŠmRRRZZZz}<êzœ§ÕªkƒR©TìV 77÷Úµkï¿ÿ¾öÁÐÐÐ}ûö¥§§O˜0¡«ÌËË;xð`QQ‘¯¯ïsÏ=—œœœ››ÛiÀCÉÈȋš¡¡Ú—ÊÈÈxùå—E"»½À¢^Žyx<ž……ÖIƒÑ444(•J¶JW*•{÷î���uttÔ>ÎårcccOŸ>=nÜ8ý“VkG;¯½öš¯¯/!„ÏçoÞ¼ÙÇÇÇß߿㯧¥¥EEE1ÅÕÕÕ%%%ÅÄİÕ?}Aï�óp8ÍÏ{4÷KŒÜ޽7v.!¤á|‰¢¨ÖÈ¥sÍM¬Æûp¼ÌʬÊBcíjn3{P(!äʯ÷n×¹áæfü1aîæf&MùdWÊ�Y4Olj=ÁÇÈímåèÑ£ …bÚ´iº�5êÈ‘#ÙÙÙ#FŒèô:ÿ÷ÿ×*Ú¡BBBbcc·mÛ¶víZ++«ö®�››[[[;räHæHRR’……ŬY³Øí"Ö$‡�æ~ )Í7rK4¼PUÉ%uF.�g%$j !¤ª©± îž‘K§jë›J+ë�\¨•¹@©RBTõÍFîv~kÃ;TeeåO?ýôüóÏ …m䱉D¡¡¡©©©Ç<‡*((ðòòjí0fΜYXX¸mÛ¶W^y¥½AÔ´´´!C†ØÚÚ2oóóóßzë­VI« ä0è¦äääÀÀÀöá5jTQQQaaÛã~üñÇÜÒÒòòË/ÿå/i3à!„ðx¼%K–>|¸Íª««¯_¿Îd/xðàÁ¾}û¦L™"•JYìÌqèuwïÞµ··g»�äªèŽÌÌÌÛ·o·J]Ð MZ�ššÚ*�öØÎË/¿¬Ïz‰D2wîÜo¾ù¦Í…=?ÿü³X, $„¨Tª­[·J¥ÒñãdzÛE"‘¨¦ÆØ“-ú·¦¦&‰DÂv-=çè²æææï¿ÿ~âĉ666Ÿùòe&iuQQQ«±ý DEEEDDlÛ¶íáÇÚÇÕjõ™3g˜AžTVV>÷Üs¬³¸¹¹!æè]MMMlWà‘„˜ Ëöïßonnž��Ð陡¡¡VVVééé………Ÿ|òÉßÿþw¹\þâ‹/v)ÚaÌ›7ÏÜÜ|Û¶mÚ³³³[ZZbbb!EEEÇ�Ÿ7ož��Û�D$Iy¹QÓZôo555åååíM‚€ æèšâââÓ§OÏŸ?ŸÏï|j(—Ë:tè¡C‡þñ�<|øðÅ_|ë­·†Ú½¢ÁÒ¥K SSS™ƒiii¡¡¡æææ …bëÖ­¡¡¡údŠ3‚ààদ¦¼¼<¶+ÐOš™™ùùù±]è·¸¬2hÓ°ž  4MrrrDD„>·4ë@nn.‡Ã;vìŒ3z>ßÌÙÙyîܹ;wî4h�§§gyyù�7¦OŸNÙµkWKKËüùóÙî¤ß‰D"Ÿ›7obƒ €^‘››ÛAÒ€âr¹–––,Î�¯¯¯7Ü^óˆyºàÔ©SUUU+V¬èø´;wîìß¿?77×ÙÙùùçŸÿí·ßnÞ¼Ù[D¢¢¢ ¶lÙ²víÚôôtWWWooï‹/ž;wnÅŠ"‘ˆíNúŸªîÝ»wìØ±fffl×àÑVSS“——·lÙ2¶+ý‡ÃÑdî×”Ý0v¹b-1€¾80gÎ ‹öιsçΑ#Grrrh´Êáp\]]ß}÷ÝÂÂÂV ܺmÞ¼yï½÷ÞöíÛoݺ5uêÔººº]»v�7®¯�¨DEE:tèĉS¦La».�¶C‡ùøø`œ M󠌅m6›e†.1€¾öíÛçääÕæ§mF;ô#''§€€Ý¤ÕÝ&–,Yò÷¿ÿ�ËåFFF~ùå—666S§Ne»‡Ú0yòä;vÄÄÄ´™ãÎÞ–…ñ¦P[kSWGK#—nnÆçó¸„ ‘‰‘K[ {~`Eaaa^^Þ«¯¾ÊvEUˆyô’ŸŸîܹ7ÞxC÷£¢FBBÂgŸ}V[[+‹{¥>žžžVVVuuuÈÏÏë­·x<Û�Ô†¨¨¨ÌÌÌo¾ùfÉ’%º3ÜbÃÜY¬[Øç°!Îl•îëiëëiËbó;…ˆÔ˜Ew‘Êåòï¾û.22Ù º 1@ç”Jerrrll¬‡‡‡öñòòòýû÷çääØÛÛ/\¸0""¢½E;���VVV§N�¢ùz®°°°®®ÎÓÓóäÉ“3fÌ�J¥lwR»^|ñÅuëÖ}÷ÝwÏ<ó sP¥RÕ×׳]µ�Hÿ塈HYl¾¶-[¶ˆD¢Y³f±]€Gb€Î=zT¡PL›6�9R^^~øðáììl;;»… †……u:Ì{ìØ±ÄÄD�@Ðó*¥§§{zz* “»wï²ÝC‰D/¾øâÇœ’’¢}ßf¸Ü,ÐCˆHÙ¢û�"%%¥¶¶výúõ}*= À#1@'*++úé§çŸ^(’ÿ�v,X O´C�5êÈ‘#YYY£G�îa•³³³ýýýoÞ¼ù /|ñž¾¾í-4ê ÜÜÜ–-[öå—_~óÍ7O=õÒ¸õ}ˆHY'—Ë·lÙR[[ûÚk¯!àè!ìI Љ={öß»wï믿~ÿý÷‹‹‹,XðþûïGFF꿊F$…‡‡§§§÷¼JçÎ�ãr¹×¯_úé§üñ3fìÞ½»¼¼œí®êˆŸŸßk¯½VQQ±eË–šš¶«Ч~òÉ'\.wíÚµnnnlWà‘‡˜ #999cÇŽMJJ¢ù¦çÏŸßÕh‡‘��PZZZXXØÃZ¥§§k4šèèèÐÐPBH|||@@ÀæÍ› ÛÖ77·µk×ZXXüãÿHIIAä «¦¦æÛo¿Ý²e‹¿¿ÿªU«$ Û5è0·  ]ÍÍÍ»vírrrúç?ÿ)‹çÍ›Éçwÿ_M¯$­ÎÏϯ¨¨°µµÕ^³páÂwÞyg×®] .d»Û:"‰V­Z•ŸŸŸ””ôïÿ; `ðàÁ�=öf»Á'—Ëýõ×ÜÜÜÜÜ\ŸW_}YÚzb€¶Ý»wïóÏ?¯««ãñx=�v=OZ½ÿ~�F³dÉí\fff‹/þ׿þÕÇöP~~~ûÛß233srrRRR!4tvvƺPªªªjkkåryyy¹™™Yppð²e˰ñ(@¯CÌÐZuuõÁƒÏŸ?¯R©Æ�7mÚ´^‰v¨ÀÀ@‡n'­¾yófaaáˆ#¼¼¼Z}äïï?iҤݻwûùù=óa¢¢¢hx–““SRRBÉÏÏ×h4l× ÀxD"Q@@!Ä××;†ƒ˜࿪««�=zöìY ±Xìãã3sæÌ^/%..nÿþýÝHZ­Ñh¾þúk>Ÿ¿hÑ¢6OHLL¼yóæ–-[V¯^Ý7·(mSpp0}°=yòd¶ëýrBHuuurròÛo¿}õêÕ™3g&&&* mIÉÊÊêê�=ZWW7jÔ¨ö⇳xñâêêê}ûö·ÿú.ŒóÀ@§=¶3sæÌèèh…Bñî»ïN™2ÅÂÂÂ% ‚˜˜˜S§Nui£ž²²²C‡q8œ±cÇvpš••³°'$$ÄÈ� Ð!æ��«®®îðáÃgÏž533›:ujll,�l¶k×.‰D2jÔ(ÃwìØ±ÜÜÜÀÀ@}ÎW(Û¶m³°°ðõõµµµíødº°gÇŽîîî�ăBÌQ]]ݱcÇÒÓÓ…BáÔ©SÇŒ# éGùùùçÎ�{ã�78Žá* ‹Nž<©gÌ“’’R__ÿðáC=#±ÄÄÄ_~ùå‘[Ø`XÏч~xþüùÄÄÄõë×�?ž x”Jerrrll¬‡‡‡¡ë����——WQQÑé™yyy¾¾¾ÎÎÎzfvâp8K–,©¬¬ÄÂŒóÀ@´xñbOOO&Ôaœl̦€!8::2i–8󺥥…¹~ýz=Wÿ(˜Û@š››÷ìÙ3aÂGGG#wâÄ Ý¤Õ*•jëÖ­R©tüøñÉÉÉ^^^ÞÞÞÝ+‚.ìÙ¼y³��öŸÇã�;6>>^$±Ýš¾1 t»ví1b‹3_½½½½¼¼Nž<ùôÓOB ÅÖ­[CCCGŒAIOOï ±›žÁÒ¥Kׯ_ìØ± &°ÕX€«¦¦F{ §¢¢B©Tšššº¸¸H¥ÒÀÀ@©TêêêjffÖÁED"‘©©iSSÓˆ#&Ož,‘HØnÖ£1 h™™™·oß^²d »Õ5jÔîÝ»§OŸ.‰víÚÕÒÒ2þ|BHaaaiiésÏ=×+¥8;;Ï�;wçÎ�þþþžžžì6 “Ëåt‡þïÝ»w›ššx<ž£££T*>|¸T*•J¥ÝZ|}}‘¨ KóÀÀÕÐÐðý÷ßO�:Õ‚ݚ >ü»ï¾KOO—H$çÎ�[±b¤NMMõññqvvî­‚¢¢¢ ¶lÙ²víÚŽ#€þ”Jeyy¹ö0Nmm-!D"‘H¥R??¿øøx©TêààÀãñzXÖäÉ“Ùnî£1 \ûöí“H$qqqlWä¿I«[ZZÆ�@ill¼|ùò¢E‹z·¬yóæ­_¿>))iÙ²el·à‘¤Ñhœ6mš1+Ü�²²²ï¿ÿ>...55•&­ÎÍÍ­­­3fŒ�J=zt^^öh4š3šñàÁ�FcmmM©…‡‡Ó‰jlmk=g�Ÿwä“,6É<ÔÅ<Ô…­ÒGØ{Œ°ggè`ˆ�ý{¶n:Xb:øÑÈ–XSSsàÀ�?ýéO}dŠ­J¥Ú¶m›ŸŸß¬Y³ iÒê´´´!C†ØÚ0Œ\¸páºuë°°šºººVÕ”J¥P(¤óÓâââ¤R©››6½éO­Â@dccãëëËv-~wàÀ�ššš—_~™ÿÍ7ßÄÆÆ^¿~ýÏþ³AË533[²dɆ ÒÒÒF�Ív7DSS“ö,µ²²²ÆÆF&gô°aÃ&Ož,•Jmmm1Q­3HÌÓp¾DU%7rKx3‹p7BˆìZEKéC#—ΙXŽtç˜ðr”äT•³h'‘Õ·@BÈ…{·ójÊ�Üp á· ÓæÛ5My÷�Y4×JhÝ�!5+++>Ÿÿí·ß:¨ÐG^^ÞñãÇ/^lmmM Ý·o_JJŠX, 4téžžž3fÌØ·oŸ¯¯o/fÄ`‹J¥ª¨¨ÐŽpª«« !¶¶¶R©ÔÛÛ;&&F*•:99õÆÅׄ(ÈŒÜí|{óîÅ<<ïÙgŸýàƒ233£¢¢ŒYçVd2YRRRDDĈ#è.—;f̘ƒ-õ~|||AAÁæÍ›ß|óM�@ÀbotCUU•v„SYY©R©D"�¨6dȺ&ÇÔ´ûË€¡ÀÜ6ˆ\]]Ç�÷Ýwß°˜V299ÙÄÄdÞ¼yÚ---Õjµ•••ѪAöìÚµkáÂ…lu€>äryiiiYYýß»wï65519££¢¢�3Ú„˜¨©S§^¾|y×®]/¾ø"+¸xñbvvöš5kZ�®œ;wÎÎÎîÌ™3ÑÑÑÆ© ]Øó÷¿ÿÝ××—Ý�/mJ¥²¼¼\{§¶¶–ÃáØÙÙI¥ÒÇ{,!!A*•:::b)t 1 P<oáÂ…7n¼xñ"3µÌhnGûµZ]__ÏbT*•á.Ž_2è³sçοþõ¯B¡Ð e)ŠmÛ¶ùùùµ¹hzzzxx8�WÆårcccOŸ>=iÒ$.—k´Þð÷÷Ÿ4iÒŽ;ÜÝÝ%‰á ªªª:tèPVV!ä±Çsww÷÷÷ÇR"è³ÊËËe2Yaaá©S§!S¦L1è¿€GNMM�v„SQQ¡T*™œÑ¬T±—WˆIDATt¢š™™Û5…v4ê`bòÔSO]¿~ý»ï¾3ôœ®”””†††—_~Y÷£Â¢¢¢ùóç3GF�uäÈ‘ììl#§X  {¶lÙ²zõjCìׯD;^^^Ï<󌗗þû}³¸N.—çåå]ºtéÍ7ߌŒŒœ5k–1WôMMMtu÷î]™LÆçó�œœ¤RiXX�plllØ®)!ˆy!B¡ðé§Ÿþì³Ï"""l Ròòò222^~ùekkkÝOÓÓÓ½¼¼ÜÜþ»jP$…‡‡§¦¦9æaöìÛ·oöìÙ½{ñüüü/¿üR(.Y²Ä˜z‹™™Yhhhhhh^^ÞÁƒß|óÍ×^{Mû_.@¿¤R©***´‡qèld‰D"•J}||bcciÎhcÎMÐbB2dHdddRRÒÚµk 1ÃM&“%%%ÅÄÄè~ÚØØ˜��=wîÜVÇÞ}÷]c&­¦èžM›6ùúúöâžÌÌÌ;v 6lÖ¬YÆl€!¤¤¤|ðÁ ,ÀÞVПh4šêêjí§²²R¥R19£ƒƒƒ¥R©‹‹‹¡ç„ôÄ<¿›5kÖûï¿¿ÿþ^Ü „ìܹÓÄĤ½{ýsçΙ˜˜ >¼Õq''§€€#'­¦üýýãââzqaÏÞ½{O�:õÔSO…††¹-†3kÖ,©Tºcǹ\ÏvuºI&“•””hOTknnÎÎÎR©täÈ‘R©ÔÍÍ 9£áÑ…˜àw"‘hîܹ_}õUhhhïÎpKKKËÉÉyýõ×ÛÛ÷&###::ºÍO>ûì3c&­fÌœ9³°°pË–-ùË_z˜6433ôW#GŽ455MII133Ãh<”J%ÛÐuuuÇÁÁA*•>þøããÇ�—J¥öööÈ ýb€ÿ  MJJzÿý÷{kùþƒöíÛ7eÊ//¯6OÈÏÏ///_±bE›Ÿ:88œ:ujúôéFî �·dÉ’uëÖ>|¸')³KJJvìØ�€ú±ÐÐÐêêê””777¬í�¾F£ÑÜ¿_{¢Ú½{÷˜œÑ®®®áááÈ ý~¹þÇüùóßzë­ôJŒ¡Ñh¶nÝ*•JÇ�ßÞ9ééé¶¶¶í�0jԨÇ'&&¶7Ld8‰dÁ‚›7oöñññ÷÷ïÆd2ÙÇŸO—âDDDÐÆÏä J‹Zu³î¾\Õbär›UJsŽ¡–.#æh[TTÔåË—ÿóŸÿ¼ýöÛ]½MÌÎÎÎÈÈxùå—;žß’––6dÈ�²hc1i5Ó!Û¶mûë_ÿªÏ\µªªªªªªö²Õõ‡£ùy�æ~‰‘{ƒ#õáŽ|’¢¾ð£æö/Æ.]ìÀ·°;_DwÞã�?ž™™YUU…åÐ)¥RY^^®äÔÖÖ29£{채„äŒèˆyÚõôÓO¿óÎ;ÇŽ›0a‚þߪ««Û¹sg\\\@@@§UWWçååµ—¢Z‹I«óæÍ[¿~}RRÒ²eË:=9''G,»¸¸ºVšû%¤4ßÈ]¡þÍV—³Pz³¬ûßEw˜···©©i~~>öê�V4̓´Çp*++ÕjµµµµT*uqq>|8�¨†œÑ½ÿ¨Ú%‹g̘±wïÞÐÐP{{{=¿•””dcc3sæÌŽOûùçŸ���õ¯O\\ÜþýûYIZM ‚¥K—®_¿ž®ÒîøäØØØ°ROv9;;Óeå0ÀÉd²’’í §¹¹Y(Òùi£F�’J¥nnnÈx`ˆy:2zôè .$%%­ZµJŸ©iiiùùùo½õVÇ sÔjõ™3g&MšÔ¥Ê°›´šrvvž;wîÎ�; äééÙÁ™%%%ÎÎÎlÕ€E¦¦¦ˆy …BA'ªÝ½{—9uuuLÎè¡C‡Nœ8Q*•ÚÚÚb¢€ñ!æèÄÂ… ×­[wêÔ©NG6ÊÊÊöíÛ7cÆ ©TÚñ™ÙÙÙ---]ª‰@ ˆ‰‰a1i5EölÙ²eíÚµ,ìÑh4HQ “‹‹Ë�;wØ®–F£¡9£<Ðh4¶¶¶R©ÔÝÝ=22R*•:99!g4@_€˜ öööS§N=pà@PPP3ÜT*Õ¶mÛ¬O¾¦´´´ÐÐÐnä¿�‹‹;qâDnnn—&Åõºyóæ½÷Þ{Ú {rrrd20P�†í*@/«««k•QM©TŠD":Q- @*•ºººššš²]Shb€ÎÅÅÅ]¾|9))éõ×_oïœÔÔÔ¼üòË�^­¼¼üÆ�ÝKE ‹‡ ròäIvc�@°dÉ’ 6¤¦¦FFF¦¤¤dee=þøãˆy hnn.û_���LÎè°°0äŒx´ æè‡ÃY¸pá»ï¾›––Öæ¼²¢¢¢ãÇ�/^¼ØÚÚºÓ«¥¦¦ºººz{{w¯2£G�þä“O***œœœXìOOÏ3fìÛ·ï矦K~ùÅØ9ˆz…J¥ºwï^YYYii)�pªªª8Ž���T*õññ3fŒT*uttÄR€Gb½ØÛÛO™2åûï¿:th«{2™lëÖ­#FŒèô: …âüùó�fuë@`` ““ÓÉ“'Ÿ~úivûÄÌÌŒÃáûìúõë333™…+/^ÌÎÎ^³f�ž�ÓÓÓÃÃÃ{øì�IZ­O¾CHIIa†R©ÌËËc¥> ¥RIsF3jkk¹\®£££‹‹Ëã�?>~üx©Tjgg‡‰jb€.puu}â‰'¾û€±X\WW·k×®qãÆyyyéóõÂÂÂÒÒÒçž{®‡Õ I«ÓÓÓÙŠy.\(‘H>ÌårÕj5s¼®®®¤¤ÄÍÍ�•ZÀÀtÿþ}&¼)//¿{÷.!D,ÓÑšoÀÝÝ�íj›ótÍĉsrrvíÚµlÙ²¤¤$GGÇ©S§êùÝôôtŸ^Ù©“õ¤Õ“'OŽŠŠúúë¯oß¾­V«µ§·!æÑÉdwîÜÑÞúS¡P…Bº+ޝ¯/Í�ýÁ@b€®áñxÏ>ûì|°mÛ¶›7o¾ûî»zî7×ØØ˜��ýÌ3ÏôJ5Äbñ°aÃØMZ-‘Hþò—¿¤¦¦îß¿_©TªÕê–––K—.Mž<™­*@?£T*™Ø†ª««ãñxNNNR©488xÒ¤IR©ÔÖÖ–íš@Ÿ†˜ Ë\]]###333gΜigg§ç·222LLLBCC{«ñññÿøÇ?XOZ¼uëÖÛ·o«TªŠŠŠªª*‰DÂb•à¥Ñh´'ª•••Ý»wO£ÑØÚÚÒaœÈÈH©Têää¤çÃ& 1@—)ŠÛ·o ‚Û·oëÿ­´´´èèh.—Û[Õðöööòòê I«%Éš5kè€�B¡ÈÏÏÇæ¤ �ºº:íçîÝ»J¥R$ÑŒj tMŽ©©)Û5€Gb€.KIIill\¼xñW_}5bÄ}²3çææVWW�3¦wkÿÍ7ß°˜´ºUe‚ƒƒ·oßž““ƒ˜t577—ý¯ÆÆF>ŸO'ª >ÜÕÕU*•¶Ú  çótM^^^FFÆË/¿»{÷n__ßNCŽ´´´€€€^Ÿqºk×®ôôô &°ÒUUUUUUÚG¦N�úË/¿°RèSÔjuee%³+NYYýs!‘H¤Ré AƒF�-•J{q Mˆyº@&“%%%ÅÄÄB¦M›výúõ”””… vð­êêêëׯ·ÚÉ´Wp¹Ü˜˜˜Ó§O�7Î7 2™ì�7Þhjjêà33³eË–º&ÐÕÔÔh�áTTT(•Jsss:?-((ˆ¾ …l×àÑVRR"—Ëékí‡��†Ãáú(44tÈ�!í}ëìÙ³b±Ø@ ÖhÒêììì#Fºù"‘(88øÜ¹s�F÷S�gaa±bÅ äªššš˜ºG&“ñù|º'""‚F8ÖÖÖl× ¿)((HIIaÞòù|µZ­V«iÌC«W¯FÀ£Í 1�õ›díaíÁVé£�}F;³Óüq®ã\Øj¸Y€ƒY€[¥GZZZNNÎ믿.˜ƒƒމ‰Ù¹sç_ÿú×6^ªÕêÌÌÌÑ£G¨V4iujjªbBHTTÔ¹sçZm^®Ñhèç«W¯î +‹ ×ѬŒÚÃ8ÕÕÕÇÎÎN*•úùùÅÅÅщj­þ>@¯‹ŒŒÜ¿ss3ýç¦Tþw¤�Ãá‚×_Ï[éå˜G¥R544°Ý(@T*•q zðàÁ¾}û¦L™âååÕ꣧žzêúõëß}÷]›ùÓ®]»V[[c¸ºÑ¤Õ%%%Fø§T*ów–âñx���‹-BÀÐoTUUiG8•••*•ÊÒÒ’ŽÞ„„„H¥Ríg@`"‘hذaçϟמv¡Ñhx<ž½½ýË/¿Œ]½?Σkô�fëÖ­R©tüøñºŸ …§Ÿ~ú³Ï>‹ˆˆN.—Ó‰j¥¥¥tЦ¦&�@àìì,•J£££¥R©«««……Û5ÒÔÔ$‘H4 3™�<kÖ¬ÁóÇ6!‡@ç>\VVöî»ï¶7gcÈ�!‘‘‘IIIk×®ÕžáV^^ž——÷ꫯº†JZÝÜÜ|éÒ¥ÌÌÌ›7oŠÅ∈ˆ‘#Gòx¼7ß|“Îg#„,X°™©-J¥²¼¼üîݻ̂œÚÚZ‡ãàà •JÆŽ+•Jííí1Q  O)++ËÈÈÈÌÌtrr‹Åµµµä�€ÇÓÓsùòåxÚƒ˜ EEEGŽyæ™gììì:8mÖ¬Yï¿ÿþþýûgÏžÍLOOwvvöóó3t%CCC÷íÛ׋I«oܸqöìÙË—/«Õê���—_~ÙßߟI 7dÈ�ëׯ ‚E‹é³=‘þjjj årytt´¡; `€Ðh4ºÕÔjµµµ5�¨6bÄ:Q�ÏÇ]@_¤T*³³³ÓÒÒJJJ†þÚk¯yxxdff~ûí·jµšÇã�1bÑ¢ElW³OÃ_7€Ž(Š­[·†††v:”!‰æÎ�ûÕW_…††Òn …âüùóS§N5B=¹\nlllÏ“VWUU�?>++ëÞ½{žžž3fÌ 33k�"oذa7oÞ|íµ×zkÑÝ»w /\¸pïÞ=BÈš5kŒÐiý•L&+))ÑΨÖÜÜÌLT‹‰‰‘J¥îîîx Ð÷Ý¿?===33ÓÔÔtôèÑþ󟙹QQQ{öìinnÆ s} æèHJJJKKËüùóõ99888444))éý÷ßçñx—.]jii‰ŒŒ4NUG�uäÈ‘î%­V(W®\9{öl~~>�ÃáììÜÞùQQQÁÁÁ=¿aº{÷îåË—¯_¿^WWÇãñ”J%‡ÃIHH°±±1N§ôJ¥’®ÀaÔÕÕ1ÕüññãÇc¢À£E£Ñ\»v---í×_2dÈ¢E‹tÿ �9ÒÎÎ.>>žíú>ó´+;;;##ãå—_Öÿæ~þüùo½õÖ�¦OŸžžžn´¤F"‘(<<¼«I« 333/]º¤P(†úç?ÿ900PŸ‘¢ž<¹¹¹yyy¹¹¹MMM|>_©Tj4•JEÎ$$$§ÇE�æþýûÚν{÷4 3Q-,, Õ] gÏžMOOW(111O?ý´­­m{'kO§‡Žá"@ÛêêêvîÜÐ…]�D"Ѽyó¶mÛæîî^TT¤çQoIHHx÷Ýw ½½½;>³¶¶633“Îasuu�=88˜Çã±]©þ1@Û’’’lllfΜÙÕ/Ž1ââÅ‹»wïöôô4òŽ`NNN>>>©©©íÅ›üþ†´VVV¶oß¾)S¦H¥Òî]!//O üòË/¥¥¥®®®Æ¬?,,Œ•a1ƒjjjbpèR™LÆãñœœœ˜]q¤Ri ” ?i•xúÅ_´°°`»Rb€ÿqàÀ�ššš—_~¹Û�J~~~yyùÒ¥KÍÌÌÞyç�cÇŽõÖ>¡ú())©®®–Éd›7o2dÈ’%K† ÒGfÅh4šÌÌÌü188xÚ´i&&&•••YYY"‘hܸql× wäääܺuëþýûo¼ñFuu5!ÄÖÖV*•ÿ¥—^j•Žæ…^@Àý‡Ãyã�7Nœ8Q\\Û”)S„Ba«Oð@¿!“ɲ²²ÒÓÓëêꢢ¢Þyç$žî#z?æÑgw€Þ¢V«{å:/^ÌÎÎ^³fM÷õKKK‹ŠŠÒþW`oo?uêÔõ|†›\.¿páBffæíÛ·���ãããÃÂÂZ¥¥¦>ûì3Ý¤ÕÆ¤R©’““ûí7íùlýOqqñÏ?ÿ|éÒ%ggçñãÇ?Ò‰§M¸¼ÁÖöÍ*¥‘Ëòø¤wnëÚÐË1�dz°°Àª,0š††¥²§ÿ&ëþ¿½{�kê¾ÿ~B¸�p-rQ¹ F"—D‘ ^Њ´^° V\½tÖÒ>ªµëæe>ºo·~·öûÝÖ­ê_éí'«"'�Òt,¬â#$Ü„$ç÷G6æD)är$¯ç_ˆ'ŸÏûsTÌ;çs^§§çìÙ³+V¬ 3g¥RÙÝݽhÑ¢‡¾Ÿžž.—ËE"Ño¼aÚÈE}÷ÝwR©ô›o¾qtt\¸pá¦M›Æ¿†0ZM¿ááák×®q8œ±ûÙlƒV«­®®&IR¥Rñùüýû÷Û@ð´—³ÛÞØ§™z``ÀJ#[þ:‹Å¢ÊþLu´X÷”Œ�×?Ôaéf‚ ®ÕµÞRõÒ<»»›ãrág'¶²é¾²©“Ωý}Ü–&Í"â›´7Þî¢áO'Írws¢®×¥tNÍâ8¬È7Š¢D"‘··wvv¶™C]¾|9..nìí‰,+??ÿÈ‘#$I¦¥¥MjÌöööÊÊJ™LÖÕÕ“ŸŸ?Á�ŽÒÓÓ/^¼¸fÍú?j’ÉdwïÞõ÷÷ùå—Çîg³,–(Eóò‚å÷¯è-Ÿ™DH4ݳsL-N€%´µµ‘$)“ÉÜÝÝÓÒÒ ¦lð´““ÓÄ? Öét½½t¿‘5‘ý;z½Þ„@Z«ÜÏCu´wh8/ÿ1鿾P÷ Ýý¾�æÙ=Ý� EDo¿–þÙ�ºû˜Y¸No ‚êï¢ù�ÖXdœ’’’¦¦¦Ÿüä'fÞ\¨V«ëëë ù»þþþk×®ýË_þ2þü‰l6®©©‘J¥MMMþþþ©©©B¡p²»ÔAQQ‘L&›l£eŽááásçÎ]»vÍÏÏ/..ÎÚ AÆ;˜â�´šHZÍ`“.§ À z½^¡P�$yýúõøøø�;wNýài�f¢ïš(ŠÒëõL—<ž¾¾>òu‘avM¥R}öÙg›6m2ÿ‰eee>>>±±±�;`åÊ•ÕÕÕgΜٻwï8ã466VTTÈår‚ ²³³£¢¢L+ÉÙÙ999¹´´”¶žG¥R;vL§Ó8pàÂ… ÖžN¯×÷õ1óÃT0Ùÿ“pº˜.¦7µZ-‘HÊËË ‚HMMÍÏÏŸ.ÁÓžžžjµzxx˜†"iÐÚÚjÂcÖÑó€ýÒëõ}ôQtt´ù-�Á`(//ÿÁš,kûöíï¼óŽT* …ýngggUU•L&koo�ŽŽÞ²eË‚ ÌÿÙ´lÙ2‰D¢T*ÇiÆ,E&“FEEíØ±ÃÌ(ˆ‰ÃÙIÁé˜,Š¢êëëËÊÊêêꢢ¢žþùi<n0š››çÍ›Çt-æÒétwîÜ1áaèyÀ~uuu½úê«æU[[;22’šš:þa!!!«V­ºpáBLLŒq£šV«ýæ›o***|||Á¢E‹|}}-µFch5I’VíyFFF +++³³³333§ø%þÉB¥²T $Lkýýýb±x``@(þüç?Ÿ¦‘<^^^ááá5556ÐóÈårŠ¢x<Þd_ˆžìT}}}iié®]»LØ:I’|>ßÝÝý�\½zµB¡8{ölff¦T*­©©1 ûöí›;w®5ºk‡V···;v¬¯¯Ï¸kLÁ dQÚ'‹BÀôÕÔÔD’¤\.â‰'V¬X!¦oð´ÑòåË�?þý÷ß?ñÄL×b:½^_ZZ*&òŽë!èyÀi4‘H”ššj‘‡Æ´¶¶666N0º¯¯/::úÊ•+ …bîܹ6lHLL´êþZ«†V3¸###_{í5OOOF`ð*Ê?Ëg,‹2x®Ã¢ç‚0\ûŠºõwºgç°ž~žåìjøæ ÕXkW ·H $LGCCCUUU$I~ÿý÷|>ÿõ×_�ˆˆ`º(ËHHH +..ÞµkÓµ˜N"‘ <÷Üs&¼=Ø£ÂÂB''§œœ‹Œ&‹CBBÆZŽN§S(R©´¾¾ÞÛÛ;<<¼µµõå—_¦ç¾k„Vëtº¢¢¢²²2sö³999™ðQ�¥h4­V;‘#™É¢tù×ß u+³kXÆž°»�îHF¦NóŒ0¨T*’$+++=<<ÒÒÒ„Bá” ž6ÙŽ;Þyç�¯¾újõêi™ŸyóæÍ’’’Ý»w›ö+z°;R©´¶¶öÍ7ß´H Õj«ªªÆy¶Ï�;w***®]»622²`Á‚‚‚‚yóæ †#GŽîܹ“†% ‚‹/Z0´Z­V?~\­V[`?›¦×ðW12LÃyx�Ãòm„³­ý“¢ÓéjkkI’lnnŽ��ß½{÷Ôž6™ŸŸß«¯¾úÞ{ﹺº¦§§3]Îä477‹D¢õë×/X°À´Ðó€}¹ÿþ¹sçžyæ™°°0‹ (“ÉFFFÁCßïëë“ÉdR©´µµ522rýúõ|>ßÍÍÍø»l6;??ÿ7¿ùMbb¢ ÷áM–³³sjjª¥B«årùéÓ§ƒƒƒ:dÚg-ÿA7BÜk$†­}62ŒžÀnuttˆÅb©Têàà�ššºcÇŽéûl]]Ý… òòò¬½v.—›��pùòe“{ã~¶ööö‚‚žö`A===ååå‰D£Ñ¤¤¤=ztšO›�ÃálÛ¶mñâʼnäÓO?íèè`º¢G›1cFddäêÕ«-ZdþhèyÀ^ܼyó³Ï>ËËËóóó³Ô˜b±8<<üÿøÇÉ“'ïÞ½;gΜ¬¬¬¤¤¤ ^,vqqÉÏÏÿõ¯�’’ií3°dÉ’÷ß¿­­Í„ŸòJ¥òäÉ“‡²‡«ÿ`3H’T(���«V­JNNvuueº(æEDDSéoÞ¼yçÎ�‘‘¦‹"‚`±X3gΜ3gŽWH ç;¡ÕjOž<¹`Á¡Ph‘ CmmmUUEQjµ:%%eÇŽ3gΜì8‘‘‘©©©"‘èСCÖÞáxùòåI]V2 ÅÅÅ—.]Zºté† ñC¦�F#“ÉÄbñýû÷ù|þ¾}ûl&xÚ²ÜÜÜbbbbbb˜.ĺðöìÂùóçGFFrssͪµµµ²²²²²²§§‡ÍfïÞ½û©§ž2çΖ�7>|øâÅ‹›6m²öyHOO/**Z·nÝ3²{{{Oœ8¡R©vïÞ���`íòÌwûöí²²²ššOOO[ ž†ÉB϶ïûï¿—H$¯¾úª9üvíšT*½uëVHHÈŠ+ÄbñüùóãããÍ,ÏÅÅ%//ïøŸÏ·ö7chµX,ÎÌÌüÁƒOœ8áããƒýl0õiµÚêêj’$ïܹÿÒK/Ùpð4Lz°qz½¾¦¦&==Ý´‹¶E}÷ÝwR©ô›o¾quuMNNÎËË mhhhkk{õÕW-Rd\\\JJŠH$:zô¨UÓcŒ¡ÕÆàµñW}éÒ¥âââ§Ÿ~ûÙ`Škkk#IR&“999¥¦¦¾ôÒKø¨‚·2`ㆇ‡MØ6ÖÞÞ^YY)“ɺ»»Ÿzê©_|1>>~´!‹Å111ü‘š““sôèÑâââuëÖYõ„ü`huoo¯H$jjjÚµkö³À”¥×ë I’ ÑÑÑ[·nµ·ài˜8ô<`ã8NrròÄ�®©©‘J¥MMM!!!Ë–-KJJòððxðµZ-—ËwïÞmÙ:7oÞ|ìØ±¤¤¤��ë��ñC«�ûÙ<<<<øÈÇ 0N­VK$’òòòááá”””-[¶Ømð4Lz°}£ _cccEE…\.g³ÙIIIÏ?ÿ|hhè#�¬¨¨àr¹æßÉó�Ççó?þøãŸþô§Vý˜*##ãÝwß}(´zt?[JJJnn®“““õ 0EQõõõeeeuuuAAAkÖ¬Að4Lz°w���UUU2™ìþýûO=õÔöíÛãââÆ¹ƒÅ`0H¥Ò´´4s²Ú'77÷g?ûÙW_}•••e½%‡‡‡‡……=Z­ÑhNžùächõ­[·Ž?îèèøÖ[o[oR€Éjjj"IR.—s¹\OƒÉÐó€Ýinn–J¥555,+))iýúõ³gÏžøËI’LHHpww·Ry‰‰‰ÕÕÕüñÁƒ­—°Éçó‹ŠŠÄb±³³sQQQRRÒæÍ›­ýPT€ ªªª"IòÞ½{ñññ{öìAð4˜=Ø‹îînãvÚÚÚbbbòòòx<ÞdS˜Õju}}ý믿nÕR·lÙrôèÑ’’’‰Ÿo½ô‚‡p¹Üìììâââøøx‹ìpS©TÇŽÓét˜3gñ¯Ðj±XŒžhÓÓÓS^^.‘Húúúø|þ�< Ö†žl —ËÝ·oŸÅ‡mii¹yófNN�kIOO7îp{ã�7ÌJ&“FEEíØ±ƒÃáŒ~ßZ�““óà7¬¡¡¡�$I…Báíí�žžŽài  z€ !I2,,Œæë!,+??ÿÈ‘#$I¦¥¥™6ÈÈÈHaaaeeevvvffæCw�†V[/#ìœF£‘Édb±øûï¿���ùå—< 4CÏðôZmUUÕæÍ›éŸÚßßíÚµùË_æÏŸoBÜ\{{û±cÇ÷íÛ7wîܱ888,]ºôêÕ«+V¬ppp �`Ãnß¾]VVVSSãêêšššºxñbO#Ðóü0™Læää´páBFf_¹reuuõ™3göîÝ;©÷ÅEFFîß¿œ­kK–,ùòË/kkkY Ø­V[]]M’äíÛ·#""òòò< ÌBÏðÃÄbqrr²³³3#³³X¬íÛ·¿óÎ;R©T(Nä%Z­öÓO?-++{ä~¶‡p8œäää+W® ç3µµµ‘$)“É CJJʶmÛ< Sz€ÐÜÜ|÷îÝ;v0XCHHȪU«.\¸óƒ;ÜŒûÙúúú·Ÿm¬eË–9r¡Õ`½^¯P(H’lhh~î¹ç< S z€ ‹çÎ�;sæLfËX³f�B¡8{öìž={Æ9̸ŸmÖ¬Y¯½öš§§ç Œ‰‰¹rå z˜µZ-‘HÊËË5 ‚§aÊBÏ0ž���ÚÚÚ^x�éBþ½Ã­ººú‘›Ðt:]QQÑÕ«W333³³³'H°lÙ²>ø »»Û„¤°7EÕ××—••ÕÕÕùúú._¾ÁÓ0•¡ç�D"qrrâóùLBÿÚávöìÙØØØ‡2 ÔjõñãÇÛÛÛ bccM<66ÖÓÓ³´´tݺuL/¦®þþþŠŠ ±XÜÙÙ÷Ê+¯ x¦>ô<ã!IrñâÅS'ÄyõêÕ …¢°°pçÎ�£ßT*•'Ož 8tè�9 K—.-))Y³f Si 0•555‘$)—Ë9‚§azAÏðXJ¥²»»ûé§Ÿfº�c³ÙÛ·oÿÅ/~‘˜˜Èãñ Cqqñ¥K—–.]ºaÃGG³þEC«e2™ÉÏ?Û344TUUE’¤J¥Šˆˆxá…< ÓŽUz7€Öм7Àø…§»³¿·ͳ»sœX,‚ 8nŽ4ÏÎõt1~1ƒÃÌÂÙ,‚ 7Â?”ΩYÞ�Öž‚$ɸ¸¸©ö!VHHÈŠ+Î�;xæÌ•Jµ{÷î„„óG6†V‹Åb‹ô<ØçvkppÿÁ6¨T*’$+++ ‚HIIyñÅ< Ó”Uz‡ù .I¸ X¸€±�ó£æG02uâS�‰OY½ x‡y)ļ¦f·µZ]WW7ÙÇ€Ò#;;ûÚµk¿øÅ/‚ƒƒÍÜÏö †Vs8œ®®.†ÏZ[[M»­`ŠÐétµµµ$IÞ¸q#88xýúõž†éÎÂ=�^¯ïëëczQ`Gôz½•F®¨¨àr¹Sð�‹Á`øë_ÿÚÝÝMQÔªU«,{Ê‚¡Õ¡¡¡J¥’É3À�¡¡!¦K0QGG‡X,–J¥CCC|>ýúõžÛ`ùë<Ö{ @ƒÁ •J§àm-½½½'Nœ¸sçή]»Ï�;7oÞ< Na©ÐêÐÐÐ/¾ø‚ÙÓ@¿ÁÁÁÖÖÖ¨¨(¦ ˜ƒÁPWWG’¤R©ôóó[¹r%‚§ÁÆ Ãàjkk»»»SSS™.ä?466ž8qÂÃÃãàÁƒ±±±uuu.\ÈË˳à,±±±æ‡VßóÕ××ÇÄÄ0{ÞèT__ïææÍt!ÒÓÓS^^.‘Hº»»ãââ < 6 =À#�$™��àîîÎt!ÿDQÔ¥K—Š‹‹SRRrss�œœ‚pqqÉÏÏÿõ¯�’’iÁéÒÓÓ/^¼hfh5‡Ã™?¾R©DÏv¥¹¹y`Zhhh IR¡P¸»»#xlz€‡µ¶¶666¾ùæ›LòO�æäɓׯ_ß¶m›@ xð·"##SSSE"Ñ¡C‡,¸ÃM \¼xÑüÐj�wêÔ©eË–y{{3uö,‚åJÑ?©ß¿²X|f!t_4`qãc©¸4ÏÎüÂÍÐÕÕU[[ûúë¯Ó\6ÀÄi4™L&‹ÛÚÚ¢££_|ñE�‡ài°yèy&‹CBBÌ¿‰ß"nݺuüøqGGÇ·Þzë‘ ¡7n<|øðÅ‹7mÚd©I���SSSKKKÍìy„Bá•+W¾þú뜜úO�9,ÝÌäìI«‰¤ÕŒÍ¾ ƒX�a‡ 7Í_|1wî\llƒ©éöíÛeee555l6[ üøÇ? d,î€fèyþƒV«­ªªÊÎÎfº‚ ˆÒÒÒ¢¢¢¤¤¤Í›7?î2Ž‹‹K^^Þþð>ŸoÁnééé%%%J¥ÒÌ亜œœ÷ßñâÅAAA�>ÂщšKŒ [ñ<>’Ó„.‹!‹Ò>™ÆÓÜܬT*q‘¦­V[]]M’äíÛ·gÏžýüóÏ'&&š³u`:²|σûÞ€Neá=G555###m!£ßðð°H$ª««Ûºuë—’’"‰Ž=j©ý \.7&&æòåËfö<ÑÑÑ�àôéÓnn�xf.ËÝ‹ýÜ«V;‘ã˜ÈQÈ¢„‰üä“O.òÀÔÑÖÖF’¤L&áóù¹¹¹³gÏfº(fX²ça±XîîîÆ-àÖGQÔÀÀ€Á`°à˜b±899™ÙÀnݺõÑGñ¸ýlcåää=z´¸¸ØÌ°µ-[¶ìw¿û�ù¡Õ999ï½÷Þ‰'^}õáÞF§ÓõööZüN�eÿæ€=Óµz½^¡P�$ÙÐÐàçç·zõjOX¸ça³Ùe­�wúÕ4/Ùí¸vvÜ '×[÷z¾k¦{v‚ âæú?áAõvRÕÿ�æ]:¬'SXsb‡F®ý½mpHGóŸðå,˜÷Au×;TíýtNíãåšøT e¯+¶´´Ü¼y377—Î…<ĸŸmþüùùùù�%àp8›7o>vìXRRRHHˆE*‰�� 4?´šÃáìÙ³çí·ß>þ|VVÖƒW{(ŠÂU˜î/\¸0<<|èÐ!¦k{§V«%Iyyy__‚§dù½mßõ|ÿmç]š—áÆvZ2o†¡îª¿ÑIóìA„z?áA PßUôÎ0‹5'V«34ÞêêÐÒ¼ða­ÞØó¨Úûi>ó!Ox$>eá›/I’ ¥s!£ŒûÙ¾ýöÛ 6¤§§Oöå<�ÏçüñÇ´Ôÿp ­&Â××wß¾}ï½÷^kkë®]»¹É `:êêêúä“O†‡‡÷ìÙÃáp˜.ìEQõõõeeeuuuž ûÐþi`` ªªjÉ’%ŒÌ®R©~ñ‹_ܺuëÀ�&4tèSŸ•€�ëïï/))9xðàï~÷»ááá�;wþêW¿Z»v-€‡ · àŸ*++�œœ.\HÿÔ2™¬°°0**jÇŽæ|TÌáp¶lÙòñÇóù|ó ³ThõhyûöíûüóÏOœ8¾lÙ²)0YÍÍÍ—/_nnnNOO·`L<ÀÄݾ}ûòåËr¹ÜÉÉI ¤¥¥!x`èyþI"‘,^¼˜æô‚‘‘‘ÂÂÂÊÊÊìììÌÌLó÷¤%&&VWW‹D¢ýû÷[d‡[zzú×_m~hµ‡ÃÙ´iÓ²eË>þøã'NDDDÄÄÄ„……=6É`*¹wïÞÍ›7kjjZ[[çÏŸÿÎ;ïøúú2]Ø—‡‚§·lÙ‚ài€‰@Ï@ÑÐÐÐÚÚZPP@ç¤íííÇŽÜ·oßܹs-5ì–-[Ž=ZZZš‘a�GIr¹Ü„„óC«äëë»ÿþ††…BQQQñùçŸañäqKa±X7nÜ ÂÇLJÇã½øâ‹ØÌ4 žÖëõ .Dð4À¤ ç ‚‹Å111tn€–Ëå"‘(22rÿþý–½õ™ËånܸñÏþs||¼Ev¸edd¼ûî»mmm–Ý8½iÓ¦–––ÎÎΖ–‚ :;;ïß¿oÁYÌäççg¼˜³bÅŠÐÐP\Øš=<˜••%˜0Yèyˆ��¹\¾{÷nz¦ÓjµŸ~úiYY™¥ö³�% �=Õo¼aþhááá!!!—/_ÎËË³Æ åñxÖ`š žàñx¯¿þ:žx `2ô<„D"ár¹ñññ4ÌeÜÏÖ××gÙýlcååå>|˜$I‹Ä,Y²¤¨¨hݺuøpÀªÆO§¥¥yyy1]Àô†ž€ IR(:8X=ºÝxíeÖ¬Y¯½öš§§§Uçâr¹ëׯÿË_þ2þ|.—kæh�àâÅ‹b±833ÓÚg À>õ÷÷WTTˆÅâû÷ïGGGïܹ“Çã±Ùl¦ë°èyÀÞ)•ÊîîîE‹Yu�NWTTtõêÕÌÌÌììlú+‚ ÒÒÒ®]»væÌ™½{÷š9”1´ÚX? •Ø•¦¦&’$< `=èyÀÞ]¾|9..ΪéjµúøñãíííL?›ˆüüü·ß~[*• …B3‡²lh5 UUU‘$©R©< `UèyÀ®©Õêúúz«FT+•Ê“'O:tˆþcûûûggg_¸p!&&ÆÌnÖ­°O*•Š$ÉÊÊJŠ¢.\¸mÛ6OXz°keee3gδқxƒÁP\\\RR²råʬ¬,GGfþ¹¥§§Ëåò³gÏîÙ³ÇÌ¡¬Z `'t:]mm-I’7nÜ ÌÎÎFð4=Ðó€ý2 åååÏ<óŒ5ïíí=qâ„J¥zå•W˜½0Âb±òóó�9R]]�˜˜hÎPáááaaaÖ ­°Ub±X*•"x€~èyÀ~ÕÖÖŽŒŒ¤¤¤X|d¥R)‰|||ÙÏ6–¿¿ÿÚµkÏž=k抟|ò B«&Â`0ÔÕÕ‘$©T*½½½ÓÓÓ/^Œàiú¡çûE’$ŸÏwww·à˜Æýl—.]Zºté† ˜ÚÏ6ÖÊ•+««« wîÜiÎ8|>¿¨¨¡Õãëéé)//—H$]]]±±±{ö쉋‹£'´Æš*oÈhÖÚÚÚØØ¸nÝ: ŽiÜÏvçÎ�Ý»w'$$0½ÄÿÀb±¶oßþÎ;ï( �gò8K—.½zõêŠ+ðŸ7ÀX $I* 777¡P¸dɦ‹°wèyÀN‰Åâ���ððpK ØØØxâÄ �ƒ0½¾G YµjÕ¹sçæÍ›çââbò8K–,)..®­­5óî [¢Ñhd2™X,nkk‹ˆˆØ¶mŸÏŸ:Wûìþ)‚=ÒjµUUUÙÙÙ�¢¨K—.§¤¤äææ:991½¾ÇZ½zµB¡¸pá‚9!'!!áÊ•+èy‚¸}ûvYYYMM A .ܵkWpp0ÓEÀ@ÏöH&“�ŒŒó‡Òh4'OžljjÚ¶m›E´*6›�ŸŸÿ_ÿõ_)))‘‘‘&�c ­nii ezMÌÐjµÕÕÕ$IÞ¾};00ðÙgŸEð4À”…žì‘X,NNN6ÿY×·nÝ:~ü¸››Û”ÝÏ6VhhèŠ+D"Ñ¡C‡LÞáf ­&I¡Õ`‡ÚÚÚH’”ÉdZ­–Çã­_¿ÁÓSz°;ÍÍÍwïÞݱc‡™ã”––%%%MñýlceggËåò‹/nÚ´ÉäAZ öF¯×+ ’$¼½½—/_Žài€é=رXŸoò7„V€ýP«Õ‰¤¼¼¼··ÁÓÓz°/µµµ/¼ð‚É#÷³9::¾õÖ[Ó÷.ÕÈÈÈ´´4‘HtôèQ6›m­›GQT}}}YYY]]�»»;‚§¦/ô<`_*++�œœø|¾i/7îg›?~~~¾9qÏSÁ³Ï>[WWW\\lòCŠ–,Yòå—_"´lOEE…X,¾ÿ~DDÄöíÛ< 0­á_/؉D²xñb®K ‹D¢o¿ývÆ éééL¯Ã\\\òóóó›ß$&&š¿Æáp’““Z ¶¤©©‰$I¹\Îf³SRRÒÒÒ¦ï%}…žìˆR©lmm-((˜ì U*Õ±cÇt:Ý�æÌ™Ãô:,&22rñâÅ"‘è§?ý©i;Ü–-[väÈ‘ææf >Ý€~CCCUUU$IªTªàààœœœäädWWW¦ëË@Ïv„$ɘ˜ŸI½J&“ÆÅÅmݺÕö2Ê6nÜxøð᯾ú*++Ë„—ÆÄÄ\¹r=LS*•Š$ÉÊÊJ½^Ïçósss#""˜. , =Ø µZ]WW·{÷ddd¤°°ðÚµk6³Ÿm,—¼¼¼?ü�Çã™¶Ã---íøñãÝÝÝ\.—éÕL”N§«­­%IòÆ�ÞÞÞ+W®LKK›1cÓu€U ç{QQQÁårããã'x|{{û±cÇml?ÛXqqqIII"‘èàÁƒ,k²/���÷ôô”H$¦]) YGG‡X,–J¥ýýýž°èyÀ. ©Tš––6ÁÿÕd2Ù¹sç"##÷ïßo{ûÙÆÊÉÉ9zôhII‰ Û ­~æ™gð¦¦,ƒÁPWWG’¤R©œ1c‚§ì z° ûÛߺ»»SSSðHã~¶ÊÊÊìììÌÌL®{LGgóæÍ'Nœàóù&¼@h5Le===åå剤««+""âG?ú‚§ì þÁƒ] I2!!ÁÝÝ}üÃŒûÙúúúöíÛ7wî\¦«¦�Ç[°`�H$Ú¿ÿd;=„VÀÔÔÐÐ@’¤B¡pttDð4€=C϶O£ÑÔ×׿þúëã&—ËE"ѬY³^{í5OOO¦«f@nnîÏ~ö³ÒÒÒŒŒŒÉ¾¡Õ0uh4™L&‹ÛÚÚ‚ƒƒ7mÚ„ài;‡žlß�7fΜý¸t:]QQÑÕ«W333³³³íö¦‡³eË–Ó§OóùüɆ°!´¦‚Û·o—••ÕÔÔ >Ÿÿ / xô<`ó(Šºyóæ³Ï>û¸ÔjõñãÇÕjuAAAll,Óõ2,11±ººúÌ™3{÷î�ìk—-[öÁ ´è§Õj«««I’¼}û¶ŸŸ_VV–P(Dð4ŒBÏ6nhhÈ`0¤¤¤<òw•JåÉ“'ƒƒƒ:dŸûÙÆÚ²eËÑ£G¥R©P(œÔ cccJKK×­[Çô"À^´µµI$’ŠŠŠÁÁÁ¸¸¸—_~yâÏ$û�žlÜàà`HHÈØôƒÁP\\\RR²råJ{ÞÏ6—Ëݸqã… bbb&{Å&==ýâÅ‹kÖ¬qvvfz`Ëôz½B¡ I²¡¡aÆŒ©©©ž€q ç[644DQÔØÍÜjµúOú“J¥zå•W°Ÿm,¡P(—ËÏž=»gÏžI½P Éd²´´´‰ßÙÙ©P(ZZZ:;;)Šº{÷îàà Ó«ÛäææÂb±|}}CCCy<ž¯¯/ÓE�)Ôjµ1xº··ÁÓ0A–ÿ±*$FFó2ØžN®A„‡zyÎ`àæA„—ŸÃª]„AGëÜ~!Ap\— gëtšîîædübÁ¼€ÈY\:§vsù´®®®¾¾¾ÞÞÞ~óÚµkgΜñ÷÷ã�7‚‚‚è¬yÉËË;|øpuuõ¤â§���“““Åbñø=�F£)--•J¥���\.wæÌ™¡¡¡Aðx¼‡þ°,¥««K­VqïÞ½üãçÏŸ÷õõ …éééöðÜaÛ T*ËÊÊêêêœ��< “bùž'ÌÓ�Áõp=\¹Œ…Q²\8D83Ûˆ�Ùs‚¼˜Z8AOøº?áënþ8“BQ” ¯ºvíÚðððÝ»w�=Êb±8c¸»»?ò›nnnvòˆR‚ ¸\îúõëÏž=;©w„ã‡V»�Ë—/»¸¸¤¦¦†……¡íFÜ»wïæÍ›‰äòåËË–-Cç3•=<½yófOÀdY¾çé é½ÐA„áèÁrt úÔDw;ݳá3“åîEi‡ˆŽ §ujnËÇÒ�wÝÝ wó`ù¡ëÔh��åÊvô7¥Ëzùå—5c ¿èìì4~=888000ÚY±X,WW×±MÑ#{$‡3ÝïJKK»víZaaáÎ�;'þªqB«[ZZ>üðÃ���åË—/Z´ˆéõ�] Z´hÑ×_ýõ×_K¥ÒÿøÇÆë�0u x,Åò=Oùíá&5ÍË`¹°ý¶-`{¹R�µTÙŸiž� ÖʱbÝ톋¿%†i½!�õôó¬„åÄ@�áËãDo'Ý+�XÀÎÞKÄ@ÕÝA%­Ý¦s¨—÷¦§Lxáèµ� ?~ƒÔÕÕõà÷õúw¼...c»#77·G¶Ll6›Î³7Aùùùo¿ý¶B¡àñxÕ#C« …H$š3gNNNŽ››Ó+ø§åË—/^¼øüùóï½÷^~~þ¤þªƒ• x,÷üü77777· Þî<<<<00`l“Fû¢Ñ¯[[[l�tº_uvvg[ÝCßqrr¢gíþþþÙÙÙçÎ�‹ŠŠ2v‰|ð‡ÃÉÎÎ焌 ­–J¥§N�ÊÈÈX¾|9=•Lœ››Û¶mÛ>ûì³?üpÛ¶m“Mi jkk#IR&“ ÅÅÅÄÄÄØÏ¦b°ô<–äâââââ2ÁƒGFFºj4úËÁÁÁŽŽŽ¿©ÕjG_èèè8‘îÈxYiâõA½½½uuuUUU±±±+W®ŒŽŽ~Ü«FC« Å©S§6nÜÈçó™þóx¬µkן:uŠÃáàjÍ žöôô\ºtijjª��Óu€í@ÏÀ'''////¯ …Oètº±;ëFuuuݽ{wô—ÃÃã/d³Ù£íÐC;ëÆ6Nco f±XÛ¶mûùÏÎçóãââZ[[ ‚ (J©T*•Ê€€€Õ«W ‚‡^%.^¼(“ÉÂÃÃE"QFF˜úø|¾J¥‰Dûöíý=ôx0x:::z×®]<ojîõ€i =Àôàèèèééééé9ÁãûûûyI£Ñôôô<¸Ëî¡Gâ̘1ÃÍÍí¡ŽèÉ'ŸüÓŸþôüóÏkµZŠ¢F·š´··Ÿ:uêìÙ³+V¬ÈÈȽKÊÙÙ955õÊ•+%%%111ØÒÓÅÚµk»»»?üðÃ"ÉÍz(Šª¯¯7O»ºº ‚´´´ÀÀ@¦ë›…žÀ6͘1c‚·üRõPSd ¬ýN{{»qsÝ©S§Æ¾Ü`0hµÚ/¿üò‹/¾HLL|î¹çŒ·ú¤§§ÿõ¯urrZ³f Ó'`6nÜøî»ï^¹r%++‹éZlP¿T*%Iòþýû³gÏÞºukbb¢³3Ö»‚žÀÞ±X,wwww÷þniiùå/ùÐ7�×|)ŠÒëõ*•J*•/ø8;;;88xyy!¥ ¦77·E‹]¹råÁK—`¾Û·o_¹r¥¶¶ÖÁÁaáÂ…»víš={6ÓE€½@Ï* % ‹Å2¶:NNN###...O>ù$�Çãñx¾A¼r力›[GGGOOÏïY˜"–/_^[[‹K=ñ`ðt``àºuëšI z˜¨„„‰DB‹Å 2¶:�‹n“ÉdK—.­ªª’J¥«V­bºv€ÉIMM•J¥èyÌ16x:66–é¢ÀN¡ç€‰b³ÙQQQ���{÷î}ê©ñÛÒÒÒÙÙÆf³KJJ222Æî×wpp`zAöÈ`0Xj(fÿ-¸�G ûüóÏ;;;'øl.…ài˜‚ÐóÀDEGGGGGúé§ÿ÷ÿ7oÞ¼qòd¯_¿Îårƒ‚‚üüüJJJäryJJJWWWEE—Ë]¼x1›Íž1c5H¿þþþ†k2ÿûúúôz½õÆ âr¹ …"##ƒ©5N;ž€) =LNvv¶\.ÿꫯÆÙöÓÐÐN„³³sRRÒÕ«W¿ûî»ï¾ûŽ ˆeË–�a±Xç›åwºh®?ÄÝ{SŸ ˆK-JeW+ͳ{:¹æDð½œÝj;^§sjW�¢’-8 ‹Åúìößšz;è\…5ò83gÎlii¡yuÓ‚§`êCÏ“Ãf³óóóÿ÷ÿ—Çã=î¹��fÖ¬YAÔÔÔÔÕÕõôôôõõ“GÛž»]×{Ú™ZH›¦—þÙ}]ÜG z‚ ºµšg×è´S¥é¡ÿZc!�tçΚW7½ x¦ ô<0i‘‘‘©©©"‘èàÁƒ�ÜÝDQÔ½{÷><22BQAƒÁx¤ñ—Óþº>Î�7H’Dð4LèyÀÏ>ûìÛo¿]RR’™™iüΕ+WF#h›šš6mÚ¤R©t:Ýh·cdí[Ï,ÅÇÇG*•2]ÅÔ244TUUE’¤J¥Bð4L#èyÀ...ùùùï¿ÿ~BB›Íþè£�nܸáëëËãñŒxyyåççŸ8qbddäÁ{ÍÑóÀtáíí=88ÈtSE[[[iiiee¥N§ãñx›6mz\N=À„žL) ûÛßöööêt:‡†††Ñž‡ ˆ   ]»v?~œ¢¨ÑV§³³“éÂ`¢ žööö^¹råâÅ‹ñ”a˜vÐó€‰ZZZÕjõh?S__ÿÐ1AAA»wï>vì˜ñÆŠ¢†‡‡™.~؃ÁÓ±±±{ö쉋‹Ãcµ`šBϦøüóÏ¿øâ ‹5z“7EQmmmc� z饗Ž;†n`ê{0xÚÝÝ](.Y²ÄßߟéºÌ‚ž&­¥¥å믿6^·y(·­¡¡aì.ÿ   M›6�>})XSÖƒÁÓÛ·oçóùŽŽxŸ¶?Ë`ÒBCCõ«_ýþ÷¿¿uë–^¯7¶=,ËÁÁáúõë�¼³966vÆ EEEAtuuùùù1½ø§Ñài6›�’’’––ÌtQ–„žLÁápÞ|óÍÑnÆ@j½^¯T*³²²ù’… QTT¤V«Ñó0nlðô¢E‹\]]™® ÀòÐó€é²²²¢¢¢þøÇ?jµZ㟛7oŽs¼±íf©T*’$< ö=˜%::ú—¿üå|`ÜçFDKKË8Ç£í`ŠN§«­­%IòÆ�ž»‚žÌÅáp8`ÜçFÄõë×™®þƒZ­¾zõªT*íïïGð4Ø!ô<`Æ}n}ôQCCÓµA xà_Ðó€ÅDGGÿüç??þ<Ó…Ø;O<?þÀ’8N~~¾L&cº;…ài€±ÐóL{Oçää$''#xÀ=À46<­×ëù|~nnnDDÓEL-èy¦Ÿƒ§ýüüÖ¬Y# g̘Át]Sz€é¤££C,K¥Ò���¸¸¸‚‚‚˜˜‹Åt]Sz€iÀ`0ÔÕÕ‘$©T*===—,Y’ššêããÃt]Óz€)­¿¿Ÿ$I‰DÒÕÕ½k×.�Çf³™® `Ú@Ï0E�O;99 ‚´´´ÀÀ@¦‹˜~ÐóL-cƒ§�³³3ÓuLWèy¦ OXz†!xÀªÐó0ÁÓ4°|Ïã4Óƒþe°œØ„£A,/*bxøA¸¸±Ââ©-­S{ùA89³fÇRš>º>3Ìø…ã3\†utNíèçNób,ÁÓt²dÏCQ”Á`à$3µƒÁ@„ųÂâ韚"Ê` <|‰Ì4:÷Ï©]g[ù`Ð`0á¶ ÐmÝiBƒ�¢(& `¢žžžòòrOÐÉÂ=O?®È�ŒÀÔ×ÐÐ@’¤B¡pvvFð4�,¼·�¢(|î0jhh¨¢¢B,·µµÍž=;77711ÁÓtB†€UŒOSµpáÂýèG³gÏfº({„žÀ’ ž ÌηéºìzË žäñx¯¿þztt4ÓEzó<<ííí�žž¾xñb///¦ë€BÏ`º[·n}øá‡ÝÝÝ111»wïNHH`º"xzÓùøø$&&¦¥¥ùûû3] <zÓyzznذ�é*`<èy€1!îÞ NÈñŒò  yvO'W'6A\gͳû»zX|Ì`Ž× NKç*¬´°mèy€1›"ø Ξ›ËÔì|ÿY|ÿY .ß"²çÌgº€†ž ×ëûúú˜®Âéõz‹Œc0˜ý´ÔBÀ çfà=ët‡?A˜.˜.ÀŠÐó€-ÃÞ6`†ƒ>sú ¦KÆ ç°Ùì3f°X,¦ {Ñß߯Ó阮˜�ž˜Áb±Î7ËïtÑ>>J¥’éBL¤T*CBB|}}™.,=X…P(”Ë僃ƒL0iƒƒƒ¸È`3Ðó€Uddd •——3]À¤•——³X¬ŒŒ ¦ Ë@ÏVÁáp²²²***p©¦ãEžœœ‡Ãt-`èyÀZ222|}}OŸ>Ít!“púôi___¡PÈt!`1ŽL¶lûöíï½÷ÞùóçsrrÆþnˆ»7ý%�NÈñŒò  yvO'W‹ _Wwšg]x€›# wr`ÁuæÐ<»¿«ÇÄ>þ|kkë¡C‡h>?`U,Š¢˜®lYCCÃû￟••µhÑ¢Ño²Ùl<ähÓ××§ÓéÆ?¦¢¢âóÏ??xð Ë`cÐó€ÕI¥ÒS§N%$$~�U±X,???______�‡GŽØô<`˘.ÀŠÐó€-C϶ =Ø2ô<`Ëþ?S¦hâ±ü%tEXtdate:create2021-11-16T15:52:10-07:00 î«%tEXtdate:modify2021-11-16T15:51:39-07:00Bw¯ºIEND®B`‚flox-0.11.2/docs/make.bat000066400000000000000000000145031514775624000151160ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\complexity.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\complexity.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end flox-0.11.2/docs/requirements-docs.txt000066400000000000000000000001201514775624000177110ustar00rootroot00000000000000numpydoc pydata-sphinx-theme nbsphinx ipython ipykernel - r ../requirements.txt flox-0.11.2/docs/source/000077500000000000000000000000001514775624000150065ustar00rootroot00000000000000flox-0.11.2/docs/source/_static/000077500000000000000000000000001514775624000164345ustar00rootroot00000000000000flox-0.11.2/docs/source/_static/style.css000066400000000000000000000003551514775624000203110ustar00rootroot00000000000000.xr-wrap { font-size: 0.85em; margin-left: 1.25em; padding-left: 1.25em; border-left: thin var(--color-foreground-muted) solid; } .xr-array-wrap, .xr-var-data, .xr-var-preview { font-size: 0.9em; } .gp { color: darkorange; } flox-0.11.2/docs/source/aggregations.md000066400000000000000000000027261514775624000200110ustar00rootroot00000000000000# Aggregations `flox` implements all common reductions provided by `numpy_groupies` in `aggregations.py`. Control this by passing the `func` kwarg: - `"sum"`, `"nansum"` - `"prod"`, `"nanprod"` - `"count"` - number of non-NaN elements by group - `"mean"`, `"nanmean"` - `"var"`, `"nanvar"` - `"std"`, `"nanstd"` - `"argmin"` - `"argmax"` - `"first"`, `"nanfirst"` - `"last"`, `"nanlast"` - `"median"`, `"nanmedian"` - `"mode"`, `"nanmode"` - `"quantile"`, `"nanquantile"` ```{tip} We would like to add support for `cumsum`, `cumprod` ([issue](https://github.com/xarray-contrib/flox/issues/91)). Contributions are welcome! ``` ## Custom Aggregations `flox` also allows you to specify a custom Aggregation (again inspired by dask.dataframe), though this might not be fully functional at the moment. See `aggregations.py` for examples. See the ["Custom Aggregations"](user-stories/custom-aggregations.ipynb) user story for a more user-friendly example. ```python mean = Aggregation( # name used for dask tasks name="mean", # operation to use for pure-numpy inputs numpy="mean", # blockwise reduction chunk=("sum", "count"), # combine intermediate results: sum the sums, sum the counts combine=("sum", "sum"), # generate final result as sum / count finalize=lambda sum_, count: sum_ / count, # Used when "reindexing" at combine-time fill_value=0, # Used when any member of `expected_groups` is not found final_fill_value=np.nan, ) ``` flox-0.11.2/docs/source/api.rst000066400000000000000000000012531514775624000163120ustar00rootroot00000000000000.. currentmodule:: flox API Reference ------------- Functions ~~~~~~~~~ .. autosummary:: :toctree: generated/ groupby_reduce groupby_scan xarray.xarray_reduce Rechunking ~~~~~~~~~~ .. autosummary:: :toctree: generated/ rechunk_for_blockwise rechunk_for_cohorts xarray.rechunk_for_blockwise xarray.rechunk_for_cohorts Visualization ~~~~~~~~~~~~~ .. autosummary:: :toctree: generated/ visualize.draw_mesh visualize.visualize_groups_1d visualize.visualize_cohorts_2d Aggregation Objects ~~~~~~~~~~~~~~~~~~~ .. autosummary:: :toctree: generated/ Aggregation Scan aggregations.sum_ aggregations.nansum flox-0.11.2/docs/source/arrays.md000066400000000000000000000025121514775624000166310ustar00rootroot00000000000000# Duck Array Support ## Sparse Arrays `sparse.COO` arrays from the `pydata/sparse` project are supported using algorithms that work on the underlying dense data. See `aggregate_sparse.py` for details. At the moment the following reductions are supported: `sum`, `nansum`, `min`, `nanmin`, `max`, `nanmax`, `count`. ## Other array types Aggregating over other array types will work if the array types supports the following methods, [ufunc.reduceat](https://numpy.org/doc/stable/reference/generated/numpy.ufunc.reduceat.html) or [ufunc.at](https://numpy.org/doc/stable/reference/generated/numpy.ufunc.at.html) | Reduction | `method="numpy"` | `method="flox"` | | ------------------------------ | ---------------- | ----------------- | | sum, nansum | bincount | add.reduceat | | mean, nanmean | bincount | add.reduceat | | var, nanvar | bincount | add.reduceat | | std, nanstd | bincount | add.reduceat | | count | bincount | add.reduceat | | prod | multiply.at | multiply.reduceat | | max, nanmax, argmax, nanargmax | maximum.at | maximum.reduceat | | min, nanmin, argmin, nanargmin | minimum.at | minimum.reduceat | flox-0.11.2/docs/source/conf.py000066400000000000000000000215761514775624000163200ustar00rootroot00000000000000# # complexity documentation build configuration file, created by # sphinx-quickstart on Tue Jul 9 22:26:36 2013. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import datetime import os import sys import flox import flox.aggregations import flox.visualize import flox.xarray # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) cwd = os.getcwd() parent = os.path.dirname(cwd) sys.path.insert(0, parent) # -- General configuration ----------------------------------------------------- extensions = [ "sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.autosummary", "sphinx.ext.intersphinx", "sphinx.ext.extlinks", "numpydoc", "sphinx.ext.napoleon", "myst_nb", "sphinx_codeautolink", "sphinx_remove_toctrees", "sphinx_copybutton", ] codeautolink_concat_default = True extlinks = { "issue": ("https://github.com/xarray-contrib/flox/issues/%s", "GH#%s"), "pr": ("https://github.com/xarray-contrib/flox/pull/%s", "PR#%s"), } templates_path = ["_templates"] source_suffix = [".rst"] master_doc = "index" language = "en" remove_from_toctrees = ["generated/*"] # General information about the project. project = "flox" current_year = datetime.datetime.now().year copyright = f"2021-{current_year}, Deepak Cherian" author = "Deepak Cherian" # Myst_nb options nb_execution_excludepatterns = ["climatology-hourly.ipynb"] nb_execution_raise_on_error = True nb_execution_mode = "cache" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # The short X.Y version. version = flox.__version__.split("+")[0] # The full version, including alpha/beta/rc tags. release = flox.__version__ # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "igor" # -- Options for HTML output --------------------------------------------------- html_theme = "furo" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. css_vars = { "admonition-font-size": "0.9rem", "font-size--small": "92%", "font-size--small--2": "87.5%", } html_theme_options = dict( sidebar_hide_name=True, light_css_variables=css_vars, dark_css_variables=css_vars, ) html_context = { "github_user": "xarray-contrib", "github_repo": "flox", "github_version": "main", "doc_path": "doc", } # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] html_title = "flox" # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] html_css_files = ["style.css"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # Output file base name for HTML help builder. htmlhelp_basename = "floxdoc" intersphinx_mapping = { "python": ("https://docs.python.org/3/", None), "pandas": ("https://pandas.pydata.org/pandas-docs/stable", None), "numpy": ("https://numpy.org/doc/stable", None), # "numba": ("https://numba.pydata.org/numba-doc/latest", None), "dask": ("https://docs.dask.org/en/latest", None), "xarray": ("https://docs.xarray.dev/en/stable/", None), } autosummary_generate = True autodoc_typehints = "none" # Napoleon configurations napoleon_google_docstring = False napoleon_numpy_docstring = True napoleon_use_param = False napoleon_use_rtype = False napoleon_preprocess_types = True napoleon_type_aliases = { # general terms "sequence": ":term:`sequence`", "iterable": ":term:`iterable`", "callable": ":py:func:`callable`", "dict_like": ":term:`dict-like `", "dict-like": ":term:`dict-like `", "path-like": ":term:`path-like `", "mapping": ":term:`mapping`", "file-like": ":term:`file-like `", # special terms # "same type as caller": "*same type as caller*", # does not work, yet # "same type as values": "*same type as values*", # does not work, yet # stdlib type aliases "MutableMapping": "~collections.abc.MutableMapping", "sys.stdout": ":obj:`sys.stdout`", "timedelta": "~datetime.timedelta", "string": ":class:`string `", # numpy terms "array_like": ":term:`array_like`", "array-like": ":term:`array-like `", "scalar": ":term:`scalar`", "array": ":term:`array`", "hashable": ":term:`hashable `", # matplotlib terms "color-like": ":py:func:`color-like `", "matplotlib colormap name": ":doc:`matplotlib colormap name `", "matplotlib axes object": ":py:class:`matplotlib axes object `", "colormap": ":py:class:`colormap `", # objects without namespace: xarray "DataArray": "~xarray.DataArray", "Dataset": "~xarray.Dataset", "Variable": "~xarray.Variable", "DatasetGroupBy": "~xarray.core.groupby.DatasetGroupBy", "DataArrayGroupBy": "~xarray.core.groupby.DataArrayGroupBy", # objects without namespace: numpy "ndarray": "~numpy.ndarray", "DaskArray": "~dask.array.Array", "MaskedArray": "~numpy.ma.MaskedArray", "dtype": "~numpy.dtype", "ComplexWarning": "~numpy.ComplexWarning", # objects without namespace: pandas "Index": "~pandas.Index", "MultiIndex": "~pandas.MultiIndex", "CategoricalIndex": "~pandas.CategoricalIndex", "TimedeltaIndex": "~pandas.TimedeltaIndex", "DatetimeIndex": "~pandas.DatetimeIndex", "Series": "~pandas.Series", "DataFrame": "~pandas.DataFrame", "Categorical": "~pandas.Categorical", "Path": "~~pathlib.Path", # objects with abbreviated namespace (from pandas) "pd.Index": "~pandas.Index", "pd.NaT": "~pandas.NaT", } flox-0.11.2/docs/source/engines.md000066400000000000000000000034031514775624000167600ustar00rootroot00000000000000(engines)= # Engines `flox` provides multiple options, using the `engine` kwarg, for computing the core GroupBy reduction on numpy or other array types other than dask. 1. `engine="numpy"` wraps `numpy_groupies.aggregate_numpy`. This uses indexing tricks and functions like `np.bincount`, or the ufunc `.at` methods (.e.g `np.maximum.at`) to provided reasonably performant aggregations. 1. `engine="numba"` wraps `numpy_groupies.aggregate_numba`. This uses `numba` kernels for the core aggregation. 1. `engine="flox"` uses the `ufunc.reduceat` method after first argsorting the array so that all group members occur sequentially. This was copied from a [gist by Stephan Hoyer](https://gist.github.com/shoyer/f538ac78ae904c936844) 1. `engine="numbagg"` uses the reductions available in [`numbagg.grouped`](https://github.com/numbagg/numbagg/blob/main/numbagg/grouped.py) from the [numbagg](https://github.com/numbagg/numbagg) project. See [](arrays) for more details. ## Tradeoffs For the common case of reducing a nD array by a 1D array of group labels (e.g. `groupby("time.month")`), `engine="numbagg"` is almost always faster, and `engine="flox"` _can_ be faster. The reason is that `numpy_groupies` converts all groupby problems to a 1D problem, this can involve [some overhead](https://github.com/ml31415/numpy-groupies/pull/46). It is possible to optimize this a bit in `flox` or `numpy_groupies`, but the work has not been done yet. The advantage of `engine="numpy"` is that it tends to work for more array types, since it appears to be more common to implement `np.bincount`, and not `np.add.reduceat`. ```{tip} One other potential engine we could add is [`datashader`](https://github.com/xarray-contrib/flox/issues/142). Contributions or discussion is very welcome! ``` flox-0.11.2/docs/source/implementation.md000066400000000000000000000366071514775624000203710ustar00rootroot00000000000000--- jupytext: text_representation: format_name: myst kernelspec: display_name: Python 3 name: python3 --- (algorithms)= # Parallel Algorithms `flox` outsources the core GroupBy operation to the vectorized implementations controlled by the [`engine` kwarg](engines.md). Applying these implementations on a parallel array type like dask can be hard. Performance strongly depends on how the groups are distributed amongst the blocks of an array. `flox` implements 4 strategies for grouped reductions, each is appropriate for a particular distribution of groups among the blocks of a dask array. ```{tip} By default, `flox >= 0.9.0` will use [heuristics](method-heuristics) to choose a `method`. ``` Switch between the various strategies by passing `method` and/or `reindex` to either {py:func}`flox.groupby_reduce` or {py:func}`flox.xarray.xarray_reduce`. Your options are: 1. [`method="map-reduce"` with `reindex=False`](map-reindex-false) 1. [`method="map-reduce"` with `reindex=True`](map-reindex-True) 1. [`method="blockwise"`](method-blockwise) 1. [`method="cohorts"`](method-cohorts) The most appropriate strategy for your problem will depend on the chunking of your dataset, and the distribution of group labels across those chunks. Currently these strategies are implemented for dask. We would like to generalize to other parallel array types as appropriate (e.g. Ramba, cubed, arkouda). Please open an issue to discuss if you are interested. (xarray-split)= ## Background Without `flox` installed, Xarray's GroupBy strategy is to find all unique group labels, index out each group, and then apply the reduction operation. Note that this only works if we know the group labels (i.e. you cannot use this strategy to group by a dask array), and is basically an unvectorized slow for-loop over groups. Schematically, this looks like (colors indicate group labels; separated groups of colors indicate different blocks of an array): ```{image} ../diagrams/new-split-apply-combine-annotated.svg --- alt: xarray-current-strategy width: 100% --- ``` The first step is to extract all members of a group, which involves a _lot_ of communication and is quite expensive (in dataframe terminology, this is a "shuffle"). This is fundamentally why many groupby reductions don't work well right now with big datasets. ## `method="map-reduce"` ![map-reduce-strategy-schematic](/../diagrams/map-reduce.png) The "map-reduce" strategy is inspired by `dask.dataframe.groupby`). The GroupBy reduction is first applied blockwise. Those intermediate results are combined by concatenating to form a new array which is then reduced again. The combining of intermediate results uses dask's `_tree_reduce` till all group results are in one block. At that point the result is "finalized" and returned to the user. ### General Tradeoffs 1. This approach works well when either the initial blockwise reduction is effective, or if the reduction at the first combine step is effective. Here "effective" means we have multiple members of a single group in a block so the blockwise application of groupby-reduce actually reduces values and releases some memory. 1. One downside is that the final result will only have one chunk along the new group axis. 1. We have two choices for how to construct the intermediate arrays. See below. (map-reindex-True)= ### `reindex=True` If we know all the group labels, we can do so right at the blockwise step (`reindex=True`). This matches `dask.array.histogram` and `xhistogram`, where the bin edges, or group labels oof the output, are known. The downside is the potential of large memory use if number of output groups is much larger than number of groups in a block. ```{image} ../diagrams/new-map-reduce-reindex-True-annotated.svg --- alt: map-reduce-reindex-True-strategy-schematic width: 100% --- ``` (map-reindex-False)= ### `reindex=False` We can `reindex` at the combine stage to groups present in the blocks being combined (`reindex=False`). This can limit memory use at the cost of a performance reduction due to extra copies of the intermediate data during reindexing. ```{image} ../diagrams/new-map-reduce-reindex-False-annotated.svg --- alt: map-reduce-reindex-True-strategy-schematic width: 100% --- ``` This approach allows grouping by a dask array so group labels can be discovered at compute time, similar to `dask.dataframe.groupby`. ### reindexing to a sparse array For large numbers of groups, we might be reducing to a very sparse array (e.g. [this issue](https://github.com/xarray-contrib/flox/issues/428)). To control memory, we can instruct flox to reindex the intermediate results to a `sparse.COO` array using: ```python from flox import ReindexArrayType, ReindexStrategy ReindexStrategy( # do not reindex to the full output grid at the blockwise aggregation stage blockwise=False, # when combining intermediate results after blockwise aggregation, reindex to the # common grid using a sparse.COO array type array_type=ReindexArrayType.SPARSE_COO, ) ``` See [this user story](user-stories/large-zonal-stats) for more discussion. ### Example For example, consider `groupby("time.month")` with monthly frequency data and chunksize of 4 along `time`. ![cohorts-schematic](/../diagrams/cohorts-month-chunk4.png) With `reindex=True`, each block will become 3x its original size at the blockwise step: input blocks have 4 timesteps while output block has a value for all 12 months. One could use `reindex=False` to control memory usage but also see [`method="cohorts"`](method-cohorts) below. (method-blockwise)= ## `method="blockwise"` One case where `method="map-reduce"` doesn't work well is the case of "resampling" reductions. An example here is resampling from daily frequency to monthly frequency data: `da.resample(time="ME").mean()` For resampling type reductions, 1. Group members occur sequentially (all days in January 2001 occur one after the other) 1. All groups not of exactly equal length (31 days in January but 28 in most Februaries) 1. All members in a group are next to each other (if the time series is sorted, which it usually is). 1. Because there can be a large number of groups, concatenating results for all groups in a single chunk could be catastrophic. In this case, it makes sense to use `dask.dataframe` resample strategy which is to rechunk using {py:func}`flox.rechunk_for_blockwise` so that all members of a group are in a single block. Then, the groupby operation can be applied blockwise. ```{image} ../diagrams/new-blockwise-annotated.svg --- alt: blockwise-strategy-schematic width: 100% --- ``` _Tradeoffs_ 1. Only works for certain groupings. 1. Group labels must be known at graph construction time, so this only works for numpy arrays 1. Currently the rechunking is only implemented for 1D arrays (being motivated by time resampling), but a nD generalization seems possible. 1. Only can use the `blockwise` strategy for grouping by `nD` arrays. 1. Works better when multiple groups are already in a single block; so that the initial rechunking only involves a small amount of communication. (method-cohorts)= ## `method="cohorts"` The `map-reduce` strategy is quite effective but can involve some unnecessary communication. It can be possible to exploit patterns in how group labels are distributed across chunks (similar to `method="blockwise"` above). Two cases are illustrative: 1. Groups labels can be _approximately-periodic_: e.g. `time.dayofyear` (period 365 or 366) or `time.month` (period 12). Consider our earlier example, `groupby("time.month")` with monthly frequency data and chunksize of 4 along `time`. ![cohorts-schematic](/../diagrams/cohorts-month-chunk4.png) Because a chunksize of 4 evenly divides the number of groups (12) all we need to do is index out blocks 0, 3, 7 and then apply the `"map-reduce"` strategy to form the final result for months Jan-Apr. Repeat for the remaining groups of months (May-Aug; Sep-Dec) and then concatenate. 1. Groups can be _spatially localized_ like the blockwise case above, for example grouping by country administrative boundaries like counties or districts. In this case, concatenating the result for the northwesternmost county or district and the southeasternmost district can involve a lot of wasteful communication (again depending on chunking). For such cases, we can adapt xarray's shuffling or subsetting strategy by indexing out "cohorts" or group labels that tend to occur next to each other. ### A motivating example : time grouping One example is the construction of "climatologies" which is a climate science term for something like `groupby("time.month")` ("monthly climatology") or `groupby("time.dayofyear")` ("daily climatology"). In these cases, 1. Groups occur sequentially (day 2 is always after day 1; and February is always after January) 1. Groups are approximately periodic (some years have 365 days and others have 366) Consider our earlier example, `groupby("time.month")` with monthly frequency data and chunksize of 4 along `time`. ![cohorts-schematic](/../diagrams/cohorts-month-chunk4.png) With `method="map-reduce", reindex=True`, each block will become 3x its original size at the blockwise step: input blocks have 4 timesteps while output block has a value for all 12 months. Note that the blockwise groupby-reduction _does not reduce_ the data since there is only one element in each group. In addition, since `map-reduce` will make the final result have only one chunk of size 12 along the new `month` dimension, the final result has chunk sizes 3x that of the input, which may not be ideal. However, because a chunksize of 4 evenly divides the number of groups (12) all we need to do is index out blocks 0, 3, 7 and then apply the `"map-reduce"` strategy to form the final result for months Jan-Apr. Repeat for the remaining groups of months (May-Aug; Sep-Dec) and then concatenate. This is the essence of `method="cohorts"` ### Summary We can generalize this idea for more complicated problems (inspired by the `split_out`kwarg in `dask.dataframe.groupby`) We first apply the groupby-reduction blockwise, then split and reindex blocks to create a new array with which we complete the reduction using `map-reduce`. Because the split or shuffle step occurs after the blockwise reduction, we _sometimes_ communicate a significantly smaller amount of data than if we split or shuffled the input array. ```{image} /../diagrams/new-cohorts-annotated.svg --- alt: cohorts-strategy-schematic width: 100% --- ``` ### Tradeoffs 1. Group labels must be known at graph construction time, so this only works for numpy arrays. 1. This does require more tasks and a more complicated graph, but the communication overhead can be significantly lower. 1. The detection of "cohorts" is currently slow but could be improved. 1. The extra effort of detecting cohorts and multiple copying of intermediate blocks may be worthwhile only if the chunk sizes are small relative to the approximate period of group labels, or small relative to the size of spatially localized groups. ### Example : sensitivity to chunking One annoyance is that if the chunksize doesn't evenly divide the number of groups, we still end up splitting a number of chunks. Consider our earlier example, `groupby("time.month")` with monthly frequency data and chunksize of 4 along `time`. ![cohorts-schematic](/../diagrams/cohorts-month-chunk4.png) ```{code-cell} import flox import numpy as np labels = np.tile(np.arange(12), 12) chunks = (tuple(np.repeat(4, labels.size // 4)),) ``` `flox` can find these cohorts, below it identifies the cohorts with labels `1,2,3,4`; `5,6,7,8`, and `9,10,11,12`. ```{code-cell} preferred_method, chunks_cohorts = flox.core.find_group_cohorts(labels, chunks) chunks_cohorts.values() ``` Now consider `chunksize=5`. ![cohorts-schematic](/../diagrams/cohorts-month-chunk5.png) ```{code-cell} labels = np.tile(np.arange(12), 12) chunks = (tuple(np.repeat(5, labels.size // 5)) + (4,),) preferred_method, chunks_cohorts = flox.core.find_group_cohorts(labels, chunks, merge=True) chunks_cohorts.values() ``` We find 7 cohorts (note the original xarray strategy is equivalent to constructing 12 cohorts). In this case, it seems to better to rechunk to a size of `4` (or `6`) along `time`. Indeed flox's heuristics think `"map-reduce"` is better for this case: ```{code-cell} preferred_method ``` ### Example : spatial grouping Spatial groupings are particularly interesting for the `"cohorts"` strategy. Consider the problem of computing county-level aggregated statistics ([example blog post](https://xarray.dev/blog/flox)). There are ~3100 groups (counties), each marked by a different color. There are ~2300 chunks of size (350, 350) in (lat, lon). Many groups are contained to a small number of chunks: see left panel where the grid lines mark chunk boundaries. ![cohorts-schematic](/../diagrams/nwm-cohorts.png) This seems like a good fit for `'cohorts'`: to get the answer for a county in the Northwest US, we needn't look at values for the southwest US. How do we decide that automatically for the user? (method-heuristics)= ## Heuristics `flox >=0.9` will automatically choose `method` for you. To do so, we need to detect how each group label is distributed across the chunks of the array; and the degree to which the chunk distribution for a particular label overlaps with all other labels. The algorithm is as follows. 1. First determine which labels are present in each chunk. The distribution of labels across chunks is represented internally as a 2D boolean sparse array `S[chunks, labels]`. `S[i, j] = 1` when label `j` is present in chunk `i`. 1. Then we look for patterns in `S` to decide if we can use `"blockwise"`. The dark color cells are `1` at that cell in `S`. ![bitmask-patterns](/../diagrams/bitmask-patterns-perfect.png) - On the left, is a monthly grouping for a monthly time series with chunk size 4. There are 3 non-overlapping cohorts so `method="cohorts"` is perfect. - On the right, is a resampling problem of a daily time series with chunk size 10 to 5-daily frequency. Two 5-day periods are exactly contained in one chunk, so `method="blockwise"` is perfect. 1. The metric used for determining the degree of overlap between the chunks occupied by different labels is [containment](http://ekzhu.com/datasketch/lshensemble.html). For each label `i` we can quickly compute containment against all other labels `j` as `C = S.T @ S / number_chunks_per_label`. Here is `C` for a range of chunk sizes from 1 to 12, for computing the monthly mean of a monthly time series problem, \[the title on each image is `(chunk size, sparsity)`\]. ```python chunks = np.arange(1, 13) labels = np.tile(np.arange(1, 13), 30) ``` ![cohorts-schematic](/../diagrams/containment.png) 1. To choose between `"map-reduce"` and `"cohorts"`, we need a summary measure of the degree to which the labels overlap with each other. We can use _sparsity_ --- the number of non-zero elements in `C` divided by the number of elements in `C`, `C.nnz/C.size`. We use sparsity(`S`) as an approximation for the sparsity(`C`) to avoid a potentially expensive sparse matrix dot product when `S` isn't particularly sparse. When sparsity(`S`) > 0.4 (arbitrary), we choose `"map-reduce"` since there is decent overlap between (any) cohorts. Otherwise we use `"cohorts"`. Cool, isn't it?! For reference here is `S` and `C` for the US county groupby problem: ![county-bitmask](/../diagrams/counties-bitmask-containment.png) The sparsity of `C` is 0.006, so `"cohorts"` seems a good strategy here. flox-0.11.2/docs/source/index.md000066400000000000000000000075301514775624000164440ustar00rootroot00000000000000# flox: fast & furious GroupBy reductions for `dask.array` [![GitHub Workflow CI Status](https://img.shields.io/github/actions/workflow/status/xarray-contrib/flox/ci.yaml?branch=main&logo=github&style=flat)](https://github.com/xarray-contrib/flox/actions) [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/xarray-contrib/flox/main.svg)](https://results.pre-commit.ci/latest/github/xarray-contrib/flox/main) [![image](https://img.shields.io/codecov/c/github/xarray-contrib/flox.svg?style=flat)](https://codecov.io/gh/xarray-contrib/flox) [![Documentation Status](https://readthedocs.org/projects/flox/badge/?version=latest)](https://flox.readthedocs.io/en/latest/?badge=latest) [![PyPI](https://img.shields.io/pypi/v/flox.svg?style=flat)](https://pypi.org/project/flox/) [![Conda-forge](https://img.shields.io/conda/vn/conda-forge/flox.svg?style=flat)](https://anaconda.org/conda-forge/flox) [![NASA-80NSSC18M0156](https://img.shields.io/badge/NASA-80NSSC18M0156-blue)](https://earthdata.nasa.gov/esds/competitive-programs/access/pangeo-ml) [![NASA-80NSSC22K0345](https://img.shields.io/badge/NASA-80NSSC22K0345-blue)](https://science.nasa.gov/open-science-overview) ## Overview `flox` mainly provides strategies for fast GroupBy reductions with dask.array. `flox` uses the MapReduce paradigm (or a "tree reduction") to run the GroupBy operation in a parallel-native way totally avoiding a sort or shuffle operation. It was motivated by 1. Dask Dataframe GroupBy [blogpost](https://blog.dask.org/2019/10/08/df-groupby) 1. numpy_groupies in Xarray [issue](https://github.com/pydata/xarray/issues/4473) See a presentation ([video](https://discourse.pangeo.io/t/november-17-2021-flox-fast-furious-groupby-reductions-with-dask-at-pangeo-scale/2016), [slides](https://docs.google.com/presentation/d/1YubKrwu9zPHC_CzVBhvORuQBW-z148BvX3Ne8XcvWsQ/edit?usp=sharing)) about this package, from the Pangeo Showcase. ## Why flox? 1. {py:func}`flox.groupby_reduce` [wraps](engines.md) the `numpy-groupies` package for performant Groupby reductions on nD arrays. 1. {py:func}`flox.groupby_reduce` provides [parallel-friendly strategies](implementation.md) for GroupBy reductions by wrapping `numpy-groupies` for dask arrays. 1. `flox` [integrates with xarray](xarray.md) to provide more performant Groupby and Resampling operations. 1. {py:func}`flox.xarray.xarray_reduce` [extends](xarray.md) Xarray's GroupBy operations allowing lazy grouping by dask arrays, grouping by multiple arrays, as well as combining categorical grouping and histogram-style binning operations using multiple variables. 1. `flox` also provides utility functions for rechunking both dask arrays and Xarray objects along a single dimension using the group labels as a guide: 1. To rechunk for blockwise operations: {py:func}`flox.rechunk_for_blockwise`, {py:func}`flox.xarray.rechunk_for_blockwise`. 1. To rechunk so that "cohorts", or groups of labels, tend to occur in the same chunks: {py:func}`flox.rechunk_for_cohorts`, {py:func}`flox.xarray.rechunk_for_cohorts`. ## Installing ```shell $ pip install flox ``` ```shell $ conda install -c conda-forge flox ``` ## Acknowledgements This work was funded in part by 1. NASA-ACCESS 80NSSC18M0156 "Community tools for analysis of NASA Earth Observing System Data in the Cloud" (PI J. Hamman), 1. NASA-OSTFL 80NSSC22K0345 "Enhancing analysis of NASA data with the open-source Python Xarray Library" (PIs Scott Henderson, University of Washington; Deepak Cherian, NCAR; Jessica Scheick, University of New Hampshire), and 1. [NCAR's Earth System Data Science Initiative](https://ncar.github.io/esds/). It was motivated by many discussions in the [Pangeo](https://pangeo.io) community. ## Contents ```{eval-rst} .. toctree:: :maxdepth: 1 intro.md aggregations.md engines.md arrays.md implementation.md xarray.md user-stories.md api.rst ``` flox-0.11.2/docs/source/intro.md000066400000000000000000000073501514775624000164700ustar00rootroot00000000000000--- jupytext: text_representation: format_name: myst kernelspec: display_name: Python 3 name: python3 --- ```{eval-rst} .. currentmodule:: flox ``` # 10 minutes to flox ## GroupBy single variable ```{code-cell} import numpy as np import xarray as xr from flox.xarray import xarray_reduce labels = xr.DataArray( [1, 2, 3, 1, 2, 3, 0, 0, 0], dims="x", name="label", ) labels ``` ### With numpy ```{code-cell} da = xr.DataArray( np.ones((9,)), dims="x", name="array" ) ``` Apply the reduction using {py:func}`flox.xarray.xarray_reduce` specifying the reduction operation in `func` ```{code-cell} xarray_reduce(da, labels, func="sum") ``` ### With dask Let's first chunk `da` and `labels` ```{code-cell} da_chunked = da.chunk(x=2) labels_chunked = labels.chunk(x=3) ``` Grouping a dask array by a numpy array is unchanged ```{code-cell} xarray_reduce(da_chunked, labels, func="sum") ``` When grouping **by** a dask array, we need to specify the "expected group labels" on the output so we can construct the result DataArray. Without the `expected_groups` kwarg, an error is raised ```{code-cell} --- tags: [raises-exception] --- xarray_reduce(da_chunked, labels_chunked, func="sum") ``` Now we specify `expected_groups`: ```{code-cell} dask_result = xarray_reduce( da_chunked, labels_chunked, func="sum", expected_groups=[0, 1, 2, 3], ) dask_result ``` Note that any group labels not present in `expected_groups` will be ignored. You can also provide `expected_groups` for the pure numpy GroupBy. ```{code-cell} numpy_result = xarray_reduce( da, labels, func="sum", expected_groups=[0, 1, 2, 3], ) numpy_result ``` The two are identical: ```{code-cell} numpy_result.identical(dask_result) ``` ## Binning by a single variable For binning, specify the bin edges in `expected_groups` using {py:class}`pandas.IntervalIndex`: ```{code-cell} import pandas as pd xarray_reduce( da, labels, func="sum", expected_groups=pd.IntervalIndex.from_breaks([0.5, 1.5, 2.5, 6]), ) ``` Similarly for dask inputs ```{code-cell} xarray_reduce( da_chunked, labels_chunked, func="sum", expected_groups=pd.IntervalIndex.from_breaks([0.5, 1.5, 2.5, 6]), ) ``` For more control over the binning (which edge is closed), pass the appropriate kwarg to {py:class}`pandas.IntervalIndex`: ```{code-cell} xarray_reduce( da_chunked, labels_chunked, func="sum", expected_groups=pd.IntervalIndex.from_breaks([0.5, 1.5, 2.5, 6], closed="left"), ) ``` ## Grouping by multiple variables ```{code-cell} arr = np.ones((4, 12)) labels1 = np.array(["a", "a", "c", "c", "c", "b", "b", "c", "c", "b", "b", "f"]) labels2 = np.array([1, 2, 2, 1]) da = xr.DataArray( arr, dims=("x", "y"), coords={"labels2": ("x", labels2), "labels1": ("y", labels1)} ) da ``` To group by multiple variables simply pass them as `*args`: ```{code-cell} xarray_reduce(da, "labels1", "labels2", func="sum") ``` ## Histogramming (Binning by multiple variables) An unweighted histogram is simply a groupby multiple variables with count. ```{code-cell} python arr = np.ones((4, 12)) labels1 = np.array(np.linspace(0, 10, 12)) labels2 = np.array([1, 2, 2, 1]) da = xr.DataArray( arr, dims=("x", "y"), coords={"labels2": ("x", labels2), "labels1": ("y", labels1)} ) da ``` Specify bins in `expected_groups` ```{code-cell} python xarray_reduce( da, "labels1", "labels2", func="count", expected_groups=( pd.IntervalIndex.from_breaks([-0.5, 4.5, 6.5, 8.9]), # labels1 pd.IntervalIndex.from_breaks([0.5, 1.5, 1.9]), # labels2 ), ) ``` ## Resampling Use the xarray interface i.e. `da.resample(time="ME").mean()`. Optionally pass [`method="blockwise"`](method-blockwise): `da.resample(time="ME").mean(method="blockwise")` flox-0.11.2/docs/source/user-stories.md000066400000000000000000000005121514775624000177720ustar00rootroot00000000000000# Tricks & Stories ```{eval-rst} .. toctree:: :maxdepth: 1 user-stories/overlaps.md user-stories/climatology.ipynb user-stories/climatology-hourly.ipynb user-stories/climatology-hourly-cubed.ipynb user-stories/custom-aggregations.ipynb user-stories/nD-bins.ipynb user-stories/large-zonal-stats.ipynb ``` flox-0.11.2/docs/source/user-stories/000077500000000000000000000000001514775624000174525ustar00rootroot00000000000000flox-0.11.2/docs/source/user-stories/climatology-hourly-cubed.ipynb000066400000000000000000000054751514775624000254530ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "id": "0", "metadata": {}, "source": [ "# More climatology reductions using Cubed\n", "\n", "This is the Cubed equivalent of [More climatology reductions](climatology-hourly.ipynb).\n", "\n", "The task is to compute an hourly climatology from an hourly dataset with 744 hours in each chunk, using the \"map-reduce\" strategy." ] }, { "cell_type": "code", "execution_count": null, "id": "1", "metadata": {}, "outputs": [], "source": [ "import cubed\n", "import cubed.array_api as xp\n", "import numpy as np\n", "import pandas as pd\n", "import xarray as xr\n", "\n", "import flox.xarray" ] }, { "cell_type": "markdown", "id": "2", "metadata": {}, "source": [ "## Create data\n", "\n", "Note that we use fewer lat/long points so the computation can be run locally." ] }, { "cell_type": "code", "execution_count": null, "id": "3", "metadata": {}, "outputs": [], "source": [ "spec = cubed.Spec(allowed_mem=\"2GB\")\n", "ds = xr.Dataset(\n", " {\n", " \"tp\": (\n", " (\"time\", \"latitude\", \"longitude\"),\n", " xp.ones((8760, 72, 144), chunks=(744, 5, 144), dtype=np.float32, spec=spec),\n", " )\n", " },\n", " coords={\"time\": pd.date_range(\"2021-01-01\", \"2021-12-31 23:59\", freq=\"h\")},\n", ")\n", "ds" ] }, { "cell_type": "markdown", "id": "4", "metadata": {}, "source": [ "## Computation" ] }, { "cell_type": "code", "execution_count": null, "id": "5", "metadata": {}, "outputs": [], "source": [ "hourly = flox.xarray.xarray_reduce(ds.tp, ds.time.dt.hour, func=\"mean\", reindex=True)\n", "hourly" ] }, { "cell_type": "code", "execution_count": null, "id": "6", "metadata": {}, "outputs": [], "source": [ "hourly.compute()" ] }, { "cell_type": "markdown", "id": "7", "metadata": {}, "source": [ "## Other climatologies: resampling by month\n", "\n", "This uses the \"blockwise\" strategy." ] }, { "cell_type": "code", "execution_count": null, "id": "8", "metadata": {}, "outputs": [], "source": [ "monthly = ds.tp.resample(time=\"ME\").sum(method=\"blockwise\")\n", "monthly" ] }, { "cell_type": "code", "execution_count": null, "id": "9", "metadata": {}, "outputs": [], "source": [ "monthly.compute()" ] } ], "metadata": { "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 5 } flox-0.11.2/docs/source/user-stories/climatology-hourly.ipynb000066400000000000000000002207661514775624000243750ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "id": "0", "metadata": {}, "source": [ "# More climatology reductions\n", "\n", "This one is motivated by\n", "[this Pangeo Discourse post](https://discourse.pangeo.io/t/dask-xarray-and-swap-memory-polution-on-local-linux-cluster/2453/5)\n", "and follows\n", "[this notebook](https://nbviewer.ipython.org/gist/fmaussion/95d1b9c9a3113db2f987b91e842cb8e0)\n", "\n", "The task is to compute an hourly climatology from an hourly dataset with 744\n", "hours in each chunk.\n", "\n", "We choose the \"map-reduce\" strategy because:\n", "\n", "1. all hours (groups) are present in each chunk;\n", "2. a groupby reduction applied blockwise will result in arrays of shape (X,\n", " Y, 744) being reduced to (X, Y, 24) i.e. 744/24=31x decrease in chunk size,\n", " so this should work well memory wise.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "1", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/Users/dcherian/mambaforge/envs/dcpy/lib/python3.8/site-packages/distributed/node.py:180: UserWarning: Port 8787 is already in use.\n", "Perhaps you already have a cluster running?\n", "Hosting the HTTP server on port 51613 instead\n", " warnings.warn(\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "fc1dd8438def4d75acee8602c544248c", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Tab(children=(HTML(value='
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.Dataset>\n",
       "Dimensions:  (time: 8760, latitude: 721, longitude: 1440)\n",
       "Coordinates:\n",
       "  * time     (time) datetime64[ns] 2021-01-01 ... 2021-12-31T23:00:00\n",
       "Dimensions without coordinates: latitude, longitude\n",
       "Data variables:\n",
       "    tp       (time, latitude, longitude) float32 dask.array<chunksize=(744, 50, 1440), meta=np.ndarray>
" ], "text/plain": [ "\n", "Dimensions: (time: 8760, latitude: 721, longitude: 1440)\n", "Coordinates:\n", " * time (time) datetime64[ns] 2021-01-01 ... 2021-12-31T23:00:00\n", "Dimensions without coordinates: latitude, longitude\n", "Data variables:\n", " tp (time, latitude, longitude) float32 dask.array" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ds = xr.Dataset(\n", " {\n", " \"tp\": (\n", " (\"time\", \"latitude\", \"longitude\"),\n", " dask.array.ones((8760, 721, 1440), chunks=(744, 50, 1440), dtype=np.float32),\n", " )\n", " },\n", " coords={\"time\": pd.date_range(\"2021-01-01\", \"2021-12-31 23:59\", freq=\"H\")},\n", ")\n", "ds" ] }, { "cell_type": "markdown", "id": "5", "metadata": {}, "source": [ "Here's just plain xarray: 10000 tasks and one chunk per hour in the output\n" ] }, { "cell_type": "code", "execution_count": null, "id": "6", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray 'tp' (hour: 24, latitude: 721, longitude: 1440)>\n",
       "dask.array<stack, shape=(24, 721, 1440), dtype=float32, chunksize=(1, 50, 1440), chunktype=numpy.ndarray>\n",
       "Coordinates:\n",
       "  * hour     (hour) int64 0 1 2 3 4 5 6 7 8 9 ... 14 15 16 17 18 19 20 21 22 23\n",
       "Dimensions without coordinates: latitude, longitude
" ], "text/plain": [ "\n", "dask.array\n", "Coordinates:\n", " * hour (hour) int64 0 1 2 3 4 5 6 7 8 9 ... 14 15 16 17 18 19 20 21 22 23\n", "Dimensions without coordinates: latitude, longitude" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ds.tp.groupby(\"time.hour\").mean()" ] }, { "cell_type": "markdown", "id": "7", "metadata": {}, "source": [ "And flox: 600 tasks and all hours in a single chunk\n" ] }, { "cell_type": "code", "execution_count": null, "id": "8", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray 'tp' (hour: 24, latitude: 721, longitude: 1440)>\n",
       "dask.array<transpose, shape=(24, 721, 1440), dtype=float32, chunksize=(24, 50, 1440), chunktype=numpy.ndarray>\n",
       "Coordinates:\n",
       "  * hour     (hour) int64 0 1 2 3 4 5 6 7 8 9 ... 14 15 16 17 18 19 20 21 22 23\n",
       "Dimensions without coordinates: latitude, longitude
" ], "text/plain": [ "\n", "dask.array\n", "Coordinates:\n", " * hour (hour) int64 0 1 2 3 4 5 6 7 8 9 ... 14 15 16 17 18 19 20 21 22 23\n", "Dimensions without coordinates: latitude, longitude" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hourly = flox.xarray.xarray_reduce(ds.tp, ds.time.dt.hour, func=\"mean\")\n", "hourly" ] }, { "cell_type": "code", "execution_count": null, "id": "9", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "id": "10", "metadata": {}, "outputs": [], "source": [ "with performance_report(\"hourly-climatology.html\"):\n", " hourly.compute()" ] }, { "cell_type": "markdown", "id": "11", "metadata": {}, "source": [ "View the performance report\n", "[here](https://rawcdn.githack.com/dcherian/flox/592c46ba0bb859f732968b68426b6332caebc213/docs/source/user-stories/hourly-climatology.html),\n", "and a video of the dask dashboard\n", "[here](https://drive.google.com/file/d/1uY36DiTbv1w7TefbrCEyBcOli5NiaNUP/view?usp=sharing)\n" ] } ], "metadata": { "keep_output": true, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3" }, "mystnb": { "execution_mode": "off" } }, "nbformat": 4, "nbformat_minor": 5 } flox-0.11.2/docs/source/user-stories/climatology.ipynb000066400000000000000000000227771514775624000230570ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "id": "0", "metadata": {}, "source": [ "# Strategies for climatology calculations\n", "\n", "This notebook is motivated by\n", "[this post](https://discourse.pangeo.io/t/understanding-optimal-zarr-chunking-scheme-for-a-climatology/2335)\n", "on the Pangeo discourse forum.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "1", "metadata": { "tags": [] }, "outputs": [], "source": [ "import dask.array\n", "import pandas as pd\n", "import xarray as xr\n", "\n", "import flox\n", "import flox.xarray" ] }, { "cell_type": "markdown", "id": "2", "metadata": {}, "source": [ "Let's first create an example Xarray Dataset representing the OISST dataset,\n", "with chunk sizes matching that in the post.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "3", "metadata": {}, "outputs": [], "source": [ "oisst = xr.DataArray(\n", " dask.array.ones((14532, 720, 1440), chunks=(20, -1, -1)),\n", " dims=(\"time\", \"lat\", \"lon\"),\n", " coords={\"time\": pd.date_range(\"1981-09-01 12:00\", \"2021-06-14 12:00\", freq=\"D\")},\n", " name=\"sst\",\n", ")\n", "oisst" ] }, { "cell_type": "markdown", "id": "4", "metadata": {}, "source": [ "To account for Feb-29 being present in some years, we'll construct a time vector to group by as \"mmm-dd\" string.\n", "\n", "```{seealso}\n", "For more options, see [this great website](https://strftime.org/).\n", "```" ] }, { "cell_type": "code", "execution_count": null, "id": "5", "metadata": {}, "outputs": [], "source": [ "day = oisst.time.dt.strftime(\"%h-%d\").rename(\"day\")\n", "day" ] }, { "cell_type": "markdown", "id": "6", "metadata": {}, "source": [ "## First, `method=\"map-reduce\"`\n", "\n", "The default\n", "[method=\"map-reduce\"](https://flox.readthedocs.io/en/latest/implementation.html#method-map-reduce)\n", "doesn't work so well. We aggregate all days in a single ~3GB chunk.\n", "\n", "For this to work well, we'd want smaller chunks in space and bigger chunks in\n", "time.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "7", "metadata": {}, "outputs": [], "source": [ "flox.xarray.xarray_reduce(\n", " oisst,\n", " day,\n", " func=\"mean\",\n", " method=\"map-reduce\",\n", ")" ] }, { "cell_type": "markdown", "id": "8", "metadata": {}, "source": [ "### Rechunking for map-reduce\n", "\n", "We can split each chunk along the `lat`, `lon` dimensions to make sure the\n", "output chunk sizes are more reasonable\n" ] }, { "cell_type": "code", "execution_count": null, "id": "9", "metadata": {}, "outputs": [], "source": [ "flox.xarray.xarray_reduce(\n", " oisst.chunk({\"lat\": -1, \"lon\": 120}),\n", " day,\n", " func=\"mean\",\n", " method=\"map-reduce\",\n", ")" ] }, { "cell_type": "markdown", "id": "10", "metadata": {}, "source": [ "But what if we didn't want to rechunk the dataset so drastically (note the 10x\n", "increase in tasks). For that let's try `method=\"cohorts\"`\n", "\n", "## `method=\"cohorts\"`\n", "\n", "We can take advantage of patterns in the groups here \"day of year\".\n", "Specifically:\n", "\n", "1. The groups at an approximately periodic interval, 365 or 366 days\n", "2. The chunk size 20 is smaller than the period of 365 or 366. This means, that\n", " to construct the mean for days 1-20, we just need to use the chunks that\n", " contain days 1-20.\n", "\n", "This strategy is implemented as\n", "[method=\"cohorts\"](https://flox.readthedocs.io/en/latest/implementation.html#method-cohorts)\n" ] }, { "cell_type": "code", "execution_count": null, "id": "11", "metadata": {}, "outputs": [], "source": [ "flox.xarray.xarray_reduce(\n", " oisst,\n", " day,\n", " func=\"mean\",\n", " method=\"cohorts\",\n", ")" ] }, { "cell_type": "markdown", "id": "12", "metadata": {}, "source": [ "By default cohorts doesn't work so well for this problem because the period\n", "isn't regular (365 vs 366) and the period isn't divisible by the chunk size. So\n", "the groups end up being \"out of phase\" (for a visual illustration\n", "[click here](https://flox.readthedocs.io/en/latest/implementation.html#method-cohorts)).\n", "Now we have the opposite problem: the chunk sizes on the output are too small.\n", "\n", "Let us inspect the cohorts" ] }, { "cell_type": "code", "execution_count": null, "id": "13", "metadata": {}, "outputs": [], "source": [ "# integer codes for each \"day\"\n", "codes, _ = pd.factorize(day.data)\n", "preferred_method, cohorts = flox.core.find_group_cohorts(\n", " labels=codes,\n", " chunks=(oisst.chunksizes[\"time\"],),\n", ")\n", "print(len(cohorts))" ] }, { "cell_type": "markdown", "id": "14", "metadata": {}, "source": [ "Looking more closely, we can see many cohorts with a single entry. " ] }, { "cell_type": "code", "execution_count": null, "id": "15", "metadata": {}, "outputs": [], "source": [ "cohorts.values()" ] }, { "cell_type": "markdown", "id": "16", "metadata": {}, "source": [ "## Rechunking data for cohorts\n", "\n", "Can we fix the \"out of phase\" problem by rechunking along time?\n", "\n", "First lets see where the current chunk boundaries are" ] }, { "cell_type": "code", "execution_count": null, "id": "17", "metadata": {}, "outputs": [], "source": [ "oisst.chunksizes[\"time\"][:10]" ] }, { "cell_type": "markdown", "id": "18", "metadata": {}, "source": [ "We'll choose to rechunk such that a single month in is a chunk. This is not too different from the current chunking but will help your periodicity problem" ] }, { "cell_type": "code", "execution_count": null, "id": "19", "metadata": {}, "outputs": [], "source": [ "newchunks = xr.ones_like(day).astype(int).resample(time=\"ME\").count()" ] }, { "cell_type": "code", "execution_count": null, "id": "20", "metadata": {}, "outputs": [], "source": [ "rechunked = oisst.chunk(time=tuple(newchunks.data))" ] }, { "cell_type": "markdown", "id": "21", "metadata": {}, "source": [ "And now our cohorts contain more than one group, *and* there is a substantial reduction in number of cohorts **162 -> 12**\n" ] }, { "cell_type": "code", "execution_count": null, "id": "22", "metadata": {}, "outputs": [], "source": [ "preferred_method, new_cohorts = flox.core.find_group_cohorts(\n", " labels=codes,\n", " chunks=(rechunked.chunksizes[\"time\"],),\n", ")\n", "# one cohort per month!\n", "len(new_cohorts)" ] }, { "cell_type": "code", "execution_count": null, "id": "23", "metadata": {}, "outputs": [], "source": [ "preferred_method" ] }, { "cell_type": "code", "execution_count": null, "id": "24", "metadata": {}, "outputs": [], "source": [ "new_cohorts.values()" ] }, { "cell_type": "markdown", "id": "25", "metadata": {}, "source": [ "Now the groupby reduction **looks OK** in terms of number of tasks but remember\n", "that rechunking to get to this point involves some communication overhead.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "26", "metadata": {}, "outputs": [], "source": [ "flox.xarray.xarray_reduce(rechunked, day, func=\"mean\", method=\"cohorts\")" ] }, { "cell_type": "markdown", "id": "27", "metadata": {}, "source": [ "flox's heuristics will choose `\"cohorts\"` automatically!" ] }, { "cell_type": "code", "execution_count": null, "id": "28", "metadata": {}, "outputs": [], "source": [ "flox.xarray.xarray_reduce(rechunked, day, func=\"mean\")" ] }, { "cell_type": "markdown", "id": "29", "metadata": {}, "source": [ "## How about other climatologies?\n", "\n", "Let's try monthly\n" ] }, { "cell_type": "code", "execution_count": null, "id": "30", "metadata": {}, "outputs": [], "source": [ "flox.xarray.xarray_reduce(oisst, oisst.time.dt.month, func=\"mean\")" ] }, { "cell_type": "markdown", "id": "31", "metadata": {}, "source": [ "This looks great. Why?\n", "\n", "It's because each chunk (size 20) is smaller than number of days in a typical\n", "month. `flox` initially applies the groupby-reduction blockwise. For the chunk\n", "size of 20, we will have at most 2 groups in each chunk, so the initial\n", "blockwise reduction is quite effective - at least a 10x reduction in size from\n", "20 elements in time to at most 2 elements in time.\n", "\n", "For this kind of problem, `\"map-reduce\"` works quite well.\n" ] } ], "metadata": { "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 5 } flox-0.11.2/docs/source/user-stories/custom-aggregations.ipynb000066400000000000000000000174031514775624000245040ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "id": "0", "metadata": {}, "source": [ "# Custom Aggregations\n", "\n", "This notebook is motivated by a\n", "[post](https://discourse.pangeo.io/t/using-xhistogram-to-bin-measurements-at-particular-stations/2365/4)\n", "on the Pangeo discourse forum.\n", "\n", "> Even better would be a command that lets me simply do the following.\n", ">\n", "> A = da.groupby(['lon_bins', 'lat_bins']).mode()\n", "\n", "This notebook will describe how to accomplish this using a custom `Aggregation`.\n", "\n", "\n", "```{tip}\n", "flox now supports `mode`, `nanmode`, `quantile`, `nanquantile`, `median`, `nanmedian` using exactly the same \n", "approach as shown below\n", "```\n" ] }, { "cell_type": "code", "execution_count": null, "id": "1", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import numpy_groupies as npg\n", "import xarray as xr\n", "\n", "import flox.xarray\n", "from flox import Aggregation\n", "from flox.aggregations import mean\n", "\n", "# define latitude and longitude bins\n", "binsize = 1.0 # 1°x1° bins\n", "lon_min, lon_max, lat_min, lat_max = [-180, 180, -65, 65]\n", "lon_bins = np.arange(lon_min, lon_max, binsize)\n", "lat_bins = np.arange(lat_min, lat_max, binsize)\n", "\n", "size = 28397\n", "\n", "\n", "da = xr.DataArray(\n", " np.random.randint(0, 7, size=size),\n", " dims=\"profile\",\n", " coords={\n", " \"lat\": (\n", " \"profile\",\n", " (np.random.random(size) - 0.5) * (lat_max - lat_min),\n", " ),\n", " \"lon\": (\n", " \"profile\",\n", " (np.random.random(size) - 0.5) * (lon_max - lon_min),\n", " ),\n", " },\n", " name=\"label\",\n", ")\n", "da" ] }, { "cell_type": "markdown", "id": "2", "metadata": {}, "source": [ "## A built-in reduction\n", "\n", "First a simple example of lat-lon binning using a built-in reduction: mean\n" ] }, { "cell_type": "code", "execution_count": null, "id": "3", "metadata": {}, "outputs": [], "source": [ "binned_mean = flox.xarray.xarray_reduce(\n", " da,\n", " da.lat,\n", " da.lon,\n", " func=\"mean\", # built-in\n", " expected_groups=(lat_bins, lon_bins),\n", " isbin=(True, True),\n", ")\n", "binned_mean.plot()" ] }, { "cell_type": "markdown", "id": "4", "metadata": {}, "source": [ "## Aggregations\n", "\n", "flox knows how to interperet `func=\"mean\"` because it's been implemented in\n", "`aggregations.py` as an\n", "[Aggregation](https://flox.readthedocs.io/en/latest/generated/flox.aggregations.Aggregation.html)\n", "\n", "An `Aggregation` is a blueprint for computing an aggregation, with both numpy\n", "and dask data.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "5", "metadata": {}, "outputs": [], "source": [ "print(type(mean))\n", "mean" ] }, { "cell_type": "markdown", "id": "6", "metadata": {}, "source": [ "Here's how the mean Aggregation is created\n", "\n", "```python\n", "mean = Aggregation(\n", " name=\"mean\",\n", "\n", " # strings in the following are built-in grouped reductions\n", " # implemented by the underlying \"engine\": flox or numpy_groupies or numbagg\n", "\n", " # for pure numpy inputs\n", " numpy=\"mean\",\n", "\n", " # The next are for dask inputs and describe how to reduce\n", " # the data in parallel\n", " chunk=(\"sum\", \"nanlen\"), # first compute these blockwise : (grouped_sum, grouped_count)\n", " combine=(\"sum\", \"sum\"), # reduce intermediate results (sum the sums, sum the counts)\n", " finalize=lambda sum_, count: sum_ / count, # final mean value (divide sum by count)\n", "\n", " fill_value=(0, 0), # fill value for intermediate sums and counts when groups have no members\n", " dtypes=(None, np.intp), # optional dtypes for intermediates\n", " final_dtype=np.floating, # final dtype for output\n", ")\n", "```\n" ] }, { "cell_type": "markdown", "id": "7", "metadata": {}, "source": [ "## Defining a custom aggregation\n", "\n", "First we'll need a function that executes the grouped reduction given numpy\n", "inputs.\n", "\n", "Custom functions are required to have this signature (copied form\n", "numpy_groupies):\n", "\n", "```python\n", "\n", "def custom_grouped_reduction(\n", " group_idx, array, *, axis=-1, size=None, fill_value=None, dtype=None\n", "):\n", " \"\"\"\n", " Parameters\n", " ----------\n", "\n", " group_idx : np.ndarray, 1D\n", " integer codes for group labels (1D)\n", " array : np.ndarray, nD\n", " values to reduce (nD)\n", " axis : int\n", " axis of array along which to reduce. Requires array.shape[axis] == len(group_idx)\n", " size : int, optional\n", " expected number of groups. If none, output.shape[-1] == number of uniques in group_idx\n", " fill_value : optional\n", " fill_value for when number groups in group_idx is less than size\n", " dtype : optional\n", " dtype of output\n", "\n", " Returns\n", " -------\n", "\n", " np.ndarray with array.shape[-1] == size, containing a single value per group\n", " \"\"\"\n", " pass\n", "```\n", "\n", "Since numpy_groupies does not implement a median, we'll do it ourselves by\n", "passing `np.median` to `numpy_groupies.aggregate_numpy.aggregate`. This will\n", "loop over all groups, and then execute `np.median` on the group members in\n", "serial. It is not fast, but quite convenient.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "8", "metadata": {}, "outputs": [], "source": [ "def grouped_median(group_idx, array, *, axis=-1, size=None, fill_value=None, dtype=None):\n", " return npg.aggregate_numpy.aggregate(\n", " group_idx,\n", " array,\n", " func=np.median,\n", " axis=axis,\n", " size=size,\n", " fill_value=fill_value,\n", " dtype=dtype,\n", " )" ] }, { "cell_type": "markdown", "id": "9", "metadata": {}, "source": [ "Now we create the `Aggregation`\n" ] }, { "cell_type": "code", "execution_count": null, "id": "10", "metadata": {}, "outputs": [], "source": [ "agg_median = Aggregation(\n", " name=\"median\",\n", " numpy=grouped_median,\n", " fill_value=-1,\n", " chunk=None,\n", " combine=None,\n", ")\n", "agg_median" ] }, { "cell_type": "markdown", "id": "11", "metadata": {}, "source": [ "And apply it!\n" ] }, { "cell_type": "code", "execution_count": null, "id": "12", "metadata": {}, "outputs": [], "source": [ "flox.xarray.xarray_reduce(\n", " da,\n", " da.lat,\n", " da.lon,\n", " func=agg_median,\n", " expected_groups=(lat_bins, lon_bins),\n", " isbin=(True, True),\n", " fill_value=np.nan,\n", ")" ] } ], "metadata": { "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 5 } flox-0.11.2/docs/source/user-stories/hourly-climatology.html000066400000000000000000026312141514775624000242140ustar00rootroot00000000000000 Dask Performance Report
flox-0.11.2/docs/source/user-stories/large-zonal-stats.ipynb000066400000000000000000000202201514775624000240600ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "id": "0", "metadata": {}, "source": [ "# Large Raster Zonal Statistics\n", "\n", "\"Zonal statistics\" spans a large range of problems. \n", "\n", "This one is inspired by [this issue](https://github.com/xarray-contrib/flox/issues/428), where a cell areas raster is aggregated over 6 different groupers and summed. Each array involved has a global extent on a 30m grid with shape 560_000 x 1440_000 and chunk size 10_000 x 10_000. Three of the groupers `tcl_year`, `drivers`, and `tcd_thresholds` have a small number of group labels (23, 5, and 7). \n", "\n", "The last 3 groupers are [GADM](https://gadm.org/) level 0, 1, 2 administrative area polygons rasterized to this grid; with 248, 86, and 854 unique labels respectively (arrays `adm0`, `adm1`, and `adm2`). These correspond to country-level, state-level, and county-level administrative boundaries. " ] }, { "cell_type": "markdown", "id": "1", "metadata": {}, "source": [ "## Example dataset" ] }, { "cell_type": "markdown", "id": "2", "metadata": {}, "source": [ "Here is a representative version of the dataset (in terms of size and chunk sizes)." ] }, { "cell_type": "code", "execution_count": null, "id": "3", "metadata": {}, "outputs": [], "source": [ "import dask.array\n", "import numpy as np\n", "import xarray as xr\n", "\n", "from flox.xarray import xarray_reduce\n", "\n", "sizes = {\"y\": 560_000, \"x\": 1440_000}\n", "chunksizes = {\"y\": 10_000, \"x\": 10_000}\n", "dims = (\"y\", \"x\")\n", "shape = tuple(sizes[d] for d in dims)\n", "chunks = tuple(chunksizes[d] for d in dims)\n", "\n", "ds = xr.Dataset(\n", " {\n", " \"areas\": (dims, dask.array.ones(shape, chunks=chunks, dtype=np.float32)),\n", " \"tcl_year\": (\n", " dims,\n", " 1 + dask.array.zeros(shape, chunks=chunks, dtype=np.float32),\n", " ),\n", " \"drivers\": (dims, 2 + dask.array.zeros(shape, chunks=chunks, dtype=np.float32)),\n", " \"tcd_thresholds\": (\n", " dims,\n", " 3 + dask.array.zeros(shape, chunks=chunks, dtype=np.float32),\n", " ),\n", " \"adm0\": (dims, 4 + dask.array.ones(shape, chunks=chunks, dtype=np.float32)),\n", " \"adm1\": (dims, 5 + dask.array.zeros(shape, chunks=chunks, dtype=np.float32)),\n", " \"adm2\": (dims, 6 + dask.array.zeros(shape, chunks=chunks, dtype=np.float32)),\n", " }\n", ")\n", "ds" ] }, { "cell_type": "markdown", "id": "4", "metadata": {}, "source": [ "## Zonal Statistics" ] }, { "cell_type": "markdown", "id": "5", "metadata": {}, "source": [ "Next define the grouper arrays and expected group labels" ] }, { "cell_type": "code", "execution_count": null, "id": "6", "metadata": {}, "outputs": [], "source": [ "by = (ds.tcl_year, ds.drivers, ds.tcd_thresholds, ds.adm0, ds.adm1, ds.adm2)\n", "expected_groups = (\n", " np.arange(23),\n", " np.arange(1, 6),\n", " np.arange(1, 8),\n", " np.arange(248),\n", " np.arange(86),\n", " np.arange(854),\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "7", "metadata": {}, "outputs": [], "source": [ "result = xarray_reduce(\n", " ds.areas,\n", " *by,\n", " expected_groups=expected_groups,\n", " func=\"sum\",\n", ")\n", "result" ] }, { "cell_type": "markdown", "id": "8", "metadata": {}, "source": [ "Formulating the three admin levels as orthogonal dimensions is quite wasteful --- not all countries have 86 states or 854 counties per state. The total number of GADM geometries for levels 0, 1, and 2 is ~48,000 which is much smaller than 23 x 5 x 7 x 248 x 86 x 854 = 14_662_360_160.\n", "\n", "We end up with one humoungous 56GB chunk, that is mostly empty (sparsity ~ 48,000/14_662_360_160 ~ 0.2%).\n", "\n", "## We can do better using a sparse array\n", "\n", "Since the results are very sparse, we can instruct flox to construct dense arrays of intermediate results on the full 23 x 5 x 7 x 248 x 86 x 854 output grid.\n", "\n", "```python\n", "ReindexStrategy(\n", " # do not reindex to the full output grid at the blockwise aggregation stage\n", " blockwise=False,\n", " # when combining intermediate results after blockwise aggregation, reindex to the\n", " # common grid using a sparse.COO array type\n", " array_type=ReindexArrayType.SPARSE_COO\n", ")\n", "```" ] }, { "cell_type": "code", "execution_count": null, "id": "9", "metadata": {}, "outputs": [], "source": [ "from flox import ReindexArrayType, ReindexStrategy\n", "\n", "result = xarray_reduce(\n", " ds.areas,\n", " *by,\n", " expected_groups=expected_groups,\n", " func=\"sum\",\n", " reindex=ReindexStrategy(\n", " blockwise=False,\n", " array_type=ReindexArrayType.SPARSE_COO,\n", " ),\n", " fill_value=0,\n", ")\n", "result" ] }, { "cell_type": "markdown", "id": "10", "metadata": {}, "source": [ "The output is a sparse array (see the **Data type** section)! Note that the size of this array cannot be estimated without computing it.\n", "\n", "The computation runs smoothly with low memory." ] }, { "cell_type": "markdown", "id": "11", "metadata": {}, "source": [ "## Why\n", "\n", "To understand why you might do this, here is how flox runs reductions. In the images below, the `areas` array on the left has 5 2D chunks. Each color represents a group, each square represents a value of the array; clearly there are different groups in each chunk. \n", "\n", "\n", "### reindex = True\n", "\n", "\n", "\n", "First, the grouped-reduction is run on each chunk independently, and the results are constructed as _dense_ arrays on the full 23 x 5 x 7 x 248 x 86 x 854 output grid. This means that every chunk balloons to ~50GB. This method cannot work well.\n", "\n", "### reindex = False with sparse intermediates\n", "\n", "\n", "\n", "First, the grouped-reduction is run on each chunk independently. Conceptually the result after this step is an array with differently sized chunks. \n", "\n", "Next results from neighbouring blocks are concatenated and a reduction is run again. These results are first aligned or reindexed to a common grid of group labels, termed \"reindexing\". At this stage, we instruct flox to construct a _sparse array_ during reindexing, otherwise we will eventually end up constructing _dense_ reindexed arrays of shape 23 x 5 x 7 x 248 x 86 x 854.\n", "\n", "\n", "## Can we do better?\n", "\n", "Yes. \n", "\n", "1. Using the reindexing machinery to convert intermediates to sparse is a little bit hacky. A better option would be to aggregate directly to sparse arrays, potentially using a new `engine=\"sparse\"` ([issue](https://github.com/xarray-contrib/flox/issues/346)).\n", "2. The total number of GADM geometries for levels 0, 1, and 2 is ~48,000. A much more sensible solution would be to allow grouping by these _geometries_ directly. This would allow us to be smart about the reduction, by exploiting the ideas underlying the [`method=\"cohorts\"` strategy](../implementation.md#method-cohorts).\n", "\n", "Regardless, the ability to do such reindexing allows flox to scale to much larger grouper arrays than previously possible.\n", "\n" ] } ], "metadata": { "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 5 } flox-0.11.2/docs/source/user-stories/nD-bins.ipynb000066400000000000000000000215371514775624000220170ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "id": "0", "metadata": { "tags": [], "user_expressions": [] }, "source": [ "# Binning with multi-dimensional bins\n", "\n", "```{warning}\n", "This post is a proof-of-concept for discussion. Expect APIs to change to enable this use case.\n", "```\n", "\n", "Here we explore a binning problem where the bins are multidimensional\n", "([xhistogram issue](https://github.com/xgcm/xhistogram/issues/28))\n", "\n", "> One of such multi-dim bin applications is the ranked probability score rps we\n", "> use in `xskillscore.rps`, where we want to know how many forecasts fell into\n", "> which bins. Bins are often defined as terciles of the forecast distribution\n", "> and the bins for these terciles\n", "> (`forecast_with_lon_lat_time_dims.quantile(q=[.33,.66],dim='time')`) depend on\n", "> `lon` and `lat`.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "1", "metadata": { "tags": [] }, "outputs": [], "source": [ "import math\n", "\n", "import numpy as np\n", "import pandas as pd\n", "import xarray as xr\n", "\n", "import flox\n", "import flox.xarray" ] }, { "cell_type": "markdown", "id": "2", "metadata": { "user_expressions": [] }, "source": [ "## Create test data\n" ] }, { "cell_type": "markdown", "id": "3", "metadata": { "user_expressions": [] }, "source": [ "Data to be reduced\n" ] }, { "cell_type": "code", "execution_count": null, "id": "4", "metadata": { "tags": [] }, "outputs": [], "source": [ "array = xr.DataArray(\n", " np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]),\n", " dims=(\"space\", \"time\"),\n", " name=\"array\",\n", ")\n", "array" ] }, { "cell_type": "markdown", "id": "5", "metadata": { "user_expressions": [] }, "source": [ "Array to group by\n" ] }, { "cell_type": "code", "execution_count": null, "id": "6", "metadata": { "tags": [] }, "outputs": [], "source": [ "by = xr.DataArray(\n", " np.array([[1, 2, 3], [3, 4, 5], [5, 6, 7], [6, 7, 9]]),\n", " dims=(\"space\", \"time\"),\n", " name=\"by\",\n", ")\n", "by" ] }, { "cell_type": "markdown", "id": "7", "metadata": { "tags": [], "user_expressions": [] }, "source": [ "Multidimensional bins:\n" ] }, { "cell_type": "code", "execution_count": null, "id": "8", "metadata": { "tags": [] }, "outputs": [], "source": [ "bins = by + 0.5\n", "bins = xr.DataArray(\n", " np.concatenate([bins, bins[:, [-1]] + 1], axis=-1)[:, :-1].T,\n", " dims=(\"time\", \"nbins\"),\n", " name=\"bins\",\n", ")\n", "bins" ] }, { "cell_type": "markdown", "id": "9", "metadata": { "user_expressions": [] }, "source": [ "## Concept\n", "\n", "The key idea is that GroupBy is two steps:\n", "\n", "1. Factorize (a.k.a \"digitize\") : convert the `by` data to a set of integer\n", " codes representing the bins.\n", "2. Apply the reduction.\n", "\n", "We treat multi-dimensional binning as a slightly complicated factorization\n", "problem. Assume that bins are a function of `time`. So we\n", "\n", "1. generate a set of appropriate integer codes by:\n", " 1. Loop over \"time\" and factorize the data appropriately.\n", " 2. Add an offset to these codes so that \"bin 0\" for `time=0` is different\n", " from \"bin 0\" for `time=1`\n", "2. apply the groupby reduction to the \"offset codes\"\n", "3. reshape the output to the right shape\n", "\n", "We will work at the xarray level, so its easy to keep track of the different\n", "dimensions.\n", "\n", "### Factorizing\n", "\n", "The core `factorize_` function (which wraps `pd.cut`) only handles 1D bins, so\n", "we use `xr.apply_ufunc` to vectorize it for us.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "10", "metadata": { "tags": [] }, "outputs": [], "source": [ "factorize_loop_dim = \"time\"" ] }, { "cell_type": "code", "execution_count": null, "id": "11", "metadata": { "tags": [] }, "outputs": [], "source": [ "def factorize_nd_bins_core(by, bins):\n", " group_idx, *_, props = flox.core.factorize_(\n", " (by,),\n", " axes=(-1,),\n", " expected_groups=(pd.IntervalIndex.from_breaks(bins),),\n", " )\n", " # Use -1 as the NaN sentinel value\n", " group_idx[props.nanmask] = -1\n", " return group_idx\n", "\n", "\n", "codes = xr.apply_ufunc(\n", " factorize_nd_bins_core,\n", " by,\n", " bins,\n", " # TODO: avoid hardcoded dim names\n", " input_core_dims=[[\"space\"], [\"nbins\"]],\n", " output_core_dims=[[\"space\"]],\n", " vectorize=True,\n", ")\n", "codes" ] }, { "cell_type": "markdown", "id": "12", "metadata": { "user_expressions": [] }, "source": [ "### Offset the codes\n", "\n", "These are integer codes appropriate for a single timestep.\n", "\n", "We now add an offset that changes in time, to make sure \"bin 0\" for `time=0` is\n", "different from \"bin 0\" for `time=1` (taken from\n", "[this StackOverflow thread](https://stackoverflow.com/questions/46256279/bin-elements-per-row-vectorized-2d-bincount-for-numpy)).\n" ] }, { "cell_type": "code", "execution_count": null, "id": "13", "metadata": { "tags": [] }, "outputs": [], "source": [ "N = math.prod([codes.sizes[d] for d in codes.dims if d != factorize_loop_dim])\n", "offset = xr.DataArray(np.arange(codes.sizes[factorize_loop_dim]), dims=factorize_loop_dim)\n", "# TODO: think about N-1 here\n", "offset_codes = (codes + offset * (N - 1)).rename(by.name)\n", "offset_codes.data[codes == -1] = -1\n", "offset_codes" ] }, { "cell_type": "markdown", "id": "14", "metadata": { "user_expressions": [] }, "source": [ "### Reduce\n", "\n", "Now that we have appropriate codes, let's apply the reduction\n" ] }, { "cell_type": "code", "execution_count": null, "id": "15", "metadata": { "tags": [] }, "outputs": [], "source": [ "interim = flox.xarray.xarray_reduce(\n", " array,\n", " offset_codes,\n", " func=\"sum\",\n", " # We use RangeIndex to indicate that `-1` code can be safely ignored\n", " # (it indicates values outside the bins)\n", " # TODO: Avoid hardcoding 9 = sizes[\"time\"] x (sizes[\"nbins\"] - 1)\n", " expected_groups=pd.RangeIndex(9),\n", ")\n", "interim" ] }, { "cell_type": "markdown", "id": "16", "metadata": { "user_expressions": [] }, "source": [ "## Make final result\n", "\n", "Now reshape that 1D result appropriately.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "17", "metadata": { "tags": [] }, "outputs": [], "source": [ "final = (\n", " interim.coarsen(by=3)\n", " # bin_number dimension is last, this makes sense since it is the core dimension\n", " # and we vectorize over the loop dims.\n", " # So the first (Nbins-1) elements are for the first index of the loop dim\n", " .construct({\"by\": (factorize_loop_dim, \"bin_number\")})\n", " .transpose(..., factorize_loop_dim)\n", " .drop_vars(\"by\")\n", ")\n", "final" ] }, { "cell_type": "markdown", "id": "18", "metadata": { "user_expressions": [] }, "source": [ "I think this is the expected answer.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "19", "metadata": { "tags": [] }, "outputs": [], "source": [ "array.isel(space=slice(1, None)).rename({\"space\": \"bin_number\"}).identical(final)" ] }, { "cell_type": "markdown", "id": "20", "metadata": { "tags": [], "user_expressions": [] }, "source": [ "## TODO\n", "\n", "This could be extended to:\n", "\n", "1. handle multiple `factorize_loop_dim`\n", "2. avoid hard coded dimension names in the `apply_ufunc` call for factorizing\n", "3. avoid hard coded number of output elements in the `xarray_reduce` call.\n", "4. Somehow propagate the bin edges to the final output.\n" ] } ], "metadata": { "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 5 } flox-0.11.2/docs/source/user-stories/overlaps.md000066400000000000000000000044641514775624000216370ustar00rootroot00000000000000--- jupytext: text_representation: format_name: myst kernelspec: display_name: Python 3 name: python3 --- ```{eval-rst} .. currentmodule:: flox ``` # Overlapping Groups This post is motivated by the problem of computing the [Meridional Overturning Circulation](https://en.wikipedia.org/wiki/Atlantic_meridional_overturning_circulation). One of the steps is a binned average over latitude, over regions of the World Ocean. Commonly we want to average globally, as well as over the Atlantic, and the Indo-Pacific. Generally group-by problems involve non-overlapping groups. In this example, the "global" group overlaps with the "Indo-Pacific" and "Atlantic" groups. Below we consider a simplified version of this problem. Consider the following labels: ```{code-cell} import numpy as np import xarray as xr from flox.xarray import xarray_reduce labels = xr.DataArray( [1, 2, 3, 1, 2, 3, 0, 0, 0], dims="x", name="label", ) labels ``` These labels are non-overlapping. So when we reduce this data array over those labels along `x` ```{code-cell} da = xr.ones_like(labels) da ``` we get (note the reduction over `x` is implicit here): ```{code-cell} xarray_reduce(da, labels, func="sum") ``` Now let's _also_ calculate the `sum` where `labels` is either `1` or `2`. We could easily compute this using the grouped result but here we use this simple example for illustration. The trick is to add a new dimension with new labels (here `4`) in the appropriate locations. ```{code-cell} # assign 4 where label == 1 or 2, and -1 otherwise newlabels = xr.where(labels.isin([1, 2]), 4, -1) # concatenate along a new dimension y; # note y is not present on da expanded = xr.concat([labels, newlabels], dim="y") expanded ``` Now we reduce over `x` _and_ the new dimension `y` (again implicitly) to get the appropriate sum under `label=4` (and `label=-1`). We can discard the value accumulated under `label=-1` later. ```{code-cell} xarray_reduce(da, expanded, func="sum") ``` This way we compute all the reductions we need, in a single pass over the data. This technique generalizes to more complicated aggregations. The trick is to - generate appropriate labels - concatenate these new labels along a new dimension (`y`) absent on the object being reduced (`da`), and - reduce over that new dimension in addition to any others. flox-0.11.2/docs/source/xarray.md000066400000000000000000000011441514775624000166360ustar00rootroot00000000000000(xarray)= # Xarray Xarray will use flox by default (if installed) for DataArrays containing numpy and dask arrays. The default choice is `method="cohorts"` which generalizes the best. Pass flox-specific kwargs to the specific reduction method: ```python ds.groupby("time.month").mean(method="map-reduce", engine="flox") ds.groupby_bins("lon", bins=[0, 10, 20]).mean(method="map-reduce") ds.resample(time="ME").mean(method="blockwise") ``` {py:func}`flox.xarray.xarray_reduce` used to provide extra functionality, but now Xarray's GroupBy object has been upgraded to match those capabilities with better API! flox-0.11.2/flox/000077500000000000000000000000001514775624000135265ustar00rootroot00000000000000flox-0.11.2/flox/__init__.py000066400000000000000000000014741514775624000156450ustar00rootroot00000000000000#!/usr/bin/env python # flake8: noqa """Top-level module for flox .""" from . import cache, cohorts, factorize from .aggregations import Aggregation, Scan, is_supported_aggregation from .core import groupby_reduce from .options import set_options from .rechunk import rechunk_for_blockwise, rechunk_for_cohorts from .reindex import ReindexArrayType, ReindexStrategy from .scan import groupby_scan def _get_version(): __version__ = "999" try: from ._version import __version__ except ImportError: pass return __version__ __version__ = _get_version() __all__ = [ "Aggregation", "Scan", "groupby_reduce", "groupby_scan", "rechunk_for_blockwise", "rechunk_for_cohorts", "set_options", "ReindexStrategy", "ReindexArrayType", "is_supported_aggregation", ] flox-0.11.2/flox/aggregate_flox.py000066400000000000000000000276721514775624000170740ustar00rootroot00000000000000from functools import partial import numpy as np from . import xrdtypes as dtypes from .xrutils import is_scalar, isnull, notnull def _prepare_for_flox(group_idx, array): """ Sort the input array once to save time. """ assert array.shape[-1] == group_idx.shape[0] issorted = (group_idx[:-1] <= group_idx[1:]).all() if issorted: ordered_array = array perm = slice(None) else: perm = group_idx.argsort(kind="stable") group_idx = group_idx[..., perm] ordered_array = array[..., perm] return group_idx, ordered_array, perm def _lerp(a, b, *, t, dtype, out=None): """ COPIED from numpy. Compute the linear interpolation weighted by gamma on each point of two same shape array. a : array_like Left bound. b : array_like Right bound. t : array_like The interpolation weight. """ if out is None: out = np.empty_like(a, dtype=dtype) with np.errstate(invalid="ignore"): diff_b_a = np.subtract(b, a) # asanyarray is a stop-gap until gh-13105 np.add(a, diff_b_a * t, out=out) np.subtract(b, diff_b_a * (1 - t), out=out, where=t >= 0.5) return out def quantile_(array, inv_idx, *, q, axis, skipna, group_idx, dtype=None, out=None): inv_idx = np.concatenate((inv_idx, [array.shape[-1]])) array_validmask = notnull(array) actual_sizes = np.add.reduceat(array_validmask, inv_idx[:-1], axis=axis) newshape = (1,) * (array.ndim - 1) + (inv_idx.size - 1,) full_sizes = np.reshape(np.diff(inv_idx), newshape) nanmask = full_sizes != actual_sizes # The approach here is to use (complex_array.partition) because # 1. The full np.lexsort((array, labels), axis=-1) is slow and unnecessary # 2. Using record_array.partition(..., order=["labels", "array"]) is incredibly slow. # 3. For complex arrays, partition will first sort by real part, then by imaginary part, so it is a two element # lex-partition. # Therefore we use approach (3) and set # complex_array = group_idx + 1j * array # group_idx is an integer (guaranteed), but array can have NaNs. # Now the sort order of np.nan is bigger than np.inf # >>> c = (np.array([0, 1, 2, np.nan]) + np.array([np.nan, 2, 3, 4]) * 1j) # >>> c.partition(2) # >>> c # array([ 1. +2.j, 2. +3.j, nan +4.j, nan+nanj]) # So we determine which indices we need using the fact that NaNs get sorted to the end. # This *was* partly inspired by https://krstn.eu/np.nanpercentile()-there-has-to-be-a-faster-way/ # but not any more now that I use partition and avoid replacing NaNs qin = q q = np.atleast_1d(qin) q = np.reshape(q, (len(q),) + (1,) * array.ndim) # This is numpy's method="linear" # TODO: could support all the interpolations here offset = actual_sizes.cumsum(axis=-1) actual_sizes -= 1 virtual_index = q * actual_sizes # virtual_index is relative to group starts, so now offset that virtual_index[..., 1:] += offset[..., :-1] is_scalar_q = is_scalar(qin) if is_scalar_q: virtual_index = virtual_index.squeeze(axis=0) idxshape = array.shape[:-1] + (actual_sizes.shape[-1],) else: idxshape = (q.shape[0],) + array.shape[:-1] + (actual_sizes.shape[-1],) lo_ = np.floor( virtual_index, casting="unsafe", out=np.empty(virtual_index.shape, dtype=np.int64), ) hi_ = np.ceil( virtual_index, casting="unsafe", out=np.empty(virtual_index.shape, dtype=np.int64), ) kth = np.unique(np.concatenate([lo_.reshape(-1), hi_.reshape(-1)])) # partition the complex array in-place labels_broadcast = np.broadcast_to(group_idx, array.shape) with np.errstate(invalid="ignore"): cmplx = 1j * (array.view(int) if array.dtype.kind in "Mm" else array) # This is a very intentional way of handling `array` with -inf/+inf values :/ # a simple (labels + 1j * array) will yield `nan+inf * 1j` instead of `0 + inf * j` cmplx.real = labels_broadcast cmplx.partition(kth=kth, axis=-1) if is_scalar_q: a_ = cmplx.imag else: a_ = np.broadcast_to(cmplx.imag, (q.shape[0],) + array.shape) # get bounds, Broadcast to (num quantiles, ..., num labels) loval = np.take_along_axis(a_, np.broadcast_to(lo_, idxshape), axis=axis) hival = np.take_along_axis(a_, np.broadcast_to(hi_, idxshape), axis=axis) # TODO: could support all the interpolations here gamma = np.broadcast_to(virtual_index, idxshape) - lo_ result = _lerp(loval, hival, t=gamma, out=out, dtype=dtype) tomask = actual_sizes == -1 if not skipna and np.any(nanmask): tomask |= nanmask result[..., tomask] = np.nan return result def _np_grouped_op( group_idx, array, op, axis=-1, size=None, fill_value=None, dtype=None, out=None, **kwargs, ): """ most of this code is from shoyer's gist https://gist.github.com/shoyer/f538ac78ae904c936844 """ # assumes input is sorted, which I do in core._prepare_for_flox aux = group_idx # Handle empty group_idx case if aux.size == 0: if size is None: size = 0 if dtype is None: dtype = array.dtype q = kwargs.get("q", None) if q is None: return np.full(array.shape[:-1] + (size,), fill_value=fill_value, dtype=dtype) else: nq = len(np.atleast_1d(q)) return np.full((nq,) + array.shape[:-1] + (size,), fill_value=fill_value, dtype=dtype) flag = np.concatenate((np.asarray([True], like=aux), aux[1:] != aux[:-1])) uniques = aux[flag] (inv_idx,) = flag.nonzero() if size is None: # This is sorted, so the last value is the largest label size = uniques[-1] + 1 if dtype is None: dtype = array.dtype if out is None: q = kwargs.get("q", None) if q is None: out = np.full(array.shape[:-1] + (size,), fill_value=fill_value, dtype=dtype) else: nq = len(np.atleast_1d(q)) out = np.full((nq,) + array.shape[:-1] + (size,), fill_value=fill_value, dtype=dtype) kwargs["group_idx"] = group_idx if (len(uniques) == size) and (uniques == np.arange(size, like=aux)).all(): # The previous version of this if condition # ((uniques[1:] - uniques[:-1]) == 1).all(): # does not work when group_idx is [1, 2] for e.g. # This happens during binning op(array, inv_idx, axis=axis, dtype=dtype, out=out, **kwargs) else: out[..., uniques] = op(array, inv_idx, axis=axis, dtype=dtype, **kwargs) return out def _nan_grouped_op(group_idx, array, func, fillna, *args, **kwargs): if fillna in [dtypes.INF, dtypes.NINF]: fillna = dtypes._get_fill_value(kwargs.get("dtype", None) or array.dtype, fillna) result = func(group_idx, np.where(isnull(array), fillna, array), *args, **kwargs) # np.nanmax([np.nan, np.nan]) = np.nan # To recover this behaviour, we need to search for the fillna value # (either np.inf or -np.inf), and replace with NaN # Our choice of fillna does the right thing for sum, prod if fillna in (np.inf, -np.inf): allnangroups = result == fillna if allnangroups.any(): result[allnangroups] = kwargs["fill_value"] return result sum = partial(_np_grouped_op, op=np.add.reduceat) nansum = partial(_nan_grouped_op, func=sum, fillna=0) prod = partial(_np_grouped_op, op=np.multiply.reduceat) nanprod = partial(_nan_grouped_op, func=prod, fillna=1) max = partial(_np_grouped_op, op=np.maximum.reduceat) nanmax = partial(_nan_grouped_op, func=max, fillna=dtypes.NINF) min = partial(_np_grouped_op, op=np.minimum.reduceat) nanmin = partial(_nan_grouped_op, func=min, fillna=dtypes.INF) quantile = partial(_np_grouped_op, op=partial(quantile_, skipna=False)) nanquantile = partial(_np_grouped_op, op=partial(quantile_, skipna=True)) median = partial(partial(_np_grouped_op, q=0.5), op=partial(quantile_, skipna=False)) nanmedian = partial(partial(_np_grouped_op, q=0.5), op=partial(quantile_, skipna=True)) # TODO: all, any def sum_of_squares(group_idx, array, *, axis=-1, size=None, fill_value=None, dtype=None): return sum( group_idx, array**2, axis=axis, size=size, fill_value=fill_value, dtype=dtype, ) def nansum_of_squares(group_idx, array, *, axis=-1, size=None, fill_value=None, dtype=None): return sum_of_squares( group_idx, np.where(isnull(array), 0, array), size=size, fill_value=fill_value, axis=axis, dtype=dtype, ) def nanlen(group_idx, array, *args, **kwargs): return sum(group_idx, (notnull(array)).astype(int), *args, **kwargs) def mean(group_idx, array, *, axis=-1, size=None, fill_value=None, dtype=None): if fill_value is None: fill_value = 0 out = sum(group_idx, array, axis=axis, size=size, dtype=dtype, fill_value=fill_value) with np.errstate(invalid="ignore", divide="ignore"): out /= nanlen(group_idx, array, size=size, axis=axis, fill_value=0) return out def nanmean(group_idx, array, *, axis=-1, size=None, fill_value=None, dtype=None): if fill_value is None: fill_value = 0 out = nansum(group_idx, array, size=size, axis=axis, dtype=dtype, fill_value=fill_value) with np.errstate(invalid="ignore", divide="ignore"): out /= nanlen(group_idx, array, size=size, axis=axis, fill_value=0) return out def ffill(group_idx, array, *, axis, **kwargs): group_idx, array, perm = _prepare_for_flox(group_idx, array) shape = array.shape ndim = array.ndim assert axis == (ndim - 1), (axis, ndim - 1) flag = np.concatenate((np.asarray([True], like=group_idx), group_idx[1:] != group_idx[:-1])) (group_starts,) = flag.nonzero() # https://stackoverflow.com/questions/41190852/most-efficient-way-to-forward-fill-nan-values-in-numpy-array mask = isnull(array).copy() # copy needed since we might have a broadcast-trick array # modified from the SO answer, just reset the index at the start of every group! mask[..., np.asarray(group_starts)] = False idx = np.where(mask, 0, np.arange(shape[axis])) np.maximum.accumulate(idx, axis=axis, out=idx) slc = [ np.arange(k)[tuple([slice(None) if dim == i else np.newaxis for dim in range(ndim)])] for i, k in enumerate(shape) ] slc[axis] = idx invert_perm = slice(None) if isinstance(perm, slice) else np.argsort(perm, kind="stable") return array[tuple(slc)][..., invert_perm] def _np_grouped_scan(group_idx, array, *, axis: int, skipna: bool, **kwargs): handle_nans = not skipna and array.dtype.kind in "cfO" group_idx, array, perm = _prepare_for_flox(group_idx, array) ndim = array.ndim assert axis == (ndim - 1), (axis, ndim - 1) flag = np.concatenate((np.asarray([True], like=group_idx), group_idx[1:] != group_idx[:-1])) (inv_idx,) = flag.nonzero() segment_lengths = np.add.reduceat(np.ones(group_idx.shape), inv_idx, dtype=np.int64) # TODO: set dtype to float properly for handle_nans? accum = np.nancumsum(array, axis=axis) if len(inv_idx) > 1: first_group_idx = inv_idx[1] # extract cumulative sum _before_ start of group prev_group_cumsum = accum[..., inv_idx[1:] - 1] accum[..., first_group_idx:] -= np.repeat(prev_group_cumsum, segment_lengths[1:], axis=axis) if handle_nans: mask = isnull(array) accummask = np.cumsum(mask, axis=-1, dtype=np.uint64) if len(inv_idx) > 1: prev_group_cumsum = accummask[..., inv_idx[1:] - 1] accummask[..., first_group_idx:] -= np.repeat(prev_group_cumsum, segment_lengths[1:], axis=axis) accum[accummask > 0] = np.nan invert_perm = slice(None) if isinstance(perm, slice) else np.argsort(perm, kind="stable") return accum[..., invert_perm] cumsum = partial(_np_grouped_scan, skipna=False) nancumsum = partial(_np_grouped_scan, skipna=True) flox-0.11.2/flox/aggregate_npg.py000066400000000000000000000134361514775624000167010ustar00rootroot00000000000000from functools import partial import numpy as np import numpy_groupies as npg def _get_aggregate(engine): return npg.aggregate_numpy if engine == "numpy" else npg.aggregate_numba def _casting_wrapper(func, grp, dtype): """Used for generic aggregates. The group is dtype=object, need to cast back to fix weird bugs""" return func(grp.astype(dtype)) def sum_of_squares( group_idx, array, engine, *, axis=-1, size=None, fill_value=None, dtype=None, ): return _get_aggregate(engine).aggregate( group_idx, array, axis=axis, func="sumofsquares", size=size, fill_value=fill_value, dtype=dtype, ) def nansum_of_squares( group_idx, array, engine, *, axis=-1, size=None, fill_value=None, dtype=None, ): return _get_aggregate(engine).aggregate( group_idx, array, axis=axis, func="nansumofsquares", size=size, fill_value=fill_value, dtype=dtype, ) def nansum(group_idx, array, engine, *, axis=-1, size=None, fill_value=None, dtype=None): # npg takes out NaNs before calling np.bincount # This means that all NaN groups are equivalent to absent groups # This behaviour does not work for xarray return _get_aggregate(engine).aggregate( group_idx, np.where(np.isnan(array), 0, array), axis=axis, func="sum", size=size, fill_value=fill_value, dtype=dtype, ) def nanprod(group_idx, array, engine, *, axis=-1, size=None, fill_value=None, dtype=None): # npg takes out NaNs before calling np.bincount # This means that all NaN groups are equivalent to absent groups # This behaviour does not work for xarray return _get_aggregate(engine).aggregate( group_idx, np.where(np.isnan(array), 1, array), axis=axis, func="prod", size=size, fill_value=fill_value, dtype=dtype, ) def _len(group_idx, array, engine, *, func, axis=-1, size=None, fill_value=None, dtype=None): if array.dtype.kind in "US": array = np.broadcast_to(np.array([1]), array.shape) result = _get_aggregate(engine).aggregate( group_idx, array, axis=axis, func=func, size=size, fill_value=0, dtype=np.int64, ) if fill_value is not None: result = result.astype(np.array([fill_value]).dtype) result[result == 0] = fill_value return result len = partial(_len, func="len") nanlen = partial(_len, func="nanlen") def _var_std_wrapper(group_idx, array, engine, *, axis=-1, **kwargs): # Attempt to increase numerical stability by subtracting the first element. # https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance # Cast any unsigned types first dtype = np.result_type(array, np.int8(-1) * array[0]) array = array.astype(dtype, copy=False) first = _get_aggregate(engine).aggregate(group_idx, array, func="nanfirst", axis=axis) array = array - first[..., group_idx] return _get_aggregate(engine).aggregate(group_idx, array, axis=axis, **kwargs) var = partial(_var_std_wrapper, func="var") nanvar = partial(_var_std_wrapper, func="nanvar") std = partial(_var_std_wrapper, func="std") nanstd = partial(_var_std_wrapper, func="nanstd") def median(group_idx, array, engine, *, axis=-1, size=None, fill_value=None, dtype=None): return npg.aggregate_numpy.aggregate( group_idx, array, func=partial(_casting_wrapper, np.median, dtype=np.result_type(array.dtype)), axis=axis, size=size, fill_value=fill_value, dtype=dtype, ) def nanmedian(group_idx, array, engine, *, axis=-1, size=None, fill_value=None, dtype=None): return npg.aggregate_numpy.aggregate( group_idx, array, func=partial(_casting_wrapper, np.nanmedian, dtype=np.result_type(array.dtype)), axis=axis, size=size, fill_value=fill_value, dtype=dtype, ) def quantile(group_idx, array, engine, *, q, axis=-1, size=None, fill_value=None, dtype=None): return npg.aggregate_numpy.aggregate( group_idx, array, func=partial( _casting_wrapper, partial(np.quantile, q=q), dtype=np.result_type(dtype, array.dtype), ), axis=axis, size=size, fill_value=fill_value, dtype=dtype, ) def nanquantile(group_idx, array, engine, *, q, axis=-1, size=None, fill_value=None, dtype=None): return npg.aggregate_numpy.aggregate( group_idx, array, func=partial( _casting_wrapper, partial(np.nanquantile, q=q), dtype=np.result_type(dtype, array.dtype), ), axis=axis, size=size, fill_value=fill_value, dtype=dtype, ) def mode_(array, nan_policy, dtype): from scipy.stats import mode # npg splits `array` into object arrays for each group # scipy.stats.mode does not like that # here we cast back return mode(array.astype(dtype, copy=False), nan_policy=nan_policy, axis=-1, keepdims=True).mode def mode(group_idx, array, engine, *, axis=-1, size=None, fill_value=None, dtype=None): return npg.aggregate_numpy.aggregate( group_idx, array, func=partial(mode_, nan_policy="propagate", dtype=array.dtype), axis=axis, size=size, fill_value=fill_value, dtype=dtype, ) def nanmode(group_idx, array, engine, *, axis=-1, size=None, fill_value=None, dtype=None): return npg.aggregate_numpy.aggregate( group_idx, array, func=partial(mode_, nan_policy="omit", dtype=array.dtype), axis=axis, size=size, fill_value=fill_value, dtype=dtype, ) flox-0.11.2/flox/aggregate_numbagg.py000066400000000000000000000071561514775624000175370ustar00rootroot00000000000000from functools import partial import numbagg import numbagg.grouped import numpy as np from packaging.version import Version NUMBAGG_SUPPORTS_DDOF = Version(numbagg.__version__) >= Version("0.7.0") DEFAULT_FILL_VALUE = { "nansum": 0, "nanmean": np.nan, "nanvar": np.nan, "nanstd": np.nan, "nanmin": np.nan, "nanmax": np.nan, "nanany": False, "nanall": False, "nansum_of_squares": 0, "nanprod": 1, "nancount": 0, "nanargmax": np.nan, "nanargmin": np.nan, "nanfirst": np.nan, "nanlast": np.nan, } CAST_TO = { # "nansum": {np.bool_: np.int64}, "nanmean": {np.int_: np.float64}, "nanvar": {np.int_: np.float64}, "nanstd": {np.int_: np.float64}, "nanfirst": {np.datetime64: np.int64, np.timedelta64: np.int64}, "nanlast": {np.datetime64: np.int64, np.timedelta64: np.int64}, "nancount": {np.datetime64: np.int64, np.timedelta64: np.int64}, } FILLNA = {"nansum": 0, "nanprod": 1} def _numbagg_wrapper( group_idx, array, *, func, axis=-1, size=None, fill_value=None, dtype=None, **kwargs, ): cast_to = CAST_TO.get(func, None) if cast_to: for from_, to_ in cast_to.items(): if np.issubdtype(array.dtype, from_): array = array.astype(to_, copy=False) func_ = getattr(numbagg.grouped, f"group_{func}") result = func_( array, group_idx, axis=axis, num_labels=size, **kwargs, # The following are unsupported # fill_value=fill_value, # dtype=dtype, ).astype(dtype, copy=False) return result def nanvar(group_idx, array, *, axis=-1, size=None, fill_value=None, dtype=None, ddof=0): kwargs = {} if NUMBAGG_SUPPORTS_DDOF: kwargs["ddof"] = ddof elif ddof != 1: raise ValueError("Need numbagg >= v0.7.0 to support ddof != 1") return _numbagg_wrapper( group_idx, array, axis=axis, size=size, func="nanvar", **kwargs, # fill_value=fill_value, # dtype=dtype, ) def nanstd(group_idx, array, *, axis=-1, size=None, fill_value=None, dtype=None, ddof=0): kwargs = {} if NUMBAGG_SUPPORTS_DDOF: kwargs["ddof"] = ddof elif ddof != 1: raise ValueError("Need numbagg >= v0.7.0 to support ddof != 1") return _numbagg_wrapper( group_idx, array, axis=axis, size=size, func="nanstd", **kwargs, # fill_value=fill_value, # dtype=dtype, ) def nanlen(group_idx, array, *, axis=-1, size=None, fill_value=None, dtype=None): if array.dtype.kind in "US": array = np.broadcast_to(np.array([1]), array.shape) return _numbagg_wrapper( group_idx, array, axis=axis, size=size, func="nancount", # fill_value=fill_value, # dtype=dtype, ) nansum = partial(_numbagg_wrapper, func="nansum") nanmean = partial(_numbagg_wrapper, func="nanmean") nanprod = partial(_numbagg_wrapper, func="nanprod") nansum_of_squares = partial(_numbagg_wrapper, func="nansum_of_squares") nanprod = partial(_numbagg_wrapper, func="nanprod") nanfirst = partial(_numbagg_wrapper, func="nanfirst") nanlast = partial(_numbagg_wrapper, func="nanlast") # nanargmax = partial(_numbagg_wrapper, func="nanargmax) # nanargmin = partial(_numbagg_wrapper, func="nanargmin) nanmax = partial(_numbagg_wrapper, func="nanmax") nanmin = partial(_numbagg_wrapper, func="nanmin") any = partial(_numbagg_wrapper, func="nanany") all = partial(_numbagg_wrapper, func="nanall") # sum = nansum # mean = nanmean # sum_of_squares = nansum_of_squares flox-0.11.2/flox/aggregate_sparse.py000066400000000000000000000136011514775624000174040ustar00rootroot00000000000000# Unlike the other aggregate_* submodules, this one simply defines a wrapper function # because we run the groupby on the underlying dense data. from collections.abc import Callable from functools import partial from typing import Any, TypeAlias import numpy as np import sparse from flox.factorize import _factorize_multiple, factorize_ from flox.lib import _is_sparse_supported_reduction from flox.xrdtypes import INF, NINF, _get_fill_value from flox.xrutils import notnull def nanadd(a, b): """ Annoyingly, there is no numpy ufunc for nan-skipping elementwise addition unlike np.fmin, np.fmax :( From https://stackoverflow.com/a/50642947/1707127 """ ab = a + b return np.where(np.isnan(ab), np.where(np.isnan(a), b, a), ab) CallableMap: TypeAlias = dict[str, Callable[[np.ndarray, np.ndarray], np.ndarray]] BINARY_OPS: CallableMap = { "sum": np.add, "nansum": nanadd, "max": np.maximum, "nanmax": np.fmax, "min": np.minimum, "nanmin": np.fmin, } HYPER_OPS: CallableMap = {"sum": np.multiply, "nansum": np.multiply} IDENTITY: dict[str, Any] = { "sum": 0, "nansum": 0, "prod": 1, "nanprod": 1, "max": NINF, "nanmax": NINF, "min": INF, "nanmin": INF, } def _sparse_agg( group_idx: np.ndarray, array: sparse.COO, func: str, engine: str, axis: int = -1, size: int | None = None, fill_value=None, dtype=None, **kwargs, ): """Wrapper function, that unwraps the underlying dense arrays, executes the groupby, and constructs the output sparse array.""" from flox.aggregations import generic_aggregate if not isinstance(array, sparse.COO): raise ValueError("Sparse aggregations only supported for sparse.COO arrays") if not _is_sparse_supported_reduction(func): raise ValueError(f"{func} is unsupported for sparse arrays.") group_idx_subset = group_idx[array.coords[axis, :]] if array.ndim > 1: new_by = tuple(array.coords[:axis, :]) + (group_idx_subset,) else: new_by = (group_idx_subset,) codes, groups, shape = _factorize_multiple( new_by, expected_groups=(None,) * len(new_by), any_by_dask=False ) # factorize again so we can construct a sparse result sparse_codes, sparse_groups, sparse_shape, _, sparse_size, _ = factorize_(codes, axes=(0,)) dense_result = generic_aggregate( sparse_codes, array.data, func=func, engine=engine, dtype=dtype, size=sparse_size, fill_value=fill_value, ) dense_counts = generic_aggregate( sparse_codes, array.data, # This counts is used to handle fill_value, so we need a count # of populated data, regardless of NaN value func="len", engine=engine, dtype=int, size=sparse_size, fill_value=0, ) assert len(sparse_groups) == 1 result_coords = np.stack(tuple(g[i] for g, i in zip(groups, np.unravel_index(*sparse_groups, shape)))) full_shape = array.shape[:-1] + (size,) count = sparse.COO(coords=result_coords, data=dense_counts, shape=full_shape, fill_value=0) assert axis in (-1, array.ndim - 1) grouped_count = generic_aggregate( group_idx, group_idx, engine=engine, func="len", dtype=np.int64, size=size, fill_value=0 ) total_count = sparse.COO.from_numpy( np.expand_dims(grouped_count, tuple(range(array.ndim - 1))), fill_value=0 ) assert func in BINARY_OPS binop = BINARY_OPS[func] ident = _get_fill_value(array.dtype, IDENTITY[func]) diff_count = total_count - count if (hyper_op := HYPER_OPS.get(func, None)) is not None: fill = hyper_op(diff_count, array.fill_value) if (diff_count > 0).any() else ident else: if "max" in func or "min" in func: # Note that fill_value for total_count, and count is 0. # So the fill_value for the `fill` result is the False branch i.e. `ident` fill = np.where(diff_count > 0, array.fill_value, ident) else: raise NotImplementedError result = sparse.COO(coords=result_coords, data=dense_result, shape=full_shape, fill_value=ident) with_fill = binop(result, fill) return with_fill def nanlen( group_idx: np.ndarray, array: sparse.COO, engine: str, axis: int = -1, size: int | None = None, fill_value=None, dtype=None, **kwargs, ): new_array = sparse.COO( coords=array.coords, data=notnull(array.data), shape=array.shape, fill_value=notnull(array.fill_value), ) return _sparse_agg( group_idx, new_array, func="sum", engine=engine, axis=axis, size=size, fill_value=0, dtype=dtype ) def mean( group_idx: np.ndarray, array: sparse.COO, engine: str, axis: int = -1, size: int | None = None, fill_value=None, dtype=None, **kwargs, ): sums = sum( group_idx, array, func="sum", engine=engine, axis=axis, size=size, fill_value=fill_value, dtype=dtype ) counts = nanlen( group_idx, array, func="sum", engine=engine, axis=axis, size=size, fill_value=0, dtype=dtype ) return sums / counts def nanmean( group_idx: np.ndarray, array: sparse.COO, engine: str, axis: int = -1, size: int | None = None, fill_value=None, dtype=None, **kwargs, ): sums = sum( group_idx, array, func="nansum", engine=engine, axis=axis, size=size, fill_value=fill_value, dtype=dtype, ) counts = nanlen( group_idx, array, func="sum", engine=engine, axis=axis, size=size, fill_value=0, dtype=dtype ) return sums / counts sum = partial(_sparse_agg, func="sum") nansum = partial(_sparse_agg, func="nansum") max = partial(_sparse_agg, func="max") nanmax = partial(_sparse_agg, func="nanmax") min = partial(_sparse_agg, func="min") nanmin = partial(_sparse_agg, func="nanmin") flox-0.11.2/flox/aggregations.py000066400000000000000000001023551514775624000165600ustar00rootroot00000000000000from __future__ import annotations import copy import logging import warnings from collections.abc import Callable, Sequence from dataclasses import dataclass from functools import cached_property, partial from typing import TYPE_CHECKING, Any, Literal, TypedDict import numpy as np import pandas as pd from numpy.typing import ArrayLike, DTypeLike from . import aggregate_flox, aggregate_npg, xrutils from . import xrdtypes as dtypes from .lib import dask_array_type, sparse_array_type from .multiarray import MultiArray from .xrutils import notnull if TYPE_CHECKING: FuncTuple = tuple[Callable | str, ...] OptionalFuncTuple = tuple[Callable | str | None, ...] logger = logging.getLogger("flox") T_ScanBinaryOpMode = Literal["apply_binary_op", "concat_then_scan"] def _is_arg_reduction(func: str | Aggregation) -> bool: if isinstance(func, str) and func in ["argmin", "argmax", "nanargmax", "nanargmin"]: return True if isinstance(func, Aggregation) and func.reduction_type == "argreduce": return True return False class AggDtypeInit(TypedDict): final: DTypeLike | None intermediate: tuple[DTypeLike, ...] class AggDtype(TypedDict): user: DTypeLike | None final: np.dtype numpy: tuple[np.dtype | type[np.intp], ...] intermediate: tuple[np.dtype | type[np.intp], ...] def get_npg_aggregation(func, *, engine): try: method_ = getattr(aggregate_npg, func) method = partial(method_, engine=engine) except AttributeError: aggregate = aggregate_npg._get_aggregate(engine).aggregate method = partial(aggregate, func=func) return method def generic_aggregate( group_idx, array, *, engine: str, func: str, axis=-1, size=None, fill_value=None, dtype=None, **kwargs, ): if func == "identity": return array if func in ["nanfirst", "nanlast"] and array.dtype.kind in "US": func = func[3:] if is_sparse := isinstance(array, sparse_array_type): # this is not an infinite loop because aggregate_sparse will call # generic_aggregate with dense data from flox import aggregate_sparse method = partial(getattr(aggregate_sparse, func), engine=engine) elif engine == "flox": try: method = getattr(aggregate_flox, func) except AttributeError: # logger.debug(f"Couldn't find {func} for engine='flox'. Falling back to numpy") method = get_npg_aggregation(func, engine="numpy") elif engine == "numbagg": from . import aggregate_numbagg try: if "var" in func or "std" in func: ddof = kwargs.get("ddof", 0) if aggregate_numbagg.NUMBAGG_SUPPORTS_DDOF or (ddof != 0): method = getattr(aggregate_numbagg, func) else: logger.debug(f"numbagg too old for ddof={ddof}. Falling back to numpy") method = get_npg_aggregation(func, engine="numpy") else: method = getattr(aggregate_numbagg, func) except AttributeError: # logger.debug(f"Couldn't find {func} for engine='numbagg'. Falling back to numpy") method = get_npg_aggregation(func, engine="numpy") elif engine in ["numpy", "numba"]: method = get_npg_aggregation(func, engine=engine) else: raise ValueError( f"Expected engine to be one of ['flox', 'numpy', 'numba', 'numbagg']. Received {engine} instead." ) # UGLY! but this avoids auto-densification errors if not is_sparse: group_idx = np.asarray(group_idx, like=array) with warnings.catch_warnings(): warnings.filterwarnings("ignore", r"All-NaN (slice|axis) encountered") result = method( group_idx, array, axis=axis, size=size, fill_value=fill_value, dtype=dtype, **kwargs, ) return result def _atleast_1d(inp, min_length: int = 1): if xrutils.is_scalar(inp): inp = (inp,) * min_length assert len(inp) >= min_length return inp def returns_empty_tuple(*args, **kwargs): return () @dataclass class Dim: values: ArrayLike name: str | None @cached_property def is_scalar(self) -> bool: return xrutils.is_scalar(self.values) @cached_property def size(self) -> int: return 0 if self.is_scalar else len(self.values) # type: ignore[arg-type] class Aggregation: def __init__( self, name: str, *, numpy: partial | str | None = None, chunk: partial | str | FuncTuple | None, combine: str | FuncTuple | None, preprocess: Callable | None = None, finalize: Callable | None = None, fill_value=None, final_fill_value=dtypes.NA, dtypes=None, final_dtype: DTypeLike | None = None, reduction_type: Literal["reduce", "argreduce"] = "reduce", new_dims_func: Callable | None = None, preserves_dtype: bool = False, ): """ Blueprint for computing grouped aggregations. See aggregations.py for examples on how to specify reductions. Attributes ---------- name : str Name of reduction. numpy : str or callable, optional Reduction function applied to numpy inputs. This function should compute the grouped reduction and must have a specific signature. If string, these must be "native" reductions implemented by the backend engines (numpy_groupies, flox, numbagg). If None, will be set to ``name``. chunk : None or str or tuple of str or callable or tuple of callable For dask inputs only. Either a single function or a list of functions to be applied blockwise on the input dask array. If None, will raise an error for dask inputs. combine : None or str or tuple of str or callbe or tuple of callable For dask inputs only. Functions applied when combining intermediate results from the blockwise stage (see ``chunk``). If None, will raise an error for dask inputs. finalize : callable For dask inputs only. Function that combines intermediate results to compute final result. preprocess : callable For dask inputs only. Preprocess inputs before ``chunk`` stage. reduction_type : {"reduce", "argreduce"} Type of reduction. fill_value : number or tuple(number), optional Value to use when a group has no members. If single value will be converted to tuple of same length as chunk. If appropriate, provide a different fill_value per reduction in ``chunk`` as a tuple. final_fill_value : optional fill_value for final result. dtypes : DType or tuple(DType), optional dtypes for intermediate results. If single value, will be converted to a tuple of same length as chunk. If appropriate, provide a different fill_value per reduction in ``chunk`` as a tuple. final_dtype : DType, optional DType for output. By default, uses dtype of array being reduced. new_dims_func: Callable Function that receives finalize_kwargs and returns a tupleof sizes of any new dimensions added by the reduction. For e.g. quantile for q=(0.5, 0.85) adds a new dimension of size 2, so returns (2,) preserves_dtype: bool, Whether a function preserves the dtype on return E.g. min, max, first, last, mode """ self.name = name # preprocess before blockwise self.preprocess = preprocess # Use "chunk_reduce" or "chunk_argreduce" self.reduction_type = reduction_type self.numpy: FuncTuple = (numpy,) if numpy is not None else (self.name,) # initialize blockwise reduction self.chunk: OptionalFuncTuple = _atleast_1d(chunk) # how to aggregate results after first round of reduction self.combine: OptionalFuncTuple = _atleast_1d(combine) # simpler reductions used with the "simple combine" algorithm self.simple_combine: OptionalFuncTuple = () # finalize results (see mean) self.finalize: Callable | None = finalize self.fill_value = {} # This is used for the final reindexing self.fill_value[name] = final_fill_value # Aggregation.fill_value is used to reindex to group labels # at the *intermediate* step. # They should make sense when aggregated together with results from other blocks self.fill_value["intermediate"] = self._normalize_dtype_fill_value(fill_value, "fill_value") self.dtype_init: AggDtypeInit = { "final": final_dtype, "intermediate": self._normalize_dtype_fill_value(dtypes, "dtype"), } self.dtype: AggDtype = None # type: ignore[assignment] # The following are set by _initialize_aggregation self.finalize_kwargs: dict[Any, Any] = {} self.min_count: int = 0 self.new_dims_func: Callable = returns_empty_tuple if new_dims_func is None else new_dims_func self.preserves_dtype = preserves_dtype @cached_property def new_dims(self) -> tuple[Dim]: return self.new_dims_func(**self.finalize_kwargs) @cached_property def num_new_vector_dims(self) -> int: return len(tuple(dim for dim in self.new_dims if not dim.is_scalar)) def _normalize_dtype_fill_value(self, value, name): value = _atleast_1d(value) if len(value) == 1 and len(value) < len(self.chunk): value = value * len(self.chunk) if len(value) != len(self.chunk): raise ValueError(f"Bad {name} specified for Aggregation {name}.") return value def __dask_tokenize__(self): return ( Aggregation, self.name, self.preprocess, self.reduction_type, self.numpy, self.chunk, self.combine, self.finalize, self.fill_value, self.dtype, ) def __repr__(self) -> str: return "\n".join( ( f"{self.name!r}, fill: {self.fill_value.values()!r}, dtype: {self.dtype}", f"chunk: {self.chunk!r}", f"combine: {self.combine!r}", f"finalize: {self.finalize!r}", f"min_count: {self.min_count!r}", ) ) count = Aggregation( "count", numpy="nanlen", chunk="nanlen", combine="sum", fill_value=0, final_fill_value=0, dtypes=np.intp, final_dtype=np.intp, ) # note that the fill values are the result of np.func([np.nan, np.nan]) # final_fill_value is used for groups that don't exist. This is usually np.nan sum_ = Aggregation("sum", chunk="sum", combine="sum", fill_value=0) nansum = Aggregation("nansum", chunk="nansum", combine="sum", fill_value=0) prod = Aggregation("prod", chunk="prod", combine="prod", fill_value=1, final_fill_value=1) nanprod = Aggregation("nanprod", chunk="nanprod", combine="prod", fill_value=1) def _mean_finalize(sum_, count): with np.errstate(invalid="ignore", divide="ignore"): return sum_ / count mean = Aggregation( "mean", chunk=("sum", "nanlen"), combine=("sum", "sum"), finalize=_mean_finalize, fill_value=(0, 0), dtypes=(None, np.intp), final_dtype=np.floating, ) nanmean = Aggregation( "nanmean", chunk=("nansum", "nanlen"), combine=("sum", "sum"), finalize=_mean_finalize, fill_value=(0, 0), dtypes=(None, np.intp), final_dtype=np.floating, ) def var_chunk( group_idx, array, *, skipna: bool, engine: str, axis=-1, size=None, fill_value=None, dtype=None ): # Calculate length and sum - important for the adjustment terms to sum squared deviations array_lens = generic_aggregate( group_idx, array, func="nanlen", engine=engine, axis=axis, size=size, fill_value=0, # Unpack fill value bc it's currently defined for multiarray dtype=dtype, ) array_sums = generic_aggregate( group_idx, array, func="nansum" if skipna else "sum", engine=engine, axis=axis, size=size, fill_value=0, # Unpack fill value bc it's currently defined for multiarray dtype=dtype, ) # Calculate sum squared deviations - the main part of variance sum with np.errstate(invalid="ignore", divide="ignore"): array_means = array_sums / array_lens sum_squared_deviations = generic_aggregate( group_idx, (array - array_means[..., group_idx]) ** 2, func="nansum" if skipna else "sum", engine=engine, axis=axis, size=size, fill_value=0, # Unpack fill value bc it's currently defined for multiarray dtype=dtype, ) return MultiArray((sum_squared_deviations, array_sums, array_lens)) def _var_combine(array, axis, keepdims=True): def clip_last(array, ax, n=1): """Return array except the last element along axis Purely included to tidy up the adj_terms line """ assert n > 0, "Clipping nothing off the end isn't implemented" not_last = [slice(None, None) for i in range(array.ndim)] not_last[ax] = slice(None, -n) return array[*not_last] def clip_first(array, ax, n=1): """Return array except the first element along axis Purely included to tidy up the adj_terms line """ not_first = [slice(None, None) for i in range(array.ndim)] not_first[ax] = slice(n, None) return array[*not_first] for ax in axis: if array.shape[ax] == 1: continue sum_deviations, sum_X, sum_len = array.arrays # Calculate parts needed for cascading combination cumsum_X = np.cumsum(sum_X, axis=ax) cumsum_len = np.cumsum(sum_len, axis=ax) # There will be instances in which one or both chunks being merged are empty # In which case, the adjustment term should be zero, but will throw a divide-by-zero error # We're going to add a constant to the bottom of the adjustment term equation on those instances # and count on the zeros on the top making our adjustment term still zero zero_denominator = (clip_last(cumsum_len, ax) == 0) | (clip_first(sum_len, ax) == 0) # Adjustment terms to tweak the sum of squared deviations because not every chunk has the same mean with np.errstate(invalid="ignore", divide="ignore"): adj_terms = ( clip_last(cumsum_len, ax) * clip_first(sum_X, ax) - clip_first(sum_len, ax) * clip_last(cumsum_X, ax) ) ** 2 / ( clip_last(cumsum_len, ax) * clip_first(sum_len, ax) * (clip_last(cumsum_len, ax) + clip_first(sum_len, ax)) + zero_denominator.astype(int) ) check = adj_terms * zero_denominator assert np.all(check[notnull(check)] == 0), ( "Instances where we add something to the denominator must come out to zero" ) array = MultiArray( ( np.sum(sum_deviations, axis=ax, keepdims=keepdims) + np.sum(adj_terms, axis=ax, keepdims=keepdims), # sum of squared deviations np.sum(sum_X, axis=ax, keepdims=keepdims), # sum of array items np.sum(sum_len, axis=ax, keepdims=keepdims), # sum of array lengths ) ) return array def is_var_chunk_reduction(agg: Callable) -> bool: if isinstance(agg, partial): agg = agg.func return agg is blockwise_or_numpy_var or agg is var_chunk def _var_finalize(multiarray, ddof=0): den = multiarray.arrays[2] den -= ddof # preserve nans for groups with 0 obs; so these values are -ddof with np.errstate(invalid="ignore", divide="ignore"): ret = multiarray.arrays[0] ret /= den ret[den < 0] = np.nan return ret def _std_finalize(multiarray, ddof=0): return np.sqrt(_var_finalize(multiarray, ddof)) def blockwise_or_numpy_var(*args, skipna: bool, ddof=0, std=False, **kwargs): res = _var_finalize(var_chunk(*args, skipna=skipna, **kwargs), ddof) return np.sqrt(res) if std else res # var, std always promote to float, so we set nan var = Aggregation( "var", chunk=partial(var_chunk, skipna=False), numpy=partial(blockwise_or_numpy_var, skipna=False), combine=(_var_combine,), finalize=_var_finalize, fill_value=((0, 0, 0),), final_fill_value=np.nan, dtypes=(None,), final_dtype=np.floating, ) nanvar = Aggregation( "nanvar", chunk=partial(var_chunk, skipna=True), numpy=partial(blockwise_or_numpy_var, skipna=True), combine=(_var_combine,), finalize=_var_finalize, fill_value=((0, 0, 0),), final_fill_value=np.nan, dtypes=(None,), final_dtype=np.floating, ) std = Aggregation( "std", chunk=partial(var_chunk, skipna=False), numpy=partial(blockwise_or_numpy_var, skipna=False, std=True), combine=(_var_combine,), finalize=_std_finalize, fill_value=((0, 0, 0),), final_fill_value=np.nan, dtypes=(None,), final_dtype=np.floating, ) nanstd = Aggregation( "nanstd", chunk=partial(var_chunk, skipna=True), numpy=partial(blockwise_or_numpy_var, skipna=True, std=True), combine=(_var_combine,), finalize=_std_finalize, fill_value=((0, 0, 0),), final_fill_value=np.nan, dtypes=(None,), final_dtype=np.floating, ) min_ = Aggregation("min", chunk="min", combine="min", fill_value=dtypes.INF, preserves_dtype=True) nanmin = Aggregation( "nanmin", chunk="nanmin", combine="nanmin", fill_value=dtypes.INF, final_fill_value=dtypes.NA, preserves_dtype=True, ) max_ = Aggregation("max", chunk="max", combine="max", fill_value=dtypes.NINF, preserves_dtype=True) nanmax = Aggregation( "nanmax", chunk="nanmax", combine="nanmax", fill_value=dtypes.NINF, final_fill_value=dtypes.NA, preserves_dtype=True, ) def argreduce_preprocess(array, axis): """Returns a tuple of array, index along axis. Copied from dask.array.chunk.argtopk_preprocess """ import dask.array import numpy as np # TODO: arg reductions along multiple axes seems weird. assert len(axis) == 1 axis = axis[0] idx = dask.array.arange(array.shape[axis], chunks=array.chunks[axis], dtype=np.intp) # broadcast (TODO: is this needed?) idx = idx[tuple(slice(None) if i == axis else np.newaxis for i in range(array.ndim))] def _zip_index(array_, idx_): return (array_, idx_) return dask.array.map_blocks( _zip_index, array, idx, dtype=array.dtype, meta=array._meta, name="groupby-argreduce-preprocess", ) def _pick_second(*x): return x[1] argmax = Aggregation( "argmax", preprocess=argreduce_preprocess, chunk=("max", "argmax"), # order is important combine=("max", "argmax"), reduction_type="argreduce", fill_value=(dtypes.NINF, 0), final_fill_value=-1, finalize=_pick_second, dtypes=(None, np.intp), final_dtype=np.intp, ) argmin = Aggregation( "argmin", preprocess=argreduce_preprocess, chunk=("min", "argmin"), # order is important combine=("min", "argmin"), reduction_type="argreduce", fill_value=(dtypes.INF, 0), final_fill_value=-1, finalize=_pick_second, dtypes=(None, np.intp), final_dtype=np.intp, ) nanargmax = Aggregation( "nanargmax", preprocess=argreduce_preprocess, chunk=("nanmax", "nanargmax"), # order is important combine=("max", "argmax"), reduction_type="argreduce", fill_value=(dtypes.NINF, 0), final_fill_value=-1, finalize=_pick_second, dtypes=(None, np.intp), final_dtype=np.intp, ) nanargmin = Aggregation( "nanargmin", preprocess=argreduce_preprocess, chunk=("nanmin", "nanargmin"), # order is important combine=("min", "argmin"), reduction_type="argreduce", fill_value=(dtypes.INF, 0), final_fill_value=-1, finalize=_pick_second, dtypes=(None, np.intp), final_dtype=np.intp, ) first = Aggregation("first", chunk=None, combine=None, fill_value=None, preserves_dtype=True) last = Aggregation("last", chunk=None, combine=None, fill_value=None, preserves_dtype=True) nanfirst = Aggregation( "nanfirst", chunk="nanfirst", combine="nanfirst", fill_value=dtypes.NA, preserves_dtype=True, ) nanlast = Aggregation( "nanlast", chunk="nanlast", combine="nanlast", fill_value=dtypes.NA, preserves_dtype=True, ) all_ = Aggregation( "all", chunk="all", combine="all", fill_value=True, final_fill_value=False, dtypes=bool, final_dtype=bool, ) any_ = Aggregation( "any", chunk="any", combine="any", fill_value=False, final_fill_value=False, dtypes=bool, final_dtype=bool, ) # Support statistical quantities only blockwise # The parallel versions will be approximate and are hard to implement! median = Aggregation( name="median", fill_value=dtypes.NA, chunk=None, combine=None, final_dtype=np.floating, ) nanmedian = Aggregation( name="nanmedian", fill_value=dtypes.NA, chunk=None, combine=None, final_dtype=np.floating, ) def quantile_new_dims_func(q) -> tuple[Dim]: return (Dim(name="quantile", values=q),) # if the input contains integers or floats smaller than float64, # the output data-type is float64. Otherwise, the output data-type is the same as that # of the input. quantile = Aggregation( name="quantile", fill_value=dtypes.NA, chunk=None, combine=None, final_dtype=np.float64, new_dims_func=quantile_new_dims_func, ) nanquantile = Aggregation( name="nanquantile", fill_value=dtypes.NA, chunk=None, combine=None, final_dtype=np.float64, new_dims_func=quantile_new_dims_func, ) mode = Aggregation(name="mode", fill_value=dtypes.NA, chunk=None, combine=None, preserves_dtype=True) nanmode = Aggregation(name="nanmode", fill_value=dtypes.NA, chunk=None, combine=None, preserves_dtype=True) @dataclass class Scan: # This dataclass is separate from Aggregations since there's not much in common # between reductions and scans name: str # binary operation (e.g. np.add) # Must be None for mode="concat_then_scan" binary_op: Callable | None # in-memory grouped scan function (e.g. cumsum) scan: str # Grouped reduction that yields the last result of the scan (e.g. sum) reduction: str # Identity element identity: Any # dtype of result dtype: Any = None preserves_dtype: bool = False # "Mode" of applying binary op. # for np.add we apply the op directly to the `state` array and the `current` array. # for ffill, bfill we concat `state` to `current` and then run the scan again. mode: T_ScanBinaryOpMode = "apply_binary_op" preprocess: Callable | None = None finalize: Callable | None = None def concatenate(arrays: Sequence[AlignedArrays], axis=-1, out=None) -> AlignedArrays: group_idx = np.concatenate([a.group_idx for a in arrays], axis=axis) array = np.concatenate([a.array for a in arrays], axis=axis) return AlignedArrays(array=array, group_idx=group_idx) @dataclass class AlignedArrays: """Simple Xarray DataArray type data class with two aligned arrays.""" array: np.ndarray group_idx: np.ndarray def __post_init__(self): assert self.array.shape[-1] == self.group_idx.size def last(self) -> AlignedArrays: from flox.core import chunk_reduce reduced = chunk_reduce( self.array, self.group_idx, func=("last",), axis=-1, # TODO: automate? engine="flox", dtype=self.array.dtype, fill_value=dtypes._get_fill_value(self.array.dtype, dtypes.NA), expected_groups=None, ) return AlignedArrays(array=reduced["intermediates"][0], group_idx=reduced["groups"]) @dataclass class ScanState: """Dataclass representing intermediates for scan.""" # last value of each group seen so far state: AlignedArrays | None # intermediate result result: AlignedArrays | None def __post_init__(self): assert (self.state is not None) or (self.result is not None) def reverse(a: AlignedArrays) -> AlignedArrays: a.group_idx = a.group_idx[..., ::-1] a.array = a.array[..., ::-1] return a def scan_binary_op(left_state: ScanState, right_state: ScanState, *, agg: Scan) -> ScanState: from .reindex import reindex_ assert left_state.state is not None left = left_state.state right = right_state.result if right_state.result is not None else right_state.state assert right is not None if agg.mode == "apply_binary_op": assert agg.binary_op is not None # Implements groupby binary operation. reindexed = reindex_( left.array, from_=pd.Index(left.group_idx), # can't use right.group_idx since we need to do the indexing later to=pd.RangeIndex(right.group_idx.max() + 1), fill_value=agg.identity, axis=-1, ) result = AlignedArrays( array=agg.binary_op(reindexed[..., right.group_idx], right.array), group_idx=right.group_idx, ) elif agg.mode == "concat_then_scan": # Implements the binary op portion of the scan as a concatenate-then-scan. # This is useful for `ffill`, and presumably more generalized scans. assert agg.binary_op is None concat = concatenate([left, right], axis=-1) final_value = generic_aggregate( concat.group_idx, concat.array, func=agg.scan, axis=concat.array.ndim - 1, engine="flox", fill_value=agg.identity, ) result = AlignedArrays(array=final_value[..., left.group_idx.size :], group_idx=right.group_idx) else: raise ValueError(f"Unknown binary op application mode: {agg.mode!r}") # This is quite important. We need to update the state seen so far and propagate that. # So we must account for what we know when entering this function: i.e. `left` # TODO: this is a bit wasteful since it will sort again, but for now let's focus on # correctness and DRY lasts = concatenate([left, result]).last() return ScanState( state=lasts, # The binary op is called on the results of the reduction too when building up the tree. # We need to be careful and assign those results only to `state` and not the final result. # Up above, `result` is privileged when it exists. result=None if right_state.result is None else result, ) cumsum = Scan("cumsum", binary_op=np.add, reduction="sum", scan="cumsum", identity=0) nancumsum = Scan("nancumsum", binary_op=np.add, reduction="nansum", scan="nancumsum", identity=0) # ffill uses the identity for scan, and then at the binary-op state, # we concatenate the blockwise-reduced values with the original block, # and then execute the scan # TODO: consider adding chunk="identity" here, like with reductions as an optimization ffill = Scan( "ffill", binary_op=None, reduction="nanlast", scan="ffill", # Important: this must be NaN otherwise, ffill does not work. identity=dtypes.NA, mode="concat_then_scan", preserves_dtype=True, ) bfill = Scan( "bfill", binary_op=None, reduction="nanlast", scan="ffill", # Important: this must be NaN otherwise, bfill does not work. identity=dtypes.NA, preserves_dtype=True, mode="concat_then_scan", preprocess=reverse, finalize=reverse, ) # TODO: not implemented in numpy_groupies # cumprod = Scan("cumprod", binary_op=np.multiply, preop="prod", scan="cumprod") REDUCTIONS: dict[str, Aggregation] = { "any": any_, "all": all_, "count": count, "sum": sum_, "nansum": nansum, "prod": prod, "nanprod": nanprod, "mean": mean, "nanmean": nanmean, "var": var, "nanvar": nanvar, "std": std, "nanstd": nanstd, "max": max_, "nanmax": nanmax, "min": min_, "nanmin": nanmin, "argmax": argmax, "nanargmax": nanargmax, "argmin": argmin, "nanargmin": nanargmin, "first": first, "nanfirst": nanfirst, "last": last, "nanlast": nanlast, "median": median, "nanmedian": nanmedian, "quantile": quantile, "nanquantile": nanquantile, "mode": mode, "nanmode": nanmode, } SCANS: dict[str, Scan] = { "cumsum": cumsum, "nancumsum": nancumsum, "ffill": ffill, "bfill": bfill, } AGGREGATIONS: dict[str, Aggregation | Scan] = {**REDUCTIONS, **SCANS} def _initialize_aggregation( func: str | Aggregation, dtype, array_dtype, fill_value, min_count: int, finalize_kwargs: dict[Any, Any] | None, ) -> Aggregation: agg: Aggregation if not isinstance(func, Aggregation): try: # TODO: need better interface # we set dtype, fillvalue on reduction later. so deepcopy now agg_ = copy.deepcopy(AGGREGATIONS[func]) assert isinstance(agg_, Aggregation) agg = agg_ except KeyError: raise NotImplementedError(f"Reduction {func!r} not implemented yet") elif isinstance(func, Aggregation): # TODO: test that func is a valid Aggregation agg = copy.deepcopy(func) func = agg.name else: raise ValueError("Bad type for func. Expected str or Aggregation") # np.dtype(None) == np.dtype("float64")!!! # so check for not None dtype_: np.dtype | None = ( np.dtype(dtype) if dtype is not None and not isinstance(dtype, np.dtype) else dtype ) final_dtype = dtypes._normalize_dtype( dtype_ or agg.dtype_init["final"], array_dtype, agg.preserves_dtype, fill_value ) agg.dtype = { "user": dtype, # Save to automatically choose an engine "final": final_dtype, "numpy": (final_dtype,), "intermediate": tuple( ( dtypes._normalize_dtype(int_dtype, np.result_type(array_dtype, final_dtype), int_fv) if int_dtype is None else np.dtype(int_dtype) ) for int_dtype, int_fv in zip(agg.dtype_init["intermediate"], agg.fill_value["intermediate"]) ), } # Replace sentinel fill values according to dtype agg.fill_value["user"] = fill_value agg.fill_value["intermediate"] = tuple( dtypes._get_fill_value(dt, fv) for dt, fv in zip(agg.dtype["intermediate"], agg.fill_value["intermediate"]) ) agg.fill_value[func] = dtypes._get_fill_value(agg.dtype["final"], agg.fill_value[func]) if _is_arg_reduction(agg): # this allows us to unravel_index easily. we have to do that nearly every time. agg.fill_value["numpy"] = (0,) else: agg.fill_value["numpy"] = (agg.fill_value[func],) if finalize_kwargs is not None: assert isinstance(finalize_kwargs, dict) agg.finalize_kwargs = finalize_kwargs # This is needed for the dask pathway. # Because we use intermediate fill_value since a group could be # absent in one block, but present in another block # We set it for numpy to get nansum, nanprod tests to pass # where the identity element is 0, 1 # Also needed for nanmin, nanmax where intermediate fill_value is +-np.inf, # but final_fill_value is dtypes.NA if ( # TODO: this is a total hack, setting a default fill_value # even though numpy doesn't define identity for nanmin, nanmax agg.name in ["nanmin", "nanmax"] and min_count == 0 ): min_count = 1 agg.fill_value["user"] = agg.fill_value["user"] or agg.fill_value[agg.name] if min_count > 0: agg.min_count = min_count agg.numpy += ("nanlen",) if agg.chunk != (None,): agg.chunk += ("nanlen",) agg.combine += ("sum",) agg.fill_value["intermediate"] += (0,) agg.fill_value["numpy"] += (0,) agg.dtype["intermediate"] += (np.intp,) agg.dtype["numpy"] += (np.intp,) else: agg.min_count = 0 simple_combine: list[Callable | None] = [] for combine in agg.combine: if isinstance(combine, str): if combine in ["nanfirst", "nanlast"]: simple_combine.append(getattr(xrutils, combine)) else: simple_combine.append(getattr(np, combine)) else: simple_combine.append(combine) agg.simple_combine = tuple(simple_combine) return agg def is_supported_aggregation(array, func: str, **kwargs) -> bool: if isinstance(array, dask_array_type): # need to check the underlying array type array = array._meta if isinstance(array, sparse_array_type): from flox.lib import _is_sparse_supported_reduction return _is_sparse_supported_reduction(func) module, *_ = type(array).__module__.split(".") if module in ["numpy", "cubed", "xarray"]: if "quantile" in func and kwargs.get("method", "linear") != "linear": return False if func in REDUCTIONS: return True if module == "cubed" and func in SCANS: return False return True else: return False flox-0.11.2/flox/cache.py000066400000000000000000000004131514775624000151410ustar00rootroot00000000000000from functools import partial try: import cachey import dask # 1MB cache cache = cachey.Cache(1e6) memoize = partial(cache.memoize, key=dask.base.tokenize) except ImportError: cache = {} memoize = lambda x: x # type: ignore[assignment] flox-0.11.2/flox/cohorts.py000066400000000000000000000300031514775624000155550ustar00rootroot00000000000000"""Functions for finding group cohorts in chunked arrays.""" from __future__ import annotations import itertools import logging import math import operator from concurrent.futures import ThreadPoolExecutor from itertools import product from typing import TYPE_CHECKING, Literal import numpy as np import toolz as tlz from scipy.sparse import csc_array, csr_array if TYPE_CHECKING: import pandas as pd from .core import T_Method logger = logging.getLogger("flox") def slices_from_chunks(chunks): """slightly modified from dask.array.core.slices_from_chunks to be lazy""" cumdims = [tlz.accumulate(operator.add, bds, 0) for bds in chunks] slices = ( (slice(s, s + dim) for s, dim in zip(starts, shapes)) for starts, shapes in zip(cumdims, chunks) ) return product(*slices) def _compute_label_chunk_bitmask(labels, chunks, nlabels): def make_bitmask(rows, cols): data = np.broadcast_to(np.array(1, dtype=np.uint8), rows.shape) return csc_array((data, (rows, cols)), dtype=bool, shape=(nchunks, nlabels)) assert isinstance(labels, np.ndarray) shape = tuple(sum(c) for c in chunks) nchunks = math.prod(len(c) for c in chunks) approx_chunk_size = math.prod(c[0] for c in chunks) # Shortcut for 1D with size-1 chunks if shape == (nchunks,): rows_array = np.arange(nchunks) cols_array = labels mask = labels >= 0 return make_bitmask(rows_array[mask], cols_array[mask]) labels = np.broadcast_to(labels, shape[-labels.ndim :]) cols = [] ilabels = np.arange(nlabels) def chunk_unique(labels, slicer, nlabels, label_is_present=None): if label_is_present is None: label_is_present = np.empty((nlabels + 1,), dtype=bool) label_is_present[:] = False subset = labels[slicer] # This is a quite fast way to find unique integers, when we know how many there are # inspired by a similar idea in numpy_groupies for first, last # instead of explicitly finding uniques, repeatedly write True to the same location label_is_present[subset.reshape(-1)] = True # skip the -1 sentinel by slicing # Faster than np.argwhere by a lot uniques = ilabels[label_is_present[:-1]] return uniques # TODO: refine this heuristic. # The general idea is that with the threadpool, we repeatedly allocate memory # for `label_is_present`. We trade that off against the parallelism across number of chunks. # For large enough number of chunks (relative to number of labels), it makes sense to # suffer the extra allocation in exchange for parallelism. THRESHOLD = 2 if nlabels < THRESHOLD * approx_chunk_size: logger.debug( "Using threadpool since num_labels %s < %d * chunksize %s", nlabels, THRESHOLD, approx_chunk_size, ) with ThreadPoolExecutor() as executor: futures = [ executor.submit(chunk_unique, labels, slicer, nlabels) for slicer in slices_from_chunks(chunks) ] cols = tuple(f.result() for f in futures) else: logger.debug( "Using serial loop since num_labels %s > %d * chunksize %s", nlabels, THRESHOLD, approx_chunk_size, ) cols = [] # Add one to handle the -1 sentinel value label_is_present = np.empty((nlabels + 1,), dtype=bool) for region in slices_from_chunks(chunks): uniques = chunk_unique(labels, region, nlabels, label_is_present) cols.append(uniques) rows_array = np.repeat(np.arange(nchunks), tuple(len(col) for col in cols)) cols_array = np.concatenate(cols) return make_bitmask(rows_array, cols_array) # @memoize def find_group_cohorts( labels, chunks, expected_groups: pd.RangeIndex | None = None, merge: bool = False ) -> tuple[T_Method, dict]: """ Finds groups labels that occur together aka "cohorts" If available, results are cached in a 1MB cache managed by `cachey`. This allows us to be quick when repeatedly calling groupby_reduce for arrays with the same chunking (e.g. an xarray Dataset). Parameters ---------- labels : np.ndarray mD Array of integer group codes, factorized so that -1 represents NaNs. chunks : tuple chunks of the array being reduced expected_groups: pd.RangeIndex (optional) Used to extract the largest label expected merge: bool (optional) Whether to merge cohorts or not. Set to True if a user specifies "cohorts" but other methods are preferable. Returns ------- preferred_method: {"blockwise", cohorts", "map-reduce"} cohorts: dict_values Iterable of cohorts """ # To do this, we must have values in memory so casting to numpy should be safe labels = np.asarray(labels) shape = tuple(sum(c) for c in chunks) nchunks = math.prod(len(c) for c in chunks) # assumes that `labels` are factorized if expected_groups is None: nlabels = labels.max() + 1 else: nlabels = expected_groups[-1] + 1 # 1. Single chunk, blockwise always if nchunks == 1: return "blockwise", {(0,): list(range(nlabels))} labels = np.broadcast_to(labels, shape[-labels.ndim :]) bitmask = _compute_label_chunk_bitmask(labels, chunks, nlabels) CHUNK_AXIS, LABEL_AXIS = 0, 1 chunks_per_label = bitmask.sum(axis=CHUNK_AXIS) # can happen when `expected_groups` is passed but not all labels are present # (binning, resampling) present_labels = np.arange(bitmask.shape[LABEL_AXIS]) present_labels_mask = chunks_per_label != 0 if not present_labels_mask.all(): present_labels = present_labels[present_labels_mask] bitmask = bitmask[..., present_labels_mask] chunks_per_label = chunks_per_label[present_labels_mask] label_chunks = { present_labels[idx].item(): bitmask.indices[slice(bitmask.indptr[idx], bitmask.indptr[idx + 1])] for idx in range(bitmask.shape[LABEL_AXIS]) } # Invert the label_chunks mapping so we know which labels occur together. def invert(x) -> tuple[np.ndarray, ...]: arr = label_chunks[x] return tuple(arr.tolist()) chunks_cohorts = tlz.groupby(invert, label_chunks.keys()) # 2. Every group is contained to one block, use blockwise here. if bitmask.shape[CHUNK_AXIS] == 1 or (chunks_per_label == 1).all(): logger.debug("find_group_cohorts: blockwise is preferred.") return "blockwise", chunks_cohorts # 3. Perfectly chunked so there is only a single cohort if len(chunks_cohorts) == 1: logger.debug("Only found a single cohort. 'map-reduce' is preferred.") return "map-reduce", chunks_cohorts if merge else {} # 4. Our dataset has chunksize one along the axis, single_chunks = all(all(a == 1 for a in ac) for ac in chunks) # 5. Every chunk only has a single group, but that group might extend across multiple chunks one_group_per_chunk = (bitmask.sum(axis=LABEL_AXIS) == 1).all() # 6. Existing cohorts don't overlap, great for time grouping with perfect chunking no_overlapping_cohorts = (np.bincount(np.concatenate(tuple(chunks_cohorts.keys()))) == 1).all() if one_group_per_chunk or single_chunks or no_overlapping_cohorts: logger.debug("find_group_cohorts: cohorts is preferred, chunking is perfect.") return "cohorts", chunks_cohorts # We'll use containment to measure degree of overlap between labels. # Containment C = |Q & S| / |Q| # - |X| is the cardinality of set X # - Q is the query set being tested # - S is the existing set # The bitmask matrix S allows us to calculate this pretty efficiently using a dot product. # S.T @ S / chunks_per_label # # We treat the sparsity(C) = (nnz/size) as a summary measure of the net overlap. # 1. For high enough sparsity, there is a lot of overlap and we should use "map-reduce". # 2. When labels are uniformly distributed amongst all chunks # (and number of labels < chunk size), sparsity is 1. # 3. Time grouping cohorts (e.g. dayofyear) appear as lines in this matrix. # 4. When there are no overlaps at all between labels, containment is a block diagonal matrix # (approximately). # # However computing S.T @ S can still be the slowest step, especially if S # is not particularly sparse. Empirically the sparsity( S.T @ S ) > min(1, 2 x sparsity(S)). # So we use sparsity(S) as a shortcut. MAX_SPARSITY_FOR_COHORTS = 0.4 # arbitrary sparsity = bitmask.nnz / math.prod(bitmask.shape) preferred_method: Literal["map-reduce"] | Literal["cohorts"] logger.debug( "sparsity of bitmask is {}, threshold is {}".format( # noqa sparsity, MAX_SPARSITY_FOR_COHORTS ) ) # 7. Groups seem fairly randomly distributed, use "map-reduce". if sparsity > MAX_SPARSITY_FOR_COHORTS: if not merge: logger.debug( "find_group_cohorts: bitmask sparsity={}, merge=False, choosing 'map-reduce'".format( # noqa sparsity ) ) return "map-reduce", {} preferred_method = "map-reduce" else: preferred_method = "cohorts" # Note: While A.T @ A is a symmetric matrix, the division by chunks_per_label # makes it non-symmetric. asfloat = bitmask.astype(float) containment = csr_array(asfloat.T @ asfloat / chunks_per_label) logger.debug( "sparsity of containment matrix is {}".format( # noqa containment.nnz / math.prod(containment.shape) ) ) # Next we for-loop over groups and merge those that are quite similar. # Use a threshold on containment to always force some merging. # Note that we do not use the filtered containment matrix for estimating "sparsity" # because it is a bit hard to reason about. MIN_CONTAINMENT = 0.75 # arbitrary mask = containment.data < MIN_CONTAINMENT # Now we also know "exact cohorts" -- cohorts whose constituent groups # occur in exactly the same chunks. We only need examine one member of each group. # Skip the others by first looping over the exact cohorts, and zero out those rows. repeated = np.concatenate([v[1:] for v in chunks_cohorts.values()]).astype(int) repeated_idx = np.searchsorted(present_labels, repeated) for i in repeated_idx: mask[containment.indptr[i] : containment.indptr[i + 1]] = True containment.data[mask] = 0 containment.eliminate_zeros() # Figure out all the labels we need to loop over later n_overlapping_labels = containment.astype(bool).sum(axis=1) order = np.argsort(n_overlapping_labels, kind="stable")[::-1] # Order is such that we iterate over labels, beginning with those with most overlaps # Also filter out any "exact" cohorts order = order[n_overlapping_labels[order] > 0] logger.debug("find_group_cohorts: merging cohorts") merged_cohorts = {} merged_keys = set() for rowidx in order: if present_labels[rowidx] in merged_keys: continue cohidx = containment.indices[slice(containment.indptr[rowidx], containment.indptr[rowidx + 1])] cohort_ = present_labels[cohidx] cohort = [elem.item() for elem in cohort_ if elem not in merged_keys] if not cohort: continue merged_keys.update(cohort) allchunks = (label_chunks[member].tolist() for member in cohort) chunk = tuple(set(itertools.chain(*allchunks))) merged_cohorts[chunk] = cohort actual_ngroups = np.concatenate(tuple(merged_cohorts.values())).size expected_ngroups = present_labels.size assert len(merged_keys) == actual_ngroups assert expected_ngroups == actual_ngroups, (expected_ngroups, actual_ngroups) # sort by first label in cohort # This will help when sort=True (default) # and we have to resort the dask array as_sorted = dict(sorted(merged_cohorts.items(), key=lambda kv: kv[1][0])) return preferred_method, as_sorted flox-0.11.2/flox/core.py000066400000000000000000001335451514775624000150430ustar00rootroot00000000000000from __future__ import annotations import datetime import logging import math from collections.abc import Callable, Sequence from functools import partial from typing import ( TYPE_CHECKING, Any, Literal, TypeAlias, TypeVar, overload, ) import numpy as np import pandas as pd from . import xrdtypes from .aggregate_flox import _prepare_for_flox from .aggregations import ( Aggregation, Scan, _atleast_1d, _initialize_aggregation, generic_aggregate, is_var_chunk_reduction, quantile_new_dims_func, ) from .factorize import ( _factorize_multiple, factorize_, ) from .lib import sparse_array_type from .rechunk import rechunk_for_blockwise from .reindex import ( ReindexArrayType, ReindexStrategy, reindex_, ) from .xrutils import ( _contains_cftime_datetimes, _to_pytimedelta, datetime_to_numeric, is_duck_array, is_duck_cubed_array, is_duck_dask_array, isnull, module_available, notnull, ) if module_available("numpy", minversion="2.0.0"): from numpy.lib.array_utils import normalize_axis_tuple else: from numpy.core.numeric import normalize_axis_tuple # type: ignore[no-redef] HAS_NUMBAGG = module_available("numbagg", minversion="0.3.0") HAS_SPARSE = module_available("sparse") if TYPE_CHECKING: from .types import CubedArray, DaskArray T_DuckArray: TypeAlias = np.ndarray | DaskArray | CubedArray # Any ? T_By: TypeAlias = T_DuckArray T_Bys: TypeAlias = tuple[T_By, ...] T_ExpectIndex: TypeAlias = pd.Index T_ExpectIndexTuple: TypeAlias = tuple[T_ExpectIndex, ...] T_ExpectIndexOpt: TypeAlias = T_ExpectIndex | None T_ExpectIndexOptTuple: TypeAlias = tuple[T_ExpectIndexOpt, ...] T_Expect: TypeAlias = Sequence | np.ndarray | T_ExpectIndex T_ExpectTuple: TypeAlias = tuple[T_Expect, ...] T_ExpectOpt: TypeAlias = Sequence | np.ndarray | T_ExpectIndexOpt T_ExpectOptTuple: TypeAlias = tuple[T_ExpectOpt, ...] T_ExpectedGroups: TypeAlias = T_Expect | T_ExpectOptTuple T_ExpectedGroupsOpt: TypeAlias = T_ExpectedGroups | None T_Func: TypeAlias = str | Callable T_Funcs: TypeAlias = T_Func | Sequence[T_Func] T_Agg: TypeAlias = str | Aggregation T_Scan: TypeAlias = str | Scan T_Axis: TypeAlias = int T_Axes: TypeAlias = tuple[T_Axis, ...] T_AxesOpt: TypeAlias = T_Axis | T_Axes | None T_Dtypes: TypeAlias = np.typing.DTypeLike | Sequence[np.typing.DTypeLike] | None T_FillValues: TypeAlias = np.typing.ArrayLike | Sequence[np.typing.ArrayLike] | None T_Engine: TypeAlias = Literal["flox", "numpy", "numba", "numbagg"] T_EngineOpt: TypeAlias = None | T_Engine T_Method: TypeAlias = Literal["map-reduce", "blockwise", "cohorts"] T_MethodOpt: TypeAlias = None | Literal["map-reduce", "blockwise", "cohorts"] T_IsBins: TypeAlias = bool | Sequence[bool] from .types import FinalResultsDict, IntermediateDict T = TypeVar("T") # This dummy axis is inserted using np.expand_dims # and then reduced over during the combine stage by # _simple_combine. DUMMY_AXIS = -2 logger = logging.getLogger("flox") from .cohorts import find_group_cohorts from .lib import ( _is_arg_reduction, _is_bool_supported_reduction, _is_first_last_reduction, _is_minmax_reduction, _is_nanlen, _is_reindex_sparse_supported_reduction, _issorted, _postprocess_numbagg, _should_auto_rechunk_blockwise, ) def _get_expected_groups(by: T_By, sort: bool) -> T_ExpectIndex: if is_duck_dask_array(by): raise ValueError("Please provide expected_groups if not grouping by a numpy array.") flatby = by.reshape(-1) expected = pd.unique(flatby[notnull(flatby)]) return _convert_expected_groups_to_index((expected,), isbin=(False,), sort=sort)[0] def _get_chunk_reduction(reduction_type: Literal["reduce", "argreduce"]) -> Callable: if reduction_type == "reduce": return chunk_reduce elif reduction_type == "argreduce": return chunk_argreduce else: raise ValueError(f"Unknown reduction type: {reduction_type}") def _move_reduce_dims_to_end(arr: np.ndarray, axis: T_Axes) -> np.ndarray: """Transpose `arr` by moving `axis` to the end.""" axis = tuple(axis) order = tuple(ax for ax in np.arange(arr.ndim) if ax not in axis) + axis arr = arr.transpose(order) return arr def _collapse_axis(arr: np.ndarray, naxis: int) -> np.ndarray: """Reshape so that the last `naxis` axes are collapsed to one axis.""" newshape = arr.shape[:-naxis] + (math.prod(arr.shape[-naxis:]),) return arr.reshape(newshape) def _unique(a: np.ndarray) -> np.ndarray: """Much faster to use pandas unique and sort the results. np.unique sorts before uniquifying and is slow.""" return np.sort(pd.unique(a.reshape(-1))) def chunk_argreduce( array_plus_idx: tuple[np.ndarray, ...], by: np.ndarray, func: T_Funcs, expected_groups: pd.Index | None, axis: T_AxesOpt, fill_value: T_FillValues, dtype: T_Dtypes = None, reindex: bool = False, engine: T_Engine = "numpy", sort: bool = True, user_dtype=None, ) -> IntermediateDict: """ Per-chunk arg reduction. Expects a tuple of (array, index along reduction axis). Inspired by dask.array.reductions.argtopk """ array, idx = array_plus_idx by = np.broadcast_to(by, array.shape) results = chunk_reduce( array, by, func, expected_groups=None, axis=axis, fill_value=fill_value, dtype=dtype, engine=engine, sort=sort, user_dtype=user_dtype, ) if not all(isnull(results["groups"])): idx = np.broadcast_to(idx, array.shape) # array, by get flattened to 1D before passing to npg # so the indexes need to be unraveled newidx = np.unravel_index(results["intermediates"][1], array.shape) # Now index into the actual "global" indexes `idx` results["intermediates"][1] = idx[newidx] if reindex and expected_groups is not None: results["intermediates"][1] = reindex_( results["intermediates"][1], results["groups"].squeeze(), expected_groups, fill_value=0, ) assert results["intermediates"][0].shape == results["intermediates"][1].shape return results def chunk_reduce( array: np.ndarray, by: np.ndarray, func: T_Funcs, expected_groups: pd.Index | None, axis: T_AxesOpt = None, fill_value: T_FillValues = None, dtype: T_Dtypes = None, reindex: bool = False, engine: T_Engine = "numpy", kwargs: Sequence[dict] | None = None, sort: bool = True, user_dtype=None, ) -> IntermediateDict: """ Wrapper for numpy_groupies aggregate that supports nD ``array`` and mD ``by``. Core groupby reduction using numpy_groupies. Uses ``pandas.factorize`` to factorize ``by``. Offsets the groups if not reducing along all dimensions of ``by``. Always ravels ``by`` to 1D, flattens appropriate dimensions of array. When dask arrays are passed to groupby_reduce, this function is called on every block. Parameters ---------- array : numpy.ndarray Array of values to reduced by : numpy.ndarray Array to group by. func : str or Callable or Sequence[str] or Sequence[Callable] Name of reduction or function, passed to numpy_groupies. Supports multiple reductions. axis : (optional) int or Sequence[int] If None, reduce along all dimensions of array. Else reduce along specified axes. Returns ------- dict """ funcs = _atleast_1d(func) nfuncs = len(funcs) dtypes = _atleast_1d(dtype, nfuncs) fill_values = _atleast_1d(fill_value, nfuncs) kwargss = _atleast_1d({}, nfuncs) if kwargs is None else kwargs if isinstance(axis, Sequence): axes: T_Axes = axis nax = len(axes) else: nax = by.ndim axes = () if axis is None else (axis,) * nax assert by.ndim <= array.ndim final_array_shape = array.shape[:-nax] + (1,) * (nax - 1) final_groups_shape = (1,) * (nax - 1) if 1 < nax < by.ndim: # when axis is a tuple # collapse and move reduction dimensions to the end by = _collapse_axis(by, nax) array = _collapse_axis(array, nax) axes = (-1,) nax = 1 # if indices=[2,2,2], npg assumes groups are (0, 1, 2); # and will return a result that is bigger than necessary # avoid by factorizing again so indices=[2,2,2] is changed to # indices=[0,0,0]. This is necessary when combining block results # factorize can handle strings etc unlike digitize group_idx, grps, found_groups_shape, _, size, props = factorize_( (by,), axes, expected_groups=(expected_groups,), reindex=bool(reindex), sort=sort ) (groups,) = grps # do this *before* possible broadcasting below. # factorize_ has already taken care of offsetting if engine == "numbagg": seen_groups = _unique(group_idx) order = "C" if nax > 1: needs_broadcast = any( group_idx.shape[ax] != array.shape[ax] and group_idx.shape[ax] == 1 for ax in range(-nax, 0) ) if needs_broadcast: # This is the dim=... case, it's a lot faster to ravel group_idx # in fortran order since group_idx is then sorted # I'm seeing 400ms -> 23ms for engine="flox" # Of course we are slower to ravel `array` but we avoid argsorting # both `array` *and* `group_idx` in _prepare_for_flox group_idx = np.broadcast_to(group_idx, array.shape[-by.ndim :]) if engine == "flox": group_idx = group_idx.reshape(-1, order="F") order = "F" # always reshape to 1D along group dimensions newshape = array.shape[: array.ndim - by.ndim] + (math.prod(array.shape[-by.ndim :]),) array = array.reshape(newshape, order=order) # type: ignore[call-overload] group_idx = group_idx.reshape(-1) assert group_idx.ndim == 1 empty = np.all(props.nanmask) hasnan = np.any(props.nanmask) results: IntermediateDict = {"groups": [], "intermediates": []} if reindex and expected_groups is not None: # TODO: what happens with binning here? results["groups"] = expected_groups else: if empty: results["groups"] = np.array([np.nan]) else: results["groups"] = groups # npg's argmax ensures that index of first "max" is returned assuming there # are many elements equal to the "max". Sorting messes this up totally. # so we skip this for argreductions if engine == "flox": # is_arg_reduction = any("arg" in f for f in func if isinstance(f, str)) # if not is_arg_reduction: group_idx, array, _ = _prepare_for_flox(group_idx, array) final_array_shape += results["groups"].shape final_groups_shape += results["groups"].shape # we commonly have func=(..., "nanlen", "nanlen") when # counts are needed for the final result as well as for masking # optimize that out. previous_reduction: T_Func = "" for reduction, fv, kw, dt in zip(funcs, fill_values, kwargss, dtypes): # UGLY! but this is because the `var` breaks our design assumptions if empty and not is_var_chunk_reduction(reduction): result = np.full(shape=final_array_shape, fill_value=fv, like=array) elif _is_nanlen(reduction) and _is_nanlen(previous_reduction): result = results["intermediates"][-1] else: # fill_value here is necessary when reducing with "offset" groups kw_func = dict(size=size, dtype=dt, fill_value=fv) kw_func.update(kw) # UGLY! but this is because the `var` breaks our design assumptions if is_var_chunk_reduction(reduction): kw_func.update(engine=engine) if callable(reduction): # passing a custom reduction for npg to apply per-group is really slow! # So this `reduction` has to do the groupby-aggregation result = reduction(group_idx, array, **kw_func) else: result = generic_aggregate( group_idx, array, axis=-1, engine=engine, func=reduction, **kw_func ).astype(dt, copy=False) if engine == "numbagg": result = _postprocess_numbagg( result, func=reduction, size=size, fill_value=fv, # Unfortunately, we cannot reuse found_groups, it has not # been "offset" and is really expected_groups in nearly all cases seen_groups=seen_groups, ) if hasnan: # remove NaN group label which should be last result = result[..., :-1] # TODO: Figure out how to generalize this if reduction in ("quantile", "nanquantile"): new_dims_shape = tuple(dim.size for dim in quantile_new_dims_func(**kw) if not dim.is_scalar) else: new_dims_shape = tuple() result = result.reshape(new_dims_shape + final_array_shape[:-1] + found_groups_shape) results["intermediates"].append(result) previous_reduction = reduction results["groups"] = np.broadcast_to(results["groups"], final_groups_shape) return results def _squeeze_results(results: IntermediateDict, axis: T_Axes) -> IntermediateDict: # at the end we squeeze out extra dims groups = results["groups"] newresults: IntermediateDict = {"groups": [], "intermediates": []} newresults["groups"] = np.squeeze( groups, axis=tuple(ax for ax in range(groups.ndim - 1) if groups.shape[ax] == 1) ) for v in results["intermediates"]: squeeze_ax = tuple(ax for ax in sorted(axis)[:-1] if v.shape[ax] == 1) newresults["intermediates"].append(np.squeeze(v, axis=squeeze_ax) if squeeze_ax else v) return newresults def _finalize_results( results: IntermediateDict, agg: Aggregation, axis: T_Axes, expected_groups: pd.Index | None, reindex: ReindexStrategy, ) -> FinalResultsDict: """Finalize results by 1. Squeezing out dummy dimensions 2. Calling agg.finalize with intermediate results 3. Mask using counts and fill with user-provided fill_value. 4. reindex to expected_groups """ squeezed = _squeeze_results(results, tuple(agg.num_new_vector_dims + ax for ax in axis)) min_count = agg.min_count if min_count > 0: counts = squeezed["intermediates"][-1] squeezed["intermediates"] = squeezed["intermediates"][:-1] # finalize step finalized: FinalResultsDict = {} if agg.finalize is None: finalized[agg.name] = squeezed["intermediates"][0] else: finalized[agg.name] = agg.finalize(*squeezed["intermediates"], **agg.finalize_kwargs) fill_value = agg.fill_value["user"] if min_count > 0: count_mask = counts < min_count if count_mask.any() or reindex.array_type is ReindexArrayType.SPARSE_COO: # For one count_mask.any() prevents promoting bool to dtype(fill_value) unless # necessary if fill_value is None: raise ValueError("Filling is required but fill_value is None.") # This allows us to match xarray's type promotion rules # Use '==' instead of 'is', as Dask serialization can break identity checks. if fill_value == xrdtypes.NA: new_dtype, fill_value = xrdtypes.maybe_promote(finalized[agg.name].dtype) finalized[agg.name] = finalized[agg.name].astype(new_dtype) if isinstance(count_mask, sparse_array_type): # This is guaranteed because for `counts`, we use `fill_value=0` # and we are in the counts < (min_count > 0) branch. # We need to flip the fill_value so that the `np.where` call preserves # the fill_value of finalized[agg.name], this is needed when dask concatenates results. assert count_mask.fill_value is np.True_, "Logic bug. I expected fill_value to be True" count_mask = type(count_mask).from_numpy(count_mask.todense(), fill_value=False) finalized[agg.name] = np.where(count_mask, fill_value, finalized[agg.name]) # Final reindexing has to be here to be lazy if not reindex.blockwise and expected_groups is not None: finalized[agg.name] = reindex_( finalized[agg.name], squeezed["groups"], expected_groups, fill_value=fill_value, array_type=reindex.array_type, ) finalized["groups"] = expected_groups else: finalized["groups"] = squeezed["groups"] finalized[agg.name] = finalized[agg.name].astype(agg.dtype["final"], copy=False) return finalized def _reduce_blockwise( array, by, agg: Aggregation, *, axis: T_Axes, expected_groups, fill_value: Any, engine: T_Engine, sort: bool, reindex: ReindexStrategy, ) -> FinalResultsDict: """ Blockwise groupby reduction that produces the final result. This code path is also used for non-dask array aggregations. """ # for pure numpy grouping, we just use npg directly and avoid "finalizing" # (agg.finalize = None). We still need to do the reindexing step in finalize # so that everything matches the dask version. agg.finalize = None assert agg.finalize_kwargs is not None finalize_kwargs_: tuple[dict[Any, Any], ...] = (agg.finalize_kwargs,) + ({},) + ({},) results = chunk_reduce( array, by, func=agg.numpy, axis=axis, expected_groups=expected_groups, # This fill_value should only apply to groups that only contain NaN observations # BUT there is funkiness when axis is a subset of all possible values # (see below) fill_value=agg.fill_value["numpy"], dtype=agg.dtype["numpy"], kwargs=finalize_kwargs_, engine=engine, sort=sort, reindex=bool(reindex.blockwise), user_dtype=agg.dtype["user"], ) if _is_arg_reduction(agg): results["intermediates"][0] = np.unravel_index(results["intermediates"][0], array.shape)[-1] result = _finalize_results(results, agg, axis, expected_groups, reindex=reindex) return result def _validate_reindex( reindex: ReindexStrategy | bool | None, func, method: T_MethodOpt, expected_groups, any_by_dask: bool, is_dask_array: bool, array_dtype: Any, ) -> ReindexStrategy: # logger.debug("Entering _validate_reindex: reindex is {}".format(reindex)) # noqa def first_or_last(): return func in ["first", "last"] or (_is_first_last_reduction(func) and array_dtype.kind != "f") all_eager = not is_dask_array and not any_by_dask if reindex is True and not all_eager: if _is_arg_reduction(func): raise NotImplementedError if method == "cohorts" or (method == "blockwise" and not any_by_dask): raise ValueError("reindex=True is not a valid choice for method='blockwise' or method='cohorts'.") if first_or_last(): raise ValueError("reindex must be None or False when func is 'first' or 'last.") if isinstance(reindex, ReindexStrategy): reindex_ = reindex else: reindex_ = ReindexStrategy(blockwise=reindex) if reindex_.blockwise is None: if method is None: # logger.debug("Leaving _validate_reindex: method = None, returning None") return ReindexStrategy(blockwise=None) if all_eager: return ReindexStrategy(blockwise=True) if first_or_last(): # have to do the grouped_combine since there's no good fill_value # Also needed for nanfirst, nanlast with no-NaN dtypes return ReindexStrategy(blockwise=False) if method == "blockwise": # for grouping by dask arrays, we set reindex=True reindex_ = ReindexStrategy(blockwise=any_by_dask) elif _is_arg_reduction(func): reindex_ = ReindexStrategy(blockwise=False) elif method == "cohorts": reindex_ = ReindexStrategy(blockwise=False) elif method == "map-reduce": if expected_groups is None and any_by_dask: reindex_ = ReindexStrategy(blockwise=False) else: reindex_ = ReindexStrategy(blockwise=True) assert isinstance(reindex_, ReindexStrategy) # logger.debug("Leaving _validate_reindex: reindex is {}".format(reindex)) # noqa return reindex_ def _assert_by_is_aligned(shape: tuple[int, ...], by: T_Bys) -> None: assert all(b.ndim == by[0].ndim for b in by[1:]) for idx, b in enumerate(by): if not all(j in [i, 1] for i, j in zip(shape[-b.ndim :], b.shape)): raise ValueError( "`array` and `by` arrays must be 'aligned' " "so that such that by_ is broadcastable to array.shape[-by.ndim:] " "for every array `by_` in `by`. " "Either array.shape[-by_.ndim :] == by_.shape or the only differences " "should be size-1 dimensions in by_." f"Received array of shape {shape} but " f"array {idx} in `by` has shape {b.shape}." ) @overload def _convert_expected_groups_to_index( expected_groups: tuple[None, ...], isbin: Sequence[bool], sort: bool ) -> tuple[None, ...]: ... @overload def _convert_expected_groups_to_index( expected_groups: T_ExpectTuple, isbin: Sequence[bool], sort: bool ) -> T_ExpectIndexTuple: ... def _convert_expected_groups_to_index( expected_groups: T_ExpectOptTuple, isbin: Sequence[bool], sort: bool ) -> T_ExpectIndexOptTuple: out: list[T_ExpectIndexOpt] = [] for ex, isbin_ in zip(expected_groups, isbin): if isinstance(ex, pd.IntervalIndex) or (isinstance(ex, pd.Index) and not isbin_): if sort: out.append(ex.sort_values()) else: out.append(ex) elif ex is not None: if isbin_: out.append(pd.IntervalIndex.from_breaks(ex)) else: if sort: ex = np.sort(ex) out.append(pd.Index(ex)) else: assert ex is None out.append(None) return tuple(out) @overload def _validate_expected_groups(nby: int, expected_groups: None) -> tuple[None, ...]: ... @overload def _validate_expected_groups(nby: int, expected_groups: T_ExpectedGroups) -> T_ExpectTuple: ... def _validate_expected_groups(nby: int, expected_groups: T_ExpectedGroupsOpt) -> T_ExpectOptTuple: if expected_groups is None: return (None,) * nby if nby == 1 and not isinstance(expected_groups, tuple): if isinstance(expected_groups, pd.Index | np.ndarray): return (expected_groups,) else: array = np.asarray(expected_groups) if np.issubdtype(array.dtype, np.integer): # preserve default dtypes # on pandas 1.5/2, on windows # when a list is passed array = array.astype(np.int64) return (array,) if nby > 1 and not isinstance(expected_groups, tuple): # TODO: test for list raise ValueError( "When grouping by multiple variables, expected_groups must be a tuple " "of either arrays or objects convertible to an array (like lists). " "For example `expected_groups=(np.array([1, 2, 3]), ['a', 'b', 'c'])`." f"Received a {type(expected_groups).__name__} instead. " "When grouping by a single variable, you can pass an array or something " "convertible to an array for convenience: `expected_groups=['a', 'b', 'c']`." ) if TYPE_CHECKING: assert isinstance(expected_groups, tuple) if len(expected_groups) != nby: raise ValueError( f"Must have same number of `expected_groups` (received {len(expected_groups)}) " f" and variables to group by (received {nby})." ) return expected_groups def _choose_method( method: T_MethodOpt, preferred_method: T_Method, agg: Aggregation, by, nax: int ) -> T_Method: if method is None: logger.debug("_choose_method: method is None") if agg.chunk == (None,): if preferred_method != "blockwise": raise ValueError( f"Aggregation {agg.name} is only supported for `method='blockwise'`, " "but the chunking is not right." ) logger.debug("_choose_method: choosing 'blockwise'") return "blockwise" if nax != by.ndim: logger.debug("_choose_method: choosing 'map-reduce'") return "map-reduce" if _is_arg_reduction(agg) and preferred_method == "blockwise": return "cohorts" logger.debug(f"_choose_method: choosing preferred_method={preferred_method}") # noqa return preferred_method else: return method def _choose_engine(by, agg: Aggregation): dtype = agg.dtype["user"] not_arg_reduce = not _is_arg_reduction(agg) if agg.name in ["quantile", "nanquantile", "median", "nanmedian"]: logger.debug(f"_choose_engine: Choosing 'flox' since {agg.name}") return "flox" # numbagg only supports nan-skipping reductions # without dtype specified has_blockwise_nan_skipping = (agg.chunk[0] is None and "nan" in agg.name) or any( (isinstance(func, str) and "nan" in func) for func in agg.chunk ) if HAS_NUMBAGG: if agg.name in ["all", "any"] or (not_arg_reduce and has_blockwise_nan_skipping and dtype is None): logger.debug("_choose_engine: Choosing 'numbagg'") return "numbagg" if not_arg_reduce and (not is_duck_dask_array(by) and _issorted(by)): logger.debug("_choose_engine: Choosing 'flox'") return "flox" else: logger.debug("_choose_engine: Choosing 'numpy'") return "numpy" def groupby_reduce( array: np.ndarray | DaskArray, *by: T_By, func: T_Agg, expected_groups: T_ExpectedGroupsOpt = None, sort: bool = True, isbin: T_IsBins = False, axis: T_AxesOpt = None, fill_value=None, dtype: np.typing.DTypeLike = None, min_count: int | None = None, method: T_MethodOpt = None, engine: T_EngineOpt = None, reindex: ReindexStrategy | bool | None = None, finalize_kwargs: dict[Any, Any] | None = None, ) -> tuple[DaskArray, *tuple[np.ndarray | DaskArray, ...]]: """ GroupBy reductions using tree reductions for dask.array Parameters ---------- array : ndarray or DaskArray Array to be reduced, possibly nD *by : ndarray or DaskArray Array of labels to group over. Must be aligned with ``array`` so that ``array.shape[-by.ndim :] == by.shape`` or any disagreements in that equality check are for dimensions of size 1 in ``by``. func : {"all", "any", "count", "sum", "nansum", "mean", "nanmean", \ "max", "nanmax", "min", "nanmin", "argmax", "nanargmax", "argmin", "nanargmin", \ "quantile", "nanquantile", "median", "nanmedian", "mode", "nanmode", \ "first", "nanfirst", "last", "nanlast"} or Aggregation Single function name or an Aggregation instance expected_groups : (optional) Sequence Expected unique labels. isbin : bool, optional Are ``expected_groups`` bin edges? sort : bool, optional Whether groups should be returned in sorted order. Only applies for dask reductions when ``method`` is not ``"map-reduce"``. For ``"map-reduce"``, the groups are always sorted. axis : None or int or Sequence[int], optional If None, reduce across all dimensions of ``by``, else reduce across corresponding axes of array. Negative integers are normalized using ``array.ndim``. fill_value : Any Value to assign when a label in ``expected_groups`` is not present. dtype : data-type , optional DType for the output. Can be anything that is accepted by ``np.dtype``. min_count : int, default: None The required number of valid values to perform the operation. If fewer than ``min_count`` non-NA values are present the result will be NA. Only used if ``skipna`` is set to True or defaults to True for the array's dtype. method : {"map-reduce", "blockwise", "cohorts"}, optional Note that this arg is chosen by default using heuristics. Strategy for reduction of dask arrays only. * ``"map-reduce"``: First apply the reduction blockwise on ``array``, then combine a few newighbouring blocks, apply the reduction. Continue until finalizing. Usually, ``func`` will need to be an ``Aggregation`` instance for this method to work. Common aggregations are implemented. * ``"blockwise"``: Only reduce using blockwise and avoid aggregating blocks together. Useful for resampling-style reductions where group members are always together. If ``by`` is 1D, ``array`` is automatically rechunked so that chunk boundaries line up with group boundaries i.e. each block contains all members of any group present in that block. For nD ``by``, you must make sure that all members of a group are present in a single block. * ``"cohorts"``: Finds group labels that tend to occur together ("cohorts"), indexes out cohorts and reduces that subset using "map-reduce", repeat for all cohorts. This works well for many time groupings where the group labels repeat at regular intervals like 'hour', 'month', dayofyear' etc. Optimize chunking ``array`` for this method by first rechunking using ``rechunk_for_cohorts`` (for 1D ``by`` only). engine : {"flox", "numpy", "numba", "numbagg"}, optional Algorithm to compute the groupby reduction on non-dask arrays and on each dask chunk: * ``"numpy"``: Use the vectorized implementations in ``numpy_groupies.aggregate_numpy``. This is the default choice because it works for most array types. * ``"flox"``: Use an internal implementation where the data is sorted so that all members of a group occur sequentially, and then numpy.ufunc.reduceat is to used for the reduction. This will fall back to ``numpy_groupies.aggregate_numpy`` for a reduction that is not yet implemented. * ``"numba"``: Use the implementations in ``numpy_groupies.aggregate_numba``. * ``"numbagg"``: Use the reductions supported by ``numbagg.grouped``. This will fall back to ``numpy_groupies.aggregate_numpy`` for a reduction that is not yet implemented. reindex : ReindexStrategy | bool, optional Whether to "reindex" the blockwise reduced results to ``expected_groups`` (possibly automatically detected). If True, the intermediate result of the blockwise groupby-reduction has a value for all expected groups, and the final result is a simple reduction of those intermediates. In nearly all cases, this is a significant boost in computation speed. For cases like time grouping, this may result in large intermediates relative to the original block size. Avoid that by using ``method="cohorts"``. By default, it is turned off for argreductions. By default, the type of ``array`` is preserved. You may optionally reindex to a sparse array type to further control memory in the case of ``expected_groups`` being very large. Pass a ``ReindexStrategy`` instance with the appropriate ``array_type``, for example (``reindex=ReindexStrategy(blockwise=False, array_type=ReindexArrayType.SPARSE_COO)``). finalize_kwargs : dict, optional Kwargs passed to finalize the reduction such as ``ddof`` for var, std or ``q`` for quantile. Returns ------- result Aggregated result *groups Group labels See Also -------- xarray.xarray_reduce """ if engine == "flox" and _is_arg_reduction(func): raise NotImplementedError( "argreductions not supported for engine='flox' yet. Try engine='numpy' or engine='numba' instead." ) if engine == "numbagg" and dtype is not None: raise NotImplementedError( "numbagg does not support the `dtype` kwarg. Either cast your " "input arguments to `dtype` or use a different `engine`: " "'flox' or 'numpy' or 'numba'. " "See https://github.com/numbagg/numbagg/issues/121." ) if func in ["quantile", "nanquantile"]: if finalize_kwargs is None or "q" not in finalize_kwargs: raise ValueError("Please pass `q` for quantile calculations.") else: nq = len(_atleast_1d(finalize_kwargs["q"])) if nq > 1 and engine == "numpy": raise ValueError( "Multiple quantiles not supported with engine='numpy'." "Use engine='flox' instead (it is also much faster), " "or set engine=None to use the default." ) bys: T_Bys = tuple(np.asarray(b) if not is_duck_array(b) else b for b in by) nby = len(bys) by_is_dask = tuple(is_duck_dask_array(b) for b in bys) any_by_dask = any(by_is_dask) provided_expected = expected_groups is not None if engine == "numbagg" and _is_arg_reduction(func) and (any_by_dask or is_duck_dask_array(array)): # There is only one test that fails, but I can't figure # out why without deep debugging. # just disable for now. # test_groupby_reduce_axis_subset_against_numpy # for array is 3D dask, by is 3D dask, axis=2 # We are falling back to numpy for the arg reduction, # so presumably something is going wrong raise NotImplementedError( "argreductions not supported for engine='numbagg' yet." "Try engine='numpy' or engine='numba' instead." ) if method == "cohorts" and any_by_dask: raise ValueError(f"method={method!r} can only be used when grouping by numpy arrays.") if not is_duck_array(array): array = np.asarray(array) reindex = _validate_reindex( reindex, func, method, expected_groups, any_by_dask, is_duck_dask_array(array), array.dtype, ) is_bool_array = np.issubdtype(array.dtype, bool) and not _is_bool_supported_reduction(func) array = array.astype(np.int_) if is_bool_array else array isbins = _atleast_1d(isbin, nby) _assert_by_is_aligned(array.shape, bys) expected_groups = _validate_expected_groups(nby, expected_groups) for idx, (expect, is_dask) in enumerate(zip(expected_groups, by_is_dask)): if is_dask and (reindex.blockwise or nby > 1) and expect is None: raise ValueError( f"`expected_groups` for array {idx} in `by` cannot be None since it is a dask.array." ) # We convert to pd.Index since that lets us know if we are binning or not # (pd.IntervalIndex or not) expected_groups = _convert_expected_groups_to_index(expected_groups, isbins, sort) # Don't factorize early only when # grouping by dask arrays, and not having expected_groups factorize_early = not ( # can't do it if we are grouping by dask array but don't have expected_groups any(is_dask and ex_ is None for is_dask, ex_ in zip(by_is_dask, expected_groups)) ) expected_: pd.RangeIndex | None if factorize_early: bys, final_groups, grp_shape = _factorize_multiple( bys, expected_groups, any_by_dask=any_by_dask, sort=sort, ) expected_ = pd.RangeIndex(math.prod(grp_shape)) else: assert expected_groups == (None,) expected_ = None assert len(bys) == 1 (by_,) = bys if axis is None: axis_ = tuple(array.ndim + np.arange(-by_.ndim, 0)) else: axis_ = normalize_axis_tuple(axis, array.ndim) nax = len(axis_) has_dask = is_duck_dask_array(array) or is_duck_dask_array(by_) has_cubed = is_duck_cubed_array(array) or is_duck_cubed_array(by_) if _should_auto_rechunk_blockwise(method, array, any_by_dask, by_): # Let's try rechunking for sorted 1D by. (single_axis,) = axis_ method, array = rechunk_for_blockwise(array, single_axis, by_, force=False) is_first_last = _is_first_last_reduction(func) if is_first_last: if has_dask and nax != 1: raise ValueError( "For dask arrays: first, last, nanfirst, nanlast reductions are " "only supported along a single axis. Please reshape appropriately." ) elif nax not in [1, by_.ndim]: raise ValueError( "first, last, nanfirst, nanlast reductions are only supported " "along a single axis or when reducing across all dimensions of `by`." ) is_npdatetime = array.dtype.kind in "Mm" is_cftime = _contains_cftime_datetimes(array) requires_numeric = ( (func not in ["count", "any", "all"] and not is_first_last) # Flox's count works with non-numeric and its faster than converting. or (func == "count" and engine != "flox") # TODO: needed for npg, move to aggregate_npg or (is_first_last and is_cftime) ) if requires_numeric: if is_npdatetime: datetime_dtype = array.dtype array = array.view(np.int64) elif is_cftime: offset = array.min() assert offset is not None array = datetime_to_numeric(array, offset, datetime_unit="us") if nax == 1 and by_.ndim > 1 and expected_ is None: # When we reduce along all axes, we are guaranteed to see all # groups in the final combine stage, so everything works. # This is not necessarily true when reducing along a subset of axes # (of by) # TODO: Does this depend on chunking of by? # For e.g., we could relax this if there is only one chunk along all # by dim != axis? raise NotImplementedError("Please provide ``expected_groups`` when not reducing along all axes.") assert nax <= by_.ndim if nax < by_.ndim: by_ = _move_reduce_dims_to_end(by_, tuple(-array.ndim + ax + by_.ndim for ax in axis_)) array = _move_reduce_dims_to_end(array, axis_) axis_ = tuple(array.ndim + np.arange(-nax, 0)) nax = len(axis_) # When axis is a subset of possible values; then npg will # apply the fill_value to groups that don't exist along a particular axis (for e.g.) # since these count as a group that is absent. thoo! # fill_value applies to all-NaN groups as well as labels in expected_groups that are not found. # The only way to do this consistently is mask out using min_count # Consider np.sum([np.nan]) = np.nan, np.nansum([np.nan]) = 0 if min_count is None: if nax < by_.ndim or (fill_value is not None and provided_expected): min_count_: int = 1 else: min_count_ = 0 else: min_count_ = min_count # TODO: set in xarray? if min_count_ > 0 and func in ["nansum", "nanprod"] and fill_value is None: # nansum, nanprod have fill_value=0, 1 # overwrite than when min_count is set fill_value = np.nan kwargs = dict(axis=axis_, fill_value=fill_value) agg = _initialize_aggregation(func, dtype, array.dtype, fill_value, min_count_, finalize_kwargs) # Need to set this early using `agg` # It cannot be done in the core loop of chunk_reduce # since we "prepare" the data for flox. kwargs["engine"] = _choose_engine(by_, agg) if engine is None else engine groups: tuple[np.ndarray | DaskArray, ...] if has_cubed: if method is None: method = "map-reduce" if method not in ("map-reduce", "blockwise"): raise NotImplementedError( "Reduction for Cubed arrays is only implemented for methods 'map-reduce' and 'blockwise'." ) from .cubed import cubed_groupby_agg partial_agg = partial(cubed_groupby_agg, **kwargs) result, groups = partial_agg( array=array, by=by_, expected_groups=expected_, agg=agg, reindex=reindex, method=method, sort=sort, ) return (result, groups) elif not has_dask: reindex.set_blockwise_for_numpy() results = _reduce_blockwise( array, by_, agg, expected_groups=expected_, reindex=reindex, sort=sort, **kwargs, ) groups = (results["groups"],) result = results[agg.name] else: if TYPE_CHECKING: # TODO: How else to narrow that array.chunks is there? assert isinstance(array, DaskArray) if (not any_by_dask and method is None) or method == "cohorts": preferred_method, chunks_cohorts = find_group_cohorts( by_, [array.chunks[ax] for ax in range(-by_.ndim, 0)], expected_groups=expected_, # when provided with cohorts, we *always* 'merge' merge=(method == "cohorts"), ) else: preferred_method = "map-reduce" chunks_cohorts = {} method = _choose_method(method, preferred_method, agg, by_, nax) if agg.chunk[0] is None and method != "blockwise": raise NotImplementedError( f"Aggregation {agg.name!r} is only implemented for dask arrays when method='blockwise'." f"Received method={method!r}" ) if ( _is_arg_reduction(agg) and method == "blockwise" and not all(nchunks == 1 for nchunks in array.numblocks[-nax:]) ): raise NotImplementedError( "arg-reductions are not supported with method='blockwise', use 'cohorts' instead." ) if nax != by_.ndim and method in ["blockwise", "cohorts"]: raise NotImplementedError( "Must reduce along all dimensions of `by` when method != 'map-reduce'." f"Received method={method!r}" ) # TODO: clean this up reindex = _validate_reindex( reindex, func, method, expected_, any_by_dask, is_duck_dask_array(array), array.dtype, ) if reindex.array_type is ReindexArrayType.SPARSE_COO: if not HAS_SPARSE: raise ImportError("Package 'sparse' must be installed to reindex to a sparse.COO array.") if not _is_reindex_sparse_supported_reduction(func): raise NotImplementedError( f"Aggregation {func=!r} is not supported when reindexing to a sparse array. " "Please raise an issue" ) if TYPE_CHECKING: assert isinstance(reindex, ReindexStrategy) assert method is not None # TODO: just do this in dask_groupby_agg # we always need some fill_value (see above) so choose the default if needed if kwargs["fill_value"] is None: kwargs["fill_value"] = agg.fill_value[agg.name] from .dask import dask_groupby_agg partial_agg = partial(dask_groupby_agg, **kwargs) # if preferred method is already blockwise, no need to rechunk if preferred_method != "blockwise" and method == "blockwise" and by_.ndim == 1: _, array = rechunk_for_blockwise(array, axis=-1, labels=by_) result, groups = partial_agg( array=array, by=by_, expected_groups=expected_, agg=agg, reindex=reindex, method=method, chunks_cohorts=chunks_cohorts, sort=sort, ) if sort and method != "map-reduce": assert len(groups) == 1 sorted_idx = np.argsort(groups[0]) # This optimization helps specifically with resampling if not _issorted(sorted_idx): result = result[..., sorted_idx] groups = (groups[0][sorted_idx],) if factorize_early: assert len(groups) == 1 (groups_,) = groups # nan group labels are factorized to -1, and preserved # now we get rid of them by reindexing # First, for "blockwise", we can have -1 repeated in different blocks # This breaks the reindexing so remove those first. if method == "blockwise" and (mask := groups_ == -1).sum(axis=-1) > 1: result = result[..., ~mask] groups_ = groups_[..., ~mask] # This reindex also handles bins with no data result = reindex_( result, from_=groups_, to=expected_, fill_value=fill_value, array_type=ReindexArrayType.AUTO, # just reindex the received array ).reshape(result.shape[:-1] + grp_shape) groups = final_groups if is_bool_array and (_is_minmax_reduction(func) or _is_first_last_reduction(func)): result = result.astype(bool) # Output of count has an int dtype. if requires_numeric and func != "count": if is_npdatetime: result = result.astype(datetime_dtype) elif is_cftime: asdelta = _to_pytimedelta(result, unit="us") nanmask = np.isnan(result) asdelta[nanmask] = datetime.timedelta(microseconds=0) result = asdelta + offset result[nanmask] = np.timedelta64("NaT") groups = map( lambda g: g.to_numpy() if isinstance(g, pd.Index) and not isinstance(g, pd.RangeIndex) else g, groups ) return (result, *groups) flox-0.11.2/flox/cubed.py000066400000000000000000000134011514775624000151610ustar00rootroot00000000000000"""Cubed-specific functions for groupby operations. This module provides Cubed-specific implementations for groupby operations. """ from __future__ import annotations from collections.abc import Sequence from functools import partial from typing import TYPE_CHECKING, Any import numpy as np import pandas as pd if TYPE_CHECKING: from .aggregations import Aggregation from .core import T_Axes, T_Engine, T_Method from .types import CubedArray, T_By from .core import ( _finalize_results, _get_chunk_reduction, _is_arg_reduction, _reduce_blockwise, ) from .reindex import ReindexStrategy from .xrutils import is_chunked_array def cubed_groupby_agg( array: CubedArray, by: T_By, agg: Aggregation, expected_groups: pd.Index | None, reindex: ReindexStrategy, axis: T_Axes = (), fill_value: Any = None, method: T_Method = "map-reduce", engine: T_Engine = "numpy", sort: bool = True, chunks_cohorts=None, ) -> tuple[CubedArray, tuple[pd.Index | np.ndarray | CubedArray]]: import cubed import cubed.core.groupby # I think _tree_reduce expects this assert isinstance(axis, Sequence) assert all(ax >= 0 for ax in axis) if method == "blockwise": assert by.ndim == 1 assert expected_groups is not None def _reduction_func(a, by, axis, start_group, num_groups): # adjust group labels to start from 0 for each chunk by_for_chunk = by - start_group expected_groups_for_chunk = pd.RangeIndex(num_groups) axis = (axis,) # convert integral axis to tuple blockwise_method = partial( _reduce_blockwise, agg=agg, axis=axis, expected_groups=expected_groups_for_chunk, fill_value=fill_value, engine=engine, sort=sort, reindex=reindex, ) out = blockwise_method(a, by_for_chunk) return out[agg.name] num_groups = len(expected_groups) result = cubed.core.groupby.groupby_blockwise( array, by, axis=axis, func=_reduction_func, num_groups=num_groups ) groups = (expected_groups,) return (result, groups) else: inds = tuple(range(array.ndim)) by_input = by # Unifying chunks is necessary for argreductions. # We need to rechunk before zipping up with the index # let's always do it anyway if not is_chunked_array(by): # chunk numpy arrays like the input array chunks = tuple(array.chunks[ax] if by.shape[ax] != 1 else (1,) for ax in range(-by.ndim, 0)) by = cubed.from_array(by, chunks=chunks, spec=array.spec) _, (array, by) = cubed.core.unify_chunks(array, inds, by, inds[-by.ndim :]) # Cubed's groupby_reduction handles the generation of "intermediates", and the # "map-reduce" combination step, so we don't have to do that here. # Only the equivalent of "_simple_combine" is supported, there is no # support for "_grouped_combine". labels_are_unknown = is_chunked_array(by_input) and expected_groups is None do_simple_combine = not _is_arg_reduction(agg) and not labels_are_unknown assert do_simple_combine assert method == "map-reduce" assert expected_groups is not None assert reindex.blockwise is True assert len(axis) == 1 # one axis/grouping def _groupby_func(a, by, axis, intermediate_dtype, num_groups): blockwise_method = partial( _get_chunk_reduction(agg.reduction_type), func=agg.chunk, fill_value=agg.fill_value["intermediate"], dtype=agg.dtype["intermediate"], reindex=reindex, user_dtype=agg.dtype["user"], axis=axis, expected_groups=expected_groups, engine=engine, sort=sort, ) out = blockwise_method(a, by) # Convert dict to one that cubed understands, dropping groups since they are # known, and the same for every block. return {f"f{idx}": intermediate for idx, intermediate in enumerate(out["intermediates"])} def _groupby_combine(a, axis, dummy_axis, dtype, keepdims): # this is similar to _simple_combine, except the dummy axis and concatenation is handled by cubed # only combine over the dummy axis, to preserve grouping along 'axis' dtype = dict(dtype) out = {} for idx, combine in enumerate(agg.simple_combine): field = f"f{idx}" out[field] = combine(a[field], axis=dummy_axis, keepdims=keepdims) return out def _groupby_aggregate(a, **kwargs): # Convert cubed dict to one that _finalize_results works with results = {"groups": expected_groups, "intermediates": a.values()} out = _finalize_results(results, agg, axis, expected_groups, reindex) return out[agg.name] # convert list of dtypes to a structured dtype for cubed intermediate_dtype = [(f"f{i}", dtype) for i, dtype in enumerate(agg.dtype["intermediate"])] dtype = agg.dtype["final"] num_groups = len(expected_groups) result = cubed.core.groupby.groupby_reduction( array, by, func=_groupby_func, combine_func=_groupby_combine, aggregate_func=_groupby_aggregate, axis=axis, intermediate_dtype=intermediate_dtype, dtype=dtype, num_groups=num_groups, ) groups = (expected_groups,) return (result, groups) __all__ = [ "cubed_groupby_agg", ] flox-0.11.2/flox/dask.py000066400000000000000000000714011514775624000150250ustar00rootroot00000000000000"""Dask-specific functions for groupby operations.""" from __future__ import annotations import itertools import operator from collections.abc import Callable, Sequence from functools import partial from numbers import Integral from typing import TYPE_CHECKING, Any import numpy as np import pandas as pd import toolz as tlz if TYPE_CHECKING: from typing import Literal from .aggregations import Aggregation from .core import T_Axes, T_Engine, T_Method from .lib import ArrayLayer from .reindex import ReindexArrayType, ReindexStrategy from .types import DaskArray, Graph, IntermediateDict, T_By T_ScanMethod = Literal["blelloch", "blockwise"] from .aggregations import Scan, scan_binary_op from .core import ( DUMMY_AXIS, _get_chunk_reduction, _reduce_blockwise, _unique, chunk_argreduce, chunk_reduce, ) from .lib import _is_arg_reduction, _is_first_last_reduction, _issorted, identity from .reindex import ( ReindexArrayType, ReindexStrategy, reindex_, ) from .scan import _finalize_scan, _zip, chunk_scan, grouped_reduce from .types import FinalResultsDict, IntermediateDict from .xrutils import is_duck_dask_array, notnull def listify_groups(x: IntermediateDict): return list(np.atleast_1d(x["groups"].squeeze())) def _find_unique_groups(x_chunk) -> np.ndarray: from dask.base import flatten from dask.utils import deepmap unique_groups = _unique(np.asarray(tuple(flatten(deepmap(listify_groups, x_chunk))))) unique_groups = unique_groups[notnull(unique_groups)] if len(unique_groups) == 0: unique_groups = np.array([np.nan]) return unique_groups def _conc2(x_chunk, key1, key2=slice(None), axis=None) -> np.ndarray: """copied from dask.array.reductions.mean_combine""" from dask.array.core import _concatenate2 from dask.utils import deepmap mapped = deepmap(lambda x: x[key1][key2], x_chunk) return _concatenate2(mapped, axes=axis) def reindex_intermediates( x: IntermediateDict, agg: Aggregation, unique_groups, array_type ) -> IntermediateDict: new_shape = x["groups"].shape[:-1] + (len(unique_groups),) newx: IntermediateDict = {"groups": np.broadcast_to(unique_groups, new_shape)} newx["intermediates"] = tuple( reindex_( v, from_=np.atleast_1d(x["groups"].squeeze()), to=pd.Index(unique_groups), fill_value=f, array_type=array_type, ) for v, f in zip(x["intermediates"], agg.fill_value["intermediate"]) ) return newx def _simple_combine( x_chunk, agg: Aggregation, axis, keepdims: bool, reindex: ReindexStrategy, is_aggregate: bool = False, ) -> IntermediateDict: """ 'Simple' combination of blockwise results. 1. After the blockwise groupby-reduce, all blocks contain a value for all possible groups, and are of the same shape; i.e. reindex must have been True 2. _expand_dims was used to insert an extra axis DUMMY_AXIS 3. Here we concatenate along DUMMY_AXIS, and then call the combine function along DUMMY_AXIS 4. At the final aggregate step, we squeeze out DUMMY_AXIS """ import warnings from dask.array.core import deepfirst from dask.utils import deepmap if not reindex.blockwise: # We didn't reindex at the blockwise step # So now reindex before combining by reducing along DUMMY_AXIS unique_groups = _find_unique_groups(x_chunk) x_chunk = deepmap( partial( reindex_intermediates, agg=agg, unique_groups=unique_groups, array_type=reindex.array_type, ), x_chunk, ) else: unique_groups = deepfirst(x_chunk)["groups"] results: IntermediateDict = {"groups": unique_groups} results["intermediates"] = [] axis_ = axis[:-1] + (DUMMY_AXIS,) for idx, combine in enumerate(agg.simple_combine): array = _conc2(x_chunk, key1="intermediates", key2=idx, axis=axis_) assert array.ndim >= 2 with warnings.catch_warnings(): warnings.filterwarnings("ignore", r"All-NaN (slice|axis) encountered") assert callable(combine) result = combine(array, axis=axis_, keepdims=True) if is_aggregate: # squeeze out DUMMY_AXIS if this is the last step i.e. called from _aggregate # can't just pass DUMMY_AXIS, because of sparse.COO result = result.squeeze(range(result.ndim)[DUMMY_AXIS]) results["intermediates"].append(result) return results def _extract_result(result_dict: FinalResultsDict, key) -> np.ndarray: from dask.array.core import deepfirst # deepfirst should be not be needed here but sometimes we receive a list of dict? return deepfirst(result_dict)[key] def _expand_dims(results: IntermediateDict) -> IntermediateDict: results["intermediates"] = tuple( np.expand_dims(array, axis=DUMMY_AXIS) for array in results["intermediates"] ) return results def _aggregate( x_chunk, combine, agg: Aggregation, expected_groups, axis, keepdims: bool, fill_value, reindex: ReindexStrategy, ) -> FinalResultsDict: """Final aggregation step of tree reduction""" from .core import _finalize_results results = combine(x_chunk, agg, axis, keepdims, is_aggregate=True) return _finalize_results(results, agg, axis, expected_groups, reindex=reindex) def _unify_chunks(array, by): from dask.array import from_array, unify_chunks inds = tuple(range(array.ndim)) # Unifying chunks is necessary for argreductions. # We need to rechunk before zipping up with the index # let's always do it anyway if not is_duck_dask_array(by): # chunk numpy arrays like the input array # This removes an extra rechunk-merge layer that would be # added otherwise chunks = tuple(array.chunks[ax] if by.shape[ax] != 1 else (1,) for ax in range(-by.ndim, 0)) by = from_array(by, chunks=chunks) _, (array, by) = unify_chunks(array, inds, by, inds[-by.ndim :]) return array, by def _grouped_combine( x_chunk, agg: Aggregation, axis: T_Axes, keepdims: bool, engine: T_Engine, is_aggregate: bool = False, sort: bool = True, ) -> IntermediateDict: """Combine intermediates step of tree reduction.""" from dask.utils import deepmap combine = agg.combine if isinstance(x_chunk, dict): # Only one block at final step; skip one extra groupby return x_chunk if len(axis) != 1: # when there's only a single axis of reduction, we can just concatenate later, # reindexing is unnecessary # I bet we can minimize the amount of reindexing for mD reductions too, but it's complicated unique_groups = _find_unique_groups(x_chunk) x_chunk = deepmap( partial( reindex_intermediates, agg=agg, unique_groups=unique_groups, array_type=ReindexArrayType.AUTO ), x_chunk, ) # these are negative axis indices useful for concatenating the intermediates neg_axis = tuple(range(-len(axis), 0)) groups = _conc2(x_chunk, "groups", axis=neg_axis) if agg.reduction_type == "argreduce": # If "nanlen" was added for masking later, we need to account for that if agg.chunk[-1] == "nanlen": slicer = slice(None, -1) else: slicer = slice(None, None) # We need to send the intermediate array values & indexes at the same time # intermediates are (value e.g. max, index e.g. argmax, counts) array_idx = tuple(_conc2(x_chunk, key1="intermediates", key2=idx, axis=axis) for idx in (0, 1)) # for a single element along axis, we don't want to run the argreduction twice # This happens when we are reducing along an axis with a single chunk. avoid_reduction = array_idx[0].shape[axis[0]] == 1 if avoid_reduction: results: IntermediateDict = { "groups": groups, "intermediates": list(array_idx), } else: results = chunk_argreduce( array_idx, groups, # count gets treated specially next func=combine[slicer], # type: ignore[arg-type] axis=axis, expected_groups=None, fill_value=agg.fill_value["intermediate"][slicer], dtype=agg.dtype["intermediate"][slicer], engine=engine, sort=sort, ) if agg.chunk[-1] == "nanlen": counts = _conc2(x_chunk, key1="intermediates", key2=2, axis=axis) if avoid_reduction: results["intermediates"].append(counts) else: # sum the counts results["intermediates"].append( chunk_reduce( counts, groups, func="sum", axis=axis, expected_groups=None, fill_value=(0,), dtype=(np.intp,), engine=engine, sort=sort, user_dtype=agg.dtype["user"], )["intermediates"][0] ) elif agg.reduction_type == "reduce": # Here we reduce the intermediates individually results = {"groups": None, "intermediates": []} for idx, (combine_, fv, dtype) in enumerate( zip(combine, agg.fill_value["intermediate"], agg.dtype["intermediate"]) ): assert combine_ is not None array = _conc2(x_chunk, key1="intermediates", key2=idx, axis=axis) if array.shape[-1] == 0: # No groups found in input data. Return to avoid a tree-reduce # step with no data. results["groups"] = groups results["intermediates"].append(array) continue reduced = chunk_reduce( array, groups, axis=axis, func=combine_, expected_groups=None, fill_value=(fv,), dtype=(dtype,), engine=engine, sort=sort, user_dtype=agg.dtype["user"], ) # we had groups so this should've been set if results["groups"] is None: results["groups"] = reduced["groups"] results["intermediates"].append(reduced["intermediates"][0]) # final pass and add keepdims=False. results["groups"] = results["groups"].squeeze() if not keepdims else results["groups"] return results def dask_groupby_agg( array: DaskArray, by: T_By, *, agg: Aggregation, expected_groups: pd.RangeIndex | None, reindex: ReindexStrategy, axis: T_Axes = (), fill_value: Any = None, method: T_Method = "map-reduce", engine: T_Engine = "numpy", sort: bool = True, chunks_cohorts=None, ) -> tuple[DaskArray, tuple[pd.Index | np.ndarray | DaskArray]]: import dask.array from dask.array.core import slices_from_chunks from dask.highlevelgraph import HighLevelGraph from .dask_array_ops import _tree_reduce # I think _tree_reduce expects this assert isinstance(axis, Sequence) assert all(ax >= 0 for ax in axis) inds = tuple(range(array.ndim)) name = f"groupby_{agg.name}" if expected_groups is None and reindex.blockwise: raise ValueError("reindex.blockwise must be False-y if expected_groups is not provided.") if method == "cohorts" and reindex.blockwise: raise ValueError("reindex.blockwise must be False-y if method is 'cohorts'.") by_input = by array, by = _unify_chunks(array, by) # tokenize here since by has already been hashed if its numpy token = dask.base.tokenize(array, by, agg, expected_groups, axis, method) # preprocess the array: # - for argreductions, this zips the index together with the array block # - not necessary for blockwise with argreductions # - if this is needed later, we can fix this then if agg.preprocess and method != "blockwise": array = agg.preprocess(array, axis=axis) # 1. We first apply the groupby-reduction blockwise to generate "intermediates" # 2. These intermediate results are combined to generate the final result using a # "map-reduce" or "tree reduction" approach. # There are two ways: # a. "_simple_combine": Where it makes sense, we tree-reduce the reduction, # NOT the groupby-reduction for a speed boost. This is what xhistogram does (effectively), # It requires that all blocks contain all groups after the initial blockwise step (1) i.e. # reindex.blockwise=True, and we must know expected_groups # b. "_grouped_combine": A more general solution where we tree-reduce the groupby reduction. # This allows us to discover groups at compute time, support argreductions, lower intermediate # memory usage (but method="cohorts" would also work to reduce memory in some cases) labels_are_unknown = is_duck_dask_array(by_input) and expected_groups is None do_grouped_combine = ( _is_arg_reduction(agg) or labels_are_unknown or (_is_first_last_reduction(agg) and array.dtype.kind != "f") ) do_simple_combine = not do_grouped_combine if method == "blockwise": # use the "non dask" code path, but applied blockwise blockwise_method = partial(_reduce_blockwise, agg=agg, fill_value=fill_value, reindex=reindex) else: # choose `chunk_reduce` or `chunk_argreduce` blockwise_method = partial( _get_chunk_reduction(agg.reduction_type), func=agg.chunk, reindex=reindex.blockwise, fill_value=agg.fill_value["intermediate"], dtype=agg.dtype["intermediate"], user_dtype=agg.dtype["user"], ) if do_simple_combine: # Add a dummy dimension that then gets reduced over blockwise_method = tlz.compose(_expand_dims, blockwise_method) # apply reduction on chunk intermediate = dask.array.blockwise( partial( blockwise_method, axis=axis, expected_groups=expected_groups if reindex.blockwise else None, engine=engine, sort=sort, ), # output indices are the same as input indices # Unlike xhistogram, we don't always know what the size of the group # dimension will be unless reindex=True inds, array, inds, by, inds[-by.ndim :], concatenate=False, dtype=array.dtype, # this is purely for show meta=array._meta, align_arrays=False, name=f"{name}-chunk-{token}", ) group_chunks: tuple[tuple[int | float, ...]] if method in ["map-reduce", "cohorts"]: combine: Callable[..., IntermediateDict] = ( partial(_simple_combine, reindex=reindex) if do_simple_combine else partial(_grouped_combine, engine=engine, sort=sort) ) tree_reduce = partial( dask.array.reductions._tree_reduce, name=f"{name}-simple-reduce", dtype=array.dtype, axis=axis, keepdims=True, concatenate=False, ) aggregate = partial(_aggregate, combine=combine, agg=agg, fill_value=fill_value, reindex=reindex) # Each chunk of `reduced`` is really a dict mapping # 1. reduction name to array # 2. "groups" to an array of group labels # Note: it does not make sense to interpret axis relative to # shape of intermediate results after the blockwise call if method == "map-reduce": reduced = tree_reduce( intermediate, combine=partial(combine, agg=agg), aggregate=partial(aggregate, expected_groups=expected_groups), ) if labels_are_unknown: groups = _extract_unknown_groups(reduced, dtype=by.dtype) group_chunks = ((np.nan,),) else: assert expected_groups is not None groups = (expected_groups,) group_chunks = ((len(expected_groups),),) elif method == "cohorts": assert chunks_cohorts block_shape = array.blocks.shape[-len(axis) :] out_name = f"{name}-reduce-{method}-{token}" groups_ = [] chunks_as_array = tuple(np.array(c) for c in array.chunks) dsk: Graph = {} for icohort, (blks, cohort) in enumerate(chunks_cohorts.items()): cohort_index = pd.Index(cohort) reindexer = ( partial( reindex_intermediates, agg=agg, unique_groups=cohort_index, array_type=reindex.array_type, ) if do_simple_combine else identity ) subset = subset_to_blocks(intermediate, blks, block_shape, reindexer, chunks_as_array) dsk |= subset.layer # type: ignore[operator] # now that we have reindexed, we can set reindex=True explicitlly new_reindex = ReindexStrategy(blockwise=do_simple_combine, array_type=reindex.array_type) _tree_reduce( subset, out_dsk=dsk, name=out_name, block_index=icohort, axis=axis, combine=partial(combine, agg=agg, reindex=new_reindex, keepdims=True), aggregate=partial( aggregate, expected_groups=cohort_index, reindex=new_reindex, keepdims=True ), ) # This is done because pandas promotes to 64-bit types when an Index is created # So we use the index to generate the return value for consistency with "map-reduce" # This is important on windows groups_.append(cohort_index.values) graph = HighLevelGraph.from_collections(out_name, dsk, dependencies=[intermediate]) out_chunks = list(array.chunks) out_chunks[axis[-1]] = tuple(len(c) for c in chunks_cohorts.values()) for ax in axis[:-1]: out_chunks[ax] = (1,) reduced = dask.array.Array(graph, out_name, out_chunks, meta=array._meta) groups = (np.concatenate(groups_),) group_chunks = (tuple(len(cohort) for cohort in groups_),) elif method == "blockwise": reduced = intermediate if reindex.blockwise: if TYPE_CHECKING: assert expected_groups is not None # TODO: we could have `expected_groups` be a dask array with appropriate chunks # for now, we have a numpy array that is interpreted as listing all group labels # that are present in every chunk groups = (expected_groups,) group_chunks = ((len(expected_groups),),) else: # TODO: use chunks_cohorts here; hard because chunks_cohorts does not include all-NaN blocks # but the array after applying the blockwise op; does. We'd have to insert a subsetting op. # Here one input chunk → one output chunks # find number of groups in each chunk, this is needed for output chunks # along the reduced axis # TODO: this logic is very specialized for the resampling case slices = slices_from_chunks(tuple(array.chunks[ax] for ax in axis)) groups_in_block = tuple(_unique(by_input[slc]) for slc in slices) groups = (np.concatenate(groups_in_block),) ngroups_per_block = tuple(len(grp) for grp in groups_in_block) group_chunks = (ngroups_per_block,) else: raise ValueError(f"Unknown method={method}.") # Adjust output for any new dimensions added, example for multiple quantiles new_dims_shape = tuple(dim.size for dim in agg.new_dims if not dim.is_scalar) new_inds = tuple(range(-len(new_dims_shape), 0)) out_inds = new_inds + inds[: -len(axis)] + (inds[-1],) output_chunks = new_dims_shape + reduced.chunks[: -len(axis)] + group_chunks new_axes = dict(zip(new_inds, new_dims_shape)) if method == "blockwise" and len(axis) > 1: # The final results are available but the blocks along axes # need to be reshaped to axis=-1 # I don't know that this is possible with blockwise # All other code paths benefit from an unmaterialized Blockwise layer reduced = _collapse_blocks_along_axes(reduced, axis, group_chunks) # Can't use map_blocks because it forces concatenate=True along drop_axes, result = dask.array.blockwise( _extract_result, out_inds, reduced, inds, adjust_chunks=dict(zip(out_inds, output_chunks)), key=agg.name, name=f"{name}-{token}", concatenate=False, new_axes=new_axes, meta=reindex.get_dask_meta(array, dtype=agg.dtype["final"], fill_value=agg.fill_value[agg.name]), ) return (result, groups) def dask_groupby_scan(array, by, axes: T_Axes, agg: Scan, method: T_ScanMethod = "blelloch") -> DaskArray: """Grouped scan for dask arrays. Parameters ---------- array : DaskArray Input array to scan. by : DaskArray Group labels array, must have same chunks as array along scan axis. axes : T_Axes Tuple of axes to scan along (must be single axis). agg : Scan Scan aggregation specification. method : {"blelloch", "blockwise"}, optional Scan method to use: - "blelloch": Blelloch parallel prefix scan algorithm, allows scanning across chunk boundaries using tree reduction. Default. - "blockwise": Each chunk is processed independently. Only valid when all members of each group are contained within a single chunk. Returns ------- DaskArray Result of the grouped scan with same shape and chunks as input. """ from dask.array import map_blocks from dask.array.reductions import cumreduction as scan from dask.base import tokenize if len(axes) > 1: raise NotImplementedError("Scans are only supported along a single axis.") (axis,) = axes array, by = _unify_chunks(array, by) # Include method in token to differentiate task graphs token = tokenize(array, by, agg, axes, method) # 1. zip together group indices & array zipped = map_blocks( _zip, by, array, dtype=array.dtype, meta=array._meta, name=f"groupby-scan-preprocess-{token}", ) # 2. Run the scan if method == "blockwise": # Apply chunk_scan blockwise - each block independently scan_func = partial(chunk_scan, agg=agg, axis=axis, dtype=agg.dtype) scanned = map_blocks( scan_func, zipped, dtype=agg.dtype, meta=array._meta, name=f"groupby-scan-{token}", ) else: # Use Blelloch parallel prefix scan algorithm scan_ = partial(chunk_scan, agg=agg) # dask tokenizing error workaround scan_.__name__ = scan_.func.__name__ # type: ignore[attr-defined] scanned = scan( func=scan_, binop=partial(scan_binary_op, agg=agg), ident=agg.identity, x=zipped, axis=axis, # TODO: support method="sequential" here. method="blelloch", preop=partial(grouped_reduce, agg=agg), dtype=agg.dtype, ) # 3. Extract final result result = map_blocks( partial(_finalize_scan, dtype=agg.dtype), scanned, dtype=agg.dtype, name=f"groupby-scan-finalize-{token}", ) assert result.chunks == array.chunks return result def _normalize_indexes(ndim: int, flatblocks: Sequence[int], blkshape: tuple[int, ...]) -> tuple: """ .blocks accessor can only accept one iterable at a time, but can handle multiple slices. To minimize tasks and layers, we normalize to produce slices along as many axes as possible, and then repeatedly apply any remaining iterables in a loop. TODO: move this upstream """ unraveled = np.unravel_index(flatblocks, blkshape) normalized: list[int | slice | list[int]] = [] for ax, idx in enumerate(unraveled): i = _unique(idx).squeeze() if i.ndim == 0: normalized.append(i.item()) else: if len(i) == blkshape[ax] and np.array_equal(i, np.arange(blkshape[ax])): normalized.append(slice(None)) elif _issorted(i) and np.array_equal(i, np.arange(i[0], i[-1] + 1)): start = None if i[0] == 0 else i[0] stop = i[-1] + 1 stop = None if stop == blkshape[ax] else stop normalized.append(slice(start, stop)) else: normalized.append(list(i)) full_normalized = (slice(None),) * (ndim - len(normalized)) + tuple(normalized) # has no iterables noiter = list(i if not hasattr(i, "__len__") else slice(None) for i in full_normalized) # has all iterables alliter = {ax: i for ax, i in enumerate(full_normalized) if hasattr(i, "__len__")} mesh = dict(zip(alliter.keys(), np.ix_(*alliter.values()))) # type: ignore[arg-type, var-annotated] full_tuple = tuple(i if ax not in mesh else mesh[ax] for ax, i in enumerate(noiter)) return full_tuple def subset_to_blocks( array: DaskArray, flatblocks: Sequence[int], blkshape: tuple[int, ...] | None = None, reindexer=identity, chunks_as_array: tuple[np.ndarray, ...] | None = None, ) -> ArrayLayer: """ Advanced indexing of .blocks such that we always get a regular array back. Parameters ---------- array : dask.array flatblocks : flat indices of blocks to extract blkshape : shape of blocks with which to unravel flatblocks Returns ------- dask.array """ from dask.base import tokenize from .lib import ArrayLayer if blkshape is None: blkshape = array.blocks.shape if chunks_as_array is None: chunks_as_array = tuple(np.array(c) for c in array.chunks) index = _normalize_indexes(array.ndim, flatblocks, blkshape) # These rest is copied from dask.array.core.py with slight modifications index = tuple(slice(k, k + 1) if isinstance(k, Integral) else k for k in index) name = "groupby-cohort-" + tokenize(array, index) new_keys = array._key_array[index] squeezed = tuple(np.squeeze(i) if isinstance(i, np.ndarray) else i for i in index) chunks = tuple(tuple(c[i].tolist()) for c, i in zip(chunks_as_array, squeezed)) keys = itertools.product(*(range(len(c)) for c in chunks)) layer: Graph = {(name,) + key: (reindexer, tuple(new_keys[key].tolist())) for key in keys} return ArrayLayer(layer=layer, chunks=chunks, name=name) def _extract_unknown_groups(reduced, dtype) -> tuple[DaskArray]: import dask.array from dask.highlevelgraph import HighLevelGraph groups_token = f"group-{reduced.name}" first_block = reduced.ndim * (0,) layer: Graph = {(groups_token, 0): (operator.getitem, (reduced.name, *first_block), "groups")} groups: tuple[DaskArray] = ( dask.array.Array( HighLevelGraph.from_collections(groups_token, layer, dependencies=[reduced]), groups_token, chunks=((np.nan,),), meta=np.array([], dtype=dtype), ), ) return groups def _collapse_blocks_along_axes(reduced: DaskArray, axis: T_Axes, group_chunks) -> DaskArray: import dask.array from dask.highlevelgraph import HighLevelGraph nblocks = tuple(reduced.numblocks[ax] for ax in axis) output_chunks = reduced.chunks[: -len(axis)] + ((1,) * (len(axis) - 1),) + group_chunks # extract results from the dict ochunks = tuple(range(len(chunks_v)) for chunks_v in output_chunks) layer2: dict[tuple, tuple] = {} name = f"reshape-{reduced.name}" for ochunk in itertools.product(*ochunks): inchunk = ochunk[: -len(axis)] + np.unravel_index(ochunk[-1], nblocks) layer2[(name, *ochunk)] = (reduced.name, *inchunk) layer2: Graph return dask.array.Array( HighLevelGraph.from_collections(name, layer2, dependencies=[reduced]), name, chunks=output_chunks, dtype=reduced.dtype, ) __all__ = [ "_collapse_blocks_along_axes", "_extract_unknown_groups", "_grouped_combine", "_normalize_indexes", "_unify_chunks", "dask_groupby_agg", "dask_groupby_scan", "subset_to_blocks", ] flox-0.11.2/flox/dask_array_ops.py000066400000000000000000000073661514775624000171150ustar00rootroot00000000000000import builtins import math from functools import lru_cache, partial from itertools import product from numbers import Integral import dask import pandas as pd from dask import config from dask.base import normalize_token from dask.blockwise import lol_tuples from packaging.version import Version from toolz import partition_all from .lib import ArrayLayer from .types import Graph if Version(dask.__version__) <= Version("2025.03.1"): # workaround for https://github.com/dask/dask/issues/11862 @normalize_token.register(pd.RangeIndex) def normalize_range_index(x): return normalize_token(type(x)), x.start, x.stop, x.step, x.dtype, x.name # _tree_reduce and partial_reduce are copied from dask.array.reductions # They have been modified to work purely with graphs, and without creating new Array layers # in the graph. The `block_index` kwarg is new and avoids a concatenation by simply setting the right # key initially def _tree_reduce( x: ArrayLayer, *, name: str, out_dsk: Graph, aggregate, axis: tuple[int, ...], block_index: int, split_every=None, combine=None, ): # Normalize split_every split_every = split_every or config.get("split_every", 4) if isinstance(split_every, dict): split_every = {k: split_every.get(k, 2) for k in axis} elif isinstance(split_every, Integral): n = builtins.max(int(split_every ** (1 / (len(axis) or 1))), 2) split_every = dict.fromkeys(axis, n) else: raise ValueError("split_every must be a int or a dict") numblocks = tuple(len(c) for c in x.chunks) out_chunks = x.chunks # Reduce across intermediates depth = 1 for i, n in enumerate(numblocks): if i in split_every and split_every[i] != 1: depth = int(builtins.max(depth, math.ceil(math.log(n, split_every[i])))) func = partial(combine or aggregate, axis=axis) agg_dep_name = x.name for level in range(depth - 1): newname = name + f"-{block_index}-partial-{level}" out_dsk, out_chunks = partial_reduce( func, out_dsk, chunks=out_chunks, split_every=split_every, name=newname, dep_name=agg_dep_name, axis=axis, ) agg_dep_name = newname func = partial(aggregate, axis=axis) return partial_reduce( func, out_dsk, chunks=out_chunks, split_every=split_every, name=name, dep_name=agg_dep_name, axis=axis, block_index=block_index, ) def partial_reduce( func, dsk, *, chunks: tuple[tuple[int, ...], ...], name: str, dep_name: str, split_every: dict[int, int], axis: tuple[int, ...], block_index: int | None = None, ): ndim = len(chunks) keys, parts, out_chunks = get_parts(tuple(split_every.items()), chunks) for k, p in zip(keys, product(*parts)): free = {i: j[0] for (i, j) in enumerate(p) if len(j) == 1 and i not in split_every} dummy = dict(i for i in enumerate(p) if i[0] in split_every) g = lol_tuples((dep_name,), range(ndim), free, dummy) assert dep_name != name if block_index is not None: k = (*k[:-1], block_index) dsk[(name,) + k] = (func, g) return dsk, out_chunks @lru_cache def get_parts(split_every_items, chunks): numblocks = tuple(len(c) for c in chunks) split_every = dict(split_every_items) parts = [list(partition_all(split_every.get(i, 1), range(n))) for (i, n) in enumerate(numblocks)] keys = tuple(product(*map(range, map(len, parts)))) out_chunks = tuple( tuple(1 for p in partition_all(split_every[i], c)) if i in split_every else c for (i, c) in enumerate(chunks) ) return keys, parts, out_chunks flox-0.11.2/flox/factorize.py000066400000000000000000000240651514775624000160750ustar00rootroot00000000000000"""Factorization functions for groupby operations. This module provides functions for factorizing groupby labels. """ from __future__ import annotations import itertools import math from concurrent.futures import ThreadPoolExecutor from functools import partial, reduce from typing import TYPE_CHECKING, Literal, cast, overload import numpy as np import pandas as pd from .types import FactorizeKwargs, FactorProps from .xrutils import is_duck_dask_array, isnull if TYPE_CHECKING: from .core import T_Axes, T_By, T_Bys, T_ExpectIndexOptTuple def offset_labels(labels: np.ndarray, ngroups: int) -> tuple[np.ndarray, int]: """ Offset group labels by dimension. This is used when we reduce over a subset of the dimensions of by. It assumes that the reductions dimensions have been flattened in the last dimension Copied from xhistogram & https://stackoverflow.com/questions/46256279/bin-elements-per-row-vectorized-2d-bincount-for-numpy """ assert labels.ndim > 1 offset: np.ndarray = ( labels + np.arange(math.prod(labels.shape[:-1])).reshape((*labels.shape[:-1], -1)) * ngroups ) # -1 indicates NaNs. preserve these otherwise we aggregate in the wrong groups! offset[labels == -1] = -1 size: int = math.prod(labels.shape[:-1]) * ngroups return offset, size def _factorize_single(by, expect, *, sort: bool, reindex: bool) -> tuple[pd.Index, np.ndarray]: flat = by.reshape(-1) if isinstance(expect, pd.RangeIndex): # idx is a view of the original `by` array # copy here so we don't have a race condition with the # group_idx[nanmask] = nan_sentinel assignment later # this is important in shared-memory parallelism with dask # TODO: figure out how to avoid this idx = flat.copy() found_groups = cast(pd.Index, expect) # TODO: fix by using masked integers idx[idx > expect[-1]] = -1 elif isinstance(expect, pd.IntervalIndex): if expect.closed == "both": raise NotImplementedError bins = np.concatenate([expect.left.to_numpy(), expect.right.to_numpy()[[-1]]]) # digitize is 0 or idx.max() for values outside the bounds of all intervals # make it behave like pd.cut which uses -1: if len(bins) > 1: right = expect.closed_right # For datetime64, convert both flat and bins to int64 for comparison is_datetime = bins.dtype.kind == "M" flat_for_digitize = flat.view(np.int64) if is_datetime else flat bins_for_digitize = bins.view(np.int64) if is_datetime else bins idx = np.digitize( flat_for_digitize, bins=bins_for_digitize, right=right, ) idx -= 1 within_bins = ( flat_for_digitize <= bins_for_digitize.max() if right else flat_for_digitize < bins_for_digitize.max() ) idx[~within_bins] = -1 else: idx = np.zeros_like(flat, dtype=np.intp) - 1 found_groups = cast(pd.Index, expect) else: if expect is not None and reindex: sorter = np.argsort(expect) groups = expect[sorter] if sort else expect idx = np.searchsorted(expect, flat, sorter=sorter) mask = ~np.isin(flat, expect) | isnull(flat) | (idx == len(expect)) if not sort: # idx is the index in to the sorted array. # if we didn't want sorting, unsort it back idx[(idx == len(expect),)] = -1 idx = sorter[(idx,)] idx[mask] = -1 else: idx, groups = pd.factorize(flat, sort=sort) found_groups = cast(pd.Index, groups) return (found_groups, idx.reshape(by.shape)) def _ravel_factorized(*factorized: np.ndarray, grp_shape: tuple[int, ...]) -> np.ndarray: group_idx = np.ravel_multi_index(factorized, grp_shape, mode="wrap") # NaNs; as well as values outside the bins are coded by -1 # Restore these after the raveling nan_by_mask = reduce(np.logical_or, [(f == -1) for f in factorized]) group_idx[nan_by_mask] = -1 return group_idx @overload def factorize_( by: T_Bys, axes: T_Axes, *, fastpath: Literal[True], expected_groups: T_ExpectIndexOptTuple | None = None, reindex: bool = False, sort: bool = True, ) -> tuple[np.ndarray, tuple[pd.Index, ...], tuple[int, ...], int, int, None]: ... @overload def factorize_( by: T_Bys, axes: T_Axes, *, expected_groups: T_ExpectIndexOptTuple | None = None, reindex: bool = False, sort: bool = True, fastpath: Literal[False] = False, ) -> tuple[np.ndarray, tuple[pd.Index, ...], tuple[int, ...], int, int, FactorProps]: ... @overload def factorize_( by: T_Bys, axes: T_Axes, *, expected_groups: T_ExpectIndexOptTuple | None = None, reindex: bool = False, sort: bool = True, fastpath: bool = False, ) -> tuple[np.ndarray, tuple[pd.Index, ...], tuple[int, ...], int, int, FactorProps | None]: ... def factorize_( by: T_Bys, axes: T_Axes, *, expected_groups: T_ExpectIndexOptTuple | None = None, reindex: bool = False, sort: bool = True, fastpath: bool = False, ) -> tuple[np.ndarray, tuple[pd.Index, ...], tuple[int, ...], int, int, FactorProps | None]: """ Returns an array of integer codes for groups (and associated data) by wrapping pd.cut and pd.factorize (depending on isbin). This method handles reindex and sort so that we don't spend time reindexing / sorting a possibly large results array. Instead we set up the appropriate integer codes (group_idx) so that the results come out in the appropriate order. """ if expected_groups is None: expected_groups = (None,) * len(by) if len(by) > 2: with ThreadPoolExecutor() as executor: futures = [ executor.submit(partial(_factorize_single, sort=sort, reindex=reindex), groupvar, expect) for groupvar, expect in zip(by, expected_groups) ] results = tuple(f.result() for f in futures) else: results = tuple( _factorize_single(groupvar, expect, sort=sort, reindex=reindex) for groupvar, expect in zip(by, expected_groups) ) found_groups = tuple(r[0] for r in results) factorized = [r[1] for r in results] grp_shape = tuple(len(grp) for grp in found_groups) ngroups = math.prod(grp_shape) if len(by) > 1: group_idx = _ravel_factorized(*factorized, grp_shape=grp_shape) else: (group_idx,) = factorized if fastpath: return group_idx, found_groups, grp_shape, ngroups, ngroups, None if len(axes) == 1 and by[0].ndim > 1: # Not reducing along all dimensions of by # this is OK because for 3D by and axis=(1,2), # we collapse to a 2D by and axis=-1 offset_group = True group_idx, size = offset_labels(group_idx.reshape(by[0].shape), ngroups) else: size = ngroups offset_group = False # numpy_groupies cannot deal with group_idx = -1 # so we'll add use ngroups as the sentinel # note we cannot simply remove the NaN locations; # that would mess up argmax, argmin nan_sentinel = size if offset_group else ngroups nanmask = group_idx == -1 if nanmask.any(): # bump it up so there's a place to assign values to the nan_sentinel index size += 1 group_idx[nanmask] = nan_sentinel props = FactorProps(offset_group, nan_sentinel, nanmask) return group_idx, tuple(found_groups), grp_shape, ngroups, size, props def _lazy_factorize_wrapper(*by: T_By, **kwargs) -> np.ndarray: group_idx, *_ = factorize_(by, **kwargs) return group_idx def _factorize_multiple( by: T_Bys, expected_groups: T_ExpectIndexOptTuple, any_by_dask: bool, sort: bool = True, ) -> tuple[tuple[np.ndarray], tuple[pd.Index, ...], tuple[int, ...]]: kwargs: FactorizeKwargs = dict( axes=(), # always (), we offset later if necessary. fastpath=True, # This is the only way it makes sense I think. # reindex controls what's actually allocated in chunk_reduce # At this point, we care about an accurate conversion to codes. reindex=True, sort=sort, ) if any_by_dask: import dask.array from . import dask_array_ops # noqa # unifying chunks will make sure all arrays in `by` are dask arrays # with compatible chunks, even if there was originally a numpy array inds = tuple(range(by[0].ndim)) for by_, expect in zip(by, expected_groups): if expect is None and is_duck_dask_array(by_): raise ValueError("Please provide expected_groups when grouping by a dask array.") found_groups = tuple( pd.Index(pd.unique(by_.reshape(-1))) if expect is None else expect for by_, expect in zip(by, expected_groups) ) grp_shape = tuple(map(len, found_groups)) chunks, by_chunked = dask.array.unify_chunks(*itertools.chain(*zip(by, (inds,) * len(by)))) group_idxs = [ dask.array.map_blocks( _lazy_factorize_wrapper, by_, expected_groups=(expect_,), meta=np.array((), dtype=np.int64), **kwargs, ) for by_, expect_ in zip(by_chunked, expected_groups) ] # This could be avoied but we'd use `np.where` # instead `_ravel_factorized` instead i.e. a copy. group_idx = dask.array.map_blocks( _ravel_factorized, *group_idxs, grp_shape=grp_shape, chunks=tuple(chunks.values()), dtype=np.int64 ) else: kwargs["by"] = by group_idx, found_groups, grp_shape, *_ = factorize_(**kwargs, expected_groups=expected_groups) return (group_idx,), found_groups, grp_shape __all__ = [ "FactorizeKwargs", "FactorProps", "_factorize_multiple", "_factorize_single", "_lazy_factorize_wrapper", "_ravel_factorized", "factorize_", "offset_labels", ] flox-0.11.2/flox/lib.py000066400000000000000000000100371514775624000146470ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass from typing import TYPE_CHECKING, TypeAlias, TypeVar from .types import DaskArray, Graph from .xrutils import is_duck_dask_array, module_available if TYPE_CHECKING: from .aggregations import Aggregation T_Agg: TypeAlias = str | Aggregation T = TypeVar("T") try: import dask.array as da dask_array_type = da.Array except ImportError: dask_array_type = () # type: ignore[assignment, misc] try: import sparse sparse_array_type = sparse.COO except ImportError: sparse_array_type = () HAS_SPARSE = module_available("sparse") @dataclass class ArrayLayer: name: str layer: Graph chunks: tuple[tuple[int, ...], ...] def to_array(self, dep: DaskArray) -> DaskArray: from dask.array import Array from dask.highlevelgraph import HighLevelGraph graph = HighLevelGraph.from_collections(self.name, self.layer, dependencies=[dep]) return Array(graph, self.name, self.chunks, meta=dep._meta) def _postprocess_numbagg(result, *, func, fill_value, size, seen_groups): """Account for numbagg not providing a fill_value kwarg.""" import numpy as np from .aggregate_numbagg import DEFAULT_FILL_VALUE if not isinstance(func, str) or func not in DEFAULT_FILL_VALUE: return result # The condition needs to be # len(found_groups) < size; if so we mask with fill_value (?) default_fv = DEFAULT_FILL_VALUE[func] needs_masking = fill_value is not None and not np.array_equal(fill_value, default_fv, equal_nan=True) groups = np.arange(size) if needs_masking: mask = np.isin(groups, seen_groups, assume_unique=True, invert=True) if mask.any(): if isinstance(result, sparse_array_type): result.fill_value = fill_value else: result[..., groups[mask]] = fill_value return result def identity(x: T) -> T: return x def _issorted(arr, ascending=True) -> bool: if ascending: return bool((arr[:-1] <= arr[1:]).all()) else: return bool((arr[:-1] >= arr[1:]).all()) def _should_auto_rechunk_blockwise(method, array, any_by_dask: bool, by) -> bool: """Check if we should attempt automatic rechunking for blockwise operations.""" return method is None and is_duck_dask_array(array) and not any_by_dask and by.ndim == 1 and _issorted(by) def _is_nanlen(reduction) -> bool: return isinstance(reduction, str) and reduction == "nanlen" def _is_arg_reduction(func: T_Agg) -> bool: from .aggregations import Aggregation if isinstance(func, str) and func in ["argmin", "argmax", "nanargmax", "nanargmin"]: return True if isinstance(func, Aggregation) and func.reduction_type == "argreduce": return True return False def _is_minmax_reduction(func: T_Agg) -> bool: return not _is_arg_reduction(func) and (isinstance(func, str) and ("max" in func or "min" in func)) def _is_first_last_reduction(func: T_Agg) -> bool: from .aggregations import Aggregation if isinstance(func, Aggregation): func = func.name return func in ["nanfirst", "nanlast", "first", "last"] def _is_bool_supported_reduction(func: T_Agg) -> bool: from .aggregations import Aggregation if isinstance(func, Aggregation): func = func.name return ( func in ["all", "any"] # TODO: enable in npg # or _is_first_last_reduction(func) # or _is_minmax_reduction(func) ) def _is_sparse_supported_reduction(func: T_Agg) -> bool: from .aggregations import SCANS, Aggregation if isinstance(func, Aggregation): func = func.name if func in SCANS: return False return not _is_arg_reduction(func) and any(f in func for f in ["len", "sum", "max", "min", "mean"]) def _is_reindex_sparse_supported_reduction(func: T_Agg) -> bool: from .aggregations import Aggregation if isinstance(func, Aggregation): func = func.name return HAS_SPARSE and all(f not in func for f in ["first", "last", "prod", "var", "std"]) flox-0.11.2/flox/multiarray.py000066400000000000000000000063671514775624000163050ustar00rootroot00000000000000from collections.abc import Callable from typing import Self import numpy as np MULTIARRAY_HANDLED_FUNCTIONS: dict[Callable, Callable] = {} class MultiArray: arrays: tuple[np.ndarray, ...] def __init__(self, arrays): self.arrays = arrays assert all(arrays[0].shape == a.shape for a in arrays), "Expect all arrays to have the same shape" def astype(self, dt, **kwargs) -> Self: return type(self)(tuple(array.astype(dt, **kwargs) for array in self.arrays)) def reshape(self, shape, **kwargs) -> Self: return type(self)(tuple(array.reshape(shape, **kwargs) for array in self.arrays)) def squeeze(self, axis=None) -> Self: return type(self)(tuple(array.squeeze(axis) for array in self.arrays)) def __setitem__(self, key, value) -> None: assert len(value) == len(self.arrays) for array, val in zip(self.arrays, value): array[key] = val def __array_function__(self, func, types, args, kwargs): if func not in MULTIARRAY_HANDLED_FUNCTIONS: return NotImplemented # Note: this allows subclasses that don't override # __array_function__ to handle MyArray objects # if not all(issubclass(t, MyArray) for t in types): # I can't see this being relevant at all for this code, but maybe it's safer to leave it in? # return NotImplemented return MULTIARRAY_HANDLED_FUNCTIONS[func](*args, **kwargs) # Shape is needed, seems likely that the other two might be # Making some strong assumptions here that all the arrays are the same shape, and I don't really like this @property def dtype(self) -> np.dtype: return self.arrays[0].dtype @property def shape(self) -> tuple[int, ...]: return self.arrays[0].shape @property def ndim(self) -> int: return self.arrays[0].ndim def __getitem__(self, key) -> Self: return type(self)([array[key] for array in self.arrays]) def implements(numpy_function): """Register an __array_function__ implementation for MyArray objects.""" def decorator(func): MULTIARRAY_HANDLED_FUNCTIONS[numpy_function] = func return func return decorator @implements(np.expand_dims) def expand_dims(multiarray, axis) -> MultiArray: return MultiArray(tuple(np.expand_dims(a, axis) for a in multiarray.arrays)) @implements(np.concatenate) def concatenate(multiarrays, axis) -> MultiArray: n_arrays = len(multiarrays[0].arrays) for ma in multiarrays[1:]: assert len(ma.arrays) == n_arrays return MultiArray( tuple(np.concatenate(tuple(ma.arrays[i] for ma in multiarrays), axis) for i in range(n_arrays)) ) @implements(np.transpose) def transpose(multiarray, axes) -> MultiArray: return MultiArray(tuple(np.transpose(a, axes) for a in multiarray.arrays)) @implements(np.squeeze) def squeeze(multiarray, axis) -> MultiArray: return MultiArray(tuple(np.squeeze(a, axis) for a in multiarray.arrays)) @implements(np.full) def full(shape, fill_values, *args, **kwargs) -> MultiArray: """All arguments except fill_value are shared by each array in the MultiArray. Iterate over fill_values to create arrays """ return MultiArray(tuple(np.full(shape, fv, *args, **kwargs) for fv in fill_values)) flox-0.11.2/flox/options.py000066400000000000000000000037321514775624000156000ustar00rootroot00000000000000""" Started from xarray options.py; vendored from cf-xarray """ import copy from collections.abc import MutableMapping from typing import Any OPTIONS: MutableMapping[str, Any] = { # Thresholds below which we will automatically rechunk to blockwise if it makes sense # 1. Fractional change in number of chunks after rechunking "rechunk_blockwise_num_chunks_threshold": 0.25, # 2. Fractional change in max chunk size after rechunking "rechunk_blockwise_chunk_size_threshold": 1.5, # 3. If input arrays have chunk size smaller than `dask.array.chunk-size`, # then adjust chunks to meet that size first. # "rechunk.blockwise.chunk_size_factor": 1.5, } class set_options: # numpydoc ignore=PR01,PR02 """ Set options for cf-xarray in a controlled context. Parameters ---------- rechunk_blockwise_num_chunks_threshold : float Rechunk if fractional change in number of chunks after rechunking is less than this amount. rechunk_blockwise_chunk_size_threshold: float Rechunk if fractional change in max chunk size after rechunking is less than this threshold. Examples -------- You can use ``set_options`` either as a context manager: >>> import flox >>> with flox.set_options(rechunk_blockwise_num_chunks_threshold=1): ... pass Or to set global options: >>> flox.set_options(rechunk_blockwise_num_chunks_threshold=1): """ def __init__(self, **kwargs): self.old = {} for k in kwargs: if k not in OPTIONS: raise ValueError(f"argument name {k!r} is not in the set of valid options {set(OPTIONS)!r}") self.old[k] = OPTIONS[k] self._apply_update(kwargs) def _apply_update(self, options_dict): options_dict = copy.deepcopy(options_dict) OPTIONS.update(options_dict) def __enter__(self): return def __exit__(self, type, value, traceback): self._apply_update(self.old) flox-0.11.2/flox/py.typed000066400000000000000000000000001514775624000152130ustar00rootroot00000000000000flox-0.11.2/flox/rechunk.py000066400000000000000000000162351514775624000155460ustar00rootroot00000000000000"""Rechunking functions for groupby operations. This module provides functions for rechunking arrays to optimize groupby operations. """ from __future__ import annotations import logging from collections.abc import Sequence from typing import TYPE_CHECKING import numpy as np import numpy_groupies as npg import pandas as pd from .aggregations import _atleast_1d from .cache import memoize from .factorize import factorize_ from .options import OPTIONS if TYPE_CHECKING: from .core import T_Axis, T_MethodOpt from .types import DaskArray logger = logging.getLogger("flox") @memoize def _get_optimal_chunks_for_groups(chunks, labels): chunkidx = np.cumsum(chunks) - 1 # what are the groups at chunk boundaries labels_at_chunk_bounds = pd.unique(labels[chunkidx]) # what's the last index of all groups last_indexes = npg.aggregate_numpy.aggregate(labels, np.arange(len(labels)), func="last") # what's the last index of groups at the chunk boundaries. lastidx = last_indexes[labels_at_chunk_bounds] if len(chunkidx) == len(lastidx) and (chunkidx == lastidx).all(): return chunks first_indexes = npg.aggregate_numpy.aggregate(labels, np.arange(len(labels)), func="first") firstidx = first_indexes[labels_at_chunk_bounds] newchunkidx = [0] for c, f, l in zip(chunkidx, firstidx, lastidx): # noqa Δf = abs(c - f) Δl = abs(c - l) if c == 0 or newchunkidx[-1] > l: continue f = f.item() # noqa l = l.item() # noqa if Δf < Δl and f > newchunkidx[-1]: newchunkidx.append(f) else: newchunkidx.append(l + 1) if newchunkidx[-1] != chunkidx[-1] + 1: newchunkidx.append(chunkidx[-1] + 1) newchunks = np.diff(newchunkidx) assert sum(newchunks) == sum(chunks) return tuple(newchunks) def rechunk_for_cohorts( array: DaskArray, axis: T_Axis, labels: np.ndarray, force_new_chunk_at: Sequence, chunksize: int | None = None, ignore_old_chunks: bool = False, debug: bool = False, ) -> DaskArray: """ Rechunks array so that each new chunk contains groups that always occur together. Parameters ---------- array : dask.array.Array array to rechunk axis : int Axis to rechunk labels : np.ndarray 1D Group labels to align chunks with. This routine works well when ``labels`` has repeating patterns: e.g. ``1, 2, 3, 1, 2, 3, 4, 1, 2, 3`` though there is no requirement that the pattern must contain sequences. force_new_chunk_at : Sequence Labels at which we always start a new chunk. For the example ``labels`` array, this would be `1`. chunksize : int, optional nominal chunk size. Chunk size is exceeded when the label in ``force_new_chunk_at`` is less than ``chunksize//2`` elements away. If None, uses median chunksize along axis. Returns ------- dask.array.Array rechunked array """ if chunksize is None: chunksize = np.median(array.chunks[axis]).astype(int) if len(labels) != array.shape[axis]: raise ValueError( "labels must be equal to array.shape[axis]. " f"Received length {len(labels)}. Expected length {array.shape[axis]}" ) force_new_chunk_at = _atleast_1d(force_new_chunk_at) oldchunks = array.chunks[axis] oldbreaks = np.insert(np.cumsum(oldchunks), 0, 0) if debug: labels_at_breaks = labels[oldbreaks[:-1]] print(labels_at_breaks[:40]) isbreak = np.isin(labels, force_new_chunk_at) if not np.any(isbreak): raise ValueError("One or more labels in ``force_new_chunk_at`` not present in ``labels``.") divisions = [] counter = 1 for idx, lab in enumerate(labels): if lab in force_new_chunk_at or idx == 0: divisions.append(idx) counter = 1 continue next_break = np.nonzero(isbreak[idx:])[0] if next_break.any(): next_break_is_close = next_break[0] <= chunksize // 2 else: next_break_is_close = False if (not ignore_old_chunks and idx in oldbreaks) or (counter >= chunksize and not next_break_is_close): divisions.append(idx) counter = 1 continue counter += 1 divisions.append(len(labels)) if debug: labels_at_breaks = labels[divisions[:-1]] print(labels_at_breaks[:40]) newchunks = tuple(np.diff(divisions)) if debug: print(divisions[:10], newchunks[:10]) print(divisions[-10:], newchunks[-10:]) assert sum(newchunks) == len(labels) if newchunks == array.chunks[axis]: return array else: return array.rechunk({axis: newchunks}) def rechunk_for_blockwise( array: DaskArray, axis: T_Axis, labels: np.ndarray, *, force: bool = True ) -> tuple[T_MethodOpt, DaskArray]: """ Rechunks array so that group boundaries line up with chunk boundaries, allowing embarrassingly parallel group reductions. This only works when the groups are sequential (e.g. labels = ``[0,0,0,1,1,1,1,2,2]``). Such patterns occur when using ``.resample``. Parameters ---------- array : DaskArray Array to rechunk axis : int Axis along which to rechunk the array. labels : np.ndarray Group labels Returns ------- DaskArray Rechunked array """ chunks = array.chunks[axis] if len(chunks) == 1: return "blockwise", array # import dask # from dask.utils import parse_bytes # factor = parse_bytes(dask.config.get("array.chunk-size")) / ( # math.prod(array.chunksize) * array.dtype.itemsize # ) # if factor > BLOCKWISE_DEFAULT_ARRAY_CHUNK_SIZE_FACTOR: # new_constant_chunks = math.ceil(factor) * max(chunks) # q, r = divmod(array.shape[axis], new_constant_chunks) # new_input_chunks = (new_constant_chunks,) * q + (r,) # else: new_input_chunks = chunks # FIXME: this should be unnecessary? labels = factorize_((labels,), axes=())[0] newchunks = _get_optimal_chunks_for_groups(new_input_chunks, labels) if newchunks == chunks: return "blockwise", array Δn = abs(len(newchunks) - len(new_input_chunks)) if pass_num_chunks_threshold := ( Δn / len(new_input_chunks) < OPTIONS["rechunk_blockwise_num_chunks_threshold"] ): logger.debug("blockwise rechunk passes num chunks threshold") if pass_chunk_size_threshold := ( # we just pick the max because number of chunks may have changed. (abs(max(newchunks) - max(new_input_chunks)) / max(new_input_chunks)) < OPTIONS["rechunk_blockwise_chunk_size_threshold"] ): logger.debug("blockwise rechunk passes chunk size change threshold") if force or (pass_num_chunks_threshold and pass_chunk_size_threshold): logger.debug("Rechunking to enable blockwise.") return "blockwise", array.rechunk({axis: newchunks}) else: logger.debug("Didn't meet thresholds to do automatic rechunking for blockwise reductions.") return None, array __all__ = [ "rechunk_for_blockwise", "rechunk_for_cohorts", ] flox-0.11.2/flox/reindex.py000066400000000000000000000167751514775624000155560ustar00rootroot00000000000000"""Reindexing functions for groupby operations. This module provides functions for reindexing arrays during groupby operations. """ from __future__ import annotations from dataclasses import dataclass from enum import Enum, auto from typing import TYPE_CHECKING, Any import numpy as np import pandas as pd from . import xrdtypes from .lib import dask_array_type, sparse_array_type from .xrutils import isnull if TYPE_CHECKING: from .core import T_Axis class ReindexArrayType(Enum): """ Enum describing which array type to reindex to. These are enumerated, rather than accepting a constructor, because we might want to optimize for specific array types, and because they don't necessarily have the same signature. For example, scipy.sparse.COO only supports a fill_value of 0. """ AUTO = auto() NUMPY = auto() SPARSE_COO = auto() # Sadly, scipy.sparse.coo_array only supports fill_value = 0 # SCIPY_SPARSE_COO = auto() # SPARSE_GCXS = auto() def is_same_type(self, other) -> bool: match self: case ReindexArrayType.AUTO: return True case ReindexArrayType.NUMPY: return isinstance(other, np.ndarray) case ReindexArrayType.SPARSE_COO: import sparse return isinstance(other, sparse.COO) @dataclass class ReindexStrategy: """ Strategy for reindexing. Attributes ---------- blockwise: bool, optional Whether to reindex at the blockwise step. Must be False for method="cohorts" array_type: ReindexArrayType, optional Whether to reindex to a different array type than array being reduced. """ # whether to reindex at the blockwise step blockwise: bool | None array_type: ReindexArrayType = ReindexArrayType.AUTO def __post_init__(self): if self.blockwise is True: if self.array_type not in (ReindexArrayType.AUTO, ReindexArrayType.NUMPY): raise ValueError("Setting reindex.blockwise=True not allowed for non-numpy array type.") def set_blockwise_for_numpy(self): self.blockwise = True if self.blockwise is None else self.blockwise def get_dask_meta(self, other, *, fill_value, dtype) -> Any: if self.array_type is ReindexArrayType.AUTO: other = other._meta if isinstance(other, dask_array_type) else other if isinstance(other, sparse_array_type): return type(other).from_numpy(np.array([], dtype=dtype)) return type(other)([], dtype=dtype) elif self.array_type is ReindexArrayType.NUMPY: return np.ndarray([], dtype=dtype) elif self.array_type is ReindexArrayType.SPARSE_COO: import sparse return sparse.COO.from_numpy(np.ones(shape=(0,) * other.ndim, dtype=dtype), fill_value=fill_value) def reindex_numpy(array, from_: pd.Index, to: pd.Index, fill_value, dtype, axis: int): idx = from_.get_indexer(to) indexer: list[slice | np.ndarray] = [slice(None, None)] * array.ndim indexer[axis] = idx reindexed = array[tuple(indexer)] if (idx == -1).any(): if fill_value is None: raise ValueError("Filling is required. fill_value cannot be None.") indexer[axis] = idx == -1 reindexed = reindexed.astype(dtype, copy=False) reindexed[tuple(indexer)] = fill_value return reindexed def reindex_pydata_sparse_coo(array, from_: pd.Index, to: pd.Index, fill_value, dtype, axis: int): import sparse assert axis == -1 # Are there any elements in `to` that are not in `from_`. if isinstance(to, pd.RangeIndex) and len(to) > len(from_): # 1. pandas optimizes set difference between two RangeIndexes only # 2. We want to avoid realizing a very large numpy array in to memory. # This happens in the `else` clause. # There are potentially other tricks we can play, but this is a simple # and effective one. If a user is reindexing to sparse, then len(to) is # almost guaranteed to be > len(from_). If len(to) <= len(from_), then realizing # another array of the same shape should be fine. needs_reindex = True else: needs_reindex = (from_.get_indexer(to) == -1).any() if needs_reindex and fill_value is None: raise ValueError("Filling is required. fill_value cannot be None.") idx = to.get_indexer(from_) mask = idx != -1 # indices along last axis to keep if mask.all(): mask = slice(None) shape = array.shape if isinstance(array, sparse.COO): subset = array[..., mask] data = subset.data coords = subset.coords if subset.nnz > 0: coords[-1, :] = idx[mask][coords[-1, :]] if fill_value is None: # no reindexing is actually needed (dense case) # preserve the fill_value fill_value = array.fill_value else: ranges = np.broadcast_arrays( *np.ix_(*(tuple(np.arange(size) for size in shape[:axis]) + (idx[mask],))) ) coords = np.stack(ranges, axis=0).reshape(array.ndim, -1) data = array[..., mask].reshape(-1) reindexed = sparse.COO( coords=coords, data=data.astype(dtype, copy=False), shape=(*array.shape[:axis], to.size), fill_value=fill_value, ) return reindexed def reindex_( array: np.ndarray, from_, to, *, array_type: ReindexArrayType = ReindexArrayType.AUTO, fill_value: Any = None, axis: T_Axis = -1, promote: bool = False, ) -> np.ndarray: if not isinstance(to, pd.Index): if promote: to = pd.Index(to) else: raise ValueError("reindex requires a pandas.Index or promote=True") if to.ndim > 1: raise ValueError(f"Cannot reindex to a multidimensional array: {to}") if array.shape[axis] == 0: # all groups were NaN shape = array.shape[:-1] + (len(to),) if array_type in (ReindexArrayType.AUTO, ReindexArrayType.NUMPY): reindexed = np.full(shape, fill_value, dtype=array.dtype) else: raise NotImplementedError return reindexed from_ = pd.Index(from_) # short-circuit for trivial case if from_.equals(to) and array_type.is_same_type(array): return array if from_.dtype.kind == "O" and isinstance(from_[0], tuple): raise NotImplementedError( "Currently does not support reindexing with object arrays of tuples. " "These occur when grouping by multi-indexed variables in xarray." ) # Use '==' instead of 'is', as Dask serialization can break identity checks. if fill_value == xrdtypes.NA or isnull(fill_value): new_dtype, fill_value = xrdtypes.maybe_promote(array.dtype) else: new_dtype = array.dtype if array_type is ReindexArrayType.AUTO: if isinstance(array, sparse_array_type): array_type = ReindexArrayType.SPARSE_COO else: # TODO: generalize here # Right now, we effectively assume NEP-18 I think array_type = ReindexArrayType.NUMPY if array_type is ReindexArrayType.NUMPY: reindexed = reindex_numpy(array, from_, to, fill_value, new_dtype, axis) elif array_type is ReindexArrayType.SPARSE_COO: reindexed = reindex_pydata_sparse_coo(array, from_, to, fill_value, new_dtype, axis) return reindexed __all__ = [ "ReindexArrayType", "ReindexStrategy", "reindex_", "reindex_numpy", "reindex_pydata_sparse_coo", ] flox-0.11.2/flox/scan.py000066400000000000000000000312301514775624000150230ustar00rootroot00000000000000"""Scan operations for groupby reductions. This module provides scan operations (cumulative reductions) for grouped data. """ from __future__ import annotations import copy from typing import TYPE_CHECKING import numpy as np import pandas as pd from .aggregations import ( SCANS, AlignedArrays, Scan, ScanState, _atleast_1d, generic_aggregate, ) from .cohorts import find_group_cohorts from .factorize import _factorize_multiple from .lib import _should_auto_rechunk_blockwise from .rechunk import rechunk_for_blockwise from .xrutils import is_duck_array, is_duck_dask_array, module_available if module_available("numpy", minversion="2.0.0"): from numpy.lib.array_utils import normalize_axis_tuple else: from numpy.core.numeric import normalize_axis_tuple # type: ignore[no-redef] if TYPE_CHECKING: from typing import Literal from .core import ( T_By, T_EngineOpt, T_ExpectedGroupsOpt, T_MethodOpt, T_Scan, ) from .types import DaskArray T_ScanMethod = Literal["blockwise", "blelloch"] def _choose_scan_method( method: T_MethodOpt, preferred_method: T_ScanMethod, nax: int, by_ndim: int ) -> T_ScanMethod: """Choose the scan method based on user input and preferred method. Parameters ---------- method : T_MethodOpt User-specified method, or None for automatic selection. preferred_method : T_ScanMethod The preferred method based on data layout analysis. nax : int Number of axes being reduced. by_ndim : int Number of dimensions in the `by` array. Returns ------- T_ScanMethod The chosen scan method: "blockwise" or "blelloch". """ if method is None: # Scans must reduce along all dimensions of by for blockwise if nax != by_ndim: return "blelloch" return preferred_method elif method == "blockwise": return "blockwise" else: # For any other method (including map-reduce, cohorts), use blelloch return "blelloch" def _validate_expected_groups(nby, expected_groups): """Validate expected_groups for scan operations.""" if expected_groups is None: return (None,) * nby return expected_groups def _convert_expected_groups_to_index(expected_groups): """Convert expected_groups to index for scan operations.""" result = [] for expect in expected_groups: if expect is None: result.append(None) elif isinstance(expect, pd.Index): result.append(expect) else: result.append(pd.Index(expect)) return tuple(result) def groupby_scan( array: np.ndarray | DaskArray, *by: T_By, func: T_Scan, expected_groups: T_ExpectedGroupsOpt = None, axis: int | tuple[int] = -1, dtype: np.typing.DTypeLike = None, method: T_MethodOpt = None, engine: T_EngineOpt = None, ) -> np.ndarray | DaskArray: """ GroupBy reductions using parallel scans for dask.array Parameters ---------- array : ndarray or DaskArray Array to be reduced, possibly nD *by : ndarray or DaskArray Array of labels to group over. Must be aligned with ``array`` so that ``array.shape[-by.ndim :] == by.shape`` or any disagreements in that equality check are for dimensions of size 1 in `by`. func : {"nancumsum", "ffill", "bfill"} or Scan Single function name or a Scan instance expected_groups : (optional) Sequence Expected unique labels. axis : None or int or Sequence[int], optional If None, reduce across all dimensions of by Else, reduce across corresponding axes of array Negative integers are normalized using array.ndim. fill_value : Any Value to assign when a label in ``expected_groups`` is not present. dtype : data-type , optional DType for the output. Can be anything that is accepted by ``np.dtype``. method : {"blockwise", "blelloch"}, optional Strategy for scan of dask arrays only: * ``"blockwise"``: Only scan using blockwise and avoid aggregating blocks together. Useful for resampling-style groupby problems where group members are always together. If `by` is 1D, `array` is automatically rechunked so that chunk boundaries line up with group boundaries i.e. each block contains all members of any group present in that block. For nD `by`, you must make sure that all members of a group are present in a single block. * ``"blelloch"``: Use Blelloch's parallel prefix scan algorithm, which allows scanning across chunk boundaries. This is the default when groups span multiple chunks. engine : {"flox", "numpy", "numba", "numbagg"}, optional Algorithm to compute the groupby reduction on non-dask arrays and on each dask chunk: * ``"numpy"``: Use the vectorized implementations in ``numpy_groupies.aggregate_numpy``. This is the default choice because it works for most array types. * ``"flox"``: Use an internal implementation where the data is sorted so that all members of a group occur sequentially, and then numpy.ufunc.reduceat is to used for the reduction. This will fall back to ``numpy_groupies.aggregate_numpy`` for a reduction that is not yet implemented. * ``"numba"``: Use the implementations in ``numpy_groupies.aggregate_numba``. * ``"numbagg"``: Use the reductions supported by ``numbagg.grouped``. This will fall back to ``numpy_groupies.aggregate_numpy`` for a reduction that is not yet implemented. Returns ------- result Aggregated result See Also -------- xarray.xarray_reduce """ from . import xrdtypes axis = _atleast_1d(axis) if len(axis) > 1: raise NotImplementedError("Scans are only supported along a single dimension.") bys: tuple = tuple(np.asarray(b) if not is_duck_array(b) else b for b in by) nby = len(by) by_is_dask = tuple(is_duck_dask_array(b) for b in bys) any_by_dask = any(by_is_dask) axis_ = normalize_axis_tuple(axis, array.ndim) if engine is not None: raise NotImplementedError("Setting `engine` is not supported for scans yet.") if engine is None: engine = "flox" assert engine == "flox" if not is_duck_array(array): array = np.asarray(array) agg = SCANS[func] if isinstance(func, str) else func assert isinstance(agg, Scan) agg = copy.deepcopy(agg) if (agg == SCANS["ffill"] or agg == SCANS["bfill"]) and array.dtype.kind != "f": # nothing to do, no NaNs! return array if expected_groups is not None: raise NotImplementedError("Setting `expected_groups` with scans is not supported yet.") expected_groups = _validate_expected_groups(nby, expected_groups) expected_groups = _convert_expected_groups_to_index(expected_groups) # Don't factorize early only when # grouping by dask arrays, and not having expected_groups factorize_early = not ( # can't do it if we are grouping by dask array but don't have expected_groups any(is_dask and ex_ is None for is_dask, ex_ in zip(by_is_dask, expected_groups)) ) if factorize_early: bys, final_groups, grp_shape = _factorize_multiple( bys, expected_groups, any_by_dask=any_by_dask, sort=False, ) else: raise NotImplementedError assert len(bys) == 1 by_: np.ndarray (by_,) = bys has_dask = is_duck_dask_array(array) or is_duck_dask_array(by_) nax = len(axis_) # Method selection for dask arrays scan_method: T_ScanMethod = "blelloch" if has_dask: (single_axis,) = axis_ # type: ignore[misc] # Try rechunking for sorted 1D by when method is not specified if _should_auto_rechunk_blockwise(method, array, any_by_dask, by_): rechunk_method, array = rechunk_for_blockwise(array, single_axis, by_, force=False) if rechunk_method == "blockwise": method = "blockwise" # Determine preferred method based on data layout if not any_by_dask and method is None: cohorts_method, _ = find_group_cohorts( by_, [array.chunks[ax] for ax in range(-by_.ndim, 0)], # type: ignore[union-attr] expected_groups=None, merge=False, ) # Map groupby_reduce methods to scan methods preferred_method: T_ScanMethod = "blockwise" if cohorts_method == "blockwise" else "blelloch" else: preferred_method = "blelloch" # Choose the final method scan_method = _choose_scan_method(method, preferred_method, nax, by_.ndim) # Rechunk if blockwise was explicitly requested but data isn't aligned if preferred_method != "blockwise" and scan_method == "blockwise" and by_.ndim == 1: _, array = rechunk_for_blockwise(array, axis=-1, labels=by_) if array.dtype.kind in "Mm": cast_to = array.dtype array = array.view(np.int64) elif array.dtype.kind == "b": array = array.view(np.int8) cast_to = None if agg.preserves_dtype: cast_to = bool else: cast_to = None # TODO: move to aggregate_npg.py if agg.name in ["cumsum", "nancumsum"] and array.dtype.kind in ["i", "u"]: # https://numpy.org/doc/stable/reference/generated/numpy.cumsum.html # it defaults to the dtype of a, unless a # has an integer dtype with a precision less than that of the default platform integer. if array.dtype.kind == "i": agg.dtype = np.result_type(array.dtype, np.int_) elif array.dtype.kind == "u": agg.dtype = np.result_type(array.dtype, np.uint) else: agg.dtype = array.dtype if dtype is None else dtype agg.identity = xrdtypes._get_fill_value(agg.dtype, agg.identity) (single_axis,) = axis_ # type: ignore[misc] # avoid some roundoff error when we can. if by_.shape[-1] == 1 or by_.shape == grp_shape: array = array.astype(agg.dtype) if cast_to is not None: array = array.astype(cast_to) return array # Made a design choice here to have `preprocess` handle both array and group_idx # Example: for reversing, we need to reverse the whole array, not just reverse # each block independently inp = AlignedArrays(array=array, group_idx=by_) if agg.preprocess: inp = agg.preprocess(inp) if not has_dask: final_state = chunk_scan(inp, axis=single_axis, agg=agg, dtype=agg.dtype) result = _finalize_scan(final_state, dtype=agg.dtype) else: from .dask import dask_groupby_scan result = dask_groupby_scan(inp.array, inp.group_idx, axes=axis_, agg=agg, method=scan_method) # Made a design choice here to have `postprocess` handle both array and group_idx out = AlignedArrays(array=result, group_idx=by_) if agg.finalize: out = agg.finalize(out) if cast_to is not None: return out.array.astype(cast_to) return out.array def chunk_scan(inp: AlignedArrays, *, axis: int, agg: Scan, dtype=None, keepdims=None) -> ScanState: assert axis == inp.array.ndim - 1 # I don't think we need to re-factorize here unless we are grouping by a dask array accumulated = generic_aggregate( inp.group_idx, inp.array, axis=axis, engine="flox", func=agg.scan, dtype=dtype, fill_value=agg.identity, ) result = AlignedArrays(array=accumulated, group_idx=inp.group_idx) return ScanState(result=result, state=None) def grouped_reduce(inp: AlignedArrays, *, agg: Scan, axis: int, keepdims=None) -> ScanState: from .core import chunk_reduce assert axis == inp.array.ndim - 1 reduced = chunk_reduce( inp.array, inp.group_idx, func=(agg.reduction,), axis=axis, engine="flox", dtype=inp.array.dtype, fill_value=agg.identity, expected_groups=None, ) return ScanState( state=AlignedArrays(array=reduced["intermediates"][0], group_idx=reduced["groups"]), result=None, ) def _zip(group_idx: np.ndarray, array: np.ndarray) -> AlignedArrays: return AlignedArrays(group_idx=group_idx, array=array) def _finalize_scan(block: ScanState, dtype) -> np.ndarray: assert block.result is not None return block.result.array.astype(dtype, copy=False) __all__ = [ "_finalize_scan", "_zip", "chunk_scan", "grouped_reduce", "groupby_scan", ] flox-0.11.2/flox/types.py000066400000000000000000000017231514775624000152470ustar00rootroot00000000000000from collections import namedtuple from collections.abc import Callable from typing import TYPE_CHECKING, Any, TypeAlias, TypedDict import numpy as np try: import cubed.Array as CubedArray except ImportError: CubedArray = Any try: import dask.array.Array as DaskArray from dask.typing import Graph except ImportError: DaskArray = Any Graph: TypeAlias = Any # type: ignore[no-redef,misc] if TYPE_CHECKING: T_DuckArray: TypeAlias = np.ndarray | DaskArray | CubedArray T_By: TypeAlias = T_DuckArray T_Bys = tuple[T_By, ...] T_Axis = int T_Axes = tuple[T_Axis, ...] IntermediateDict = dict[str | Callable, Any] FinalResultsDict = dict[str, DaskArray | CubedArray | np.ndarray] class FactorizeKwargs(TypedDict, total=False): """Used in _factorize_multiple""" by: "T_Bys" axes: "T_Axes" fastpath: bool reindex: bool sort: bool FactorProps = namedtuple("FactorProps", "offset_group nan_sentinel nanmask") flox-0.11.2/flox/visualize.py000066400000000000000000000120741514775624000161170ustar00rootroot00000000000000import random from itertools import product import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np import pandas as pd from .cohorts import find_group_cohorts from .core import _unique def draw_mesh( nrow, ncol, *, draw_line_at=None, nspaces=0, space_at=0, pxin=0.3, counter=None, colors=None, randomize=True, x0=0, y0=0, append=False, ): dx = 2 xpts = x0 + np.arange(0, (ncol + nspaces) * dx, dx) ypts = y0 + np.arange(0, nrow * dx, dx) if colors is None: colors = mpl.cm.Set2.colors[:4] if not append: plt.figure() ax = plt.axes() else: ax = plt.gca() ax.set_aspect(1) ax.set_axis_off() # ncolors = len(colors) if not randomize: colors = iter(colors) icolor = -1 for n, (y, x) in enumerate(product(ypts, xpts)): if space_at > 0 and (n % space_at) == 0: continue if randomize: fcolor = random.choice(colors) else: fcolor = next(colors) icolor += 1 if counter is not None: counter[fcolor] += 1 ax.add_patch( mpl.patches.Rectangle( (x, y), dx, dx, edgecolor="w", linewidth=1, facecolor=fcolor, ) ) if draw_line_at is not None and icolor > 0 and icolor % draw_line_at == 0: plt.plot([x, x], [y - 0.75 * dx, y + 0.75 * dx], color="k", lw=2) # assert n + 1 == ncolors, (n, ncolors) ax.set_xlim((0, max(xpts) + 2 * dx)) ax.set_ylim((-0.75 * dx + min(ypts), max(ypts) + 0.75 * dx)) if not append: plt.gcf().set_size_inches((ncol * pxin, (nrow + 2) * pxin)) def visualize_groups_1d(array, labels, axis=-1, colors=None, cmap=None, append=True, x0=0): """ Visualize group distribution for a 1D array of group labels. """ labels = np.asarray(labels) assert labels.ndim == 1 factorized, unique_labels = pd.factorize(labels) assert np.array(labels).ndim == 1 chunks = array.chunks[axis] if colors is None: if cmap is None: colors = list(mpl.cm.tab20.colors) elif cmap is not None: colors = [cmap((num - 1) / len(unique_labels)) for num in unique_labels] if len(unique_labels) > len(colors): raise ValueError("Not enough unique colors") if not append: fig = plt.figure() i0 = 0 for i in chunks: lab = labels[i0 : i0 + i] col = [colors[label] for label in lab] + [(1, 1, 1)] draw_mesh( 1, len(lab) + 1, colors=col, randomize=False, append=append, x0=x0 + i0 * 2.3, # + (i0 - 1) * 0.025, ) i0 += i if not append: pxin = 0.8 fig.set_size_inches((len(labels) * pxin, 1 * pxin)) def get_colormap(N): cmap = mpl.cm.get_cmap("tab20_r").copy() ncolors = len(cmap.colors) q = N // ncolors r = N % ncolors cmap = mpl.colors.ListedColormap(np.concatenate([cmap.colors] * q + [cmap.colors[: r + 1]])) cmap.set_under(color="k") return cmap def factorize_cohorts(chunks, cohorts): chunk_grid = tuple(len(c) for c in chunks) nchunks = np.prod(chunk_grid) factorized = np.full((nchunks,), -1, dtype=np.int64) for idx, cohort in enumerate(cohorts): factorized[list(cohort)] = idx return factorized.reshape(chunk_grid) def visualize_cohorts_2d(by, chunks): assert by.ndim == 2 print("finding cohorts...") chunks = [chunks[ax] for ax in range(-by.ndim, 0)] _, chunks_cohorts = find_group_cohorts(by, chunks) print("finished cohorts...") xticks = np.cumsum(chunks[-1]) yticks = np.cumsum(chunks[-2]) f, ax = plt.subplots(1, 2, constrained_layout=True, sharex=False, sharey=False) ax = ax.ravel() # ax[1].set_visible(False) # ax = ax[[0, 2, 3]] ngroups = len(_unique(by)) h0 = ax[0].imshow(by, vmin=0, cmap=get_colormap(ngroups)) h2 = _visualize_cohorts(chunks, chunks_cohorts, ax=ax[1]) ax[0].grid(True, which="both") for axx in ax[:1]: axx.set_xticks(xticks) axx.set_yticks(yticks) for h, axx in zip([h0, h2], ax): f.colorbar(h, ax=axx, orientation="horizontal") ax[0].set_title(f"by: {ngroups} groups") ax[1].set_title(f"{len(chunks_cohorts)} cohorts") f.set_size_inches((9, 6)) def _visualize_cohorts(chunks, cohorts, ax=None): if ax is None: _, ax = plt.subplots(1, 1) data = factorize_cohorts(chunks, cohorts) return ax.imshow(data, vmin=0, cmap=get_colormap(len(cohorts))) def visualize_groups_2d(labels, y0=0, **kwargs): colors = mpl.cm.tab10_r for _i, chunk in enumerate(labels): chunk = np.atleast_2d(chunk) draw_mesh( *chunk.shape, colors=tuple(colors(label) for label in np.flipud(chunk).ravel()), randomize=False, append=True, y0=y0, **kwargs, ) y0 = y0 + 2 * chunk.shape[0] + 2 plt.ylim([-1, y0]) flox-0.11.2/flox/xarray.py000066400000000000000000000603161514775624000154140ustar00rootroot00000000000000from __future__ import annotations from collections.abc import Hashable, Iterable, Sequence from typing import TYPE_CHECKING, Any, cast import numpy as np import pandas as pd import toolz import xarray as xr from packaging.version import Version from .aggregations import Aggregation, Dim, _atleast_1d, quantile_new_dims_func from .core import ( _convert_expected_groups_to_index, _get_expected_groups, _validate_expected_groups, groupby_reduce, ) from .rechunk import rechunk_for_blockwise as rechunk_array_for_blockwise from .rechunk import rechunk_for_cohorts as rechunk_array_for_cohorts from .reindex import ReindexStrategy try: from xarray.indexes import PandasMultiIndex except ImportError: PandasMultiIndex = tuple() # type: ignore[misc,assignment,unused-ignore] if TYPE_CHECKING: from xarray.core.types import T_DataArray, T_Dataset from .core import T_ExpectedGroupsOpt, T_ExpectIndex, T_ExpectOpt Dims = str | Iterable[Hashable] | None def _restore_dim_order(result, obj, by, no_groupby_reorder=False): def lookup_order(dimension): if dimension == by.name and by.ndim == 1: (dimension,) = by.dims if no_groupby_reorder: return -1e6 # some arbitrarily low value if dimension in obj.dims: axis = obj.get_axis_num(dimension) else: axis = 1e6 # some arbitrarily high value return axis new_order = sorted(result.dims, key=lookup_order) return result.transpose(*new_order) def _broadcast_size_one_dims(*arrays, core_dims): """Broadcast by adding size-1 dimensions in the right place. Workaround because apply_ufunc doesn't support this yet. https://github.com/pydata/xarray/issues/3032#issuecomment-503337637 Specialized to the groupby problem. """ array_dims = set(core_dims[0]) broadcasted = [arrays[0]] for dims, array in zip(core_dims[1:], arrays[1:]): assert set(dims).issubset(array_dims) order = [dims.index(d) for d in core_dims[0] if d in dims] array = array.transpose(*order) axis = [core_dims[0].index(d) for d in core_dims[0] if d not in dims] broadcasted.append(np.expand_dims(array, axis)) return broadcasted def xarray_reduce( obj: T_Dataset | T_DataArray, *by: T_DataArray | Hashable, func: str | Aggregation, expected_groups: T_ExpectedGroupsOpt = None, isbin: bool | Sequence[bool] = False, sort: bool = True, dim: Dims | ellipsis = None, fill_value=None, dtype: np.typing.DTypeLike = None, method: str | None = None, engine: str | None = None, keep_attrs: bool | None = True, skipna: bool | None = None, min_count: int | None = None, reindex: ReindexStrategy | bool | None = None, **finalize_kwargs, ): """GroupBy reduce operations on xarray objects using numpy-groupies. Parameters ---------- obj : DataArray or Dataset Xarray object to reduce *by : DataArray or iterable of str or iterable of DataArray Variables with which to group by ``obj`` func : {"all", "any", "count", "sum", "nansum", "mean", "nanmean", \ "max", "nanmax", "min", "nanmin", "argmax", "nanargmax", "argmin", "nanargmin", \ "quantile", "nanquantile", "median", "nanmedian", "mode", "nanmode", \ "first", "nanfirst", "last", "nanlast"} or Aggregation Single function name or an Aggregation instance expected_groups : str or sequence expected group labels corresponding to each `by` variable isbin : iterable of bool If True, corresponding entry in ``expected_groups`` are bin edges. If False, the entry in ``expected_groups`` is treated as a simple label. sort : (optional), bool Whether groups should be returned in sorted order. Only applies for dask reductions when ``method`` is not ``"map-reduce"``. For ``"map-reduce"``, the groups are always sorted. dim : hashable dimension name along which to reduce. If None, reduces across all dimensions of `by` fill_value : Any Value used for missing groups in the output i.e. when one of the labels in ``expected_groups`` is not actually present in ``by``. dtype : data-type, optional DType for the output. Can be anything that is accepted by ``np.dtype``. method : {"map-reduce", "blockwise", "cohorts"}, optional Note that this arg is chosen by default using heuristics. Strategy for reduction of dask arrays only. * ``"map-reduce"``: First apply the reduction blockwise on ``array``, then combine a few newighbouring blocks, apply the reduction. Continue until finalizing. Usually, ``func`` will need to be an ``Aggregation`` instance for this method to work. Common aggregations are implemented. * ``"blockwise"``: Only reduce using blockwise and avoid aggregating blocks together. Useful for resampling-style reductions where group members are always together. If ``by`` is 1D, ``array`` is automatically rechunked so that chunk boundaries line up with group boundaries i.e. each block contains all members of any group present in that block. For nD ``by``, you must make sure that all members of a group are present in a single block. * ``"cohorts"``: Finds group labels that tend to occur together ("cohorts"), indexes out cohorts and reduces that subset using "map-reduce", repeat for all cohorts. This works well for many time groupings where the group labels repeat at regular intervals like 'hour', 'month', dayofyear' etc. Optimize chunking ``array`` for this method by first rechunking using ``rechunk_for_cohorts`` (for 1D ``by`` only). engine : {"flox", "numpy", "numba", "numbagg"}, optional Algorithm to compute the groupby reduction on non-dask arrays and on each dask chunk: * ``"numpy"``: Use the vectorized implementations in ``numpy_groupies.aggregate_numpy``. This is the default choice because it works for most array types. * ``"flox"``: Use an internal implementation where the data is sorted so that all members of a group occur sequentially, and then numpy.ufunc.reduceat is to used for the reduction. This will fall back to ``numpy_groupies.aggregate_numpy`` for a reduction that is not yet implemented. * ``"numba"``: Use the implementations in ``numpy_groupies.aggregate_numba``. * ``"numbagg"``: Use the reductions supported by ``numbagg.grouped``. This will fall back to ``numpy_groupies.aggregate_numpy`` for a reduction that is not yet implemented. keep_attrs : bool, optional Preserve attrs? skipna : bool, optional If True, skip missing values (as marked by NaN). By default, only skips missing values for float dtypes; other dtypes either do not have a sentinel missing value (int) or ``skipna=True`` has not been implemented (object, datetime64 or timedelta64). min_count : int, default: None The required number of valid values to perform the operation. If fewer than min_count non-NA values are present the result will be NA. Only used if skipna is set to True or defaults to True for the array's dtype. reindex : ReindexStrategy | bool, optional Whether to "reindex" the blockwise reduced results to ``expected_groups`` (possibly automatically detected). If True, the intermediate result of the blockwise groupby-reduction has a value for all expected groups, and the final result is a simple reduction of those intermediates. In nearly all cases, this is a significant boost in computation speed. For cases like time grouping, this may result in large intermediates relative to the original block size. Avoid that by using ``method="cohorts"``. By default, it is turned off for argreductions. By default, the type of ``array`` is preserved. You may optionally reindex to a sparse array type to further control memory in the case of ``expected_groups`` being very large. Pass a ``ReindexStrategy`` instance with the appropriate ``array_type``, for example (``reindex=ReindexStrategy(blockwise=False, array_type=ReindexArrayType.SPARSE_COO)``). **finalize_kwargs: dict, optional kwargs passed to the finalize function, like ``ddof`` for var, std or ``q`` for quantile. Returns ------- DataArray or Dataset Reduced object See Also -------- flox.core.groupby_reduce Raises ------ NotImplementedError ValueError Examples -------- >>> import xarray as xr >>> from flox.xarray import xarray_reduce >>> # Create a group index: >>> labels = xr.DataArray( ... [1, 2, 3, 1, 2, 3, 0, 0, 0], ... dims="x", ... name="label", ... ) >>> # Create a DataArray to apply the group index on: >>> da = da = xr.ones_like(labels) >>> # Sum all values in da that matches the elements in the group index: >>> xarray_reduce(da, labels, func="sum") Size: 32B array([3, 2, 2, 2]) Coordinates: * label (label) int64 32B 0 1 2 3 """ if skipna is not None and isinstance(func, Aggregation): raise ValueError("skipna must be None when func is an Aggregation.") nby = len(by) for b in by: if isinstance(b, xr.DataArray) and b.name is None: raise ValueError("Cannot group by unnamed DataArrays.") # TODO: move to GroupBy._flox_reduce if keep_attrs is None: keep_attrs = True if isinstance(isbin, Sequence): isbins = isbin else: isbins = (isbin,) * nby expected_groups_valid = _validate_expected_groups(nby, expected_groups) if not sort: raise NotImplementedError("sort must be True for xarray_reduce") # eventually drop the variables we are grouping by maybe_drop = {b for b in by if isinstance(b, Hashable)} unindexed_dims = tuple( b for b, isbin_ in zip(by, isbins) if isinstance(b, Hashable) and not isbin_ and b in obj.dims and b not in obj.xindexes ) by_da = tuple(obj[g] if isinstance(g, Hashable) else g for g in by) grouper_dims = [] for g in by_da: for d in g.dims: if d not in grouper_dims: grouper_dims.append(d) if isinstance(obj, xr.Dataset): ds = cast(xr.Dataset, obj) else: ds = obj._to_temp_dataset() more_drop = set() for var in maybe_drop: maybe_midx = ds._indexes.get(var, None) if isinstance(maybe_midx, PandasMultiIndex): idx_coord_names = set(tuple(maybe_midx.index.names) + (maybe_midx.dim,)) idx_other_names = idx_coord_names - set(maybe_drop) more_drop.update(idx_other_names) maybe_drop.update(more_drop) if dim is Ellipsis: if nby > 1: raise NotImplementedError("Multiple by are not allowed when dim is Ellipsis.") name_ = by_da[0].name if name_ in ds.dims and not isbins[0]: dim_tuple = tuple(d for d in obj.dims if d != name_) else: dim_tuple = tuple(obj.dims) elif dim is not None: dim_tuple = _atleast_1d(dim) else: dim_tuple = tuple(grouper_dims) # broadcast to make sure grouper dimensions are present in the array. exclude_dims = tuple(d for d in ds.dims if d not in grouper_dims and d not in dim_tuple) if any(d not in grouper_dims and d not in obj.dims for d in dim_tuple): raise ValueError(f"Cannot reduce over absent dimensions {dim}.") try: xr.align(ds, *by_da, join="exact", copy=False) except ValueError as e: raise ValueError("Object being grouped must be exactly aligned with every array in `by`.") from e needs_broadcast = any( not set(grouper_dims).issubset(set(variable.dims)) for variable in ds.data_vars.values() ) if needs_broadcast: ds_broad = cast(xr.Dataset, xr.broadcast(ds, *by_da, exclude=exclude_dims)[0]) else: ds_broad = ds dims_not_in_groupers = tuple(d for d in dim_tuple if d not in grouper_dims) if dims_not_in_groupers == tuple(dim_tuple) and not any(isbins): # reducing along a dimension along which groups do not vary # This is really just a normal reduction. # This is not right when binning so we exclude. if isinstance(func, str) and func.startswith("nan"): raise ValueError(f"Specify func={func[3:]}, skipna=True instead of func={func}") elif isinstance(func, Aggregation): raise NotImplementedError( "func must be a string when reducing along a dimension not present in `by`" ) # skipna is not supported for all reductions # https://github.com/pydata/xarray/issues/8819 kwargs = {"skipna": skipna} if skipna is not None else {} kwargs.update(finalize_kwargs) result = getattr(ds_broad, func)(dim=dim_tuple, **kwargs) if isinstance(obj, xr.DataArray): return obj._from_temp_dataset(result) else: return result ds = ds.drop_vars([var for var in maybe_drop if var in ds.variables]) axis = tuple(range(-len(dim_tuple), 0)) # Set expected_groups and convert to index since we need coords, sizes # for output xarray objects expected_groups_valid_list: list[T_ExpectIndex] = [] group_names: tuple[Any, ...] = () group_sizes: dict[Any, int] = {} for idx, (b_, expect, isbin_) in enumerate(zip(by_da, expected_groups_valid, isbins)): group_name = f"{b_.name}_bins" if isbin_ or isinstance(expect, pd.IntervalIndex) else b_.name group_names += (group_name,) if isbin_ and isinstance(expect, int): raise NotImplementedError("flox does not support binning into an integer number of bins yet.") expect1: T_ExpectOpt if expect is None: if isbin_: raise ValueError( f"Please provided bin edges for group variable {idx} " f"named {group_name} in expected_groups." ) expect1 = _get_expected_groups(b_.data, sort=sort) else: expect1 = expect expect_index = _convert_expected_groups_to_index((expect1,), (isbin_,), sort=sort)[0] # The if-check is for type hinting mainly, it narrows down the return # type of _convert_expected_groups_to_index to pure pd.Index: if expect_index is not None: expected_groups_valid_list.append(expect_index) group_sizes[group_name] = len(expect_index) else: # This will never be reached raise ValueError("expect_index cannot be None") def wrapper(array, *by, func, skipna, core_dims, **kwargs): array, *by = _broadcast_size_one_dims(array, *by, core_dims=core_dims) # Handle skipna here because I need to know dtype to make a good default choice. # We cannot handle this easily for xarray Datasets in xarray_reduce if skipna and func in ["all", "any", "count"]: raise ValueError(f"skipna cannot be truthy for {func} reductions.") if skipna or (skipna is None and isinstance(func, str) and array.dtype.kind in "cfO"): if "nan" not in func and func not in ["all", "any", "count"]: func = f"nan{func}" result, *groups = groupby_reduce(array, *by, func=func, **kwargs) # Transpose the new quantile dimension to the end. This is ugly. # but new core dimensions are expected at the end :/ # but groupby_reduce inserts them at the beginning if func in ["quantile", "nanquantile"]: (newdim,) = quantile_new_dims_func(**finalize_kwargs) if not newdim.is_scalar: # NOTE: _restore_dim_order will move any new dims to the end anyway. # This transpose is simply makes it easy to specify output_core_dims # output dim order: (*broadcast_dims, *group_dims, quantile_dim) result = np.moveaxis(result, 0, -1) return result # These data variables do not have any of the core dimension, # take them out to prevent errors. # apply_ufunc can handle non-dim coordinate variables without core dimensions missing_dim = {} if isinstance(obj, xr.Dataset): # broadcasting means the group dim gets added to ds, so we check the original obj for k, v in obj.data_vars.items(): is_missing_dim = not (any(d in v.dims for d in dim_tuple)) if is_missing_dim: missing_dim[k] = v # dim_tuple contains dimensions we are reducing over. These need to be the last # core dimensions to be synchronized with axis. input_core_dims = [[d for d in grouper_dims if d not in dim_tuple] + list(dim_tuple)] input_core_dims += [list(b.dims) for b in by_da] newdims: tuple[Dim, ...] = ( quantile_new_dims_func(**finalize_kwargs) if func in ["quantile", "nanquantile"] else () ) output_core_dims = [d for d in input_core_dims[0] if d not in dim_tuple] output_core_dims.extend(group_names) vector_dims = [dim.name for dim in newdims if not dim.is_scalar] output_core_dims.extend(vector_dims) output_sizes = group_sizes output_sizes.update({dim.name: dim.size for dim in newdims if dim.size != 0}) actual = xr.apply_ufunc( wrapper, ds_broad.drop_vars(tuple(missing_dim)).transpose(..., *grouper_dims), *by_da, input_core_dims=input_core_dims, # for xarray's test_groupby_duplicate_coordinate_labels exclude_dims=set(dim_tuple), output_core_dims=[output_core_dims], dask="allowed", dask_gufunc_kwargs=dict( output_sizes=output_sizes, output_dtypes=[dtype] if dtype is not None else None, ), keep_attrs=keep_attrs, kwargs={ "func": func, "axis": axis, "sort": sort, "fill_value": fill_value, "method": method, "min_count": min_count, "skipna": skipna, "engine": engine, "reindex": reindex, "expected_groups": tuple(expected_groups_valid_list), "isbin": isbins, "finalize_kwargs": finalize_kwargs, "dtype": dtype, "core_dims": input_core_dims, }, ) # restore non-dim coord variables without the core dimension # TODO: shouldn't apply_ufunc handle this? for var in set(ds_broad._coord_names) - set(ds_broad._indexes) - set(ds_broad.dims): if all(d not in ds_broad[var].dims for d in dim_tuple): actual[var] = ds_broad[var] for newdim in newdims: actual.coords[newdim.name] = newdim.values if newdim.is_scalar else np.array(newdim.values) expect3: T_ExpectIndex | np.ndarray for name, expect2, by_ in zip(group_names, expected_groups_valid_list, by_da): # Can't remove this until xarray handles IntervalIndex: if isinstance(expect2, pd.IntervalIndex): # TODO: Only place where expect3 is an ndarray, remove the type if xarray # starts supporting IntervalIndex. expect3 = expect2.to_numpy() else: expect3 = expect2 if isinstance(actual, xr.Dataset) and name in actual: actual = actual.drop_vars(name) # When grouping by MultiIndex, expect is an pd.Index wrapping # an object array of tuples xindex = ds_broad.xindexes.get(name) if isinstance(xindex, PandasMultiIndex) and not isinstance(expect3, pd.RangeIndex): levelnames = xindex.index.names if isinstance(expect3, np.ndarray): # TODO: workaround for IntervalIndex issue. raise NotImplementedError expect3 = pd.MultiIndex.from_tuples(expect3.values.tolist(), names=levelnames) actual[name] = expect3 if Version(xr.__version__) > Version("2022.03.0"): actual = actual.set_coords(levelnames) else: actual[name] = expect3 if keep_attrs: actual[name].attrs = by_.attrs if unindexed_dims: actual = actual.drop_vars(unindexed_dims) if nby == 1: for var in actual: if isinstance(obj, xr.Dataset): template = obj[var] else: template = obj if actual[var].ndim > 1 + len(vector_dims): no_groupby_reorder = isinstance(obj, xr.Dataset) # do not re-order dataarrays inside datasets actual[var] = _restore_dim_order( actual[var].variable, template, by_da[0], no_groupby_reorder=no_groupby_reorder, ) if missing_dim: for k, v in missing_dim.items(): missing_group_dims = {d: size for d, size in group_sizes.items() if d not in v.dims} # The expand_dims is for backward compat with xarray's questionable behaviour if missing_group_dims: actual[k] = v.expand_dims(missing_group_dims).variable else: actual[k] = v.variable if isinstance(obj, xr.DataArray): return obj._from_temp_dataset(actual) else: return actual def rechunk_for_cohorts( obj: T_DataArray | T_Dataset, dim: str, labels: T_DataArray, force_new_chunk_at, chunksize: int | None = None, ignore_old_chunks: bool = False, debug: bool = False, ): """ Rechunks array so that each new chunk contains groups that always occur together. Parameters ---------- obj : DataArray or Dataset array to rechunk dim : str Dimension to rechunk labels : DataArray 1D Group labels to align chunks with. This routine works well when ``labels`` has repeating patterns: e.g. ``1, 2, 3, 1, 2, 3, 4, 1, 2, 3`` though there is no requirement that the pattern must contain sequences. force_new_chunk_at : Sequence Labels at which we always start a new chunk. For the example ``labels`` array, this would be `1`. chunksize : int, optional nominal chunk size. Chunk size is exceeded when the label in ``force_new_chunk_at`` is less than ``chunksize//2`` elements away. If None, uses median chunksize along ``dim``. Returns ------- DataArray or Dataset Xarray object with rechunked arrays. """ return _rechunk( rechunk_array_for_cohorts, obj, dim, labels, force_new_chunk_at=force_new_chunk_at, chunksize=chunksize, ignore_old_chunks=ignore_old_chunks, debug=debug, ) def rechunk_for_blockwise(obj: T_DataArray | T_Dataset, dim: str, labels: T_DataArray): """ Rechunks array so that group boundaries line up with chunk boundaries, allowing embarrassingly parallel group reductions. This only works when the groups are sequential (e.g. labels = ``[0,0,0,1,1,1,1,2,2]``). Such patterns occur when using ``.resample``. Parameters ---------- obj : DataArray or Dataset Array to rechunk dim : hashable Name of dimension to rechunk labels : DataArray Group labels Returns ------- DataArray or Dataset Xarray object with rechunked arrays. """ return _rechunk(toolz.compose(toolz.last, rechunk_array_for_blockwise), obj, dim, labels) def _rechunk(func, obj, dim, labels, **kwargs): """Common logic for rechunking xarray objects.""" obj = obj.copy(deep=True) if isinstance(obj, xr.Dataset): for var in obj: if obj[var].chunks is not None: obj[var] = obj[var].copy( data=func( obj[var].data, axis=obj[var].get_axis_num(dim), labels=labels.data, **kwargs, ) ) else: if obj.chunks is not None: obj = obj.copy(data=func(obj.data, axis=obj.get_axis_num(dim), labels=labels.data, **kwargs)) return obj flox-0.11.2/flox/xrdtypes.py000066400000000000000000000136321514775624000157670ustar00rootroot00000000000000import functools import numpy as np from numpy.typing import DTypeLike from . import xrutils as utils # Use as a sentinel value to indicate a dtype appropriate NA value. NA = utils.ReprObject("") @functools.total_ordering class AlwaysGreaterThan: def __gt__(self, other): return True def __eq__(self, other): return isinstance(other, type(self)) @functools.total_ordering class AlwaysLessThan: def __lt__(self, other): return True def __eq__(self, other): return isinstance(other, type(self)) # Equivalence to np.inf (-np.inf) for object-type INF = AlwaysGreaterThan() NINF = AlwaysLessThan() def maybe_promote(dtype): """Simpler equivalent of pandas.core.common._maybe_promote Parameters ---------- dtype : np.dtype Returns ------- dtype : Promoted dtype that can hold missing values. fill_value : Valid missing value for the promoted dtype. """ # N.B. these casting rules should match pandas if np.issubdtype(dtype, np.floating): fill_value = np.nan elif np.issubdtype(dtype, np.timedelta64): # See https://github.com/numpy/numpy/issues/10685 # np.timedelta64 is a subclass of np.integer # Check np.timedelta64 before np.integer fill_value = np.timedelta64("NaT") elif np.issubdtype(dtype, np.integer): dtype = np.float32 if dtype.itemsize <= 2 else np.float64 fill_value = np.nan elif np.issubdtype(dtype, np.complexfloating): fill_value = np.nan + np.nan * 1j elif np.issubdtype(dtype, np.datetime64): fill_value = np.datetime64("NaT") else: dtype = object fill_value = np.nan return np.dtype(dtype), fill_value NAT_TYPES = {np.datetime64("NaT").dtype, np.timedelta64("NaT").dtype} def get_fill_value(dtype): """Return an appropriate fill value for this dtype. Parameters ---------- dtype : np.dtype Returns ------- fill_value : Missing value corresponding to this dtype. """ _, fill_value = maybe_promote(dtype) return fill_value def get_pos_infinity(dtype, max_for_int=False): """Return an appropriate positive infinity for this dtype. Parameters ---------- dtype : np.dtype max_for_int : bool Return np.iinfo(dtype).max instead of np.inf Returns ------- fill_value : positive infinity value corresponding to this dtype. """ if issubclass(dtype.type, np.floating): return np.inf if issubclass(dtype.type, np.integer): if max_for_int: dtype = np.int64 if dtype.kind in "Mm" else dtype return np.iinfo(dtype).max else: return np.inf if issubclass(dtype.type, np.complexfloating): return np.inf + 1j * np.inf return INF def get_neg_infinity(dtype, min_for_int=False): """Return an appropriate positive infinity for this dtype. Parameters ---------- dtype : np.dtype min_for_int : bool Return np.iinfo(dtype).min instead of -np.inf Returns ------- fill_value : positive infinity value corresponding to this dtype. """ if is_datetime_like(dtype): unit, _ = np.datetime_data(dtype) return dtype.type(np.iinfo(np.int64).min + 1, unit) if issubclass(dtype.type, np.floating): return -np.inf if issubclass(dtype.type, np.integer): if min_for_int: return np.iinfo(dtype).min else: return -np.inf if issubclass(dtype.type, np.complexfloating): return -np.inf - 1j * np.inf return NINF def is_datetime_like(dtype): """Check if a dtype is a subclass of the numpy datetime types""" return np.issubdtype(dtype, np.datetime64) or np.issubdtype(dtype, np.timedelta64) def _normalize_dtype( dtype: DTypeLike, array_dtype: np.dtype, preserves_dtype: bool, fill_value=None ) -> np.dtype: if dtype is None: if not preserves_dtype: dtype = _maybe_promote_int(array_dtype) else: dtype = array_dtype if dtype is np.floating: # mean, std, var always result in floating # but we preserve the array's dtype if it is floating if array_dtype.kind in "fcmM": dtype = array_dtype else: dtype = np.dtype("float64") elif not isinstance(dtype, np.dtype): dtype = np.dtype(dtype) if fill_value not in [None, INF, NINF, NA]: dtype = np.result_type(dtype, fill_value) return dtype def _maybe_promote_int(dtype) -> np.dtype: # https://numpy.org/doc/stable/reference/generated/numpy.prod.html # The dtype of a is used by default unless a has an integer dtype of less precision # than the default platform integer. if not isinstance(dtype, np.dtype): dtype = np.dtype(dtype) if dtype.kind == "i": dtype = np.result_type(dtype, np.int_) elif dtype.kind == "u": dtype = np.result_type(dtype, np.uint) return dtype def _get_fill_value(dtype, fill_value): """Returns dtype appropriate infinity. Returns +Inf equivalent for None.""" if fill_value in [None, NA] and dtype.kind in "US": return "" if fill_value == INF or fill_value is None: return get_pos_infinity(dtype, max_for_int=True) if fill_value == NINF: return get_neg_infinity(dtype, min_for_int=True) if fill_value == NA: if np.issubdtype(dtype, np.floating) or np.issubdtype(dtype, np.complexfloating): return np.nan elif np.issubdtype(dtype, np.timedelta64): return np.timedelta64("NaT") elif np.issubdtype(dtype, np.datetime64): return np.datetime64("NaT") # This is madness, but npg checks that fill_value is compatible # with array dtype even if the fill_value is never used. elif np.issubdtype(dtype, np.integer): return get_neg_infinity(dtype, min_for_int=True) else: return None return fill_value flox-0.11.2/flox/xrutils.py000066400000000000000000000316251514775624000156210ustar00rootroot00000000000000# The functions defined here were copied based on the source code # defined in xarray import datetime import importlib from collections.abc import Iterable from types import ModuleType from typing import Any import numpy as np import pandas as pd from packaging.version import Version def module_available(module: str, minversion: str | None = None) -> bool: """Checks whether a module is installed without importing it. Use this for a lightweight check and lazy imports. Parameters ---------- module : str Name of the module. Returns ------- available : bool Whether the module is installed. """ has = importlib.util.find_spec(module) is not None if has: mod = importlib.import_module(module) return Version(mod.__version__) >= Version(minversion) if minversion is not None else True else: return False if module_available("numpy", minversion="2.0.0"): from numpy.lib.array_utils import normalize_axis_index else: from numpy.core.numeric import normalize_axis_index # type: ignore[no-redef] try: import cftime except ImportError: cftime = None cubed: ModuleType | None try: import cubed # type: ignore[no-redef] except ImportError: cubed = None dask: ModuleType | None try: import dask.array dask_array_type = dask.array.Array # type: ignore[union-attr] except ImportError: dask = None dask_array_type = () def asarray(data, xp=np): return data if is_duck_array(data) else xp.asarray(data) def is_duck_array(value: Any) -> bool: """Checks if value is a duck array.""" if isinstance(value, np.ndarray): return True return ( hasattr(value, "ndim") and hasattr(value, "shape") and hasattr(value, "dtype") and ( (hasattr(value, "__array_function__") and hasattr(value, "__array_ufunc__")) or hasattr(value, "__array_namespace__") ) ) def is_chunked_array(x) -> bool: """True if dask or cubed""" return is_duck_dask_array(x) or (is_duck_array(x) and hasattr(x, "chunks")) def is_dask_collection(x): if dask is None: return False return dask.is_dask_collection(x) def is_duck_dask_array(x): return is_duck_array(x) and is_dask_collection(x) def is_duck_cubed_array(x): if cubed is None: return False return is_duck_array(x) and isinstance(x, cubed.Array) class ReprObject: """Object that prints as the given value, for use with sentinel values.""" __slots__ = ("_value",) def __init__(self, value: str): self._value = value def __repr__(self) -> str: return self._value def __eq__(self, other) -> bool: if isinstance(other, ReprObject): return self._value == other._value return False def __hash__(self) -> int: return hash((type(self), self._value)) def __dask_tokenize__(self): from dask.base import normalize_token return normalize_token((type(self), self._value)) def is_scalar(value: Any, include_0d: bool = True) -> bool: """Whether to treat a value as a scalar. Any non-iterable, string, dict, or 0-D array """ NON_NUMPY_SUPPORTED_ARRAY_TYPES = (dask_array_type, pd.Index) if include_0d: include_0d = getattr(value, "ndim", None) == 0 return ( include_0d or isinstance(value, str | bytes | dict) or not ( isinstance(value, (Iterable,) + NON_NUMPY_SUPPORTED_ARRAY_TYPES) or hasattr(value, "__array_function__") ) ) def notnull(data): if isinstance(data, tuple) and len(data) == 3 and data == (0, 0, 0): # boo: another special case for Var return True if not is_duck_array(data): data = np.asarray(data) scalar_type = data.dtype.type if issubclass(scalar_type, np.bool_ | np.integer | np.character | np.void): # these types cannot represent missing values return np.broadcast_to(np.array(True), data.shape) else: out = isnull(data) if np.isscalar(out): return ~out else: np.logical_not(out, out=out) return out def isnull(data: Any): if isinstance(data, tuple) and len(data) == 3 and data == (0, 0, 0): # boo: another special case for Var return False if data is None: return False if not is_duck_array(data): data = np.asarray(data) scalar_type = data.dtype.type if issubclass(scalar_type, np.datetime64 | np.timedelta64): # datetime types use NaT for null # note: must check timedelta64 before integers, because currently # timedelta64 inherits from np.integer return np.isnat(data) elif issubclass(scalar_type, np.inexact): # float types use NaN for null return np.isnan(data) elif issubclass(scalar_type, np.bool_ | np.integer | np.character | np.void): # these types cannot represent missing values return np.broadcast_to(np.array(False), data.shape) else: # at this point, array should have dtype=object if isinstance(data, (np.ndarray, dask_array_type)): # noqa return pd.isnull(data) else: # Not reachable yet, but intended for use with other duck array # types. For full consistency with pandas, we should accept None as # a null value as well as NaN, but it isn't clear how to do this # with duck typing. return data != data def datetime_to_numeric(array, offset=None, datetime_unit=None, dtype=float): """Convert an array containing datetime-like data to numerical values. Convert the datetime array to a timedelta relative to an offset. Parameters ---------- array : array-like Input data offset : None, datetime or cftime.datetime Datetime offset. If None, this is set by default to the array's minimum value to reduce round off errors. datetime_unit : {None, Y, M, W, D, h, m, s, ms, us, ns, ps, fs, as} If not None, convert output to a given datetime unit. Note that some conversions are not allowed due to non-linear relationships between units. dtype : dtype Output dtype. Returns ------- array Numerical representation of datetime object relative to an offset. Notes ----- Some datetime unit conversions won't work, for example from days to years, even though some calendars would allow for them (e.g. no_leap). This is because there is no `cftime.timedelta` object. """ # TODO: make this function dask-compatible? # Set offset to minimum if not given if offset is None: if array.dtype.kind in "Mm": offset = _datetime_nanmin(array) else: offset = array.min() # Compute timedelta object. # For np.datetime64, this can silently yield garbage due to overflow. # One option is to enforce 1970-01-01 as the universal offset. # This map_blocks call is for backwards compatibility. # dask == 2021.04.1 does not support subtracting object arrays # which is required for cftime if is_duck_dask_array(array) and np.issubdtype(array.dtype, object): array = array.map_blocks(lambda a, b: a - b, offset, meta=array._meta) else: array = array - offset # Scalar is converted to 0d-array if not hasattr(array, "dtype"): array = np.array(array) # Convert timedelta objects to float by first converting to microseconds. if array.dtype.kind in "O": return py_timedelta_to_float(array, datetime_unit or "ns").astype(dtype) # Convert np.NaT to np.nan elif array.dtype.kind in "mM": # Convert to specified timedelta units. if datetime_unit: array = array / np.timedelta64(1, datetime_unit) return np.where(isnull(array), np.nan, array.astype(dtype)) def timedelta_to_numeric(value, datetime_unit="ns", dtype=float): """Convert a timedelta-like object to numerical values. Parameters ---------- value : datetime.timedelta, numpy.timedelta64, pandas.Timedelta, str Time delta representation. datetime_unit : {Y, M, W, D, h, m, s, ms, us, ns, ps, fs, as} The time units of the output values. Note that some conversions are not allowed due to non-linear relationships between units. dtype : type The output data type. """ import datetime as dt if isinstance(value, dt.timedelta): out = py_timedelta_to_float(value, datetime_unit) elif isinstance(value, np.timedelta64): out = np_timedelta64_to_float(value, datetime_unit) elif isinstance(value, pd.Timedelta): out = pd_timedelta_to_float(value, datetime_unit) elif isinstance(value, str): try: a = pd.to_timedelta(value) except ValueError: raise ValueError(f"Could not convert {value!r} to timedelta64 using pandas.to_timedelta") return py_timedelta_to_float(a, datetime_unit) else: raise TypeError( f"Expected value of type str, pandas.Timedelta, datetime.timedelta " f"or numpy.timedelta64, but received {type(value).__name__}" ) return out.astype(dtype) def _to_pytimedelta(array, unit="us"): return array.astype(f"timedelta64[{unit}]").astype(datetime.timedelta) def np_timedelta64_to_float(array, datetime_unit): """Convert numpy.timedelta64 to float. Notes ----- The array is first converted to microseconds, which is less likely to cause overflow errors. """ array = array.astype("timedelta64[ns]").astype(np.float64) conversion_factor = np.timedelta64(1, "ns") / np.timedelta64(1, datetime_unit) return conversion_factor * array def pd_timedelta_to_float(value, datetime_unit): """Convert pandas.Timedelta to float. Notes ----- Built on the assumption that pandas timedelta values are in nanoseconds, which is also the numpy default resolution. """ value = value.to_timedelta64() return np_timedelta64_to_float(value, datetime_unit) def _timedelta_to_seconds(array): return np.reshape([a.total_seconds() for a in array.ravel()], array.shape) * 1e6 def py_timedelta_to_float(array, datetime_unit): """Convert a timedelta object to a float, possibly at a loss of resolution.""" array = asarray(array) if is_duck_dask_array(array): array = array.map_blocks(_timedelta_to_seconds, meta=np.array([], dtype=np.float64)) else: array = _timedelta_to_seconds(array) conversion_factor = np.timedelta64(1, "us") / np.timedelta64(1, datetime_unit) return conversion_factor * array def _contains_cftime_datetimes(array) -> bool: """Check if an array contains cftime.datetime objects""" if cftime is None: return False else: if array.dtype == np.dtype("O") and array.size > 0: sample = array.ravel()[0] if is_duck_dask_array(sample): sample = sample.compute() if isinstance(sample, np.ndarray): sample = sample.item() return isinstance(sample, cftime.datetime) else: return False def _datetime_nanmin(array): """nanmin() function for datetime64. Caveats that this function deals with: - In numpy < 1.18, min() on datetime64 incorrectly ignores NaT - numpy nanmin() don't work on datetime64 (all versions at the moment of writing) - dask min() does not work on datetime64 (all versions at the moment of writing) """ from .xrdtypes import is_datetime_like dtype = array.dtype assert is_datetime_like(dtype) # (NaT).astype(float) does not produce NaN... array = np.where(pd.isnull(array), np.nan, array.astype(float)) array = np.nanmin(array) if isinstance(array, float): array = np.array(array) # ...but (NaN).astype("M8") does produce NaT return array.astype(dtype) def _select_along_axis(values, idx, axis): other_ind = np.ix_(*[np.arange(s) for s in idx.shape]) sl = other_ind[:axis] + (idx,) + other_ind[axis:] return values[sl] def nanfirst(values, axis, keepdims=False): if isinstance(axis, tuple): (axis,) = axis if not is_duck_array(values): values = np.asarray(values) axis = normalize_axis_index(axis, values.ndim) idx_first = np.argmax(~isnull(values), axis=axis) result = _select_along_axis(values, idx_first, axis) if keepdims: return np.expand_dims(result, axis=axis) else: return result def nanlast(values, axis, keepdims=False): if isinstance(axis, tuple): (axis,) = axis if not is_duck_array(values): values = np.asarray(values) axis = normalize_axis_index(axis, values.ndim) rev = (slice(None),) * axis + (slice(None, None, -1),) idx_last = -1 - np.argmax(~isnull(values)[rev], axis=axis) result = _select_along_axis(values, idx_last, axis) if keepdims: return np.expand_dims(result, axis=axis) else: return result flox-0.11.2/pyproject.toml000066400000000000000000000133721514775624000155000ustar00rootroot00000000000000[project] name = "flox" description = "GroupBy operations for dask.array" license = "Apache-2.0" readme = "README.md" requires-python = ">=3.11" keywords = ["xarray", "dask", "groupby"] classifiers = [ "Development Status :: 4 - Beta", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", ] dependencies = [ "pandas>=2.1", "packaging>=21.3", "numpy>=1.26", "numpy_groupies>=0.9.19", "toolz", "scipy>=1.12", ] dynamic=["version"] [project.urls] homepage = "https://flox.readthedocs.io" documentation = "https://flox.readthedocs.io" repository = "https://github.com/xarray-contrib/flox.git" changelog = "https://github.com/xarray-contrib/flox/releases" [project.optional-dependencies] all = ["cachey", "dask", "numba", "numbagg", "xarray"] test = ["netCDF4"] docs = [ "cubed>=0.20.0", "cubed-xarray", "dask", "xarray", "numpydoc", "matplotlib", "myst-parser", "myst-nb", "sparse", "sphinx", "sphinx-remove-toctrees", "furo>=2024.08", "ipykernel", "jupyter", "sphinx-codeautolink", "sphinx-copybutton", "pyarrow", ] [build-system] requires = [ "pandas>=2.1", "numpy>=1.26", "numpy_groupies>=0.9.19", "scipy>=1.12", "toolz", "setuptools>=61.0.0", "setuptools_scm[toml]>=7.0", ] build-backend = "setuptools.build_meta" [tool.setuptools] packages = ["flox"] [tool.setuptools.dynamic] version = {attr = "flox.__version__"} [tool.setuptools_scm] fallback_version = "999" write_to = "flox/_version.py" write_to_template= '__version__ = "{version}"' [tool.ruff] line-length = 110 target-version = "py311" builtins = ["ellipsis"] exclude = [ ".eggs", "doc", ] [tool.ruff.lint] # E402: module level import not at top of file # E501: line too long - let black worry about that # E731: do not assign a lambda expression, use a def ignore = [ "E402", "E501", "E731", ] select = [ # Bugbear # "B", # Pyflakes "F", # Pycodestyle "E", "W", # isort "I", # Pyupgrade "UP", # flake8-tidy-imports "TID", ] [tool.ruff.lint.isort] known-first-party = ["flox"] known-third-party = [ "dask", "numpy", "numpy_groupies", "pandas", "pkg_resources", "pytest", "setuptools", "scipy", "xarray" ] [tool.ruff.format] # Enable reformatting of code snippets in docstrings. docstring-code-format = true [tool.mypy] allow_redefinition = true files = "**/*.py" show_error_codes = true warn_unused_ignores = true warn_unreachable = true enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] exclude=["asv_bench/pkgs"] [[tool.mypy.overrides]] module=[ "asv_runner.*", "cachey", "cftime", "cubed.*", "dask.*", "importlib_metadata", "numba", "numbagg.*", "numpy_groupies.*", "matplotlib.*", "pandas", "setuptools", "scipy.*", "sparse.*", "toolz.*", ] ignore_missing_imports = true [tool.pytest.ini_options] addopts = "--tb=short" minversion = "7" testpaths = ["tests"] [tool.codespell] ignore-words-list = "nd,nax,coo" skip = "*.html" [dependency-groups] test = [ "hypothesis", "pytest>=7", "pytest-cov", "pytest-pretty", "pytest-xdist", "syrupy", "pooch", "codecov", "pyarrow", ] all = ["cachey", "dask", "numba", "numbagg>=0.3", "xarray"] complete = [ {include-group = "test"}, "cachey", "dask", "numba", "numbagg>=0.3", "xarray", "asv", "cftime", "cubed>=0.20.0", "lxml", "matplotlib", "sparse", "zarr", "pre-commit", ] minimal = [ {include-group = "test"}, ] no-dask = [ {include-group = "test"}, "cftime", "sparse", "numpydoc", "numba", "numbagg>=0.3", "xarray", "pre-commit", ] no-numba = [ {include-group = "test"}, "asv", "cachey", "cftime", "dask[core]", "sparse", "lxml", "matplotlib", "xarray", "pre-commit", ] no-xarray = [ {include-group = "test"}, "sparse", "dask[core]", "numpydoc", "numba", "pre-commit", ] numpy1 = [ {include-group = "complete"}, ] docs = [ "cubed>=0.20.0", "cubed-xarray", "dask", "xarray", "numpydoc", "matplotlib", "myst-parser", "myst-nb", "sparse", "sphinx", "sphinx-remove-toctrees", "furo>=2024.08", "ipykernel", "jupyter", "sphinx-codeautolink", "sphinx-copybutton", ] benchmark = [ "asv>=0.6.4", "py-rattler", "build", "cachey", "dask[core]", "xarray", "numbagg>=0.3", "wheel", "pyarrow", ] upstream = [ {include-group = "test"}, "asv_runner", "cachey", "pooch", "hypothesis", "toolz", "pytest-reportlog", "cython>=0.29.20", "py-cpuinfo", "python-dateutil", "pytz", ] upstream-dev = [ {include-group = "upstream"}, # These will be overridden by tool.uv.sources when this group is used "numpy", "scipy", "pandas", "xarray", "dask[core]", "numpy-groupies", "sparse", "cftime", ] dev = [ {include-group = "complete"}, "dask[complete]", "pdbpp", "ipykernel", "snakeviz", "line_profiler", "memory-profiler", "memray", ] types = [ "mypy", "pandas-stubs", "types-PyYAML", "types-Pygments", "types-defusedxml", "types-docutils", "types-pexpect", "types-psutil", "types-pytz", "types-requests", "types-setuptools", ] [[tool.uv.index]] name = "testpypi" url = "https://test.pypi.org/simple/" publish-url = "https://test.pypi.org/legacy/" explicit = true flox-0.11.2/setup.py000066400000000000000000000000751514775624000142720ustar00rootroot00000000000000#!/usr/bin/env python from setuptools import setup setup() flox-0.11.2/tests/000077500000000000000000000000001514775624000137205ustar00rootroot00000000000000flox-0.11.2/tests/__init__.py000066400000000000000000000215041514775624000160330ustar00rootroot00000000000000import importlib from contextlib import nullcontext from typing import Any import numpy as np import packaging.version import pandas as pd import pytest from flox.lib import dask_array_type, sparse_array_type from flox.xrutils import is_duck_dask_array pd_types = (pd.Index,) try: import xarray as xr xr_types = (xr.DataArray, xr.Dataset) except ImportError: xr_types = () # type: ignore[assignment] def _importorskip(modname, minversion=None): try: mod = importlib.import_module(modname) has = True if minversion is not None: if LooseVersion(mod.__version__) < LooseVersion(minversion): raise ImportError("Minimum version not satisfied") except ImportError: has = False func = pytest.mark.skipif(not has, reason=f"requires {modname}") return has, func def LooseVersion(vstring): # Our development version is something like '0.10.9+aac7bfc' # This function just ignored the git commit id. vstring = vstring.split("+")[0] return packaging.version.Version(vstring) has_cftime, requires_cftime = _importorskip("cftime") has_cubed, requires_cubed = _importorskip("cubed") has_dask, requires_dask = _importorskip("dask") has_sparse, requires_sparse = _importorskip("sparse") has_numba, requires_numba = _importorskip("numba") has_numbagg, requires_numbagg = _importorskip("numbagg") has_scipy, requires_scipy = _importorskip("scipy") has_xarray, requires_xarray = _importorskip("xarray") class CountingScheduler: """Simple dask scheduler counting the number of computes. Reference: https://stackoverflow.com/questions/53289286/""" def __init__(self, max_computes=0): self.total_computes = 0 self.max_computes = max_computes def __call__(self, dsk, keys, **kwargs): import dask self.total_computes += 1 if self.total_computes > self.max_computes: raise RuntimeError(f"Too many computes. Total: {self.total_computes} > max: {self.max_computes}.") return dask.get(dsk, keys, **kwargs) def raise_if_dask_computes(max_computes=0): # return a dummy context manager so that this can be used for non-dask objects if not has_dask: return nullcontext() import dask scheduler = CountingScheduler(max_computes) return dask.config.set(scheduler=scheduler) def assert_equal(a, b, tolerance=None): __tracebackhide__ = True if isinstance(a, list): a = np.array(a) if isinstance(b, list): b = np.array(b) if isinstance(a, pd_types) or isinstance(b, pd_types): pd.testing.assert_index_equal(a, b) return if has_xarray and isinstance(a, xr_types) or isinstance(b, xr_types): xr.testing.assert_identical(a, b) return if tolerance is None: if np.issubdtype(a.dtype, np.float64) | np.issubdtype(b.dtype, np.float64): tolerance = {"atol": 1e-18, "rtol": 1e-15} else: tolerance = {} # Always run the numpy comparison first, so that we get nice error messages with dask. # sometimes it's nice to see values and shapes # rather than being dropped into some file in dask if a.dtype != b.dtype: raise AssertionError(f"a and b have different dtypes: (a: {a.dtype}, b: {b.dtype})") if has_dask: a_eager = a.compute() if isinstance(a, dask_array_type) else a b_eager = b.compute() if isinstance(b, dask_array_type) else b else: a_eager, b_eager = a, b if has_sparse: one_is_sparse = isinstance(a_eager, sparse_array_type) or isinstance(b_eager, sparse_array_type) a_eager = a_eager.todense() if isinstance(a_eager, sparse_array_type) else a_eager b_eager = b_eager.todense() if isinstance(b_eager, sparse_array_type) else b_eager else: one_is_sparse = False if a.dtype.kind in "SUMmO": np.testing.assert_equal(a_eager, b_eager) else: np.testing.assert_allclose(a_eager, b_eager, equal_nan=True, **tolerance) if has_dask and isinstance(a, dask_array_type) or isinstance(b, dask_array_type): # does some validation of the dask graph dask_assert_eq(a, b, equal_nan=True, check_type=not one_is_sparse) def assert_equal_tuple(a, b): """assert_equal for .blocks indexing tuples""" assert len(a) == len(b) for a_, b_ in zip(a, b): assert type(a_) is type(b_) if isinstance(a_, np.ndarray): np.testing.assert_array_equal(a_, b_) else: assert a_ == b_ SCIPY_STATS_FUNCS = ("mode", "nanmode") BLOCKWISE_FUNCS = ("median", "nanmedian", "quantile", "nanquantile") + SCIPY_STATS_FUNCS ALL_FUNCS = ( "sum", "nansum", "argmax", "nanfirst", "nanargmax", "prod", "nanprod", "mean", "nanmean", "var", "nanvar", "std", "nanstd", "max", "nanmax", "min", "nanmin", "argmin", "nanargmin", "any", "all", "nanlast", "median", "nanmedian", "quantile", "nanquantile", ) + tuple(SCIPY_STATS_FUNCS) def dask_assert_eq( a, b, check_shape=True, check_graph=True, check_meta=True, check_chunks=True, check_ndim=True, check_type=True, check_dtype=True, equal_nan=True, scheduler="sync", **kwargs, ): """dask.array.utils.assert_eq modified to skip value checks. Their code is buggy for some dtypes. We just check values through numpy and care about validating the graph in this function.""" from dask.array.utils import _get_dt_meta_computed a_original = a b_original = b if isinstance(a, list | int | float): a = np.array(a) if isinstance(b, list | int | float): b = np.array(b) a, adt, a_meta, a_computed = _get_dt_meta_computed( a, check_shape=check_shape, check_graph=check_graph, check_chunks=check_chunks, check_ndim=check_ndim, scheduler=scheduler, ) b, bdt, b_meta, b_computed = _get_dt_meta_computed( b, check_shape=check_shape, check_graph=check_graph, check_chunks=check_chunks, check_ndim=check_ndim, scheduler=scheduler, ) if check_type: _a = a if a.shape else a.item() _b = b if b.shape else b.item() assert type(_a) is type(_b), f"a and b have different types (a: {type(_a)}, b: {type(_b)})" if check_meta: if hasattr(a, "_meta") and hasattr(b, "_meta"): dask_assert_eq(a._meta, b._meta) if hasattr(a_original, "_meta"): msg = ( f"compute()-ing 'a' changes its number of dimensions " f"(before: {a_original._meta.ndim}, after: {a.ndim})" ) assert a_original._meta.ndim == a.ndim, msg if a_meta is not None: msg = ( f"compute()-ing 'a' changes its type " f"(before: {type(a_original._meta)}, after: {type(a_meta)})" ) assert type(a_original._meta) is type(a_meta), msg if not (np.isscalar(a_meta) or np.isscalar(a_computed)): msg = ( f"compute()-ing 'a' results in a different type than implied by its metadata " f"(meta: {type(a_meta)}, computed: {type(a_computed)})" ) assert type(a_meta) is type(a_computed), msg if hasattr(b_original, "_meta"): msg = ( f"compute()-ing 'b' changes its number of dimensions " f"(before: {b_original._meta.ndim}, after: {b.ndim})" ) assert b_original._meta.ndim == b.ndim, msg if b_meta is not None: msg = ( f"compute()-ing 'b' changes its type " f"(before: {type(b_original._meta)}, after: {type(b_meta)})" ) assert type(b_original._meta) is type(b_meta), msg if not (np.isscalar(b_meta) or np.isscalar(b_computed)): msg = ( f"compute()-ing 'b' results in a different type than implied by its metadata " f"(meta: {type(b_meta)}, computed: {type(b_computed)})" ) assert type(b_meta) is type(b_computed), msg def to_numpy(data) -> np.ndarray[Any, np.dtype[Any]]: try: return data.to_numpy() except AttributeError: pass # TODO first attempt to call .to_numpy() once some libraries implement it if is_duck_dask_array(data): data = data.compute() # if isinstance(data, array_type("cupy")): # data = data.get() # # pint has to be imported dynamically as pint imports xarray # if isinstance(data, array_type("pint")): # data = data.magnitude if isinstance(data, sparse_array_type): data = data.todense() data = np.asarray(data) return data flox-0.11.2/tests/__snapshots__/000077500000000000000000000000001514775624000165365ustar00rootroot00000000000000flox-0.11.2/tests/__snapshots__/test_cohorts.ambr000066400000000000000000004632371514775624000221400ustar00rootroot00000000000000# serializer version: 1 # name: test_snapshot_cohorts[ERA5DayOfYear] dict({ tuple( 0, 365, 183, ): list([ 0, ]), tuple( 0, 366, 183, ): list([ 1, ]), tuple( 8, 373, 191, ): list([ 16, ]), tuple( 8, 374, 191, ): list([ 17, ]), tuple( 16, 381, 199, ): list([ 32, ]), tuple( 16, 382, 199, ): list([ 33, ]), tuple( 24, 389, 207, ): list([ 48, ]), tuple( 24, 390, 207, ): list([ 49, ]), tuple( 32, 397, 215, ): list([ 64, ]), tuple( 32, 398, 215, ): list([ 65, ]), tuple( 40, 405, 223, ): list([ 80, ]), tuple( 40, 406, 223, ): list([ 81, ]), tuple( 48, 413, 231, ): list([ 96, ]), tuple( 48, 414, 231, ): list([ 97, ]), tuple( 56, 421, 239, ): list([ 112, ]), tuple( 56, 422, 239, ): list([ 113, ]), tuple( 64, 429, 247, ): list([ 128, ]), tuple( 64, 430, 247, ): list([ 129, ]), tuple( 72, 437, 255, ): list([ 144, ]), tuple( 72, 438, 255, ): list([ 145, ]), tuple( 80, 445, 263, ): list([ 160, ]), tuple( 80, 446, 263, ): list([ 161, ]), tuple( 88, 453, 271, ): list([ 176, ]), tuple( 88, 454, 271, ): list([ 177, ]), tuple( 96, 461, 279, ): list([ 192, ]), tuple( 96, 462, 279, ): list([ 193, ]), tuple( 104, 469, 287, ): list([ 208, ]), tuple( 104, 470, 287, ): list([ 209, ]), tuple( 112, 477, 295, ): list([ 224, ]), tuple( 112, 478, 295, ): list([ 225, ]), tuple( 120, 485, 303, ): list([ 240, ]), tuple( 120, 486, 303, ): list([ 241, ]), tuple( 128, 493, 311, ): list([ 256, ]), tuple( 128, 494, 311, ): list([ 257, ]), tuple( 136, 501, 319, ): list([ 272, ]), tuple( 136, 502, 319, ): list([ 273, ]), tuple( 144, 509, 327, ): list([ 288, ]), tuple( 144, 510, 327, ): list([ 289, ]), tuple( 152, 517, 335, ): list([ 304, ]), tuple( 152, 518, 335, ): list([ 305, ]), tuple( 160, 525, 343, ): list([ 320, ]), tuple( 160, 526, 343, ): list([ 321, ]), tuple( 168, 533, 351, ): list([ 336, ]), tuple( 168, 534, 351, ): list([ 337, ]), tuple( 176, 541, 359, ): list([ 352, ]), tuple( 176, 542, 359, ): list([ 353, ]), tuple( 184, 1, 366, ): list([ 2, ]), tuple( 184, 1, 367, ): list([ 3, ]), tuple( 185, 2, 367, ): list([ 4, ]), tuple( 192, 9, 374, ): list([ 18, ]), tuple( 192, 9, 375, ): list([ 19, ]), tuple( 193, 10, 375, ): list([ 20, ]), tuple( 200, 17, 382, ): list([ 34, ]), tuple( 200, 17, 383, ): list([ 35, ]), tuple( 201, 18, 383, ): list([ 36, ]), tuple( 208, 25, 390, ): list([ 50, ]), tuple( 208, 25, 391, ): list([ 51, ]), tuple( 209, 26, 391, ): list([ 52, ]), tuple( 216, 33, 398, ): list([ 66, ]), tuple( 216, 33, 399, ): list([ 67, ]), tuple( 217, 34, 399, ): list([ 68, ]), tuple( 224, 41, 406, ): list([ 82, ]), tuple( 224, 41, 407, ): list([ 83, ]), tuple( 225, 42, 407, ): list([ 84, ]), tuple( 232, 49, 414, ): list([ 98, ]), tuple( 232, 49, 415, ): list([ 99, ]), tuple( 233, 50, 415, ): list([ 100, ]), tuple( 240, 57, 422, ): list([ 114, ]), tuple( 240, 57, 423, ): list([ 115, ]), tuple( 241, 58, 423, ): list([ 116, ]), tuple( 248, 65, 430, ): list([ 130, ]), tuple( 248, 65, 431, ): list([ 131, ]), tuple( 249, 66, 431, ): list([ 132, ]), tuple( 256, 73, 438, ): list([ 146, ]), tuple( 256, 73, 439, ): list([ 147, ]), tuple( 257, 74, 439, ): list([ 148, ]), tuple( 264, 81, 446, ): list([ 162, ]), tuple( 264, 81, 447, ): list([ 163, ]), tuple( 265, 82, 447, ): list([ 164, ]), tuple( 272, 89, 454, ): list([ 178, ]), tuple( 272, 89, 455, ): list([ 179, ]), tuple( 273, 90, 455, ): list([ 180, ]), tuple( 280, 97, 462, ): list([ 194, ]), tuple( 280, 97, 463, ): list([ 195, ]), tuple( 281, 98, 463, ): list([ 196, ]), tuple( 288, 105, 470, ): list([ 210, ]), tuple( 288, 105, 471, ): list([ 211, ]), tuple( 289, 106, 471, ): list([ 212, ]), tuple( 296, 113, 478, ): list([ 226, ]), tuple( 296, 113, 479, ): list([ 227, ]), tuple( 297, 114, 479, ): list([ 228, ]), tuple( 304, 121, 486, ): list([ 242, ]), tuple( 304, 121, 487, ): list([ 243, ]), tuple( 305, 122, 487, ): list([ 244, ]), tuple( 312, 129, 494, ): list([ 258, ]), tuple( 312, 129, 495, ): list([ 259, ]), tuple( 313, 130, 495, ): list([ 260, ]), tuple( 320, 137, 502, ): list([ 274, ]), tuple( 320, 137, 503, ): list([ 275, ]), tuple( 321, 138, 503, ): list([ 276, ]), tuple( 328, 145, 510, ): list([ 290, ]), tuple( 328, 145, 511, ): list([ 291, ]), tuple( 329, 146, 511, ): list([ 292, ]), tuple( 336, 153, 518, ): list([ 306, ]), tuple( 336, 153, 519, ): list([ 307, ]), tuple( 337, 154, 519, ): list([ 308, ]), tuple( 344, 161, 526, ): list([ 322, ]), tuple( 344, 161, 527, ): list([ 323, ]), tuple( 345, 162, 527, ): list([ 324, ]), tuple( 352, 169, 534, ): list([ 338, ]), tuple( 352, 169, 535, ): list([ 339, ]), tuple( 353, 170, 535, ): list([ 340, ]), tuple( 360, 177, 542, ): list([ 354, ]), tuple( 360, 177, 543, ): list([ 355, ]), tuple( 361, 178, 543, ): list([ 356, ]), tuple( 368, 185, 2, ): list([ 5, ]), tuple( 368, 186, 3, ): list([ 6, ]), tuple( 369, 186, 3, ): list([ 7, ]), tuple( 369, 187, 4, ): list([ 8, ]), tuple( 370, 187, 4, ): list([ 9, ]), tuple( 370, 188, 5, ): list([ 10, ]), tuple( 371, 188, 5, ): list([ 11, ]), tuple( 371, 189, 6, ): list([ 12, ]), tuple( 372, 189, 6, ): list([ 13, ]), tuple( 372, 190, 7, ): list([ 14, ]), tuple( 373, 190, 7, ): list([ 15, ]), tuple( 376, 193, 10, ): list([ 21, ]), tuple( 376, 194, 11, ): list([ 22, ]), tuple( 377, 194, 11, ): list([ 23, ]), tuple( 377, 195, 12, ): list([ 24, ]), tuple( 378, 195, 12, ): list([ 25, ]), tuple( 378, 196, 13, ): list([ 26, ]), tuple( 379, 196, 13, ): list([ 27, ]), tuple( 379, 197, 14, ): list([ 28, ]), tuple( 380, 197, 14, ): list([ 29, ]), tuple( 380, 198, 15, ): list([ 30, ]), tuple( 381, 198, 15, ): list([ 31, ]), tuple( 384, 201, 18, ): list([ 37, ]), tuple( 384, 202, 19, ): list([ 38, ]), tuple( 385, 202, 19, ): list([ 39, ]), tuple( 385, 203, 20, ): list([ 40, ]), tuple( 386, 203, 20, ): list([ 41, ]), tuple( 386, 204, 21, ): list([ 42, ]), tuple( 387, 204, 21, ): list([ 43, ]), tuple( 387, 205, 22, ): list([ 44, ]), tuple( 388, 205, 22, ): list([ 45, ]), tuple( 388, 206, 23, ): list([ 46, ]), tuple( 389, 206, 23, ): list([ 47, ]), tuple( 392, 209, 26, ): list([ 53, ]), tuple( 392, 210, 27, ): list([ 54, ]), tuple( 393, 210, 27, ): list([ 55, ]), tuple( 393, 211, 28, ): list([ 56, ]), tuple( 394, 211, 28, ): list([ 57, ]), tuple( 394, 212, 29, ): list([ 58, ]), tuple( 395, 212, 29, ): list([ 59, ]), tuple( 395, 213, 30, ): list([ 60, ]), tuple( 396, 213, 30, ): list([ 61, ]), tuple( 396, 214, 31, ): list([ 62, ]), tuple( 397, 214, 31, ): list([ 63, ]), tuple( 400, 217, 34, ): list([ 69, ]), tuple( 400, 218, 35, ): list([ 70, ]), tuple( 401, 218, 35, ): list([ 71, ]), tuple( 401, 219, 36, ): list([ 72, ]), tuple( 402, 219, 36, ): list([ 73, ]), tuple( 402, 220, 37, ): list([ 74, ]), tuple( 403, 220, 37, ): list([ 75, ]), tuple( 403, 221, 38, ): list([ 76, ]), tuple( 404, 221, 38, ): list([ 77, ]), tuple( 404, 222, 39, ): list([ 78, ]), tuple( 405, 222, 39, ): list([ 79, ]), tuple( 408, 225, 42, ): list([ 85, ]), tuple( 408, 226, 43, ): list([ 86, ]), tuple( 409, 226, 43, ): list([ 87, ]), tuple( 409, 227, 44, ): list([ 88, ]), tuple( 410, 227, 44, ): list([ 89, ]), tuple( 410, 228, 45, ): list([ 90, ]), tuple( 411, 228, 45, ): list([ 91, ]), tuple( 411, 229, 46, ): list([ 92, ]), tuple( 412, 229, 46, ): list([ 93, ]), tuple( 412, 230, 47, ): list([ 94, ]), tuple( 413, 230, 47, ): list([ 95, ]), tuple( 416, 233, 50, ): list([ 101, ]), tuple( 416, 234, 51, ): list([ 102, ]), tuple( 417, 234, 51, ): list([ 103, ]), tuple( 417, 235, 52, ): list([ 104, ]), tuple( 418, 235, 52, ): list([ 105, ]), tuple( 418, 236, 53, ): list([ 106, ]), tuple( 419, 236, 53, ): list([ 107, ]), tuple( 419, 237, 54, ): list([ 108, ]), tuple( 420, 237, 54, ): list([ 109, ]), tuple( 420, 238, 55, ): list([ 110, ]), tuple( 421, 238, 55, ): list([ 111, ]), tuple( 424, 241, 58, ): list([ 117, ]), tuple( 424, 242, 59, ): list([ 118, ]), tuple( 425, 242, 59, ): list([ 119, ]), tuple( 425, 243, 60, ): list([ 120, ]), tuple( 426, 243, 60, ): list([ 121, ]), tuple( 426, 244, 61, ): list([ 122, ]), tuple( 427, 244, 61, ): list([ 123, ]), tuple( 427, 245, 62, ): list([ 124, ]), tuple( 428, 245, 62, ): list([ 125, ]), tuple( 428, 246, 63, ): list([ 126, ]), tuple( 429, 246, 63, ): list([ 127, ]), tuple( 432, 249, 66, ): list([ 133, ]), tuple( 432, 250, 67, ): list([ 134, ]), tuple( 433, 250, 67, ): list([ 135, ]), tuple( 433, 251, 68, ): list([ 136, ]), tuple( 434, 251, 68, ): list([ 137, ]), tuple( 434, 252, 69, ): list([ 138, ]), tuple( 435, 252, 69, ): list([ 139, ]), tuple( 435, 253, 70, ): list([ 140, ]), tuple( 436, 253, 70, ): list([ 141, ]), tuple( 436, 254, 71, ): list([ 142, ]), tuple( 437, 254, 71, ): list([ 143, ]), tuple( 440, 257, 74, ): list([ 149, ]), tuple( 440, 258, 75, ): list([ 150, ]), tuple( 441, 258, 75, ): list([ 151, ]), tuple( 441, 259, 76, ): list([ 152, ]), tuple( 442, 259, 76, ): list([ 153, ]), tuple( 442, 260, 77, ): list([ 154, ]), tuple( 443, 260, 77, ): list([ 155, ]), tuple( 443, 261, 78, ): list([ 156, ]), tuple( 444, 261, 78, ): list([ 157, ]), tuple( 444, 262, 79, ): list([ 158, ]), tuple( 445, 262, 79, ): list([ 159, ]), tuple( 448, 265, 82, ): list([ 165, ]), tuple( 448, 266, 83, ): list([ 166, ]), tuple( 449, 266, 83, ): list([ 167, ]), tuple( 449, 267, 84, ): list([ 168, ]), tuple( 450, 267, 84, ): list([ 169, ]), tuple( 450, 268, 85, ): list([ 170, ]), tuple( 451, 268, 85, ): list([ 171, ]), tuple( 451, 269, 86, ): list([ 172, ]), tuple( 452, 269, 86, ): list([ 173, ]), tuple( 452, 270, 87, ): list([ 174, ]), tuple( 453, 270, 87, ): list([ 175, ]), tuple( 456, 273, 90, ): list([ 181, ]), tuple( 456, 274, 91, ): list([ 182, ]), tuple( 457, 274, 91, ): list([ 183, ]), tuple( 457, 275, 92, ): list([ 184, ]), tuple( 458, 275, 92, ): list([ 185, ]), tuple( 458, 276, 93, ): list([ 186, ]), tuple( 459, 276, 93, ): list([ 187, ]), tuple( 459, 277, 94, ): list([ 188, ]), tuple( 460, 277, 94, ): list([ 189, ]), tuple( 460, 278, 95, ): list([ 190, ]), tuple( 461, 278, 95, ): list([ 191, ]), tuple( 464, 281, 98, ): list([ 197, ]), tuple( 464, 282, 99, ): list([ 198, ]), tuple( 465, 282, 99, ): list([ 199, ]), tuple( 465, 283, 100, ): list([ 200, ]), tuple( 466, 283, 100, ): list([ 201, ]), tuple( 466, 284, 101, ): list([ 202, ]), tuple( 467, 284, 101, ): list([ 203, ]), tuple( 467, 285, 102, ): list([ 204, ]), tuple( 468, 285, 102, ): list([ 205, ]), tuple( 468, 286, 103, ): list([ 206, ]), tuple( 469, 286, 103, ): list([ 207, ]), tuple( 472, 289, 106, ): list([ 213, ]), tuple( 472, 290, 107, ): list([ 214, ]), tuple( 473, 290, 107, ): list([ 215, ]), tuple( 473, 291, 108, ): list([ 216, ]), tuple( 474, 291, 108, ): list([ 217, ]), tuple( 474, 292, 109, ): list([ 218, ]), tuple( 475, 292, 109, ): list([ 219, ]), tuple( 475, 293, 110, ): list([ 220, ]), tuple( 476, 293, 110, ): list([ 221, ]), tuple( 476, 294, 111, ): list([ 222, ]), tuple( 477, 294, 111, ): list([ 223, ]), tuple( 480, 297, 114, ): list([ 229, ]), tuple( 480, 298, 115, ): list([ 230, ]), tuple( 481, 298, 115, ): list([ 231, ]), tuple( 481, 299, 116, ): list([ 232, ]), tuple( 482, 299, 116, ): list([ 233, ]), tuple( 482, 300, 117, ): list([ 234, ]), tuple( 483, 300, 117, ): list([ 235, ]), tuple( 483, 301, 118, ): list([ 236, ]), tuple( 484, 301, 118, ): list([ 237, ]), tuple( 484, 302, 119, ): list([ 238, ]), tuple( 485, 302, 119, ): list([ 239, ]), tuple( 488, 305, 122, ): list([ 245, ]), tuple( 488, 306, 123, ): list([ 246, ]), tuple( 489, 306, 123, ): list([ 247, ]), tuple( 489, 307, 124, ): list([ 248, ]), tuple( 490, 307, 124, ): list([ 249, ]), tuple( 490, 308, 125, ): list([ 250, ]), tuple( 491, 308, 125, ): list([ 251, ]), tuple( 491, 309, 126, ): list([ 252, ]), tuple( 492, 309, 126, ): list([ 253, ]), tuple( 492, 310, 127, ): list([ 254, ]), tuple( 493, 310, 127, ): list([ 255, ]), tuple( 496, 313, 130, ): list([ 261, ]), tuple( 496, 314, 131, ): list([ 262, ]), tuple( 497, 314, 131, ): list([ 263, ]), tuple( 497, 315, 132, ): list([ 264, ]), tuple( 498, 315, 132, ): list([ 265, ]), tuple( 498, 316, 133, ): list([ 266, ]), tuple( 499, 316, 133, ): list([ 267, ]), tuple( 499, 317, 134, ): list([ 268, ]), tuple( 500, 317, 134, ): list([ 269, ]), tuple( 500, 318, 135, ): list([ 270, ]), tuple( 501, 318, 135, ): list([ 271, ]), tuple( 504, 321, 138, ): list([ 277, ]), tuple( 504, 322, 139, ): list([ 278, ]), tuple( 505, 322, 139, ): list([ 279, ]), tuple( 505, 323, 140, ): list([ 280, ]), tuple( 506, 323, 140, ): list([ 281, ]), tuple( 506, 324, 141, ): list([ 282, ]), tuple( 507, 324, 141, ): list([ 283, ]), tuple( 507, 325, 142, ): list([ 284, ]), tuple( 508, 325, 142, ): list([ 285, ]), tuple( 508, 326, 143, ): list([ 286, ]), tuple( 509, 326, 143, ): list([ 287, ]), tuple( 512, 329, 146, ): list([ 293, ]), tuple( 512, 330, 147, ): list([ 294, ]), tuple( 513, 330, 147, ): list([ 295, ]), tuple( 513, 331, 148, ): list([ 296, ]), tuple( 514, 331, 148, ): list([ 297, ]), tuple( 514, 332, 149, ): list([ 298, ]), tuple( 515, 332, 149, ): list([ 299, ]), tuple( 515, 333, 150, ): list([ 300, ]), tuple( 516, 333, 150, ): list([ 301, ]), tuple( 516, 334, 151, ): list([ 302, ]), tuple( 517, 334, 151, ): list([ 303, ]), tuple( 520, 337, 154, ): list([ 309, ]), tuple( 520, 338, 155, ): list([ 310, ]), tuple( 521, 338, 155, ): list([ 311, ]), tuple( 521, 339, 156, ): list([ 312, ]), tuple( 522, 339, 156, ): list([ 313, ]), tuple( 522, 340, 157, ): list([ 314, ]), tuple( 523, 340, 157, ): list([ 315, ]), tuple( 523, 341, 158, ): list([ 316, ]), tuple( 524, 341, 158, ): list([ 317, ]), tuple( 524, 342, 159, ): list([ 318, ]), tuple( 525, 342, 159, ): list([ 319, ]), tuple( 528, 345, 162, ): list([ 325, ]), tuple( 528, 346, 163, ): list([ 326, ]), tuple( 529, 346, 163, ): list([ 327, ]), tuple( 529, 347, 164, ): list([ 328, ]), tuple( 530, 347, 164, ): list([ 329, ]), tuple( 530, 348, 165, ): list([ 330, ]), tuple( 531, 348, 165, ): list([ 331, ]), tuple( 531, 349, 166, ): list([ 332, ]), tuple( 532, 349, 166, ): list([ 333, ]), tuple( 532, 350, 167, ): list([ 334, ]), tuple( 533, 350, 167, ): list([ 335, ]), tuple( 536, 353, 170, ): list([ 341, ]), tuple( 536, 354, 171, ): list([ 342, ]), tuple( 537, 354, 171, ): list([ 343, ]), tuple( 537, 355, 172, ): list([ 344, ]), tuple( 538, 355, 172, ): list([ 345, ]), tuple( 538, 356, 173, ): list([ 346, ]), tuple( 539, 356, 173, ): list([ 347, ]), tuple( 539, 357, 174, ): list([ 348, ]), tuple( 540, 357, 174, ): list([ 349, ]), tuple( 540, 358, 175, ): list([ 350, ]), tuple( 541, 358, 175, ): list([ 351, ]), tuple( 544, 361, 178, ): list([ 357, ]), tuple( 544, 362, 179, ): list([ 358, ]), tuple( 545, 362, 179, ): list([ 359, ]), tuple( 545, 363, 180, ): list([ 360, ]), tuple( 546, 363, 180, ): list([ 361, ]), tuple( 546, 364, 181, ): list([ 362, ]), tuple( 547, 364, 181, ): list([ 363, ]), tuple( 547, 365, 182, ): list([ 364, 365, ]), }) # --- # name: test_snapshot_cohorts[ERA5Google] dict({ tuple( 0, 1, 2, 3, 124, 125, 126, 127, 236, 237, 238, 239, 360, 361, 362, 363, 480, 481, 482, 483, 604, 605, 606, 607, 724, 725, 726, 727, 848, 849, 850, 851, ): list([ 0, ]), tuple( 4, 5, 6, 7, 128, 129, 130, 131, 240, 241, 242, 243, 364, 365, 366, 367, 484, 485, 486, 487, 608, 609, 610, 611, 728, 729, 730, 731, 852, 853, 854, 855, ): list([ 1, ]), tuple( 8, 9, 10, 11, 132, 133, 134, 135, 244, 245, 246, 247, 368, 369, 370, 371, 488, 489, 490, 491, 612, 613, 614, 615, 732, 733, 734, 735, 856, 857, 858, 859, ): list([ 2, ]), tuple( 12, 13, 14, 15, 136, 137, 138, 139, 248, 249, 250, 251, 372, 373, 374, 375, 492, 493, 494, 495, 616, 617, 618, 619, 736, 737, 738, 739, 860, 861, 862, 863, ): list([ 3, ]), tuple( 16, 17, 18, 19, 140, 141, 142, 143, 252, 253, 254, 255, 376, 377, 378, 379, 496, 497, 498, 499, 620, 621, 622, 623, 740, 741, 742, 743, 864, 865, 866, 867, ): list([ 4, ]), tuple( 20, 21, 22, 23, 144, 145, 146, 147, 256, 257, 258, 259, 380, 381, 382, 383, 500, 501, 502, 503, 624, 625, 626, 627, 744, 745, 746, 747, 868, 869, 870, 871, ): list([ 5, ]), tuple( 24, 25, 26, 27, 148, 149, 150, 151, 260, 261, 262, 263, 384, 385, 386, 387, 504, 505, 506, 507, 628, 629, 630, 631, 748, 749, 750, 751, 872, 873, 874, 875, ): list([ 6, ]), tuple( 28, 29, 30, 31, 152, 153, 154, 155, 264, 265, 266, 267, 388, 389, 390, 391, 508, 509, 510, 511, 632, 633, 634, 635, 752, 753, 754, 755, 876, 877, 878, 879, ): list([ 7, ]), tuple( 32, 33, 34, 35, 156, 157, 158, 159, 268, 269, 270, 271, 392, 393, 394, 395, 512, 513, 514, 515, 636, 637, 638, 639, 756, 757, 758, 759, 880, 881, 882, 883, ): list([ 8, ]), tuple( 36, 37, 38, 39, 160, 161, 162, 163, 272, 273, 274, 275, 396, 397, 398, 399, 516, 517, 518, 519, 640, 641, 642, 643, 760, 761, 762, 763, 884, 885, 886, 887, ): list([ 9, ]), tuple( 40, 41, 42, 43, 164, 165, 166, 167, 276, 277, 278, 279, 400, 401, 402, 403, 520, 521, 522, 523, 644, 645, 646, 647, 764, 765, 766, 767, 888, 889, 890, 891, ): list([ 10, ]), tuple( 44, 45, 46, 47, 168, 169, 170, 171, 280, 281, 282, 283, 404, 405, 406, 407, 524, 525, 526, 527, 648, 649, 650, 651, 768, 769, 770, 771, 892, 893, 894, 895, ): list([ 11, ]), tuple( 48, 49, 50, 51, 172, 173, 174, 175, 284, 285, 286, 287, 408, 409, 410, 411, 528, 529, 530, 531, 652, 653, 654, 655, 772, 773, 774, 775, 896, 897, 898, 899, ): list([ 12, ]), tuple( 52, 53, 54, 55, 176, 177, 178, 179, 288, 289, 290, 291, 412, 413, 414, 415, 532, 533, 534, 535, 656, 657, 658, 659, 776, 777, 778, 779, ): list([ 13, ]), tuple( 56, 57, 58, 59, 180, 181, 182, 183, 292, 293, 294, 295, 416, 417, 418, 419, 536, 537, 538, 539, 660, 661, 662, 663, 780, 781, 782, 783, ): list([ 14, ]), tuple( 60, 61, 62, 63, 184, 185, 186, 187, 296, 297, 298, 299, 420, 421, 422, 423, 540, 541, 542, 543, 664, 665, 666, 667, 784, 785, 786, 787, ): list([ 15, ]), tuple( 64, 65, 66, 67, 188, 189, 190, 191, 300, 301, 302, 303, 424, 425, 426, 427, 544, 545, 546, 547, 668, 669, 670, 671, 788, 789, 790, 791, ): list([ 16, ]), tuple( 68, 69, 70, 71, 192, 193, 194, 195, 304, 305, 306, 307, 428, 429, 430, 431, 548, 549, 550, 551, 672, 673, 674, 675, 792, 793, 794, 795, ): list([ 17, ]), tuple( 72, 73, 74, 75, 196, 197, 198, 199, 308, 309, 310, 311, 432, 433, 434, 435, 552, 553, 554, 555, 676, 677, 678, 679, 796, 797, 798, 799, ): list([ 18, ]), tuple( 76, 77, 78, 79, 200, 201, 202, 203, 312, 313, 314, 315, 436, 437, 438, 439, 556, 557, 558, 559, 680, 681, 682, 683, 800, 801, 802, 803, ): list([ 19, ]), tuple( 80, 81, 82, 83, 204, 205, 206, 207, 316, 317, 318, 319, 440, 441, 442, 443, 560, 561, 562, 563, 684, 685, 686, 687, 804, 805, 806, 807, ): list([ 20, ]), tuple( 84, 85, 86, 87, 208, 209, 210, 211, 320, 321, 322, 323, 444, 445, 446, 447, 564, 565, 566, 567, 688, 689, 690, 691, 808, 809, 810, 811, ): list([ 21, ]), tuple( 88, 89, 90, 91, 212, 213, 214, 215, 324, 325, 326, 327, 448, 449, 450, 451, 568, 569, 570, 571, 692, 693, 694, 695, 812, 813, 814, 815, ): list([ 22, ]), tuple( 92, 93, 94, 95, 216, 217, 218, 219, 328, 329, 330, 331, 452, 453, 454, 455, 572, 573, 574, 575, 696, 697, 698, 699, 816, 817, 818, 819, ): list([ 23, ]), tuple( 96, 97, 98, 99, 220, 221, 222, 223, 332, 333, 334, 335, 456, 457, 458, 459, 576, 577, 578, 579, 700, 701, 702, 703, 820, 821, 822, 823, ): list([ 24, ]), tuple( 100, 101, 102, 103, 224, 225, 226, 227, 336, 337, 338, 339, 460, 461, 462, 463, 580, 581, 582, 583, 704, 705, 706, 707, 824, 825, 826, 827, ): list([ 25, ]), tuple( 104, 105, 106, 107, 228, 229, 230, 231, 340, 341, 342, 343, 464, 465, 466, 467, 584, 585, 586, 587, 708, 709, 710, 711, 828, 829, 830, 831, ): list([ 26, ]), tuple( 108, 109, 110, 111, 232, 233, 234, 235, 344, 345, 346, 347, 468, 469, 470, 471, 588, 589, 590, 591, 712, 713, 714, 715, 832, 833, 834, 835, ): list([ 27, ]), tuple( 112, 113, 114, 115, 348, 349, 350, 351, 472, 473, 474, 475, 592, 593, 594, 595, 716, 717, 718, 719, 836, 837, 838, 839, ): list([ 28, ]), tuple( 116, 117, 118, 119, 352, 353, 354, 355, 476, 477, 478, 479, 596, 597, 598, 599, 720, 721, 722, 723, 840, 841, 842, 843, ): list([ 29, ]), tuple( 120, 121, 122, 123, 356, 357, 358, 359, 600, 601, 602, 603, 844, 845, 846, 847, ): list([ 30, ]), }) # --- # name: test_snapshot_cohorts[ERA5MonthHourRechunked] dict({ tuple( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, ): list([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, ]), tuple( 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, ): list([ 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, ]), tuple( 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 122, 123, 124, 125, 126, 127, ): list([ 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, ]), tuple( 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, ): list([ 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, ]), tuple( 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 168, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, ): list([ 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, ]), tuple( 183, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, ): list([ 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, ]), tuple( 256, 257, 258, 61, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, ): list([ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, ]), tuple( 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 76, ): list([ 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, ]), tuple( 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, ): list([ 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, ]), tuple( 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, ): list([ 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, ]), tuple( 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 380, 381, 382, 383, ): list([ 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, ]), tuple( 512, 513, 514, 515, 516, 505, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 506, 507, 503, 122, 504, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 501, 502, 508, 509, 510, 511, ): list([ 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, ]), }) # --- # name: test_snapshot_cohorts[ERA5MonthHour] dict({ tuple( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, ): list([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, ]), tuple( 128, 129, 130, 131, 132, 133, 134, 135, 136, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 122, 123, 124, 125, 126, 127, ): list([ 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, ]), tuple( 256, 257, 258, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, ): list([ 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, ]), tuple( 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, ): list([ 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, ]), tuple( 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, ): list([ 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, ]), tuple( 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, ): list([ 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, ]), tuple( 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 381, 382, 383, ): list([ 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, ]), tuple( 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, ): list([ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, ]), tuple( 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, ): list([ 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, ]), tuple( 512, 513, 514, 515, 516, 517, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, ): list([ 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, ]), tuple( 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, ): list([ 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, ]), tuple( 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, ): list([ 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, ]), }) # --- # name: test_snapshot_cohorts[NWMMidwest] dict({ tuple( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 26, 27, 39, 52, 65, ): list([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 29, ]), tuple( 2, 3, 5, 6, 10, 12, 14, 15, 52, 26, 27, ): list([ 24, 28, ]), tuple( 2, 4, 6, 14, 15, 26, 27, ): list([ 30, ]), tuple( 3, 4, 5, 7, 39, 40, 8, 9, 14, 15, 16, 26, 27, 28, ): list([ 32, 33, 35, 36, 37, 38, 40, 46, 51, ]), tuple( 3, 4, 6, 15, 16, 17, 27, ): list([ 34, 41, ]), tuple( 3, 6, 7, 14, 15, 26, 27, ): list([ 31, ]), tuple( 4, 5, 6, 8, 40, 9, 15, 17, 18, 27, 28, ): list([ 49, 53, 56, 57, ]), tuple( 4, 10, 11, 15, 17, 52, 53, ): list([ 45, ]), tuple( 5, 6, 7, 10, 11, 12, 15, 16, 52, 53, 27, 28, ): list([ 43, 47, 50, ]), tuple( 6, 7, 40, 9, 41, 8, 16, 17, 19, 20, 21, 27, 28, ): list([ 44, 54, 55, 58, 60, 63, 66, 69, 72, 80, ]), tuple( 6, 10, 11, 19, 53, 28, ): list([ 62, ]), tuple( 6, 39, 40, 9, 15, 16, 27, ): list([ 39, ]), tuple( 7, 8, 40, 11, 15, 16, 17, 52, 53, ): list([ 48, ]), tuple( 7, 40, 9, 10, 8, 41, 11, 16, 17, 18, 53, 28, 29, ): list([ 59, 61, 64, 65, 67, ]), tuple( 8, 41, 10, 11, 17, 21, 53, 54, ): list([ 75, 79, ]), tuple( 9, 10, 41, 22, 23, 54, ): list([ 87, ]), tuple( 9, 41, 17, 18, 22, 30, ): list([ 84, ]), tuple( 10, 11, 54, 23, 24, ): list([ 93, ]), tuple( 10, 12, 18, 19, 20, 53, 54, 28, 29, ): list([ 76, 85, ]), tuple( 11, 19, 54, 24, 29, 30, ): list([ 99, ]), tuple( 16, 17, 18, 29, 30, 31, ): list([ 78, 92, ]), tuple( 24, 54, 31, ): list([ 111, ]), tuple( 25, 67, 54, ): list([ 120, ]), tuple( 32, 33, 34, 35, 44, 45, 55, 24, 25, 56, ): list([ 143, 145, 149, 154, 156, 160, 163, 164, 166, 169, 170, 175, 183, 190, ]), tuple( 32, 33, 34, 36, 37, 45, 46, 47, 57, ): list([ 165, 172, 173, 178, 179, 180, 185, ]), tuple( 32, 33, 34, 44, 23, 55, 24, 56, ): list([ 121, 140, 144, 151, 153, 155, ]), tuple( 32, 33, 41, 42, 23, 55, 54, 22, 24, 25, 31, ): list([ 101, 113, 116, 124, 125, 129, 130, 131, ]), tuple( 32, 33, 42, 19, 20, 22, 30, 31, ): list([ 110, 122, 159, ]), tuple( 32, 33, 45, ): list([ 150, 158, ]), tuple( 32, 33, 55, 24, 31, ): list([ 137, ]), tuple( 32, 41, 42, 43, 19, 20, 21, 22, 23, 54, 24, 29, 30, 31, ): list([ 77, 90, 94, 95, 98, 100, 104, 107, 108, 109, 112, 115, 117, ]), tuple( 32, 42, 43, 20, 21, 31, ): list([ 123, ]), tuple( 32, 43, 22, ): list([ 139, ]), tuple( 33, 34, 35, 42, 43, 44, 20, 21, 22, ): list([ 106, 119, 128, 132, 138, 141, 147, 152, ]), tuple( 33, 43, 44, 20, 30, 31, ): list([ 118, 126, 135, 142, ]), tuple( 34, 46, 47, ): list([ 187, 193, ]), tuple( 35, 36, 37, 38, 44, 45, 46, 57, 58, 59, ): list([ 168, 177, 186, 191, 195, 199, 203, 204, 213, ]), tuple( 35, 36, 43, 44, 22, 23, 56, 57, ): list([ 146, 157, 162, ]), tuple( 35, 36, 46, 48, 49, 59, ): list([ 200, 205, 212, 219, ]), tuple( 35, 37, 44, 24, 57, 56, ): list([ 171, 176, 192, 198, ]), tuple( 35, 38, 45, 56, 25, 57, ): list([ 181, ]), tuple( 36, 37, 23, 24, 57, 56, ): list([ 167, 182, 189, ]), tuple( 36, 37, 38, 46, 47, 48, 57, 58, ): list([ 188, 201, 208, 211, 217, 223, ]), tuple( 36, 37, 47, 58, 59, ): list([ 197, 206, 210, 216, 221, ]), tuple( 36, 37, 49, 50, 59, ): list([ 224, 230, 234, ]), tuple( 37, 49, 58, 59, 60, ): list([ 225, ]), tuple( 38, 71, 72, 58, 59, ): list([ 218, 226, 231, 237, ]), tuple( 38, 72, 73, 51, 59, 60, ): list([ 245, 252, 260, 267, ]), tuple( 39, 40, 9, 10, 52, 53, ): list([ 42, ]), tuple( 40, 9, 10, 41, 53, 54, ): list([ 74, ]), tuple( 40, 9, 41, 12, 18, 19, 53, 22, 28, 29, ): list([ 68, 82, ]), tuple( 40, 9, 41, 16, 17, 18, 29, ): list([ 71, ]), tuple( 41, 10, 18, 19, 21, 54, 23, 29, 30, ): list([ 73, 86, 89, 91, ]), tuple( 41, 11, 18, 20, 53, 54, 21, 24, 29, 30, ): list([ 81, 83, 96, ]), tuple( 41, 18, 22, 30, 31, ): list([ 97, 103, ]), tuple( 42, 43, 20, 21, 54, 24, ): list([ 114, ]), tuple( 42, 43, 23, 22, 55, 24, 56, ): list([ 127, 136, ]), tuple( 43, 23, 21, 55, 24, ): list([ 133, ]), tuple( 46, 47, 48, 49, 50, 59, 60, ): list([ 194, 202, 209, 214, 215, 220, 222, 227, 228, 229, 232, 233, 235, 236, 241, 243, ]), tuple( 48, 49, 50, 61, 62, 63, ): list([ 253, 254, 255, 258, 259, 261, 263, 264, 265, 266, 269, 270, 271, 274, 275, 276, 279, 280, 283, 284, 287, 290, ]), tuple( 48, 49, 61, ): list([ 242, 249, ]), tuple( 49, 50, 60, ): list([ 239, 246, 247, 250, 251, 257, ]), tuple( 49, 51, 59, 60, 61, ): list([ 248, 256, 262, ]), tuple( 50, 59, 60, ): list([ 238, 244, ]), tuple( 59, 51, 38, ): list([ 240, ]), tuple( 64, 74, 75, 51, 61, 62, ): list([ 268, 272, 273, 277, 278, 281, ]), tuple( 64, 75, 76, 62, 63, ): list([ 282, 285, 286, 288, 289, 291, 292, 293, 294, ]), tuple( 64, 77, ): list([ 295, 296, ]), tuple( 65, 66, 12, 52, 53, ): list([ 52, ]), tuple( 66, 67, 12, 53, 54, ): list([ 70, 88, ]), tuple( 67, 42, 12, 19, 21, 54, 25, 30, ): list([ 102, 105, ]), tuple( 68, 69, 55, 56, 25, ): list([ 134, 148, 161, ]), tuple( 69, 38, 70, 56, 25, 57, ): list([ 174, 184, 196, 207, ]), }) # --- # name: test_snapshot_cohorts[OISST] dict({ tuple( 0, 255, 1168, 401, 146, 657, 913, 1424, 1022, 767, 1278, 1314, 547, 36, 292, 803, 1059, 1315, 693, 182, 438, 949, 1205, 694, 839, 72, 328, 584, 1095, 1351, 73, 840, 1241, 474, 219, 730, 986, 1387, 620, 109, 365, 876, 1132, 621, 1388, 766, 511, ): list([ 242, 243, 244, ]), tuple( 0, 256, 255, 1424, 401, 146, 657, 767, 913, 1169, 402, 1023, 1278, 803, 36, 292, 548, 1059, 1315, 949, 182, 438, 694, 1205, 183, 950, 1095, 328, 73, 584, 840, 1351, 329, 1096, 730, 475, 219, 986, 1242, 876, 109, 365, 621, 1132, 1388, 877, 1022, 511, ): list([ 245, 246, 247, ]), tuple( 0, 256, 512, 1, 768, 913, 146, 402, 658, 1169, 1425, 147, 914, 1023, 1279, 548, 37, 293, 804, 1060, 1315, 694, 439, 183, 950, 1206, 840, 73, 329, 585, 1096, 1352, 74, 841, 986, 475, 219, 731, 1242, 220, 987, 1388, 621, 110, 366, 877, 1133, 767, ): list([ 251, 252, 253, ]), tuple( 0, 256, 512, 1424, 657, 146, 402, 767, 913, 1169, 658, 1425, 1023, 1059, 292, 37, 548, 804, 1315, 1279, 1205, 438, 183, 694, 950, 439, 1206, 1351, 584, 73, 329, 840, 1096, 585, 1352, 986, 475, 219, 731, 1242, 1132, 365, 110, 621, 877, 1388, 1278, 511, ): list([ 248, 249, 250, ]), tuple( 128, 383, 1296, 529, 18, 274, 785, 1041, 1297, 895, 1406, 675, 164, 420, 931, 1187, 1442, 1443, 676, 821, 54, 310, 566, 1077, 1333, 55, 822, 1223, 456, 201, 712, 968, 1369, 602, 347, 91, 858, 1114, 603, 1370, 748, 237, 493, 1004, 1260, 749, 1150, 639, ): list([ 62, 63, 64, ]), tuple( 128, 384, 383, 785, 18, 274, 530, 1041, 1297, 895, 1151, 931, 164, 420, 676, 1187, 1443, 165, 932, 1077, 310, 55, 566, 822, 1333, 311, 1078, 712, 201, 457, 968, 1224, 858, 347, 603, 91, 1114, 1370, 859, 1004, 237, 493, 749, 1260, 1005, 238, 1406, 639, ): list([ 65, 66, 67, ]), tuple( 128, 384, 640, 129, 896, 1297, 530, 19, 275, 786, 1042, 1151, 1443, 676, 165, 421, 932, 1188, 1407, 1444, 822, 567, 55, 311, 1078, 1334, 56, 823, 968, 201, 457, 713, 1224, 202, 969, 1370, 603, 348, 92, 859, 1115, 749, 238, 494, 1005, 1261, 750, 895, ): list([ 71, 72, 73, ]), tuple( 128, 384, 640, 1041, 274, 19, 530, 786, 1297, 895, 1151, 1187, 420, 165, 676, 932, 1443, 1407, 421, 1188, 1333, 566, 55, 311, 822, 1078, 567, 1334, 968, 201, 457, 713, 1224, 1114, 603, 347, 92, 859, 1370, 1260, 493, 238, 749, 1005, 494, 1261, 1406, 639, ): list([ 68, 69, 70, ]), tuple( 256, 1, 512, 768, 1024, 1169, 402, 147, 658, 914, 1425, 1170, 403, 1279, 548, 37, 293, 804, 1060, 1316, 549, 950, 439, 183, 695, 1206, 1096, 329, 74, 585, 841, 1352, 330, 1097, 1242, 475, 220, 731, 987, 476, 1243, 877, 110, 366, 622, 1133, 1389, 1023, ): list([ 254, 255, 256, ]), tuple( 382, 893, 527, 16, 272, 783, 1039, 1295, 528, 1149, 1404, 929, 162, 418, 674, 1185, 1441, 1075, 308, 53, 564, 820, 1331, 309, 1076, 1221, 454, 199, 710, 966, 455, 1222, 856, 345, 601, 89, 1112, 1368, 1002, 235, 491, 747, 1258, 1003, 381, 1148, 637, 126, ): list([ 44, 45, 46, ]), tuple( 382, 1295, 528, 17, 273, 784, 894, 1040, 1296, 529, 1150, 1405, 674, 163, 419, 930, 1186, 1442, 675, 1076, 309, 54, 565, 821, 1332, 1222, 455, 200, 711, 967, 456, 1223, 1368, 601, 346, 90, 857, 1113, 602, 1369, 1003, 236, 492, 748, 1259, 1149, 638, 127, ): list([ 53, 54, 55, ]), tuple( 384, 129, 640, 896, 1152, 385, 530, 19, 275, 786, 1042, 1298, 531, 932, 165, 421, 677, 1188, 1407, 1444, 1078, 567, 311, 56, 823, 1334, 312, 1079, 1224, 457, 202, 713, 969, 458, 1225, 859, 348, 92, 604, 1115, 1371, 1005, 238, 494, 750, 1261, 1006, 1151, ): list([ 74, 75, 76, ]), tuple( 509, 764, 1020, 1421, 654, 143, 399, 910, 1166, 655, 1422, 1276, 1056, 289, 34, 545, 801, 1312, 1202, 435, 180, 691, 947, 436, 1203, 1348, 581, 70, 326, 837, 1093, 582, 1349, 983, 472, 216, 728, 1239, 1129, 362, 107, 618, 874, 1385, 1275, 508, 253, ): list([ 218, 219, 220, ]), tuple( 511, 766, 912, 145, 401, 657, 1168, 1424, 1022, 1278, 1058, 291, 36, 547, 803, 1314, 292, 1059, 1204, 437, 182, 693, 949, 438, 1205, 839, 72, 328, 584, 1095, 1351, 985, 474, 218, 730, 1241, 1131, 364, 109, 620, 876, 1387, 365, 1132, 1277, 510, 255, ): list([ 239, 240, 241, ]), tuple( 512, 1, 257, 768, 1024, 1425, 658, 147, 403, 914, 1170, 659, 1426, 804, 37, 293, 549, 1060, 1316, 38, 805, 1206, 439, 184, 695, 951, 1352, 585, 74, 330, 841, 1097, 586, 1353, 731, 476, 220, 987, 1243, 732, 1133, 110, 366, 622, 878, 1389, 111, 1279, ): list([ 257, 258, 259, ]), tuple( 637, 892, 1038, 271, 16, 527, 783, 1148, 1294, 1404, 1184, 417, 162, 673, 929, 1440, 418, 1185, 1330, 563, 52, 308, 819, 1075, 564, 1331, 965, 198, 454, 710, 1221, 1111, 600, 344, 89, 856, 1367, 1257, 490, 235, 746, 1002, 491, 1258, 125, 1403, 636, 381, ): list([ 38, 39, 40, ]), tuple( 638, 893, 783, 16, 272, 528, 1039, 1295, 1149, 17, 784, 1405, 1185, 418, 163, 674, 930, 1441, 1331, 564, 53, 309, 820, 1076, 565, 1332, 710, 199, 455, 966, 1222, 711, 1112, 345, 601, 89, 857, 1368, 90, 1258, 491, 236, 747, 1003, 126, 1404, 637, 382, ): list([ 47, 48, 49, ]), tuple( 638, 1039, 272, 17, 528, 784, 1295, 1149, 273, 1040, 894, 1405, 1441, 674, 163, 419, 930, 1186, 820, 53, 309, 565, 1076, 1332, 966, 199, 455, 711, 1222, 200, 967, 1112, 601, 345, 90, 857, 1368, 346, 1113, 747, 236, 492, 1003, 1259, 126, 893, 382, 127, ): list([ 50, 51, 52, ]), tuple( 639, 1040, 273, 18, 529, 785, 1296, 274, 1041, 1150, 1186, 419, 164, 675, 931, 1406, 1442, 420, 1187, 821, 54, 310, 566, 1077, 1333, 967, 200, 456, 712, 1223, 1113, 602, 346, 91, 858, 1369, 347, 1114, 1259, 492, 237, 748, 1004, 493, 1260, 127, 894, 383, ): list([ 59, 60, 61, ]), tuple( 640, 129, 385, 896, 1152, 641, 1408, 786, 19, 275, 531, 1042, 1298, 20, 787, 1188, 421, 166, 677, 933, 1444, 1334, 567, 312, 56, 823, 1079, 568, 1335, 713, 202, 458, 969, 1225, 714, 1115, 348, 92, 604, 860, 1371, 93, 1261, 494, 239, 750, 1006, 1407, ): list([ 77, 78, 79, ]), tuple( 764, 1019, 1165, 398, 143, 654, 910, 1275, 1421, 1311, 544, 33, 289, 800, 1056, 1312, 690, 179, 435, 946, 1202, 691, 836, 69, 325, 581, 1092, 1348, 70, 837, 1238, 471, 216, 727, 983, 1384, 617, 106, 362, 873, 1129, 618, 1385, 252, 763, 508, ): list([ 212, 213, 214, ]), tuple( 764, 1020, 1421, 398, 143, 654, 910, 1166, 1275, 399, 800, 33, 289, 545, 1056, 1312, 946, 179, 435, 691, 1202, 180, 947, 1092, 325, 70, 581, 837, 1348, 326, 1093, 727, 472, 216, 983, 1239, 873, 106, 362, 618, 1129, 1385, 874, 252, 1019, 508, 253, ): list([ 215, 216, 217, ]), tuple( 765, 1020, 910, 143, 399, 655, 1166, 1422, 1276, 144, 911, 1312, 545, 34, 290, 801, 1057, 691, 180, 436, 947, 1203, 837, 70, 326, 582, 1093, 1349, 71, 838, 983, 472, 216, 728, 1239, 217, 984, 1385, 618, 107, 363, 874, 1130, 253, 764, 509, ): list([ 221, 222, 223, ]), tuple( 765, 1166, 399, 144, 655, 911, 1422, 1276, 1167, 400, 1021, 545, 34, 290, 801, 1057, 1313, 546, 947, 180, 436, 692, 1203, 1093, 326, 71, 582, 838, 1349, 327, 1094, 1239, 472, 217, 728, 984, 473, 1240, 874, 107, 363, 619, 1130, 1386, 253, 1020, 509, ): list([ 224, 225, 226, ]), tuple( 765, 1422, 655, 144, 400, 911, 1021, 1167, 656, 1423, 801, 34, 290, 546, 1057, 1313, 35, 802, 1203, 436, 181, 692, 948, 1349, 582, 71, 327, 838, 1094, 583, 1350, 728, 473, 217, 984, 1240, 729, 1130, 107, 363, 619, 875, 1386, 108, 1276, 509, 254, ): list([ 227, 228, 229, ]), tuple( 766, 1167, 400, 145, 656, 912, 1423, 1168, 1277, 1313, 546, 35, 291, 802, 1058, 1314, 547, 692, 181, 437, 948, 1204, 693, 1094, 327, 72, 583, 839, 1350, 1240, 473, 218, 729, 985, 474, 1241, 1386, 619, 108, 364, 875, 1131, 620, 1387, 254, 1021, 510, ): list([ 233, 234, 235, ]), tuple( 766, 1423, 656, 145, 401, 912, 1168, 1022, 802, 35, 291, 547, 1058, 1314, 36, 803, 948, 181, 437, 693, 1204, 182, 949, 1350, 583, 72, 328, 839, 1095, 729, 474, 218, 985, 1241, 730, 875, 108, 364, 620, 1131, 1387, 876, 109, 254, 1277, 510, 255, ): list([ 236, 237, 238, ]), tuple( 768, 1, 257, 513, 1024, 1280, 914, 147, 403, 659, 1170, 1426, 148, 915, 1060, 293, 38, 549, 805, 1316, 294, 1061, 695, 440, 184, 951, 1207, 841, 74, 330, 586, 1097, 1353, 987, 476, 220, 732, 1243, 221, 988, 1133, 366, 111, 622, 878, 1389, 367, 1134, ): list([ 260, 261, 262, ]), tuple( 769, 2, 258, 514, 1025, 1281, 3, 770, 1171, 404, 149, 660, 916, 1427, 1317, 550, 39, 295, 806, 1062, 1318, 696, 441, 185, 952, 1208, 697, 842, 75, 331, 587, 1098, 1354, 76, 843, 1244, 477, 222, 733, 989, 1390, 623, 112, 368, 879, 1135, 624, 1391, ): list([ 272, 273, 274, ]), tuple( 770, 3, 259, 515, 1026, 1282, 4, 771, 916, 149, 405, 661, 1172, 1428, 150, 917, 1318, 551, 40, 296, 807, 1063, 697, 442, 186, 953, 1209, 843, 76, 332, 588, 1099, 1355, 77, 844, 989, 478, 222, 734, 1245, 223, 990, 1391, 624, 113, 369, 880, 1136, ): list([ 281, 282, 283, ]), tuple( 771, 4, 260, 516, 1027, 1283, 917, 150, 406, 662, 1173, 1429, 151, 918, 1063, 296, 41, 552, 808, 1319, 297, 1064, 698, 443, 187, 954, 1210, 844, 77, 333, 589, 1100, 1356, 990, 479, 223, 735, 1246, 224, 991, 1136, 369, 114, 625, 881, 1392, 370, 1137, ): list([ 290, 291, 292, ]), tuple( 772, 5, 261, 517, 1028, 1284, 6, 773, 1174, 663, 407, 152, 919, 1430, 1320, 553, 42, 298, 809, 1065, 1321, 699, 444, 188, 955, 1211, 700, 845, 78, 334, 590, 1101, 1357, 79, 846, 1247, 480, 225, 736, 992, 1393, 626, 115, 371, 882, 1138, 627, 1394, ): list([ 302, 303, 304, ]), tuple( 773, 6, 262, 518, 1029, 1285, 7, 774, 919, 408, 664, 152, 1175, 1431, 153, 920, 1321, 554, 43, 299, 810, 1066, 700, 189, 445, 956, 1212, 846, 79, 335, 591, 1102, 1358, 80, 847, 992, 225, 481, 737, 1248, 226, 993, 1394, 627, 116, 372, 883, 1139, ): list([ 311, 312, 313, ]), tuple( 774, 7, 263, 519, 1030, 1286, 920, 409, 665, 153, 1176, 1432, 154, 921, 1066, 299, 44, 555, 811, 1322, 300, 1067, 701, 446, 190, 957, 1213, 847, 80, 336, 592, 1103, 1359, 993, 226, 482, 738, 1249, 227, 994, 1139, 372, 117, 628, 884, 1395, 373, 1140, ): list([ 320, 321, 322, ]), tuple( 775, 8, 264, 520, 1031, 1287, 9, 776, 1177, 666, 410, 155, 922, 1433, 1323, 556, 45, 301, 812, 1068, 1324, 702, 447, 191, 958, 1214, 703, 848, 81, 337, 593, 1104, 1360, 82, 849, 1250, 483, 228, 739, 995, 1396, 629, 118, 374, 885, 1141, 630, 1397, ): list([ 332, 333, 334, ]), tuple( 776, 9, 265, 521, 1032, 1288, 10, 777, 922, 411, 155, 667, 1178, 1434, 156, 923, 1324, 557, 46, 302, 813, 1069, 703, 192, 448, 959, 1215, 849, 82, 338, 594, 1105, 1361, 83, 850, 375, 995, 228, 484, 740, 1251, 229, 996, 1397, 630, 119, 886, 1142, ): list([ 341, 342, 343, ]), tuple( 777, 10, 266, 522, 1033, 1289, 923, 668, 412, 156, 1179, 1435, 157, 924, 1069, 302, 47, 558, 814, 1325, 303, 1070, 704, 193, 449, 960, 1216, 850, 83, 339, 595, 1106, 1362, 1143, 375, 996, 229, 485, 741, 1252, 376, 230, 997, 887, 1142, 631, 120, 1398, ): list([ 350, 351, 352, ]), tuple( 784, 17, 273, 529, 894, 1040, 1296, 18, 785, 1150, 930, 163, 419, 675, 1186, 1442, 164, 931, 1332, 565, 54, 310, 821, 1077, 711, 200, 456, 967, 1223, 712, 857, 346, 602, 90, 1113, 1369, 858, 91, 1259, 236, 492, 748, 1004, 237, 127, 1405, 638, 383, ): list([ 56, 57, 58, ]), tuple( 891, 1146, 1036, 269, 14, 525, 781, 1292, 1402, 270, 1037, 1438, 671, 160, 416, 927, 1183, 817, 50, 306, 562, 1073, 1329, 963, 196, 452, 708, 1219, 197, 964, 1109, 342, 87, 598, 854, 1365, 343, 1110, 744, 233, 489, 123, 1000, 1256, 379, 890, 635, 124, ): list([ 20, 21, 22, ]), tuple( 891, 1147, 781, 14, 270, 526, 1037, 1293, 15, 782, 927, 160, 416, 672, 1183, 1439, 161, 928, 1329, 562, 51, 307, 818, 1074, 708, 197, 453, 964, 1220, 709, 854, 599, 87, 343, 1110, 1366, 855, 88, 1256, 233, 489, 745, 1001, 234, 124, 1402, 635, 380, ): list([ 26, 27, 28, ]), tuple( 891, 1147, 1292, 525, 14, 270, 781, 1037, 1402, 1293, 526, 671, 160, 416, 927, 1183, 1439, 672, 1073, 306, 51, 562, 818, 1329, 1219, 452, 197, 708, 964, 453, 1220, 1365, 598, 87, 343, 854, 1110, 599, 1366, 1000, 233, 489, 745, 1256, 379, 1146, 635, 124, ): list([ 23, 24, 25, ]), tuple( 892, 782, 15, 271, 527, 1038, 1148, 1294, 928, 161, 417, 673, 1184, 1440, 162, 929, 1074, 307, 52, 563, 819, 1330, 308, 1075, 709, 198, 454, 965, 1221, 855, 344, 600, 88, 1111, 1367, 856, 1001, 234, 490, 746, 1257, 1002, 235, 381, 380, 1403, 636, 125, ): list([ 35, 36, 37, ]), tuple( 892, 1293, 526, 15, 271, 782, 1038, 1403, 1294, 1439, 672, 161, 417, 928, 1184, 1440, 673, 818, 51, 307, 563, 1074, 1330, 52, 819, 1220, 453, 198, 709, 965, 1366, 599, 344, 88, 855, 1111, 600, 1367, 745, 234, 490, 1001, 1257, 746, 380, 1147, 636, 125, ): list([ 32, 33, 34, ]), tuple( 893, 1294, 527, 16, 272, 783, 1039, 1148, 1404, 1440, 673, 162, 418, 929, 1185, 1441, 819, 52, 308, 564, 1075, 1331, 53, 820, 965, 198, 454, 710, 1221, 199, 966, 1367, 600, 345, 89, 856, 1112, 746, 235, 491, 1002, 1258, 747, 381, 125, 892, 637, 126, ): list([ 41, 42, 43, ]), tuple( 896, 129, 385, 641, 1152, 1408, 130, 897, 1042, 275, 20, 531, 787, 1298, 276, 1043, 1444, 677, 166, 422, 933, 1189, 823, 312, 568, 56, 1079, 1335, 969, 202, 458, 714, 1225, 203, 970, 1115, 348, 93, 604, 860, 1371, 349, 1116, 750, 239, 495, 1006, 1262, ): list([ 80, 81, 82, ]), tuple( 897, 130, 386, 642, 1153, 1409, 1043, 276, 21, 532, 788, 1299, 277, 1044, 1189, 422, 167, 678, 934, 1445, 423, 1190, 824, 313, 569, 57, 1080, 1336, 970, 203, 459, 715, 1226, 1116, 605, 349, 94, 861, 1372, 350, 1117, 1262, 495, 240, 751, 1007, 496, 1263, ): list([ 89, 90, 91, ]), tuple( 898, 131, 387, 643, 1154, 1410, 132, 899, 1300, 533, 22, 278, 789, 1045, 1446, 679, 168, 424, 935, 1191, 1447, 825, 314, 570, 58, 1081, 1337, 59, 826, 971, 204, 460, 716, 1227, 205, 972, 1373, 606, 351, 95, 862, 1118, 752, 241, 497, 1008, 1264, 753, ): list([ 101, 102, 103, ]), tuple( 899, 132, 388, 644, 1155, 1411, 133, 900, 1045, 278, 23, 534, 790, 1301, 279, 1046, 1447, 680, 169, 425, 936, 1192, 826, 315, 571, 59, 1082, 1338, 972, 205, 461, 717, 1228, 206, 973, 1118, 607, 96, 351, 863, 1374, 352, 1119, 753, 242, 498, 1009, 1265, ): list([ 110, 111, 112, ]), tuple( 900, 133, 389, 645, 1156, 1412, 1046, 535, 279, 24, 791, 1302, 280, 1047, 1192, 425, 170, 681, 937, 1448, 426, 1193, 827, 316, 60, 572, 1083, 1339, 973, 206, 462, 718, 1229, 1119, 352, 97, 608, 864, 1375, 353, 1120, 1265, 498, 243, 754, 1010, 499, 1266, ): list([ 119, 120, 121, ]), tuple( 901, 134, 390, 646, 1157, 1413, 135, 902, 1303, 536, 281, 25, 792, 1048, 1449, 682, 171, 427, 938, 1194, 1450, 828, 573, 317, 61, 1084, 1340, 62, 829, 974, 207, 463, 719, 1230, 208, 975, 1376, 609, 98, 354, 865, 1121, 755, 244, 500, 1011, 1267, 756, ): list([ 131, 132, 133, ]), tuple( 902, 135, 391, 647, 1158, 1414, 136, 903, 1048, 537, 281, 26, 793, 1304, 282, 1049, 1450, 683, 172, 428, 939, 1195, 829, 318, 574, 62, 1085, 1341, 975, 208, 464, 720, 1231, 209, 976, 1121, 354, 99, 610, 866, 1377, 355, 1122, 756, 245, 501, 1012, 1268, ): list([ 140, 141, 142, ]), tuple( 903, 136, 392, 648, 1159, 1415, 1049, 538, 282, 27, 794, 1305, 283, 1050, 1195, 428, 173, 684, 940, 1451, 429, 1196, 830, 319, 575, 63, 1086, 1342, 976, 209, 465, 721, 1232, 1122, 355, 100, 611, 867, 1378, 356, 1123, 1268, 501, 246, 757, 1013, 502, 1269, ): list([ 149, 150, 151, ]), tuple( 904, 137, 393, 649, 1160, 1416, 138, 905, 1306, 539, 284, 28, 795, 1051, 1452, 685, 174, 430, 941, 1197, 1453, 831, 64, 320, 576, 1087, 1343, 65, 832, 977, 210, 466, 722, 1233, 211, 978, 247, 1379, 612, 101, 357, 868, 1124, 759, 758, 503, 1014, 1270, ): list([ 161, 162, 163, ]), tuple( 905, 138, 394, 650, 1161, 1417, 139, 906, 1051, 540, 284, 29, 796, 1307, 285, 1052, 686, 175, 431, 942, 1198, 832, 65, 321, 577, 1088, 1344, 978, 211, 467, 723, 1234, 212, 979, 248, 1124, 357, 102, 613, 869, 1380, 358, 1125, 1015, 759, 504, 1271, ): list([ 170, 171, 172, ]), tuple( 911, 144, 400, 656, 1021, 1167, 1423, 145, 912, 1277, 1057, 290, 35, 546, 802, 1313, 291, 1058, 692, 181, 437, 948, 1204, 838, 71, 327, 583, 1094, 1350, 984, 473, 217, 729, 1240, 218, 985, 1130, 363, 108, 619, 875, 1386, 364, 1131, 254, 765, 510, ): list([ 230, 231, 232, ]), tuple( 1018, 1273, 1163, 396, 141, 652, 908, 1419, 1164, 397, 542, 287, 31, 798, 1054, 1310, 543, 944, 177, 433, 689, 1200, 1090, 323, 68, 579, 835, 1346, 324, 1091, 1236, 469, 214, 725, 981, 470, 1237, 762, 871, 104, 360, 616, 250, 1127, 1383, 1017, 506, ): list([ 194, 195, 196, ]), tuple( 1018, 1274, 908, 141, 397, 653, 1164, 1420, 142, 909, 1054, 543, 32, 287, 799, 1310, 288, 1055, 689, 178, 434, 945, 1201, 835, 68, 324, 580, 1091, 1347, 981, 214, 470, 726, 1237, 215, 982, 1127, 360, 105, 616, 872, 1383, 361, 1128, 251, 762, 507, ): list([ 200, 201, 202, ]), tuple( 1018, 1419, 652, 141, 397, 908, 1164, 653, 1420, 798, 287, 543, 31, 1054, 1310, 32, 799, 1200, 433, 178, 689, 945, 1346, 579, 68, 324, 835, 1091, 580, 1347, 725, 214, 470, 981, 1237, 726, 762, 1127, 104, 360, 616, 872, 1383, 105, 1273, 506, 251, ): list([ 197, 198, 199, ]), tuple( 1019, 909, 142, 398, 654, 1165, 1421, 1275, 1055, 288, 33, 544, 800, 1311, 289, 1056, 1201, 434, 179, 690, 946, 435, 1202, 836, 69, 325, 581, 1092, 1348, 982, 471, 215, 727, 1238, 1128, 361, 106, 617, 873, 1384, 508, 362, 1129, 1274, 507, 252, 763, ): list([ 209, 210, 211, ]), tuple( 1019, 1420, 653, 142, 398, 909, 1165, 799, 32, 288, 544, 1055, 1311, 33, 800, 945, 178, 434, 690, 1201, 179, 946, 1347, 580, 69, 325, 836, 1092, 726, 471, 215, 982, 1238, 727, 872, 105, 361, 617, 1128, 1384, 873, 251, 106, 1274, 507, 252, 763, ): list([ 206, 207, 208, ]), tuple( 1024, 257, 2, 513, 769, 1280, 1170, 403, 148, 659, 915, 1426, 1171, 1316, 549, 38, 294, 805, 1061, 1317, 550, 695, 440, 184, 951, 1207, 696, 1097, 330, 75, 586, 842, 1353, 1243, 476, 221, 732, 988, 477, 1244, 1389, 622, 111, 367, 878, 1134, 623, 1390, ): list([ 263, 264, 265, ]), tuple( 1025, 258, 3, 514, 770, 1281, 1026, 259, 1427, 404, 149, 660, 916, 1172, 405, 806, 39, 295, 551, 1062, 1318, 952, 441, 185, 697, 1208, 186, 953, 1098, 331, 76, 587, 843, 1354, 332, 1099, 733, 478, 222, 989, 1245, 879, 112, 368, 624, 1135, 1391, 880, ): list([ 275, 276, 277, ]), tuple( 1026, 259, 4, 515, 771, 1282, 1027, 1172, 405, 150, 661, 917, 1428, 1173, 406, 551, 40, 296, 807, 1063, 1319, 552, 953, 442, 186, 698, 1209, 1099, 332, 77, 588, 844, 1355, 333, 1100, 1245, 478, 223, 734, 990, 479, 1246, 880, 113, 369, 625, 1136, 1392, ): list([ 284, 285, 286, ]), tuple( 1027, 260, 5, 516, 772, 1283, 1173, 406, 151, 662, 918, 1429, 1174, 1319, 552, 41, 297, 808, 1064, 1320, 553, 698, 443, 187, 954, 1210, 699, 1100, 333, 78, 589, 845, 1356, 1246, 479, 224, 735, 991, 480, 1247, 1392, 625, 114, 370, 881, 1137, 626, 1393, ): list([ 293, 294, 295, ]), tuple( 1028, 261, 6, 517, 773, 1284, 1029, 262, 1430, 663, 407, 152, 919, 1175, 408, 809, 42, 298, 554, 1065, 1321, 955, 444, 188, 700, 1211, 189, 956, 1101, 334, 79, 590, 846, 1357, 335, 1102, 736, 225, 481, 992, 1248, 882, 115, 371, 627, 1138, 1394, 883, ): list([ 305, 306, 307, ]), tuple( 1029, 262, 7, 518, 774, 1285, 1030, 1175, 664, 408, 153, 920, 1431, 1176, 409, 554, 43, 299, 810, 1066, 1322, 555, 956, 189, 445, 701, 1212, 1102, 335, 80, 591, 847, 1358, 336, 1103, 1248, 481, 226, 737, 993, 482, 1249, 883, 116, 372, 628, 1139, 1395, ): list([ 314, 315, 316, ]), tuple( 1030, 263, 8, 519, 775, 1286, 1176, 665, 409, 154, 921, 1432, 1177, 1322, 555, 44, 300, 811, 1067, 1323, 556, 701, 446, 190, 957, 1213, 702, 1103, 336, 81, 592, 848, 1359, 1249, 482, 227, 738, 994, 483, 1250, 1395, 628, 117, 373, 884, 1140, 629, 1396, ): list([ 323, 324, 325, ]), tuple( 1031, 264, 9, 520, 776, 1287, 1032, 265, 1433, 666, 410, 155, 922, 1178, 411, 812, 45, 301, 557, 1068, 1324, 958, 447, 191, 703, 1214, 192, 959, 1104, 337, 82, 593, 849, 1360, 338, 1105, 739, 228, 484, 995, 1251, 885, 118, 374, 630, 1141, 1397, 886, ): list([ 335, 336, 337, ]), tuple( 1032, 265, 10, 521, 777, 1288, 1033, 1178, 667, 411, 156, 923, 1434, 1179, 412, 557, 46, 302, 813, 1069, 1325, 558, 959, 192, 448, 704, 1215, 1105, 338, 83, 594, 850, 1361, 119, 339, 1106, 375, 1251, 484, 229, 740, 996, 485, 1252, 886, 631, 1142, 1398, ): list([ 344, 345, 346, ]), tuple( 1033, 266, 11, 522, 778, 1289, 1179, 668, 412, 157, 924, 1435, 1180, 1325, 558, 47, 303, 814, 1070, 1326, 559, 704, 193, 449, 960, 1216, 705, 1106, 339, 84, 595, 851, 1143, 1362, 120, 1252, 485, 230, 741, 997, 486, 1253, 632, 887, 1398, 631, 376, 1399, ): list([ 353, 354, 355, ]), tuple( 1145, 779, 12, 268, 524, 1035, 1291, 925, 414, 670, 158, 1181, 1437, 159, 926, 1071, 304, 49, 560, 816, 1327, 305, 1072, 706, 195, 451, 962, 1218, 852, 85, 341, 597, 1108, 1364, 853, 889, 998, 231, 487, 377, 743, 1254, 999, 232, 378, 1400, 633, 122, ): list([ 5, 6, 7, ]), tuple( 1145, 1401, 1035, 268, 13, 524, 780, 1291, 1181, 670, 414, 159, 926, 1437, 415, 1182, 1327, 560, 49, 305, 816, 1072, 561, 1328, 962, 195, 451, 707, 1218, 1108, 341, 86, 597, 853, 1364, 889, 122, 487, 232, 743, 999, 1254, 488, 1255, 634, 1400, 633, 378, ): list([ 8, 9, 10, ]), tuple( 1145, 1401, 1291, 524, 13, 269, 780, 1036, 1437, 670, 415, 159, 926, 1182, 1438, 816, 49, 305, 561, 1072, 1328, 50, 817, 962, 195, 451, 707, 1218, 196, 963, 1364, 597, 86, 342, 853, 1109, 122, 743, 232, 488, 999, 1255, 744, 634, 889, 378, 123, 890, ): list([ 11, 12, 13, ]), tuple( 1146, 780, 13, 269, 525, 1036, 1292, 14, 781, 1402, 1182, 671, 160, 415, 927, 1438, 1328, 561, 50, 306, 817, 1073, 562, 1329, 707, 196, 452, 963, 1219, 708, 1109, 86, 342, 598, 854, 1365, 87, 1255, 488, 233, 744, 123, 1000, 635, 1401, 634, 379, 890, ): list([ 17, 18, 19, ]), tuple( 1146, 1401, 524, 13, 269, 780, 1036, 1292, 525, 926, 415, 671, 159, 1182, 1438, 1072, 305, 50, 561, 817, 1328, 306, 1073, 1218, 451, 196, 707, 963, 452, 1219, 853, 86, 342, 598, 1109, 1365, 999, 232, 488, 744, 1255, 1000, 378, 379, 1145, 634, 123, 890, ): list([ 14, 15, 16, ]), tuple( 1147, 1037, 270, 15, 526, 782, 1293, 1403, 271, 1038, 1183, 416, 161, 672, 928, 1439, 417, 1184, 818, 51, 307, 563, 1074, 1330, 964, 197, 453, 709, 1220, 1110, 599, 343, 88, 855, 1366, 344, 1111, 1256, 489, 234, 745, 1001, 490, 1257, 124, 380, 891, 636, ): list([ 29, 30, 31, ]), tuple( 1152, 385, 130, 641, 897, 1408, 1153, 1298, 531, 20, 276, 787, 1043, 1299, 532, 677, 166, 422, 933, 1189, 1445, 678, 1079, 568, 312, 57, 824, 1335, 1225, 458, 203, 714, 970, 459, 1226, 1371, 604, 349, 93, 860, 1116, 605, 1372, 1006, 239, 495, 751, 1262, ): list([ 83, 84, 85, ]), tuple( 1153, 386, 131, 642, 898, 1409, 1299, 532, 21, 277, 788, 1044, 1300, 1445, 678, 167, 423, 934, 1190, 1446, 679, 824, 313, 569, 57, 1080, 1336, 58, 825, 1226, 459, 204, 715, 971, 1372, 605, 350, 94, 861, 1117, 606, 1373, 751, 240, 496, 1007, 1263, 752, ): list([ 92, 93, 94, ]), tuple( 1154, 387, 132, 643, 899, 1410, 1155, 388, 533, 22, 278, 789, 1045, 1301, 534, 935, 168, 424, 680, 1191, 1447, 1081, 570, 314, 59, 826, 1337, 315, 1082, 1227, 460, 205, 716, 972, 461, 1228, 862, 351, 607, 95, 1118, 1374, 1008, 241, 497, 753, 1264, 1009, ): list([ 104, 105, 106, ]), tuple( 1155, 388, 133, 644, 900, 1411, 1156, 1301, 534, 279, 23, 790, 1046, 1302, 535, 680, 169, 425, 936, 1192, 1448, 681, 1082, 571, 315, 60, 827, 1338, 1228, 461, 206, 717, 973, 462, 1229, 1374, 607, 96, 352, 863, 1119, 608, 1375, 1009, 242, 498, 754, 1265, ): list([ 113, 114, 115, ]), tuple( 1156, 389, 134, 645, 901, 1412, 1302, 535, 280, 24, 791, 1047, 1303, 1448, 681, 170, 426, 937, 1193, 1449, 682, 827, 316, 60, 572, 1083, 1339, 61, 828, 1229, 462, 207, 718, 974, 1375, 608, 97, 353, 864, 1120, 609, 1376, 754, 243, 499, 1010, 1266, 755, ): list([ 122, 123, 124, ]), tuple( 1157, 390, 135, 646, 902, 1413, 1158, 391, 536, 281, 25, 792, 1048, 1304, 537, 938, 171, 427, 683, 1194, 1450, 1084, 573, 317, 62, 829, 1340, 318, 1085, 1230, 463, 208, 719, 975, 464, 1231, 865, 98, 354, 610, 1121, 1377, 1011, 244, 500, 756, 1267, 1012, ): list([ 134, 135, 136, ]), tuple( 1158, 391, 136, 647, 903, 1414, 1159, 1304, 537, 282, 26, 793, 1049, 1305, 538, 683, 172, 428, 939, 1195, 1451, 684, 1085, 574, 318, 63, 830, 1341, 1231, 464, 209, 720, 976, 465, 1232, 1377, 610, 99, 355, 866, 1122, 611, 1378, 1012, 245, 501, 757, 1268, ): list([ 143, 144, 145, ]), tuple( 1159, 392, 137, 648, 904, 1415, 1305, 538, 283, 27, 794, 1050, 1306, 1451, 684, 173, 429, 940, 1196, 1452, 685, 830, 319, 575, 63, 1086, 1342, 64, 831, 1232, 465, 210, 721, 977, 1378, 611, 100, 356, 867, 1123, 612, 1379, 757, 246, 502, 1013, 1269, 758, ): list([ 152, 153, 154, ]), tuple( 1160, 393, 138, 649, 905, 1416, 1161, 394, 539, 284, 28, 795, 1051, 1307, 540, 941, 174, 430, 686, 1197, 1453, 1087, 320, 65, 576, 832, 1343, 321, 1088, 1233, 466, 211, 722, 978, 467, 1234, 247, 868, 101, 357, 613, 1124, 1380, 759, 1015, 1014, 503, 1270, ): list([ 164, 165, 166, ]), tuple( 1161, 394, 139, 650, 906, 1417, 1162, 1307, 540, 285, 29, 796, 1052, 1308, 541, 686, 175, 431, 942, 1198, 687, 1088, 321, 66, 577, 833, 1344, 1234, 467, 212, 723, 979, 468, 1235, 248, 1380, 613, 102, 358, 869, 1125, 614, 1381, 760, 1015, 504, 1271, ): list([ 173, 174, 175, ]), tuple( 1272, 906, 139, 395, 651, 1162, 1418, 1052, 541, 285, 30, 797, 1308, 286, 1053, 1198, 431, 176, 687, 943, 432, 1199, 833, 66, 322, 578, 1089, 1345, 979, 212, 468, 724, 1235, 1016, 1125, 358, 103, 614, 870, 1381, 359, 1126, 505, 760, 1271, 504, 249, ): list([ 179, 180, 181, ]), tuple( 1272, 1162, 395, 140, 651, 907, 1418, 1308, 541, 30, 286, 797, 1053, 1309, 687, 176, 432, 943, 1199, 688, 833, 66, 322, 578, 1089, 1345, 67, 834, 1235, 468, 213, 724, 980, 1016, 249, 614, 103, 359, 870, 1126, 1381, 615, 1382, 761, 760, 505, ): list([ 182, 183, 184, ]), tuple( 1272, 1418, 395, 140, 651, 907, 1163, 396, 797, 542, 30, 286, 1053, 1309, 943, 176, 432, 688, 1199, 177, 944, 1089, 322, 67, 578, 834, 1345, 323, 1090, 724, 213, 469, 980, 1236, 249, 870, 103, 359, 615, 1126, 1382, 871, 761, 1016, 505, 250, 1017, ): list([ 185, 186, 187, ]), tuple( 1273, 907, 140, 396, 652, 1163, 1419, 141, 908, 1309, 542, 287, 31, 798, 1054, 688, 177, 433, 944, 1200, 834, 67, 323, 579, 1090, 1346, 68, 835, 980, 213, 469, 725, 1236, 214, 981, 762, 1382, 615, 104, 360, 250, 871, 1127, 761, 506, 1017, ): list([ 191, 192, 193, ]), tuple( 1273, 1418, 651, 140, 396, 907, 1163, 652, 1419, 1053, 542, 286, 31, 798, 1309, 1199, 432, 177, 688, 944, 433, 1200, 1345, 578, 67, 323, 834, 1090, 579, 1346, 980, 213, 469, 725, 1236, 1126, 359, 104, 615, 871, 1382, 506, 761, 1272, 505, 250, 1017, ): list([ 188, 189, 190, ]), tuple( 1274, 1164, 397, 142, 653, 909, 1420, 1165, 1310, 543, 32, 288, 799, 1055, 1311, 544, 689, 178, 434, 945, 1201, 690, 1091, 324, 69, 580, 836, 1347, 1237, 470, 215, 726, 982, 471, 1238, 1383, 616, 105, 361, 872, 1128, 617, 1384, 251, 1018, 507, 763, ): list([ 203, 204, 205, ]), tuple( 1280, 257, 2, 513, 769, 1025, 258, 1426, 659, 148, 404, 915, 1171, 805, 38, 294, 550, 1061, 1317, 39, 806, 951, 440, 184, 696, 1207, 185, 952, 1353, 586, 75, 331, 842, 1098, 732, 477, 221, 988, 1244, 733, 878, 111, 367, 623, 1134, 1390, 879, 112, ): list([ 266, 267, 268, ]), tuple( 1280, 513, 2, 258, 769, 1025, 514, 1281, 915, 148, 404, 660, 1171, 1427, 1061, 294, 39, 550, 806, 1317, 295, 1062, 1207, 440, 185, 696, 952, 441, 1208, 842, 75, 331, 587, 1098, 1354, 988, 477, 221, 733, 1244, 1134, 367, 112, 623, 879, 1390, 368, 1135, ): list([ 269, 270, 271, ]), tuple( 1281, 514, 3, 259, 770, 1026, 515, 1282, 1427, 660, 149, 405, 916, 1172, 661, 1428, 1062, 295, 40, 551, 807, 1318, 1208, 441, 186, 697, 953, 442, 1209, 1354, 587, 76, 332, 843, 1099, 588, 1355, 989, 478, 222, 734, 1245, 1135, 368, 113, 624, 880, 1391, ): list([ 278, 279, 280, ]), tuple( 1282, 515, 4, 260, 771, 1027, 1428, 661, 150, 406, 917, 1173, 662, 1429, 807, 40, 296, 552, 1063, 1319, 41, 808, 1209, 442, 187, 698, 954, 1355, 588, 77, 333, 844, 1100, 589, 1356, 734, 479, 223, 990, 1246, 735, 1136, 113, 369, 625, 881, 1392, 114, ): list([ 287, 288, 289, ]), tuple( 1283, 260, 5, 516, 772, 1028, 261, 1429, 662, 407, 151, 918, 1174, 808, 41, 297, 553, 1064, 1320, 42, 809, 954, 443, 187, 699, 1210, 188, 955, 1356, 589, 78, 334, 845, 1101, 735, 224, 480, 991, 1247, 736, 881, 114, 370, 626, 1137, 1393, 882, 115, ): list([ 296, 297, 298, ]), tuple( 1283, 516, 5, 261, 772, 1028, 517, 1284, 918, 407, 663, 151, 1174, 1430, 1064, 297, 42, 553, 809, 1320, 298, 1065, 1210, 443, 188, 699, 955, 444, 1211, 845, 78, 334, 590, 1101, 1357, 991, 224, 480, 736, 1247, 1137, 370, 115, 626, 882, 1393, 371, 1138, ): list([ 299, 300, 301, ]), tuple( 1284, 517, 6, 262, 773, 1029, 518, 1285, 1430, 663, 408, 152, 919, 1175, 664, 1431, 1065, 298, 43, 554, 810, 1321, 1211, 444, 189, 700, 956, 445, 1212, 1357, 590, 79, 335, 846, 1102, 591, 1358, 992, 225, 481, 737, 1248, 1138, 371, 116, 627, 883, 1394, ): list([ 308, 309, 310, ]), tuple( 1285, 518, 7, 263, 774, 1030, 1431, 664, 409, 153, 920, 1176, 665, 1432, 810, 43, 299, 555, 1066, 1322, 44, 811, 1212, 445, 190, 701, 957, 1358, 591, 80, 336, 847, 1103, 592, 1359, 737, 226, 482, 993, 1249, 738, 1139, 116, 372, 628, 884, 1395, 117, ): list([ 317, 318, 319, ]), tuple( 1286, 263, 8, 519, 775, 1031, 264, 1432, 665, 410, 154, 921, 1177, 811, 44, 300, 556, 1067, 1323, 45, 812, 957, 446, 190, 702, 1213, 191, 958, 1359, 592, 81, 337, 848, 1104, 738, 227, 483, 994, 1250, 739, 884, 117, 373, 629, 1140, 1396, 885, 118, ): list([ 326, 327, 328, ]), tuple( 1286, 519, 8, 264, 775, 1031, 520, 1287, 921, 410, 666, 154, 1177, 1433, 1067, 300, 45, 556, 812, 1323, 301, 1068, 1213, 446, 191, 702, 958, 447, 1214, 848, 81, 337, 593, 1104, 1360, 994, 227, 483, 739, 1250, 1140, 373, 118, 629, 885, 1396, 374, 1141, ): list([ 329, 330, 331, ]), tuple( 1287, 520, 9, 265, 776, 1032, 521, 1288, 1433, 666, 411, 155, 922, 1178, 667, 1434, 1068, 301, 46, 557, 813, 1324, 1214, 447, 192, 703, 959, 448, 1215, 1360, 593, 82, 338, 849, 1105, 594, 1361, 995, 228, 484, 740, 1251, 1141, 374, 119, 630, 886, 1397, ): list([ 338, 339, 340, ]), tuple( 1288, 521, 10, 266, 777, 1033, 1434, 667, 412, 156, 923, 1179, 668, 1435, 813, 46, 302, 558, 1069, 1325, 47, 814, 1215, 448, 193, 704, 960, 1361, 594, 83, 339, 850, 1106, 119, 595, 1362, 120, 375, 740, 229, 485, 996, 1252, 741, 887, 1142, 631, 1398, ): list([ 347, 348, 349, ]), tuple( 1289, 266, 11, 522, 778, 1034, 267, 1435, 668, 157, 413, 924, 1180, 814, 47, 303, 559, 1070, 1326, 48, 815, 960, 193, 449, 705, 1216, 194, 961, 1362, 595, 84, 340, 851, 1107, 1143, 120, 741, 230, 486, 997, 1253, 742, 632, 888, 887, 376, 121, 1399, ): list([ 356, 357, 358, ]), tuple( 1289, 522, 11, 267, 778, 1034, 523, 1290, 924, 669, 157, 413, 1180, 1436, 1070, 303, 48, 559, 815, 1326, 304, 1071, 1216, 449, 194, 705, 961, 450, 1217, 851, 84, 340, 596, 1107, 1363, 376, 997, 230, 486, 742, 1253, 377, 888, 1143, 632, 121, 1144, 1399, ): list([ 359, 360, 361, ]), tuple( 1400, 1034, 267, 12, 523, 779, 1290, 268, 1035, 11, 778, 1436, 669, 414, 158, 925, 1181, 413, 1180, 1326, 815, 48, 304, 560, 1071, 1327, 559, 961, 194, 450, 706, 1217, 705, 1107, 340, 85, 596, 852, 1363, 341, 1108, 84, 851, 121, 1253, 742, 231, 487, 998, 1254, 486, 632, 633, 1399, 888, 377, 1144, ): list([ 0, 1, 362, 363, 364, 365, ]), tuple( 1400, 1290, 523, 12, 268, 779, 1035, 1291, 1436, 669, 414, 158, 925, 1181, 1437, 670, 815, 48, 304, 560, 1071, 1327, 49, 816, 1217, 450, 195, 706, 962, 1363, 596, 85, 341, 852, 1108, 597, 1364, 889, 742, 231, 487, 377, 998, 1254, 743, 1144, 633, 122, ): list([ 2, 3, 4, ]), tuple( 1408, 641, 130, 386, 897, 1153, 787, 20, 276, 532, 1043, 1299, 21, 788, 933, 166, 422, 678, 1189, 1445, 167, 934, 1335, 568, 313, 57, 824, 1080, 714, 203, 459, 970, 1226, 715, 860, 605, 349, 93, 1116, 1372, 861, 94, 1262, 239, 495, 751, 1007, 240, ): list([ 86, 87, 88, ]), tuple( 1409, 386, 131, 642, 898, 1154, 387, 788, 21, 277, 533, 1044, 1300, 934, 167, 423, 679, 1190, 1446, 168, 935, 1080, 569, 313, 58, 825, 1336, 314, 1081, 715, 204, 460, 971, 1227, 861, 350, 606, 94, 1117, 1373, 862, 1007, 240, 496, 752, 1263, 1008, 241, ): list([ 95, 96, 97, ]), tuple( 1409, 642, 131, 387, 898, 1154, 643, 1410, 1044, 277, 22, 533, 789, 1300, 1190, 423, 168, 679, 935, 1446, 424, 1191, 1336, 569, 314, 58, 825, 1081, 570, 1337, 971, 204, 460, 716, 1227, 1117, 606, 350, 95, 862, 1373, 1263, 496, 241, 752, 1008, 497, 1264, ): list([ 98, 99, 100, ]), tuple( 1410, 643, 132, 388, 899, 1155, 644, 1411, 789, 22, 278, 534, 1045, 1301, 23, 790, 1191, 424, 169, 680, 936, 1447, 1337, 570, 315, 59, 826, 1082, 571, 1338, 716, 205, 461, 972, 1228, 717, 1118, 351, 607, 95, 863, 1374, 96, 1264, 497, 242, 753, 1009, ): list([ 107, 108, 109, ]), tuple( 1411, 644, 133, 389, 900, 1156, 790, 535, 279, 23, 1046, 1302, 24, 791, 936, 169, 425, 681, 1192, 1448, 170, 937, 1338, 571, 316, 60, 827, 1083, 717, 206, 462, 973, 1229, 718, 863, 96, 352, 608, 1119, 1375, 864, 97, 1265, 242, 498, 754, 1010, 243, ): list([ 116, 117, 118, ]), tuple( 1412, 389, 134, 645, 901, 1157, 390, 791, 280, 536, 24, 1047, 1303, 937, 170, 426, 682, 1193, 1449, 171, 938, 1083, 316, 61, 572, 828, 1339, 317, 1084, 718, 207, 463, 974, 1230, 864, 97, 353, 609, 1120, 1376, 865, 1010, 243, 499, 755, 1266, 1011, 244, ): list([ 125, 126, 127, ]), tuple( 1412, 645, 134, 390, 901, 1157, 646, 1413, 1047, 536, 280, 25, 792, 1303, 1193, 426, 171, 682, 938, 1449, 427, 1194, 1339, 572, 317, 61, 828, 1084, 573, 1340, 974, 207, 463, 719, 1230, 1120, 353, 98, 609, 865, 1376, 1266, 499, 244, 755, 1011, 500, 1267, ): list([ 128, 129, 130, ]), tuple( 1413, 646, 135, 391, 902, 1158, 647, 1414, 792, 281, 537, 25, 1048, 1304, 26, 793, 1194, 427, 172, 683, 939, 1450, 1340, 573, 318, 62, 829, 1085, 574, 1341, 719, 208, 464, 975, 1231, 720, 1121, 98, 354, 610, 866, 1377, 99, 1267, 500, 245, 756, 1012, ): list([ 137, 138, 139, ]), tuple( 1414, 647, 136, 392, 903, 1159, 793, 282, 538, 26, 1049, 1305, 27, 794, 939, 172, 428, 684, 1195, 1451, 173, 940, 1341, 574, 319, 63, 830, 1086, 720, 209, 465, 976, 1232, 721, 866, 99, 355, 611, 1122, 1378, 867, 100, 1268, 245, 501, 757, 1013, 246, ): list([ 146, 147, 148, ]), tuple( 1415, 392, 137, 648, 904, 1160, 393, 794, 283, 539, 27, 1050, 1306, 940, 173, 429, 685, 1196, 1452, 174, 941, 1086, 575, 64, 319, 831, 1342, 320, 1087, 721, 210, 466, 977, 1233, 247, 867, 100, 356, 612, 1123, 1379, 868, 1013, 246, 502, 758, 1269, 1014, ): list([ 155, 156, 157, ]), tuple( 1415, 648, 137, 393, 904, 1160, 649, 1416, 1050, 539, 283, 28, 795, 1306, 1196, 429, 174, 685, 941, 1452, 430, 1197, 1342, 575, 64, 320, 831, 1087, 576, 1343, 977, 210, 466, 722, 1233, 1123, 356, 101, 612, 868, 1379, 503, 1269, 502, 247, 758, 1014, 1270, ): list([ 158, 159, 160, ]), tuple( 1416, 649, 138, 394, 905, 1161, 650, 1417, 795, 284, 28, 540, 1051, 1307, 29, 796, 1197, 430, 175, 686, 942, 1343, 576, 65, 321, 832, 1088, 577, 1344, 722, 211, 467, 978, 1234, 723, 1124, 101, 357, 613, 869, 1380, 102, 759, 1015, 1270, 503, 248, ): list([ 167, 168, 169, ]), tuple( 1417, 650, 139, 395, 906, 1162, 796, 541, 285, 29, 1052, 1308, 30, 797, 942, 175, 431, 687, 1198, 176, 943, 1344, 577, 66, 322, 833, 1089, 723, 212, 468, 979, 1235, 724, 1016, 248, 869, 102, 358, 614, 1125, 1381, 870, 103, 760, 1271, 504, 249, ): list([ 176, 177, 178, ]), }) # --- # name: test_snapshot_cohorts[PerfectBlockwiseResampling] dict({ tuple( 0, ): list([ 0, 1, ]), tuple( 1, ): list([ 2, 3, ]), tuple( 2, ): list([ 4, 5, ]), tuple( 3, ): list([ 6, 7, ]), tuple( 4, ): list([ 8, 9, ]), tuple( 5, ): list([ 10, 11, ]), tuple( 6, ): list([ 12, 13, ]), tuple( 7, ): list([ 14, 15, ]), tuple( 8, ): list([ 16, 17, ]), tuple( 9, ): list([ 18, 19, ]), tuple( 10, ): list([ 20, 21, ]), tuple( 11, ): list([ 22, 23, ]), tuple( 12, ): list([ 24, 25, ]), tuple( 13, ): list([ 26, 27, ]), tuple( 14, ): list([ 28, 29, ]), tuple( 15, ): list([ 30, 31, ]), tuple( 16, ): list([ 32, 33, ]), tuple( 17, ): list([ 34, 35, ]), tuple( 18, ): list([ 36, 37, ]), tuple( 19, ): list([ 38, 39, ]), tuple( 20, ): list([ 40, 41, ]), tuple( 21, ): list([ 42, 43, ]), tuple( 22, ): list([ 44, 45, ]), tuple( 23, ): list([ 46, 47, ]), tuple( 24, ): list([ 48, 49, ]), tuple( 25, ): list([ 50, 51, ]), tuple( 26, ): list([ 52, 53, ]), tuple( 27, ): list([ 54, 55, ]), tuple( 28, ): list([ 56, 57, ]), tuple( 29, ): list([ 58, 59, ]), tuple( 30, ): list([ 60, 61, ]), tuple( 31, ): list([ 62, 63, ]), tuple( 32, ): list([ 64, 65, ]), tuple( 33, ): list([ 66, 67, ]), tuple( 34, ): list([ 68, 69, ]), tuple( 35, ): list([ 70, 71, ]), tuple( 36, ): list([ 72, 73, ]), tuple( 37, ): list([ 74, 75, ]), tuple( 38, ): list([ 76, 77, ]), tuple( 39, ): list([ 78, 79, ]), tuple( 40, ): list([ 80, 81, ]), tuple( 41, ): list([ 82, 83, ]), tuple( 42, ): list([ 84, 85, ]), tuple( 43, ): list([ 86, 87, ]), tuple( 44, ): list([ 88, 89, ]), tuple( 45, ): list([ 90, 91, ]), tuple( 46, ): list([ 92, 93, ]), tuple( 47, ): list([ 94, 95, ]), tuple( 48, ): list([ 96, 97, ]), tuple( 49, ): list([ 98, 99, ]), tuple( 50, ): list([ 100, 101, ]), tuple( 51, ): list([ 102, 103, ]), tuple( 52, ): list([ 104, 105, ]), tuple( 53, ): list([ 106, 107, ]), tuple( 54, ): list([ 108, 109, ]), tuple( 55, ): list([ 110, 111, ]), tuple( 56, ): list([ 112, 113, ]), tuple( 57, ): list([ 114, 115, ]), tuple( 58, ): list([ 116, 117, ]), tuple( 59, ): list([ 118, 119, ]), tuple( 60, ): list([ 120, 121, ]), tuple( 61, ): list([ 122, 123, ]), tuple( 62, ): list([ 124, 125, ]), tuple( 63, ): list([ 126, 127, ]), tuple( 64, ): list([ 128, 129, ]), tuple( 65, ): list([ 130, 131, ]), tuple( 66, ): list([ 132, 133, ]), tuple( 67, ): list([ 134, 135, ]), tuple( 68, ): list([ 136, 137, ]), tuple( 69, ): list([ 138, 139, ]), tuple( 70, ): list([ 140, 141, ]), tuple( 71, ): list([ 142, 143, ]), tuple( 72, ): list([ 144, 145, ]), tuple( 73, ): list([ 146, 147, ]), tuple( 74, ): list([ 148, 149, ]), tuple( 75, ): list([ 150, 151, ]), tuple( 76, ): list([ 152, 153, ]), tuple( 77, ): list([ 154, 155, ]), tuple( 78, ): list([ 156, 157, ]), tuple( 79, ): list([ 158, 159, ]), tuple( 80, ): list([ 160, 161, ]), tuple( 81, ): list([ 162, 163, ]), tuple( 82, ): list([ 164, 165, ]), tuple( 83, ): list([ 166, 167, ]), tuple( 84, ): list([ 168, 169, ]), tuple( 85, ): list([ 170, 171, ]), tuple( 86, ): list([ 172, 173, ]), tuple( 87, ): list([ 174, 175, ]), tuple( 88, ): list([ 176, 177, ]), tuple( 89, ): list([ 178, 179, ]), tuple( 90, ): list([ 180, 181, ]), tuple( 91, ): list([ 182, 183, ]), tuple( 92, ): list([ 184, 185, ]), tuple( 93, ): list([ 186, 187, ]), tuple( 94, ): list([ 188, 189, ]), tuple( 95, ): list([ 190, 191, ]), tuple( 96, ): list([ 192, 193, ]), tuple( 97, ): list([ 194, 195, ]), tuple( 98, ): list([ 196, 197, ]), tuple( 99, ): list([ 198, 199, ]), tuple( 100, ): list([ 200, 201, ]), tuple( 101, ): list([ 202, 203, ]), tuple( 102, ): list([ 204, 205, ]), tuple( 103, ): list([ 206, 207, ]), tuple( 104, ): list([ 208, 209, ]), tuple( 105, ): list([ 210, 211, ]), tuple( 106, ): list([ 212, 213, ]), tuple( 107, ): list([ 214, 215, ]), tuple( 108, ): list([ 216, 217, ]), tuple( 109, ): list([ 218, 219, ]), tuple( 110, ): list([ 220, 221, ]), tuple( 111, ): list([ 222, 223, ]), tuple( 112, ): list([ 224, 225, ]), tuple( 113, ): list([ 226, 227, ]), tuple( 114, ): list([ 228, 229, ]), tuple( 115, ): list([ 230, 231, ]), tuple( 116, ): list([ 232, 233, ]), tuple( 117, ): list([ 234, 235, ]), tuple( 118, ): list([ 236, 237, ]), tuple( 119, ): list([ 238, 239, ]), tuple( 120, ): list([ 240, 241, ]), tuple( 121, ): list([ 242, 243, ]), tuple( 122, ): list([ 244, 245, ]), tuple( 123, ): list([ 246, 247, ]), tuple( 124, ): list([ 248, 249, ]), tuple( 125, ): list([ 250, 251, ]), tuple( 126, ): list([ 252, 253, ]), tuple( 127, ): list([ 254, 255, ]), tuple( 128, ): list([ 256, 257, ]), tuple( 129, ): list([ 258, 259, ]), tuple( 130, ): list([ 260, 261, ]), tuple( 131, ): list([ 262, 263, ]), tuple( 132, ): list([ 264, 265, ]), tuple( 133, ): list([ 266, 267, ]), tuple( 134, ): list([ 268, 269, ]), tuple( 135, ): list([ 270, 271, ]), tuple( 136, ): list([ 272, 273, ]), tuple( 137, ): list([ 274, 275, ]), tuple( 138, ): list([ 276, 277, ]), tuple( 139, ): list([ 278, 279, ]), tuple( 140, ): list([ 280, 281, ]), tuple( 141, ): list([ 282, 283, ]), tuple( 142, ): list([ 284, 285, ]), tuple( 143, ): list([ 286, 287, ]), tuple( 144, ): list([ 288, 289, ]), tuple( 145, ): list([ 290, 291, ]), tuple( 146, ): list([ 292, ]), }) # --- # name: test_snapshot_cohorts[PerfectMonthly] dict({ tuple( 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99, 102, 105, 108, 111, 114, 117, 120, 123, 126, 129, 132, 135, 138, 141, 144, 147, 150, 153, 156, 159, 162, 165, 168, 171, ): list([ 0, 1, 2, 3, ]), tuple( 1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, 49, 52, 55, 58, 61, 64, 67, 70, 73, 76, 79, 82, 85, 88, 91, 94, 97, 100, 103, 106, 109, 112, 115, 118, 121, 124, 127, 130, 133, 136, 139, 142, 145, 148, 151, 154, 157, 160, 163, 166, 169, 172, ): list([ 4, 5, 6, 7, ]), tuple( 2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, 56, 59, 62, 65, 68, 71, 74, 77, 80, 83, 86, 89, 92, 95, 98, 101, 104, 107, 110, 113, 116, 119, 122, 125, 128, 131, 134, 137, 140, 143, 146, 149, 152, 155, 158, 161, 164, 167, 170, 173, ): list([ 8, 9, 10, 11, ]), }) # --- # name: test_snapshot_cohorts[RandomBigArray] dict({ }) # --- # name: test_snapshot_cohorts[SingleChunk] dict({ tuple( 0, ): list([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, ]), }) # --- flox-0.11.2/tests/conftest.py000066400000000000000000000013501514775624000161160ustar00rootroot00000000000000import pytest from hypothesis import HealthCheck, Verbosity, settings from . import requires_numbagg settings.register_profile( "ci", max_examples=1000, deadline=None, suppress_health_check=[HealthCheck.filter_too_much, HealthCheck.too_slow], ) settings.register_profile( "default", max_examples=300, deadline=500, suppress_health_check=[HealthCheck.filter_too_much, HealthCheck.too_slow], verbosity=Verbosity.verbose, ) settings.load_profile("default") @pytest.fixture( scope="module", params=[ "flox", "numpy", # pytest.param("numba", marks=requires_numba), pytest.param("numbagg", marks=requires_numbagg), ], ) def engine(request): return request.param flox-0.11.2/tests/strategies.py000066400000000000000000000126721514775624000164540ustar00rootroot00000000000000from __future__ import annotations from collections.abc import Callable from typing import Any import cftime import dask import hypothesis.extra.numpy as npst import hypothesis.strategies as st import numpy as np import sparse from . import ALL_FUNCS, SCIPY_STATS_FUNCS Chunks = tuple[tuple[int, ...], ...] calendars = st.sampled_from( [ "standard", "gregorian", "proleptic_gregorian", "noleap", "365_day", "360_day", "julian", "all_leap", "366_day", ] ) @st.composite def units(draw, *, calendar: str) -> str: choices = ["days", "hours", "minutes", "seconds", "milliseconds", "microseconds"] if calendar == "360_day": choices += ["months"] elif calendar == "noleap": choices += ["common_years"] time_units = draw(st.sampled_from(choices)) dt = draw(st.datetimes()) year, month, day = dt.year, dt.month, dt.day if calendar == "360_day": day = min(day, 30) if calendar in ["360_day", "365_day", "noleap"] and month == 2 and day == 29: day = 28 return f"{time_units} since {year}-{month}-{day}" @st.composite def cftime_arrays( draw: st.DrawFn, *, shape: st.SearchStrategy[tuple[int, ...]] = npst.array_shapes(), calendars: st.SearchStrategy[str] = calendars, elements: dict[str, Any] | None = None, ) -> np.ndarray[Any, Any]: if elements is None: elements = {} elements.setdefault("min_value", -10_000) elements.setdefault("max_value", 10_000) cal = draw(calendars) values = draw(npst.arrays(dtype=np.int64, shape=shape, elements=elements)) unit = draw(units(calendar=cal)) return cftime.num2date(values, units=unit, calendar=cal) def insert_nans(draw: st.DrawFn, array: np.ndarray) -> np.ndarray: if array.dtype.kind in "cf": nan_idx = draw( st.lists( st.integers(min_value=0, max_value=array.shape[-1] - 1), max_size=array.shape[-1] - 1, unique=True, ) ) if nan_idx: array[..., nan_idx] = np.nan return array numeric_dtypes = ( npst.integer_dtypes(endianness="=") | npst.unsigned_integer_dtypes(endianness="=") | npst.floating_dtypes(endianness="=", sizes=(32, 64)) # TODO: add complex here not in supported_dtypes ) numeric_like_dtypes = ( npst.boolean_dtypes() | numeric_dtypes | npst.datetime64_dtypes(endianness="=") | npst.timedelta64_dtypes(endianness="=") ) supported_dtypes = ( numeric_like_dtypes | npst.unicode_string_dtypes(endianness="=") | npst.complex_number_dtypes(endianness="=") ) by_dtype_st = supported_dtypes NON_NUMPY_FUNCS = [ "first", "last", "nanfirst", "nanlast", "count", "any", "all", ] + list(SCIPY_STATS_FUNCS) func_st = st.sampled_from([f for f in ALL_FUNCS if f not in NON_NUMPY_FUNCS]) @st.composite def numpy_arrays(draw: st.DrawFn, *, dtype) -> np.ndarray: array = draw(npst.arrays(elements={"allow_subnormal": False}, shape=npst.array_shapes(), dtype=dtype)) array = insert_nans(draw, array) return array numeric_arrays = numpy_arrays(dtype=numeric_dtypes) numeric_like_arrays = numpy_arrays(dtype=numeric_like_dtypes) all_arrays = numeric_like_arrays | cftime_arrays() def by_arrays( shape: st.SearchStrategy[tuple[int, ...]], *, elements: dict[str, Any] | None = None ) -> st.SearchStrategy[np.ndarray[Any, Any]]: if elements is None: elements = {} elements.setdefault("alphabet", st.characters(exclude_categories=["C"])) return st.one_of( npst.arrays( dtype=npst.integer_dtypes(endianness="=") | npst.unicode_string_dtypes(endianness="="), shape=shape, elements=elements, ), cftime_arrays(shape=shape, elements=elements), ) @st.composite def chunks(draw: st.DrawFn, *, shape: tuple[int, ...]) -> Chunks: chunks = [] for size in shape: if size > 1: nchunks = draw(st.integers(min_value=1, max_value=size - 1)) dividers = sorted( set(draw(st.integers(min_value=1, max_value=size - 1)) for _ in range(nchunks - 1)) ) chunks.append(tuple(a - b for a, b in zip(dividers + [size], [0] + dividers))) else: chunks.append((1,)) return tuple(chunks) @st.composite def chunked_arrays( draw: st.DrawFn, *, chunks: Callable[..., st.SearchStrategy[Chunks]] = chunks, arrays=all_arrays, from_array: Callable = dask.array.from_array, ) -> dask.array.Array: array = draw(arrays) chunks = draw(chunks(shape=array.shape)) return from_array(array, chunks=chunks) @st.composite def sparse_arrays( draw: st.DrawFn, *, elements={"allow_subnormal": False}, shapes=npst.array_shapes(), dtypes=npst.boolean_dtypes() | numeric_dtypes, sparse_class=sparse.COO, ) -> sparse.COO: dtype = draw(dtypes) fill_value = draw(npst.from_dtype(dtype=dtype, **elements)) # assume(dtype.kind not in "mM") # sparse doesn't support .view array = draw(npst.arrays(elements=elements, shape=shapes, dtype=st.just(dtype), fill=st.just(fill_value))) if draw(st.booleans()) and dtype.kind == "f": array = insert_nans(draw, array) if draw(st.booleans()) and dtype.kind == "f": # need to increase probability of NaN fill_value fill_value = np.nan sparse_array = sparse_class.from_numpy(array, fill_value=fill_value) return sparse_array flox-0.11.2/tests/test_asv.py000066400000000000000000000011111514775624000161140ustar00rootroot00000000000000# Run asv benchmarks as tests import pytest pytest.importorskip("dask") from asv_bench.benchmarks import reduce @pytest.mark.parametrize("problem", [reduce.ChunkReduce1D, reduce.ChunkReduce2D, reduce.ChunkReduce2DAllAxes]) def test_reduce(problem) -> None: testcase = problem() testcase.setup() for args in zip(*testcase.time_reduce.params): testcase.time_reduce(*args) def test_reduce_bare() -> None: testcase = reduce.ChunkReduce1D() testcase.setup() for args in zip(*testcase.time_reduce_bare.params): testcase.time_reduce_bare(*args) flox-0.11.2/tests/test_cohorts.py000066400000000000000000000012461514775624000170150ustar00rootroot00000000000000# Snapshot tests for cohorts detection import pytest pytest.importorskip("dask") from asv_bench.benchmarks import cohorts @pytest.mark.parametrize( "testcase", [ cohorts.ERA5DayOfYear, cohorts.ERA5Google, cohorts.ERA5MonthHour, cohorts.ERA5MonthHourRechunked, cohorts.OISST, cohorts.PerfectBlockwiseResampling, cohorts.PerfectMonthly, cohorts.RandomBigArray, cohorts.SingleChunk, cohorts.NWMMidwest, ], ) def test_snapshot_cohorts(testcase, snapshot): problem = testcase() problem.setup() chunks_cohorts = problem.chunks_cohorts() assert chunks_cohorts == snapshot flox-0.11.2/tests/test_core.py000066400000000000000000002410271514775624000162670ustar00rootroot00000000000000from __future__ import annotations import itertools import logging import warnings from collections.abc import Callable from functools import partial, reduce from typing import TYPE_CHECKING, Any from unittest.mock import MagicMock, patch import numpy as np import pandas as pd import pytest from numpy_groupies.aggregate_numpy import aggregate import flox import flox.dask from flox import set_options, xrutils from flox import xrdtypes as dtypes from flox.aggregations import Aggregation, _initialize_aggregation from flox.cohorts import find_group_cohorts from flox.core import ( HAS_NUMBAGG, _choose_engine, _convert_expected_groups_to_index, _validate_reindex, groupby_reduce, ) from flox.dask import _normalize_indexes, subset_to_blocks from flox.factorize import factorize_ from flox.lib import _is_sparse_supported_reduction from flox.rechunk import _get_optimal_chunks_for_groups, rechunk_for_blockwise, rechunk_for_cohorts from flox.reindex import ReindexArrayType, ReindexStrategy, reindex_ from flox.scan import groupby_scan from . import ( ALL_FUNCS, BLOCKWISE_FUNCS, SCIPY_STATS_FUNCS, assert_equal, assert_equal_tuple, has_cubed, has_dask, has_sparse, raise_if_dask_computes, requires_cubed, requires_dask, requires_sparse, to_numpy, ) logger = logging.getLogger("flox") logger.setLevel(logging.DEBUG) labels = np.array([0, 0, 2, 2, 2, 1, 1, 2, 2, 1, 1, 0]) nan_labels = labels.astype(float) # copy nan_labels[:5] = np.nan labels2d = np.array([labels[:5], np.flip(labels[:5])]) if has_dask: import dask import dask.array as da from dask.array import from_array dask.config.set(scheduler="sync") else: def dask_array_ones(*args): return None if has_cubed: import cubed DEFAULT_QUANTILE = 0.9 REINDEX_SPARSE_STRAT = ReindexStrategy(blockwise=False, array_type=ReindexArrayType.SPARSE_COO) REINDEX_SPARSE_PARAM = pytest.param( REINDEX_SPARSE_STRAT, marks=(requires_dask, pytest.mark.skipif(not has_sparse, reason="no sparse")) ) if TYPE_CHECKING: from flox.core import T_Agg, T_Engine, T_ExpectedGroupsOpt, T_Method def _get_array_func(func: str) -> Callable: if func == "count": def npfunc(x, **kwargs): x = np.asarray(x) return (~xrutils.isnull(x)).sum(**kwargs) elif func in ["nanfirst", "nanlast"]: npfunc = getattr(xrutils, func) elif func in SCIPY_STATS_FUNCS: import scipy.stats if "nan" in func: func = func[3:] nan_policy = "omit" else: nan_policy = "propagate" def npfunc(x, **kwargs): spfunc = partial(getattr(scipy.stats, func), nan_policy=nan_policy) with warnings.catch_warnings(): warnings.filterwarnings("ignore", r"After omitting NaNs, one or more axis-slices") result = getattr(spfunc(x, **kwargs), func) return result else: npfunc = getattr(np, func) return npfunc def test_alignment_error(): da = np.ones((12,)) labels = np.ones((5,)) with pytest.raises(ValueError): groupby_reduce(da, labels, func="mean") @pytest.mark.parametrize("dtype", (float, int)) @pytest.mark.parametrize("chunk", [False, pytest.param(True, marks=requires_dask)]) # TODO: make this intp when python 3.8 is dropped @pytest.mark.parametrize("expected_groups", [None, [0, 1, 2], np.array([0, 1, 2], dtype=np.int64)]) @pytest.mark.parametrize( "func, array, by, expected", [ ("sum", np.ones((12,)), labels, [3, 4, 5]), # form 1 ("sum", np.ones((12,)), nan_labels, [1, 4, 2]), # form 1 ("sum", np.ones((2, 12)), labels, [[3, 4, 5], [3, 4, 5]]), # form 3 ("sum", np.ones((2, 12)), nan_labels, [[1, 4, 2], [1, 4, 2]]), # form 3 ( "sum", np.ones((2, 12)), np.array([labels, labels]), [6, 8, 10], ), # form 1 after reshape ("sum", np.ones((2, 12)), np.array([nan_labels, nan_labels]), [2, 8, 4]), # (np.ones((12,)), np.array([labels, labels])), # form 4 ("count", np.ones((12,)), labels, [3, 4, 5]), # form 1 ("count", np.ones((12,)), nan_labels, [1, 4, 2]), # form 1 ("count", np.ones((2, 12)), labels, [[3, 4, 5], [3, 4, 5]]), # form 3 ("count", np.ones((2, 12)), nan_labels, [[1, 4, 2], [1, 4, 2]]), # form 3 ( "count", np.ones((2, 12)), np.array([labels, labels]), [6, 8, 10], ), # form 1 after reshape ("count", np.ones((2, 12)), np.array([nan_labels, nan_labels]), [2, 8, 4]), ("nanmean", np.ones((12,)), labels, [1, 1, 1]), # form 1 ("nanmean", np.ones((12,)), nan_labels, [1, 1, 1]), # form 1 ("nanmean", np.ones((2, 12)), labels, [[1, 1, 1], [1, 1, 1]]), # form 3 ("nanmean", np.ones((2, 12)), nan_labels, [[1, 1, 1], [1, 1, 1]]), # form 3 ("nanmean", np.ones((2, 12)), np.array([labels, labels]), [1, 1, 1]), ("nanmean", np.ones((2, 12)), np.array([nan_labels, nan_labels]), [1, 1, 1]), # (np.ones((12,)), np.array([labels, labels])), # form 4 ], ) def test_groupby_reduce( engine: T_Engine, func: T_Agg, array: np.ndarray, by: np.ndarray, expected: list[float], expected_groups: T_ExpectedGroupsOpt, chunk: bool, dtype: np.typing.DTypeLike, ) -> None: array = array.astype(dtype) if chunk: array = da.from_array(array, chunks=(3,) if array.ndim == 1 else (1, 3)) by = da.from_array(by, chunks=(3,) if by.ndim == 1 else (1, 3)) if func == "mean" or func == "nanmean": expected_result = np.array(expected, dtype=np.float64) elif func == "sum": expected_result = np.array(expected, dtype=dtypes._maybe_promote_int(array.dtype)) elif func == "count": expected_result = np.array(expected, dtype=np.intp) (result, *groups) = groupby_reduce( array, by, func=func, expected_groups=expected_groups, fill_value=123, engine=engine, ) (groups_array,) = groups # we use pd.Index(expected_groups).to_numpy() which is always int64 # for the values in this test if expected_groups is None: g_dtype = by.dtype elif isinstance(expected_groups, np.ndarray): g_dtype = expected_groups.dtype else: g_dtype = np.dtype(np.int64) assert_equal(groups_array, np.array([0, 1, 2], g_dtype)) assert_equal(expected_result, result) def gen_array_by(size, func): by = np.ones(size[-1]) rng = np.random.default_rng(12345) array = rng.random(tuple(6 if s == 1 else s for s in size)) if ("nan" in func or "fill" in func) and "nanarg" not in func: array[[1, 4, 5], ...] = np.nan elif "nanarg" in func and len(size) > 1: array[[1, 4, 5], 1] = np.nan if func in ["any", "all"]: array = array > 0.5 return array, by @pytest.mark.parametrize("to_sparse", [pytest.param(True, marks=requires_sparse), False]) @pytest.mark.parametrize( "chunks", [ None, pytest.param(-1, marks=requires_dask), pytest.param(3, marks=requires_dask), pytest.param(4, marks=requires_dask), ], ) @pytest.mark.parametrize("size", [(1, 12), (12,), (12, 9)]) @pytest.mark.parametrize("nby", [1, 2, 3]) @pytest.mark.parametrize("add_nan_by", [True, False]) @pytest.mark.parametrize("func", ALL_FUNCS) def test_groupby_reduce_all(to_sparse, nby, size, chunks, func, add_nan_by, engine): if ("arg" in func and engine in ["flox", "numbagg"]) or (func in BLOCKWISE_FUNCS and chunks != -1): pytest.skip() array, by = gen_array_by(size, func) if to_sparse: import sparse array = sparse.COO.from_numpy(array) if not _is_sparse_supported_reduction(func): pytest.skip() if chunks: array = dask.array.from_array(array, chunks=chunks) by = (by,) * nby by = [b + idx for idx, b in enumerate(by)] if add_nan_by: for idx in range(nby): by[idx][2 * idx : 2 * idx + 3] = np.nan by = tuple(by) nanmask = reduce(np.logical_or, (np.isnan(b) for b in by)) finalize_kwargs = [{}] if "var" in func or "std" in func: finalize_kwargs = finalize_kwargs + [{"ddof": 1}, {"ddof": 0}] fill_value = np.nan tolerance = {"rtol": 1e-13, "atol": 1e-15} elif "quantile" in func: finalize_kwargs = [ {"q": DEFAULT_QUANTILE}, {"q": [DEFAULT_QUANTILE / 2, DEFAULT_QUANTILE]}, ] fill_value = None tolerance = None else: fill_value = None tolerance = None # for constructing expected array_func = _get_array_func(func) for kwargs in finalize_kwargs: if "quantile" in func and isinstance(kwargs["q"], list) and engine != "flox": continue flox_kwargs = dict(func=func, engine=engine, finalize_kwargs=kwargs, fill_value=fill_value) with np.errstate(invalid="ignore", divide="ignore"): with warnings.catch_warnings(): warnings.filterwarnings("ignore", r"All-NaN (slice|axis) encountered") warnings.filterwarnings("ignore", r"Degrees of freedom <= 0 for slice") warnings.filterwarnings("ignore", r"Mean of empty slice") # computing silences a bunch of dask warnings array_ = to_numpy(array) if "arg" in func and add_nan_by: # NaNs are in by, but we can't call np.argmax([..., NaN, .. ]) # That would return index of the NaN # This way, we insert NaNs where there are NaNs in by, and # call np.nanargmax func_ = f"nan{func}" if "nan" not in func else func array_[..., nanmask] = np.nan expected = getattr(np, func_)(array_, axis=-1, **kwargs) else: expected = array_func(array_[..., ~nanmask], axis=-1, **kwargs) for _ in range(nby): expected = np.expand_dims(expected, axis=-1) if func in BLOCKWISE_FUNCS: assert chunks == -1 actual, *groups = groupby_reduce(array, *by, **flox_kwargs) if "quantile" in func and isinstance(kwargs["q"], list): assert actual.ndim == expected.ndim == (array.ndim + nby) else: assert actual.ndim == expected.ndim == (array.ndim + nby - 1) expected_groups = tuple(np.array([idx + 1.0]) for idx in range(nby)) for actual_group, expect in zip(groups, expected_groups): assert_equal(actual_group, expect) if "arg" in func: assert actual.dtype.kind == "i" assert_equal(expected, actual, tolerance) if "nan" not in func and "arg" not in func: # test non-NaN skipping behaviour when NaNs are present nanned = array_.copy() # remove nans in by to reduce complexity # We are checking for consistent behaviour with NaNs in array by_ = tuple(np.nan_to_num(b, nan=np.nanmin(b)) for b in by) nanned[[1, 4, 5], ...] = np.nan nanned.reshape(-1)[0] = np.nan actual, *_ = groupby_reduce(nanned, *by_, **flox_kwargs) expected_0 = array_func(nanned, axis=-1, **kwargs) for _ in range(nby): expected_0 = np.expand_dims(expected_0, -1) assert_equal(expected_0, actual, tolerance) if not has_dask or chunks is None or func in BLOCKWISE_FUNCS: continue params = list( itertools.product( ["map-reduce"], [True, False, None, REINDEX_SPARSE_STRAT], ) ) params.extend(itertools.product(["cohorts"], [False, None])) if chunks == -1: params.extend([("blockwise", None)]) combine_error = RuntimeError("This combine should not have been called.") for method, reindex in params: if isinstance(reindex, ReindexStrategy) and not _is_sparse_supported_reduction(func): continue call = partial( groupby_reduce, array, *by, method=method, reindex=reindex, **flox_kwargs, ) if ("arg" in func or func in ["first", "last"]) and reindex is True: # simple_combine with argreductions not supported right now with pytest.raises(NotImplementedError): call() continue if method == "blockwise": # no combine necessary mocks = { "_simple_combine": MagicMock(side_effect=combine_error), "_grouped_combine": MagicMock(side_effect=combine_error), } else: if "arg" not in func: # make sure we use simple combine mocks = {"_grouped_combine": MagicMock(side_effect=combine_error)} else: mocks = {"_simple_combine": MagicMock(side_effect=combine_error)} with patch.multiple(flox.dask, **mocks): actual, *groups = call() for actual_group, expect in zip(groups, expected_groups): assert_equal(actual_group, expect, tolerance) if "arg" in func: assert actual.dtype.kind == "i" if isinstance(reindex, ReindexStrategy): import sparse expected = sparse.COO.from_numpy(expected) assert_equal(actual, expected, tolerance) @requires_dask @pytest.mark.parametrize("size", ((12,), (12, 5))) @pytest.mark.parametrize("func", ("argmax", "nanargmax", "argmin", "nanargmin")) def test_arg_reduction_dtype_is_int(size, func): """avoid bugs being hidden by the xfail in the above test.""" rng = np.random.default_rng(12345) array = rng.random(size) by = np.ones(size[-1]) if "nanarg" in func and len(size) > 1: array[[1, 4, 5], 1] = np.nan expected = getattr(np, func)(array, axis=-1) expected = np.expand_dims(expected, -1) actual, _ = groupby_reduce(array, by, func=func, engine="numpy") assert actual.dtype.kind == "i" actual, _ = groupby_reduce(da.from_array(array, chunks=3), by, func=func, engine="numpy") assert actual.dtype.kind == "i" def test_groupby_reduce_count(): array = np.array([0, 0, np.nan, np.nan, np.nan, 1, 1]) labels = np.array(["a", "b", "b", "b", "c", "c", "c"]) result, _ = groupby_reduce(array, labels, func="count") assert_equal(result, np.array([1, 1, 2], dtype=np.intp)) def test_func_is_aggregation(): from flox.aggregations import mean array = np.array([0, 0, np.nan, np.nan, np.nan, 1, 1]) labels = np.array(["a", "b", "b", "b", "c", "c", "c"]) expected, _ = groupby_reduce(array, labels, func="mean") actual, _ = groupby_reduce(array, labels, func=mean) assert_equal(actual, expected) @requires_dask @pytest.mark.parametrize("func", ("sum", "prod")) @pytest.mark.parametrize("dtype", [np.float32, np.float64, np.int32, np.int64]) def test_groupby_reduce_preserves_dtype(dtype, func): array = np.ones((2, 12), dtype=dtype) by = np.array([labels] * 2) result, _ = groupby_reduce(from_array(array, chunks=(-1, 4)), by, func=func) expect_dtype = dtypes._maybe_promote_int(array.dtype) assert result.dtype == expect_dtype def test_numpy_reduce_nd_md(): array = np.ones((2, 12)) by = np.array([labels] * 2) expected = aggregate(by.ravel(), array.ravel(), func="sum") result, groups = groupby_reduce(array, by, func="sum", fill_value=123) actual = reindex_(result, groups, pd.Index(np.unique(by)), axis=0, fill_value=0) np.testing.assert_equal(expected, actual) array = np.ones((4, 2, 12)) by = np.array([labels] * 2) expected = aggregate(by.ravel(), array.reshape(4, 24), func="sum", axis=-1, fill_value=0) result, groups = groupby_reduce(array, by, func="sum") actual = reindex_(result, groups, pd.Index(np.unique(by)), axis=-1, fill_value=0) assert_equal(expected, actual) array = np.ones((4, 2, 12)) by = np.broadcast_to(np.array([labels] * 2), array.shape) expected = aggregate(by.ravel(), array.ravel(), func="sum", axis=-1) result, groups = groupby_reduce(array, by, func="sum") actual = reindex_(result, groups, pd.Index(np.unique(by)), axis=-1, fill_value=0) assert_equal(expected, actual) array = np.ones((2, 3, 4)) by = np.ones((2, 3, 4)) actual, _ = groupby_reduce(array, by, axis=(1, 2), func="sum") expected = np.sum(array, axis=(1, 2), keepdims=True).squeeze(2) assert_equal(actual, expected) @requires_dask @pytest.mark.parametrize("reindex", [None, False, True, REINDEX_SPARSE_PARAM]) @pytest.mark.parametrize("func", ALL_FUNCS) @pytest.mark.parametrize("add_nan", [False, True]) @pytest.mark.parametrize("dtype", (float,)) @pytest.mark.parametrize( "shape, array_chunks, group_chunks", [ ((12,), (3,), 3), # form 1 ((12,), (3,), (4,)), # form 1, chunks not aligned ((12,), ((3, 5, 4),), (2,)), # form 1 ((10, 12), (3, 3), -1), # form 3 ((10, 12), (3, 3), 3), # form 3 ], ) def test_groupby_agg_dask(func, shape, array_chunks, group_chunks, add_nan, dtype, engine, reindex): """Tests groupby_reduce with dask arrays against groupby_reduce with numpy arrays""" if func in ["first", "last"] or func in BLOCKWISE_FUNCS: pytest.skip() if "arg" in func and (engine in ["flox", "numbagg"] or reindex): pytest.skip() if isinstance(reindex, ReindexStrategy) and not _is_sparse_supported_reduction(func): pytest.skip() rng = np.random.default_rng(12345) array = dask.array.from_array(rng.random(shape), chunks=array_chunks).astype(dtype) array = dask.array.ones(shape, chunks=array_chunks) labels = np.array([0, 0, 2, 2, 2, 1, 1, 2, 2, 1, 1, 0]) if add_nan: labels = labels.astype(float) labels[:3] = np.nan # entire block is NaN when group_chunks=3 labels[-2:] = np.nan kwargs = dict( func=func, expected_groups=[0, 1, 2], fill_value=False if func in ["all", "any"] else 123, ) expected, _ = groupby_reduce(array.compute(), labels, engine="numpy", **kwargs) actual, _ = groupby_reduce(array.compute(), labels, engine=engine, **kwargs) assert_equal(actual, expected) with raise_if_dask_computes(): actual, _ = groupby_reduce(array, labels, engine=engine, **kwargs) assert_equal(actual, expected) by = from_array(labels, group_chunks) with raise_if_dask_computes(): actual, _ = groupby_reduce(array, by, engine=engine, **kwargs) assert_equal(expected, actual) kwargs["expected_groups"] = [0, 2, 1] with raise_if_dask_computes(): actual, groups = groupby_reduce(array, by, engine=engine, **kwargs, sort=False) assert_equal(groups, np.array([0, 2, 1], dtype=np.int64)) assert_equal(expected, actual[..., [0, 2, 1]]) with raise_if_dask_computes(): actual, groups = groupby_reduce(array, by, engine=engine, **kwargs, sort=True) assert_equal(groups, np.array([0, 1, 2], np.int64)) assert_equal(expected, actual) @requires_cubed @pytest.mark.parametrize("reindex", [True]) @pytest.mark.parametrize("func", ALL_FUNCS) @pytest.mark.parametrize("add_nan", [False, True]) @pytest.mark.parametrize( "shape, array_chunks, group_chunks", [ ((12,), (3,), 3), # form 1 ], ) def test_groupby_agg_cubed(func, shape, array_chunks, group_chunks, add_nan, engine, reindex): """Tests groupby_reduce with cubed arrays against groupby_reduce with numpy arrays""" if func in ["first", "last", "var", "nanvar", "std", "nanstd"] or func in BLOCKWISE_FUNCS: pytest.skip() if "arg" in func and (engine in ["flox", "numbagg"] or reindex): pytest.skip() array = cubed.array_api.ones(shape, chunks=array_chunks) labels = np.array([0, 0, 2, 2, 2, 1, 1, 2, 2, 1, 1, 0]) if add_nan: labels = labels.astype(float) labels[:3] = np.nan # entire block is NaN when group_chunks=3 labels[-2:] = np.nan kwargs = dict( func=func, expected_groups=[0, 1, 2], fill_value=False if func in ["all", "any"] else 123, reindex=reindex, ) expected, _ = groupby_reduce(array.compute(), labels, engine="numpy", **kwargs) actual, _ = groupby_reduce(array.compute(), labels, engine=engine, **kwargs) assert_equal(actual, expected) # TODO: raise_if_cubed_computes actual, _ = groupby_reduce(array, labels, engine=engine, **kwargs) assert_equal(expected, actual) def test_numpy_reduce_axis_subset(engine): # TODO: add NaNs by = labels2d array = np.ones_like(by, dtype=np.int64) kwargs = dict(func="count", engine=engine, fill_value=0) result, _ = groupby_reduce(array, by, **kwargs, axis=1) assert_equal(result, np.array([[2, 3], [2, 3]], dtype=np.intp)) by = np.broadcast_to(labels2d, (3, *labels2d.shape)) array = np.ones_like(by) result, _ = groupby_reduce(array, by, **kwargs, axis=1) subarr = np.array([[1, 1], [1, 1], [0, 2], [1, 1], [1, 1]], dtype=np.intp) expected = np.tile(subarr, (3, 1, 1)) assert_equal(result, expected) result, _ = groupby_reduce(array, by, **kwargs, axis=2) subarr = np.array([[2, 3], [2, 3]], dtype=np.intp) expected = np.tile(subarr, (3, 1, 1)) assert_equal(result, expected) result, _ = groupby_reduce(array, by, **kwargs, axis=(1, 2)) expected = np.array([[4, 6], [4, 6], [4, 6]], dtype=np.intp) assert_equal(result, expected) result, _ = groupby_reduce(array, by, **kwargs, axis=(2, 1)) assert_equal(result, expected) result, _ = groupby_reduce(array, by[0, ...], **kwargs, axis=(1, 2)) expected = np.array([[4, 6], [4, 6], [4, 6]], dtype=np.intp) assert_equal(result, expected) @requires_dask def test_dask_reduce_axis_subset(): by = labels2d array = np.ones_like(by, dtype=np.int64) with raise_if_dask_computes(): result, _ = groupby_reduce( da.from_array(array, chunks=(2, 3)), da.from_array(by, chunks=(2, 2)), func="count", axis=1, expected_groups=[0, 2], ) assert_equal(result, np.array([[2, 3], [2, 3]], dtype=np.intp)) by = np.broadcast_to(labels2d, (3, *labels2d.shape)) array = np.ones_like(by) subarr = np.array([[1, 1], [1, 1], [123, 2], [1, 1], [1, 1]], dtype=np.intp) expected = np.tile(subarr, (3, 1, 1)) with raise_if_dask_computes(): result, _ = groupby_reduce( da.from_array(array, chunks=(1, 2, 3)), da.from_array(by, chunks=(2, 2, 2)), func="count", axis=1, expected_groups=[0, 2], fill_value=123, ) assert_equal(result, expected) subarr = np.array([[2, 3], [2, 3]], dtype=np.intp) expected = np.tile(subarr, (3, 1, 1)) with raise_if_dask_computes(): result, _ = groupby_reduce( da.from_array(array, chunks=(1, 2, 3)), da.from_array(by, chunks=(2, 2, 2)), func="count", axis=2, expected_groups=[0, 2], ) assert_equal(result, expected) with pytest.raises(NotImplementedError): groupby_reduce( da.from_array(array, chunks=(1, 3, 2)), da.from_array(by, chunks=(2, 2, 2)), func="count", axis=2, ) @pytest.mark.parametrize("group_idx", [[0, 1, 0], [0, 0, 1], [1, 0, 0], [1, 1, 0]]) @pytest.mark.parametrize( "func", [ # "first", "last", "nanfirst", "nanlast", ], ) @pytest.mark.parametrize( "chunks", [ None, pytest.param(1, marks=pytest.mark.skipif(not has_dask, reason="no dask")), pytest.param(2, marks=pytest.mark.skipif(not has_dask, reason="no dask")), pytest.param(3, marks=pytest.mark.skipif(not has_dask, reason="no dask")), ], ) def test_first_last_useless(func, chunks, group_idx): array = np.array([[0, 0, 0], [0, 0, 0]], dtype=np.int8) if chunks is not None: array = dask.array.from_array(array, chunks=chunks) actual, _ = groupby_reduce(array, np.array(group_idx), func=func, engine="numpy") expected = np.array([[0, 0], [0, 0]], dtype=np.int8) assert_equal(actual, expected) @pytest.mark.parametrize("func", ["first", "last", "nanfirst", "nanlast"]) @pytest.mark.parametrize("axis", [(0, 1)]) def test_first_last_disallowed(axis, func): with pytest.raises(ValueError): groupby_reduce(np.empty((2, 3, 2)), np.ones((2, 3, 2)), func=func, axis=axis) @requires_dask @pytest.mark.parametrize("func", ["nanfirst", "nanlast"]) @pytest.mark.parametrize("axis", [None, (0, 1, 2)]) def test_nanfirst_nanlast_disallowed_dask(axis, func): with pytest.raises(ValueError): groupby_reduce(dask.array.empty((2, 3, 2)), np.ones((2, 3, 2)), func=func, axis=axis) @requires_dask @pytest.mark.xfail @pytest.mark.parametrize("func", ["first", "last"]) def test_first_last_allowed_dask(func): # blockwise should be fine... but doesn't work now. groupby_reduce(dask.array.empty((2, 3, 2)), np.ones((2, 3, 2)), func=func, axis=-1) @requires_dask @pytest.mark.xfail @pytest.mark.parametrize("func", ["first", "last"]) def test_first_last_disallowed_dask(func): # blockwise is fine groupby_reduce(dask.array.empty((2, 3, 2)), np.ones((2, 3, 2)), func=func, axis=-1) # anything else is not. with pytest.raises(ValueError): groupby_reduce( dask.array.empty((2, 3, 2), chunks=(-1, -1, 1)), np.ones((2,)), func=func, axis=-1, ) @requires_dask @pytest.mark.parametrize("func", ALL_FUNCS) @pytest.mark.parametrize("axis", [None, (0, 1, 2), (0, 1), (0, 2), (1, 2), 0, 1, 2, (0,), (1,), (2,)]) def test_groupby_reduce_axis_subset_against_numpy(func, axis, engine): if ("arg" in func and engine in ["flox", "numbagg"]) or func in BLOCKWISE_FUNCS: pytest.skip() if not isinstance(axis, int): if "arg" in func and (axis is None or len(axis) > 1): pytest.skip() if ("first" in func or "last" in func) and (axis is not None and len(axis) not in [1, 3]): pytest.skip() if func in ["all", "any"]: fill_value = False else: fill_value = 123 if "var" in func or "std" in func: tolerance = {"rtol": 1e-14, "atol": 1e-16} else: tolerance = None # tests against the numpy output to make sure dask compute matches by = np.broadcast_to(labels2d, (3, *labels2d.shape)) rng = np.random.default_rng(12345) array = rng.random(by.shape) kwargs = dict(func=func, axis=axis, expected_groups=[0, 2], fill_value=fill_value) expected, _ = groupby_reduce(array, by, engine=engine, **kwargs) if engine == "flox": expected_npg, _ = groupby_reduce(array, by, **kwargs, engine="numpy") assert_equal(expected_npg, expected) if func in ["all", "any"]: fill_value = False else: fill_value = 123 if "var" in func or "std" in func: tolerance = {"rtol": 1e-14, "atol": 1e-16} else: tolerance = None # tests against the numpy output to make sure dask compute matches by = np.broadcast_to(labels2d, (3, *labels2d.shape)) rng = np.random.default_rng(12345) array = rng.random(by.shape) kwargs = dict(func=func, axis=axis, expected_groups=[0, 2], fill_value=fill_value) expected, _ = groupby_reduce(array, by, engine=engine, **kwargs) if engine == "flox": expected_npg, _ = groupby_reduce(array, by, **kwargs, engine="numpy") assert_equal(expected_npg, expected) if ("first" in func or "last" in func) and ( axis is None or (not isinstance(axis, int) and len(axis) != 1) ): return with raise_if_dask_computes(): actual, _ = groupby_reduce( da.from_array(array, chunks=(-1, 2, 3)), da.from_array(by, chunks=(-1, 2, 2)), engine=engine, **kwargs, ) assert_equal(actual, expected, tolerance) @pytest.mark.parametrize( "reindex, chunks", [ (None, None), pytest.param(False, (2, 2, 3), marks=requires_dask), pytest.param(True, (2, 2, 3), marks=requires_dask), pytest.param(REINDEX_SPARSE_PARAM, (2, 2, 3), marks=requires_dask), ], ) @pytest.mark.parametrize( "axis, groups, expected_shape", [ (2, [0, 1, 2], (3, 5, 3)), (None, [0, 1, 2], (3,)), # global reduction; 0 shaped group axis (None, [0], (1,)), # global reduction; 0 shaped group axis; 1 group ], ) def test_groupby_reduce_nans(reindex, chunks, axis, groups, expected_shape, engine): def _maybe_chunk(arr): if chunks: return da.from_array(arr, chunks=chunks) else: return arr # test when entire by are NaNs by = np.full((3, 5, 2), fill_value=np.nan) array = np.ones_like(by) # along an axis; requires expected_group # TODO: this should check for fill_value result, _ = groupby_reduce( _maybe_chunk(array), _maybe_chunk(by), func="count", expected_groups=groups, axis=axis, fill_value=0, engine=engine, reindex=reindex, ) assert_equal(result, np.zeros(expected_shape, dtype=np.intp)) # now when subsets are NaN # labels = np.array([0, 0, 1, 1, 1], dtype=float) # labels2d = np.array([labels[:5], np.flip(labels[:5])]) # labels2d[0, :5] = np.nan # labels2d[1, 5:] = np.nan # by = np.broadcast_to(labels2d, (3, *labels2d.shape)) @requires_dask @pytest.mark.parametrize( "expected_groups, reindex", [ (None, None), (None, False), ([0, 1, 2], True), ([0, 1, 2], False), pytest.param([0, 1, 2], REINDEX_SPARSE_PARAM), ], ) def test_groupby_all_nan_blocks_dask(expected_groups, reindex, engine): labels = np.array([0, 0, 2, 2, 2, 1, 1, 2, 2, 1, 1, 0]) nan_labels = labels.astype(float) # copy nan_labels[:5] = np.nan array, by, expected = ( np.ones((2, 12), dtype=np.int64), np.array([nan_labels, nan_labels[::-1]]), np.array([2, 8, 4], dtype=np.int64), ) actual, _ = groupby_reduce( da.from_array(array, chunks=(1, 3)), da.from_array(by, chunks=(1, 3)), func="sum", expected_groups=expected_groups, engine=engine, reindex=reindex, method="map-reduce", ) assert_equal(actual, expected) @pytest.mark.parametrize("axis", (0, 1, 2, -1)) def test_reindex(axis): shape = [2, 2, 2] fill_value = 0 array = np.broadcast_to(np.array([1, 2]), shape) groups = np.array(["a", "b"]) expected_groups = pd.Index(["a", "b", "c"]) actual = reindex_(array, groups, expected_groups, fill_value=fill_value, axis=axis) if axis < 0: axis = array.ndim + axis result_shape = tuple(len(expected_groups) if ax == axis else s for ax, s in enumerate(shape)) slicer = tuple(slice(None, s) for s in shape) expected = np.full(result_shape, fill_value) expected[slicer] = array assert_equal(actual, expected) @pytest.mark.xfail def test_bad_npg_behaviour(): labels = np.array([0, 0, 2, 2, 2, 1, 1, 2, 2, 1, 1, 0], dtype=int) # fmt: off array = np.array([[1] * 12, [1] * 12]) # fmt: on assert_equal( aggregate(labels, array, axis=-1, func="argmax"), np.array([[0, 5, 2], [0, 5, 2]]), ) assert ( aggregate( np.array([0, 1, 2, 0, 1, 2]), np.array([-np.inf, 0, 0, -np.inf, 0, 0]), func="max", )[0] == -np.inf ) @pytest.mark.xfail @pytest.mark.parametrize("func", ("nanargmax", "nanargmin")) def test_npg_nanarg_bug(func): array = np.array([1, 1, 2, 1, 1, np.nan, 6, 1]) labels = np.array([1, 1, 1, 1, 1, 1, 1, 1]) - 1 actual = aggregate(labels, array, func=func).astype(int) expected = getattr(np, func)(array) assert_equal(actual, expected) @pytest.mark.parametrize( "kwargs", ( dict(expected_groups=np.array([1, 2, 4, 5]), isbin=True), dict(expected_groups=pd.IntervalIndex.from_breaks([1, 2, 4, 5])), ), ) @pytest.mark.parametrize("method", ["cohorts", "map-reduce"]) @pytest.mark.parametrize("chunk_labels", [False, True]) @pytest.mark.parametrize( "chunks", ( (), pytest.param((1,), marks=requires_dask), pytest.param((2,), marks=requires_dask), ), ) def test_groupby_bins(chunk_labels, kwargs, chunks, engine, method) -> None: array = [1, 1, 1, 1, 1, 1] labels = [0.2, 1.5, 1.9, 2, 3, 20] if method == "cohorts" and chunk_labels: pytest.xfail() if chunks: array = dask.array.from_array(array, chunks=chunks) if chunk_labels: labels = dask.array.from_array(labels, chunks=chunks) with raise_if_dask_computes(): actual, *groups = groupby_reduce( array, labels, func="count", fill_value=0, engine=engine, method=method, **kwargs, ) (groups_array,) = groups expected = np.array([3, 1, 0], dtype=np.intp) for left, right in zip(groups_array, pd.IntervalIndex.from_arrays([1, 2, 4], [2, 4, 5]).to_numpy()): assert left == right assert_equal(actual, expected) @requires_dask @pytest.mark.parametrize( "inchunks, expected, expected_method", [ [(1,) * 10, (3, 2, 2, 3), None], [(2,) * 5, (3, 2, 2, 3), None], [(3, 3, 3, 1), (3, 2, 5), None], [(3, 1, 1, 2, 1, 1, 1), (3, 2, 2, 3), None], [(3, 2, 2, 3), (3, 2, 2, 3), "blockwise"], [(4, 4, 2), (3, 4, 3), None], [(5, 5), (5, 5), "blockwise"], [(6, 4), (5, 5), None], [(7, 3), (7, 3), "blockwise"], [(8, 2), (7, 3), None], [(9, 1), (10,), None], [(10,), (10,), "blockwise"], ], ) def test_rechunk_for_blockwise(inchunks, expected, expected_method): labels = np.array([1, 1, 1, 2, 2, 3, 3, 5, 5, 5]) assert _get_optimal_chunks_for_groups(inchunks, labels) == expected # reversed assert _get_optimal_chunks_for_groups(inchunks, labels[::-1]) == expected with set_options(rechunk_blockwise_chunk_size_threshold=-1): array = dask.array.ones(labels.size, chunks=(inchunks,)) method, array = rechunk_for_blockwise(array, -1, labels, force=False) assert method == expected_method assert array.chunks == (inchunks,) method, array = rechunk_for_blockwise(array, -1, labels[::-1], force=False) assert method == expected_method assert array.chunks == (inchunks,) @requires_dask @pytest.mark.parametrize( "expected, labels, chunks", [ [[[0, 1, 2, 3]], [0, 1, 2, 0, 1, 2, 3], (3, 4)], [[[0], [1], [2], [3]], [0, 1, 2, 0, 1, 2, 3], (2, 2, 2, 1)], [[[0, 1, 2], [3]], [0, 1, 2, 0, 1, 2, 3], (3, 3, 1)], [ [[0], [1, 2, 3, 4], [5]], np.repeat(np.arange(6), [4, 4, 12, 2, 3, 4]), (4, 8, 4, 9, 4), ], ], ) def test_find_group_cohorts(expected, labels, chunks: tuple[int]) -> None: # force merging of cohorts for the test _, chunks_cohorts = find_group_cohorts(labels, (chunks,), merge=True) actual = list(chunks_cohorts.values()) assert actual == expected, (actual, expected) @requires_dask def test_find_cohorts_missing_groups(): by = np.array([np.nan, np.nan, np.nan, 2.0, 2.0, 1.0, 1.0, 2.0, 2.0, 1.0, np.nan, np.nan]) kwargs = {"func": "sum", "expected_groups": [0, 1, 2], "fill_value": 123} array = dask.array.ones_like(by, chunks=(3,)) actual, _ = groupby_reduce(array, by, method="cohorts", **kwargs) expected, _ = groupby_reduce(array.compute(), by, **kwargs) assert_equal(expected, actual) @pytest.mark.parametrize("chunksize", [12, 13, 14, 24, 36, 48, 72, 71]) def test_verify_complex_cohorts(chunksize: int) -> None: time = pd.Series(pd.date_range("2016-01-01", "2018-12-31 23:59", freq="h")) chunks = (chunksize,) * (len(time) // chunksize) by = np.array(time.dt.dayofyear.values) if len(by) != sum(chunks): chunks += (len(by) - sum(chunks),) _, chunk_cohorts = find_group_cohorts(by - 1, (chunks,)) chunks_ = np.sort(np.concatenate(tuple(chunk_cohorts.keys()))) groups = np.sort(np.concatenate(tuple(chunk_cohorts.values()))) assert_equal(np.unique(chunks_).astype(np.int64), np.arange(len(chunks), dtype=np.int64)) assert_equal(groups.astype(np.int64), np.arange(366, dtype=np.int64)) @requires_dask @pytest.mark.parametrize("chunksize", (12,) + tuple(range(1, 13)) + (-1,)) def test_method_guessing(chunksize): # just a regression test labels = np.tile(np.arange(0, 12), 30) by = dask.array.from_array(labels, chunks=chunksize) - 1 preferred_method, chunks_cohorts = find_group_cohorts(labels, by.chunks[slice(-1, None)]) if chunksize == -1: assert preferred_method == "blockwise" assert chunks_cohorts == {(0,): list(range(12))} elif chunksize in (1, 2, 3, 4, 6): assert preferred_method == "cohorts" assert len(chunks_cohorts) == 12 // chunksize else: assert preferred_method == "map-reduce" assert chunks_cohorts == {} @requires_dask @pytest.mark.parametrize("ndim", [1, 2, 3]) def test_single_chunk_method_is_blockwise(ndim): for by_ndim in range(1, ndim + 1): chunks = (5,) * (ndim - by_ndim) + (-1,) * by_ndim assert len(chunks) == ndim array = dask.array.ones(shape=(10,) * ndim, chunks=chunks) by = np.zeros(shape=(10,) * by_ndim, dtype=int) method, chunks_cohorts = find_group_cohorts( by, chunks=[array.chunks[ax] for ax in range(-by.ndim, 0)] ) assert method == "blockwise" assert chunks_cohorts == {(0,): [0]} @requires_dask @pytest.mark.parametrize( "chunk_at,expected", [ [1, ((1, 6, 1, 6, 1, 6, 1, 6, 1, 1),)], [0, ((7, 7, 7, 7, 2),)], [3, ((3, 4, 3, 4, 3, 4, 3, 4, 2),)], ], ) def test_rechunk_for_cohorts(chunk_at, expected): array = dask.array.ones((30,), chunks=7) labels = np.arange(0, 30) % 7 rechunked = rechunk_for_cohorts(array, axis=-1, force_new_chunk_at=chunk_at, labels=labels) assert rechunked.chunks == expected @pytest.mark.parametrize("chunks", [None, pytest.param(3, marks=requires_dask)]) @pytest.mark.parametrize("fill_value", [123, np.nan]) @pytest.mark.parametrize("func", ALL_FUNCS) def test_fill_value_behaviour(func, chunks, fill_value, engine): # fill_value = np.nan tests promotion of int counts to float # This is used by xarray if (func in ["all", "any"] or "arg" in func) or func in BLOCKWISE_FUNCS: pytest.skip() npfunc = _get_array_func(func) by = np.array([1, 2, 3, 1, 2, 3]) array = np.array([np.nan, 1, 1, np.nan, 1, 1]) if chunks: array = dask.array.from_array(array, chunks) actual, _ = groupby_reduce( array, by, func=func, engine=engine, fill_value=fill_value, expected_groups=[0, 1, 2, 3], ) expected = np.array([fill_value, fill_value, npfunc([1.0, 1.0], axis=0), npfunc([1.0, 1.0], axis=0)]) assert_equal(actual, expected) @requires_dask @pytest.mark.parametrize("func", ["mean", "sum"]) @pytest.mark.parametrize("dtype", ["float32", "float64", "int32", "int64"]) def test_dtype_preservation(dtype, func, engine): if engine == "numbagg": # https://github.com/numbagg/numbagg/issues/121 pytest.skip() if func == "sum": expected = dtypes._maybe_promote_int(dtype) elif func == "mean" and "int" in dtype: expected = np.float64 else: expected = np.dtype(dtype) array = np.ones((20,), dtype=dtype) by = np.ones(array.shape, dtype=int) actual, _ = groupby_reduce(array, by, func=func, engine=engine) assert actual.dtype == expected array = dask.array.from_array(array, chunks=(4,)) actual, _ = groupby_reduce(array, by, func=func, engine=engine) assert actual.dtype == expected @requires_dask @pytest.mark.parametrize("dtype", [np.float32, np.float64, np.int32, np.int64]) @pytest.mark.parametrize("labels_dtype", [np.float32, np.float64, np.int32, np.int64]) @pytest.mark.parametrize("method", ["map-reduce", "cohorts"]) def test_cohorts_map_reduce_consistent_dtypes(method, dtype, labels_dtype): repeats = np.array([4, 4, 12, 2, 3, 4], dtype=np.int32) labels = np.repeat(np.arange(6, dtype=labels_dtype), repeats) array = dask.array.from_array(labels.astype(dtype), chunks=(4, 8, 4, 9, 4)) actual, actual_groups = groupby_reduce(array, labels, func="count", method=method) assert_equal(actual_groups, np.arange(6, dtype=labels.dtype)) assert_equal(actual, repeats.astype(np.intp)) actual, actual_groups = groupby_reduce(array, labels, func="sum", method=method) assert_equal(actual_groups, np.arange(6, dtype=labels.dtype)) expect_dtype = dtypes._maybe_promote_int(dtype) assert_equal(actual, np.array([0, 4, 24, 6, 12, 20], dtype=expect_dtype)) @requires_dask @pytest.mark.parametrize("func", ALL_FUNCS) @pytest.mark.parametrize("axis", (-1, None)) @pytest.mark.parametrize("method", ["blockwise", "cohorts", "map-reduce"]) def test_cohorts_nd_by(func, method, axis, engine): if ( ("arg" in func and (axis is None or engine in ["flox", "numbagg"])) or (method != "blockwise" and func in BLOCKWISE_FUNCS) or (axis is None and ("first" in func or "last" in func)) ): pytest.skip() if axis is not None and method != "map-reduce": pytest.xfail() o = dask.array.ones((3,), chunks=-1) o2 = dask.array.ones((2, 3), chunks=-1) array = dask.array.block([[o, 2 * o], [3 * o2, 4 * o2]]) by = array.compute().astype(np.int64) by[0, 1] = 30 by[2, 1] = 40 by[0, 4] = 31 array = np.broadcast_to(array, (2, 3) + array.shape) if func in ["any", "all"]: fill_value = False else: fill_value = -123 kwargs = dict(func=func, engine=engine, method=method, axis=axis, fill_value=fill_value) if "quantile" in func: kwargs["finalize_kwargs"] = {"q": DEFAULT_QUANTILE} actual, groups = groupby_reduce(array, by, **kwargs) expected, sorted_groups = groupby_reduce(array.compute(), by, **kwargs) assert_equal(groups, sorted_groups) assert_equal(actual, expected) actual, groups = groupby_reduce(array, by, sort=False, **kwargs) assert_equal(groups, np.array([1, 30, 2, 31, 3, 4, 40], dtype=np.int64)) reindexed = reindex_(actual, groups, pd.Index(sorted_groups)) assert_equal(reindexed, expected) @pytest.mark.parametrize("func", ["sum", "count"]) @pytest.mark.parametrize("fill_value, expected", ((0, np.integer), (np.nan, np.floating))) def test_dtype_promotion(func, fill_value, expected, engine): array = np.array([1, 1]) by = [0, 1] actual, _ = groupby_reduce( array, by, func=func, expected_groups=[1, 2], fill_value=fill_value, engine=engine, ) assert np.issubdtype(actual.dtype, expected) @pytest.mark.parametrize("func", ["mean", "nanmean"]) def test_empty_bins(func, engine): array = np.ones((2, 3, 2)) by = np.broadcast_to([0, 1], array.shape) actual, _ = groupby_reduce( array, by, func=func, expected_groups=[-1, 0, 1, 2], isbin=True, engine=engine, axis=(0, 1, 2), ) expected = np.array([1.0, 1.0, np.nan]) assert_equal(actual, expected) def test_datetime_binning(): time_bins = pd.date_range(start="2010-08-01", end="2010-08-15", freq="24h") by = pd.date_range("2010-08-01", "2010-08-15", freq="15min") (actual,) = _convert_expected_groups_to_index((time_bins,), isbin=(True,), sort=False) expected = pd.IntervalIndex.from_arrays(time_bins[:-1], time_bins[1:]) assert_equal(actual, expected) ret = factorize_((by.to_numpy(),), axes=(0,), expected_groups=(actual,)) group_idx = ret[0] # Ignore pd.cut's dtype as it won't match np.digitize: expected = pd.cut(by, time_bins).codes.copy().astype(group_idx.dtype) expected[0] = 14 # factorize doesn't return -1 for nans assert_equal(group_idx, expected) @pytest.mark.parametrize("func", ALL_FUNCS) def test_bool_reductions(func, engine): if "arg" in func and engine == "flox": pytest.skip() if "quantile" in func or "mode" in func: pytest.skip() groups = np.array([1, 1, 1]) data = np.array([True, True, False]) npfunc = _get_array_func(func) expected = np.expand_dims(npfunc(data, axis=0), -1) actual, _ = groupby_reduce(data, groups, func=func, engine=engine) assert_equal(expected, actual) @requires_dask def test_map_reduce_blockwise_mixed() -> None: t = pd.date_range("2000-01-01", "2000-12-31", freq="D").to_series() data = t.dt.dayofyear actual, *_ = groupby_reduce( dask.array.from_array(data.values, chunks=365), t.dt.month, func="mean", method="map-reduce", ) expected, *_ = groupby_reduce(data, t.dt.month, func="mean") assert_equal(expected, actual) @requires_dask @pytest.mark.parametrize("method", ["blockwise", "map-reduce", "cohorts"]) def test_group_by_datetime(engine, method): kwargs = dict( func="mean", method=method, engine=engine, ) t = pd.date_range("2000-01-01", "2000-12-31", freq="D").to_series() data = t.dt.dayofyear daskarray = dask.array.from_array(data.values, chunks=30) actual, _ = groupby_reduce(daskarray, t, **kwargs) expected = data.to_numpy().astype(float) assert_equal(expected, actual) if method == "blockwise": return None edges = pd.date_range("1999-12-31", "2000-12-31", freq="ME").to_series().to_numpy() actual, _ = groupby_reduce(daskarray, t.to_numpy(), isbin=True, expected_groups=edges, **kwargs) expected = data.resample("ME").mean().to_numpy() assert_equal(expected, actual) actual, _ = groupby_reduce( np.broadcast_to(daskarray, (2, 3, daskarray.shape[-1])), t.to_numpy(), isbin=True, expected_groups=edges, **kwargs, ) expected = np.broadcast_to(expected, (2, 3, expected.shape[-1])) assert_equal(expected, actual) @requires_cubed @pytest.mark.parametrize("method", ["blockwise", "map-reduce"]) def test_group_by_datetime_cubed(engine, method): kwargs = dict( func="mean", method=method, engine=engine, ) t = pd.date_range("2000-01-01", "2000-12-31", freq="D").to_series() data = t.dt.dayofyear cubedarray = cubed.from_array(data.values, chunks=30) actual, _ = groupby_reduce(cubedarray, t, **kwargs) expected = data.to_numpy().astype(float) assert_equal(expected, actual) edges = pd.date_range("1999-12-31", "2000-12-31", freq="ME").to_series().to_numpy() actual, _ = groupby_reduce(cubedarray, t.to_numpy(), isbin=True, expected_groups=edges, **kwargs) expected = data.resample("ME").mean().to_numpy() assert_equal(expected, actual) actual, _ = groupby_reduce( cubed.array_api.broadcast_to(cubedarray, (2, 3, cubedarray.shape[-1])), t.to_numpy(), isbin=True, expected_groups=edges, **kwargs, ) expected = np.broadcast_to(expected, (2, 3, expected.shape[-1])) assert_equal(expected, actual) def test_factorize_values_outside_bins(): # pd.factorize returns intp vals = factorize_( (np.arange(10).reshape(5, 2), np.arange(10).reshape(5, 2)), axes=(0, 1), expected_groups=( pd.IntervalIndex.from_breaks(np.arange(2, 8, 1)), pd.IntervalIndex.from_breaks(np.arange(2, 8, 1)), ), reindex=True, fastpath=True, ) actual = vals[0] expected = np.array([[-1, -1], [-1, 0], [6, 12], [18, 24], [-1, -1]], np.intp) assert_equal(expected, actual) @pytest.mark.parametrize("chunk", [pytest.param(True, marks=requires_dask), False]) def test_multiple_groupers_bins(chunk) -> None: xp = dask.array if chunk else np array_kwargs = {"chunks": 2} if chunk else {} array = xp.ones((5, 2), **array_kwargs, dtype=np.int64) actual, *_ = groupby_reduce( array, np.arange(10).reshape(5, 2), xp.arange(10).reshape(5, 2), axis=(0, 1), expected_groups=( pd.IntervalIndex.from_breaks(np.arange(2, 8, 1)), pd.IntervalIndex.from_breaks(np.arange(2, 8, 1)), ), func="count", ) # output from `count` is intp expected = np.eye(5, 5, dtype=np.intp) assert_equal(expected, actual) @pytest.mark.parametrize("expected_groups", [None, (np.arange(5), [2, 3]), (None, [2, 3])]) @pytest.mark.parametrize("by1", [np.arange(5)[:, None], np.broadcast_to(np.arange(5)[:, None], (5, 2))]) @pytest.mark.parametrize( "by2", [ np.arange(2, 4).reshape(1, 2), np.broadcast_to(np.arange(2, 4).reshape(1, 2), (5, 2)), np.arange(2, 4).reshape(1, 2), ], ) @pytest.mark.parametrize("chunk", [pytest.param(True, marks=requires_dask), False]) def test_multiple_groupers(chunk, by1, by2, expected_groups) -> None: if chunk and expected_groups is None: pytest.skip() xp = dask.array if chunk else np array_kwargs = {"chunks": 2} if chunk else {} array = xp.ones((5, 2), **array_kwargs, dtype=np.int64) if chunk: by2 = dask.array.from_array(by2) # output from `count` is intp expected = np.ones((5, 2), dtype=np.intp) actual, *_ = groupby_reduce(array, by1, by2, axis=(0, 1), func="count", expected_groups=expected_groups) assert_equal(expected, actual) @pytest.mark.parametrize( "expected_groups", ( [None, None, None], (None,), ), ) def test_validate_expected_groups(expected_groups): with pytest.raises(ValueError): groupby_reduce( np.ones((10,)), np.ones((10,)), np.ones((10,)), expected_groups=expected_groups, func="mean", ) @requires_dask def test_validate_expected_groups_not_none_dask() -> None: with pytest.raises(ValueError): groupby_reduce( dask.array.ones((5, 2)), np.arange(10).reshape(5, 2), dask.array.arange(10).reshape(5, 2), axis=(0, 1), expected_groups=None, func="count", ) def test_factorize_reindex_sorting_strings(): # pd.factorize seems to return intp so int32 on 32bit arch kwargs = dict( by=(np.array(["El-Nino", "La-Nina", "boo", "Neutral"]),), axes=(-1,), expected_groups=(np.array(["El-Nino", "Neutral", "foo", "La-Nina"]),), ) expected = factorize_(**kwargs, reindex=True, sort=True)[0] assert_equal(expected, np.array([0, 1, 4, 2], dtype=np.intp)) expected = factorize_(**kwargs, reindex=True, sort=False)[0] assert_equal(expected, np.array([0, 3, 4, 1], dtype=np.intp)) expected = factorize_(**kwargs, reindex=False, sort=False)[0] assert_equal(expected, np.array([0, 1, 2, 3], dtype=np.intp)) expected = factorize_(**kwargs, reindex=False, sort=True)[0] assert_equal(expected, np.array([0, 1, 3, 2], dtype=np.intp)) def test_factorize_reindex_sorting_ints(): # pd.factorize seems to return intp so int32 on 32bit arch kwargs = dict( by=(np.array([-10, 1, 10, 2, 3, 5]),), axes=(-1,), expected_groups=(np.array([0, 1, 2, 3, 4, 5], np.int64),), ) expected = factorize_(**kwargs, reindex=True, sort=True)[0] assert_equal(expected, np.array([6, 1, 6, 2, 3, 5], dtype=np.intp)) expected = factorize_(**kwargs, reindex=True, sort=False)[0] assert_equal(expected, np.array([6, 1, 6, 2, 3, 5], dtype=np.intp)) kwargs["expected_groups"] = (np.arange(5, -1, -1),) expected = factorize_(**kwargs, reindex=True, sort=True)[0] assert_equal(expected, np.array([6, 1, 6, 2, 3, 5], dtype=np.intp)) expected = factorize_(**kwargs, reindex=True, sort=False)[0] assert_equal(expected, np.array([6, 4, 6, 3, 2, 0], dtype=np.intp)) @requires_dask def test_custom_aggregation_blockwise(): def grouped_median(group_idx, array, *, axis=-1, size=None, fill_value=None, dtype=None): return aggregate( group_idx, array, func=np.median, axis=axis, size=size, fill_value=fill_value, dtype=dtype, ) agg_median = Aggregation(name="median", numpy=grouped_median, fill_value=-1, chunk=None, combine=None) array = np.arange(100, dtype=np.float32).reshape(5, 20) by = np.ones((20,)) actual, _ = groupby_reduce(array, by, func=agg_median, axis=-1) expected = np.median(array, axis=-1, keepdims=True) assert_equal(expected, actual) for method in ["map-reduce", "cohorts"]: with pytest.raises(NotImplementedError): groupby_reduce( dask.array.from_array(array, chunks=(1, -1)), by, func=agg_median, axis=-1, method=method, ) actual, _ = groupby_reduce( dask.array.from_array(array, chunks=(1, -1)), by, func=agg_median, axis=-1, method="blockwise", ) assert_equal(expected, actual) @pytest.mark.parametrize("func", ALL_FUNCS) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_dtype(func, dtype, engine): if engine == "numbagg": # https://github.com/numbagg/numbagg/issues/121 pytest.skip() if "arg" in func or func in ["any", "all"]: pytest.skip() finalize_kwargs = {"q": DEFAULT_QUANTILE} if "quantile" in func else {} arr = np.ones((4, 12), dtype=dtype) labels = np.array(["a", "a", "c", "c", "c", "b", "b", "c", "c", "b", "b", "f"]) actual, _ = groupby_reduce( arr, labels, func=func, dtype=np.float64, engine=engine, finalize_kwargs=finalize_kwargs, ) assert actual.dtype == np.dtype("float64") @requires_dask @pytest.mark.parametrize( "flatblocks, expected", ( ((0, 1, 2, 3, 4), (slice(None),)), ((1, 2, 3), (slice(1, 4),)), ((1, 3), ([1, 3],)), ((0, 1, 3), ([0, 1, 3],)), ), ) def test_normalize_block_indexing_1d(flatblocks, expected): nblocks = 5 array = dask.array.ones((nblocks,), chunks=(1,)) expected = tuple(np.array(i) if isinstance(i, list) else i for i in expected) actual = _normalize_indexes(array.ndim, flatblocks, array.blocks.shape) assert_equal_tuple(expected, actual) @requires_dask @pytest.mark.parametrize( "flatblocks, expected", ( ((0, 1, 2, 3, 4), (0, slice(None))), ((1, 2, 3), (0, slice(1, 4))), ((1, 3), (0, [1, 3])), ((0, 1, 3), (0, [0, 1, 3])), (tuple(range(10)), (slice(None, 2), slice(None))), ((0, 1, 3, 5, 6, 8), (slice(None, 2), [0, 1, 3])), ((0, 3, 4, 5, 6, 8, 24), np.ix_([0, 1, 4], [0, 1, 3, 4])), ), ) def test_normalize_block_indexing_2d(flatblocks: tuple[int, ...], expected: tuple[Any, ...]) -> None: nblocks = 5 ndim = 2 array = dask.array.ones((nblocks,) * ndim, chunks=(1,) * ndim) expected = tuple(np.array(i) if isinstance(i, list) else i for i in expected) actual = _normalize_indexes(array.ndim, flatblocks, array.blocks.shape) assert_equal_tuple(expected, actual) @requires_dask def test_subset_blocks(): array = dask.array.random.random((120,), chunks=(4,)) blockid = (0, 3, 6, 9, 12, 15, 18, 21, 24, 27) subset = subset_to_blocks(array, blockid).to_array(array) assert subset.blocks.shape == (len(blockid),) @pytest.mark.skip("temporarily removed this optimization") @requires_dask def test_subset_block_passthrough(): from flox.core import identity # full slice pass through array = dask.array.ones((5,), chunks=(1,)) expected = dask.array.map_blocks(identity, array) subset = subset_to_blocks(array, np.arange(5)).to_array(array) assert subset.name == expected.name array = dask.array.ones((5, 5), chunks=1) expected = dask.array.map_blocks(identity, array) subset = subset_to_blocks(array, np.arange(25)).to_array(array) assert subset.name == expected.name @requires_dask @pytest.mark.parametrize( "flatblocks, expectidx", [ (np.arange(10), (slice(2), slice(None))), (np.arange(8), (slice(2), slice(None))), ([0, 10], ([0, 2], slice(1))), ([0, 7], (slice(2), [0, 2])), ([0, 7, 9], (slice(2), [0, 2, 4])), ([0, 6, 12, 14], (slice(3), [0, 1, 2, 4])), ([0, 12, 14, 19], np.ix_([0, 2, 3], [0, 2, 4])), ], ) def test_subset_block_2d(flatblocks, expectidx): array = dask.array.from_array(np.arange(25).reshape((5, 5)), chunks=1) subset = subset_to_blocks(array, flatblocks).to_array(array) assert len(subset.dask.layers) == 2 assert_equal(subset, array.compute()[expectidx]) @pytest.mark.parametrize( "dask_expected, reindex, func, expected_groups, any_by_dask", [ # argmax only False [False, None, "argmax", None, False], # True when by is numpy but expected is None [True, None, "sum", None, False], # False when by is dask but expected is None [False, None, "sum", None, True], # if expected_groups then always True [True, None, "sum", [1, 2, 3], False], [True, None, "sum", ([1], [2]), False], [True, None, "sum", ([1], [2]), True], [True, None, "sum", ([1], None), False], [True, None, "sum", ([1], None), True], ], ) def test_validate_reindex_map_reduce(dask_expected, reindex, func, expected_groups, any_by_dask) -> None: actual = _validate_reindex( reindex, func, "map-reduce", expected_groups, any_by_dask, is_dask_array=True, array_dtype=np.dtype("int32"), ) assert actual == ReindexStrategy(blockwise=dask_expected) # always reindex with all numpy inputs actual = _validate_reindex( reindex, func, "map-reduce", expected_groups, any_by_dask=False, is_dask_array=False, array_dtype=np.dtype("int32"), ) assert actual.blockwise actual = _validate_reindex( True, func, "map-reduce", expected_groups, any_by_dask=False, is_dask_array=False, array_dtype=np.dtype("int32"), ) assert actual.blockwise def test_validate_reindex() -> None: methods: list[T_Method] = ["map-reduce", "cohorts"] for method in methods: with pytest.raises(NotImplementedError): _validate_reindex( True, "argmax", method, expected_groups=None, any_by_dask=False, is_dask_array=True, array_dtype=np.dtype("int32"), ) methods: list[T_Method] = ["blockwise", "cohorts"] for method in methods: with pytest.raises(ValueError): _validate_reindex( True, "sum", method, expected_groups=None, any_by_dask=False, is_dask_array=True, array_dtype=np.dtype("int32"), ) for func in ["sum", "argmax"]: actual = _validate_reindex( None, func, method, expected_groups=None, any_by_dask=False, is_dask_array=True, array_dtype=np.dtype("int32"), ).blockwise assert actual is False with pytest.raises(ValueError): _validate_reindex( True, "sum", method="blockwise", expected_groups=np.array([1, 2, 3]), any_by_dask=False, is_dask_array=True, array_dtype=np.dtype("int32"), ) assert _validate_reindex( True, "sum", method="blockwise", expected_groups=np.array([1, 2, 3]), any_by_dask=True, is_dask_array=True, array_dtype=np.dtype("int32"), ).blockwise assert _validate_reindex( None, "sum", method="blockwise", expected_groups=np.array([1, 2, 3]), any_by_dask=True, is_dask_array=True, array_dtype=np.dtype("int32"), ).blockwise kwargs = dict( method="blockwise", expected_groups=np.array([1, 2, 3]), any_by_dask=True, is_dask_array=True, ) for func in ["nanfirst", "nanlast"]: assert not _validate_reindex(None, func, array_dtype=np.dtype("int32"), **kwargs).blockwise # type: ignore[arg-type] assert _validate_reindex(None, func, array_dtype=np.dtype("float32"), **kwargs).blockwise # type: ignore[arg-type] for func in ["first", "last"]: assert not _validate_reindex(None, func, array_dtype=np.dtype("int32"), **kwargs).blockwise # type: ignore[arg-type] assert not _validate_reindex(None, func, array_dtype=np.dtype("float32"), **kwargs).blockwise # type: ignore[arg-type] @requires_dask def test_1d_blockwise_sort_optimization() -> None: # Make sure for resampling problems sorting isn't done. time = pd.Series(pd.date_range("2020-09-01", "2020-12-31 23:59", freq="3h")) array = dask.array.ones((len(time),), chunks=(224,)) actual, *_ = groupby_reduce(array, time.dt.dayofyear.values, method="blockwise", func="count") assert all("getitem" not in k for k in actual.dask) actual, *_ = groupby_reduce( array, time.dt.dayofyear.values[::-1], sort=True, method="blockwise", func="count", ) assert any("getitem" in k for k in actual.dask.layers) actual, *_ = groupby_reduce( array, time.dt.dayofyear.values[::-1], sort=False, method="blockwise", func="count", ) assert all("getitem" not in k for k in actual.dask.layers) @requires_dask def test_negative_index_factorize_race_condition() -> None: # shape = (10, 2000) # chunks = ((shape[0]-1,1), 10) shape = (101, 174000) chunks = ((101,), 8760) eps = dask.array.random.random_sample(shape, chunks=chunks) N2 = dask.array.random.random_sample(shape, chunks=chunks) S2 = dask.array.random.random_sample(shape, chunks=chunks) bins = np.arange(-5, -2.05, 0.1) func = ["mean", "count", "sum"] out = [ groupby_reduce( eps, N2, S2, func=f, expected_groups=(bins, bins), isbin=(True, True), ) for f in func ] [dask.compute(out, scheduler="threads") for _ in range(5)] @pytest.mark.parametrize("sort", [True, False]) def test_expected_index_conversion_passthrough_range_index(sort) -> None: index = pd.RangeIndex(100) actual = _convert_expected_groups_to_index(expected_groups=(index,), isbin=(False,), sort=(sort,)) # type: ignore[call-overload] assert actual[0] is index def test_method_check_numpy() -> None: bins = [-2, -1, 0, 1, 2] field = np.ones((5, 3)) by = np.array([[-1.5, -1.5, 0.5, 1.5, 1.5] * 3]).reshape(5, 3) actual, *_ = groupby_reduce( field, by, expected_groups=pd.IntervalIndex.from_breaks(bins), func="count", method="cohorts", fill_value=np.nan, ) expected = np.array([6, np.nan, 3, 6]) assert_equal(actual, expected) actual, *_ = groupby_reduce( field, by, expected_groups=pd.IntervalIndex.from_breaks(bins), func="count", fill_value=np.nan, method="cohorts", axis=0, ) expected = np.array( [ [2.0, np.nan, 1.0, 2.0], [2.0, np.nan, 1.0, 2.0], [2.0, np.nan, 1.0, 2.0], ] ) assert_equal(actual, expected) @pytest.mark.parametrize("dtype", [None, np.float64]) def test_choose_engine(dtype) -> None: numbagg_possible = HAS_NUMBAGG and dtype is None default = "numbagg" if numbagg_possible else "numpy" mean = _initialize_aggregation( "mean", dtype=dtype, array_dtype=np.dtype("int64"), fill_value=0, min_count=0, finalize_kwargs=None, ) argmax = _initialize_aggregation( "argmax", dtype=dtype, array_dtype=np.dtype("int64"), fill_value=0, min_count=0, finalize_kwargs=None, ) # count_engine for method in ["all", "any", "count"]: agg = _initialize_aggregation( method, dtype=None, array_dtype=dtype, fill_value=0, min_count=0, finalize_kwargs=None, ) engine = _choose_engine(np.array([1, 1, 2, 2]), agg=agg) assert engine == ("numbagg" if HAS_NUMBAGG else "flox") # sorted by -> flox sorted_engine = _choose_engine(np.array([1, 1, 2, 2]), agg=mean) assert sorted_engine == ("numbagg" if numbagg_possible else "flox") # unsorted by -> numpy assert _choose_engine(np.array([3, 1, 1]), agg=mean) == default # argmax does not give engine="flox" assert _choose_engine(np.array([1, 1, 2, 2]), agg=argmax) == "numpy" def test_xarray_fill_value_behaviour() -> None: bar = np.array([1, 2, 3, np.nan, np.nan, np.nan, 4, 5, np.nan, np.nan]) times = np.arange(0, 20, 2) actual, *_ = groupby_reduce(bar, times, func="nansum", expected_groups=(np.arange(19),)) nan = np.nan # fmt: off expected = np.array( [ 1., nan, 2., nan, 3., nan, 0., nan, 0., nan, 0., nan, 4., nan, 5., nan, 0., nan, 0.] ) # fmt: on assert_equal(expected, actual) @pytest.mark.parametrize("q", (0.5, (0.5,), (0.5, 0.67, 0.85))) @pytest.mark.parametrize("func", ["nanquantile", "quantile"]) @pytest.mark.parametrize("chunk", [pytest.param(True, marks=requires_dask), False]) @pytest.mark.parametrize("by_ndim", [1, 2]) def test_multiple_quantiles(q, chunk, func, by_ndim) -> None: array = np.array([[1, -1, np.nan, 3, 4, 10, 5], [1, np.nan, np.nan, 3, 4, np.nan, np.nan]]) labels = np.array([0, 0, 0, 1, 0, 1, 1]) if by_ndim == 2: labels = np.broadcast_to(labels, (5, *labels.shape)) array = np.broadcast_to(np.expand_dims(array, -2), (2, 5, array.shape[-1])) axis = tuple(range(-by_ndim, 0)) if chunk: array = dask.array.from_array(array, chunks=(1,) + (-1,) * by_ndim) actual, *_ = groupby_reduce(array, labels, func=func, finalize_kwargs=dict(q=q), axis=axis) sorted_array = array[..., [0, 1, 2, 4, 3, 5, 6]] f = partial(getattr(np, func), q=q, axis=axis, keepdims=True) if chunk: sorted_array = sorted_array.compute() # type: ignore[attr-defined] expected = np.concatenate((f(sorted_array[..., :4]), f(sorted_array[..., 4:])), axis=-1) if by_ndim == 2: expected = expected.squeeze(axis=-2) assert_equal(expected, actual, tolerance={"atol": 1e-14}) @pytest.mark.parametrize("dtype", ["U3", "S3"]) def test_nanlen_string(dtype, engine) -> None: array = np.array(["ABC", "DEF", "GHI", "JKL", "MNO", "PQR"], dtype=dtype) by = np.array([0, 0, 1, 2, 1, 0]) expected = np.array([3, 2, 1], dtype=np.intp) actual, *_ = groupby_reduce(array, by, func="count", engine=engine) assert_equal(expected, actual) @pytest.mark.parametrize( "array", [ np.array([1, 1, 1, 2, 3, 4, 5], dtype=np.uint64), np.array([1, 1, 1, 2, np.nan, 4, 5], dtype=np.float64), ], ) @pytest.mark.parametrize("func", ["cumsum", "nancumsum"]) def test_cumsum_simple(array, func) -> None: by = np.array([0] * array.shape[-1]) expected = getattr(np, func)(array, axis=-1) actual = groupby_scan(array, by, func=func, axis=-1) assert_equal(actual, expected) if has_dask: da = dask.array.from_array(array, chunks=2) actual = groupby_scan(da, by, func=func, axis=-1) assert_equal(actual, expected) def test_cumsum() -> None: array = np.array( [ [1, 2, np.nan, 4, 5], [3, np.nan, 4, 6, 6], ] ) by = [0, 1, 1, 0, 1] expected = np.array( [ [1, 2, np.nan, 5, np.nan], [3, np.nan, np.nan, 9, np.nan], ] ) actual = groupby_scan(array, by, func="cumsum", axis=-1) assert_equal(actual, expected) if has_dask: da = dask.array.from_array(array, chunks=2) actual = groupby_scan(da, by, func="cumsum", axis=-1) assert_equal(actual, expected) expected = np.array([[1, 2, 2, 5, 7], [3, 0, 4, 9, 10]], dtype=np.float64) actual = groupby_scan(array, by, func="nancumsum", axis=-1) assert_equal(actual, expected) if has_dask: da = dask.array.from_array(array, chunks=2) actual = groupby_scan(da, by, func="nancumsum", axis=-1) assert_equal(actual, expected) @pytest.mark.parametrize( "chunks", [ pytest.param(-1, marks=requires_dask), pytest.param(3, marks=requires_dask), pytest.param(4, marks=requires_dask), ], ) @pytest.mark.parametrize("size", ((1, 12), (12,), (12, 9))) @pytest.mark.parametrize("add_nan_by", [True, False]) @pytest.mark.parametrize("func", ["ffill", "bfill"]) def test_ffill_bfill(chunks, size, add_nan_by, func) -> None: array, by = gen_array_by(size, func) if chunks: array = dask.array.from_array(array, chunks=chunks) if add_nan_by: by[0:3] = np.nan by = tuple(by) expected = flox.groupby_scan(array.compute(), by, func=func) actual = flox.groupby_scan(array, by, func=func) assert_equal(expected, actual) @requires_dask def test_blockwise_nans() -> None: array = dask.array.ones((1, 10), chunks=2) by = np.array([-1, 0, -1, 1, -1, 2, -1, 3, 4, 4]) actual, *actual_groups = flox.groupby_reduce(array, by, func="sum", expected_groups=pd.RangeIndex(0, 5)) expected, *expected_groups = flox.groupby_reduce( array.compute(), by, func="sum", expected_groups=pd.RangeIndex(0, 5) ) assert_equal(expected_groups, actual_groups) assert_equal(expected, actual) @requires_dask @pytest.mark.parametrize("func", ["nancumsum", "ffill", "bfill"]) @pytest.mark.parametrize("method", ["blockwise", "blelloch"]) def test_groupby_scan_method(func, method) -> None: """Test that groupby_scan works correctly with explicit method parameter.""" # Create array where groups fit within chunks (suitable for blockwise) # Include NaN values for ffill/bfill to actually test gap filling if "fill" in func: data = [1.0, np.nan, 3.0, 4.0, np.nan, 6.0] else: data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] array = dask.array.from_array(data, chunks=3) by = np.array([0, 0, 0, 1, 1, 1]) expected = groupby_scan(array.compute(), by, func=func, axis=-1) actual = groupby_scan(array, by, func=func, axis=-1, method=method) assert_equal(expected, actual) @requires_dask def test_groupby_scan_blockwise_auto_rechunk() -> None: """Test that blockwise scan auto-rechunks when groups are sorted but span chunks.""" from flox import scan from flox.rechunk import rechunk_for_blockwise as real_rechunk # Create array with sorted groups that span chunk boundaries array = dask.array.from_array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], chunks=2) by = np.array([0, 0, 0, 1, 1, 1]) # sorted, but group 0 spans chunks 0 and 1 expected = groupby_scan(array.compute(), by, func="nancumsum", axis=-1) # This should auto-rechunk to enable blockwise with patch.object(scan, "rechunk_for_blockwise", wraps=real_rechunk) as rechunk_spy: actual = groupby_scan(array, by, func="nancumsum", axis=-1) assert_equal(expected, actual) # Verify rechunk_for_blockwise was called assert rechunk_spy.call_count >= 1 # Explicit method="blockwise" should also rechunk and produce correct results with patch.object(scan, "rechunk_for_blockwise", wraps=real_rechunk) as rechunk_spy: actual_explicit = groupby_scan(array, by, func="nancumsum", axis=-1, method="blockwise") assert_equal(expected, actual_explicit) assert rechunk_spy.call_count >= 1 @pytest.mark.parametrize("func", ["sum", "prod", "count", "nansum"]) @pytest.mark.parametrize("engine", ["flox", "numpy"]) def test_agg_dtypes(func, engine) -> None: # regression test for GH388 counts = np.array([0, 2, 1, 0, 1]) group = np.array([1, 1, 1, 2, 2]) actual, *_ = groupby_reduce( counts, group, expected_groups=(np.array([1, 2]),), func=func, dtype="uint8", engine=engine ) expected = _get_array_func(func)(counts, dtype="uint8") assert actual.dtype == np.uint8 == expected.dtype @requires_dask def test_blockwise_avoid_rechunk() -> None: array = dask.array.zeros((6,), chunks=(2, 4), dtype=np.int64) by = np.array(["1", "1", "0", "", "0", ""], dtype=" None: # GH403 array = np.array([np.datetime64("2000-01-01"), np.datetime64("2000-01-02"), np.datetime64("2000-01-03")]) by = np.array([0, 0, 1]) actual, *_ = flox.groupby_reduce(array, by, func="nanmin", engine=engine) expected = array[[0, 2]] assert_equal(expected, actual) expected = array[[1, 2]] actual, *_ = flox.groupby_reduce(array, by, func="nanmax", engine=engine) assert_equal(expected, actual) @pytest.mark.parametrize("func", ["first", "last", "nanfirst", "nanlast"]) def test_datetime_timedelta_first_last(engine, func) -> None: idx = 0 if "first" in func else -1 idx1 = 2 if "first" in func else -1 ## datetime dt = pd.date_range("2001-01-01", freq="d", periods=5).values by = np.ones(dt.shape, dtype=int) actual, *_ = groupby_reduce(dt, by, func=func, engine=engine) assert_equal(actual, dt[[idx]]) # missing group by = np.array([0, 2, 3, 3, 3]) actual, *_ = groupby_reduce( dt, by, expected_groups=([0, 1, 2, 3],), func=func, engine=engine, fill_value=dtypes.NA ) assert_equal(actual, [dt[0], np.datetime64("NaT"), dt[1], dt[idx1]]) ## timedelta dt = dt - dt[0] by = np.ones(dt.shape, dtype=int) actual, *_ = groupby_reduce(dt, by, func=func, engine=engine) assert_equal(actual, dt[[idx]]) # missing group by = np.array([0, 2, 3, 3, 3]) actual, *_ = groupby_reduce( dt, by, expected_groups=([0, 1, 2, 3],), func=func, engine=engine, fill_value=dtypes.NA ) assert_equal(actual, [dt[0], np.timedelta64("NaT"), dt[1], dt[idx1]]) @requires_dask @requires_sparse @pytest.mark.xdist_group(name="sparse-group") @pytest.mark.parametrize("size", [2**62 - 1, 11]) def test_reindex_sparse(size): import sparse array = dask.array.ones((2, 12), chunks=(-1, 3)) func = "sum" expected_groups = pd.RangeIndex(size) by = dask.array.from_array(np.repeat(np.arange(6) * 2, 2), chunks=(3,)) dense = np.zeros((2, 11)) dense[..., np.arange(6) * 2] = 2 expected = sparse.COO.from_numpy(dense) with pytest.raises(ValueError): ReindexStrategy(blockwise=True, array_type=ReindexArrayType.SPARSE_COO) reindex = ReindexStrategy(blockwise=False, array_type=ReindexArrayType.SPARSE_COO) original_reindex = flox.core.reindex_ def mocked_reindex(*args, **kwargs): res = original_reindex(*args, **kwargs) if isinstance(res, dask.array.Array): assert isinstance(res._meta, sparse.COO) else: assert isinstance(res, sparse.COO) return res # Define the error-raising property def raise_error(self): raise AttributeError("Access to '_data' is not allowed.") with patch("flox.core.reindex_") as mocked_reindex_func: with patch.object(pd.RangeIndex, "_data", property(raise_error)): mocked_reindex_func.side_effect = mocked_reindex actual, *_ = groupby_reduce( array, by, func=func, reindex=reindex, expected_groups=expected_groups, fill_value=0 ) if size == 11: assert_equal(actual, expected) else: actual.compute() # just compute # once during graph construction, 10 times afterward assert mocked_reindex_func.call_count > 1 @requires_dask def test_sparse_errors(): call = partial( groupby_reduce, dask.array.from_array([1, 2, 3], chunks=(1,)), [0, 1, 1], reindex=REINDEX_SPARSE_STRAT, fill_value=0, expected_groups=[0, 1, 2], ) if not has_sparse: with pytest.raises(ImportError): call(func="sum") else: with pytest.raises(ValueError): call(func="first") @requires_sparse @pytest.mark.parametrize( "chunks", [ None, pytest.param(-1, marks=requires_dask), pytest.param(3, marks=requires_dask), pytest.param(4, marks=requires_dask), ], ) @pytest.mark.parametrize("shape", [(1, 12), (12,), (12, 9)]) @pytest.mark.parametrize("fill_value", [np.nan, 0]) @pytest.mark.parametrize("func", ["sum", "mean", "min", "max", "nansum", "nanmean", "nanmin", "nanmax"]) def test_sparse_nan_fill_value_reductions(chunks, fill_value, shape, func): import sparse numpy_array, by = gen_array_by(shape, func) array = sparse.COO.from_numpy(numpy_array, fill_value=fill_value) if chunks: array = dask.array.from_array(array, chunks=chunks) npfunc = _get_array_func(func) with warnings.catch_warnings(): warnings.filterwarnings("ignore", r"Mean of empty slice") warnings.filterwarnings("ignore", r"All-NaN slice encountered") # warnings.filterwarnings("ignore", r"encountered in divide") expected = np.expand_dims(npfunc(numpy_array, axis=-1), axis=-1) actual, *_ = groupby_reduce(array, by, func=func, axis=-1) assert_equal(actual, expected) @pytest.mark.parametrize("func", ("nanvar", "var")) @pytest.mark.parametrize( # Should fail at 10e8 for old algorithm, and survive 10e12 for current "exponent", (2, 4, 6, 8, 10, 12), ) def test_std_var_precision(func, exponent, engine): # Generate a dataset with small variance and big mean # Check that func with engine gives you the same answer as numpy size = 1000 offset = 10**exponent array = np.linspace(-1, 1, size) # has zero mean labels = np.arange(size) % 2 # Ideally we'd parametrize this too. # These two need to be the same function, but with the offset added and not added no_offset, _ = groupby_reduce(array, labels, engine=engine, func=func) with_offset, _ = groupby_reduce(array + offset, labels, engine=engine, func=func) expected = np.concatenate([np.nanvar(array[::2], keepdims=True), np.nanvar(array[1::2], keepdims=True)]) expected_offset = np.concatenate( [np.nanvar(array[::2] + offset, keepdims=True), np.nanvar(array[1::2] + offset, keepdims=True)] ) tol = {"rtol": 3e-8, "atol": 1e-9} # Not sure how stringent to be here assert_equal(expected, no_offset, tol) assert_equal(expected_offset, with_offset, tol) if exponent < 10: # TODO: figure this exponent limit # TODO: Failure threshold in my external tests is dependent on dask chunksize, # maybe needs exploring better? assert_equal(no_offset, with_offset, tol) @requires_sparse def test_sparse_is_supported_aggregation(): import sparse array = sparse.COO.from_numpy(np.array([1, 2, 3])) assert flox.is_supported_aggregation(array, "sum") assert not flox.is_supported_aggregation(array, "cumsum") assert not flox.is_supported_aggregation(array, "ffill") @requires_dask def test_dask_is_supported_aggregation(): # Test dask array wrapping numpy array = da.from_array(np.array([1, 2, 3]), chunks=2) assert flox.is_supported_aggregation(array, "sum") assert flox.is_supported_aggregation(array, "cumsum") assert flox.is_supported_aggregation(array, "ffill") # Test dask array wrapping sparse if has_sparse: import sparse sparse_array = sparse.COO.from_numpy(np.array([1, 2, 3])) dask_sparse = da.from_array(sparse_array, chunks=2) assert flox.is_supported_aggregation(dask_sparse, "sum") assert not flox.is_supported_aggregation(dask_sparse, "cumsum") assert not flox.is_supported_aggregation(dask_sparse, "ffill") @requires_cubed def test_cubed_is_supported_aggregation(): array = cubed.from_array(np.array([1, 2, 3]), chunks=2) assert flox.is_supported_aggregation(array, "sum") assert not flox.is_supported_aggregation(array, "cumsum") assert not flox.is_supported_aggregation(array, "ffill") def test_is_supported_aggregation_quantile_method(): array = np.array([1, 2, 3]) assert flox.is_supported_aggregation(array, "quantile") assert flox.is_supported_aggregation(array, "quantile", method="linear") assert not flox.is_supported_aggregation(array, "quantile", method="nearest") assert flox.is_supported_aggregation(array, "nanquantile") assert flox.is_supported_aggregation(array, "nanquantile", method="linear") assert not flox.is_supported_aggregation(array, "nanquantile", method="nearest") flox-0.11.2/tests/test_properties.py000066400000000000000000000275711514775624000175410ustar00rootroot00000000000000import warnings from collections.abc import Callable from typing import Any import pandas as pd import pytest pytest.importorskip("hypothesis") pytest.importorskip("dask") pytest.importorskip("sparse") pytest.importorskip("cftime") import dask import hypothesis.extra.numpy as npst import hypothesis.strategies as st import numpy as np from hypothesis import assume, given, note, settings import flox from flox.core import groupby_reduce from flox.lib import _is_sparse_supported_reduction, sparse_array_type from flox.scan import groupby_scan from flox.xrutils import ( _contains_cftime_datetimes, _to_pytimedelta, datetime_to_numeric, is_duck_dask_array, isnull, notnull, ) from . import BLOCKWISE_FUNCS, assert_equal, to_numpy from .strategies import ( all_arrays, by_arrays, chunked_arrays, func_st, numeric_dtypes, numeric_like_arrays, sparse_arrays, ) from .strategies import chunks as chunks_strategy dask.config.set(scheduler="sync") def ffill(array, axis, dtype=None): return flox.aggregate_flox.ffill(np.zeros(array.shape[-1], dtype=int), array, axis=axis) def bfill(array, axis, dtype=None): return flox.aggregate_flox.ffill( np.zeros(array.shape[-1], dtype=int), array[::-1], axis=axis, )[::-1] NUMPY_SCAN_FUNCS: dict[str, Callable] = { "cumsum": np.cumsum, "nancumsum": np.nancumsum, "ffill": ffill, "bfill": bfill, } def not_overflowing_array(array: np.ndarray[Any, Any]) -> bool: if array.dtype.kind in "Mm": array = array.view(np.int64) array = array.ravel() array = array[notnull(array)] if array.size == 0: return True if array.dtype.kind == "f": info = np.finfo(array.dtype) limit = 2 ** (info.nmant + 1) elif array.dtype.kind in ["i", "u"]: info = np.iinfo(array.dtype) # type: ignore[assignment] else: return True with warnings.catch_warnings(): warnings.simplefilter("ignore", RuntimeWarning) result = bool(np.all((array < info.max / array.size) & (array > info.min / array.size))) if array.dtype.kind == "f": result = result and bool(np.all(np.abs(array) < limit / array.size)) # note(f"returning {result}, {array.min()} vs {info.min}, {array.max()} vs {info.max}") return result @given( data=st.data(), array=st.one_of(all_arrays, chunked_arrays()), func=func_st, ) @settings(deadline=None) def test_groupby_reduce(data, array, func: str) -> None: # overflow behaviour differs between bincount and sum (for example) assume(not_overflowing_array(array)) # TODO: fix var for complex numbers upstream assume(not (("quantile" in func or "var" in func or "std" in func) and array.dtype.kind == "c")) assume(not ("quantile" in func and array.dtype.kind == "b")) # arg* with nans in array are weird assume("arg" not in func and not np.any(isnull(array).ravel())) # TODO: funny bugs with overflows here is_cftime = _contains_cftime_datetimes(array) assume( not ( is_cftime and func in ["prod", "nanprod", "var", "nanvar", "std", "nanstd", "quantile", "nanquantile"] ) ) axis = -1 by = data.draw( by_arrays( elements={ "alphabet": st.just("a"), "min_value": 1, "max_value": 1, "min_size": 1, "max_size": 1, }, shape=st.just((array.shape[-1],)), ) ) if func in BLOCKWISE_FUNCS and isinstance(array, dask.array.Array): array = array.rechunk({axis: -1}) assert len(np.unique(by)) == 1 kwargs = {"q": 0.8} if "quantile" in func else {} flox_kwargs: dict[str, Any] = {} with np.errstate(invalid="ignore", divide="ignore"): actual, *_ = groupby_reduce( array, by, func=func, axis=axis, engine="numpy", **flox_kwargs, finalize_kwargs=kwargs, ) # numpy-groupies always does the calculation in float64 if ( ("var" in func or "std" in func or "sum" in func or "mean" in func or "quantile" in func) and array.dtype.kind == "f" and array.dtype.itemsize != 8 ): # bincount always accumulates in float64, # casting to float64 handles std more like npg does. # Setting dtype=float64 works fine for sum, mean. cast_to = array.dtype array = array.astype(np.float64) note(f"casting array to float64, cast_to={cast_to!r}") else: cast_to = None if array.dtype.kind in "Mm": array = array.view(np.int64) cast_to = array.dtype elif is_cftime: offset = array.min() array = datetime_to_numeric(array, offset, datetime_unit="us") note(("kwargs:", kwargs, "cast_to:", cast_to)) expected = getattr(np, func)(array, axis=axis, keepdims=True, **kwargs) if cast_to is not None: note(("casting to:", cast_to)) expected = expected.astype(cast_to) actual = actual.astype(cast_to) if is_cftime: expected = _to_pytimedelta(expected, unit="us") + offset note(("expected: ", expected, "actual: ", actual)) tolerance = {"atol": 1e-15} assert_equal(expected, actual, tolerance) @settings(deadline=None) @given( data=st.data(), array=chunked_arrays(arrays=numeric_like_arrays | sparse_arrays()) | sparse_arrays(), func=func_st, ) def test_groupby_reduce_numpy_vs_other(data, array, func: str) -> None: if ( isinstance(array, sparse_array_type) or (is_duck_dask_array(array) and isinstance(array._meta, sparse_array_type)) and not _is_sparse_supported_reduction(func) ): assume(False) numpy_array = to_numpy(array) # overflow behaviour differs between bincount and sum (for example) assume(not_overflowing_array(numpy_array)) # TODO: fix var for complex numbers upstream assume(not (("quantile" in func or "var" in func or "std" in func) and array.dtype.kind == "c")) # # arg* with nans in array are weird assume("arg" not in func and not np.any(isnull(numpy_array.ravel()))) if hasattr(array, "rechunk") and func in ["nanmedian", "nanquantile", "median", "quantile"]: array = array.rechunk({-1: -1}) axis = -1 by = data.draw(by_arrays(shape=st.just((array.shape[-1],)))) kwargs = {"q": 0.8} if "quantile" in func else {} flox_kwargs: dict[str, Any] = {} kwargs = dict( func=func, axis=axis, engine="numpy", **flox_kwargs, finalize_kwargs=kwargs, ) result_other, *_ = groupby_reduce(array, by, **kwargs) result_numpy, *_ = groupby_reduce(numpy_array, by, **kwargs) assert isinstance(result_other, type(array)) assert_equal(result_other, result_numpy) @given( data=st.data(), array=chunked_arrays(arrays=numeric_like_arrays), func=st.sampled_from(tuple(NUMPY_SCAN_FUNCS)), ) def test_scans_against_numpy(data, array: dask.array.Array, func: str) -> None: if "cum" in func: assume(not_overflowing_array(np.asarray(array))) by = data.draw(by_arrays(shape=st.just((array.shape[-1],)))) axis = array.ndim - 1 # Too many float32 edge-cases! if "cum" in func and array.dtype.kind == "f" and array.dtype.itemsize == 4: assume(False) numpy_array = array.compute() if numpy_array.dtype.kind not in "Mm": assume((np.abs(numpy_array) < 2**53).all()) if numpy_array.dtype.kind in "Mm": dtype = numpy_array.dtype asnumeric = numpy_array.view(np.int64) else: asnumeric = numpy_array dtype = NUMPY_SCAN_FUNCS[func](asnumeric[..., [0]], axis=axis).dtype expected = np.empty_like(numpy_array, dtype=dtype) group_idx, uniques = pd.factorize(by) for i in range(len(uniques)): mask = group_idx == i if not mask.any(): note((by, group_idx, uniques)) raise ValueError expected[..., mask] = NUMPY_SCAN_FUNCS[func](asnumeric[..., mask], axis=axis) if dtype: expected = expected.astype(dtype) note((numpy_array, group_idx, array.chunks)) tolerance = {"rtol": 1e-13, "atol": 1e-15} actual = groupby_scan(numpy_array, by, func=func, axis=-1, dtype=dtype) assert_equal(actual, expected, tolerance) actual = groupby_scan(array, by, func=func, axis=-1, dtype=dtype) assert_equal(actual, expected, tolerance) @given(data=st.data(), array=chunked_arrays()) def test_ffill_bfill_reverse(data, array: dask.array.Array) -> None: by = data.draw(by_arrays(shape=st.just((array.shape[-1],)))) def reverse(arr): return arr[..., ::-1] forward = groupby_scan(array, by, func="ffill") as_numpy = groupby_scan(array.compute(), by, func="ffill") assert_equal(forward, as_numpy) backward = groupby_scan(array, by, func="bfill") as_numpy = groupby_scan(array.compute(), by, func="bfill") assert_equal(backward, as_numpy) backward_reversed = reverse(groupby_scan(reverse(array), reverse(by), func="bfill")) assert_equal(forward, backward_reversed) forward_reversed = reverse(groupby_scan(reverse(array), reverse(by), func="ffill")) assert_equal(forward_reversed, backward) @given( data=st.data(), array=chunked_arrays(), func=st.sampled_from(["first", "last", "nanfirst", "nanlast"]), ) def test_first_last(data, array: dask.array.Array, func: str) -> None: by = data.draw(by_arrays(shape=st.just((array.shape[-1],)))) INVERSES = { "first": "last", "last": "first", "nanfirst": "nanlast", "nanlast": "nanfirst", } MATES = { "first": "nanfirst", "last": "nanlast", "nanfirst": "first", "nanlast": "last", } inverse = INVERSES[func] mate = MATES[func] if func in ["first", "last"]: array = array.rechunk((*array.chunks[:-1], -1)) for arr in [array, array.compute()]: forward, *fg = groupby_reduce(arr, by, func=func, engine="flox") reverse, *rg = groupby_reduce(arr[..., ::-1], by[..., ::-1], func=inverse, engine="flox") assert forward.dtype == reverse.dtype assert forward.dtype == arr.dtype assert_equal(fg, rg) assert_equal(forward, reverse) if arr.dtype.kind == "f" and not isnull(array.compute()).any(): if mate in ["first", "last"]: array = array.rechunk((*array.chunks[:-1], -1)) first, *_ = groupby_reduce(array, by, func=func, engine="flox") second, *_ = groupby_reduce(array, by, func=mate, engine="flox") assert_equal(first, second) @given(data=st.data(), func=st.sampled_from(["nanfirst", "nanlast"])) def test_first_last_useless(data, func): shape = data.draw(npst.array_shapes()) by = data.draw(by_arrays(shape=st.just(shape[slice(-1, None)]))) chunks = data.draw(chunks_strategy(shape=shape)) array = np.zeros(shape, dtype=np.int8) if chunks is not None: array = dask.array.from_array(array, chunks=chunks) actual, groups = groupby_reduce(array, by, axis=-1, func=func, engine="numpy") expected = np.zeros(shape[:-1] + (len(groups),), dtype=array.dtype) assert_equal(actual, expected) @given( func=st.sampled_from(["sum", "prod", "nansum", "nanprod"]), engine=st.sampled_from(["numpy", "flox"]), array_dtype=st.none() | numeric_dtypes, dtype=st.none() | numeric_dtypes, ) def test_agg_dtype_specified(func, array_dtype, dtype, engine): # regression test for GH388 counts = np.array([0, 2, 1, 0, 1], dtype=array_dtype) group = np.array([1, 1, 1, 2, 2]) actual, _ = groupby_reduce( counts, group, expected_groups=(np.array([1, 2]),), func=func, dtype=dtype, engine=engine, ) expected = getattr(np, func)(counts, keepdims=True, dtype=dtype) assert actual.dtype == expected.dtype flox-0.11.2/tests/test_xarray.py000066400000000000000000000672031514775624000166470ustar00rootroot00000000000000import numpy as np import pandas as pd import pytest # isort: off xr = pytest.importorskip("xarray") # isort: on from flox import is_supported_aggregation from flox import xrdtypes as dtypes from flox.xarray import rechunk_for_blockwise, xarray_reduce from . import ( ALL_FUNCS, assert_equal, has_dask, raise_if_dask_computes, requires_cftime, requires_dask, ) if has_dask: import dask dask.config.set(scheduler="sync") # test against legacy xarray implementation # avoid some compilation overhead xr.set_options(use_flox=False, use_numbagg=False, use_bottleneck=False) tolerance64 = {"rtol": 1e-15, "atol": 1e-18} np.random.seed(123) @pytest.mark.parametrize("reindex", [None, False, True]) @pytest.mark.parametrize("min_count", [None, 1, 3]) @pytest.mark.parametrize("add_nan", [True, False]) @pytest.mark.parametrize("skipna", [True, False]) def test_xarray_reduce(skipna, add_nan, min_count, engine, reindex): if skipna is False and min_count is not None: pytest.skip() arr = np.ones((4, 12)) if add_nan: arr[1, ...] = np.nan arr[[0, 2], [3, 4]] = np.nan labels = np.array(["a", "a", "c", "c", "c", "b", "b", "c", "c", "b", "b", "f"]) labels = np.array(labels) labels2 = np.array([1, 2, 2, 1]) da = xr.DataArray( arr, dims=("x", "y"), coords={"labels2": ("x", labels2), "labels": ("y", labels)}, ).expand_dims(z=4) expected = da.groupby("labels").sum(skipna=skipna, min_count=min_count) actual = xarray_reduce( da, "labels", func="sum", skipna=skipna, min_count=min_count, engine=engine, reindex=reindex, ) assert_equal(expected, actual) da["labels2"] = da.labels2.astype(float) da["labels2"][0] = np.nan expected = da.groupby("labels2").sum(skipna=skipna, min_count=min_count) actual = xarray_reduce( da, "labels2", func="sum", skipna=skipna, min_count=min_count, engine=engine, reindex=reindex, ) assert_equal(expected, actual) # test dimension ordering # actual = xarray_reduce( # da.transpose("y", ...), "labels", func="sum", skipna=skipna, min_count=min_count # ) # assert_equal(expected, actual) # TODO: sort @pytest.mark.parametrize("pass_expected_groups", [True, False]) @pytest.mark.parametrize("chunk", (pytest.param(True, marks=requires_dask), False)) def test_xarray_reduce_multiple_groupers(pass_expected_groups, chunk, engine): if chunk and pass_expected_groups is False: pytest.skip() arr = np.ones((4, 12)) labels = np.array(["a", "a", "c", "c", "c", "b", "b", "c", "c", "b", "b", "f"]) labels2 = np.array([1, 2, 2, 1]) da = xr.DataArray( arr, dims=("x", "y"), coords={"labels2": ("x", labels2), "labels": ("y", labels)}, ).expand_dims(z=4) if chunk: da = da.chunk({"x": 2, "z": 1}) expected = xr.DataArray( [[4, 4], [8, 8], [10, 10], [2, 2]], dims=("labels", "labels2"), coords={"labels": ["a", "b", "c", "f"], "labels2": [1, 2]}, ).expand_dims(z=4) kwargs = dict(func="count", engine=engine) if pass_expected_groups: kwargs["expected_groups"] = (expected.labels.data, expected.labels2.data) with raise_if_dask_computes(): actual = xarray_reduce(da, da.labels, da.labels2, **kwargs) xr.testing.assert_identical(expected, actual) with raise_if_dask_computes(): actual = xarray_reduce(da, "labels", da.labels2, **kwargs) xr.testing.assert_identical(expected, actual) with raise_if_dask_computes(): actual = xarray_reduce(da, "labels", "labels2", **kwargs) xr.testing.assert_identical(expected, actual) if pass_expected_groups: kwargs["expected_groups"] = (expected.labels2.data, expected.labels.data) with raise_if_dask_computes(): actual = xarray_reduce(da, "labels2", "labels", **kwargs) xr.testing.assert_identical(expected.transpose("z", "labels2", "labels"), actual) @pytest.mark.parametrize("pass_expected_groups", [True, False]) @pytest.mark.parametrize("chunk", (pytest.param(True, marks=requires_dask), False)) def test_xarray_reduce_multiple_groupers_2(pass_expected_groups, chunk, engine): if chunk and pass_expected_groups is False: pytest.skip() arr = np.ones((2, 12)) labels = np.array(["a", "a", "c", "c", "c", "b", "b", "c", "c", "b", "b", "f"]) da = xr.DataArray( arr, dims=("x", "y"), coords={"labels2": ("y", labels), "labels": ("y", labels)} ).expand_dims(z=4) if chunk: da = da.chunk({"x": 2, "z": 1}) expected = xr.DataArray( [[2, 0, 0, 0], [0, 4, 0, 0], [0, 0, 5, 0], [0, 0, 0, 1]], dims=("labels", "labels2"), coords={ "labels": ["a", "b", "c", "f"], "labels2": ["a", "b", "c", "f"], }, ).expand_dims(z=4, x=2) kwargs = dict(func="count", engine=engine) if pass_expected_groups: kwargs["expected_groups"] = (expected.labels.data, expected.labels.data) with raise_if_dask_computes(): actual = xarray_reduce(da, "labels", "labels2", **kwargs) xr.testing.assert_identical(expected, actual) with pytest.raises(NotImplementedError): xarray_reduce(da, "labels", "labels2", dim=..., **kwargs) @requires_dask @pytest.mark.parametrize( "expected_groups", (None, (None, None), [[1, 2], [1, 2]]), ) def test_validate_expected_groups(expected_groups): da = xr.DataArray([1.0, 2.0], dims="x", coords={"labels": ("x", [1, 2]), "labels2": ("x", [1, 2])}) with pytest.raises(ValueError): xarray_reduce( da.chunk({"x": 1}), "labels", "labels2", func="count", expected_groups=expected_groups, ) @requires_cftime @pytest.mark.parametrize("indexer", [slice(None), pytest.param(slice(12), id="missing-group")]) @pytest.mark.parametrize("expected_groups", [None, [0, 1, 2, 3]]) @pytest.mark.parametrize("func", ["first", "last", "min", "max", "count"]) def test_xarray_reduce_cftime_var(engine, indexer, expected_groups, func): times = xr.date_range("1980-09-01 00:00", "1982-09-18 00:00", freq="ME", calendar="noleap") ds = xr.Dataset({"var": ("time", times)}, coords={"time": np.repeat(np.arange(4), 6)}) ds = ds.isel(time=indexer) actual = xarray_reduce( ds, ds.time, func=func, fill_value=dtypes.NA if func in ["first", "last"] else np.nan, engine=engine, expected_groups=expected_groups, ) expected = getattr(ds.groupby("time"), func)() if expected_groups is not None: expected = expected.reindex(time=expected_groups) xr.testing.assert_identical(actual, expected) @requires_cftime @requires_dask def test_xarray_reduce_single_grouper(engine): # DataArray ds = xr.Dataset( { "Tair": ( ("time", "x", "y"), dask.array.ones((36, 205, 275), chunks=(9, -1, -1)), ) }, coords={"time": xr.date_range("1980-09-01 00:00", "1983-09-18 00:00", freq="ME", calendar="noleap")}, ) actual = xarray_reduce(ds.Tair, ds.time.dt.month, func="mean", engine=engine) expected = ds.Tair.groupby("time.month").mean() xr.testing.assert_allclose(actual, expected) # Ellipsis reduction actual = xarray_reduce(ds.Tair, ds.time.dt.month, func="mean", dim=..., engine=engine) expected = ds.Tair.groupby("time.month").mean(...) xr.testing.assert_allclose(actual, expected) # Dataset expected = ds.groupby("time.month").mean() actual = xarray_reduce(ds, ds.time.dt.month, func="mean", engine=engine) xr.testing.assert_allclose(actual, expected) # reduce along other dimensions expected = ds.groupby("time.month").mean(("x", "y")) actual = xarray_reduce(ds, ds.time.dt.month, dim=["x", "y"], func="mean", engine=engine) xr.testing.assert_allclose(actual, expected) # add data var with missing grouper dim ds["foo"] = ("bar", [1, 2, 3]) expected = ds.groupby("time.month").mean() actual = xarray_reduce(ds, ds.time.dt.month, func="mean", engine=engine) xr.testing.assert_allclose(actual, expected) del ds["foo"] # non-dim coord with missing grouper dim ds.coords["foo"] = ("bar", [1, 2, 3]) expected = ds.groupby("time.month").mean() actual = xarray_reduce(ds, ds.time.dt.month, func="mean", engine=engine) xr.testing.assert_allclose(actual, expected) del ds["foo"] # unindexed dim by = ds.time.dt.month.drop_vars("time") ds = ds.drop_vars("time") expected = ds.groupby(by).mean() actual = xarray_reduce(ds, by, func="mean") xr.testing.assert_allclose(actual, expected) def test_xarray_reduce_errors(): da = xr.DataArray(np.ones((12,)), dims="x") by = xr.DataArray(np.ones((12,)), dims="x") with pytest.raises(ValueError, match="group by unnamed"): xarray_reduce(da, by, func="mean") by.name = "by" with pytest.raises(ValueError, match="Cannot reduce over"): xarray_reduce(da, by, func="mean", dim="foo") if has_dask: with pytest.raises(ValueError, match="provide expected_groups"): xarray_reduce(da, by.chunk(), func="mean") @requires_dask @pytest.mark.parametrize( "inchunks, expected", [ [(1,) * 10, (3, 2, 2, 3)], [(2,) * 5, (3, 2, 2, 3)], [(3, 3, 3, 1), (3, 2, 5)], [(3, 1, 1, 2, 1, 1, 1), (3, 2, 2, 3)], [(3, 2, 2, 3), (3, 2, 2, 3)], [(4, 4, 2), (3, 4, 3)], [(5, 5), (5, 5)], [(6, 4), (5, 5)], [(7, 3), (7, 3)], [(8, 2), (7, 3)], [(9, 1), (10,)], [(10,), (10,)], ], ) def test_rechunk_for_blockwise(inchunks, expected): labels = np.array([1, 1, 1, 2, 2, 3, 3, 5, 5, 5]) da = xr.DataArray(dask.array.ones((10,), chunks=inchunks), dims="x", name="foo") rechunked = rechunk_for_blockwise(da, "x", xr.DataArray(labels, dims="x")) assert rechunked.chunks == (expected,) da = xr.DataArray(dask.array.ones((5, 10), chunks=(-1, inchunks)), dims=("y", "x"), name="foo") rechunked = rechunk_for_blockwise(da, "x", xr.DataArray(labels, dims="x")) assert rechunked.chunks == ((5,), expected) ds = da.to_dataset() rechunked = rechunk_for_blockwise(ds, "x", xr.DataArray(labels, dims="x")) assert rechunked.foo.chunks == ((5,), expected) # everything below this is copied from xarray's test_groupby.py # TODO: chunk these # TODO: dim=None, dim=Ellipsis, groupby unindexed dim def test_groupby_duplicate_coordinate_labels(engine): # fix for http://stackoverflow.com/questions/38065129 array = xr.DataArray([1, 2, 3], [("x", [1, 1, 2])]) expected = xr.DataArray([3, 3], [("x", [1, 2])]) actual = xarray_reduce(array, array.x, func="sum", engine=engine) assert_equal(expected, actual) def test_multi_index_groupby_sum(engine): # regression test for xarray GH873 ds = xr.Dataset( {"foo": (("x", "y", "z"), np.ones((3, 4, 2)))}, {"x": ["a", "b", "c"], "y": [1, 2, 3, 4]}, ) expected = ds.sum("z") stacked = ds.stack(space=["x", "y"]) actual = xarray_reduce(stacked, "space", dim="z", func="sum", engine=engine) expected_xarray = stacked.groupby("space").sum("z") assert_equal(expected_xarray, actual) assert_equal(expected, actual.unstack("space")) actual = xarray_reduce(stacked.foo, "space", dim="z", func="sum", engine=engine) assert_equal(expected.foo, actual.unstack("space")) ds = xr.Dataset( dict(a=(("z",), np.ones(10))), coords=dict(b=(("z"), np.arange(2).repeat(5)), c=(("z"), np.arange(5).repeat(2))), ).set_index(bc=["b", "c"]) expected = ds.groupby("bc").sum() actual = xarray_reduce(ds, "bc", func="sum") assert_equal(expected, actual) @pytest.mark.parametrize("chunks", (None, pytest.param(2, marks=requires_dask))) def test_xarray_groupby_bins(chunks, engine): array = xr.DataArray([1, 1, 1, 1, 1], dims="x") labels = xr.DataArray([1, 1.5, 1.9, 2, 3], dims="x", name="labels") if chunks: array = array.chunk({"x": chunks}) labels = labels.chunk({"x": chunks}) kwargs = dict( dim="x", func="count", engine=engine, expected_groups=np.array([1, 2, 4, 5]), isbin=True, fill_value=0, ) with raise_if_dask_computes(): actual = xarray_reduce(array, labels, **kwargs) expected = xr.DataArray( np.array([3, 1, 0]), dims="labels_bins", coords={"labels_bins": [pd.Interval(1, 2), pd.Interval(2, 4), pd.Interval(4, 5)]}, ) xr.testing.assert_equal(actual, expected) # 3D array, 2D by, single dim, with NaNs in by array = array.expand_dims(y=2, z=3) labels = labels.expand_dims(y=2).copy() labels.data[-1, -1] = np.nan with raise_if_dask_computes(): actual = xarray_reduce(array, labels, **kwargs) expected = xr.DataArray( np.array([[[3, 1, 0]] * 3, [[3, 0, 0]] * 3]), dims=("y", "z", "labels_bins"), coords={"labels_bins": [pd.Interval(1, 2), pd.Interval(2, 4), pd.Interval(4, 5)]}, ) xr.testing.assert_equal(actual, expected) @requires_dask def test_func_is_aggregation(): from flox.aggregations import mean ds = xr.Dataset( { "Tair": ( ("time", "x", "y"), dask.array.ones((36, 205, 275), chunks=(9, -1, -1)), ) }, coords={"time": xr.date_range("1980-09-01 00:00", "1983-09-18 00:00", freq="ME", calendar="noleap")}, ) expected = xarray_reduce(ds.Tair, ds.time.dt.month, func="mean") actual = xarray_reduce(ds.Tair, ds.time.dt.month, func=mean) xr.testing.assert_allclose(actual, expected) with pytest.raises(ValueError): xarray_reduce(ds.Tair, ds.time.dt.month, func=mean, skipna=True) with pytest.raises(ValueError): xarray_reduce(ds.Tair, ds.time.dt.month, func=mean, skipna=False) # @requires_dask # def test_cache(): # pytest.importorskip("cachey") # from flox.cache import cache # ds = xr.Dataset( # { # "foo": (("x", "y"), dask.array.ones((10, 20), chunks=2)), # "bar": (("x", "y"), dask.array.ones((10, 20), chunks=2)), # }, # coords={"labels": ("y", np.repeat([1, 2], 10))}, # ) # cache.clear() # xarray_reduce(ds, "labels", func="mean", method="cohorts") # assert len(cache.data) == 1 # xarray_reduce(ds, "labels", func="mean", method="blockwise") # assert len(cache.data) == 2 @requires_dask @pytest.mark.parametrize("method", ["cohorts", "map-reduce"]) def test_groupby_bins_indexed_coordinate(method): ds = xr.Dataset( { "air": ( ("time", "lat", "lon"), dask.array.random.random((125, 25, 53), chunks=(20, 5, -1)), ) }, coords={ "time": pd.date_range("2013-01-01", "2013-02-01", freq="6h"), "lat": np.arange(75.0, 14.9, -2.5), "lon": np.arange(200.0, 331.0, 2.5), }, ) bins = [40, 50, 60, 70] expected = ds.groupby_bins("lat", bins=bins).mean(keep_attrs=True, dim=...) actual = xarray_reduce( ds, ds.lat, dim=ds.air.dims, expected_groups=([40, 50, 60, 70],), isbin=(True,), func="mean", method=method, ) xr.testing.assert_allclose(expected, actual) actual = xarray_reduce( ds, ds.lat, dim=ds.air.dims, expected_groups=pd.IntervalIndex.from_breaks([40, 50, 60, 70]), func="mean", method=method, ) xr.testing.assert_allclose(expected, actual) @pytest.mark.parametrize("chunk", (True, False)) def test_mixed_grouping(chunk): if not has_dask and chunk: pytest.skip() # regression test for https://github.com/xarray-contrib/flox/pull/111 sa = 10 sb = 13 sc = 3 x = xr.Dataset( { "v0": xr.DataArray( ((np.arange(sa * sb * sc) / sa) % 1).reshape((sa, sb, sc)), dims=("a", "b", "c"), ), "v1": xr.DataArray((np.arange(sa * sb) % 3).reshape(sa, sb), dims=("a", "b")), } ) if chunk: x["v0"] = x["v0"].chunk({"a": 5}) r = xarray_reduce( x["v0"], x["v1"], x["v0"], expected_groups=(np.arange(6), np.linspace(0, 1, num=5)), isbin=[False, True], func="count", dim="b", fill_value=0, ) assert (r.sel(v1=[3, 4, 5]) == 0).all().data def test_alignment_error(): da = xr.DataArray(np.arange(10), dims="x", coords={"x": np.arange(10)}) with pytest.raises(ValueError): xarray_reduce(da, da.x.sel(x=slice(5)), func="count") @pytest.mark.parametrize("add_nan", [True, False]) @pytest.mark.parametrize("dtype_out", [np.float64, "float64", np.dtype("float64")]) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) @pytest.mark.parametrize("chunk", (pytest.param(True, marks=requires_dask), False)) def test_dtype(add_nan, chunk, dtype, dtype_out, engine): if engine == "numbagg": # https://github.com/numbagg/numbagg/issues/121 pytest.skip() xp = dask.array if chunk else np data = xp.linspace(0, 1, 48, dtype=dtype).reshape((4, 12)) if add_nan: data[1, ...] = np.nan data[0, [0, 2]] = np.nan arr = xr.DataArray( data, dims=("x", "t"), coords={ "labels": ( "t", np.array(["a", "a", "c", "c", "c", "b", "b", "c", "c", "b", "b", "f"]), ) }, name="arr", ) kwargs = dict(func="mean", dtype=dtype_out, engine=engine) actual = xarray_reduce(arr, "labels", **kwargs) expected = arr.groupby("labels").mean(dtype="float64") assert actual.dtype == np.dtype("float64") assert actual.compute().dtype == np.dtype("float64") xr.testing.assert_allclose(expected, actual, **tolerance64) actual = xarray_reduce(arr.to_dataset(), "labels", **kwargs) expected = arr.to_dataset().groupby("labels").mean(dtype="float64") assert actual.arr.dtype == np.dtype("float64") assert actual.compute().arr.dtype == np.dtype("float64") xr.testing.assert_allclose(expected, actual.transpose("labels", ...), **tolerance64) @pytest.mark.parametrize("chunk", [pytest.param(True, marks=requires_dask), False]) @pytest.mark.parametrize("use_flox", [True, False]) def test_dtype_accumulation(use_flox, chunk): datetimes = pd.date_range("2010-01", "2015-01", freq="6h", inclusive="left") samples = 10 + np.cos(2 * np.pi * 0.001 * np.arange(len(datetimes))) * 1 samples += np.random.randn(len(datetimes)) samples = samples.astype("float32") nan_indices = np.random.default_rng().integers(0, len(samples), size=5_000) samples[nan_indices] = np.nan da = xr.DataArray(samples, dims=("time",), coords=[datetimes]) if chunk: da = da.chunk(time=1024) gb = da.groupby("time.month") with xr.set_options(use_flox=use_flox): expected = gb.reduce(np.nanmean) actual = gb.mean() xr.testing.assert_allclose(expected, actual) assert np.issubdtype(actual.dtype, np.float32) assert np.issubdtype(actual.compute().dtype, np.float32) expected = gb.reduce(np.nanmean, dtype="float64") actual = gb.mean(dtype="float64") assert np.issubdtype(actual.dtype, np.float64) assert np.issubdtype(actual.compute().dtype, np.float64) xr.testing.assert_allclose(expected, actual, **tolerance64) def test_preserve_multiindex(): """Regression test for GH issue #215""" vort = xr.DataArray( name="vort", data=np.random.uniform(size=(4, 2)), dims=["i", "face"], coords={"i": ("i", np.arange(4)), "face": ("face", np.arange(2))}, ) vort = ( vort.coarsen(i=2) .construct(i=("i_region_coarse", "i_region")) .stack(region=["face", "i_region_coarse"]) ) bins = [np.linspace(0, 1, 10)] bin_intervals = tuple(pd.IntervalIndex.from_breaks(b) for b in bins) hist = xarray_reduce( xr.DataArray(1), # weights vort, # variables we want to bin func="count", # count occurrences falling in bins expected_groups=bin_intervals, # bins for each variable dim=["i_region"], # broadcast dimensions fill_value=0, # fill empty bins with 0 counts ) assert "region" in hist.coords def test_fill_value_xarray_behaviour(): times = pd.date_range("2000-01-01", freq="6h", periods=10) ds = xr.Dataset( { "bar": ( "time", [1, 2, 3, np.nan, np.nan, np.nan, 4, 5, np.nan, np.nan], {"meta": "data"}, ), "time": times, } ) pd.date_range("2000-01-01", freq="3h", periods=19) with xr.set_options(use_flox=False): expected = ds.resample(time="3h").sum() with xr.set_options(use_flox=True): actual = ds.resample(time="3h").sum() xr.testing.assert_identical(expected, actual) def test_fill_value_xarray_binning(): array = np.linspace(0, 10, 5 * 10, dtype=int).reshape(5, 10) x = np.array([0, 0, 1, 2, 2]) y = np.arange(array.shape[1]) * 3 u = np.linspace(0, 1, 5) data_array = xr.DataArray(data=array, coords={"x": x, "y": y, "u": ("x", u)}, dims=("x", "y")) with xr.set_options(use_flox=False): expected = data_array.groupby_bins("y", bins=4).mean() with xr.set_options(use_flox=True): actual = data_array.groupby_bins("y", bins=4).mean() xr.testing.assert_identical(expected, actual) def test_groupby_2d_dataset(): d = { "coords": { "bit_index": { "dims": ("bit_index",), "attrs": {"name": "bit_index"}, "data": [0, 1], }, "index": {"dims": ("index",), "data": [0, 6, 8, 10, 14]}, "clifford": {"dims": ("index",), "attrs": {}, "data": [1, 1, 4, 10, 4]}, }, "dims": {"bit_index": 2, "index": 5}, "data_vars": { "counts": { "dims": ("bit_index", "index"), "attrs": { "name": "counts", }, "data": [[18, 30, 45, 70, 38], [382, 370, 355, 330, 362]], } }, } ds = xr.Dataset.from_dict(d) with xr.set_options(use_flox=False): expected = ds.groupby("clifford").mean() with xr.set_options(use_flox=True): actual = ds.groupby("clifford").mean() assert expected.counts.dims == actual.counts.dims # https://github.com/pydata/xarray/issues/8292 xr.testing.assert_identical(expected, actual) @pytest.mark.parametrize("chunk", (pytest.param(True, marks=requires_dask), False)) def test_resampling_missing_groups(chunk): # Regression test for https://github.com/pydata/xarray/issues/8592 time_coords = pd.to_datetime(["2018-06-13T03:40:36", "2018-06-13T05:50:37", "2018-06-15T03:02:34"]) latitude_coords = [0.0] longitude_coords = [0.0] data = [[[1.0]], [[2.0]], [[3.0]]] da = xr.DataArray( data, coords={ "time": time_coords, "latitude": latitude_coords, "longitude": longitude_coords, }, dims=["time", "latitude", "longitude"], ) if chunk: da = da.chunk(time=1) # Without chunking the dataarray, it works: with xr.set_options(use_flox=False): expected = da.resample(time="1D").mean() with xr.set_options(use_flox=True): actual = da.resample(time="1D").mean() xr.testing.assert_identical(expected, actual) @pytest.mark.parametrize("q", (0.5, (0.5,), (0.5, 0.67, 0.85))) @pytest.mark.parametrize("skipna", [False, True]) @pytest.mark.parametrize("chunk", [pytest.param(True, marks=requires_dask), False]) @pytest.mark.parametrize("by_ndim", [1, 2]) def test_multiple_quantiles(q, chunk, by_ndim, skipna): array = np.array([[1, -1, np.nan, 3, 4, 10, 5], [1, np.nan, np.nan, 3, 4, np.nan, np.nan]]) labels = np.array([0, 0, 0, 1, 0, 1, 1]) dims = ("y",) if by_ndim == 2: labels = np.broadcast_to(labels, (5, *labels.shape)) array = np.broadcast_to(np.expand_dims(array, -2), (2, 5, array.shape[-1])) dims += ("y0",) if chunk: array = dask.array.from_array(array, chunks=(1,) + (-1,) * by_ndim) da = xr.DataArray(array, dims=("x", *dims)) by = xr.DataArray(labels, dims=dims, name="by") actual = xarray_reduce(da, by, func="quantile", skipna=skipna, q=q, engine="flox") with xr.set_options(use_flox=False): expected = da.groupby(by).quantile(q, skipna=skipna) xr.testing.assert_allclose(expected, actual) @pytest.mark.parametrize("func", ALL_FUNCS) def test_direct_reduction(func): if "arg" in func or "mode" in func: pytest.skip() # regression test for https://github.com/pydata/xarray/issues/8819 rand = np.random.choice([True, False], size=(2, 3)) if func not in ["any", "all"]: rand = rand.astype(float) if "nan" in func: func = func[3:] kwargs = {"skipna": True} else: kwargs = {} if "first" not in func and "last" not in func: kwargs["dim"] = "y" if "quantile" in func: kwargs["q"] = 0.9 data = xr.DataArray(rand, dims=("x", "y"), coords={"x": [10, 20], "y": [0, 1, 2]}) with xr.set_options(use_flox=True): actual = xarray_reduce(data, "x", func=func, **kwargs) with xr.set_options(use_flox=False): expected = getattr(data.groupby("x", squeeze=False), func)(**kwargs) xr.testing.assert_identical(expected, actual) @pytest.mark.parametrize("reduction", ["max", "min", "nanmax", "nanmin", "sum", "nansum", "prod", "nanprod"]) def test_groupby_preserve_dtype(reduction): # all groups are present, we should follow numpy exactly ds = xr.Dataset( { "test": ( ["x", "y"], np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype="int16"), ) }, coords={"idx": ("x", [1, 2, 1])}, ) kwargs = {"engine": "numpy"} if "nan" in reduction: kwargs["skipna"] = True with xr.set_options(use_flox=True): actual = getattr(ds.groupby("idx"), reduction.removeprefix("nan"))(**kwargs).test.dtype expected = getattr(np, reduction)(ds.test.data, axis=0).dtype assert actual == expected @requires_dask def test_resample_first_last_empty(): with xr.set_options(use_flox=True), dask.config.set(scheduler="processes"): arr = xr.DataArray( np.nan, coords={ "date": pd.to_datetime(["2025-03-24", "2025-06-23"]), }, dims=["date"], ).chunk(date=(1, 1)) arr.resample(date="QE").last().compute() def test_xarray_indexing_array_support(): from xarray.core.indexing import LazilyIndexedArray data = np.array([[5, 1, 3, 8], [2, 9, 4, 7], [6, 0, 10, 1]]) da = xr.DataArray( LazilyIndexedArray(data), dims=("x", "y"), coords={"labels": ("y", ["a", "a", "b", "b"])}, ) assert is_supported_aggregation(da.variable._data, "sum") def test_nanmedian_gh_494(): n = 100 dt = pd.date_range("2020-01-01", periods=n, freq="D") data = np.random.randn(n) data[0:35] = np.nan # groups 0, 1, 2 fully NaN; group 3 partially NaN data[50:80] = np.nan # groups 5, 6, 7 fully NaN <-- interior all-NaN groups data[90:] = np.nan # group 9 fully NaN da = xr.DataArray(data, dims=["time"], coords={"time": dt}, name="data") group = xr.DataArray([i // 10 for i in range(n)], dims=["time"], coords={"time": dt}, name="group") result_flox = xarray_reduce(da, group, func="nanmedian").compute().to_pandas() result_pandas = pd.Series(data).rename("data").groupby(group.values).agg("median") result_pandas.index.name = "group" pd.testing.assert_series_equal(result_flox, result_pandas) flox-0.11.2/uv-numpy1.toml000066400000000000000000000002271514775624000153350ustar00rootroot00000000000000# Configuration for numpy<2 testing # Use with: uv sync --group numpy1 --config-file uv-numpy1.toml override-dependencies = [ "numpy>=1.26,<2", ] flox-0.11.2/uv.lock000066400000000000000000027172751514775624000141070ustar00rootroot00000000000000version = 1 revision = 3 requires-python = ">=3.11" resolution-markers = [ "python_full_version >= '3.14' and platform_machine == 'ARM64' and sys_platform == 'win32'", "python_full_version >= '3.14' and platform_machine != 'ARM64' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine == 'ARM64' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine != 'ARM64' and sys_platform == 'win32'", "python_full_version < '3.12' and platform_machine == 'ARM64' and sys_platform == 'win32'", "python_full_version < '3.12' and platform_machine != 'ARM64' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", "python_full_version < '3.12' and sys_platform == 'emscripten'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] [[package]] name = "accessible-pygments" version = "0.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" }, ] [[package]] name = "aiostream" version = "0.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8b/65/b9b69695702b76a878c9879f2ee80cefce75bc5cb864fc100460bc1c5380/aiostream-0.7.1.tar.gz", hash = "sha256:272aaa0d8f83beb906f5aa9022bb59046bb7a103fa3770f807c31f918595acf6", size = 44059, upload-time = "2025-10-13T20:02:06.961Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/52/a0/d7c6ca304140f3f49987d710e15bc164248924a35d8cdfac2f6e87fca041/aiostream-0.7.1-py3-none-any.whl", hash = "sha256:ea8739e9158ee6a606b3feedf3762721c3507344e540d09a10984c5e88a13b37", size = 41416, upload-time = "2025-10-13T20:02:05.535Z" }, ] [[package]] name = "alabaster" version = "1.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, ] [[package]] name = "anyio" version = "4.12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, ] [[package]] name = "appnope" version = "0.1.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, ] [[package]] name = "argon2-cffi" version = "25.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argon2-cffi-bindings" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, ] [[package]] name = "argon2-cffi-bindings" version = "25.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" }, { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" }, { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" }, { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" }, { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" }, { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" }, { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" }, { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" }, { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" }, { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" }, { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" }, { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" }, { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" }, { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" }, { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" }, { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" }, { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" }, { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" }, { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" }, { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, ] [[package]] name = "array-api-compat" version = "1.13.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/68/36/f799b36d7025a92a23819f9f06541babdb84b6fd0bd4253f8be2eca348a4/array_api_compat-1.13.0.tar.gz", hash = "sha256:8b83a56aa8b9477472fee37f7731968dd213e20c198a05ac49caeff9b03f48a6", size = 103065, upload-time = "2025-12-28T11:26:57.734Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/df/5d/493b1b5528ab5072feae30821ff3a07b7a0474213d548efb1fdf135f85c1/array_api_compat-1.13.0-py3-none-any.whl", hash = "sha256:c15026a0ddec42815383f07da285472e1b1ff2e632eb7afbcfe9b08fcbad9bf1", size = 58585, upload-time = "2025-12-28T11:26:56.081Z" }, ] [[package]] name = "arrow" version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-dateutil" }, { name = "tzdata" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, ] [[package]] name = "asttokens" version = "3.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, ] [[package]] name = "asv" version = "0.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asv-runner" }, { name = "build" }, { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "importlib-metadata" }, { name = "json5" }, { name = "packaging" }, { name = "pympler", marker = "platform_python_implementation != 'PyPy'" }, { name = "pyyaml", marker = "platform_python_implementation != 'PyPy'" }, { name = "tabulate" }, { name = "virtualenv" }, ] sdist = { url = "https://files.pythonhosted.org/packages/88/50/f762be4ee8632d88aff4ba9e62e7c156a0684ef52db629c22bae24fda449/asv-0.6.5.tar.gz", hash = "sha256:a8eeb7c5037cd78c146bd727d27203132438d4d62f36e669eb0cd5d63da0cf39", size = 402650, upload-time = "2025-09-13T16:25:48.141Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b0/95/27569d3f5077153911863207c2d1f96ea8b21ef7e9f8bc969dfac29d65f7/asv-0.6.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cfc6e195f51d83060458f443155adf8b968d73520c63d0c28c72f30be8d58858", size = 180216, upload-time = "2025-09-13T16:24:15.452Z" }, { url = "https://files.pythonhosted.org/packages/68/92/293280069e6cb7b670c72760edef05ef7e8fd2c77837d077c01b8d7b0448/asv-0.6.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:816a420666f39980e75b4dd58545a0702474811087b8c933dd14fb9f0cac5dd6", size = 180511, upload-time = "2025-09-13T16:24:17.144Z" }, { url = "https://files.pythonhosted.org/packages/1d/aa/42e55241325cd03b489105206919dd56351f6b55113dd844ce0cba8b0d5c/asv-0.6.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5bba7568ca5c72e1d980746925c353ac9e76d46329fc324ed43778f91c5c00a1", size = 254428, upload-time = "2025-09-13T16:24:19.157Z" }, { url = "https://files.pythonhosted.org/packages/3f/b6/271d74413393b886b073dc4aa5dbb698426cf8e4f6774555969a70921595/asv-0.6.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423f671f671f6ae5dd2a6912c64130ed374d01dfe2c3d05a6a5307cc47d5ed", size = 256087, upload-time = "2025-09-13T16:24:20.41Z" }, { url = "https://files.pythonhosted.org/packages/8b/3d/bddea222ad0141f4ae4b24ba406ebfd13ab1637a50c60407c13d5ec9e090/asv-0.6.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4d82cc4e95530ce386c0280d84586b6358a7e73b6bb6d814b35734c2d49cc43a", size = 808082, upload-time = "2025-09-13T16:24:22.048Z" }, { url = "https://files.pythonhosted.org/packages/08/11/8199376b9df37457c2a59a55e7f43f501f3c44cb54279dc5307f4966f666/asv-0.6.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7911132aab8263751946ff79d9b75d1178ec278d2731f09d6d14ef4f26842c70", size = 1384612, upload-time = "2025-09-13T16:24:24.38Z" }, { url = "https://files.pythonhosted.org/packages/b5/47/3aceb8c4ca6d91e00b3fa6c6312580958791407034b0afd588f66dc6690e/asv-0.6.5-cp311-cp311-win_amd64.whl", hash = "sha256:48fba7264d348b932fd4d2f42b6128836347d46af3d61df0c9d641bc61678af8", size = 182792, upload-time = "2025-09-13T16:24:26.124Z" }, { url = "https://files.pythonhosted.org/packages/e7/58/3c94d6043f2d815480b873f302ecda3b9debb0f202556fd822b118e87415/asv-0.6.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:375da7109fa160d41e4b86a5de7783e8c9bc9f1c930a1c02c29b652b15d46835", size = 180231, upload-time = "2025-09-13T16:24:27.465Z" }, { url = "https://files.pythonhosted.org/packages/f2/75/2a23346aabb19698e97b7ee8f57eb905dabf7460b226a2070c6cdd3e8c17/asv-0.6.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:980cb8e9c3be5350621c85201bfb25f70c26695f69bd4e91b19f1b3c97f00ff3", size = 180531, upload-time = "2025-09-13T16:24:28.693Z" }, { url = "https://files.pythonhosted.org/packages/27/d3/22bc22619266bced0a53ff07ee94a41bb09fed2bbe505e07a146fc4c1a58/asv-0.6.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13f503d45077ba275d357a9712fe1506b98e507eb276ca01e981c9e5baf30b43", size = 254851, upload-time = "2025-09-13T16:24:30.035Z" }, { url = "https://files.pythonhosted.org/packages/de/70/9991371db795a84b4fb0774ff050ed48730c14568953f9f59ca35170541b/asv-0.6.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:440aca773d254f6590f7a459bdc388441027bc2745eae644675665acc3809c2e", size = 256342, upload-time = "2025-09-13T16:24:31.309Z" }, { url = "https://files.pythonhosted.org/packages/8a/1d/7cb7f02091908201c45c609d29ced5bbc1101a057edc0e1fb153f7592b4e/asv-0.6.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bf63f7ee3d35ec8191543d82ca3a3be3a3c0cde8eb2d45a672f09f32ba5fbb37", size = 807867, upload-time = "2025-09-13T16:24:33.298Z" }, { url = "https://files.pythonhosted.org/packages/ff/02/946c53292fe551b3bb977260f29b5bfa9f284bf17088ccef5c8ddf28f06c/asv-0.6.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:58a0d2de09ebc67642b661d904d2e686e0f32bb5e8b4867523a15ff7561f061a", size = 1384841, upload-time = "2025-09-13T16:24:34.857Z" }, { url = "https://files.pythonhosted.org/packages/cc/f7/b43efa62f20af7e9035f6fd6dadb7109feddfb4e0b1e2ff791656ae55606/asv-0.6.5-cp312-cp312-win_amd64.whl", hash = "sha256:8df71cd3c656680051e0d0b2834521f7ab6da3d4804c48354c0e5ca341a0a39e", size = 182804, upload-time = "2025-09-13T16:24:36.135Z" }, { url = "https://files.pythonhosted.org/packages/25/70/a00673c98d30de3377b036cd85247274330ade931c0e4909c3e2d269c0c6/asv-0.6.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bedfc7f0138ab136ccd67f42575dd4b2471c811239ad8c7b7aabc83f5eba79c3", size = 180236, upload-time = "2025-09-13T16:24:37.775Z" }, { url = "https://files.pythonhosted.org/packages/2f/fd/d2f5c43dcb614cfedd1f339feb2f711b0a69e7c2ad96d68b547d44227c80/asv-0.6.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2f062e0658e568b98154afe11d91b5fb631f884678b7ad4a00ebcc0d6aa6b41f", size = 180537, upload-time = "2025-09-13T16:24:39.396Z" }, { url = "https://files.pythonhosted.org/packages/6d/00/a8dba759f554e6aedc5a7caf2a8efd00e5c69536560c670b1f41e7b53d83/asv-0.6.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6caedcca3ec60602b907caeab54d1706abb12e088ce96650862c6fc117831cc0", size = 254761, upload-time = "2025-09-13T16:24:41.262Z" }, { url = "https://files.pythonhosted.org/packages/3e/bd/89c4483e1e781e39957547d7bda0a7a7af338911ea159cd66a9415cc811d/asv-0.6.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:861ae69bfdd8659f95a058891c98757d3a5f857bf0ddc5d810e0dabf3405042e", size = 256267, upload-time = "2025-09-13T16:24:43.033Z" }, { url = "https://files.pythonhosted.org/packages/35/f1/752a51a07be0c153281f04795433524c76e6926063da005b78e2053117c5/asv-0.6.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7344c0020c1ab1cb33af9626cf24e05c346b4fd521d4f866f35a7a9a276f8e16", size = 806346, upload-time = "2025-09-13T16:24:45.123Z" }, { url = "https://files.pythonhosted.org/packages/3e/ce/c6d9f2dc42462db7897178e21e38c925d8efb75813dde8e7a0a2e5126387/asv-0.6.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1279a92cd8a601d2be5430afe3dd9f942ab0e6003f33ff1914dd9f638b595a3d", size = 1384779, upload-time = "2025-09-13T16:24:46.758Z" }, { url = "https://files.pythonhosted.org/packages/f0/4f/48af74f756f1c67b71074c99f265d382927de742288cf416e212c1808c7d/asv-0.6.5-cp313-cp313-win_amd64.whl", hash = "sha256:dbc3269464ec27d025d3b25e0e1f3d616035e05e01bcfceb2f4e965278d72197", size = 182807, upload-time = "2025-09-13T16:24:47.978Z" }, { url = "https://files.pythonhosted.org/packages/35/78/ee6ad8ca4de3ad89e382f7fd45eabcfecfcd6dca844462706a8aa1f8d895/asv-0.6.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5204d6e4c9d574a9f8dd9b3834c3385b8b118d48ce4fcb3bc10b61f60de287fd", size = 179852, upload-time = "2025-09-13T16:25:24.263Z" }, { url = "https://files.pythonhosted.org/packages/de/24/22a85656df00f64ceb1bc7c4a5a358a3990410cd9a96c48486fc2c13bdaf/asv-0.6.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c419db85e40ca1cebefbe8bd5f338a16cbc5f7a2cbf2de0793f95d6bda9ede6c", size = 179470, upload-time = "2025-09-13T16:25:25.76Z" }, { url = "https://files.pythonhosted.org/packages/33/28/30a5571658ab4cd0f8ba616afad7782ed7ad82e04a7b6eda77e480abf174/asv-0.6.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80a76dc32330c97bfba861c547dd17bcc04811946f386e00c8c7dfbb12354280", size = 180233, upload-time = "2025-09-13T16:25:27.158Z" }, { url = "https://files.pythonhosted.org/packages/3d/d6/04875bb5abf2c6b8390d63e0ff45dc0a5c6413377be975b9e825150c7d75/asv-0.6.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:0f26006545918b9a2fb78323823bc4f9fa8bad628fc2aae5bd77d41f58fa61ac", size = 182887, upload-time = "2025-09-13T16:25:28.58Z" }, ] [[package]] name = "asv-runner" version = "0.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-metadata" }, ] sdist = { url = "https://files.pythonhosted.org/packages/32/4b/da5ae9c35e0b9f793d07d4939ad99e1d2ba7c9c502fd6074af5ff4554b03/asv_runner-0.2.1.tar.gz", hash = "sha256:945dd301a06fa9102f221b1e9ddd048f5ecd863796d4c8cd487f5577fe0db66d", size = 39518, upload-time = "2024-02-17T14:11:48.104Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/54/9a/6872af94fc8e8072723946651e65f66e16a0ca0efec7806bce8c2e2483d1/asv_runner-0.2.1-py3-none-any.whl", hash = "sha256:655d466208ce311768071f5003a61611481b24b3ad5ac41fb8a6374197e647e9", size = 47660, upload-time = "2024-02-11T21:50:07.026Z" }, ] [[package]] name = "async-lru" version = "2.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ef/c3/bbf34f15ea88dfb649ab2c40f9d75081784a50573a9ea431563cab64adb8/async_lru-2.1.0.tar.gz", hash = "sha256:9eeb2fecd3fe42cc8a787fc32ead53a3a7158cc43d039c3c55ab3e4e5b2a80ed", size = 12041, upload-time = "2026-01-17T22:52:18.931Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2e/e9/eb6a5db5ac505d5d45715388e92bced7a5bb556facc4d0865d192823f2d2/async_lru-2.1.0-py3-none-any.whl", hash = "sha256:fa12dcf99a42ac1280bc16c634bbaf06883809790f6304d85cdab3f666f33a7e", size = 6933, upload-time = "2026-01-17T22:52:17.389Z" }, ] [[package]] name = "attrs" version = "25.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] [[package]] name = "babel" version = "2.18.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, ] [[package]] name = "beautifulsoup4" version = "4.14.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "soupsieve" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, ] [[package]] name = "bleach" version = "6.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "webencodings" }, ] sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" }, ] [package.optional-dependencies] css = [ { name = "tinycss2" }, ] [[package]] name = "bokeh" version = "3.8.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "contourpy" }, { name = "jinja2" }, { name = "narwhals" }, { name = "numpy" }, { name = "packaging" }, { name = "pandas" }, { name = "pillow" }, { name = "pyyaml" }, { name = "tornado", marker = "sys_platform != 'emscripten'" }, { name = "xyzservices" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e4/31/7ee0c4dfd0255631b0624ce01be178704f91f763f02a1879368eb109befd/bokeh-3.8.2.tar.gz", hash = "sha256:8e7dcacc21d53905581b54328ad2705954f72f2997f99fc332c1de8da53aa3cc", size = 6529251, upload-time = "2026-01-06T00:20:06.568Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f6/a8/877f306720bc114c612579c5af36bcb359026b83d051226945499b306b1a/bokeh-3.8.2-py3-none-any.whl", hash = "sha256:5e2c0d84f75acb25d60efb9e4d2f434a791c4639b47d685534194c4e07bd0111", size = 7207131, upload-time = "2026-01-06T00:20:04.917Z" }, ] [[package]] name = "build" version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "os_name == 'nt'" }, { name = "packaging" }, { name = "pyproject-hooks" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/18/94eaffda7b329535d91f00fe605ab1f1e5cd68b2074d03f255c7d250687d/build-1.4.0.tar.gz", hash = "sha256:f1b91b925aa322be454f8330c6fb48b465da993d1e7e7e6fa35027ec49f3c936", size = 50054, upload-time = "2026-01-08T16:41:47.696Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl", hash = "sha256:6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596", size = 24141, upload-time = "2026-01-08T16:41:46.453Z" }, ] [[package]] name = "cachey" version = "0.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "heapdict" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c6/9c/e3c959c1601013bf8a72e8bf91ea1ebc6fe8a2305bd2324b039ee0403277/cachey-0.2.1.tar.gz", hash = "sha256:0310ba8afe52729fa7626325c8d8356a8421c434bf887ac851e58dcf7cf056a6", size = 6461, upload-time = "2020-03-11T15:34:08.721Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/57/f0/e24f3e5d5d539abeb783087b87c26cfb99c259f1126700569e000243745a/cachey-0.2.1-py3-none-any.whl", hash = "sha256:49cf8528496ce3f99d47f1bd136b7c88237e55347a15d880f47cefc0615a83c3", size = 6415, upload-time = "2020-03-11T15:34:07.347Z" }, ] [[package]] name = "certifi" version = "2026.1.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, ] [[package]] name = "cffi" version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, ] [[package]] name = "cfgv" version = "3.5.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, ] [[package]] name = "cftime" version = "1.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/65/dc/470ffebac2eb8c54151eb893055024fe81b1606e7c6ff8449a588e9cd17f/cftime-1.6.5.tar.gz", hash = "sha256:8225fed6b9b43fb87683ebab52130450fc1730011150d3092096a90e54d1e81e", size = 326605, upload-time = "2025-10-13T18:56:26.352Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e4/f6/9da7aba9548ede62d25936b8b448acd7e53e5dcc710896f66863dcc9a318/cftime-1.6.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:474e728f5a387299418f8d7cb9c52248dcd5d977b2a01de7ec06bba572e26b02", size = 512733, upload-time = "2025-10-13T18:56:00.189Z" }, { url = "https://files.pythonhosted.org/packages/1f/d5/d86ad95fc1fd89947c34b495ff6487b6d361cf77500217423b4ebcb1f0c2/cftime-1.6.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ab9e80d4de815cac2e2d88a2335231254980e545d0196eb34ee8f7ed612645f1", size = 492946, upload-time = "2025-10-13T18:56:01.262Z" }, { url = "https://files.pythonhosted.org/packages/4f/93/d7e8dd76b03a9d5be41a3b3185feffc7ea5359228bdffe7aa43ac772a75b/cftime-1.6.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ad24a563784e4795cb3d04bd985895b5db49ace2cbb71fcf1321fd80141f9a52", size = 1689856, upload-time = "2025-10-13T19:39:12.873Z" }, { url = "https://files.pythonhosted.org/packages/3e/8d/86586c0d75110f774e46e2bd6d134e2d1cca1dedc9bb08c388fa3df76acd/cftime-1.6.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a3cda6fd12c7fb25eff40a6a857a2bf4d03e8cc71f80485d8ddc65ccbd80f16a", size = 1718573, upload-time = "2025-10-13T18:56:02.788Z" }, { url = "https://files.pythonhosted.org/packages/bb/fe/7956914cfc135992e89098ebbc67d683c51ace5366ba4b114fef1de89b21/cftime-1.6.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:28cda78d685397ba23d06273b9c916c3938d8d9e6872a537e76b8408a321369b", size = 1788563, upload-time = "2025-10-13T18:56:04.075Z" }, { url = "https://files.pythonhosted.org/packages/e5/c7/6669708fcfe1bb7b2a7ce693b8cc67165eac00d3ac5a5e8f6ce1be551ff9/cftime-1.6.5-cp311-cp311-win_amd64.whl", hash = "sha256:93ead088e3a216bdeb9368733a0ef89a7451dfc1d2de310c1c0366a56ad60dc8", size = 473631, upload-time = "2025-10-13T18:56:05.159Z" }, { url = "https://files.pythonhosted.org/packages/82/c5/d70cb1ab533ca790d7c9b69f98215fa4fead17f05547e928c8f2b8f96e54/cftime-1.6.5-cp311-cp311-win_arm64.whl", hash = "sha256:3384d69a0a7f3d45bded21a8cbcce66c8ba06c13498eac26c2de41b1b9b6e890", size = 459383, upload-time = "2026-01-02T21:16:47.317Z" }, { url = "https://files.pythonhosted.org/packages/b6/c1/e8cb7f78a3f87295450e7300ebaecf83076d96a99a76190593d4e1d2be40/cftime-1.6.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:eef25caed5ebd003a38719bd3ff8847cd52ef2ea56c3ebdb2c9345ba131fc7c5", size = 504175, upload-time = "2025-10-13T18:56:06.398Z" }, { url = "https://files.pythonhosted.org/packages/50/1a/86e1072b09b2f9049bb7378869f64b6747f96a4f3008142afed8955b52a4/cftime-1.6.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c87d2f3b949e45463e559233c69e6a9cf691b2b378c1f7556166adfabbd1c6b0", size = 485980, upload-time = "2025-10-13T18:56:08.669Z" }, { url = "https://files.pythonhosted.org/packages/35/28/d3177b60da3f308b60dee2aef2eb69997acfab1e863f0bf0d2a418396ce5/cftime-1.6.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:82cb413973cc51b55642b3a1ca5b28db5b93a294edbef7dc049c074b478b4647", size = 1591166, upload-time = "2025-10-13T19:39:14.109Z" }, { url = "https://files.pythonhosted.org/packages/d1/fd/a7266970312df65e68b5641b86e0540a739182f5e9c62eec6dbd29f18055/cftime-1.6.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85ba8e7356d239cfe56ef7707ac30feaf67964642ac760a82e507ee3c5db4ac4", size = 1642614, upload-time = "2025-10-13T18:56:09.815Z" }, { url = "https://files.pythonhosted.org/packages/c4/73/f0035a4bc2df8885bb7bd5fe63659686ea1ec7d0cc74b4e3d50e447402e5/cftime-1.6.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:456039af7907a3146689bb80bfd8edabd074c7f3b4eca61f91b9c2670addd7ad", size = 1688090, upload-time = "2025-10-13T18:56:11.442Z" }, { url = "https://files.pythonhosted.org/packages/88/15/8856a0ab76708553ff597dd2e617b088c734ba87dc3fd395e2b2f3efffe8/cftime-1.6.5-cp312-cp312-win_amd64.whl", hash = "sha256:da84534c43699960dc980a9a765c33433c5de1a719a4916748c2d0e97a071e44", size = 464840, upload-time = "2025-10-13T18:56:12.506Z" }, { url = "https://files.pythonhosted.org/packages/3a/85/451009a986d9273d2208fc0898aa00262275b5773259bf3f942f6716a9e7/cftime-1.6.5-cp312-cp312-win_arm64.whl", hash = "sha256:c62cd8db9ea40131eea7d4523691c5d806d3265d31279e4a58574a42c28acd77", size = 450534, upload-time = "2026-01-02T21:16:48.784Z" }, { url = "https://files.pythonhosted.org/packages/2e/60/74ea344b3b003fada346ed98a6899085d6fd4c777df608992d90c458fda6/cftime-1.6.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4aba66fd6497711a47c656f3a732c2d1755ad15f80e323c44a8716ebde39ddd5", size = 502453, upload-time = "2025-10-13T18:56:13.545Z" }, { url = "https://files.pythonhosted.org/packages/1e/14/adb293ac6127079b49ff11c05cf3d5ce5c1f17d097f326dc02d74ddfcb6e/cftime-1.6.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:89e7cba699242366e67d6fb5aee579440e791063f92a93853610c91647167c0d", size = 484541, upload-time = "2025-10-13T18:56:14.612Z" }, { url = "https://files.pythonhosted.org/packages/4f/74/bb8a4566af8d0ef3f045d56c462a9115da4f04b07c7fbbf2b4875223eebd/cftime-1.6.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2f1eb43d7a7b919ec99aee709fb62ef87ef1cf0679829ef93d37cc1c725781e9", size = 1591014, upload-time = "2025-10-13T19:39:15.346Z" }, { url = "https://files.pythonhosted.org/packages/ba/08/52f06ff2f04d376f9cd2c211aefcf2b37f1978e43289341f362fc99f6a0e/cftime-1.6.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e02a1d80ffc33fe469c7db68aa24c4a87f01da0c0c621373e5edadc92964900b", size = 1633625, upload-time = "2025-10-13T18:56:15.745Z" }, { url = "https://files.pythonhosted.org/packages/cf/33/03e0b23d58ea8fab94ecb4f7c5b721e844a0800c13694876149d98830a73/cftime-1.6.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18ab754805233cdd889614b2b3b86a642f6d51a57a1ec327c48053f3414f87d8", size = 1684269, upload-time = "2025-10-13T18:56:17.04Z" }, { url = "https://files.pythonhosted.org/packages/a4/60/a0cfba63847b43599ef1cdbbf682e61894994c22b9a79fd9e1e8c7e9de41/cftime-1.6.5-cp313-cp313-win_amd64.whl", hash = "sha256:6c27add8f907f4a4cd400e89438f2ea33e2eb5072541a157a4d013b7dbe93f9c", size = 465364, upload-time = "2025-10-13T18:56:18.05Z" }, { url = "https://files.pythonhosted.org/packages/3d/e8/ec32f2aef22c15604e6fda39ff8d581a00b5469349f8fba61640d5358d2c/cftime-1.6.5-cp313-cp313-win_arm64.whl", hash = "sha256:31d1ff8f6bbd4ca209099d24459ec16dea4fb4c9ab740fbb66dd057ccbd9b1b9", size = 450468, upload-time = "2026-01-02T21:16:50.193Z" }, { url = "https://files.pythonhosted.org/packages/ea/6c/a9618f589688358e279720f5c0fe67ef0077fba07334ce26895403ebc260/cftime-1.6.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c69ce3bdae6a322cbb44e9ebc20770d47748002fb9d68846a1e934f1bd5daf0b", size = 502725, upload-time = "2025-10-13T18:56:19.424Z" }, { url = "https://files.pythonhosted.org/packages/d8/e3/da3c36398bfb730b96248d006cabaceed87e401ff56edafb2a978293e228/cftime-1.6.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e62e9f2943e014c5ef583245bf2e878398af131c97e64f8cd47c1d7baef5c4e2", size = 485445, upload-time = "2025-10-13T18:56:20.853Z" }, { url = "https://files.pythonhosted.org/packages/32/93/b05939e5abd14bd1ab69538bbe374b4ee2a15467b189ff895e9a8cdaddf6/cftime-1.6.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7da5fdaa4360d8cb89b71b8ded9314f2246aa34581e8105c94ad58d6102d9e4f", size = 1584434, upload-time = "2025-10-13T19:39:17.084Z" }, { url = "https://files.pythonhosted.org/packages/7f/89/648397f9936e0b330999c4e776ebf296ec3c6a65f9901687dbca4ab820da/cftime-1.6.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bff865b4ea4304f2744a1ad2b8149b8328b321dd7a2b9746ef926d229bd7cd49", size = 1609812, upload-time = "2025-10-13T18:56:21.971Z" }, { url = "https://files.pythonhosted.org/packages/e7/0f/901b4835aa67ad3e915605d4e01d0af80a44b114eefab74ae33de6d36933/cftime-1.6.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e552c5d1c8a58f25af7521e49237db7ca52ed2953e974fe9f7c4491e95fdd36c", size = 1669768, upload-time = "2025-10-13T18:56:24.027Z" }, { url = "https://files.pythonhosted.org/packages/22/d5/e605e4b28363e7a9ae98ed12cabbda5b155b6009270e6a231d8f10182a17/cftime-1.6.5-cp314-cp314-win_amd64.whl", hash = "sha256:e645b095dc50a38ac454b7e7f0742f639e7d7f6b108ad329358544a6ff8c9ba2", size = 463818, upload-time = "2025-10-13T18:56:25.376Z" }, { url = "https://files.pythonhosted.org/packages/3d/89/a8f85ae697ff10206ec401c2621f5ca9f327554f586d62f244739ceeb347/cftime-1.6.5-cp314-cp314-win_arm64.whl", hash = "sha256:b9044d7ac82d3d8af189df1032fdc871bbd3f3dd41a6ec79edceb5029b71e6e0", size = 459862, upload-time = "2026-01-02T20:45:02.625Z" }, { url = "https://files.pythonhosted.org/packages/ab/05/7410e12fd03a0c52717e74e6a1b49958810807dda212e23b65d43ea99676/cftime-1.6.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9ef56460cb0576e1a9161e1428c9e1a633f809a23fa9d598f313748c1ae5064e", size = 533781, upload-time = "2026-01-02T20:45:04.818Z" }, { url = "https://files.pythonhosted.org/packages/44/ba/10e3546426d3ed9f9cc82e4a99836bb6fac1642c7830f7bdd0ac1c3f0805/cftime-1.6.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4f4873d38b10032f9f3111c547a1d485519ae64eee6a7a2d091f1f8b08e1ba50", size = 515218, upload-time = "2026-01-02T20:45:06.788Z" }, { url = "https://files.pythonhosted.org/packages/bd/68/efa11eae867749e921bfec6a865afdba8166e96188112dde70bb8bb49254/cftime-1.6.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ccce0f4c9d3f38dd948a117e578b50d0e0db11e2ca9435fb358fd524813e4b61", size = 1579932, upload-time = "2026-01-02T20:45:11.194Z" }, { url = "https://files.pythonhosted.org/packages/9d/6c/0971e602c1390a423e6621dfbad9f1d375186bdaf9c9c7f75e06f1fbf355/cftime-1.6.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:19cbfc5152fb0b34ce03acf9668229af388d7baa63a78f936239cb011ccbe6b1", size = 1555894, upload-time = "2026-01-02T20:45:16.351Z" }, { url = "https://files.pythonhosted.org/packages/ad/fc/8475a15b7c3209a4a68b563dfc5e01ce74f2d8b9822372c3d30c68ab7f39/cftime-1.6.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4470cd5ef3c2514566f53efbcbb64dd924fa0584637d90285b2f983bd4ee7d97", size = 513027, upload-time = "2026-01-02T20:45:20.023Z" }, { url = "https://files.pythonhosted.org/packages/f7/80/4ecbda8318fbf40ad4e005a4a93aebba69e81382e5b4c6086251cd5d0ee8/cftime-1.6.5-cp314-cp314t-win_arm64.whl", hash = "sha256:034c15a67144a0a5590ef150c99f844897618b148b87131ed34fda7072614662", size = 469065, upload-time = "2026-01-02T20:45:23.398Z" }, ] [[package]] name = "charset-normalizer" version = "3.4.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] [[package]] name = "click" version = "8.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] [[package]] name = "cloudpickle" version = "3.1.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, ] [[package]] name = "codecov" version = "2.1.13" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage" }, { name = "requests" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2c/bb/594b26d2c85616be6195a64289c578662678afa4910cef2d3ce8417cf73e/codecov-2.1.13.tar.gz", hash = "sha256:2362b685633caeaf45b9951a9b76ce359cd3581dd515b430c6c3f5dfb4d92a8c", size = 21416, upload-time = "2023-04-17T23:11:39.779Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/af/02/18785edcdf6266cdd6c6dc7635f1cbeefd9a5b4c3bb8aff8bd681e9dd095/codecov-2.1.13-py2.py3-none-any.whl", hash = "sha256:c2ca5e51bba9ebb43644c43d0690148a55086f7f5e6fd36170858fa4206744d5", size = 16512, upload-time = "2023-04-17T23:11:37.344Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "comm" version = "0.2.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, ] [[package]] name = "contourpy" version = "1.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, ] [[package]] name = "coverage" version = "7.13.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/11/43/3e4ac666cc35f231fa70c94e9f38459299de1a152813f9d2f60fc5f3ecaf/coverage-7.13.3.tar.gz", hash = "sha256:f7f6182d3dfb8802c1747eacbfe611b669455b69b7c037484bb1efbbb56711ac", size = 826832, upload-time = "2026-02-03T14:02:30.944Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ec/09/1ac74e37cf45f17eb41e11a21854f7f92a4c2d6c6098ef4a1becb0c6d8d3/coverage-7.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5907605ee20e126eeee2abe14aae137043c2c8af2fa9b38d2ab3b7a6b8137f73", size = 219276, upload-time = "2026-02-03T14:00:00.296Z" }, { url = "https://files.pythonhosted.org/packages/2e/cb/71908b08b21beb2c437d0d5870c4ec129c570ca1b386a8427fcdb11cf89c/coverage-7.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a88705500988c8acad8b8fd86c2a933d3aa96bec1ddc4bc5cb256360db7bbd00", size = 219776, upload-time = "2026-02-03T14:00:02.414Z" }, { url = "https://files.pythonhosted.org/packages/09/85/c4f3dd69232887666a2c0394d4be21c60ea934d404db068e6c96aa59cd87/coverage-7.13.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bbb5aa9016c4c29e3432e087aa29ebee3f8fda089cfbfb4e6d64bd292dcd1c2", size = 250196, upload-time = "2026-02-03T14:00:04.197Z" }, { url = "https://files.pythonhosted.org/packages/9c/cc/560ad6f12010344d0778e268df5ba9aa990aacccc310d478bf82bf3d302c/coverage-7.13.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0c2be202a83dde768937a61cdc5d06bf9fb204048ca199d93479488e6247656c", size = 252111, upload-time = "2026-02-03T14:00:05.639Z" }, { url = "https://files.pythonhosted.org/packages/f0/66/3193985fb2c58e91f94cfbe9e21a6fdf941e9301fe2be9e92c072e9c8f8c/coverage-7.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f45e32ef383ce56e0ca099b2e02fcdf7950be4b1b56afaab27b4ad790befe5b", size = 254217, upload-time = "2026-02-03T14:00:07.738Z" }, { url = "https://files.pythonhosted.org/packages/c5/78/f0f91556bf1faa416792e537c523c5ef9db9b1d32a50572c102b3d7c45b3/coverage-7.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ed2e787249b922a93cd95c671cc9f4c9797a106e81b455c83a9ddb9d34590c0", size = 250318, upload-time = "2026-02-03T14:00:09.224Z" }, { url = "https://files.pythonhosted.org/packages/6f/aa/fc654e45e837d137b2c1f3a2cc09b4aea1e8b015acd2f774fa0f3d2ddeba/coverage-7.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:05dd25b21afffe545e808265897c35f32d3e4437663923e0d256d9ab5031fb14", size = 251909, upload-time = "2026-02-03T14:00:10.712Z" }, { url = "https://files.pythonhosted.org/packages/73/4d/ab53063992add8a9ca0463c9d92cce5994a29e17affd1c2daa091b922a93/coverage-7.13.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:46d29926349b5c4f1ea4fca95e8c892835515f3600995a383fa9a923b5739ea4", size = 249971, upload-time = "2026-02-03T14:00:12.402Z" }, { url = "https://files.pythonhosted.org/packages/29/25/83694b81e46fcff9899694a1b6f57573429cdd82b57932f09a698f03eea5/coverage-7.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:fae6a21537519c2af00245e834e5bf2884699cc7c1055738fd0f9dc37a3644ad", size = 249692, upload-time = "2026-02-03T14:00:13.868Z" }, { url = "https://files.pythonhosted.org/packages/d4/ef/d68fc304301f4cb4bf6aefa0045310520789ca38dabdfba9dbecd3f37919/coverage-7.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c672d4e2f0575a4ca2bf2aa0c5ced5188220ab806c1bb6d7179f70a11a017222", size = 250597, upload-time = "2026-02-03T14:00:15.461Z" }, { url = "https://files.pythonhosted.org/packages/8d/85/240ad396f914df361d0f71e912ddcedb48130c71b88dc4193fe3c0306f00/coverage-7.13.3-cp311-cp311-win32.whl", hash = "sha256:fcda51c918c7a13ad93b5f89a58d56e3a072c9e0ba5c231b0ed81404bf2648fb", size = 221773, upload-time = "2026-02-03T14:00:17.462Z" }, { url = "https://files.pythonhosted.org/packages/2f/71/165b3a6d3d052704a9ab52d11ea64ef3426745de517dda44d872716213a7/coverage-7.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:d1a049b5c51b3b679928dd35e47c4a2235e0b6128b479a7596d0ef5b42fa6301", size = 222711, upload-time = "2026-02-03T14:00:19.449Z" }, { url = "https://files.pythonhosted.org/packages/51/d0/0ddc9c5934cdd52639c5df1f1eb0fdab51bb52348f3a8d1c7db9c600d93a/coverage-7.13.3-cp311-cp311-win_arm64.whl", hash = "sha256:79f2670c7e772f4917895c3d89aad59e01f3dbe68a4ed2d0373b431fad1dcfba", size = 221377, upload-time = "2026-02-03T14:00:20.968Z" }, { url = "https://files.pythonhosted.org/packages/94/44/330f8e83b143f6668778ed61d17ece9dc48459e9e74669177de02f45fec5/coverage-7.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ed48b4170caa2c4420e0cd27dc977caaffc7eecc317355751df8373dddcef595", size = 219441, upload-time = "2026-02-03T14:00:22.585Z" }, { url = "https://files.pythonhosted.org/packages/08/e7/29db05693562c2e65bdf6910c0af2fd6f9325b8f43caf7a258413f369e30/coverage-7.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8f2adf4bcffbbec41f366f2e6dffb9d24e8172d16e91da5799c9b7ed6b5716e6", size = 219801, upload-time = "2026-02-03T14:00:24.186Z" }, { url = "https://files.pythonhosted.org/packages/90/ae/7f8a78249b02b0818db46220795f8ac8312ea4abd1d37d79ea81db5cae81/coverage-7.13.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01119735c690786b6966a1e9f098da4cd7ca9174c4cfe076d04e653105488395", size = 251306, upload-time = "2026-02-03T14:00:25.798Z" }, { url = "https://files.pythonhosted.org/packages/62/71/a18a53d1808e09b2e9ebd6b47dad5e92daf4c38b0686b4c4d1b2f3e42b7f/coverage-7.13.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8bb09e83c603f152d855f666d70a71765ca8e67332e5829e62cb9466c176af23", size = 254051, upload-time = "2026-02-03T14:00:27.474Z" }, { url = "https://files.pythonhosted.org/packages/4a/0a/eb30f6455d04c5a3396d0696cad2df0269ae7444bb322f86ffe3376f7bf9/coverage-7.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b607a40cba795cfac6d130220d25962931ce101f2f478a29822b19755377fb34", size = 255160, upload-time = "2026-02-03T14:00:29.024Z" }, { url = "https://files.pythonhosted.org/packages/7b/7e/a45baac86274ce3ed842dbb84f14560c673ad30535f397d89164ec56c5df/coverage-7.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:44f14a62f5da2e9aedf9080e01d2cda61df39197d48e323538ec037336d68da8", size = 251709, upload-time = "2026-02-03T14:00:30.641Z" }, { url = "https://files.pythonhosted.org/packages/c0/df/dd0dc12f30da11349993f3e218901fdf82f45ee44773596050c8f5a1fb25/coverage-7.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:debf29e0b157769843dff0981cc76f79e0ed04e36bb773c6cac5f6029054bd8a", size = 253083, upload-time = "2026-02-03T14:00:32.14Z" }, { url = "https://files.pythonhosted.org/packages/ab/32/fc764c8389a8ce95cb90eb97af4c32f392ab0ac23ec57cadeefb887188d3/coverage-7.13.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:824bb95cd71604031ae9a48edb91fd6effde669522f960375668ed21b36e3ec4", size = 251227, upload-time = "2026-02-03T14:00:34.721Z" }, { url = "https://files.pythonhosted.org/packages/dd/ca/d025e9da8f06f24c34d2da9873957cfc5f7e0d67802c3e34d0caa8452130/coverage-7.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8f1010029a5b52dc427c8e2a8dbddb2303ddd180b806687d1acd1bb1d06649e7", size = 250794, upload-time = "2026-02-03T14:00:36.278Z" }, { url = "https://files.pythonhosted.org/packages/45/c7/76bf35d5d488ec8f68682eb8e7671acc50a6d2d1c1182de1d2b6d4ffad3b/coverage-7.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cd5dee4fd7659d8306ffa79eeaaafd91fa30a302dac3af723b9b469e549247e0", size = 252671, upload-time = "2026-02-03T14:00:38.368Z" }, { url = "https://files.pythonhosted.org/packages/bf/10/1921f1a03a7c209e1cb374f81a6b9b68b03cdb3ecc3433c189bc90e2a3d5/coverage-7.13.3-cp312-cp312-win32.whl", hash = "sha256:f7f153d0184d45f3873b3ad3ad22694fd73aadcb8cdbc4337ab4b41ea6b4dff1", size = 221986, upload-time = "2026-02-03T14:00:40.442Z" }, { url = "https://files.pythonhosted.org/packages/3c/7c/f5d93297f8e125a80c15545edc754d93e0ed8ba255b65e609b185296af01/coverage-7.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:03a6e5e1e50819d6d7436f5bc40c92ded7e484e400716886ac921e35c133149d", size = 222793, upload-time = "2026-02-03T14:00:42.106Z" }, { url = "https://files.pythonhosted.org/packages/43/59/c86b84170015b4555ebabca8649bdf9f4a1f737a73168088385ed0f947c4/coverage-7.13.3-cp312-cp312-win_arm64.whl", hash = "sha256:51c4c42c0e7d09a822b08b6cf79b3c4db8333fffde7450da946719ba0d45730f", size = 221410, upload-time = "2026-02-03T14:00:43.726Z" }, { url = "https://files.pythonhosted.org/packages/81/f3/4c333da7b373e8c8bfb62517e8174a01dcc373d7a9083698e3b39d50d59c/coverage-7.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:853c3d3c79ff0db65797aad79dee6be020efd218ac4510f15a205f1e8d13ce25", size = 219468, upload-time = "2026-02-03T14:00:45.829Z" }, { url = "https://files.pythonhosted.org/packages/d6/31/0714337b7d23630c8de2f4d56acf43c65f8728a45ed529b34410683f7217/coverage-7.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f75695e157c83d374f88dcc646a60cb94173304a9258b2e74ba5a66b7614a51a", size = 219839, upload-time = "2026-02-03T14:00:47.407Z" }, { url = "https://files.pythonhosted.org/packages/12/99/bd6f2a2738144c98945666f90cae446ed870cecf0421c767475fcf42cdbe/coverage-7.13.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2d098709621d0819039f3f1e471ee554f55a0b2ac0d816883c765b14129b5627", size = 250828, upload-time = "2026-02-03T14:00:49.029Z" }, { url = "https://files.pythonhosted.org/packages/6f/99/97b600225fbf631e6f5bfd3ad5bcaf87fbb9e34ff87492e5a572ff01bbe2/coverage-7.13.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16d23d6579cf80a474ad160ca14d8b319abaa6db62759d6eef53b2fc979b58c8", size = 253432, upload-time = "2026-02-03T14:00:50.655Z" }, { url = "https://files.pythonhosted.org/packages/5f/5c/abe2b3490bda26bd4f5e3e799be0bdf00bd81edebedc2c9da8d3ef288fa8/coverage-7.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00d34b29a59d2076e6f318b30a00a69bf63687e30cd882984ed444e753990cc1", size = 254672, upload-time = "2026-02-03T14:00:52.757Z" }, { url = "https://files.pythonhosted.org/packages/31/ba/5d1957c76b40daff53971fe0adb84d9c2162b614280031d1d0653dd010c1/coverage-7.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ab6d72bffac9deb6e6cb0f61042e748de3f9f8e98afb0375a8e64b0b6e11746b", size = 251050, upload-time = "2026-02-03T14:00:54.332Z" }, { url = "https://files.pythonhosted.org/packages/69/dc/dffdf3bfe9d32090f047d3c3085378558cb4eb6778cda7de414ad74581ed/coverage-7.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e129328ad1258e49cae0123a3b5fcb93d6c2fa90d540f0b4c7cdcdc019aaa3dc", size = 252801, upload-time = "2026-02-03T14:00:56.121Z" }, { url = "https://files.pythonhosted.org/packages/87/51/cdf6198b0f2746e04511a30dc9185d7b8cdd895276c07bdb538e37f1cd50/coverage-7.13.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2213a8d88ed35459bda71597599d4eec7c2ebad201c88f0bfc2c26fd9b0dd2ea", size = 250763, upload-time = "2026-02-03T14:00:58.719Z" }, { url = "https://files.pythonhosted.org/packages/d7/1a/596b7d62218c1d69f2475b69cc6b211e33c83c902f38ee6ae9766dd422da/coverage-7.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:00dd3f02de6d5f5c9c3d95e3e036c3c2e2a669f8bf2d3ceb92505c4ce7838f67", size = 250587, upload-time = "2026-02-03T14:01:01.197Z" }, { url = "https://files.pythonhosted.org/packages/f7/46/52330d5841ff660f22c130b75f5e1dd3e352c8e7baef5e5fef6b14e3e991/coverage-7.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f9bada7bc660d20b23d7d312ebe29e927b655cf414dadcdb6335a2075695bd86", size = 252358, upload-time = "2026-02-03T14:01:02.824Z" }, { url = "https://files.pythonhosted.org/packages/36/8a/e69a5be51923097ba7d5cff9724466e74fe486e9232020ba97c809a8b42b/coverage-7.13.3-cp313-cp313-win32.whl", hash = "sha256:75b3c0300f3fa15809bd62d9ca8b170eb21fcf0100eb4b4154d6dc8b3a5bbd43", size = 222007, upload-time = "2026-02-03T14:01:04.876Z" }, { url = "https://files.pythonhosted.org/packages/0a/09/a5a069bcee0d613bdd48ee7637fa73bc09e7ed4342b26890f2df97cc9682/coverage-7.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:a2f7589c6132c44c53f6e705e1a6677e2b7821378c22f7703b2cf5388d0d4587", size = 222812, upload-time = "2026-02-03T14:01:07.296Z" }, { url = "https://files.pythonhosted.org/packages/3d/4f/d62ad7dfe32f9e3d4a10c178bb6f98b10b083d6e0530ca202b399371f6c1/coverage-7.13.3-cp313-cp313-win_arm64.whl", hash = "sha256:123ceaf2b9d8c614f01110f908a341e05b1b305d6b2ada98763b9a5a59756051", size = 221433, upload-time = "2026-02-03T14:01:09.156Z" }, { url = "https://files.pythonhosted.org/packages/04/b2/4876c46d723d80b9c5b695f1a11bf5f7c3dabf540ec00d6edc076ff025e6/coverage-7.13.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:cc7fd0f726795420f3678ac82ff882c7fc33770bd0074463b5aef7293285ace9", size = 220162, upload-time = "2026-02-03T14:01:11.409Z" }, { url = "https://files.pythonhosted.org/packages/fc/04/9942b64a0e0bdda2c109f56bda42b2a59d9d3df4c94b85a323c1cae9fc77/coverage-7.13.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d358dc408edc28730aed5477a69338e444e62fba0b7e9e4a131c505fadad691e", size = 220510, upload-time = "2026-02-03T14:01:13.038Z" }, { url = "https://files.pythonhosted.org/packages/5a/82/5cfe1e81eae525b74669f9795f37eb3edd4679b873d79d1e6c1c14ee6c1c/coverage-7.13.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5d67b9ed6f7b5527b209b24b3df9f2e5bf0198c1bbf99c6971b0e2dcb7e2a107", size = 261801, upload-time = "2026-02-03T14:01:14.674Z" }, { url = "https://files.pythonhosted.org/packages/0b/ec/a553d7f742fd2cd12e36a16a7b4b3582d5934b496ef2b5ea8abeb10903d4/coverage-7.13.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59224bfb2e9b37c1335ae35d00daa3a5b4e0b1a20f530be208fff1ecfa436f43", size = 263882, upload-time = "2026-02-03T14:01:16.343Z" }, { url = "https://files.pythonhosted.org/packages/e1/58/8f54a2a93e3d675635bc406de1c9ac8d551312142ff52c9d71b5e533ad45/coverage-7.13.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9306b5299e31e31e0d3b908c66bcb6e7e3ddca143dea0266e9ce6c667346d3", size = 266306, upload-time = "2026-02-03T14:01:18.02Z" }, { url = "https://files.pythonhosted.org/packages/1a/be/e593399fd6ea1f00aee79ebd7cc401021f218d34e96682a92e1bae092ff6/coverage-7.13.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:343aaeb5f8bb7bcd38620fd7bc56e6ee8207847d8c6103a1e7b72322d381ba4a", size = 261051, upload-time = "2026-02-03T14:01:19.757Z" }, { url = "https://files.pythonhosted.org/packages/5c/e5/e9e0f6138b21bcdebccac36fbfde9cf15eb1bbcea9f5b1f35cd1f465fb91/coverage-7.13.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2182129f4c101272ff5f2f18038d7b698db1bf8e7aa9e615cb48440899ad32e", size = 263868, upload-time = "2026-02-03T14:01:21.487Z" }, { url = "https://files.pythonhosted.org/packages/9a/bf/de72cfebb69756f2d4a2dde35efcc33c47d85cd3ebdf844b3914aac2ef28/coverage-7.13.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:94d2ac94bd0cc57c5626f52f8c2fffed1444b5ae8c9fc68320306cc2b255e155", size = 261498, upload-time = "2026-02-03T14:01:23.097Z" }, { url = "https://files.pythonhosted.org/packages/f2/91/4a2d313a70fc2e98ca53afd1c8ce67a89b1944cd996589a5b1fe7fbb3e5c/coverage-7.13.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:65436cde5ecabe26fb2f0bf598962f0a054d3f23ad529361326ac002c61a2a1e", size = 260394, upload-time = "2026-02-03T14:01:24.949Z" }, { url = "https://files.pythonhosted.org/packages/40/83/25113af7cf6941e779eb7ed8de2a677865b859a07ccee9146d4cc06a03e3/coverage-7.13.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db83b77f97129813dbd463a67e5335adc6a6a91db652cc085d60c2d512746f96", size = 262579, upload-time = "2026-02-03T14:01:26.703Z" }, { url = "https://files.pythonhosted.org/packages/1e/19/a5f2b96262977e82fb9aabbe19b4d83561f5d063f18dde3e72f34ffc3b2f/coverage-7.13.3-cp313-cp313t-win32.whl", hash = "sha256:dfb428e41377e6b9ba1b0a32df6db5409cb089a0ed1d0a672dc4953ec110d84f", size = 222679, upload-time = "2026-02-03T14:01:28.553Z" }, { url = "https://files.pythonhosted.org/packages/81/82/ef1747b88c87a5c7d7edc3704799ebd650189a9158e680a063308b6125ef/coverage-7.13.3-cp313-cp313t-win_amd64.whl", hash = "sha256:5badd7e596e6b0c89aa8ec6d37f4473e4357f982ce57f9a2942b0221cd9cf60c", size = 223740, upload-time = "2026-02-03T14:01:30.776Z" }, { url = "https://files.pythonhosted.org/packages/1c/4c/a67c7bb5b560241c22736a9cb2f14c5034149ffae18630323fde787339e4/coverage-7.13.3-cp313-cp313t-win_arm64.whl", hash = "sha256:989aa158c0eb19d83c76c26f4ba00dbb272485c56e452010a3450bdbc9daafd9", size = 221996, upload-time = "2026-02-03T14:01:32.495Z" }, { url = "https://files.pythonhosted.org/packages/5e/b3/677bb43427fed9298905106f39c6520ac75f746f81b8f01104526a8026e4/coverage-7.13.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c6f6169bbdbdb85aab8ac0392d776948907267fcc91deeacf6f9d55f7a83ae3b", size = 219513, upload-time = "2026-02-03T14:01:34.29Z" }, { url = "https://files.pythonhosted.org/packages/42/53/290046e3bbf8986cdb7366a42dab3440b9983711eaff044a51b11006c67b/coverage-7.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2f5e731627a3d5ef11a2a35aa0c6f7c435867c7ccbc391268eb4f2ca5dbdcc10", size = 219850, upload-time = "2026-02-03T14:01:35.984Z" }, { url = "https://files.pythonhosted.org/packages/ea/2b/ab41f10345ba2e49d5e299be8663be2b7db33e77ac1b85cd0af985ea6406/coverage-7.13.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9db3a3285d91c0b70fab9f39f0a4aa37d375873677efe4e71e58d8321e8c5d39", size = 250886, upload-time = "2026-02-03T14:01:38.287Z" }, { url = "https://files.pythonhosted.org/packages/72/2d/b3f6913ee5a1d5cdd04106f257e5fac5d048992ffc2d9995d07b0f17739f/coverage-7.13.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06e49c5897cb12e3f7ecdc111d44e97c4f6d0557b81a7a0204ed70a8b038f86f", size = 253393, upload-time = "2026-02-03T14:01:40.118Z" }, { url = "https://files.pythonhosted.org/packages/f0/f6/b1f48810ffc6accf49a35b9943636560768f0812330f7456aa87dc39aff5/coverage-7.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb25061a66802df9fc13a9ba1967d25faa4dae0418db469264fd9860a921dde4", size = 254740, upload-time = "2026-02-03T14:01:42.413Z" }, { url = "https://files.pythonhosted.org/packages/57/d0/e59c54f9be0b61808f6bc4c8c4346bd79f02dd6bbc3f476ef26124661f20/coverage-7.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:99fee45adbb1caeb914da16f70e557fb7ff6ddc9e4b14de665bd41af631367ef", size = 250905, upload-time = "2026-02-03T14:01:44.163Z" }, { url = "https://files.pythonhosted.org/packages/d5/f7/5291bcdf498bafbee3796bb32ef6966e9915aebd4d0954123c8eae921c32/coverage-7.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:318002f1fd819bdc1651c619268aa5bc853c35fa5cc6d1e8c96bd9cd6c828b75", size = 252753, upload-time = "2026-02-03T14:01:45.974Z" }, { url = "https://files.pythonhosted.org/packages/a0/a9/1dcafa918c281554dae6e10ece88c1add82db685be123e1b05c2056ff3fb/coverage-7.13.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:71295f2d1d170b9977dc386d46a7a1b7cbb30e5405492529b4c930113a33f895", size = 250716, upload-time = "2026-02-03T14:01:48.844Z" }, { url = "https://files.pythonhosted.org/packages/44/bb/4ea4eabcce8c4f6235df6e059fbc5db49107b24c4bdffc44aee81aeca5a8/coverage-7.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5b1ad2e0dc672625c44bc4fe34514602a9fd8b10d52ddc414dc585f74453516c", size = 250530, upload-time = "2026-02-03T14:01:50.793Z" }, { url = "https://files.pythonhosted.org/packages/6d/31/4a6c9e6a71367e6f923b27b528448c37f4e959b7e4029330523014691007/coverage-7.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b2beb64c145593a50d90db5c7178f55daeae129123b0d265bdb3cbec83e5194a", size = 252186, upload-time = "2026-02-03T14:01:52.607Z" }, { url = "https://files.pythonhosted.org/packages/27/92/e1451ef6390a4f655dc42da35d9971212f7abbbcad0bdb7af4407897eb76/coverage-7.13.3-cp314-cp314-win32.whl", hash = "sha256:3d1aed4f4e837a832df2f3b4f68a690eede0de4560a2dbc214ea0bc55aabcdb4", size = 222253, upload-time = "2026-02-03T14:01:55.071Z" }, { url = "https://files.pythonhosted.org/packages/8a/98/78885a861a88de020c32a2693487c37d15a9873372953f0c3c159d575a43/coverage-7.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9f9efbbaf79f935d5fbe3ad814825cbce4f6cdb3054384cb49f0c0f496125fa0", size = 223069, upload-time = "2026-02-03T14:01:56.95Z" }, { url = "https://files.pythonhosted.org/packages/eb/fb/3784753a48da58a5337972abf7ca58b1fb0f1bda21bc7b4fae992fd28e47/coverage-7.13.3-cp314-cp314-win_arm64.whl", hash = "sha256:31b6e889c53d4e6687ca63706148049494aace140cffece1c4dc6acadb70a7b3", size = 221633, upload-time = "2026-02-03T14:01:58.758Z" }, { url = "https://files.pythonhosted.org/packages/40/f9/75b732d9674d32cdbffe801ed5f770786dd1c97eecedef2125b0d25102dc/coverage-7.13.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c5e9787cec750793a19a28df7edd85ac4e49d3fb91721afcdc3b86f6c08d9aa8", size = 220243, upload-time = "2026-02-03T14:02:01.109Z" }, { url = "https://files.pythonhosted.org/packages/cf/7e/2868ec95de5a65703e6f0c87407ea822d1feb3619600fbc3c1c4fa986090/coverage-7.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5b86db331c682fd0e4be7098e6acee5e8a293f824d41487c667a93705d415ca", size = 220515, upload-time = "2026-02-03T14:02:02.862Z" }, { url = "https://files.pythonhosted.org/packages/7d/eb/9f0d349652fced20bcaea0f67fc5777bd097c92369f267975732f3dc5f45/coverage-7.13.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:edc7754932682d52cf6e7a71806e529ecd5ce660e630e8bd1d37109a2e5f63ba", size = 261874, upload-time = "2026-02-03T14:02:04.727Z" }, { url = "https://files.pythonhosted.org/packages/ee/a5/6619bc4a6c7b139b16818149a3e74ab2e21599ff9a7b6811b6afde99f8ec/coverage-7.13.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3a16d6398666510a6886f67f43d9537bfd0e13aca299688a19daa84f543122f", size = 264004, upload-time = "2026-02-03T14:02:06.634Z" }, { url = "https://files.pythonhosted.org/packages/29/b7/90aa3fc645a50c6f07881fca4fd0ba21e3bfb6ce3a7078424ea3a35c74c9/coverage-7.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:303d38b19626c1981e1bb067a9928236d88eb0e4479b18a74812f05a82071508", size = 266408, upload-time = "2026-02-03T14:02:09.037Z" }, { url = "https://files.pythonhosted.org/packages/62/55/08bb2a1e4dcbae384e638f0effef486ba5987b06700e481691891427d879/coverage-7.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:284e06eadfe15ddfee2f4ee56631f164ef897a7d7d5a15bca5f0bb88889fc5ba", size = 260977, upload-time = "2026-02-03T14:02:11.755Z" }, { url = "https://files.pythonhosted.org/packages/9b/76/8bd4ae055a42d8fb5dd2230e5cf36ff2e05f85f2427e91b11a27fea52ed7/coverage-7.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d401f0864a1d3198422816878e4e84ca89ec1c1bf166ecc0ae01380a39b888cd", size = 263868, upload-time = "2026-02-03T14:02:13.565Z" }, { url = "https://files.pythonhosted.org/packages/e3/f9/ba000560f11e9e32ec03df5aa8477242c2d95b379c99ac9a7b2e7fbacb1a/coverage-7.13.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3f379b02c18a64de78c4ccdddf1c81c2c5ae1956c72dacb9133d7dd7809794ab", size = 261474, upload-time = "2026-02-03T14:02:16.069Z" }, { url = "https://files.pythonhosted.org/packages/90/4b/4de4de8f9ca7af4733bfcf4baa440121b7dbb3856daf8428ce91481ff63b/coverage-7.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:7a482f2da9086971efb12daca1d6547007ede3674ea06e16d7663414445c683e", size = 260317, upload-time = "2026-02-03T14:02:17.996Z" }, { url = "https://files.pythonhosted.org/packages/05/71/5cd8436e2c21410ff70be81f738c0dddea91bcc3189b1517d26e0102ccb3/coverage-7.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:562136b0d401992118d9b49fbee5454e16f95f85b120a4226a04d816e33fe024", size = 262635, upload-time = "2026-02-03T14:02:20.405Z" }, { url = "https://files.pythonhosted.org/packages/e7/f8/2834bb45bdd70b55a33ec354b8b5f6062fc90e5bb787e14385903a979503/coverage-7.13.3-cp314-cp314t-win32.whl", hash = "sha256:ca46e5c3be3b195098dd88711890b8011a9fa4feca942292bb84714ce5eab5d3", size = 223035, upload-time = "2026-02-03T14:02:22.323Z" }, { url = "https://files.pythonhosted.org/packages/26/75/f8290f0073c00d9ae14056d2b84ab92dff21d5370e464cb6cb06f52bf580/coverage-7.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:06d316dbb3d9fd44cca05b2dbcfbef22948493d63a1f28e828d43e6cc505fed8", size = 224142, upload-time = "2026-02-03T14:02:24.143Z" }, { url = "https://files.pythonhosted.org/packages/03/01/43ac78dfea8946c4a9161bbc034b5549115cb2b56781a4b574927f0d141a/coverage-7.13.3-cp314-cp314t-win_arm64.whl", hash = "sha256:299d66e9218193f9dc6e4880629ed7c4cd23486005166247c283fb98531656c3", size = 222166, upload-time = "2026-02-03T14:02:26.005Z" }, { url = "https://files.pythonhosted.org/packages/7d/fb/70af542d2d938c778c9373ce253aa4116dbe7c0a5672f78b2b2ae0e1b94b/coverage-7.13.3-py3-none-any.whl", hash = "sha256:90a8af9dba6429b2573199622d72e0ebf024d6276f16abce394ad4d181bb0910", size = 211237, upload-time = "2026-02-03T14:02:27.986Z" }, ] [package.optional-dependencies] toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] [[package]] name = "cubed" version = "0.25.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiostream" }, { name = "array-api-compat" }, { name = "cloudpickle" }, { name = "donfig" }, { name = "mypy-extensions" }, { name = "ndindex" }, { name = "networkx" }, { name = "numpy" }, { name = "psutil" }, { name = "tenacity" }, { name = "toolz" }, { name = "zarr" }, ] sdist = { url = "https://files.pythonhosted.org/packages/db/1c/fa47bdfc952e595dd45bf54592dc5ca60cfde92ddffc95247815a52314fc/cubed-0.25.0.tar.gz", hash = "sha256:f1e37953fa58f14b3de1952908cbb282da7f65f3b998371a8b2c3dc7def1eed0", size = 185715, upload-time = "2026-01-29T10:01:03.888Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/8d/a1/b59296c0471b9a2a6f979bd9f72bcd30eed34774477eea539652fcae6ec5/cubed-0.25.0-py3-none-any.whl", hash = "sha256:903f426220a2cb03b26aff5297c344eda0d2abc704fc57eb5955c1a11a452694", size = 237733, upload-time = "2026-01-29T10:01:02.485Z" }, ] [[package]] name = "cubed-xarray" version = "0.0.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cubed" }, { name = "numpy" }, { name = "xarray" }, ] sdist = { url = "https://files.pythonhosted.org/packages/20/d9/40381f32f73fccc9ab26a642ab49dfa328748db096447edd13e86597e753/cubed_xarray-0.0.9.tar.gz", hash = "sha256:ff1064199c248f78e1a79ca91f5effc8e6854ad47ba45d2d32a5d4f5c26f5482", size = 20319, upload-time = "2025-12-10T11:37:11.22Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/eb/3f/c33fbb5349a202320b4e1b28ce4eeb0560f8823052a00cdd80ff39729c6f/cubed_xarray-0.0.9-py3-none-any.whl", hash = "sha256:f5c90feb6ff3194991a8f7c14059dd01eb7c3222b8b259a071830a7f9a5c9127", size = 16766, upload-time = "2025-12-10T11:37:10.145Z" }, ] [[package]] name = "cycler" version = "0.12.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, ] [[package]] name = "cython" version = "3.2.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/91/85/7574c9cd44b69a27210444b6650f6477f56c75fee1b70d7672d3e4166167/cython-3.2.4.tar.gz", hash = "sha256:84226ecd313b233da27dc2eb3601b4f222b8209c3a7216d8733b031da1dc64e6", size = 3280291, upload-time = "2026-01-04T14:14:14.473Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/85/cc/8f06145ec3efa121c8b1b67f06a640386ddacd77ee3e574da582a21b14ee/cython-3.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff9af2134c05e3734064808db95b4dd7341a39af06e8945d05ea358e1741aaed", size = 2953769, upload-time = "2026-01-04T14:15:00.361Z" }, { url = "https://files.pythonhosted.org/packages/55/b0/706cf830eddd831666208af1b3058c2e0758ae157590909c1f634b53bed9/cython-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67922c9de058a0bfb72d2e75222c52d09395614108c68a76d9800f150296ddb3", size = 3243841, upload-time = "2026-01-04T14:15:02.066Z" }, { url = "https://files.pythonhosted.org/packages/ac/25/58893afd4ef45f79e3d4db82742fa4ff874b936d67a83c92939053920ccd/cython-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b362819d155fff1482575e804e43e3a8825332d32baa15245f4642022664a3f4", size = 3378083, upload-time = "2026-01-04T14:15:04.248Z" }, { url = "https://files.pythonhosted.org/packages/32/e4/424a004d7c0d8a4050c81846ebbd22272ececfa9a498cb340aa44fccbec2/cython-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:1a64a112a34ec719b47c01395647e54fb4cf088a511613f9a3a5196694e8e382", size = 2769990, upload-time = "2026-01-04T14:15:06.53Z" }, { url = "https://files.pythonhosted.org/packages/91/4d/1eb0c7c196a136b1926f4d7f0492a96c6fabd604d77e6cd43b56a3a16d83/cython-3.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:64d7f71be3dd6d6d4a4c575bb3a4674ea06d1e1e5e4cd1b9882a2bc40ed3c4c9", size = 2970064, upload-time = "2026-01-04T14:15:08.567Z" }, { url = "https://files.pythonhosted.org/packages/03/1c/46e34b08bea19a1cdd1e938a4c123e6299241074642db9d81983cef95e9f/cython-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:869487ea41d004f8b92171f42271fbfadb1ec03bede3158705d16cd570d6b891", size = 3226757, upload-time = "2026-01-04T14:15:10.812Z" }, { url = "https://files.pythonhosted.org/packages/12/33/3298a44d201c45bcf0d769659725ae70e9c6c42adf8032f6d89c8241098d/cython-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:55b6c44cd30821f0b25220ceba6fe636ede48981d2a41b9bbfe3c7902ce44ea7", size = 3388969, upload-time = "2026-01-04T14:15:12.45Z" }, { url = "https://files.pythonhosted.org/packages/bb/f3/4275cd3ea0a4cf4606f9b92e7f8766478192010b95a7f516d1b7cf22cb10/cython-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:767b143704bdd08a563153448955935844e53b852e54afdc552b43902ed1e235", size = 2756457, upload-time = "2026-01-04T14:15:14.67Z" }, { url = "https://files.pythonhosted.org/packages/18/b5/1cfca43b7d20a0fdb1eac67313d6bb6b18d18897f82dd0f17436bdd2ba7f/cython-3.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:28e8075087a59756f2d059273184b8b639fe0f16cf17470bd91c39921bc154e0", size = 2960506, upload-time = "2026-01-04T14:15:16.733Z" }, { url = "https://files.pythonhosted.org/packages/71/bb/8f28c39c342621047fea349a82fac712a5e2b37546d2f737bbde48d5143d/cython-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03893c88299a2c868bb741ba6513357acd104e7c42265809fd58dce1456a36fc", size = 3213148, upload-time = "2026-01-04T14:15:18.804Z" }, { url = "https://files.pythonhosted.org/packages/7a/d2/16fa02f129ed2b627e88d9d9ebd5ade3eeb66392ae5ba85b259d2d52b047/cython-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f81eda419b5ada7b197bbc3c5f4494090e3884521ffd75a3876c93fbf66c9ca8", size = 3375764, upload-time = "2026-01-04T14:15:20.817Z" }, { url = "https://files.pythonhosted.org/packages/91/3f/deb8f023a5c10c0649eb81332a58c180fad27c7533bb4aae138b5bc34d92/cython-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:83266c356c13c68ffe658b4905279c993d8a5337bb0160fa90c8a3e297ea9a2e", size = 2754238, upload-time = "2026-01-04T14:15:23.001Z" }, { url = "https://files.pythonhosted.org/packages/ee/d7/3bda3efce0c5c6ce79cc21285dbe6f60369c20364e112f5a506ee8a1b067/cython-3.2.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d4b4fd5332ab093131fa6172e8362f16adef3eac3179fd24bbdc392531cb82fa", size = 2971496, upload-time = "2026-01-04T14:15:25.038Z" }, { url = "https://files.pythonhosted.org/packages/89/ed/1021ffc80b9c4720b7ba869aea8422c82c84245ef117ebe47a556bdc00c3/cython-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3b5ac54e95f034bc7fb07313996d27cbf71abc17b229b186c1540942d2dc28e", size = 3256146, upload-time = "2026-01-04T14:15:26.741Z" }, { url = "https://files.pythonhosted.org/packages/0c/51/ca221ec7e94b3c5dc4138dcdcbd41178df1729c1e88c5dfb25f9d30ba3da/cython-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f43be4eaa6afd58ce20d970bb1657a3627c44e1760630b82aa256ba74b4acb", size = 3383458, upload-time = "2026-01-04T14:15:28.425Z" }, { url = "https://files.pythonhosted.org/packages/79/2e/1388fc0243240cd54994bb74f26aaaf3b2e22f89d3a2cf8da06d75d46ca2/cython-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:983f9d2bb8a896e16fa68f2b37866ded35fa980195eefe62f764ddc5f9f5ef8e", size = 2791241, upload-time = "2026-01-04T14:15:30.448Z" }, { url = "https://files.pythonhosted.org/packages/0a/8b/fd393f0923c82be4ec0db712fffb2ff0a7a131707b842c99bf24b549274d/cython-3.2.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:36bf3f5eb56d5281aafabecbaa6ed288bc11db87547bba4e1e52943ae6961ccf", size = 2875622, upload-time = "2026-01-04T14:15:39.749Z" }, { url = "https://files.pythonhosted.org/packages/73/48/48530d9b9d64ec11dbe0dd3178a5fe1e0b27977c1054ecffb82be81e9b6a/cython-3.2.4-cp39-abi3-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6d5267f22b6451eb1e2e1b88f6f78a2c9c8733a6ddefd4520d3968d26b824581", size = 3210669, upload-time = "2026-01-04T14:15:41.911Z" }, { url = "https://files.pythonhosted.org/packages/5e/91/4865fbfef1f6bb4f21d79c46104a53d1a3fa4348286237e15eafb26e0828/cython-3.2.4-cp39-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3b6e58f73a69230218d5381817850ce6d0da5bb7e87eb7d528c7027cbba40b06", size = 2856835, upload-time = "2026-01-04T14:15:43.815Z" }, { url = "https://files.pythonhosted.org/packages/fa/39/60317957dbef179572398253f29d28f75f94ab82d6d39ea3237fb6c89268/cython-3.2.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e71efb20048358a6b8ec604a0532961c50c067b5e63e345e2e359fff72feaee8", size = 2994408, upload-time = "2026-01-04T14:15:45.422Z" }, { url = "https://files.pythonhosted.org/packages/8d/30/7c24d9292650db4abebce98abc9b49c820d40fa7c87921c0a84c32f4efe7/cython-3.2.4-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:28b1e363b024c4b8dcf52ff68125e635cb9cb4b0ba997d628f25e32543a71103", size = 2891478, upload-time = "2026-01-04T14:15:47.394Z" }, { url = "https://files.pythonhosted.org/packages/86/70/03dc3c962cde9da37a93cca8360e576f904d5f9beecfc9d70b1f820d2e5f/cython-3.2.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:31a90b4a2c47bb6d56baeb926948348ec968e932c1ae2c53239164e3e8880ccf", size = 3225663, upload-time = "2026-01-04T14:15:49.446Z" }, { url = "https://files.pythonhosted.org/packages/b1/97/10b50c38313c37b1300325e2e53f48ea9a2c078a85c0c9572057135e31d5/cython-3.2.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e65e4773021f8dc8532010b4fbebe782c77f9a0817e93886e518c93bd6a44e9d", size = 3115628, upload-time = "2026-01-04T14:15:51.323Z" }, { url = "https://files.pythonhosted.org/packages/8f/b1/d6a353c9b147848122a0db370863601fdf56de2d983b5c4a6a11e6ee3cd7/cython-3.2.4-cp39-abi3-win32.whl", hash = "sha256:2b1f12c0e4798293d2754e73cd6f35fa5bbdf072bdc14bc6fc442c059ef2d290", size = 2437463, upload-time = "2026-01-04T14:15:53.787Z" }, { url = "https://files.pythonhosted.org/packages/2d/d8/319a1263b9c33b71343adfd407e5daffd453daef47ebc7b642820a8b68ed/cython-3.2.4-cp39-abi3-win_arm64.whl", hash = "sha256:3b8e62049afef9da931d55de82d8f46c9a147313b69d5ff6af6e9121d545ce7a", size = 2442754, upload-time = "2026-01-04T14:15:55.382Z" }, { url = "https://files.pythonhosted.org/packages/ff/fa/d3c15189f7c52aaefbaea76fb012119b04b9013f4bf446cb4eb4c26c4e6b/cython-3.2.4-py3-none-any.whl", hash = "sha256:732fc93bc33ae4b14f6afaca663b916c2fdd5dcbfad7114e17fb2434eeaea45c", size = 1257078, upload-time = "2026-01-04T14:14:12.373Z" }, ] [[package]] name = "dask" version = "2026.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "cloudpickle" }, { name = "fsspec" }, { name = "importlib-metadata", marker = "python_full_version < '3.12'" }, { name = "packaging" }, { name = "partd" }, { name = "pyyaml" }, { name = "toolz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bd/52/b0f9172b22778def907db1ff173249e4eb41f054b46a9c83b1528aaf811f/dask-2026.1.2.tar.gz", hash = "sha256:1136683de2750d98ea792670f7434e6c1cfce90cab2cc2f2495a9e60fd25a4fc", size = 10997838, upload-time = "2026-01-30T21:04:20.54Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl", hash = "sha256:46a0cf3b8d87f78a3d2e6b145aea4418a6d6d606fe6a16c79bd8ca2bb862bc91", size = 1482084, upload-time = "2026-01-30T21:04:18.363Z" }, ] [package.optional-dependencies] complete = [ { name = "bokeh" }, { name = "distributed" }, { name = "jinja2" }, { name = "lz4" }, { name = "numpy" }, { name = "pandas" }, { name = "pyarrow" }, ] [[package]] name = "debugpy" version = "1.8.20" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/51/56/c3baf5cbe4dd77427fd9aef99fcdade259ad128feeb8a786c246adb838e5/debugpy-1.8.20-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:eada6042ad88fa1571b74bd5402ee8b86eded7a8f7b827849761700aff171f1b", size = 2208318, upload-time = "2026-01-29T23:03:36.481Z" }, { url = "https://files.pythonhosted.org/packages/9a/7d/4fa79a57a8e69fe0d9763e98d1110320f9ecd7f1f362572e3aafd7417c9d/debugpy-1.8.20-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:7de0b7dfeedc504421032afba845ae2a7bcc32ddfb07dae2c3ca5442f821c344", size = 3171493, upload-time = "2026-01-29T23:03:37.775Z" }, { url = "https://files.pythonhosted.org/packages/7d/f2/1e8f8affe51e12a26f3a8a8a4277d6e60aa89d0a66512f63b1e799d424a4/debugpy-1.8.20-cp311-cp311-win32.whl", hash = "sha256:773e839380cf459caf73cc533ea45ec2737a5cc184cf1b3b796cd4fd98504fec", size = 5209240, upload-time = "2026-01-29T23:03:39.109Z" }, { url = "https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl", hash = "sha256:1f7650546e0eded1902d0f6af28f787fa1f1dbdbc97ddabaf1cd963a405930cb", size = 5233481, upload-time = "2026-01-29T23:03:40.659Z" }, { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" }, { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" }, { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" }, { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" }, { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560, upload-time = "2026-01-29T23:03:48.738Z" }, { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272, upload-time = "2026-01-29T23:03:50.169Z" }, { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208, upload-time = "2026-01-29T23:03:51.547Z" }, { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930, upload-time = "2026-01-29T23:03:53.585Z" }, { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066, upload-time = "2026-01-29T23:03:54.999Z" }, { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425, upload-time = "2026-01-29T23:03:56.518Z" }, { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407, upload-time = "2026-01-29T23:03:58.481Z" }, { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521, upload-time = "2026-01-29T23:03:59.864Z" }, { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, ] [[package]] name = "decorator" version = "5.2.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, ] [[package]] name = "defusedxml" version = "0.7.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, ] [[package]] name = "distlib" version = "0.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] [[package]] name = "distributed" version = "2026.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "cloudpickle" }, { name = "dask" }, { name = "jinja2" }, { name = "locket" }, { name = "msgpack" }, { name = "packaging" }, { name = "psutil" }, { name = "pyyaml" }, { name = "sortedcontainers" }, { name = "tblib" }, { name = "toolz" }, { name = "tornado" }, { name = "urllib3" }, { name = "zict" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4e/75/b6e5b77229097ff03dd5ba6a07c77e2da87e7e991ccfef412549bba78746/distributed-2026.1.2.tar.gz", hash = "sha256:8333fa7a34151ed3b4cf1a03136fe1f1799eca706a5e47bdb63022c8795d853b", size = 2103721, upload-time = "2026-01-30T21:07:03.307Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ad/14/0fe5889a83991ac29c93e6b2e121ad2afc3bff5f9327f34447d3068d8142/distributed-2026.1.2-py3-none-any.whl", hash = "sha256:30ccb5587351f50304f6f6e219ea91bc09d88401125779caa8be5253e9d3ecf2", size = 1009083, upload-time = "2026-01-30T21:07:01.363Z" }, ] [[package]] name = "docutils" version = "0.22.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, ] [[package]] name = "donfig" version = "0.8.1.post1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyyaml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/25/71/80cc718ff6d7abfbabacb1f57aaa42e9c1552bfdd01e64ddd704e4a03638/donfig-0.8.1.post1.tar.gz", hash = "sha256:3bef3413a4c1c601b585e8d297256d0c1470ea012afa6e8461dc28bfb7c23f52", size = 19506, upload-time = "2024-05-23T14:14:31.513Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0c/d5/c5db1ea3394c6e1732fb3286b3bd878b59507a8f77d32a2cebda7d7b7cd4/donfig-0.8.1.post1-py3-none-any.whl", hash = "sha256:2a3175ce74a06109ff9307d90a230f81215cbac9a751f4d1c6194644b8204f9d", size = 21592, upload-time = "2024-05-23T14:13:55.283Z" }, ] [[package]] name = "execnet" version = "2.1.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, ] [[package]] name = "executing" version = "2.2.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, ] [[package]] name = "fancycompleter" version = "0.11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyreadline3", marker = "python_full_version < '3.13' and sys_platform == 'win32'" }, { name = "pyrepl", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4e/4c/d11187dee93eff89d082afda79b63c79320ae1347e49485a38f05ad359d0/fancycompleter-0.11.1.tar.gz", hash = "sha256:5b4ad65d76b32b1259251516d0f1cb2d82832b1ff8506697a707284780757f69", size = 341776, upload-time = "2025-05-26T12:59:11.045Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/30/c3/6f0e3896f193528bbd2b4d2122d4be8108a37efab0b8475855556a8c4afa/fancycompleter-0.11.1-py3-none-any.whl", hash = "sha256:44243d7fab37087208ca5acacf8f74c0aa4d733d04d593857873af7513cdf8a6", size = 11207, upload-time = "2025-05-26T12:59:09.857Z" }, ] [[package]] name = "fastjsonschema" version = "2.21.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130, upload-time = "2025-08-14T18:49:36.666Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" }, ] [[package]] name = "filelock" version = "3.20.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, ] [[package]] name = "flox" source = { editable = "." } dependencies = [ { name = "numpy" }, { name = "numpy-groupies" }, { name = "packaging" }, { name = "pandas" }, { name = "scipy" }, { name = "toolz" }, ] [package.optional-dependencies] all = [ { name = "cachey" }, { name = "dask" }, { name = "numba" }, { name = "numbagg" }, { name = "xarray" }, ] docs = [ { name = "cubed" }, { name = "cubed-xarray" }, { name = "dask" }, { name = "furo" }, { name = "ipykernel" }, { name = "jupyter" }, { name = "matplotlib" }, { name = "myst-nb" }, { name = "myst-parser" }, { name = "numpydoc" }, { name = "pyarrow" }, { name = "sparse" }, { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "sphinx-codeautolink" }, { name = "sphinx-copybutton" }, { name = "sphinx-remove-toctrees" }, { name = "xarray" }, ] test = [ { name = "netcdf4" }, ] [package.dev-dependencies] all = [ { name = "cachey" }, { name = "dask" }, { name = "numba" }, { name = "numbagg" }, { name = "xarray" }, ] benchmark = [ { name = "asv" }, { name = "build" }, { name = "cachey" }, { name = "dask" }, { name = "numbagg" }, { name = "py-rattler" }, { name = "pyarrow" }, { name = "wheel" }, { name = "xarray" }, ] complete = [ { name = "asv" }, { name = "cachey" }, { name = "cftime" }, { name = "codecov" }, { name = "cubed" }, { name = "dask" }, { name = "hypothesis" }, { name = "lxml" }, { name = "matplotlib" }, { name = "numba" }, { name = "numbagg" }, { name = "pooch" }, { name = "pre-commit" }, { name = "pyarrow" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-xdist" }, { name = "sparse" }, { name = "syrupy" }, { name = "xarray" }, { name = "zarr" }, ] dev = [ { name = "asv" }, { name = "cachey" }, { name = "cftime" }, { name = "codecov" }, { name = "cubed" }, { name = "dask", extra = ["complete"] }, { name = "hypothesis" }, { name = "ipykernel" }, { name = "line-profiler" }, { name = "lxml" }, { name = "matplotlib" }, { name = "memory-profiler" }, { name = "memray" }, { name = "numba" }, { name = "numbagg" }, { name = "pdbpp" }, { name = "pooch" }, { name = "pre-commit" }, { name = "pyarrow" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-xdist" }, { name = "snakeviz" }, { name = "sparse" }, { name = "syrupy" }, { name = "xarray" }, { name = "zarr" }, ] docs = [ { name = "cubed" }, { name = "cubed-xarray" }, { name = "dask" }, { name = "furo" }, { name = "ipykernel" }, { name = "jupyter" }, { name = "matplotlib" }, { name = "myst-nb" }, { name = "myst-parser" }, { name = "numpydoc" }, { name = "sparse" }, { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "sphinx-codeautolink" }, { name = "sphinx-copybutton" }, { name = "sphinx-remove-toctrees" }, { name = "xarray" }, ] minimal = [ { name = "codecov" }, { name = "hypothesis" }, { name = "pooch" }, { name = "pyarrow" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-xdist" }, { name = "syrupy" }, ] no-dask = [ { name = "cftime" }, { name = "codecov" }, { name = "hypothesis" }, { name = "numba" }, { name = "numbagg" }, { name = "numpydoc" }, { name = "pooch" }, { name = "pre-commit" }, { name = "pyarrow" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-xdist" }, { name = "sparse" }, { name = "syrupy" }, { name = "xarray" }, ] no-numba = [ { name = "asv" }, { name = "cachey" }, { name = "cftime" }, { name = "codecov" }, { name = "dask" }, { name = "hypothesis" }, { name = "lxml" }, { name = "matplotlib" }, { name = "pooch" }, { name = "pre-commit" }, { name = "pyarrow" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-xdist" }, { name = "sparse" }, { name = "syrupy" }, { name = "xarray" }, ] no-xarray = [ { name = "codecov" }, { name = "dask" }, { name = "hypothesis" }, { name = "numba" }, { name = "numpydoc" }, { name = "pooch" }, { name = "pre-commit" }, { name = "pyarrow" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-xdist" }, { name = "sparse" }, { name = "syrupy" }, ] numpy1 = [ { name = "asv" }, { name = "cachey" }, { name = "cftime" }, { name = "codecov" }, { name = "cubed" }, { name = "dask" }, { name = "hypothesis" }, { name = "lxml" }, { name = "matplotlib" }, { name = "numba" }, { name = "numbagg" }, { name = "pooch" }, { name = "pre-commit" }, { name = "pyarrow" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-xdist" }, { name = "sparse" }, { name = "syrupy" }, { name = "xarray" }, { name = "zarr" }, ] test = [ { name = "codecov" }, { name = "hypothesis" }, { name = "pooch" }, { name = "pyarrow" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-xdist" }, { name = "syrupy" }, ] types = [ { name = "mypy" }, { name = "pandas-stubs" }, { name = "types-defusedxml" }, { name = "types-docutils" }, { name = "types-pexpect" }, { name = "types-psutil" }, { name = "types-pygments" }, { name = "types-pytz" }, { name = "types-pyyaml" }, { name = "types-requests" }, { name = "types-setuptools" }, ] upstream = [ { name = "asv-runner" }, { name = "cachey" }, { name = "codecov" }, { name = "cython" }, { name = "hypothesis" }, { name = "pooch" }, { name = "py-cpuinfo" }, { name = "pyarrow" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-reportlog" }, { name = "pytest-xdist" }, { name = "python-dateutil" }, { name = "pytz" }, { name = "syrupy" }, { name = "toolz" }, ] upstream-dev = [ { name = "asv-runner" }, { name = "cachey" }, { name = "cftime" }, { name = "codecov" }, { name = "cython" }, { name = "dask" }, { name = "hypothesis" }, { name = "numpy" }, { name = "numpy-groupies" }, { name = "pandas" }, { name = "pooch" }, { name = "py-cpuinfo" }, { name = "pyarrow" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-reportlog" }, { name = "pytest-xdist" }, { name = "python-dateutil" }, { name = "pytz" }, { name = "scipy" }, { name = "sparse" }, { name = "syrupy" }, { name = "toolz" }, { name = "xarray" }, ] [package.metadata] requires-dist = [ { name = "cachey", marker = "extra == 'all'" }, { name = "cubed", marker = "extra == 'docs'", specifier = ">=0.20.0" }, { name = "cubed-xarray", marker = "extra == 'docs'" }, { name = "dask", marker = "extra == 'all'" }, { name = "dask", marker = "extra == 'docs'" }, { name = "furo", marker = "extra == 'docs'", specifier = ">=2024.8" }, { name = "ipykernel", marker = "extra == 'docs'" }, { name = "jupyter", marker = "extra == 'docs'" }, { name = "matplotlib", marker = "extra == 'docs'" }, { name = "myst-nb", marker = "extra == 'docs'" }, { name = "myst-parser", marker = "extra == 'docs'" }, { name = "netcdf4", marker = "extra == 'test'" }, { name = "numba", marker = "extra == 'all'" }, { name = "numbagg", marker = "extra == 'all'" }, { name = "numpy", specifier = ">=1.26" }, { name = "numpy-groupies", specifier = ">=0.9.19" }, { name = "numpydoc", marker = "extra == 'docs'" }, { name = "packaging", specifier = ">=21.3" }, { name = "pandas", specifier = ">=2.1" }, { name = "pyarrow", marker = "extra == 'docs'" }, { name = "scipy", specifier = ">=1.12" }, { name = "sparse", marker = "extra == 'docs'" }, { name = "sphinx", marker = "extra == 'docs'" }, { name = "sphinx-codeautolink", marker = "extra == 'docs'" }, { name = "sphinx-copybutton", marker = "extra == 'docs'" }, { name = "sphinx-remove-toctrees", marker = "extra == 'docs'" }, { name = "toolz" }, { name = "xarray", marker = "extra == 'all'" }, { name = "xarray", marker = "extra == 'docs'" }, ] provides-extras = ["all", "test", "docs"] [package.metadata.requires-dev] all = [ { name = "cachey" }, { name = "dask" }, { name = "numba" }, { name = "numbagg", specifier = ">=0.3" }, { name = "xarray" }, ] benchmark = [ { name = "asv", specifier = ">=0.6.4" }, { name = "build" }, { name = "cachey" }, { name = "dask", extras = ["core"] }, { name = "numbagg", specifier = ">=0.3" }, { name = "py-rattler" }, { name = "pyarrow" }, { name = "wheel" }, { name = "xarray" }, ] complete = [ { name = "asv" }, { name = "cachey" }, { name = "cftime" }, { name = "codecov" }, { name = "cubed", specifier = ">=0.20.0" }, { name = "dask" }, { name = "hypothesis" }, { name = "lxml" }, { name = "matplotlib" }, { name = "numba" }, { name = "numbagg", specifier = ">=0.3" }, { name = "pooch" }, { name = "pre-commit" }, { name = "pyarrow" }, { name = "pytest", specifier = ">=7" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-xdist" }, { name = "sparse" }, { name = "syrupy" }, { name = "xarray" }, { name = "zarr" }, ] dev = [ { name = "asv" }, { name = "cachey" }, { name = "cftime" }, { name = "codecov" }, { name = "cubed", specifier = ">=0.20.0" }, { name = "dask" }, { name = "dask", extras = ["complete"] }, { name = "hypothesis" }, { name = "ipykernel" }, { name = "line-profiler" }, { name = "lxml" }, { name = "matplotlib" }, { name = "memory-profiler" }, { name = "memray" }, { name = "numba" }, { name = "numbagg", specifier = ">=0.3" }, { name = "pdbpp" }, { name = "pooch" }, { name = "pre-commit" }, { name = "pyarrow" }, { name = "pytest", specifier = ">=7" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-xdist" }, { name = "snakeviz" }, { name = "sparse" }, { name = "syrupy" }, { name = "xarray" }, { name = "zarr" }, ] docs = [ { name = "cubed", specifier = ">=0.20.0" }, { name = "cubed-xarray" }, { name = "dask" }, { name = "furo", specifier = ">=2024.8" }, { name = "ipykernel" }, { name = "jupyter" }, { name = "matplotlib" }, { name = "myst-nb" }, { name = "myst-parser" }, { name = "numpydoc" }, { name = "sparse" }, { name = "sphinx" }, { name = "sphinx-codeautolink" }, { name = "sphinx-copybutton" }, { name = "sphinx-remove-toctrees" }, { name = "xarray" }, ] minimal = [ { name = "codecov" }, { name = "hypothesis" }, { name = "pooch" }, { name = "pyarrow" }, { name = "pytest", specifier = ">=7" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-xdist" }, { name = "syrupy" }, ] no-dask = [ { name = "cftime" }, { name = "codecov" }, { name = "hypothesis" }, { name = "numba" }, { name = "numbagg", specifier = ">=0.3" }, { name = "numpydoc" }, { name = "pooch" }, { name = "pre-commit" }, { name = "pyarrow" }, { name = "pytest", specifier = ">=7" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-xdist" }, { name = "sparse" }, { name = "syrupy" }, { name = "xarray" }, ] no-numba = [ { name = "asv" }, { name = "cachey" }, { name = "cftime" }, { name = "codecov" }, { name = "dask", extras = ["core"] }, { name = "hypothesis" }, { name = "lxml" }, { name = "matplotlib" }, { name = "pooch" }, { name = "pre-commit" }, { name = "pyarrow" }, { name = "pytest", specifier = ">=7" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-xdist" }, { name = "sparse" }, { name = "syrupy" }, { name = "xarray" }, ] no-xarray = [ { name = "codecov" }, { name = "dask", extras = ["core"] }, { name = "hypothesis" }, { name = "numba" }, { name = "numpydoc" }, { name = "pooch" }, { name = "pre-commit" }, { name = "pyarrow" }, { name = "pytest", specifier = ">=7" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-xdist" }, { name = "sparse" }, { name = "syrupy" }, ] numpy1 = [ { name = "asv" }, { name = "cachey" }, { name = "cftime" }, { name = "codecov" }, { name = "cubed", specifier = ">=0.20.0" }, { name = "dask" }, { name = "hypothesis" }, { name = "lxml" }, { name = "matplotlib" }, { name = "numba" }, { name = "numbagg", specifier = ">=0.3" }, { name = "pooch" }, { name = "pre-commit" }, { name = "pyarrow" }, { name = "pytest", specifier = ">=7" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-xdist" }, { name = "sparse" }, { name = "syrupy" }, { name = "xarray" }, { name = "zarr" }, ] test = [ { name = "codecov" }, { name = "hypothesis" }, { name = "pooch" }, { name = "pyarrow" }, { name = "pytest", specifier = ">=7" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-xdist" }, { name = "syrupy" }, ] types = [ { name = "mypy" }, { name = "pandas-stubs" }, { name = "types-defusedxml" }, { name = "types-docutils" }, { name = "types-pexpect" }, { name = "types-psutil" }, { name = "types-pygments" }, { name = "types-pytz" }, { name = "types-pyyaml" }, { name = "types-requests" }, { name = "types-setuptools" }, ] upstream = [ { name = "asv-runner" }, { name = "cachey" }, { name = "codecov" }, { name = "cython", specifier = ">=0.29.20" }, { name = "hypothesis" }, { name = "pooch" }, { name = "py-cpuinfo" }, { name = "pyarrow" }, { name = "pytest", specifier = ">=7" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-reportlog" }, { name = "pytest-xdist" }, { name = "python-dateutil" }, { name = "pytz" }, { name = "syrupy" }, { name = "toolz" }, ] upstream-dev = [ { name = "asv-runner" }, { name = "cachey" }, { name = "cftime" }, { name = "codecov" }, { name = "cython", specifier = ">=0.29.20" }, { name = "dask", extras = ["core"] }, { name = "hypothesis" }, { name = "numpy" }, { name = "numpy-groupies" }, { name = "pandas" }, { name = "pooch" }, { name = "py-cpuinfo" }, { name = "pyarrow" }, { name = "pytest", specifier = ">=7" }, { name = "pytest-cov" }, { name = "pytest-pretty" }, { name = "pytest-reportlog" }, { name = "pytest-xdist" }, { name = "python-dateutil" }, { name = "pytz" }, { name = "scipy" }, { name = "sparse" }, { name = "syrupy" }, { name = "toolz" }, { name = "xarray" }, ] [[package]] name = "fonttools" version = "4.61.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" }, { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" }, { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" }, { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" }, { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" }, { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" }, { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" }, { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" }, { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" }, { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" }, { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" }, { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" }, { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" }, { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" }, { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" }, { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" }, { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" }, { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" }, { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" }, { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" }, { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" }, { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" }, { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" }, { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" }, { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, ] [[package]] name = "fqdn" version = "1.5.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" }, ] [[package]] name = "fsspec" version = "2026.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d5/7d/5df2650c57d47c57232af5ef4b4fdbff182070421e405e0d62c6cdbfaa87/fsspec-2026.1.0.tar.gz", hash = "sha256:e987cb0496a0d81bba3a9d1cee62922fb395e7d4c3b575e57f547953334fe07b", size = 310496, upload-time = "2026-01-09T15:21:35.562Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/01/c9/97cc5aae1648dcb851958a3ddf73ccd7dbe5650d95203ecb4d7720b4cdbf/fsspec-2026.1.0-py3-none-any.whl", hash = "sha256:cb76aa913c2285a3b49bdd5fc55b1d7c708d7208126b60f2eb8194fe1b4cbdcc", size = 201838, upload-time = "2026-01-09T15:21:34.041Z" }, ] [[package]] name = "furo" version = "2025.12.19" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "accessible-pygments" }, { name = "beautifulsoup4" }, { name = "pygments" }, { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "sphinx-basic-ng" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ec/20/5f5ad4da6a5a27c80f2ed2ee9aee3f9e36c66e56e21c00fde467b2f8f88f/furo-2025.12.19.tar.gz", hash = "sha256:188d1f942037d8b37cd3985b955839fea62baa1730087dc29d157677c857e2a7", size = 1661473, upload-time = "2025-12-19T17:34:40.889Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f4/b2/50e9b292b5cac13e9e81272c7171301abc753a60460d21505b606e15cf21/furo-2025.12.19-py3-none-any.whl", hash = "sha256:bb0ead5309f9500130665a26bee87693c41ce4dbdff864dbfb6b0dae4673d24f", size = 339262, upload-time = "2025-12-19T17:34:38.905Z" }, ] [[package]] name = "google-crc32c" version = "1.8.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192, upload-time = "2025-12-16T00:35:25.142Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5d/ef/21ccfaab3d5078d41efe8612e0ed0bfc9ce22475de074162a91a25f7980d/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:014a7e68d623e9a4222d663931febc3033c5c7c9730785727de2a81f87d5bab8", size = 31298, upload-time = "2025-12-16T00:20:32.241Z" }, { url = "https://files.pythonhosted.org/packages/c5/b8/f8413d3f4b676136e965e764ceedec904fe38ae8de0cdc52a12d8eb1096e/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:86cfc00fe45a0ac7359e5214a1704e51a99e757d0272554874f419f79838c5f7", size = 30872, upload-time = "2025-12-16T00:33:58.785Z" }, { url = "https://files.pythonhosted.org/packages/f6/fd/33aa4ec62b290477181c55bb1c9302c9698c58c0ce9a6ab4874abc8b0d60/google_crc32c-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:19b40d637a54cb71e0829179f6cb41835f0fbd9e8eb60552152a8b52c36cbe15", size = 33243, upload-time = "2025-12-16T00:40:21.46Z" }, { url = "https://files.pythonhosted.org/packages/71/03/4820b3bd99c9653d1a5210cb32f9ba4da9681619b4d35b6a052432df4773/google_crc32c-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:17446feb05abddc187e5441a45971b8394ea4c1b6efd88ab0af393fd9e0a156a", size = 33608, upload-time = "2025-12-16T00:40:22.204Z" }, { url = "https://files.pythonhosted.org/packages/7c/43/acf61476a11437bf9733fb2f70599b1ced11ec7ed9ea760fdd9a77d0c619/google_crc32c-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:71734788a88f551fbd6a97be9668a0020698e07b2bf5b3aa26a36c10cdfb27b2", size = 34439, upload-time = "2025-12-16T00:35:20.458Z" }, { url = "https://files.pythonhosted.org/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300, upload-time = "2025-12-16T00:21:56.723Z" }, { url = "https://files.pythonhosted.org/packages/21/8e/58c0d5d86e2220e6a37befe7e6a94dd2f6006044b1a33edf1ff6d9f7e319/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:2a3dc3318507de089c5384cc74d54318401410f82aa65b2d9cdde9d297aca7cb", size = 30867, upload-time = "2025-12-16T00:38:31.302Z" }, { url = "https://files.pythonhosted.org/packages/ce/a9/a780cc66f86335a6019f557a8aaca8fbb970728f0efd2430d15ff1beae0e/google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411", size = 33364, upload-time = "2025-12-16T00:40:22.96Z" }, { url = "https://files.pythonhosted.org/packages/21/3f/3457ea803db0198c9aaca2dd373750972ce28a26f00544b6b85088811939/google_crc32c-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb5c869c2923d56cb0c8e6bcdd73c009c36ae39b652dbe46a05eb4ef0ad01454", size = 33740, upload-time = "2025-12-16T00:40:23.96Z" }, { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437, upload-time = "2025-12-16T00:35:21.395Z" }, { url = "https://files.pythonhosted.org/packages/d1/db/000f15b41724589b0e7bc24bc7a8967898d8d3bc8caf64c513d91ef1f6c0/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3ebb04528e83b2634857f43f9bb8ef5b2bbe7f10f140daeb01b58f972d04736b", size = 31297, upload-time = "2025-12-16T00:23:20.709Z" }, { url = "https://files.pythonhosted.org/packages/d7/0d/8ebed0c39c53a7e838e2a486da8abb0e52de135f1b376ae2f0b160eb4c1a/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:450dc98429d3e33ed2926fc99ee81001928d63460f8538f21a5d6060912a8e27", size = 30867, upload-time = "2025-12-16T00:43:14.628Z" }, { url = "https://files.pythonhosted.org/packages/ce/42/b468aec74a0354b34c8cbf748db20d6e350a68a2b0912e128cabee49806c/google_crc32c-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3b9776774b24ba76831609ffbabce8cdf6fa2bd5e9df37b594221c7e333a81fa", size = 33344, upload-time = "2025-12-16T00:40:24.742Z" }, { url = "https://files.pythonhosted.org/packages/1c/e8/b33784d6fc77fb5062a8a7854e43e1e618b87d5ddf610a88025e4de6226e/google_crc32c-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:89c17d53d75562edfff86679244830599ee0a48efc216200691de8b02ab6b2b8", size = 33694, upload-time = "2025-12-16T00:40:25.505Z" }, { url = "https://files.pythonhosted.org/packages/92/b1/d3cbd4d988afb3d8e4db94ca953df429ed6db7282ed0e700d25e6c7bfc8d/google_crc32c-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:57a50a9035b75643996fbf224d6661e386c7162d1dfdab9bc4ca790947d1007f", size = 34435, upload-time = "2025-12-16T00:35:22.107Z" }, { url = "https://files.pythonhosted.org/packages/21/88/8ecf3c2b864a490b9e7010c84fd203ec8cf3b280651106a3a74dd1b0ca72/google_crc32c-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:e6584b12cb06796d285d09e33f63309a09368b9d806a551d8036a4207ea43697", size = 31301, upload-time = "2025-12-16T00:24:48.527Z" }, { url = "https://files.pythonhosted.org/packages/36/c6/f7ff6c11f5ca215d9f43d3629163727a272eabc356e5c9b2853df2bfe965/google_crc32c-1.8.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:f4b51844ef67d6cf2e9425983274da75f18b1597bb2c998e1c0a0e8d46f8f651", size = 30868, upload-time = "2025-12-16T00:48:12.163Z" }, { url = "https://files.pythonhosted.org/packages/56/15/c25671c7aad70f8179d858c55a6ae8404902abe0cdcf32a29d581792b491/google_crc32c-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b0d1a7afc6e8e4635564ba8aa5c0548e3173e41b6384d7711a9123165f582de2", size = 33381, upload-time = "2025-12-16T00:40:26.268Z" }, { url = "https://files.pythonhosted.org/packages/42/fa/f50f51260d7b0ef5d4898af122d8a7ec5a84e2984f676f746445f783705f/google_crc32c-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3f68782f3cbd1bce027e48768293072813469af6a61a86f6bb4977a4380f21", size = 33734, upload-time = "2025-12-16T00:40:27.028Z" }, { url = "https://files.pythonhosted.org/packages/08/a5/7b059810934a09fb3ccb657e0843813c1fee1183d3bc2c8041800374aa2c/google_crc32c-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:d511b3153e7011a27ab6ee6bb3a5404a55b994dc1a7322c0b87b29606d9790e2", size = 34878, upload-time = "2025-12-16T00:35:23.142Z" }, { url = "https://files.pythonhosted.org/packages/52/c5/c171e4d8c44fec1422d801a6d2e5d7ddabd733eeda505c79730ee9607f07/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:87fa445064e7db928226b2e6f0d5304ab4cd0339e664a4e9a25029f384d9bb93", size = 28615, upload-time = "2025-12-16T00:40:29.298Z" }, { url = "https://files.pythonhosted.org/packages/9c/97/7d75fe37a7a6ed171a2cf17117177e7aab7e6e0d115858741b41e9dd4254/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f639065ea2042d5c034bf258a9f085eaa7af0cd250667c0635a3118e8f92c69c", size = 28800, upload-time = "2025-12-16T00:40:30.322Z" }, ] [[package]] name = "greenlet" version = "3.3.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8a/99/1cd3411c56a410994669062bd73dd58270c00cc074cac15f385a1fd91f8a/greenlet-3.3.1.tar.gz", hash = "sha256:41848f3230b58c08bb43dee542e74a2a2e34d3c59dc3076cec9151aeeedcae98", size = 184690, upload-time = "2026-01-23T15:31:02.076Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ec/e8/2e1462c8fdbe0f210feb5ac7ad2d9029af8be3bf45bd9fa39765f821642f/greenlet-3.3.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5fd23b9bc6d37b563211c6abbb1b3cab27db385a4449af5c32e932f93017080c", size = 274974, upload-time = "2026-01-23T15:31:02.891Z" }, { url = "https://files.pythonhosted.org/packages/7e/a8/530a401419a6b302af59f67aaf0b9ba1015855ea7e56c036b5928793c5bd/greenlet-3.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f51496a0bfbaa9d74d36a52d2580d1ef5ed4fdfcff0a73730abfbbbe1403dd", size = 577175, upload-time = "2026-01-23T16:00:56.213Z" }, { url = "https://files.pythonhosted.org/packages/8e/89/7e812bb9c05e1aaef9b597ac1d0962b9021d2c6269354966451e885c4e6b/greenlet-3.3.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb0feb07fe6e6a74615ee62a880007d976cf739b6669cce95daa7373d4fc69c5", size = 590401, upload-time = "2026-01-23T16:05:26.365Z" }, { url = "https://files.pythonhosted.org/packages/70/ae/e2d5f0e59b94a2269b68a629173263fa40b63da32f5c231307c349315871/greenlet-3.3.1-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:67ea3fc73c8cd92f42467a72b75e8f05ed51a0e9b1d15398c913416f2dafd49f", size = 601161, upload-time = "2026-01-23T16:15:53.456Z" }, { url = "https://files.pythonhosted.org/packages/5c/ae/8d472e1f5ac5efe55c563f3eabb38c98a44b832602e12910750a7c025802/greenlet-3.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39eda9ba259cc9801da05351eaa8576e9aa83eb9411e8f0c299e05d712a210f2", size = 590272, upload-time = "2026-01-23T15:32:49.411Z" }, { url = "https://files.pythonhosted.org/packages/a8/51/0fde34bebfcadc833550717eade64e35ec8738e6b097d5d248274a01258b/greenlet-3.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e2e7e882f83149f0a71ac822ebf156d902e7a5d22c9045e3e0d1daf59cee2cc9", size = 1550729, upload-time = "2026-01-23T16:04:20.867Z" }, { url = "https://files.pythonhosted.org/packages/16/c9/2fb47bee83b25b119d5a35d580807bb8b92480a54b68fef009a02945629f/greenlet-3.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80aa4d79eb5564f2e0a6144fcc744b5a37c56c4a92d60920720e99210d88db0f", size = 1615552, upload-time = "2026-01-23T15:33:45.743Z" }, { url = "https://files.pythonhosted.org/packages/1f/54/dcf9f737b96606f82f8dd05becfb8d238db0633dd7397d542a296fe9cad3/greenlet-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:32e4ca9777c5addcbf42ff3915d99030d8e00173a56f80001fb3875998fe410b", size = 226462, upload-time = "2026-01-23T15:36:50.422Z" }, { url = "https://files.pythonhosted.org/packages/91/37/61e1015cf944ddd2337447d8e97fb423ac9bc21f9963fb5f206b53d65649/greenlet-3.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:da19609432f353fed186cc1b85e9440db93d489f198b4bdf42ae19cc9d9ac9b4", size = 225715, upload-time = "2026-01-23T15:33:17.298Z" }, { url = "https://files.pythonhosted.org/packages/f9/c8/9d76a66421d1ae24340dfae7e79c313957f6e3195c144d2c73333b5bfe34/greenlet-3.3.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7e806ca53acf6d15a888405880766ec84721aa4181261cd11a457dfe9a7a4975", size = 276443, upload-time = "2026-01-23T15:30:10.066Z" }, { url = "https://files.pythonhosted.org/packages/81/99/401ff34bb3c032d1f10477d199724f5e5f6fbfb59816ad1455c79c1eb8e7/greenlet-3.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d842c94b9155f1c9b3058036c24ffb8ff78b428414a19792b2380be9cecf4f36", size = 597359, upload-time = "2026-01-23T16:00:57.394Z" }, { url = "https://files.pythonhosted.org/packages/2b/bc/4dcc0871ed557792d304f50be0f7487a14e017952ec689effe2180a6ff35/greenlet-3.3.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20fedaadd422fa02695f82093f9a98bad3dab5fcda793c658b945fcde2ab27ba", size = 607805, upload-time = "2026-01-23T16:05:28.068Z" }, { url = "https://files.pythonhosted.org/packages/3b/cd/7a7ca57588dac3389e97f7c9521cb6641fd8b6602faf1eaa4188384757df/greenlet-3.3.1-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c620051669fd04ac6b60ebc70478210119c56e2d5d5df848baec4312e260e4ca", size = 622363, upload-time = "2026-01-23T16:15:54.754Z" }, { url = "https://files.pythonhosted.org/packages/cf/05/821587cf19e2ce1f2b24945d890b164401e5085f9d09cbd969b0c193cd20/greenlet-3.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14194f5f4305800ff329cbf02c5fcc88f01886cadd29941b807668a45f0d2336", size = 609947, upload-time = "2026-01-23T15:32:51.004Z" }, { url = "https://files.pythonhosted.org/packages/a4/52/ee8c46ed9f8babaa93a19e577f26e3d28a519feac6350ed6f25f1afee7e9/greenlet-3.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7b2fe4150a0cf59f847a67db8c155ac36aed89080a6a639e9f16df5d6c6096f1", size = 1567487, upload-time = "2026-01-23T16:04:22.125Z" }, { url = "https://files.pythonhosted.org/packages/8f/7c/456a74f07029597626f3a6db71b273a3632aecb9afafeeca452cfa633197/greenlet-3.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49f4ad195d45f4a66a0eb9c1ba4832bb380570d361912fa3554746830d332149", size = 1636087, upload-time = "2026-01-23T15:33:47.486Z" }, { url = "https://files.pythonhosted.org/packages/34/2f/5e0e41f33c69655300a5e54aeb637cf8ff57f1786a3aba374eacc0228c1d/greenlet-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cc98b9c4e4870fa983436afa999d4eb16b12872fab7071423d5262fa7120d57a", size = 227156, upload-time = "2026-01-23T15:34:34.808Z" }, { url = "https://files.pythonhosted.org/packages/c8/ab/717c58343cf02c5265b531384b248787e04d8160b8afe53d9eec053d7b44/greenlet-3.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:bfb2d1763d777de5ee495c85309460f6fd8146e50ec9d0ae0183dbf6f0a829d1", size = 226403, upload-time = "2026-01-23T15:31:39.372Z" }, { url = "https://files.pythonhosted.org/packages/ec/ab/d26750f2b7242c2b90ea2ad71de70cfcd73a948a49513188a0fc0d6fc15a/greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3", size = 275205, upload-time = "2026-01-23T15:30:24.556Z" }, { url = "https://files.pythonhosted.org/packages/10/d3/be7d19e8fad7c5a78eeefb2d896a08cd4643e1e90c605c4be3b46264998f/greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac", size = 599284, upload-time = "2026-01-23T16:00:58.584Z" }, { url = "https://files.pythonhosted.org/packages/ae/21/fe703aaa056fdb0f17e5afd4b5c80195bbdab701208918938bd15b00d39b/greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd", size = 610274, upload-time = "2026-01-23T16:05:29.312Z" }, { url = "https://files.pythonhosted.org/packages/06/00/95df0b6a935103c0452dad2203f5be8377e551b8466a29650c4c5a5af6cc/greenlet-3.3.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:12184c61e5d64268a160226fb4818af4df02cfead8379d7f8b99a56c3a54ff3e", size = 624375, upload-time = "2026-01-23T16:15:55.915Z" }, { url = "https://files.pythonhosted.org/packages/cb/86/5c6ab23bb3c28c21ed6bebad006515cfe08b04613eb105ca0041fecca852/greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3", size = 612904, upload-time = "2026-01-23T15:32:52.317Z" }, { url = "https://files.pythonhosted.org/packages/c2/f3/7949994264e22639e40718c2daf6f6df5169bf48fb038c008a489ec53a50/greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951", size = 1567316, upload-time = "2026-01-23T16:04:23.316Z" }, { url = "https://files.pythonhosted.org/packages/8d/6e/d73c94d13b6465e9f7cd6231c68abde838bb22408596c05d9059830b7872/greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2", size = 1636549, upload-time = "2026-01-23T15:33:48.643Z" }, { url = "https://files.pythonhosted.org/packages/5e/b3/c9c23a6478b3bcc91f979ce4ca50879e4d0b2bd7b9a53d8ecded719b92e2/greenlet-3.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:27289986f4e5b0edec7b5a91063c109f0276abb09a7e9bdab08437525977c946", size = 227042, upload-time = "2026-01-23T15:33:58.216Z" }, { url = "https://files.pythonhosted.org/packages/90/e7/824beda656097edee36ab15809fd063447b200cc03a7f6a24c34d520bc88/greenlet-3.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:2f080e028001c5273e0b42690eaf359aeef9cb1389da0f171ea51a5dc3c7608d", size = 226294, upload-time = "2026-01-23T15:30:52.73Z" }, { url = "https://files.pythonhosted.org/packages/ae/fb/011c7c717213182caf78084a9bea51c8590b0afda98001f69d9f853a495b/greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", size = 275737, upload-time = "2026-01-23T15:32:16.889Z" }, { url = "https://files.pythonhosted.org/packages/41/2e/a3a417d620363fdbb08a48b1dd582956a46a61bf8fd27ee8164f9dfe87c2/greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", size = 646422, upload-time = "2026-01-23T16:01:00.354Z" }, { url = "https://files.pythonhosted.org/packages/b4/09/c6c4a0db47defafd2d6bab8ddfe47ad19963b4e30f5bed84d75328059f8c/greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", size = 658219, upload-time = "2026-01-23T16:05:30.956Z" }, { url = "https://files.pythonhosted.org/packages/e2/89/b95f2ddcc5f3c2bc09c8ee8d77be312df7f9e7175703ab780f2014a0e781/greenlet-3.3.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3e0f3878ca3a3ff63ab4ea478585942b53df66ddde327b59ecb191b19dbbd62d", size = 671455, upload-time = "2026-01-23T16:15:57.232Z" }, { url = "https://files.pythonhosted.org/packages/80/38/9d42d60dffb04b45f03dbab9430898352dba277758640751dc5cc316c521/greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", size = 660237, upload-time = "2026-01-23T15:32:53.967Z" }, { url = "https://files.pythonhosted.org/packages/96/61/373c30b7197f9e756e4c81ae90a8d55dc3598c17673f91f4d31c3c689c3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", size = 1615261, upload-time = "2026-01-23T16:04:25.066Z" }, { url = "https://files.pythonhosted.org/packages/fd/d3/ca534310343f5945316f9451e953dcd89b36fe7a19de652a1dc5a0eeef3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", size = 1683719, upload-time = "2026-01-23T15:33:50.61Z" }, { url = "https://files.pythonhosted.org/packages/52/cb/c21a3fd5d2c9c8b622e7bede6d6d00e00551a5ee474ea6d831b5f567a8b4/greenlet-3.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:96aff77af063b607f2489473484e39a0bbae730f2ea90c9e5606c9b73c44174a", size = 228125, upload-time = "2026-01-23T15:32:45.265Z" }, { url = "https://files.pythonhosted.org/packages/6a/8e/8a2db6d11491837af1de64b8aff23707c6e85241be13c60ed399a72e2ef8/greenlet-3.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:b066e8b50e28b503f604fa538adc764a638b38cf8e81e025011d26e8a627fa79", size = 227519, upload-time = "2026-01-23T15:31:47.284Z" }, { url = "https://files.pythonhosted.org/packages/28/24/cbbec49bacdcc9ec652a81d3efef7b59f326697e7edf6ed775a5e08e54c2/greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", size = 282706, upload-time = "2026-01-23T15:33:05.525Z" }, { url = "https://files.pythonhosted.org/packages/86/2e/4f2b9323c144c4fe8842a4e0d92121465485c3c2c5b9e9b30a52e80f523f/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", size = 651209, upload-time = "2026-01-23T16:01:01.517Z" }, { url = "https://files.pythonhosted.org/packages/d9/87/50ca60e515f5bb55a2fbc5f0c9b5b156de7d2fc51a0a69abc9d23914a237/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", size = 654300, upload-time = "2026-01-23T16:05:32.199Z" }, { url = "https://files.pythonhosted.org/packages/7c/25/c51a63f3f463171e09cb586eb64db0861eb06667ab01a7968371a24c4f3b/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b9721549a95db96689458a1e0ae32412ca18776ed004463df3a9299c1b257ab", size = 662574, upload-time = "2026-01-23T16:15:58.364Z" }, { url = "https://files.pythonhosted.org/packages/1d/94/74310866dfa2b73dd08659a3d18762f83985ad3281901ba0ee9a815194fb/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", size = 653842, upload-time = "2026-01-23T15:32:55.671Z" }, { url = "https://files.pythonhosted.org/packages/97/43/8bf0ffa3d498eeee4c58c212a3905dd6146c01c8dc0b0a046481ca29b18c/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53", size = 1614917, upload-time = "2026-01-23T16:04:26.276Z" }, { url = "https://files.pythonhosted.org/packages/89/90/a3be7a5f378fc6e84abe4dcfb2ba32b07786861172e502388b4c90000d1b/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", size = 1676092, upload-time = "2026-01-23T15:33:52.176Z" }, { url = "https://files.pythonhosted.org/packages/e1/2b/98c7f93e6db9977aaee07eb1e51ca63bd5f779b900d362791d3252e60558/greenlet-3.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:301860987846c24cb8964bdec0e31a96ad4a2a801b41b4ef40963c1b44f33451", size = 233181, upload-time = "2026-01-23T15:33:00.29Z" }, ] [[package]] name = "h11" version = "0.16.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] [[package]] name = "heapdict" version = "1.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/5a/9b/d8963ae7e388270b695f3b556b6dc9adb70ae9618fba09aa1e7b1886652d/HeapDict-1.0.1.tar.gz", hash = "sha256:8495f57b3e03d8e46d5f1b2cc62ca881aca392fd5cc048dc0aa2e1a6d23ecdb6", size = 4274, upload-time = "2019-09-09T18:57:02.154Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b6/9d/cd4777dbcf3bef9d9627e0fe4bc43d2e294b1baeb01d0422399d5e9de319/HeapDict-1.0.1-py3-none-any.whl", hash = "sha256:6065f90933ab1bb7e50db403b90cab653c853690c5992e69294c2de2b253fc92", size = 3917, upload-time = "2019-09-09T18:57:00.821Z" }, ] [[package]] name = "httpcore" version = "1.0.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "h11" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] [[package]] name = "httpx" version = "0.28.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "certifi" }, { name = "httpcore" }, { name = "idna" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] [[package]] name = "hypothesis" version = "6.151.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sortedcontainers" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4e/d7/c40dcd401cc360d8d084e584ffb7ab17255fde22e2b9cf2b53bf25aed629/hypothesis-6.151.5.tar.gz", hash = "sha256:ae3a0622f9693e6b19c697777c2c266c02801f9769ab7c2c37b7ec83d4743783", size = 475923, upload-time = "2026-02-03T19:33:55.845Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c0/d9/53a8b53e75279a953fae608bd01025d9afcf393406c0da1dda1b7f5693c5/hypothesis-6.151.5-py3-none-any.whl", hash = "sha256:c0e15c91fa0e67bc0295551ef5041bebad42753b7977a610cd7a6ec1ad04ef13", size = 543338, upload-time = "2026-02-03T19:33:54.583Z" }, ] [[package]] name = "identify" version = "2.6.16" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/5b/8d/e8b97e6bd3fb6fb271346f7981362f1e04d6a7463abd0de79e1fda17c067/identify-2.6.16.tar.gz", hash = "sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980", size = 99360, upload-time = "2026-01-12T18:58:58.201Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b8/58/40fbbcefeda82364720eba5cf2270f98496bdfa19ea75b4cccae79c698e6/identify-2.6.16-py2.py3-none-any.whl", hash = "sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0", size = 99202, upload-time = "2026-01-12T18:58:56.627Z" }, ] [[package]] name = "idna" version = "3.11" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] [[package]] name = "imagesize" version = "1.4.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, ] [[package]] name = "importlib-metadata" version = "8.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "zipp" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, ] [[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] name = "ipykernel" version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "appnope", marker = "sys_platform == 'darwin'" }, { name = "comm" }, { name = "debugpy" }, { name = "ipython" }, { name = "jupyter-client" }, { name = "jupyter-core" }, { name = "matplotlib-inline" }, { name = "nest-asyncio" }, { name = "packaging" }, { name = "psutil" }, { name = "pyzmq" }, { name = "tornado" }, { name = "traitlets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/a4/4948be6eb88628505b83a1f2f40d90254cab66abf2043b3c40fa07dfce0f/ipykernel-7.1.0.tar.gz", hash = "sha256:58a3fc88533d5930c3546dc7eac66c6d288acde4f801e2001e65edc5dc9cf0db", size = 174579, upload-time = "2025-10-27T09:46:39.471Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl", hash = "sha256:763b5ec6c5b7776f6a8d7ce09b267693b4e5ce75cb50ae696aaefb3c85e1ea4c", size = 117968, upload-time = "2025-10-27T09:46:37.805Z" }, ] [[package]] name = "ipython" version = "9.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "decorator" }, { name = "ipython-pygments-lexers" }, { name = "jedi" }, { name = "matplotlib-inline" }, { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, { name = "prompt-toolkit" }, { name = "pygments" }, { name = "stack-data" }, { name = "traitlets" }, { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a6/60/2111715ea11f39b1535bed6024b7dec7918b71e5e5d30855a5b503056b50/ipython-9.10.0.tar.gz", hash = "sha256:cd9e656be97618a0676d058134cd44e6dc7012c0e5cb36a9ce96a8c904adaf77", size = 4426526, upload-time = "2026-02-02T10:00:33.594Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl", hash = "sha256:c6ab68cc23bba8c7e18e9b932797014cc61ea7fd6f19de180ab9ba73e65ee58d", size = 622774, upload-time = "2026-02-02T10:00:31.503Z" }, ] [[package]] name = "ipython-pygments-lexers" version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, ] [[package]] name = "ipywidgets" version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "comm" }, { name = "ipython" }, { name = "jupyterlab-widgets" }, { name = "traitlets" }, { name = "widgetsnbextension" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4c/ae/c5ce1edc1afe042eadb445e95b0671b03cee61895264357956e61c0d2ac0/ipywidgets-8.1.8.tar.gz", hash = "sha256:61f969306b95f85fba6b6986b7fe45d73124d1d9e3023a8068710d47a22ea668", size = 116739, upload-time = "2025-11-01T21:18:12.393Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl", hash = "sha256:ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e", size = 139808, upload-time = "2025-11-01T21:18:10.956Z" }, ] [[package]] name = "isoduration" version = "20.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "arrow" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" }, ] [[package]] name = "jedi" version = "0.19.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "parso" }, ] sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, ] [[package]] name = "jinja2" version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] name = "json5" version = "0.13.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/77/e8/a3f261a66e4663f22700bc8a17c08cb83e91fbf086726e7a228398968981/json5-0.13.0.tar.gz", hash = "sha256:b1edf8d487721c0bf64d83c28e91280781f6e21f4a797d3261c7c828d4c165bf", size = 52441, upload-time = "2026-01-01T19:42:14.99Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl", hash = "sha256:9a08e1dd65f6a4d4c6fa82d216cf2477349ec2346a38fd70cc11d2557499fbcc", size = 36163, upload-time = "2026-01-01T19:42:13.962Z" }, ] [[package]] name = "jsonpointer" version = "3.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, ] [[package]] name = "jsonschema" version = "4.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "jsonschema-specifications" }, { name = "referencing" }, { name = "rpds-py" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, ] [package.optional-dependencies] format-nongpl = [ { name = "fqdn" }, { name = "idna" }, { name = "isoduration" }, { name = "jsonpointer" }, { name = "rfc3339-validator" }, { name = "rfc3986-validator" }, { name = "rfc3987-syntax" }, { name = "uri-template" }, { name = "webcolors" }, ] [[package]] name = "jsonschema-specifications" version = "2025.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "referencing" }, ] sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] [[package]] name = "jupyter" version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ipykernel" }, { name = "ipywidgets" }, { name = "jupyter-console" }, { name = "jupyterlab" }, { name = "nbconvert" }, { name = "notebook" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/f3/af28ea964ab8bc1e472dba2e82627d36d470c51f5cd38c37502eeffaa25e/jupyter-1.1.1.tar.gz", hash = "sha256:d55467bceabdea49d7e3624af7e33d59c37fff53ed3a350e1ac957bed731de7a", size = 5714959, upload-time = "2024-08-30T07:15:48.299Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/38/64/285f20a31679bf547b75602702f7800e74dbabae36ef324f716c02804753/jupyter-1.1.1-py2.py3-none-any.whl", hash = "sha256:7a59533c22af65439b24bbe60373a4e95af8f16ac65a6c00820ad378e3f7cc83", size = 2657, upload-time = "2024-08-30T07:15:47.045Z" }, ] [[package]] name = "jupyter-cache" version = "1.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "click" }, { name = "importlib-metadata" }, { name = "nbclient" }, { name = "nbformat" }, { name = "pyyaml" }, { name = "sqlalchemy" }, { name = "tabulate" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bb/f7/3627358075f183956e8c4974603232b03afd4ddc7baf72c2bc9fff522291/jupyter_cache-1.0.1.tar.gz", hash = "sha256:16e808eb19e3fb67a223db906e131ea6e01f03aa27f49a7214ce6a5fec186fb9", size = 32048, upload-time = "2024-11-15T16:03:55.322Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/64/6b/67b87da9d36bff9df7d0efbd1a325fa372a43be7158effaf43ed7b22341d/jupyter_cache-1.0.1-py3-none-any.whl", hash = "sha256:9c3cafd825ba7da8b5830485343091143dff903e4d8c69db9349b728b140abf6", size = 33907, upload-time = "2024-11-15T16:03:54.021Z" }, ] [[package]] name = "jupyter-client" version = "8.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-core" }, { name = "python-dateutil" }, { name = "pyzmq" }, { name = "tornado" }, { name = "traitlets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a", size = 107371, upload-time = "2026-01-08T13:55:45.562Z" }, ] [[package]] name = "jupyter-console" version = "6.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ipykernel" }, { name = "ipython" }, { name = "jupyter-client" }, { name = "jupyter-core" }, { name = "prompt-toolkit" }, { name = "pygments" }, { name = "pyzmq" }, { name = "traitlets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bd/2d/e2fd31e2fc41c14e2bcb6c976ab732597e907523f6b2420305f9fc7fdbdb/jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539", size = 34363, upload-time = "2023-03-06T14:13:31.02Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ca/77/71d78d58f15c22db16328a476426f7ac4a60d3a5a7ba3b9627ee2f7903d4/jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485", size = 24510, upload-time = "2023-03-06T14:13:28.229Z" }, ] [[package]] name = "jupyter-core" version = "5.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "platformdirs" }, { name = "traitlets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, ] [[package]] name = "jupyter-events" version = "0.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonschema", extra = ["format-nongpl"] }, { name = "packaging" }, { name = "python-json-logger" }, { name = "pyyaml" }, { name = "referencing" }, { name = "rfc3339-validator" }, { name = "rfc3986-validator" }, { name = "traitlets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196, upload-time = "2025-02-03T17:23:41.485Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430, upload-time = "2025-02-03T17:23:38.643Z" }, ] [[package]] name = "jupyter-lsp" version = "2.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-server" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/5a/9066c9f8e94ee517133cd98dba393459a16cd48bba71a82f16a65415206c/jupyter_lsp-2.3.0.tar.gz", hash = "sha256:458aa59339dc868fb784d73364f17dbce8836e906cd75fd471a325cba02e0245", size = 54823, upload-time = "2025-08-27T17:47:34.671Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl", hash = "sha256:e914a3cb2addf48b1c7710914771aaf1819d46b2e5a79b0f917b5478ec93f34f", size = 76687, upload-time = "2025-08-27T17:47:33.15Z" }, ] [[package]] name = "jupyter-server" version = "2.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "argon2-cffi" }, { name = "jinja2" }, { name = "jupyter-client" }, { name = "jupyter-core" }, { name = "jupyter-events" }, { name = "jupyter-server-terminals" }, { name = "nbconvert" }, { name = "nbformat" }, { name = "overrides", marker = "python_full_version < '3.12'" }, { name = "packaging" }, { name = "prometheus-client" }, { name = "pywinpty", marker = "os_name == 'nt'" }, { name = "pyzmq" }, { name = "send2trash" }, { name = "terminado" }, { name = "tornado" }, { name = "traitlets" }, { name = "websocket-client" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5b/ac/e040ec363d7b6b1f11304cc9f209dac4517ece5d5e01821366b924a64a50/jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5", size = 731949, upload-time = "2025-08-21T14:42:54.042Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl", hash = "sha256:e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f", size = 388221, upload-time = "2025-08-21T14:42:52.034Z" }, ] [[package]] name = "jupyter-server-terminals" version = "0.5.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pywinpty", marker = "os_name == 'nt'" }, { name = "terminado" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f4/a7/bcd0a9b0cbba88986fe944aaaf91bfda603e5a50bda8ed15123f381a3b2f/jupyter_server_terminals-0.5.4.tar.gz", hash = "sha256:bbda128ed41d0be9020349f9f1f2a4ab9952a73ed5f5ac9f1419794761fb87f5", size = 31770, upload-time = "2026-01-14T16:53:20.213Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl", hash = "sha256:55be353fc74a80bc7f3b20e6be50a55a61cd525626f578dcb66a5708e2007d14", size = 13704, upload-time = "2026-01-14T16:53:18.738Z" }, ] [[package]] name = "jupyterlab" version = "4.5.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-lru" }, { name = "httpx" }, { name = "ipykernel" }, { name = "jinja2" }, { name = "jupyter-core" }, { name = "jupyter-lsp" }, { name = "jupyter-server" }, { name = "jupyterlab-server" }, { name = "notebook-shim" }, { name = "packaging" }, { name = "setuptools" }, { name = "tornado" }, { name = "traitlets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3e/76/393eae3349f9a39bf21f8f5406e5244d36e2bfc932049b6070c271f92764/jupyterlab-4.5.3.tar.gz", hash = "sha256:4a159f71067cb38e4a82e86a42de8e7e926f384d7f2291964f282282096d27e8", size = 23939231, upload-time = "2026-01-23T15:04:25.768Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9e/9a/0bf9a7a45f0006d7ff4fdc4fc313de4255acab02bf4db1887c65f0472c01/jupyterlab-4.5.3-py3-none-any.whl", hash = "sha256:63c9f3a48de72ba00df766ad6eed416394f5bb883829f11eeff0872302520ba7", size = 12391761, upload-time = "2026-01-23T15:04:21.214Z" }, ] [[package]] name = "jupyterlab-pygments" version = "0.3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" }, ] [[package]] name = "jupyterlab-server" version = "2.28.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "babel" }, { name = "jinja2" }, { name = "json5" }, { name = "jsonschema" }, { name = "jupyter-server" }, { name = "packaging" }, { name = "requests" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d6/2c/90153f189e421e93c4bb4f9e3f59802a1f01abd2ac5cf40b152d7f735232/jupyterlab_server-2.28.0.tar.gz", hash = "sha256:35baa81898b15f93573e2deca50d11ac0ae407ebb688299d3a5213265033712c", size = 76996, upload-time = "2025-10-22T13:59:18.37Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl", hash = "sha256:e4355b148fdcf34d312bbbc80f22467d6d20460e8b8736bf235577dd18506968", size = 59830, upload-time = "2025-10-22T13:59:16.767Z" }, ] [[package]] name = "jupyterlab-widgets" version = "3.0.16" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/26/2d/ef58fed122b268c69c0aa099da20bc67657cdfb2e222688d5731bd5b971d/jupyterlab_widgets-3.0.16.tar.gz", hash = "sha256:423da05071d55cf27a9e602216d35a3a65a3e41cdf9c5d3b643b814ce38c19e0", size = 897423, upload-time = "2025-11-01T21:11:29.724Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl", hash = "sha256:45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8", size = 914926, upload-time = "2025-11-01T21:11:28.008Z" }, ] [[package]] name = "kiwisolver" version = "1.4.9" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, ] [[package]] name = "lark" version = "1.3.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/da/34/28fff3ab31ccff1fd4f6c7c7b0ceb2b6968d8ea4950663eadcb5720591a0/lark-1.3.1.tar.gz", hash = "sha256:b426a7a6d6d53189d318f2b6236ab5d6429eaf09259f1ca33eb716eed10d2905", size = 382732, upload-time = "2025-10-27T18:25:56.653Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" }, ] [[package]] name = "librt" version = "0.7.8" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e7/24/5f3646ff414285e0f7708fa4e946b9bf538345a41d1c375c439467721a5e/librt-0.7.8.tar.gz", hash = "sha256:1a4ede613941d9c3470b0368be851df6bb78ab218635512d0370b27a277a0862", size = 148323, upload-time = "2026-01-14T12:56:16.876Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/1b/a3/87ea9c1049f2c781177496ebee29430e4631f439b8553a4969c88747d5d8/librt-0.7.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3e9c11aa260c31493d4b3197d1e28dd07768594a4f92bec4506849d736248f", size = 56507, upload-time = "2026-01-14T12:54:54.156Z" }, { url = "https://files.pythonhosted.org/packages/5e/4a/23bcef149f37f771ad30203d561fcfd45b02bc54947b91f7a9ac34815747/librt-0.7.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ddb52499d0b3ed4aa88746aaf6f36a08314677d5c346234c3987ddc506404eac", size = 58455, upload-time = "2026-01-14T12:54:55.978Z" }, { url = "https://files.pythonhosted.org/packages/22/6e/46eb9b85c1b9761e0f42b6e6311e1cc544843ac897457062b9d5d0b21df4/librt-0.7.8-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e9c0afebbe6ce177ae8edba0c7c4d626f2a0fc12c33bb993d163817c41a7a05c", size = 164956, upload-time = "2026-01-14T12:54:57.311Z" }, { url = "https://files.pythonhosted.org/packages/7a/3f/aa7c7f6829fb83989feb7ba9aa11c662b34b4bd4bd5b262f2876ba3db58d/librt-0.7.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:631599598e2c76ded400c0a8722dec09217c89ff64dc54b060f598ed68e7d2a8", size = 174364, upload-time = "2026-01-14T12:54:59.089Z" }, { url = "https://files.pythonhosted.org/packages/3f/2d/d57d154b40b11f2cb851c4df0d4c4456bacd9b1ccc4ecb593ddec56c1a8b/librt-0.7.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c1ba843ae20db09b9d5c80475376168feb2640ce91cd9906414f23cc267a1ff", size = 188034, upload-time = "2026-01-14T12:55:00.141Z" }, { url = "https://files.pythonhosted.org/packages/59/f9/36c4dad00925c16cd69d744b87f7001792691857d3b79187e7a673e812fb/librt-0.7.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b5b007bb22ea4b255d3ee39dfd06d12534de2fcc3438567d9f48cdaf67ae1ae3", size = 186295, upload-time = "2026-01-14T12:55:01.303Z" }, { url = "https://files.pythonhosted.org/packages/23/9b/8a9889d3df5efb67695a67785028ccd58e661c3018237b73ad081691d0cb/librt-0.7.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dbd79caaf77a3f590cbe32dc2447f718772d6eea59656a7dcb9311161b10fa75", size = 181470, upload-time = "2026-01-14T12:55:02.492Z" }, { url = "https://files.pythonhosted.org/packages/43/64/54d6ef11afca01fef8af78c230726a9394759f2addfbf7afc5e3cc032a45/librt-0.7.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:87808a8d1e0bd62a01cafc41f0fd6818b5a5d0ca0d8a55326a81643cdda8f873", size = 201713, upload-time = "2026-01-14T12:55:03.919Z" }, { url = "https://files.pythonhosted.org/packages/2d/29/73e7ed2991330b28919387656f54109139b49e19cd72902f466bd44415fd/librt-0.7.8-cp311-cp311-win32.whl", hash = "sha256:31724b93baa91512bd0a376e7cf0b59d8b631ee17923b1218a65456fa9bda2e7", size = 43803, upload-time = "2026-01-14T12:55:04.996Z" }, { url = "https://files.pythonhosted.org/packages/3f/de/66766ff48ed02b4d78deea30392ae200bcbd99ae61ba2418b49fd50a4831/librt-0.7.8-cp311-cp311-win_amd64.whl", hash = "sha256:978e8b5f13e52cf23a9e80f3286d7546baa70bc4ef35b51d97a709d0b28e537c", size = 50080, upload-time = "2026-01-14T12:55:06.489Z" }, { url = "https://files.pythonhosted.org/packages/6f/e3/33450438ff3a8c581d4ed7f798a70b07c3206d298cf0b87d3806e72e3ed8/librt-0.7.8-cp311-cp311-win_arm64.whl", hash = "sha256:20e3946863d872f7cabf7f77c6c9d370b8b3d74333d3a32471c50d3a86c0a232", size = 43383, upload-time = "2026-01-14T12:55:07.49Z" }, { url = "https://files.pythonhosted.org/packages/56/04/79d8fcb43cae376c7adbab7b2b9f65e48432c9eced62ac96703bcc16e09b/librt-0.7.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9b6943885b2d49c48d0cff23b16be830ba46b0152d98f62de49e735c6e655a63", size = 57472, upload-time = "2026-01-14T12:55:08.528Z" }, { url = "https://files.pythonhosted.org/packages/b4/ba/60b96e93043d3d659da91752689023a73981336446ae82078cddf706249e/librt-0.7.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46ef1f4b9b6cc364b11eea0ecc0897314447a66029ee1e55859acb3dd8757c93", size = 58986, upload-time = "2026-01-14T12:55:09.466Z" }, { url = "https://files.pythonhosted.org/packages/7c/26/5215e4cdcc26e7be7eee21955a7e13cbf1f6d7d7311461a6014544596fac/librt-0.7.8-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:907ad09cfab21e3c86e8f1f87858f7049d1097f77196959c033612f532b4e592", size = 168422, upload-time = "2026-01-14T12:55:10.499Z" }, { url = "https://files.pythonhosted.org/packages/0f/84/e8d1bc86fa0159bfc24f3d798d92cafd3897e84c7fea7fe61b3220915d76/librt-0.7.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2991b6c3775383752b3ca0204842743256f3ad3deeb1d0adc227d56b78a9a850", size = 177478, upload-time = "2026-01-14T12:55:11.577Z" }, { url = "https://files.pythonhosted.org/packages/57/11/d0268c4b94717a18aa91df1100e767b010f87b7ae444dafaa5a2d80f33a6/librt-0.7.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03679b9856932b8c8f674e87aa3c55ea11c9274301f76ae8dc4d281bda55cf62", size = 192439, upload-time = "2026-01-14T12:55:12.7Z" }, { url = "https://files.pythonhosted.org/packages/8d/56/1e8e833b95fe684f80f8894ae4d8b7d36acc9203e60478fcae599120a975/librt-0.7.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3968762fec1b2ad34ce57458b6de25dbb4142713e9ca6279a0d352fa4e9f452b", size = 191483, upload-time = "2026-01-14T12:55:13.838Z" }, { url = "https://files.pythonhosted.org/packages/17/48/f11cf28a2cb6c31f282009e2208312aa84a5ee2732859f7856ee306176d5/librt-0.7.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bb7a7807523a31f03061288cc4ffc065d684c39db7644c676b47d89553c0d714", size = 185376, upload-time = "2026-01-14T12:55:15.017Z" }, { url = "https://files.pythonhosted.org/packages/b8/6a/d7c116c6da561b9155b184354a60a3d5cdbf08fc7f3678d09c95679d13d9/librt-0.7.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad64a14b1e56e702e19b24aae108f18ad1bf7777f3af5fcd39f87d0c5a814449", size = 206234, upload-time = "2026-01-14T12:55:16.571Z" }, { url = "https://files.pythonhosted.org/packages/61/de/1975200bb0285fc921c5981d9978ce6ce11ae6d797df815add94a5a848a3/librt-0.7.8-cp312-cp312-win32.whl", hash = "sha256:0241a6ed65e6666236ea78203a73d800dbed896cf12ae25d026d75dc1fcd1dac", size = 44057, upload-time = "2026-01-14T12:55:18.077Z" }, { url = "https://files.pythonhosted.org/packages/8e/cd/724f2d0b3461426730d4877754b65d39f06a41ac9d0a92d5c6840f72b9ae/librt-0.7.8-cp312-cp312-win_amd64.whl", hash = "sha256:6db5faf064b5bab9675c32a873436b31e01d66ca6984c6f7f92621656033a708", size = 50293, upload-time = "2026-01-14T12:55:19.179Z" }, { url = "https://files.pythonhosted.org/packages/bd/cf/7e899acd9ee5727ad8160fdcc9994954e79fab371c66535c60e13b968ffc/librt-0.7.8-cp312-cp312-win_arm64.whl", hash = "sha256:57175aa93f804d2c08d2edb7213e09276bd49097611aefc37e3fa38d1fb99ad0", size = 43574, upload-time = "2026-01-14T12:55:20.185Z" }, { url = "https://files.pythonhosted.org/packages/a1/fe/b1f9de2829cf7fc7649c1dcd202cfd873837c5cc2fc9e526b0e7f716c3d2/librt-0.7.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4c3995abbbb60b3c129490fa985dfe6cac11d88fc3c36eeb4fb1449efbbb04fc", size = 57500, upload-time = "2026-01-14T12:55:21.219Z" }, { url = "https://files.pythonhosted.org/packages/eb/d4/4a60fbe2e53b825f5d9a77325071d61cd8af8506255067bf0c8527530745/librt-0.7.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:44e0c2cbc9bebd074cf2cdbe472ca185e824be4e74b1c63a8e934cea674bebf2", size = 59019, upload-time = "2026-01-14T12:55:22.256Z" }, { url = "https://files.pythonhosted.org/packages/6a/37/61ff80341ba5159afa524445f2d984c30e2821f31f7c73cf166dcafa5564/librt-0.7.8-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4d2f1e492cae964b3463a03dc77a7fe8742f7855d7258c7643f0ee32b6651dd3", size = 169015, upload-time = "2026-01-14T12:55:23.24Z" }, { url = "https://files.pythonhosted.org/packages/1c/86/13d4f2d6a93f181ebf2fc953868826653ede494559da8268023fe567fca3/librt-0.7.8-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:451e7ffcef8f785831fdb791bd69211f47e95dc4c6ddff68e589058806f044c6", size = 178161, upload-time = "2026-01-14T12:55:24.826Z" }, { url = "https://files.pythonhosted.org/packages/88/26/e24ef01305954fc4d771f1f09f3dd682f9eb610e1bec188ffb719374d26e/librt-0.7.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3469e1af9f1380e093ae06bedcbdd11e407ac0b303a56bbe9afb1d6824d4982d", size = 193015, upload-time = "2026-01-14T12:55:26.04Z" }, { url = "https://files.pythonhosted.org/packages/88/a0/92b6bd060e720d7a31ed474d046a69bd55334ec05e9c446d228c4b806ae3/librt-0.7.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f11b300027ce19a34f6d24ebb0a25fd0e24a9d53353225a5c1e6cadbf2916b2e", size = 192038, upload-time = "2026-01-14T12:55:27.208Z" }, { url = "https://files.pythonhosted.org/packages/06/bb/6f4c650253704279c3a214dad188101d1b5ea23be0606628bc6739456624/librt-0.7.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4adc73614f0d3c97874f02f2c7fd2a27854e7e24ad532ea6b965459c5b757eca", size = 186006, upload-time = "2026-01-14T12:55:28.594Z" }, { url = "https://files.pythonhosted.org/packages/dc/00/1c409618248d43240cadf45f3efb866837fa77e9a12a71481912135eb481/librt-0.7.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:60c299e555f87e4c01b2eca085dfccda1dde87f5a604bb45c2906b8305819a93", size = 206888, upload-time = "2026-01-14T12:55:30.214Z" }, { url = "https://files.pythonhosted.org/packages/d9/83/b2cfe8e76ff5c1c77f8a53da3d5de62d04b5ebf7cf913e37f8bca43b5d07/librt-0.7.8-cp313-cp313-win32.whl", hash = "sha256:b09c52ed43a461994716082ee7d87618096851319bf695d57ec123f2ab708951", size = 44126, upload-time = "2026-01-14T12:55:31.44Z" }, { url = "https://files.pythonhosted.org/packages/a9/0b/c59d45de56a51bd2d3a401fc63449c0ac163e4ef7f523ea8b0c0dee86ec5/librt-0.7.8-cp313-cp313-win_amd64.whl", hash = "sha256:f8f4a901a3fa28969d6e4519deceab56c55a09d691ea7b12ca830e2fa3461e34", size = 50262, upload-time = "2026-01-14T12:55:33.01Z" }, { url = "https://files.pythonhosted.org/packages/fc/b9/973455cec0a1ec592395250c474164c4a58ebf3e0651ee920fef1a2623f1/librt-0.7.8-cp313-cp313-win_arm64.whl", hash = "sha256:43d4e71b50763fcdcf64725ac680d8cfa1706c928b844794a7aa0fa9ac8e5f09", size = 43600, upload-time = "2026-01-14T12:55:34.054Z" }, { url = "https://files.pythonhosted.org/packages/1a/73/fa8814c6ce2d49c3827829cadaa1589b0bf4391660bd4510899393a23ebc/librt-0.7.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:be927c3c94c74b05128089a955fba86501c3b544d1d300282cc1b4bd370cb418", size = 57049, upload-time = "2026-01-14T12:55:35.056Z" }, { url = "https://files.pythonhosted.org/packages/53/fe/f6c70956da23ea235fd2e3cc16f4f0b4ebdfd72252b02d1164dd58b4e6c3/librt-0.7.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7b0803e9008c62a7ef79058233db7ff6f37a9933b8f2573c05b07ddafa226611", size = 58689, upload-time = "2026-01-14T12:55:36.078Z" }, { url = "https://files.pythonhosted.org/packages/1f/4d/7a2481444ac5fba63050d9abe823e6bc16896f575bfc9c1e5068d516cdce/librt-0.7.8-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:79feb4d00b2a4e0e05c9c56df707934f41fcb5fe53fd9efb7549068d0495b758", size = 166808, upload-time = "2026-01-14T12:55:37.595Z" }, { url = "https://files.pythonhosted.org/packages/ac/3c/10901d9e18639f8953f57c8986796cfbf4c1c514844a41c9197cf87cb707/librt-0.7.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9122094e3f24aa759c38f46bd8863433820654927370250f460ae75488b66ea", size = 175614, upload-time = "2026-01-14T12:55:38.756Z" }, { url = "https://files.pythonhosted.org/packages/db/01/5cbdde0951a5090a80e5ba44e6357d375048123c572a23eecfb9326993a7/librt-0.7.8-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e03bea66af33c95ce3addf87a9bf1fcad8d33e757bc479957ddbc0e4f7207ac", size = 189955, upload-time = "2026-01-14T12:55:39.939Z" }, { url = "https://files.pythonhosted.org/packages/6a/b4/e80528d2f4b7eaf1d437fcbd6fc6ba4cbeb3e2a0cb9ed5a79f47c7318706/librt-0.7.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f1ade7f31675db00b514b98f9ab9a7698c7282dad4be7492589109471852d398", size = 189370, upload-time = "2026-01-14T12:55:41.057Z" }, { url = "https://files.pythonhosted.org/packages/c1/ab/938368f8ce31a9787ecd4becb1e795954782e4312095daf8fd22420227c8/librt-0.7.8-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a14229ac62adcf1b90a15992f1ab9c69ae8b99ffb23cb64a90878a6e8a2f5b81", size = 183224, upload-time = "2026-01-14T12:55:42.328Z" }, { url = "https://files.pythonhosted.org/packages/3c/10/559c310e7a6e4014ac44867d359ef8238465fb499e7eb31b6bfe3e3f86f5/librt-0.7.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5bcaaf624fd24e6a0cb14beac37677f90793a96864c67c064a91458611446e83", size = 203541, upload-time = "2026-01-14T12:55:43.501Z" }, { url = "https://files.pythonhosted.org/packages/f8/db/a0db7acdb6290c215f343835c6efda5b491bb05c3ddc675af558f50fdba3/librt-0.7.8-cp314-cp314-win32.whl", hash = "sha256:7aa7d5457b6c542ecaed79cec4ad98534373c9757383973e638ccced0f11f46d", size = 40657, upload-time = "2026-01-14T12:55:44.668Z" }, { url = "https://files.pythonhosted.org/packages/72/e0/4f9bdc2a98a798511e81edcd6b54fe82767a715e05d1921115ac70717f6f/librt-0.7.8-cp314-cp314-win_amd64.whl", hash = "sha256:3d1322800771bee4a91f3b4bd4e49abc7d35e65166821086e5afd1e6c0d9be44", size = 46835, upload-time = "2026-01-14T12:55:45.655Z" }, { url = "https://files.pythonhosted.org/packages/f9/3d/59c6402e3dec2719655a41ad027a7371f8e2334aa794ed11533ad5f34969/librt-0.7.8-cp314-cp314-win_arm64.whl", hash = "sha256:5363427bc6a8c3b1719f8f3845ea53553d301382928a86e8fab7984426949bce", size = 39885, upload-time = "2026-01-14T12:55:47.138Z" }, { url = "https://files.pythonhosted.org/packages/4e/9c/2481d80950b83085fb14ba3c595db56330d21bbc7d88a19f20165f3538db/librt-0.7.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ca916919793a77e4a98d4a1701e345d337ce53be4a16620f063191f7322ac80f", size = 59161, upload-time = "2026-01-14T12:55:48.45Z" }, { url = "https://files.pythonhosted.org/packages/96/79/108df2cfc4e672336765d54e3ff887294c1cc36ea4335c73588875775527/librt-0.7.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:54feb7b4f2f6706bb82325e836a01be805770443e2400f706e824e91f6441dde", size = 61008, upload-time = "2026-01-14T12:55:49.527Z" }, { url = "https://files.pythonhosted.org/packages/46/f2/30179898f9994a5637459d6e169b6abdc982012c0a4b2d4c26f50c06f911/librt-0.7.8-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:39a4c76fee41007070f872b648cc2f711f9abf9a13d0c7162478043377b52c8e", size = 187199, upload-time = "2026-01-14T12:55:50.587Z" }, { url = "https://files.pythonhosted.org/packages/b4/da/f7563db55cebdc884f518ba3791ad033becc25ff68eb70902b1747dc0d70/librt-0.7.8-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac9c8a458245c7de80bc1b9765b177055efff5803f08e548dd4bb9ab9a8d789b", size = 198317, upload-time = "2026-01-14T12:55:51.991Z" }, { url = "https://files.pythonhosted.org/packages/b3/6c/4289acf076ad371471fa86718c30ae353e690d3de6167f7db36f429272f1/librt-0.7.8-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b67aa7eff150f075fda09d11f6bfb26edffd300f6ab1666759547581e8f666", size = 210334, upload-time = "2026-01-14T12:55:53.682Z" }, { url = "https://files.pythonhosted.org/packages/4a/7f/377521ac25b78ac0a5ff44127a0360ee6d5ddd3ce7327949876a30533daa/librt-0.7.8-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:535929b6eff670c593c34ff435d5440c3096f20fa72d63444608a5aef64dd581", size = 211031, upload-time = "2026-01-14T12:55:54.827Z" }, { url = "https://files.pythonhosted.org/packages/c5/b1/e1e96c3e20b23d00cf90f4aad48f0deb4cdfec2f0ed8380d0d85acf98bbf/librt-0.7.8-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:63937bd0f4d1cb56653dc7ae900d6c52c41f0015e25aaf9902481ee79943b33a", size = 204581, upload-time = "2026-01-14T12:55:56.811Z" }, { url = "https://files.pythonhosted.org/packages/43/71/0f5d010e92ed9747e14bef35e91b6580533510f1e36a8a09eb79ee70b2f0/librt-0.7.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf243da9e42d914036fd362ac3fa77d80a41cadcd11ad789b1b5eec4daaf67ca", size = 224731, upload-time = "2026-01-14T12:55:58.175Z" }, { url = "https://files.pythonhosted.org/packages/22/f0/07fb6ab5c39a4ca9af3e37554f9d42f25c464829254d72e4ebbd81da351c/librt-0.7.8-cp314-cp314t-win32.whl", hash = "sha256:171ca3a0a06c643bd0a2f62a8944e1902c94aa8e5da4db1ea9a8daf872685365", size = 41173, upload-time = "2026-01-14T12:55:59.315Z" }, { url = "https://files.pythonhosted.org/packages/24/d4/7e4be20993dc6a782639625bd2f97f3c66125c7aa80c82426956811cfccf/librt-0.7.8-cp314-cp314t-win_amd64.whl", hash = "sha256:445b7304145e24c60288a2f172b5ce2ca35c0f81605f5299f3fa567e189d2e32", size = 47668, upload-time = "2026-01-14T12:56:00.261Z" }, { url = "https://files.pythonhosted.org/packages/fc/85/69f92b2a7b3c0f88ffe107c86b952b397004b5b8ea5a81da3d9c04c04422/librt-0.7.8-cp314-cp314t-win_arm64.whl", hash = "sha256:8766ece9de08527deabcd7cb1b4f1a967a385d26e33e536d6d8913db6ef74f06", size = 40550, upload-time = "2026-01-14T12:56:01.542Z" }, ] [[package]] name = "line-profiler" version = "5.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ea/5c/bbe9042ef5cf4c6cad4bf4d6f7975193430eba9191b7278ea114a3993fbb/line_profiler-5.0.0.tar.gz", hash = "sha256:a80f0afb05ba0d275d9dddc5ff97eab637471167ff3e66dcc7d135755059398c", size = 376919, upload-time = "2025-07-23T20:15:41.819Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/f7/4e0fd2610749136d60f3e168812b5f6c697ffcfbb167b10d4aac24af1223/line_profiler-5.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f32d536c056393b7ca703e459632edc327ff9e0fc320c7b0e0ed14b84d342b7f", size = 634587, upload-time = "2025-07-23T20:14:39.069Z" }, { url = "https://files.pythonhosted.org/packages/d7/fe/b5458452c2dbf7a9590b5ad3cf4250710a2554a5a045bfa6395cdea1b2d5/line_profiler-5.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a7da04ffc5a0a1f6653f43b13ad2e7ebf66f1d757174b7e660dfa0cbe74c4fc6", size = 492744, upload-time = "2025-07-23T20:14:40.632Z" }, { url = "https://files.pythonhosted.org/packages/4c/e1/b69e20aeea8a11340f8c5d540c88ecf955a3559d8fbd5034cfe5677c69cf/line_profiler-5.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d2746f6b13c19ca4847efd500402d53a5ebb2fe31644ce8af74fbeac5ea4c54c", size = 478101, upload-time = "2025-07-23T20:14:42.306Z" }, { url = "https://files.pythonhosted.org/packages/0d/3b/b29e5539b2c98d2bd9f5651f10597dd70e07d5b09bb47cc0aa8d48927d72/line_profiler-5.0.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b4290319a59730c04cbd03755472d10524130065a20a695dc10dd66ffd92172", size = 1455927, upload-time = "2025-07-23T20:14:44.139Z" }, { url = "https://files.pythonhosted.org/packages/82/1d/dcc75d2cf82bbe6ef65d0f39cc32410e099e7e1cd7f85b121a8d440ce8bc/line_profiler-5.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cd168a8af0032e8e3cb2fbb9ffc7694cdcecd47ec356ae863134df07becb3a2", size = 1508770, upload-time = "2025-07-23T20:14:45.868Z" }, { url = "https://files.pythonhosted.org/packages/cc/9f/cbf9d011381c878f848f824190ad833fbfeb5426eb6c42811b5b759d5d54/line_profiler-5.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cbe7b095865d00dda0f53d7d4556c2b1b5d13f723173a85edb206a78779ee07a", size = 2551269, upload-time = "2025-07-23T20:14:47.279Z" }, { url = "https://files.pythonhosted.org/packages/7c/86/06999bff316e2522fc1d11fcd3720be81a7c47e94c785a9d93c290ae0415/line_profiler-5.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ff176045ea8a9e33900856db31b0b979357c337862ae4837140c98bd3161c3c7", size = 2491091, upload-time = "2025-07-23T20:14:48.637Z" }, { url = "https://files.pythonhosted.org/packages/61/d1/758f2f569b5d4fdc667b88e88e7424081ba3a1d17fb531042ed7f0f08d7f/line_profiler-5.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:474e0962d02123f1190a804073b308a67ef5f9c3b8379184483d5016844a00df", size = 462954, upload-time = "2025-07-23T20:14:50.094Z" }, { url = "https://files.pythonhosted.org/packages/73/d8/383c37c36f888c4ca82a28ffea27c589988463fc3f0edd6abae221c35275/line_profiler-5.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:729b18c0ac66b3368ade61203459219c202609f76b34190cbb2508b8e13998c8", size = 628109, upload-time = "2025-07-23T20:14:51.71Z" }, { url = "https://files.pythonhosted.org/packages/54/a3/75a27b1f3e14ae63a2e99f3c7014dbc1e3a37f56c91b63a2fc171e72990d/line_profiler-5.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:438ed24278c428119473b61a473c8fe468ace7c97c94b005cb001137bc624547", size = 489142, upload-time = "2025-07-23T20:14:52.993Z" }, { url = "https://files.pythonhosted.org/packages/8b/85/f65cdbfe8537da6fab97c42958109858df846563546b9c234a902a98c313/line_profiler-5.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:920b0076dca726caadbf29f0bfcce0cbcb4d9ff034cd9445a7308f9d556b4b3a", size = 475838, upload-time = "2025-07-23T20:14:54.637Z" }, { url = "https://files.pythonhosted.org/packages/0c/ea/cfa53c8ede0ef539cfe767a390d7ccfc015f89c39cc2a8c34e77753fd023/line_profiler-5.0.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53326eaad2d807487dcd45d2e385feaaed81aaf72b9ecd4f53c1a225d658006f", size = 1402290, upload-time = "2025-07-23T20:14:56.775Z" }, { url = "https://files.pythonhosted.org/packages/e4/2c/3467cd5051afbc0eb277ee426e8dffdbd1fcdd82f1bc95a0cd8945b6c106/line_profiler-5.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e3995a989cdea022f0ede5db19a6ab527f818c59ffcebf4e5f7a8be4eb8e880", size = 1457827, upload-time = "2025-07-23T20:14:58.158Z" }, { url = "https://files.pythonhosted.org/packages/d9/87/d5039608979b37ce3dadfa3eed7bf8bfec53b645acd30ca12c8088cf738d/line_profiler-5.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8bf57892a1d3a42273652506746ba9f620c505773ada804367c42e5b4146d6b6", size = 2497423, upload-time = "2025-07-23T20:15:01.015Z" }, { url = "https://files.pythonhosted.org/packages/59/3e/e5e09699e2841b4f41c16d01ff2adfd20fde6cb73cfa512262f0421e15e0/line_profiler-5.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43672085f149f5fbf3f08bba072ad7014dd485282e8665827b26941ea97d2d76", size = 2439733, upload-time = "2025-07-23T20:15:02.582Z" }, { url = "https://files.pythonhosted.org/packages/d0/cf/18d8fefabd8a56fb963f944149cadb69be67a479ce6723275cae2c943af5/line_profiler-5.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:446bd4f04e4bd9e979d68fdd916103df89a9d419e25bfb92b31af13c33808ee0", size = 460852, upload-time = "2025-07-23T20:15:03.827Z" }, { url = "https://files.pythonhosted.org/packages/7d/eb/bc4420cf68661406c98d590656d72eed6f7d76e45accf568802dc83615ef/line_profiler-5.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9873fabbae1587778a551176758a70a5f6c89d8d070a1aca7a689677d41a1348", size = 624828, upload-time = "2025-07-23T20:15:05.315Z" }, { url = "https://files.pythonhosted.org/packages/f2/6e/6e0a4c1009975d27810027427d601acbad75b45947040d0fd80cec5b3e94/line_profiler-5.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2cd6cdb5a4d3b4ced607104dbed73ec820a69018decd1a90904854380536ed32", size = 487651, upload-time = "2025-07-23T20:15:06.961Z" }, { url = "https://files.pythonhosted.org/packages/3b/2c/e60e61f24faa0e6eca375bdac9c4b4b37c3267488d7cb1a8c5bd74cf5cdc/line_profiler-5.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:34d6172a3bd14167b3ea2e629d71b08683b17b3bc6eb6a4936d74e3669f875b6", size = 474071, upload-time = "2025-07-23T20:15:08.607Z" }, { url = "https://files.pythonhosted.org/packages/e1/d5/6f178e74746f84cc17381f607d191c54772207770d585fda773b868bfe28/line_profiler-5.0.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5edd859be322aa8252253e940ac1c60cca4c385760d90a402072f8f35e4b967", size = 1405434, upload-time = "2025-07-23T20:15:09.862Z" }, { url = "https://files.pythonhosted.org/packages/9b/32/ce67bbf81e5c78cc8d606afe6a192fbef30395021b2aaffe15681e186e3f/line_profiler-5.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d4f97b223105eed6e525994f5653061bd981e04838ee5d14e01d17c26185094", size = 1467553, upload-time = "2025-07-23T20:15:11.195Z" }, { url = "https://files.pythonhosted.org/packages/c1/c1/431ffb89a351aaa63f8358442e0b9456a3bb745cebdf9c0d7aa4d47affca/line_profiler-5.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4758007e491bee3be40ebcca460596e0e28e7f39b735264694a9cafec729dfa9", size = 2442489, upload-time = "2025-07-23T20:15:12.602Z" }, { url = "https://files.pythonhosted.org/packages/ce/9d/e34cc99c8abca3a27911d3542a87361e9c292fa1258d182e4a0a5c442850/line_profiler-5.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:213b19c4b65942db5d477e603c18c76126e3811a39d8bab251d930d8ce82ffba", size = 461377, upload-time = "2025-07-23T20:15:13.871Z" }, ] [[package]] name = "linkify-it-py" version = "2.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "uc-micro-py" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2a/ae/bb56c6828e4797ba5a4821eec7c43b8bf40f69cda4d4f5f8c8a2810ec96a/linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048", size = 27946, upload-time = "2024-02-04T14:48:04.179Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/04/1e/b832de447dee8b582cac175871d2f6c3d5077cc56d5575cadba1fd1cccfa/linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79", size = 19820, upload-time = "2024-02-04T14:48:02.496Z" }, ] [[package]] name = "llvmlite" version = "0.46.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d21fe2ea536500563ef1943c5105bf6ab4ee3829f68e/llvmlite-0.46.0.tar.gz", hash = "sha256:227c9fd6d09dce2783c18b754b7cd9d9b3b3515210c46acc2d3c5badd9870ceb", size = 193456, upload-time = "2025-12-08T18:15:36.295Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7a/a1/2ad4b2367915faeebe8447f0a057861f646dbf5fbbb3561db42c65659cf3/llvmlite-0.46.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82f3d39b16f19aa1a56d5fe625883a6ab600d5cc9ea8906cca70ce94cabba067", size = 37232766, upload-time = "2025-12-08T18:14:48.836Z" }, { url = "https://files.pythonhosted.org/packages/12/b5/99cf8772fdd846c07da4fd70f07812a3c8fd17ea2409522c946bb0f2b277/llvmlite-0.46.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a3df43900119803bbc52720e758c76f316a9a0f34612a886862dfe0a5591a17e", size = 56275175, upload-time = "2025-12-08T18:14:51.604Z" }, { url = "https://files.pythonhosted.org/packages/38/f2/ed806f9c003563732da156139c45d970ee435bd0bfa5ed8de87ba972b452/llvmlite-0.46.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de183fefc8022d21b0aa37fc3e90410bc3524aed8617f0ff76732fc6c3af5361", size = 55128630, upload-time = "2025-12-08T18:14:55.107Z" }, { url = "https://files.pythonhosted.org/packages/19/0c/8f5a37a65fc9b7b17408508145edd5f86263ad69c19d3574e818f533a0eb/llvmlite-0.46.0-cp311-cp311-win_amd64.whl", hash = "sha256:e8b10bc585c58bdffec9e0c309bb7d51be1f2f15e169a4b4d42f2389e431eb93", size = 38138652, upload-time = "2025-12-08T18:14:58.171Z" }, { url = "https://files.pythonhosted.org/packages/2b/f8/4db016a5e547d4e054ff2f3b99203d63a497465f81ab78ec8eb2ff7b2304/llvmlite-0.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b9588ad4c63b4f0175a3984b85494f0c927c6b001e3a246a3a7fb3920d9a137", size = 37232767, upload-time = "2025-12-08T18:15:00.737Z" }, { url = "https://files.pythonhosted.org/packages/aa/85/4890a7c14b4fa54400945cb52ac3cd88545bbdb973c440f98ca41591cdc5/llvmlite-0.46.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3535bd2bb6a2d7ae4012681ac228e5132cdb75fefb1bcb24e33f2f3e0c865ed4", size = 56275176, upload-time = "2025-12-08T18:15:03.936Z" }, { url = "https://files.pythonhosted.org/packages/6a/07/3d31d39c1a1a08cd5337e78299fca77e6aebc07c059fbd0033e3edfab45c/llvmlite-0.46.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cbfd366e60ff87ea6cc62f50bc4cd800ebb13ed4c149466f50cf2163a473d1e", size = 55128630, upload-time = "2025-12-08T18:15:07.196Z" }, { url = "https://files.pythonhosted.org/packages/2a/6b/d139535d7590a1bba1ceb68751bef22fadaa5b815bbdf0e858e3875726b2/llvmlite-0.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:398b39db462c39563a97b912d4f2866cd37cba60537975a09679b28fbbc0fb38", size = 38138940, upload-time = "2025-12-08T18:15:10.162Z" }, { url = "https://files.pythonhosted.org/packages/e6/ff/3eba7eb0aed4b6fca37125387cd417e8c458e750621fce56d2c541f67fa8/llvmlite-0.46.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:30b60892d034bc560e0ec6654737aaa74e5ca327bd8114d82136aa071d611172", size = 37232767, upload-time = "2025-12-08T18:15:13.22Z" }, { url = "https://files.pythonhosted.org/packages/0e/54/737755c0a91558364b9200702c3c9c15d70ed63f9b98a2c32f1c2aa1f3ba/llvmlite-0.46.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6cc19b051753368a9c9f31dc041299059ee91aceec81bd57b0e385e5d5bf1a54", size = 56275176, upload-time = "2025-12-08T18:15:16.339Z" }, { url = "https://files.pythonhosted.org/packages/e6/91/14f32e1d70905c1c0aa4e6609ab5d705c3183116ca02ac6df2091868413a/llvmlite-0.46.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bca185892908f9ede48c0acd547fe4dc1bafefb8a4967d47db6cf664f9332d12", size = 55128629, upload-time = "2025-12-08T18:15:19.493Z" }, { url = "https://files.pythonhosted.org/packages/4a/a7/d526ae86708cea531935ae777b6dbcabe7db52718e6401e0fb9c5edea80e/llvmlite-0.46.0-cp313-cp313-win_amd64.whl", hash = "sha256:67438fd30e12349ebb054d86a5a1a57fd5e87d264d2451bcfafbbbaa25b82a35", size = 38138941, upload-time = "2025-12-08T18:15:22.536Z" }, { url = "https://files.pythonhosted.org/packages/95/ae/af0ffb724814cc2ea64445acad05f71cff5f799bb7efb22e47ee99340dbc/llvmlite-0.46.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:d252edfb9f4ac1fcf20652258e3f102b26b03eef738dc8a6ffdab7d7d341d547", size = 37232768, upload-time = "2025-12-08T18:15:25.055Z" }, { url = "https://files.pythonhosted.org/packages/c9/19/5018e5352019be753b7b07f7759cdabb69ca5779fea2494be8839270df4c/llvmlite-0.46.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:379fdd1c59badeff8982cb47e4694a6143bec3bb49aa10a466e095410522064d", size = 56275173, upload-time = "2025-12-08T18:15:28.109Z" }, { url = "https://files.pythonhosted.org/packages/9f/c9/d57877759d707e84c082163c543853245f91b70c804115a5010532890f18/llvmlite-0.46.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e8cbfff7f6db0fa2c771ad24154e2a7e457c2444d7673e6de06b8b698c3b269", size = 55128628, upload-time = "2025-12-08T18:15:31.098Z" }, { url = "https://files.pythonhosted.org/packages/30/a8/e61a8c2b3cc7a597073d9cde1fcbb567e9d827f1db30c93cf80422eac70d/llvmlite-0.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:7821eda3ec1f18050f981819756631d60b6d7ab1a6cf806d9efefbe3f4082d61", size = 39153056, upload-time = "2025-12-08T18:15:33.938Z" }, ] [[package]] name = "locket" version = "1.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2f/83/97b29fe05cb6ae28d2dbd30b81e2e402a3eed5f460c26e9eaa5895ceacf5/locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632", size = 4350, upload-time = "2022-04-20T22:04:44.312Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3", size = 4398, upload-time = "2022-04-20T22:04:42.23Z" }, ] [[package]] name = "lxml" version = "6.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426, upload-time = "2025-09-22T04:04:59.287Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/77/d5/becbe1e2569b474a23f0c672ead8a29ac50b2dc1d5b9de184831bda8d14c/lxml-6.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:13e35cbc684aadf05d8711a5d1b5857c92e5e580efa9a0d2be197199c8def607", size = 8634365, upload-time = "2025-09-22T04:00:45.672Z" }, { url = "https://files.pythonhosted.org/packages/28/66/1ced58f12e804644426b85d0bb8a4478ca77bc1761455da310505f1a3526/lxml-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1675e096e17c6fe9c0e8c81434f5736c0739ff9ac6123c87c2d452f48fc938", size = 4650793, upload-time = "2025-09-22T04:00:47.783Z" }, { url = "https://files.pythonhosted.org/packages/11/84/549098ffea39dfd167e3f174b4ce983d0eed61f9d8d25b7bf2a57c3247fc/lxml-6.0.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac6e5811ae2870953390452e3476694196f98d447573234592d30488147404d", size = 4944362, upload-time = "2025-09-22T04:00:49.845Z" }, { url = "https://files.pythonhosted.org/packages/ac/bd/f207f16abf9749d2037453d56b643a7471d8fde855a231a12d1e095c4f01/lxml-6.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5aa0fc67ae19d7a64c3fe725dc9a1bb11f80e01f78289d05c6f62545affec438", size = 5083152, upload-time = "2025-09-22T04:00:51.709Z" }, { url = "https://files.pythonhosted.org/packages/15/ae/bd813e87d8941d52ad5b65071b1affb48da01c4ed3c9c99e40abb266fbff/lxml-6.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de496365750cc472b4e7902a485d3f152ecf57bd3ba03ddd5578ed8ceb4c5964", size = 5023539, upload-time = "2025-09-22T04:00:53.593Z" }, { url = "https://files.pythonhosted.org/packages/02/cd/9bfef16bd1d874fbe0cb51afb00329540f30a3283beb9f0780adbb7eec03/lxml-6.0.2-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:200069a593c5e40b8f6fc0d84d86d970ba43138c3e68619ffa234bc9bb806a4d", size = 5344853, upload-time = "2025-09-22T04:00:55.524Z" }, { url = "https://files.pythonhosted.org/packages/b8/89/ea8f91594bc5dbb879734d35a6f2b0ad50605d7fb419de2b63d4211765cc/lxml-6.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d2de809c2ee3b888b59f995625385f74629707c9355e0ff856445cdcae682b7", size = 5225133, upload-time = "2025-09-22T04:00:57.269Z" }, { url = "https://files.pythonhosted.org/packages/b9/37/9c735274f5dbec726b2db99b98a43950395ba3d4a1043083dba2ad814170/lxml-6.0.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:b2c3da8d93cf5db60e8858c17684c47d01fee6405e554fb55018dd85fc23b178", size = 4677944, upload-time = "2025-09-22T04:00:59.052Z" }, { url = "https://files.pythonhosted.org/packages/20/28/7dfe1ba3475d8bfca3878365075abe002e05d40dfaaeb7ec01b4c587d533/lxml-6.0.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:442de7530296ef5e188373a1ea5789a46ce90c4847e597856570439621d9c553", size = 5284535, upload-time = "2025-09-22T04:01:01.335Z" }, { url = "https://files.pythonhosted.org/packages/e7/cf/5f14bc0de763498fc29510e3532bf2b4b3a1c1d5d0dff2e900c16ba021ef/lxml-6.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2593c77efde7bfea7f6389f1ab249b15ed4aa5bc5cb5131faa3b843c429fbedb", size = 5067343, upload-time = "2025-09-22T04:01:03.13Z" }, { url = "https://files.pythonhosted.org/packages/1c/b0/bb8275ab5472f32b28cfbbcc6db7c9d092482d3439ca279d8d6fa02f7025/lxml-6.0.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3e3cb08855967a20f553ff32d147e14329b3ae70ced6edc2f282b94afbc74b2a", size = 4725419, upload-time = "2025-09-22T04:01:05.013Z" }, { url = "https://files.pythonhosted.org/packages/25/4c/7c222753bc72edca3b99dbadba1b064209bc8ed4ad448af990e60dcce462/lxml-6.0.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ed6c667fcbb8c19c6791bbf40b7268ef8ddf5a96940ba9404b9f9a304832f6c", size = 5275008, upload-time = "2025-09-22T04:01:07.327Z" }, { url = "https://files.pythonhosted.org/packages/6c/8c/478a0dc6b6ed661451379447cdbec77c05741a75736d97e5b2b729687828/lxml-6.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b8f18914faec94132e5b91e69d76a5c1d7b0c73e2489ea8929c4aaa10b76bbf7", size = 5248906, upload-time = "2025-09-22T04:01:09.452Z" }, { url = "https://files.pythonhosted.org/packages/2d/d9/5be3a6ab2784cdf9accb0703b65e1b64fcdd9311c9f007630c7db0cfcce1/lxml-6.0.2-cp311-cp311-win32.whl", hash = "sha256:6605c604e6daa9e0d7f0a2137bdc47a2e93b59c60a65466353e37f8272f47c46", size = 3610357, upload-time = "2025-09-22T04:01:11.102Z" }, { url = "https://files.pythonhosted.org/packages/e2/7d/ca6fb13349b473d5732fb0ee3eec8f6c80fc0688e76b7d79c1008481bf1f/lxml-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e5867f2651016a3afd8dd2c8238baa66f1e2802f44bc17e236f547ace6647078", size = 4036583, upload-time = "2025-09-22T04:01:12.766Z" }, { url = "https://files.pythonhosted.org/packages/ab/a2/51363b5ecd3eab46563645f3a2c3836a2fc67d01a1b87c5017040f39f567/lxml-6.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:4197fb2534ee05fd3e7afaab5d8bfd6c2e186f65ea7f9cd6a82809c887bd1285", size = 3680591, upload-time = "2025-09-22T04:01:14.874Z" }, { url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", size = 8661887, upload-time = "2025-09-22T04:01:17.265Z" }, { url = "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", size = 4667818, upload-time = "2025-09-22T04:01:19.688Z" }, { url = "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", size = 4950807, upload-time = "2025-09-22T04:01:21.487Z" }, { url = "https://files.pythonhosted.org/packages/da/87/f6cb9442e4bada8aab5ae7e1046264f62fdbeaa6e3f6211b93f4c0dd97f1/lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534", size = 5109179, upload-time = "2025-09-22T04:01:23.32Z" }, { url = "https://files.pythonhosted.org/packages/c8/20/a7760713e65888db79bbae4f6146a6ae5c04e4a204a3c48896c408cd6ed2/lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564", size = 5023044, upload-time = "2025-09-22T04:01:25.118Z" }, { url = "https://files.pythonhosted.org/packages/a2/b0/7e64e0460fcb36471899f75831509098f3fd7cd02a3833ac517433cb4f8f/lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f", size = 5359685, upload-time = "2025-09-22T04:01:27.398Z" }, { url = "https://files.pythonhosted.org/packages/b9/e1/e5df362e9ca4e2f48ed6411bd4b3a0ae737cc842e96877f5bf9428055ab4/lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0", size = 5654127, upload-time = "2025-09-22T04:01:29.629Z" }, { url = "https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192", size = 5253958, upload-time = "2025-09-22T04:01:31.535Z" }, { url = "https://files.pythonhosted.org/packages/35/35/d955a070994725c4f7d80583a96cab9c107c57a125b20bb5f708fe941011/lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0", size = 4711541, upload-time = "2025-09-22T04:01:33.801Z" }, { url = "https://files.pythonhosted.org/packages/1e/be/667d17363b38a78c4bd63cfd4b4632029fd68d2c2dc81f25ce9eb5224dd5/lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092", size = 5267426, upload-time = "2025-09-22T04:01:35.639Z" }, { url = "https://files.pythonhosted.org/packages/ea/47/62c70aa4a1c26569bc958c9ca86af2bb4e1f614e8c04fb2989833874f7ae/lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f", size = 5064917, upload-time = "2025-09-22T04:01:37.448Z" }, { url = "https://files.pythonhosted.org/packages/bd/55/6ceddaca353ebd0f1908ef712c597f8570cc9c58130dbb89903198e441fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8", size = 4788795, upload-time = "2025-09-22T04:01:39.165Z" }, { url = "https://files.pythonhosted.org/packages/cf/e8/fd63e15da5e3fd4c2146f8bbb3c14e94ab850589beab88e547b2dbce22e1/lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f", size = 5676759, upload-time = "2025-09-22T04:01:41.506Z" }, { url = "https://files.pythonhosted.org/packages/76/47/b3ec58dc5c374697f5ba37412cd2728f427d056315d124dd4b61da381877/lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6", size = 5255666, upload-time = "2025-09-22T04:01:43.363Z" }, { url = "https://files.pythonhosted.org/packages/19/93/03ba725df4c3d72afd9596eef4a37a837ce8e4806010569bedfcd2cb68fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322", size = 5277989, upload-time = "2025-09-22T04:01:45.215Z" }, { url = "https://files.pythonhosted.org/packages/c6/80/c06de80bfce881d0ad738576f243911fccf992687ae09fd80b734712b39c/lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849", size = 3611456, upload-time = "2025-09-22T04:01:48.243Z" }, { url = "https://files.pythonhosted.org/packages/f7/d7/0cdfb6c3e30893463fb3d1e52bc5f5f99684a03c29a0b6b605cfae879cd5/lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f", size = 4011793, upload-time = "2025-09-22T04:01:50.042Z" }, { url = "https://files.pythonhosted.org/packages/ea/7b/93c73c67db235931527301ed3785f849c78991e2e34f3fd9a6663ffda4c5/lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6", size = 3672836, upload-time = "2025-09-22T04:01:52.145Z" }, { url = "https://files.pythonhosted.org/packages/53/fd/4e8f0540608977aea078bf6d79f128e0e2c2bba8af1acf775c30baa70460/lxml-6.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9b33d21594afab46f37ae58dfadd06636f154923c4e8a4d754b0127554eb2e77", size = 8648494, upload-time = "2025-09-22T04:01:54.242Z" }, { url = "https://files.pythonhosted.org/packages/5d/f4/2a94a3d3dfd6c6b433501b8d470a1960a20ecce93245cf2db1706adf6c19/lxml-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c8963287d7a4c5c9a432ff487c52e9c5618667179c18a204bdedb27310f022f", size = 4661146, upload-time = "2025-09-22T04:01:56.282Z" }, { url = "https://files.pythonhosted.org/packages/25/2e/4efa677fa6b322013035d38016f6ae859d06cac67437ca7dc708a6af7028/lxml-6.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1941354d92699fb5ffe6ed7b32f9649e43c2feb4b97205f75866f7d21aa91452", size = 4946932, upload-time = "2025-09-22T04:01:58.989Z" }, { url = "https://files.pythonhosted.org/packages/ce/0f/526e78a6d38d109fdbaa5049c62e1d32fdd70c75fb61c4eadf3045d3d124/lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb2f6ca0ae2d983ded09357b84af659c954722bbf04dea98030064996d156048", size = 5100060, upload-time = "2025-09-22T04:02:00.812Z" }, { url = "https://files.pythonhosted.org/packages/81/76/99de58d81fa702cc0ea7edae4f4640416c2062813a00ff24bd70ac1d9c9b/lxml-6.0.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb2a12d704f180a902d7fa778c6d71f36ceb7b0d317f34cdc76a5d05aa1dd1df", size = 5019000, upload-time = "2025-09-22T04:02:02.671Z" }, { url = "https://files.pythonhosted.org/packages/b5/35/9e57d25482bc9a9882cb0037fdb9cc18f4b79d85df94fa9d2a89562f1d25/lxml-6.0.2-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:6ec0e3f745021bfed19c456647f0298d60a24c9ff86d9d051f52b509663feeb1", size = 5348496, upload-time = "2025-09-22T04:02:04.904Z" }, { url = "https://files.pythonhosted.org/packages/a6/8e/cb99bd0b83ccc3e8f0f528e9aa1f7a9965dfec08c617070c5db8d63a87ce/lxml-6.0.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:846ae9a12d54e368933b9759052d6206a9e8b250291109c48e350c1f1f49d916", size = 5643779, upload-time = "2025-09-22T04:02:06.689Z" }, { url = "https://files.pythonhosted.org/packages/d0/34/9e591954939276bb679b73773836c6684c22e56d05980e31d52a9a8deb18/lxml-6.0.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef9266d2aa545d7374938fb5c484531ef5a2ec7f2d573e62f8ce722c735685fd", size = 5244072, upload-time = "2025-09-22T04:02:08.587Z" }, { url = "https://files.pythonhosted.org/packages/8d/27/b29ff065f9aaca443ee377aff699714fcbffb371b4fce5ac4ca759e436d5/lxml-6.0.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:4077b7c79f31755df33b795dc12119cb557a0106bfdab0d2c2d97bd3cf3dffa6", size = 4718675, upload-time = "2025-09-22T04:02:10.783Z" }, { url = "https://files.pythonhosted.org/packages/2b/9f/f756f9c2cd27caa1a6ef8c32ae47aadea697f5c2c6d07b0dae133c244fbe/lxml-6.0.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a7c5d5e5f1081955358533be077166ee97ed2571d6a66bdba6ec2f609a715d1a", size = 5255171, upload-time = "2025-09-22T04:02:12.631Z" }, { url = "https://files.pythonhosted.org/packages/61/46/bb85ea42d2cb1bd8395484fd72f38e3389611aa496ac7772da9205bbda0e/lxml-6.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8f8d0cbd0674ee89863a523e6994ac25fd5be9c8486acfc3e5ccea679bad2679", size = 5057175, upload-time = "2025-09-22T04:02:14.718Z" }, { url = "https://files.pythonhosted.org/packages/95/0c/443fc476dcc8e41577f0af70458c50fe299a97bb6b7505bb1ae09aa7f9ac/lxml-6.0.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2cbcbf6d6e924c28f04a43f3b6f6e272312a090f269eff68a2982e13e5d57659", size = 4785688, upload-time = "2025-09-22T04:02:16.957Z" }, { url = "https://files.pythonhosted.org/packages/48/78/6ef0b359d45bb9697bc5a626e1992fa5d27aa3f8004b137b2314793b50a0/lxml-6.0.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dfb874cfa53340009af6bdd7e54ebc0d21012a60a4e65d927c2e477112e63484", size = 5660655, upload-time = "2025-09-22T04:02:18.815Z" }, { url = "https://files.pythonhosted.org/packages/ff/ea/e1d33808f386bc1339d08c0dcada6e4712d4ed8e93fcad5f057070b7988a/lxml-6.0.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb8dae0b6b8b7f9e96c26fdd8121522ce5de9bb5538010870bd538683d30e9a2", size = 5247695, upload-time = "2025-09-22T04:02:20.593Z" }, { url = "https://files.pythonhosted.org/packages/4f/47/eba75dfd8183673725255247a603b4ad606f4ae657b60c6c145b381697da/lxml-6.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:358d9adae670b63e95bc59747c72f4dc97c9ec58881d4627fe0120da0f90d314", size = 5269841, upload-time = "2025-09-22T04:02:22.489Z" }, { url = "https://files.pythonhosted.org/packages/76/04/5c5e2b8577bc936e219becb2e98cdb1aca14a4921a12995b9d0c523502ae/lxml-6.0.2-cp313-cp313-win32.whl", hash = "sha256:e8cd2415f372e7e5a789d743d133ae474290a90b9023197fd78f32e2dc6873e2", size = 3610700, upload-time = "2025-09-22T04:02:24.465Z" }, { url = "https://files.pythonhosted.org/packages/fe/0a/4643ccc6bb8b143e9f9640aa54e38255f9d3b45feb2cbe7ae2ca47e8782e/lxml-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:b30d46379644fbfc3ab81f8f82ae4de55179414651f110a1514f0b1f8f6cb2d7", size = 4010347, upload-time = "2025-09-22T04:02:26.286Z" }, { url = "https://files.pythonhosted.org/packages/31/ef/dcf1d29c3f530577f61e5fe2f1bd72929acf779953668a8a47a479ae6f26/lxml-6.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:13dcecc9946dca97b11b7c40d29fba63b55ab4170d3c0cf8c0c164343b9bfdcf", size = 3671248, upload-time = "2025-09-22T04:02:27.918Z" }, { url = "https://files.pythonhosted.org/packages/03/15/d4a377b385ab693ce97b472fe0c77c2b16ec79590e688b3ccc71fba19884/lxml-6.0.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:b0c732aa23de8f8aec23f4b580d1e52905ef468afb4abeafd3fec77042abb6fe", size = 8659801, upload-time = "2025-09-22T04:02:30.113Z" }, { url = "https://files.pythonhosted.org/packages/c8/e8/c128e37589463668794d503afaeb003987373c5f94d667124ffd8078bbd9/lxml-6.0.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4468e3b83e10e0317a89a33d28f7aeba1caa4d1a6fd457d115dd4ffe90c5931d", size = 4659403, upload-time = "2025-09-22T04:02:32.119Z" }, { url = "https://files.pythonhosted.org/packages/00/ce/74903904339decdf7da7847bb5741fc98a5451b42fc419a86c0c13d26fe2/lxml-6.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:abd44571493973bad4598a3be7e1d807ed45aa2adaf7ab92ab7c62609569b17d", size = 4966974, upload-time = "2025-09-22T04:02:34.155Z" }, { url = "https://files.pythonhosted.org/packages/1f/d3/131dec79ce61c5567fecf82515bd9bc36395df42501b50f7f7f3bd065df0/lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5", size = 5102953, upload-time = "2025-09-22T04:02:36.054Z" }, { url = "https://files.pythonhosted.org/packages/3a/ea/a43ba9bb750d4ffdd885f2cd333572f5bb900cd2408b67fdda07e85978a0/lxml-6.0.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:901e3b4219fa04ef766885fb40fa516a71662a4c61b80c94d25336b4934b71c0", size = 5055054, upload-time = "2025-09-22T04:02:38.154Z" }, { url = "https://files.pythonhosted.org/packages/60/23/6885b451636ae286c34628f70a7ed1fcc759f8d9ad382d132e1c8d3d9bfd/lxml-6.0.2-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:a4bf42d2e4cf52c28cc1812d62426b9503cdb0c87a6de81442626aa7d69707ba", size = 5352421, upload-time = "2025-09-22T04:02:40.413Z" }, { url = "https://files.pythonhosted.org/packages/48/5b/fc2ddfc94ddbe3eebb8e9af6e3fd65e2feba4967f6a4e9683875c394c2d8/lxml-6.0.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2c7fdaa4d7c3d886a42534adec7cfac73860b89b4e5298752f60aa5984641a0", size = 5673684, upload-time = "2025-09-22T04:02:42.288Z" }, { url = "https://files.pythonhosted.org/packages/29/9c/47293c58cc91769130fbf85531280e8cc7868f7fbb6d92f4670071b9cb3e/lxml-6.0.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98a5e1660dc7de2200b00d53fa00bcd3c35a3608c305d45a7bbcaf29fa16e83d", size = 5252463, upload-time = "2025-09-22T04:02:44.165Z" }, { url = "https://files.pythonhosted.org/packages/9b/da/ba6eceb830c762b48e711ded880d7e3e89fc6c7323e587c36540b6b23c6b/lxml-6.0.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:dc051506c30b609238d79eda75ee9cab3e520570ec8219844a72a46020901e37", size = 4698437, upload-time = "2025-09-22T04:02:46.524Z" }, { url = "https://files.pythonhosted.org/packages/a5/24/7be3f82cb7990b89118d944b619e53c656c97dc89c28cfb143fdb7cd6f4d/lxml-6.0.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8799481bbdd212470d17513a54d568f44416db01250f49449647b5ab5b5dccb9", size = 5269890, upload-time = "2025-09-22T04:02:48.812Z" }, { url = "https://files.pythonhosted.org/packages/1b/bd/dcfb9ea1e16c665efd7538fc5d5c34071276ce9220e234217682e7d2c4a5/lxml-6.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9261bb77c2dab42f3ecd9103951aeca2c40277701eb7e912c545c1b16e0e4917", size = 5097185, upload-time = "2025-09-22T04:02:50.746Z" }, { url = "https://files.pythonhosted.org/packages/21/04/a60b0ff9314736316f28316b694bccbbabe100f8483ad83852d77fc7468e/lxml-6.0.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:65ac4a01aba353cfa6d5725b95d7aed6356ddc0a3cd734de00124d285b04b64f", size = 4745895, upload-time = "2025-09-22T04:02:52.968Z" }, { url = "https://files.pythonhosted.org/packages/d6/bd/7d54bd1846e5a310d9c715921c5faa71cf5c0853372adf78aee70c8d7aa2/lxml-6.0.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b22a07cbb82fea98f8a2fd814f3d1811ff9ed76d0fc6abc84eb21527596e7cc8", size = 5695246, upload-time = "2025-09-22T04:02:54.798Z" }, { url = "https://files.pythonhosted.org/packages/fd/32/5643d6ab947bc371da21323acb2a6e603cedbe71cb4c99c8254289ab6f4e/lxml-6.0.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d759cdd7f3e055d6bc8d9bec3ad905227b2e4c785dc16c372eb5b5e83123f48a", size = 5260797, upload-time = "2025-09-22T04:02:57.058Z" }, { url = "https://files.pythonhosted.org/packages/33/da/34c1ec4cff1eea7d0b4cd44af8411806ed943141804ac9c5d565302afb78/lxml-6.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:945da35a48d193d27c188037a05fec5492937f66fb1958c24fc761fb9d40d43c", size = 5277404, upload-time = "2025-09-22T04:02:58.966Z" }, { url = "https://files.pythonhosted.org/packages/82/57/4eca3e31e54dc89e2c3507e1cd411074a17565fa5ffc437c4ae0a00d439e/lxml-6.0.2-cp314-cp314-win32.whl", hash = "sha256:be3aaa60da67e6153eb15715cc2e19091af5dc75faef8b8a585aea372507384b", size = 3670072, upload-time = "2025-09-22T04:03:38.05Z" }, { url = "https://files.pythonhosted.org/packages/e3/e0/c96cf13eccd20c9421ba910304dae0f619724dcf1702864fd59dd386404d/lxml-6.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa25afbadead523f7001caf0c2382afd272c315a033a7b06336da2637d92d6ed", size = 4080617, upload-time = "2025-09-22T04:03:39.835Z" }, { url = "https://files.pythonhosted.org/packages/d5/5d/b3f03e22b3d38d6f188ef044900a9b29b2fe0aebb94625ce9fe244011d34/lxml-6.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:063eccf89df5b24e361b123e257e437f9e9878f425ee9aae3144c77faf6da6d8", size = 3754930, upload-time = "2025-09-22T04:03:41.565Z" }, { url = "https://files.pythonhosted.org/packages/5e/5c/42c2c4c03554580708fc738d13414801f340c04c3eff90d8d2d227145275/lxml-6.0.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:6162a86d86893d63084faaf4ff937b3daea233e3682fb4474db07395794fa80d", size = 8910380, upload-time = "2025-09-22T04:03:01.645Z" }, { url = "https://files.pythonhosted.org/packages/bf/4f/12df843e3e10d18d468a7557058f8d3733e8b6e12401f30b1ef29360740f/lxml-6.0.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:414aaa94e974e23a3e92e7ca5b97d10c0cf37b6481f50911032c69eeb3991bba", size = 4775632, upload-time = "2025-09-22T04:03:03.814Z" }, { url = "https://files.pythonhosted.org/packages/e4/0c/9dc31e6c2d0d418483cbcb469d1f5a582a1cd00a1f4081953d44051f3c50/lxml-6.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48461bd21625458dd01e14e2c38dd0aea69addc3c4f960c30d9f59d7f93be601", size = 4975171, upload-time = "2025-09-22T04:03:05.651Z" }, { url = "https://files.pythonhosted.org/packages/e7/2b/9b870c6ca24c841bdd887504808f0417aa9d8d564114689266f19ddf29c8/lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed", size = 5110109, upload-time = "2025-09-22T04:03:07.452Z" }, { url = "https://files.pythonhosted.org/packages/bf/0c/4f5f2a4dd319a178912751564471355d9019e220c20d7db3fb8307ed8582/lxml-6.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5179c60288204e6ddde3f774a93350177e08876eaf3ab78aa3a3649d43eb7d37", size = 5041061, upload-time = "2025-09-22T04:03:09.297Z" }, { url = "https://files.pythonhosted.org/packages/12/64/554eed290365267671fe001a20d72d14f468ae4e6acef1e179b039436967/lxml-6.0.2-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:967aab75434de148ec80597b75062d8123cadf2943fb4281f385141e18b21338", size = 5306233, upload-time = "2025-09-22T04:03:11.651Z" }, { url = "https://files.pythonhosted.org/packages/7a/31/1d748aa275e71802ad9722df32a7a35034246b42c0ecdd8235412c3396ef/lxml-6.0.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d100fcc8930d697c6561156c6810ab4a508fb264c8b6779e6e61e2ed5e7558f9", size = 5604739, upload-time = "2025-09-22T04:03:13.592Z" }, { url = "https://files.pythonhosted.org/packages/8f/41/2c11916bcac09ed561adccacceaedd2bf0e0b25b297ea92aab99fd03d0fa/lxml-6.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ca59e7e13e5981175b8b3e4ab84d7da57993eeff53c07764dcebda0d0e64ecd", size = 5225119, upload-time = "2025-09-22T04:03:15.408Z" }, { url = "https://files.pythonhosted.org/packages/99/05/4e5c2873d8f17aa018e6afde417c80cc5d0c33be4854cce3ef5670c49367/lxml-6.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:957448ac63a42e2e49531b9d6c0fa449a1970dbc32467aaad46f11545be9af1d", size = 4633665, upload-time = "2025-09-22T04:03:17.262Z" }, { url = "https://files.pythonhosted.org/packages/0f/c9/dcc2da1bebd6275cdc723b515f93edf548b82f36a5458cca3578bc899332/lxml-6.0.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7fc49c37f1786284b12af63152fe1d0990722497e2d5817acfe7a877522f9a9", size = 5234997, upload-time = "2025-09-22T04:03:19.14Z" }, { url = "https://files.pythonhosted.org/packages/9c/e2/5172e4e7468afca64a37b81dba152fc5d90e30f9c83c7c3213d6a02a5ce4/lxml-6.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e19e0643cc936a22e837f79d01a550678da8377d7d801a14487c10c34ee49c7e", size = 5090957, upload-time = "2025-09-22T04:03:21.436Z" }, { url = "https://files.pythonhosted.org/packages/a5/b3/15461fd3e5cd4ddcb7938b87fc20b14ab113b92312fc97afe65cd7c85de1/lxml-6.0.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1db01e5cf14345628e0cbe71067204db658e2fb8e51e7f33631f5f4735fefd8d", size = 4764372, upload-time = "2025-09-22T04:03:23.27Z" }, { url = "https://files.pythonhosted.org/packages/05/33/f310b987c8bf9e61c4dd8e8035c416bd3230098f5e3cfa69fc4232de7059/lxml-6.0.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:875c6b5ab39ad5291588aed6925fac99d0097af0dd62f33c7b43736043d4a2ec", size = 5634653, upload-time = "2025-09-22T04:03:25.767Z" }, { url = "https://files.pythonhosted.org/packages/70/ff/51c80e75e0bc9382158133bdcf4e339b5886c6ee2418b5199b3f1a61ed6d/lxml-6.0.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cdcbed9ad19da81c480dfd6dd161886db6096083c9938ead313d94b30aadf272", size = 5233795, upload-time = "2025-09-22T04:03:27.62Z" }, { url = "https://files.pythonhosted.org/packages/56/4d/4856e897df0d588789dd844dbed9d91782c4ef0b327f96ce53c807e13128/lxml-6.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80dadc234ebc532e09be1975ff538d154a7fa61ea5031c03d25178855544728f", size = 5257023, upload-time = "2025-09-22T04:03:30.056Z" }, { url = "https://files.pythonhosted.org/packages/0f/85/86766dfebfa87bea0ab78e9ff7a4b4b45225df4b4d3b8cc3c03c5cd68464/lxml-6.0.2-cp314-cp314t-win32.whl", hash = "sha256:da08e7bb297b04e893d91087df19638dc7a6bb858a954b0cc2b9f5053c922312", size = 3911420, upload-time = "2025-09-22T04:03:32.198Z" }, { url = "https://files.pythonhosted.org/packages/fe/1a/b248b355834c8e32614650b8008c69ffeb0ceb149c793961dd8c0b991bb3/lxml-6.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:252a22982dca42f6155125ac76d3432e548a7625d56f5a273ee78a5057216eca", size = 4406837, upload-time = "2025-09-22T04:03:34.027Z" }, { url = "https://files.pythonhosted.org/packages/92/aa/df863bcc39c5e0946263454aba394de8a9084dbaff8ad143846b0d844739/lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c", size = 3822205, upload-time = "2025-09-22T04:03:36.249Z" }, { url = "https://files.pythonhosted.org/packages/0b/11/29d08bc103a62c0eba8016e7ed5aeebbf1e4312e83b0b1648dd203b0e87d/lxml-6.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1c06035eafa8404b5cf475bb37a9f6088b0aca288d4ccc9d69389750d5543700", size = 3949829, upload-time = "2025-09-22T04:04:45.608Z" }, { url = "https://files.pythonhosted.org/packages/12/b3/52ab9a3b31e5ab8238da241baa19eec44d2ab426532441ee607165aebb52/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c7d13103045de1bdd6fe5d61802565f1a3537d70cd3abf596aa0af62761921ee", size = 4226277, upload-time = "2025-09-22T04:04:47.754Z" }, { url = "https://files.pythonhosted.org/packages/a0/33/1eaf780c1baad88224611df13b1c2a9dfa460b526cacfe769103ff50d845/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a3c150a95fbe5ac91de323aa756219ef9cf7fde5a3f00e2281e30f33fa5fa4f", size = 4330433, upload-time = "2025-09-22T04:04:49.907Z" }, { url = "https://files.pythonhosted.org/packages/7a/c1/27428a2ff348e994ab4f8777d3a0ad510b6b92d37718e5887d2da99952a2/lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60fa43be34f78bebb27812ed90f1925ec99560b0fa1decdb7d12b84d857d31e9", size = 4272119, upload-time = "2025-09-22T04:04:51.801Z" }, { url = "https://files.pythonhosted.org/packages/f0/d0/3020fa12bcec4ab62f97aab026d57c2f0cfd480a558758d9ca233bb6a79d/lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21c73b476d3cfe836be731225ec3421fa2f048d84f6df6a8e70433dff1376d5a", size = 4417314, upload-time = "2025-09-22T04:04:55.024Z" }, { url = "https://files.pythonhosted.org/packages/6c/77/d7f491cbc05303ac6801651aabeb262d43f319288c1ea96c66b1d2692ff3/lxml-6.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:27220da5be049e936c3aca06f174e8827ca6445a4353a1995584311487fc4e3e", size = 3518768, upload-time = "2025-09-22T04:04:57.097Z" }, ] [[package]] name = "lz4" version = "4.4.5" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/57/51/f1b86d93029f418033dddf9b9f79c8d2641e7454080478ee2aab5123173e/lz4-4.4.5.tar.gz", hash = "sha256:5f0b9e53c1e82e88c10d7c180069363980136b9d7a8306c4dca4f760d60c39f0", size = 172886, upload-time = "2025-11-03T13:02:36.061Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/93/5b/6edcd23319d9e28b1bedf32768c3d1fd56eed8223960a2c47dacd2cec2af/lz4-4.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d6da84a26b3aa5da13a62e4b89ab36a396e9327de8cd48b436a3467077f8ccd4", size = 207391, upload-time = "2025-11-03T13:01:36.644Z" }, { url = "https://files.pythonhosted.org/packages/34/36/5f9b772e85b3d5769367a79973b8030afad0d6b724444083bad09becd66f/lz4-4.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61d0ee03e6c616f4a8b69987d03d514e8896c8b1b7cc7598ad029e5c6aedfd43", size = 207146, upload-time = "2025-11-03T13:01:37.928Z" }, { url = "https://files.pythonhosted.org/packages/04/f4/f66da5647c0d72592081a37c8775feacc3d14d2625bbdaabd6307c274565/lz4-4.4.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:33dd86cea8375d8e5dd001e41f321d0a4b1eb7985f39be1b6a4f466cd480b8a7", size = 1292623, upload-time = "2025-11-03T13:01:39.341Z" }, { url = "https://files.pythonhosted.org/packages/85/fc/5df0f17467cdda0cad464a9197a447027879197761b55faad7ca29c29a04/lz4-4.4.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:609a69c68e7cfcfa9d894dc06be13f2e00761485b62df4e2472f1b66f7b405fb", size = 1279982, upload-time = "2025-11-03T13:01:40.816Z" }, { url = "https://files.pythonhosted.org/packages/25/3b/b55cb577aa148ed4e383e9700c36f70b651cd434e1c07568f0a86c9d5fbb/lz4-4.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:75419bb1a559af00250b8f1360d508444e80ed4b26d9d40ec5b09fe7875cb989", size = 1368674, upload-time = "2025-11-03T13:01:42.118Z" }, { url = "https://files.pythonhosted.org/packages/fb/31/e97e8c74c59ea479598e5c55cbe0b1334f03ee74ca97726e872944ed42df/lz4-4.4.5-cp311-cp311-win32.whl", hash = "sha256:12233624f1bc2cebc414f9efb3113a03e89acce3ab6f72035577bc61b270d24d", size = 88168, upload-time = "2025-11-03T13:01:43.282Z" }, { url = "https://files.pythonhosted.org/packages/18/47/715865a6c7071f417bef9b57c8644f29cb7a55b77742bd5d93a609274e7e/lz4-4.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:8a842ead8ca7c0ee2f396ca5d878c4c40439a527ebad2b996b0444f0074ed004", size = 99491, upload-time = "2025-11-03T13:01:44.167Z" }, { url = "https://files.pythonhosted.org/packages/14/e7/ac120c2ca8caec5c945e6356ada2aa5cfabd83a01e3170f264a5c42c8231/lz4-4.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:83bc23ef65b6ae44f3287c38cbf82c269e2e96a26e560aa551735883388dcc4b", size = 91271, upload-time = "2025-11-03T13:01:45.016Z" }, { url = "https://files.pythonhosted.org/packages/1b/ac/016e4f6de37d806f7cc8f13add0a46c9a7cfc41a5ddc2bc831d7954cf1ce/lz4-4.4.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:df5aa4cead2044bab83e0ebae56e0944cc7fcc1505c7787e9e1057d6d549897e", size = 207163, upload-time = "2025-11-03T13:01:45.895Z" }, { url = "https://files.pythonhosted.org/packages/8d/df/0fadac6e5bd31b6f34a1a8dbd4db6a7606e70715387c27368586455b7fc9/lz4-4.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d0bf51e7745484d2092b3a51ae6eb58c3bd3ce0300cf2b2c14f76c536d5697a", size = 207150, upload-time = "2025-11-03T13:01:47.205Z" }, { url = "https://files.pythonhosted.org/packages/b7/17/34e36cc49bb16ca73fb57fbd4c5eaa61760c6b64bce91fcb4e0f4a97f852/lz4-4.4.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7b62f94b523c251cf32aa4ab555f14d39bd1a9df385b72443fd76d7c7fb051f5", size = 1292045, upload-time = "2025-11-03T13:01:48.667Z" }, { url = "https://files.pythonhosted.org/packages/90/1c/b1d8e3741e9fc89ed3b5f7ef5f22586c07ed6bb04e8343c2e98f0fa7ff04/lz4-4.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c3ea562c3af274264444819ae9b14dbbf1ab070aff214a05e97db6896c7597e", size = 1279546, upload-time = "2025-11-03T13:01:50.159Z" }, { url = "https://files.pythonhosted.org/packages/55/d9/e3867222474f6c1b76e89f3bd914595af69f55bf2c1866e984c548afdc15/lz4-4.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24092635f47538b392c4eaeff14c7270d2c8e806bf4be2a6446a378591c5e69e", size = 1368249, upload-time = "2025-11-03T13:01:51.273Z" }, { url = "https://files.pythonhosted.org/packages/b2/e7/d667d337367686311c38b580d1ca3d5a23a6617e129f26becd4f5dc458df/lz4-4.4.5-cp312-cp312-win32.whl", hash = "sha256:214e37cfe270948ea7eb777229e211c601a3e0875541c1035ab408fbceaddf50", size = 88189, upload-time = "2025-11-03T13:01:52.605Z" }, { url = "https://files.pythonhosted.org/packages/a5/0b/a54cd7406995ab097fceb907c7eb13a6ddd49e0b231e448f1a81a50af65c/lz4-4.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:713a777de88a73425cf08eb11f742cd2c98628e79a8673d6a52e3c5f0c116f33", size = 99497, upload-time = "2025-11-03T13:01:53.477Z" }, { url = "https://files.pythonhosted.org/packages/6a/7e/dc28a952e4bfa32ca16fa2eb026e7a6ce5d1411fcd5986cd08c74ec187b9/lz4-4.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:a88cbb729cc333334ccfb52f070463c21560fca63afcf636a9f160a55fac3301", size = 91279, upload-time = "2025-11-03T13:01:54.419Z" }, { url = "https://files.pythonhosted.org/packages/2f/46/08fd8ef19b782f301d56a9ccfd7dafec5fd4fc1a9f017cf22a1accb585d7/lz4-4.4.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6bb05416444fafea170b07181bc70640975ecc2a8c92b3b658c554119519716c", size = 207171, upload-time = "2025-11-03T13:01:56.595Z" }, { url = "https://files.pythonhosted.org/packages/8f/3f/ea3334e59de30871d773963997ecdba96c4584c5f8007fd83cfc8f1ee935/lz4-4.4.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b424df1076e40d4e884cfcc4c77d815368b7fb9ebcd7e634f937725cd9a8a72a", size = 207163, upload-time = "2025-11-03T13:01:57.721Z" }, { url = "https://files.pythonhosted.org/packages/41/7b/7b3a2a0feb998969f4793c650bb16eff5b06e80d1f7bff867feb332f2af2/lz4-4.4.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:216ca0c6c90719731c64f41cfbd6f27a736d7e50a10b70fad2a9c9b262ec923d", size = 1292136, upload-time = "2025-11-03T13:02:00.375Z" }, { url = "https://files.pythonhosted.org/packages/89/d1/f1d259352227bb1c185288dd694121ea303e43404aa77560b879c90e7073/lz4-4.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:533298d208b58b651662dd972f52d807d48915176e5b032fb4f8c3b6f5fe535c", size = 1279639, upload-time = "2025-11-03T13:02:01.649Z" }, { url = "https://files.pythonhosted.org/packages/d2/fb/ba9256c48266a09012ed1d9b0253b9aa4fe9cdff094f8febf5b26a4aa2a2/lz4-4.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:451039b609b9a88a934800b5fc6ee401c89ad9c175abf2f4d9f8b2e4ef1afc64", size = 1368257, upload-time = "2025-11-03T13:02:03.35Z" }, { url = "https://files.pythonhosted.org/packages/a5/6d/dee32a9430c8b0e01bbb4537573cabd00555827f1a0a42d4e24ca803935c/lz4-4.4.5-cp313-cp313-win32.whl", hash = "sha256:a5f197ffa6fc0e93207b0af71b302e0a2f6f29982e5de0fbda61606dd3a55832", size = 88191, upload-time = "2025-11-03T13:02:04.406Z" }, { url = "https://files.pythonhosted.org/packages/18/e0/f06028aea741bbecb2a7e9648f4643235279a770c7ffaf70bd4860c73661/lz4-4.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:da68497f78953017deb20edff0dba95641cc86e7423dfadf7c0264e1ac60dc22", size = 99502, upload-time = "2025-11-03T13:02:05.886Z" }, { url = "https://files.pythonhosted.org/packages/61/72/5bef44afb303e56078676b9f2486f13173a3c1e7f17eaac1793538174817/lz4-4.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:c1cfa663468a189dab510ab231aad030970593f997746d7a324d40104db0d0a9", size = 91285, upload-time = "2025-11-03T13:02:06.77Z" }, { url = "https://files.pythonhosted.org/packages/49/55/6a5c2952971af73f15ed4ebfdd69774b454bd0dc905b289082ca8664fba1/lz4-4.4.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67531da3b62f49c939e09d56492baf397175ff39926d0bd5bd2d191ac2bff95f", size = 207348, upload-time = "2025-11-03T13:02:08.117Z" }, { url = "https://files.pythonhosted.org/packages/4e/d7/fd62cbdbdccc35341e83aabdb3f6d5c19be2687d0a4eaf6457ddf53bba64/lz4-4.4.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a1acbbba9edbcbb982bc2cac5e7108f0f553aebac1040fbec67a011a45afa1ba", size = 207340, upload-time = "2025-11-03T13:02:09.152Z" }, { url = "https://files.pythonhosted.org/packages/77/69/225ffadaacb4b0e0eb5fd263541edd938f16cd21fe1eae3cd6d5b6a259dc/lz4-4.4.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a482eecc0b7829c89b498fda883dbd50e98153a116de612ee7c111c8bcf82d1d", size = 1293398, upload-time = "2025-11-03T13:02:10.272Z" }, { url = "https://files.pythonhosted.org/packages/c6/9e/2ce59ba4a21ea5dc43460cba6f34584e187328019abc0e66698f2b66c881/lz4-4.4.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e099ddfaa88f59dd8d36c8a3c66bd982b4984edf127eb18e30bb49bdba68ce67", size = 1281209, upload-time = "2025-11-03T13:02:12.091Z" }, { url = "https://files.pythonhosted.org/packages/80/4f/4d946bd1624ec229b386a3bc8e7a85fa9a963d67d0a62043f0af0978d3da/lz4-4.4.5-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2af2897333b421360fdcce895c6f6281dc3fab018d19d341cf64d043fc8d90d", size = 1369406, upload-time = "2025-11-03T13:02:13.683Z" }, { url = "https://files.pythonhosted.org/packages/02/a2/d429ba4720a9064722698b4b754fb93e42e625f1318b8fe834086c7c783b/lz4-4.4.5-cp313-cp313t-win32.whl", hash = "sha256:66c5de72bf4988e1b284ebdd6524c4bead2c507a2d7f172201572bac6f593901", size = 88325, upload-time = "2025-11-03T13:02:14.743Z" }, { url = "https://files.pythonhosted.org/packages/4b/85/7ba10c9b97c06af6c8f7032ec942ff127558863df52d866019ce9d2425cf/lz4-4.4.5-cp313-cp313t-win_amd64.whl", hash = "sha256:cdd4bdcbaf35056086d910d219106f6a04e1ab0daa40ec0eeef1626c27d0fddb", size = 99643, upload-time = "2025-11-03T13:02:15.978Z" }, { url = "https://files.pythonhosted.org/packages/77/4d/a175459fb29f909e13e57c8f475181ad8085d8d7869bd8ad99033e3ee5fa/lz4-4.4.5-cp313-cp313t-win_arm64.whl", hash = "sha256:28ccaeb7c5222454cd5f60fcd152564205bcb801bd80e125949d2dfbadc76bbd", size = 91504, upload-time = "2025-11-03T13:02:17.313Z" }, { url = "https://files.pythonhosted.org/packages/63/9c/70bdbdb9f54053a308b200b4678afd13efd0eafb6ddcbb7f00077213c2e5/lz4-4.4.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c216b6d5275fc060c6280936bb3bb0e0be6126afb08abccde27eed23dead135f", size = 207586, upload-time = "2025-11-03T13:02:18.263Z" }, { url = "https://files.pythonhosted.org/packages/b6/cb/bfead8f437741ce51e14b3c7d404e3a1f6b409c440bad9b8f3945d4c40a7/lz4-4.4.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c8e71b14938082ebaf78144f3b3917ac715f72d14c076f384a4c062df96f9df6", size = 207161, upload-time = "2025-11-03T13:02:19.286Z" }, { url = "https://files.pythonhosted.org/packages/e7/18/b192b2ce465dfbeabc4fc957ece7a1d34aded0d95a588862f1c8a86ac448/lz4-4.4.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9b5e6abca8df9f9bdc5c3085f33ff32cdc86ed04c65e0355506d46a5ac19b6e9", size = 1292415, upload-time = "2025-11-03T13:02:20.829Z" }, { url = "https://files.pythonhosted.org/packages/67/79/a4e91872ab60f5e89bfad3e996ea7dc74a30f27253faf95865771225ccba/lz4-4.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b84a42da86e8ad8537aabef062e7f661f4a877d1c74d65606c49d835d36d668", size = 1279920, upload-time = "2025-11-03T13:02:22.013Z" }, { url = "https://files.pythonhosted.org/packages/f1/01/d52c7b11eaa286d49dae619c0eec4aabc0bf3cda7a7467eb77c62c4471f3/lz4-4.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bba042ec5a61fa77c7e380351a61cb768277801240249841defd2ff0a10742f", size = 1368661, upload-time = "2025-11-03T13:02:23.208Z" }, { url = "https://files.pythonhosted.org/packages/f7/da/137ddeea14c2cb86864838277b2607d09f8253f152156a07f84e11768a28/lz4-4.4.5-cp314-cp314-win32.whl", hash = "sha256:bd85d118316b53ed73956435bee1997bd06cc66dd2fa74073e3b1322bd520a67", size = 90139, upload-time = "2025-11-03T13:02:24.301Z" }, { url = "https://files.pythonhosted.org/packages/18/2c/8332080fd293f8337779a440b3a143f85e374311705d243439a3349b81ad/lz4-4.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:92159782a4502858a21e0079d77cdcaade23e8a5d252ddf46b0652604300d7be", size = 101497, upload-time = "2025-11-03T13:02:25.187Z" }, { url = "https://files.pythonhosted.org/packages/ca/28/2635a8141c9a4f4bc23f5135a92bbcf48d928d8ca094088c962df1879d64/lz4-4.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:d994b87abaa7a88ceb7a37c90f547b8284ff9da694e6afcfaa8568d739faf3f7", size = 93812, upload-time = "2025-11-03T13:02:26.133Z" }, ] [[package]] name = "markdown-it-py" version = "4.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] [package.optional-dependencies] linkify = [ { name = "linkify-it-py" }, ] [[package]] name = "markupsafe" version = "3.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] [[package]] name = "matplotlib" version = "3.10.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "contourpy" }, { name = "cycler" }, { name = "fonttools" }, { name = "kiwisolver" }, { name = "numpy" }, { name = "packaging" }, { name = "pillow" }, { name = "pyparsing" }, { name = "python-dateutil" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" }, { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" }, { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" }, { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" }, { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" }, { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" }, { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" }, { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" }, { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" }, { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" }, { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" }, { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" }, { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" }, { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" }, { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" }, { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" }, { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" }, { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" }, { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" }, { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" }, { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" }, { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" }, { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" }, { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" }, { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" }, { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" }, { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" }, { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" }, { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" }, { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" }, { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" }, { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" }, { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" }, { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" }, { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" }, { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" }, { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, ] [[package]] name = "matplotlib-inline" version = "0.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "traitlets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, ] [[package]] name = "mdit-py-plugins" version = "0.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" }, ] [[package]] name = "mdurl" version = "0.1.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] [[package]] name = "memory-profiler" version = "0.61.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "psutil" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b2/88/e1907e1ca3488f2d9507ca8b0ae1add7b1cd5d3ca2bc8e5b329382ea2c7b/memory_profiler-0.61.0.tar.gz", hash = "sha256:4e5b73d7864a1d1292fb76a03e82a3e78ef934d06828a698d9dada76da2067b0", size = 35935, upload-time = "2022-11-15T17:57:28.994Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/49/26/aaca612a0634ceede20682e692a6c55e35a94c21ba36b807cc40fe910ae1/memory_profiler-0.61.0-py3-none-any.whl", hash = "sha256:400348e61031e3942ad4d4109d18753b2fb08c2f6fb8290671c5513a34182d84", size = 31803, upload-time = "2022-11-15T17:57:27.031Z" }, ] [[package]] name = "memray" version = "1.19.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jinja2" }, { name = "rich" }, { name = "textual" }, ] sdist = { url = "https://files.pythonhosted.org/packages/36/18/5df5995a7b142e12ab194f4b2fd1473efd51f4f622dfe47f3c013c3c11f7/memray-1.19.1.tar.gz", hash = "sha256:7fcf306eae2c00144920b01913f42fa7f235af7a80fa3226ab124672a5cb1d8f", size = 2395421, upload-time = "2025-10-16T02:26:51.513Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0f/3b/3b4486ca09a304b5083c211bfc11ef8f982dea8ddfee81bd53d13dcae57d/memray-1.19.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:2cb5026aede2805215edc442519d85ecf0604e98bd1d9ef6be060004547f6688", size = 2185258, upload-time = "2025-10-16T02:25:37.4Z" }, { url = "https://files.pythonhosted.org/packages/eb/d9/3a0765130b889ccc6f79c33835f90e71fd5b9093e4a4ccd00e3136bbd337/memray-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2eb057b3e9545a1bc90ca71911834bde019f66d7e5306729abce3478a23855b", size = 2152129, upload-time = "2025-10-16T02:25:38.863Z" }, { url = "https://files.pythonhosted.org/packages/ad/cc/82c52291e161148a6bff30525d7066d146c8b74507894580884944f2efb1/memray-1.19.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:004588fbf0ac91fb15d58e09b16c5fc28644ee48893b04dfcd28338ae56e378d", size = 9787551, upload-time = "2025-10-16T02:25:40.285Z" }, { url = "https://files.pythonhosted.org/packages/2e/c4/630d0ec979c0d2a36edaaf3f8cc9de7eaa19b0319147b5c106abb53f429c/memray-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:548cb205ef856f546275754abc1c5f7f6aafac427c247bb4581791eaaa47a770", size = 10031985, upload-time = "2025-10-16T02:25:42.576Z" }, { url = "https://files.pythonhosted.org/packages/04/11/c00f16dca17915657c64cf81892a306afe3fd10fbaa872eeb1cf7b6f3226/memray-1.19.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b53dfb11f22d390a3d58cfee59eef8c2385bcc3cff5e7f79c80fa5952bc224a4", size = 9415840, upload-time = "2025-10-16T02:25:44.199Z" }, { url = "https://files.pythonhosted.org/packages/3e/41/e2536161e6069b592b6d749f9a31182cb5e9be7bdb373a4ba8ae8ad33089/memray-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:464a2705c601ab2ff59d26c99345965a33bcd98065877a0a1884d9a17745ccd1", size = 12260092, upload-time = "2025-10-16T02:25:47.048Z" }, { url = "https://files.pythonhosted.org/packages/49/8d/42030314a7f8721984e3df85186c8432b15f05b2b6d915ed0f322aa7eb45/memray-1.19.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:57b0430e4800b8cbc38e1f529ad7af959cc96386e00773c8af57c46eddb15ecd", size = 2187201, upload-time = "2025-10-16T02:25:48.744Z" }, { url = "https://files.pythonhosted.org/packages/fa/8c/92972176b8079a7ffb367958e118475c7a0d13c3983215fd280f4ea69c6f/memray-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:595414e753a0152282974b954827aeaf552dc02f47ed16a2743821ed461b6c51", size = 2155387, upload-time = "2025-10-16T02:25:49.802Z" }, { url = "https://files.pythonhosted.org/packages/7a/ab/47e5beaa5291c2e3e2e695bcbaf8266ed61734ece75fb8bf8f24041ad660/memray-1.19.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ead57c4be9ea89b78d8ce2017f8f3e28f552fc2279cf5d24bf75d70bdfe39ca7", size = 9748375, upload-time = "2025-10-16T02:25:51.107Z" }, { url = "https://files.pythonhosted.org/packages/ef/15/d9331de7f2e7ff88289998d0feb5b14e97902abac1753b6238fdc0b9c395/memray-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:41d829191287a995eea8b832fe7c8de243cf9e5d32d53169952695c7210e3a6b", size = 10019968, upload-time = "2025-10-16T02:25:53.357Z" }, { url = "https://files.pythonhosted.org/packages/a6/48/07353c55b8e40d03f125e2fb750cae3845dabed6a3c4e7c78214cfd927f5/memray-1.19.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5855a9c3f3cfcf8ef01151514332535756b5d7be17bdba84016b0ca57d86f7f8", size = 9397345, upload-time = "2025-10-16T02:25:54.998Z" }, { url = "https://files.pythonhosted.org/packages/51/05/47706972dc07c50ed7c4098d6e0d19e322dee05769952ff945d5e54dc04d/memray-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:216918a42abdd3c18c4771862584feda3a24bf7205db6f000a41be9ddc1c98b4", size = 12238354, upload-time = "2025-10-16T02:25:56.684Z" }, { url = "https://files.pythonhosted.org/packages/0e/5f/a67da1226dc993501253164bd348d553a522e954057fd1042a74d0ee5768/memray-1.19.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:56b20156c2754762ccfcfa03fd88ce33ecd712aacd302ef099a871b3197fe4a2", size = 2185883, upload-time = "2025-10-16T02:25:59.046Z" }, { url = "https://files.pythonhosted.org/packages/50/94/e48e6999910b542254e5354b07cac6cad6dd1c4d4f1dd8b2cb16c6bdffc6/memray-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e9d93995a91a8383fda95a1f7a15247aca2abd2f80f7f7c7ff56b3d89a5d7893", size = 2154720, upload-time = "2025-10-16T02:26:00.547Z" }, { url = "https://files.pythonhosted.org/packages/b0/dd/5d90c042c0afce46b42de271b6157ca32048522f7ba8097a602593b2292a/memray-1.19.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:500956020d245ad3440cc2fae06c1d781f339e30f8d58654bc5ae9f51f999fab", size = 9745457, upload-time = "2025-10-16T02:26:01.761Z" }, { url = "https://files.pythonhosted.org/packages/52/40/617b15e62d5de1718e81ee436a1f19d4d40274ead97ac0eda188baebb986/memray-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9770b6046a8b7d7bb982c2cefb324a60a0ee7cd7c35c58c0df058355a9a54676", size = 10019011, upload-time = "2025-10-16T02:26:03.75Z" }, { url = "https://files.pythonhosted.org/packages/47/9c/cdd27e52876244a8350ade32460eb18ae4a5f69656d0f02474f9fbdb1f85/memray-1.19.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89593bfec1924aff4571e7bb00066b1cd832a828d701b0380009d790139aa814", size = 9397754, upload-time = "2025-10-16T02:26:05.934Z" }, { url = "https://files.pythonhosted.org/packages/4e/74/17352ebd79117d46064016cce8389d88920da1cb99883cb1838f59221176/memray-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5dd00e0b4f5820f7a793691c0faeb15e4fbb5472198184605c29d0a961355741", size = 12244258, upload-time = "2025-10-16T02:26:07.614Z" }, { url = "https://files.pythonhosted.org/packages/c8/74/29f3f3f07b2e5a208d9f9bf3fee2374a5f155ff82c4b124b69b8ee246cc7/memray-1.19.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:b202f1d96211d73712f5db8281c437dcbbd9cc91e520ca44b8406466b9672624", size = 2185812, upload-time = "2025-10-16T02:26:09.582Z" }, { url = "https://files.pythonhosted.org/packages/f6/81/e96753df56e5369b8e123fa62f291aac1ca7d34b8368c2cf7a11e91e2a0e/memray-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f1a99cbb0b413a945e07529e521c7441cb46e4d5e6868dd810cbdaa80af0b74c", size = 2156210, upload-time = "2025-10-16T02:26:10.655Z" }, { url = "https://files.pythonhosted.org/packages/34/0c/8de8faa01bf8fcf8938d845d93026310b8c3ee4cbb2402382a92898533d5/memray-1.19.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:c48461e7a8ba0b12ae740316e41564e18db2533ebeb1a093b2c8232d9c7c2653", size = 9745083, upload-time = "2025-10-16T02:26:12.194Z" }, { url = "https://files.pythonhosted.org/packages/15/77/9a0e205fd77ceabd2ce2359cb6b27e7e7e36cb9fc74ee1361643246b8307/memray-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:265812e729c90a9240d6a23dfa89d8bea11cb67d37a1411f7a690948584ff024", size = 9999936, upload-time = "2025-10-16T02:26:16.636Z" }, { url = "https://files.pythonhosted.org/packages/e3/ea/58049103b757f8cdf6bff78eb24317d781f28bc44a5fe38887efe1937ac8/memray-1.19.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:808eb35fa012fa8e25582e3c9b76d9f0471e87776c7cd86e6c149da34fed22ed", size = 9394455, upload-time = "2025-10-16T02:26:18.423Z" }, { url = "https://files.pythonhosted.org/packages/1e/fc/93a97315d3d321243b60fe70366db1b7846e4b5911e7ebda7e118522e5c1/memray-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e4b33fa1b6e8619e589882b44e6bdce0ad51d8bea2dd24f7afae6efcfcd8ffa8", size = 12233185, upload-time = "2025-10-16T02:26:20.508Z" }, { url = "https://files.pythonhosted.org/packages/ad/3c/e5154059614a181fd13ed4106b4a69a811da35a2dabf2be1642c640977c9/memray-1.19.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:e905d04e337e1482af988f349b1062ec330408bf1d8e5b0cfe8c0c7b47959692", size = 2202778, upload-time = "2025-10-16T02:26:22.094Z" }, { url = "https://files.pythonhosted.org/packages/52/3f/473fb7935f4f34834da63a59f659201b917cd1386decbccf7c8e9bfd74cc/memray-1.19.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa0d8d8df8a0cad97e934dcd1cb698af00ffb10cd79277907c2cf97212f0bd9", size = 2172671, upload-time = "2025-10-16T02:26:23.187Z" }, { url = "https://files.pythonhosted.org/packages/51/f5/13f1ce7a88d4437eb2b4a24c7b7cead1fedf9284e5381b86e4630a3ce044/memray-1.19.1-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2fe3886eef669017810782ce63b1cdf8a426f07a27ea6a9f73d9dc3e5c448b0b", size = 9703654, upload-time = "2025-10-16T02:26:24.402Z" }, { url = "https://files.pythonhosted.org/packages/bf/0a/a23f7b893915bfb1536963d5923ec048f10c70ec83c2ba31843d357be4c5/memray-1.19.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f7238113251d325da2d405b067ec180842b93a7fb10ff06fb3f7c261225b33ae", size = 9962696, upload-time = "2025-10-16T02:26:26.142Z" }, { url = "https://files.pythonhosted.org/packages/37/28/c1a1fc6bdd2cf3f5241623c54ac66525b30c9c7bb16e2ad70046d9bd8db7/memray-1.19.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:334754a0ad5664a703516307772a4555ffdc616586168b28efd31e8862a6cdb1", size = 9379408, upload-time = "2025-10-16T02:26:27.937Z" }, { url = "https://files.pythonhosted.org/packages/ab/45/4bf72fff0070f1760e0ead5fc78b50e2bef35be702cb7c3cd5b33aa776d5/memray-1.19.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6a193cc20bbe60eccee8c9b4d9eb78ccd69a3248f0291d5d1a7fdda62aa19b53", size = 12148593, upload-time = "2025-10-16T02:26:29.708Z" }, ] [[package]] name = "mistune" version = "3.2.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467, upload-time = "2025-12-23T11:36:34.994Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" }, ] [[package]] name = "msgpack" version = "1.1.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2c/97/560d11202bcd537abca693fd85d81cebe2107ba17301de42b01ac1677b69/msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2e86a607e558d22985d856948c12a3fa7b42efad264dca8a3ebbcfa2735d786c", size = 82271, upload-time = "2025-10-08T09:14:49.967Z" }, { url = "https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0", size = 84914, upload-time = "2025-10-08T09:14:50.958Z" }, { url = "https://files.pythonhosted.org/packages/71/46/b817349db6886d79e57a966346cf0902a426375aadc1e8e7a86a75e22f19/msgpack-1.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61c8aa3bd513d87c72ed0b37b53dd5c5a0f58f2ff9f26e1555d3bd7948fb7296", size = 416962, upload-time = "2025-10-08T09:14:51.997Z" }, { url = "https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:454e29e186285d2ebe65be34629fa0e8605202c60fbc7c4c650ccd41870896ef", size = 426183, upload-time = "2025-10-08T09:14:53.477Z" }, { url = "https://files.pythonhosted.org/packages/25/98/6a19f030b3d2ea906696cedd1eb251708e50a5891d0978b012cb6107234c/msgpack-1.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7bc8813f88417599564fafa59fd6f95be417179f76b40325b500b3c98409757c", size = 411454, upload-time = "2025-10-08T09:14:54.648Z" }, { url = "https://files.pythonhosted.org/packages/b7/cd/9098fcb6adb32187a70b7ecaabf6339da50553351558f37600e53a4a2a23/msgpack-1.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bafca952dc13907bdfdedfc6a5f579bf4f292bdd506fadb38389afa3ac5b208e", size = 422341, upload-time = "2025-10-08T09:14:56.328Z" }, { url = "https://files.pythonhosted.org/packages/e6/ae/270cecbcf36c1dc85ec086b33a51a4d7d08fc4f404bdbc15b582255d05ff/msgpack-1.1.2-cp311-cp311-win32.whl", hash = "sha256:602b6740e95ffc55bfb078172d279de3773d7b7db1f703b2f1323566b878b90e", size = 64747, upload-time = "2025-10-08T09:14:57.882Z" }, { url = "https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68", size = 71633, upload-time = "2025-10-08T09:14:59.177Z" }, { url = "https://files.pythonhosted.org/packages/73/4d/7c4e2b3d9b1106cd0aa6cb56cc57c6267f59fa8bfab7d91df5adc802c847/msgpack-1.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:86f8136dfa5c116365a8a651a7d7484b65b13339731dd6faebb9a0242151c406", size = 64755, upload-time = "2025-10-08T09:15:00.48Z" }, { url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939, upload-time = "2025-10-08T09:15:01.472Z" }, { url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064, upload-time = "2025-10-08T09:15:03.764Z" }, { url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131, upload-time = "2025-10-08T09:15:05.136Z" }, { url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556, upload-time = "2025-10-08T09:15:06.837Z" }, { url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920, upload-time = "2025-10-08T09:15:08.179Z" }, { url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013, upload-time = "2025-10-08T09:15:09.83Z" }, { url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096, upload-time = "2025-10-08T09:15:11.11Z" }, { url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708, upload-time = "2025-10-08T09:15:12.554Z" }, { url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119, upload-time = "2025-10-08T09:15:13.589Z" }, { url = "https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf", size = 81212, upload-time = "2025-10-08T09:15:14.552Z" }, { url = "https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7", size = 84315, upload-time = "2025-10-08T09:15:15.543Z" }, { url = "https://files.pythonhosted.org/packages/d3/68/93180dce57f684a61a88a45ed13047558ded2be46f03acb8dec6d7c513af/msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999", size = 412721, upload-time = "2025-10-08T09:15:16.567Z" }, { url = "https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e", size = 424657, upload-time = "2025-10-08T09:15:17.825Z" }, { url = "https://files.pythonhosted.org/packages/38/f8/4398c46863b093252fe67368b44edc6c13b17f4e6b0e4929dbf0bdb13f23/msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162", size = 402668, upload-time = "2025-10-08T09:15:19.003Z" }, { url = "https://files.pythonhosted.org/packages/28/ce/698c1eff75626e4124b4d78e21cca0b4cc90043afb80a507626ea354ab52/msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794", size = 419040, upload-time = "2025-10-08T09:15:20.183Z" }, { url = "https://files.pythonhosted.org/packages/67/32/f3cd1667028424fa7001d82e10ee35386eea1408b93d399b09fb0aa7875f/msgpack-1.1.2-cp313-cp313-win32.whl", hash = "sha256:a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c", size = 65037, upload-time = "2025-10-08T09:15:21.416Z" }, { url = "https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9", size = 72631, upload-time = "2025-10-08T09:15:22.431Z" }, { url = "https://files.pythonhosted.org/packages/e5/db/0314e4e2db56ebcf450f277904ffd84a7988b9e5da8d0d61ab2d057df2b6/msgpack-1.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84", size = 64118, upload-time = "2025-10-08T09:15:23.402Z" }, { url = "https://files.pythonhosted.org/packages/22/71/201105712d0a2ff07b7873ed3c220292fb2ea5120603c00c4b634bcdafb3/msgpack-1.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e23ce8d5f7aa6ea6d2a2b326b4ba46c985dbb204523759984430db7114f8aa00", size = 81127, upload-time = "2025-10-08T09:15:24.408Z" }, { url = "https://files.pythonhosted.org/packages/1b/9f/38ff9e57a2eade7bf9dfee5eae17f39fc0e998658050279cbb14d97d36d9/msgpack-1.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c15b7d74c939ebe620dd8e559384be806204d73b4f9356320632d783d1f7939", size = 84981, upload-time = "2025-10-08T09:15:25.812Z" }, { url = "https://files.pythonhosted.org/packages/8e/a9/3536e385167b88c2cc8f4424c49e28d49a6fc35206d4a8060f136e71f94c/msgpack-1.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99e2cb7b9031568a2a5c73aa077180f93dd2e95b4f8d3b8e14a73ae94a9e667e", size = 411885, upload-time = "2025-10-08T09:15:27.22Z" }, { url = "https://files.pythonhosted.org/packages/2f/40/dc34d1a8d5f1e51fc64640b62b191684da52ca469da9cd74e84936ffa4a6/msgpack-1.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:180759d89a057eab503cf62eeec0aa61c4ea1200dee709f3a8e9397dbb3b6931", size = 419658, upload-time = "2025-10-08T09:15:28.4Z" }, { url = "https://files.pythonhosted.org/packages/3b/ef/2b92e286366500a09a67e03496ee8b8ba00562797a52f3c117aa2b29514b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:04fb995247a6e83830b62f0b07bf36540c213f6eac8e851166d8d86d83cbd014", size = 403290, upload-time = "2025-10-08T09:15:29.764Z" }, { url = "https://files.pythonhosted.org/packages/78/90/e0ea7990abea5764e4655b8177aa7c63cdfa89945b6e7641055800f6c16b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8e22ab046fa7ede9e36eeb4cfad44d46450f37bb05d5ec482b02868f451c95e2", size = 415234, upload-time = "2025-10-08T09:15:31.022Z" }, { url = "https://files.pythonhosted.org/packages/72/4e/9390aed5db983a2310818cd7d3ec0aecad45e1f7007e0cda79c79507bb0d/msgpack-1.1.2-cp314-cp314-win32.whl", hash = "sha256:80a0ff7d4abf5fecb995fcf235d4064b9a9a8a40a3ab80999e6ac1e30b702717", size = 66391, upload-time = "2025-10-08T09:15:32.265Z" }, { url = "https://files.pythonhosted.org/packages/6e/f1/abd09c2ae91228c5f3998dbd7f41353def9eac64253de3c8105efa2082f7/msgpack-1.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:9ade919fac6a3e7260b7f64cea89df6bec59104987cbea34d34a2fa15d74310b", size = 73787, upload-time = "2025-10-08T09:15:33.219Z" }, { url = "https://files.pythonhosted.org/packages/6a/b0/9d9f667ab48b16ad4115c1935d94023b82b3198064cb84a123e97f7466c1/msgpack-1.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:59415c6076b1e30e563eb732e23b994a61c159cec44deaf584e5cc1dd662f2af", size = 66453, upload-time = "2025-10-08T09:15:34.225Z" }, { url = "https://files.pythonhosted.org/packages/16/67/93f80545eb1792b61a217fa7f06d5e5cb9e0055bed867f43e2b8e012e137/msgpack-1.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:897c478140877e5307760b0ea66e0932738879e7aa68144d9b78ea4c8302a84a", size = 85264, upload-time = "2025-10-08T09:15:35.61Z" }, { url = "https://files.pythonhosted.org/packages/87/1c/33c8a24959cf193966ef11a6f6a2995a65eb066bd681fd085afd519a57ce/msgpack-1.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a668204fa43e6d02f89dbe79a30b0d67238d9ec4c5bd8a940fc3a004a47b721b", size = 89076, upload-time = "2025-10-08T09:15:36.619Z" }, { url = "https://files.pythonhosted.org/packages/fc/6b/62e85ff7193663fbea5c0254ef32f0c77134b4059f8da89b958beb7696f3/msgpack-1.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5559d03930d3aa0f3aacb4c42c776af1a2ace2611871c84a75afe436695e6245", size = 435242, upload-time = "2025-10-08T09:15:37.647Z" }, { url = "https://files.pythonhosted.org/packages/c1/47/5c74ecb4cc277cf09f64e913947871682ffa82b3b93c8dad68083112f412/msgpack-1.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70c5a7a9fea7f036b716191c29047374c10721c389c21e9ffafad04df8c52c90", size = 432509, upload-time = "2025-10-08T09:15:38.794Z" }, { url = "https://files.pythonhosted.org/packages/24/a4/e98ccdb56dc4e98c929a3f150de1799831c0a800583cde9fa022fa90602d/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f2cb069d8b981abc72b41aea1c580ce92d57c673ec61af4c500153a626cb9e20", size = 415957, upload-time = "2025-10-08T09:15:40.238Z" }, { url = "https://files.pythonhosted.org/packages/da/28/6951f7fb67bc0a4e184a6b38ab71a92d9ba58080b27a77d3e2fb0be5998f/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d62ce1f483f355f61adb5433ebfd8868c5f078d1a52d042b0a998682b4fa8c27", size = 422910, upload-time = "2025-10-08T09:15:41.505Z" }, { url = "https://files.pythonhosted.org/packages/f0/03/42106dcded51f0a0b5284d3ce30a671e7bd3f7318d122b2ead66ad289fed/msgpack-1.1.2-cp314-cp314t-win32.whl", hash = "sha256:1d1418482b1ee984625d88aa9585db570180c286d942da463533b238b98b812b", size = 75197, upload-time = "2025-10-08T09:15:42.954Z" }, { url = "https://files.pythonhosted.org/packages/15/86/d0071e94987f8db59d4eeb386ddc64d0bb9b10820a8d82bcd3e53eeb2da6/msgpack-1.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5a46bf7e831d09470ad92dff02b8b1ac92175ca36b087f904a0519857c6be3ff", size = 85772, upload-time = "2025-10-08T09:15:43.954Z" }, { url = "https://files.pythonhosted.org/packages/81/f2/08ace4142eb281c12701fc3b93a10795e4d4dc7f753911d836675050f886/msgpack-1.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d99ef64f349d5ec3293688e91486c5fdb925ed03807f64d98d205d2713c60b46", size = 70868, upload-time = "2025-10-08T09:15:44.959Z" }, ] [[package]] name = "mypy" version = "1.19.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, { name = "mypy-extensions" }, { name = "pathspec" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" }, { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" }, { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" }, { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, ] [[package]] name = "mypy-extensions" version = "1.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] [[package]] name = "myst-nb" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-metadata" }, { name = "ipykernel" }, { name = "ipython" }, { name = "jupyter-cache" }, { name = "myst-parser" }, { name = "nbclient" }, { name = "nbformat" }, { name = "pyyaml" }, { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/21/83/a894bd8dea7a6e9f053502ee8413484dcbf75a219013d6a72e971c0fecfd/myst_nb-1.3.0.tar.gz", hash = "sha256:df3cd4680f51a5af673fd46b38b562be3559aef1475e906ed0f2e66e4587ce4b", size = 81963, upload-time = "2025-07-13T22:49:38.493Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/69/a6/03d410c114b8c4856579b3d294dafc27626a7690a552625eec42b16dfa41/myst_nb-1.3.0-py3-none-any.whl", hash = "sha256:1f36af3c19964960ec4e51ac30949b6ed6df220356ffa8d60dd410885e132d7d", size = 82396, upload-time = "2025-07-13T22:49:37.019Z" }, ] [[package]] name = "myst-parser" version = "5.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docutils" }, { name = "jinja2" }, { name = "markdown-it-py" }, { name = "mdit-py-plugins" }, { name = "pyyaml" }, { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/33/fa/7b45eef11b7971f0beb29d27b7bfe0d747d063aa29e170d9edd004733c8a/myst_parser-5.0.0.tar.gz", hash = "sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a", size = 98535, upload-time = "2026-01-15T09:08:18.036Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d3/ac/686789b9145413f1a61878c407210e41bfdb097976864e0913078b24098c/myst_parser-5.0.0-py3-none-any.whl", hash = "sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211", size = 84533, upload-time = "2026-01-15T09:08:16.788Z" }, ] [[package]] name = "narwhals" version = "2.16.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/fc/6f/713be67779028d482c6e0f2dde5bc430021b2578a4808c1c9f6d7ad48257/narwhals-2.16.0.tar.gz", hash = "sha256:155bb45132b370941ba0396d123cf9ed192bf25f39c4cea726f2da422ca4e145", size = 618268, upload-time = "2026-02-02T10:31:00.545Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/03/cc/7cb74758e6df95e0c4e1253f203b6dd7f348bf2f29cf89e9210a2416d535/narwhals-2.16.0-py3-none-any.whl", hash = "sha256:846f1fd7093ac69d63526e50732033e86c30ea0026a44d9b23991010c7d1485d", size = 443951, upload-time = "2026-02-02T10:30:58.635Z" }, ] [[package]] name = "nbclient" version = "0.10.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-client" }, { name = "jupyter-core" }, { name = "nbformat" }, { name = "traitlets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/91/1c1d5a4b9a9ebba2b4e32b8c852c2975c872aec1fe42ab5e516b2cecd193/nbclient-0.10.4.tar.gz", hash = "sha256:1e54091b16e6da39e297b0ece3e10f6f29f4ac4e8ee515d29f8a7099bd6553c9", size = 62554, upload-time = "2025-12-23T07:45:46.369Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl", hash = "sha256:9162df5a7373d70d606527300a95a975a47c137776cd942e52d9c7e29ff83440", size = 25465, upload-time = "2025-12-23T07:45:44.51Z" }, ] [[package]] name = "nbconvert" version = "7.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, { name = "bleach", extra = ["css"] }, { name = "defusedxml" }, { name = "jinja2" }, { name = "jupyter-core" }, { name = "jupyterlab-pygments" }, { name = "markupsafe" }, { name = "mistune" }, { name = "nbclient" }, { name = "nbformat" }, { name = "packaging" }, { name = "pandocfilters" }, { name = "pygments" }, { name = "traitlets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/38/47/81f886b699450d0569f7bc551df2b1673d18df7ff25cc0c21ca36ed8a5ff/nbconvert-7.17.0.tar.gz", hash = "sha256:1b2696f1b5be12309f6c7d707c24af604b87dfaf6d950794c7b07acab96dda78", size = 862855, upload-time = "2026-01-29T16:37:48.478Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl", hash = "sha256:4f99a63b337b9a23504347afdab24a11faa7d86b405e5c8f9881cd313336d518", size = 261510, upload-time = "2026-01-29T16:37:46.322Z" }, ] [[package]] name = "nbformat" version = "5.10.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "fastjsonschema" }, { name = "jsonschema" }, { name = "jupyter-core" }, { name = "traitlets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" }, ] [[package]] name = "ndindex" version = "1.10.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f5/92/4b9d2f4e0f3eabcfc7b02b48261f6e5ad36a3e2c1bbdcc4e3b7b6c768fa6/ndindex-1.10.1.tar.gz", hash = "sha256:0f6113c1f031248f8818cbee1aa92aa3c9472b7701debcce9fddebcd2f610f11", size = 271395, upload-time = "2025-11-19T20:40:08.899Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/8c/d9/c94ab6151c9fdd199c2b560f23e3759a9fb86a7a1275855e0b97291bf05a/ndindex-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e2ad917bcdf8dc5ba1e21f01054c991d26862d4d01c3c203a50e907096d558ac", size = 172128, upload-time = "2025-11-19T20:38:28.977Z" }, { url = "https://files.pythonhosted.org/packages/3a/34/880c4073750766e44492d51280d025f28e36475394ca3d741b0a4adad4b0/ndindex-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e851990a68937db5f485cd9f3e760c1fd47fa0f2a99f63a5e2cc880908faf3bb", size = 171423, upload-time = "2025-11-19T20:38:30.357Z" }, { url = "https://files.pythonhosted.org/packages/f0/1e/0342da55dabe4075efc2b2ab91a6a22ed3047c5bd511ef771a7a3f822c90/ndindex-1.10.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27385939f317b55773ea53f6bf9334810cf1d66206034c0a6a6f2a88f2001c3c", size = 519590, upload-time = "2025-11-19T20:38:32.464Z" }, { url = "https://files.pythonhosted.org/packages/fd/cb/7a02b6f29b15a16cd0002f4591d14493eff8e9236f7ca4c02ee4d4bcefbd/ndindex-1.10.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fdf3ca16efcdfbb8800aa88fbab1bc6528e6a0504bcb9cf7af4cb9d50e9f5d9", size = 516676, upload-time = "2025-11-19T20:38:34.276Z" }, { url = "https://files.pythonhosted.org/packages/67/d5/38da808f968a54b0fead2d7e15ca011d3df93c96a07f4914e8ef3974506e/ndindex-1.10.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3307817bdc92846b18f309fae3582856f567dd6e0742fb0b41ac68682bfc4e2a", size = 1491141, upload-time = "2025-11-19T20:38:35.785Z" }, { url = "https://files.pythonhosted.org/packages/bc/1f/8c66ef982a01ae4cbdabba679a2bc711f262cedf23bfb9682293146f8a98/ndindex-1.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae73cd2d66b09ef2f2a7d7f93bad396d6abf168d1ee825e403c6c5fb8ae1341c", size = 1543876, upload-time = "2025-11-19T20:38:37.456Z" }, { url = "https://files.pythonhosted.org/packages/05/a1/7c7e3a3c6e81b4284fd0d53cbaec51d9e5b90df26dd78e9bde06cb307217/ndindex-1.10.1-cp311-cp311-win32.whl", hash = "sha256:890bb92f0a779e6f16bdbcc8bd2e06c32bcc0239e5893ba246114eb924aecaaa", size = 149149, upload-time = "2025-11-19T20:38:38.911Z" }, { url = "https://files.pythonhosted.org/packages/3b/38/99e1fb0effdef74b883be615ea0053ebcea28a53fd8b896263f4e99b0113/ndindex-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:1827a40301405b44ad709e388c5b48cf35cd90a67f77e63f0f17d87f6000fa81", size = 157246, upload-time = "2025-11-19T20:38:40.197Z" }, { url = "https://files.pythonhosted.org/packages/65/90/774ddd08b2a1b41faa56da111f0fbfeb4f17ee537214c938ef41d61af949/ndindex-1.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:87f83e8c35a7f49a68cd3a3054c406e6c22f8c1315f3905f7a778c657669187e", size = 177348, upload-time = "2025-11-19T20:38:41.768Z" }, { url = "https://files.pythonhosted.org/packages/ed/ee/a423e857f5b45da3adc8ddbcfbfd4a0e9a047edce3915d3e3d6e189b6bd9/ndindex-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cf9e05986b2eb8c5993bce0f911d6cedd15bda30b5e35dd354b1ad1f4cc3599d", size = 176561, upload-time = "2025-11-19T20:38:43.06Z" }, { url = "https://files.pythonhosted.org/packages/1f/40/139b6b050ba2b2a0bb40e0381a352b1eb6551302dcb8f86fb4c97dd34e92/ndindex-1.10.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:046c1e88d46b2bd2fd3483e06d27b4e85132b55bc693f2fca2db0bb56eea1e78", size = 542901, upload-time = "2025-11-19T20:38:44.43Z" }, { url = "https://files.pythonhosted.org/packages/27/ae/defd665dbbeb2fffa077491365ed160acaec49274ce8d4b979f55db71f18/ndindex-1.10.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03cf1e6cdac876bd8fc92d3b65bb223496b1581d10eab3ba113f7c195121a959", size = 546875, upload-time = "2025-11-19T20:38:45.938Z" }, { url = "https://files.pythonhosted.org/packages/59/43/6d54d48e8eaee25cdab70d3e4c4f579ddb0255e4f1660040d5ad55e029c6/ndindex-1.10.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:752e78a5e87911ded117c57a7246596f26c9c6da066de3c2b533b3db694949bb", size = 1510036, upload-time = "2025-11-19T20:38:47.444Z" }, { url = "https://files.pythonhosted.org/packages/09/61/e28ba3b98eacd18193176526526b34d7d70d2a6f9fd2b4d8309ab5692678/ndindex-1.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9dd58d91220b1c1fe516324bfcf4114566c98e84b1cbbe416abe345c75bd557", size = 1571849, upload-time = "2025-11-19T20:38:48.951Z" }, { url = "https://files.pythonhosted.org/packages/8f/63/83fff78a3712cb9f478dd84a19ec389acf6f8c7b01dc347a65ae74e6123d/ndindex-1.10.1-cp312-cp312-win32.whl", hash = "sha256:3b0d9ce2c8488444499ab6d40e92e09867bf4413f5cf04c01635de923f44aa67", size = 149792, upload-time = "2025-11-19T20:38:50.959Z" }, { url = "https://files.pythonhosted.org/packages/52/fd/a5e3c8c043d0dddea6cd4567bfaea568f022ac197301882b3d85d9c1e9b3/ndindex-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:5c026dbbf2455d97ce6456d8a50b349aee8fefa11027d020638c89e9be2c9c4c", size = 158164, upload-time = "2025-11-19T20:38:52.242Z" }, { url = "https://files.pythonhosted.org/packages/60/ea/03676266cb38cc671679a9d258cc59bfc58c69726db87b0d6eeafb308895/ndindex-1.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:157b5c34a1b779f5d27b790d9bd7e7b156d284e76be83c591a3ba003984f4956", size = 176323, upload-time = "2025-11-19T20:38:53.528Z" }, { url = "https://files.pythonhosted.org/packages/89/f4/2d350439031b108b0bb8897cad315390c5ad88c14d87419a54c2ffa95c80/ndindex-1.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f99b3e89220da3244d03c9c5473669c7107d361c129fd9b064622744dee1ce15", size = 175584, upload-time = "2025-11-19T20:38:57.968Z" }, { url = "https://files.pythonhosted.org/packages/77/34/a51b7c6f7159718a6a0a694fc1058b94d793c416d9a4fd649f1924cce5f8/ndindex-1.10.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6928e47fb008903f2e41309b7ff1e59b16abbcd59e2e945454571c28b2433c9e", size = 524127, upload-time = "2025-11-19T20:38:59.412Z" }, { url = "https://files.pythonhosted.org/packages/21/91/d8f19f0b8fc9c5585b50fda44c05415da0bdc5fa9c9c69011015dac27880/ndindex-1.10.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e69a2cb1ac7be955c3c77f1def83f410775a81525c9ce2d4c0a3f2a61589ed47", size = 528213, upload-time = "2025-11-19T20:39:00.882Z" }, { url = "https://files.pythonhosted.org/packages/2c/a9/77d9d037e871a3faa8579b354ca2dd09cc5bbf3e085d9e3c67f786d55ee3/ndindex-1.10.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cb76e0f3f235d8b1c768b17e771de48775d281713795c3aa045e8114ad61bdda", size = 1492172, upload-time = "2025-11-19T20:39:02.387Z" }, { url = "https://files.pythonhosted.org/packages/ac/29/ad13676fc9312e0aa1a80a7c04bcb0b502b877ed4956136117ad663eced0/ndindex-1.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7da34a78410c14341d5fff73be5ce924bd36500bf7f640fc59b8607d3a0df95e", size = 1552614, upload-time = "2025-11-19T20:39:04.232Z" }, { url = "https://files.pythonhosted.org/packages/63/34/e6e6fd81423810c07ae623c4d36e099f42a812994977e8e3bfa182c02472/ndindex-1.10.1-cp313-cp313-win32.whl", hash = "sha256:9599fcb7411ffe601c367f0a5d4bc0ed588e3e7d9dc7604bdb32c8f669456b9e", size = 149330, upload-time = "2025-11-19T20:39:05.727Z" }, { url = "https://files.pythonhosted.org/packages/4d/d3/830a20626e2ec0e31a926be90e67068a029930f99e6cfebf2f9768e7b7b1/ndindex-1.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:ef3ef22390a892d16286505083ee5b326317b21c255a0c7f744b1290a0b964a6", size = 157309, upload-time = "2025-11-19T20:39:07.394Z" }, { url = "https://files.pythonhosted.org/packages/4a/73/3bdeecd1f6ec0ad81478a53d96da4ba9be74ed297c95f2b4fbe2b80843e1/ndindex-1.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:72af787dcee3661f36fff9d144d989aacefe32e2c8b51ceef9babd46afb93a18", size = 181022, upload-time = "2025-11-19T20:39:10.487Z" }, { url = "https://files.pythonhosted.org/packages/b9/b1/0d97ba134b5aa71b5ed638fac193a7ec4d987e091e2f4e4162ebdaacbda1/ndindex-1.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa60637dfae1ee3fc057e420a52cc4ace38cf2c0d1a0451af2a3cba84d281842", size = 181289, upload-time = "2025-11-19T20:39:11.793Z" }, { url = "https://files.pythonhosted.org/packages/e2/d7/1df02df24880ce3f3c8137b6f3ca5a901a58d9079dcfd8c818419277ff87/ndindex-1.10.1-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0ebdba2fade3f6916fe21fd49e2a0935af4f58c56100a60f3f2eb26e20baee7", size = 632517, upload-time = "2025-11-19T20:39:13.259Z" }, { url = "https://files.pythonhosted.org/packages/34/96/b509c2b14e9b10710fe6ab6ba8bda1ee6ce36ab16397ff2f5bbb33bbbba3/ndindex-1.10.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:346a4bf09f5771548665c8206e81daadb6b9925d409746e709894bdd98adc701", size = 616179, upload-time = "2025-11-19T20:39:14.757Z" }, { url = "https://files.pythonhosted.org/packages/38/e3/f89d60cf351c33a484bf1a4546a5dee6f4e7a6a973613ffa12bd316b14ad/ndindex-1.10.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:23d35696f802548143b5cc199bf2f171efb0061aa7934959251dd3bae56d038c", size = 1588373, upload-time = "2025-11-19T20:39:16.62Z" }, { url = "https://files.pythonhosted.org/packages/ee/19/002fc1e6a4abeef8d92e9aa2e43aea4d462f6b170090f7752ea8887f4897/ndindex-1.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a91e1a0398120233d5c3b23ccb2d4b78e970d66136f1a7221fa9a53873c3d5c5", size = 1636436, upload-time = "2025-11-19T20:39:18.266Z" }, { url = "https://files.pythonhosted.org/packages/5f/8f/28b1ad78c787ac8fafd6e26419a80366617784b1779e3857fa687492f6bc/ndindex-1.10.1-cp313-cp313t-win32.whl", hash = "sha256:78bfe25941d2dac406391ddd9baf0b0fce163807b98ecc2c47a3030ee8466319", size = 158780, upload-time = "2025-11-19T20:39:20.454Z" }, { url = "https://files.pythonhosted.org/packages/d0/56/b81060607a19865bb8be8d705b1b3e8aefb8747c0fbd383e38b4cae4bd71/ndindex-1.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:08bfdc1f7a0b408d15b3ce61d141ebbebdb47a25341967e425e104c5bd512a5c", size = 167485, upload-time = "2025-11-19T20:39:21.733Z" }, { url = "https://files.pythonhosted.org/packages/da/9b/aac1131e9f3a5635ba7b0312c3bfa610511ab4108f85c0d914a32887aa00/ndindex-1.10.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9b5297f207ebc068c7cdf9e3cd7b95aa5c9ec04295d0a7e56b529f66787d4685", size = 176478, upload-time = "2025-11-19T20:39:23.747Z" }, { url = "https://files.pythonhosted.org/packages/1a/05/a0d8ca0432c84550bc17af6d6479a803936895b8b8403a1216c5a55475fb/ndindex-1.10.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c5e9762452b163e33cfb6e821f86e45ba0b53bdfcd23ab5d57b48a8f566898cb", size = 175480, upload-time = "2025-11-19T20:39:25.365Z" }, { url = "https://files.pythonhosted.org/packages/09/4a/028ab78a9f29fd2a7e86a90337cde4658eaa77b425c63045d83a1d2e4f26/ndindex-1.10.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf80241b40adffdc3276b2c9fb63a96c6c98b4a9d941892738de8add65083962", size = 528125, upload-time = "2025-11-19T20:39:26.798Z" }, { url = "https://files.pythonhosted.org/packages/00/a9/bd823b345fb06c83ade6ef1c1933521d4357cd04490e684d4fa30126926c/ndindex-1.10.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf5855881884b8467dfcf45764ccf2e4279075be14b155b89c96994bb08d2e6f", size = 527328, upload-time = "2025-11-19T20:39:28.292Z" }, { url = "https://files.pythonhosted.org/packages/91/4f/40b9c15588cbf9dde43c4fb88a31dd1f636a913fa29649f18f8e3ebca36a/ndindex-1.10.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e81a9bd36fe054b6c9fcc53d26bc9a28cf15d1ab52a0f5b854f894116f3a54e1", size = 1497508, upload-time = "2025-11-19T20:39:30.735Z" }, { url = "https://files.pythonhosted.org/packages/24/8f/b8048f7837d2e9dff0af507b398307fa84a2aa9ea3db71b4aa800b21da4a/ndindex-1.10.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:588e8875d836a93b3cd9af482c8074bb02288ae1aff92cf277e1f02d9ae0f992", size = 1552625, upload-time = "2025-11-19T20:39:32.404Z" }, { url = "https://files.pythonhosted.org/packages/20/aa/0ecb53c7e690a44769f2f92a843723ccb1d0ce080d93ba1ea811304cca12/ndindex-1.10.1-cp314-cp314-win32.whl", hash = "sha256:28741daca5926adff402247cd406f453ed5bb6042e82d6855938f805190e5ce9", size = 151237, upload-time = "2025-11-19T20:39:34.847Z" }, { url = "https://files.pythonhosted.org/packages/8c/4e/197982fa8b4e6e6b9d15c38505c41076d1c552921f09f4d35acbbbbc0b70/ndindex-1.10.1-cp314-cp314-win_amd64.whl", hash = "sha256:59a3222befc0f7cdc85fb9b90a567ae890f70a864bdeb660517e9ebcb36bf1bc", size = 158925, upload-time = "2025-11-19T20:39:37.149Z" }, { url = "https://files.pythonhosted.org/packages/24/ad/116b6154046a69fc04e2d4490905801d3839a3f21290c0b4d49b1044e251/ndindex-1.10.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:967b87b88dadb62555ec1039695c347254eccb8ca3d124c0e5dbe084c525fa93", size = 181724, upload-time = "2025-11-19T20:39:38.635Z" }, { url = "https://files.pythonhosted.org/packages/c4/00/3ce4351366c890bcc87a5e9f1f90102547962eef356ac7c799bfdd0dddce/ndindex-1.10.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c67dde588c0fb89d872931a4ed5f9b4d21c1c70a3d92fdf0812a1de154239816", size = 181653, upload-time = "2025-11-19T20:39:40.048Z" }, { url = "https://files.pythonhosted.org/packages/4d/05/a6fda696a2f02a3f8dd2ee9d816cb2edff6423bf0110a4876cc3b1259732/ndindex-1.10.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c65ca639a7abf72d79f22424f4abd18dece1f289a2b7b028a0ca455edd2168d4", size = 630898, upload-time = "2025-11-19T20:39:41.495Z" }, { url = "https://files.pythonhosted.org/packages/73/78/eb2e5d067d4c054451e33eaece74cbdcb58236dc60516e73d783dae34c7e/ndindex-1.10.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5c3634a8df43e7928122225a3d64d850c8957bd1edf2e403907deacb478af27b", size = 614419, upload-time = "2025-11-19T20:39:43.254Z" }, { url = "https://files.pythonhosted.org/packages/78/51/261bfb49eb7920c2a7314cacba5821930a529911dce48c7c6cd786096a5a/ndindex-1.10.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9d581f931e61f182478f18bdf5edd3955899df5da4892ed0d5de547a4cfd5b6f", size = 1587517, upload-time = "2025-11-19T20:39:44.809Z" }, { url = "https://files.pythonhosted.org/packages/ec/37/084a332ecdf8b0049151bd78001a7baf2daf7f500d043beb8a1f95d0f4e3/ndindex-1.10.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:78ce45106ebf67aeba99714818c721d8fd5fb9534daebd2565665a2d64b50fc9", size = 1635372, upload-time = "2025-11-19T20:39:47.231Z" }, { url = "https://files.pythonhosted.org/packages/28/f4/716580fbb03018ab1daa86ed12c1925c67e79689db5fee82393e840758a2/ndindex-1.10.1-cp314-cp314t-win32.whl", hash = "sha256:fe5341e24dc992b09c258456ac90a09a6d25efdc2cb86dcc91d32c8891e1df9a", size = 162186, upload-time = "2025-11-19T20:39:48.81Z" }, { url = "https://files.pythonhosted.org/packages/4d/20/28f669c09a470e7f523b0cc10b94336664d9648594015e3f2a1ec29047b1/ndindex-1.10.1-cp314-cp314t-win_amd64.whl", hash = "sha256:37f87f0e7690ae0324334740e0661d6297f2e62c9bf925127d249fb7eddd0ad8", size = 171077, upload-time = "2025-11-19T20:39:50.108Z" }, ] [[package]] name = "nest-asyncio" version = "1.6.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, ] [[package]] name = "netcdf4" version = "1.7.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "cftime" }, { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/34/b6/0370bb3af66a12098da06dc5843f3b349b7c83ccbdf7306e7afa6248b533/netcdf4-1.7.4.tar.gz", hash = "sha256:cdbfdc92d6f4d7192ca8506c9b3d4c1d9892969ff28d8e8e1fc97ca08bf12164", size = 838352, upload-time = "2026-01-05T02:27:38.593Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/38/de/38ed7e1956943d28e8ea74161e97c3a00fb98d6d08943b4fd21bae32c240/netcdf4-1.7.4-cp311-abi3-macosx_13_0_x86_64.whl", hash = "sha256:dec70e809cc65b04ebe95113ee9c85ba46a51c3a37c058d2b2b0cadc4d3052d8", size = 23427499, upload-time = "2026-01-05T02:27:06.568Z" }, { url = "https://files.pythonhosted.org/packages/e5/70/2f73c133b71709c412bc81d8b721e28dc6237ba9d7dad861b7bfbb70408a/netcdf4-1.7.4-cp311-abi3-macosx_14_0_arm64.whl", hash = "sha256:75cf59100f0775bc4d6b9d4aca7cbabd12e2b8cf3b9a4fb16d810b92743a315a", size = 22847667, upload-time = "2026-01-05T02:27:09.421Z" }, { url = "https://files.pythonhosted.org/packages/77/ce/43a3c0c41a6e2e940d87feea79d29aa88302211ac122604838f8a5a48de6/netcdf4-1.7.4-cp311-abi3-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ddfc7e9d261125c74708119440c85ea288b5fee41db676d2ba1ce9be11f96932", size = 10274769, upload-time = "2026-01-05T21:31:19.243Z" }, { url = "https://files.pythonhosted.org/packages/7b/7a/a8d32501bb95ecff342004a674720164f95ad616f269450b3bc13dc88ae3/netcdf4-1.7.4-cp311-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a72c9f58767779ec14cb7451c3b56bdd8fdc027a792fac2062b14e090c5617f3", size = 10123122, upload-time = "2026-01-05T21:31:22.773Z" }, { url = "https://files.pythonhosted.org/packages/18/68/e89b4fa9242e59326c849c39ce0f49eb68499603c639405a8449900a4f15/netcdf4-1.7.4-cp311-abi3-win_amd64.whl", hash = "sha256:9476e1f23161ae5159cd1548c50c8a37922e77d76583e247133f256ef7b825fc", size = 21299637, upload-time = "2026-01-05T02:27:11.856Z" }, { url = "https://files.pythonhosted.org/packages/6c/fc/edd41a3607241027aa4533e7f18e0cd647e74dde10a63274c65350f59967/netcdf4-1.7.4-cp311-abi3-win_arm64.whl", hash = "sha256:876ad9d58f09c98741c066c726164c45a098a58fb90e5fac9e74de4bb8a793fd", size = 2386377, upload-time = "2026-01-05T02:27:13.808Z" }, { url = "https://files.pythonhosted.org/packages/f1/3e/1e83534ba68459bc5ae39df46fa71003984df58aabf31f7dcd6e22ecddb0/netcdf4-1.7.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56688c03444fffe0d0c7512cb45245e650389cd841c955b30e4552fa681c4cd9", size = 10519821, upload-time = "2026-01-05T02:27:15.413Z" }, { url = "https://files.pythonhosted.org/packages/c0/8c/a15d6fe97f81d6d5202b17838a9a298b5955b3e9971e20609195112829b5/netcdf4-1.7.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ecf471ba8a6ddb2200121949bedfa0095db228822f38227d5da680694a38358", size = 10371133, upload-time = "2026-01-05T02:27:17.224Z" }, { url = "https://files.pythonhosted.org/packages/d8/2b/684b15dd4791f8be295b2f6fa97377bbc07a768478a63b7d3c4951712e36/netcdf4-1.7.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a5841de0735e8e4875b367c668e81d334287858d64dd9f3e3e2261e808c84922", size = 10395635, upload-time = "2026-01-05T02:27:19.655Z" }, { url = "https://files.pythonhosted.org/packages/37/dc/44d21524cf1b1c64254f92e22395a7a10f70c18f3a13a18ac9db258760f7/netcdf4-1.7.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86fac03a8c5b250d57866e7d98918a64742e4b0de1681c5c86bac5726bab8aee", size = 10237725, upload-time = "2026-01-05T02:27:22.298Z" }, { url = "https://files.pythonhosted.org/packages/d4/9d/c3ddf54296ad8f18f02f77f23452bdb0971aece1b87e84bab9d734bf72cc/netcdf4-1.7.4-cp314-cp314t-macosx_13_0_x86_64.whl", hash = "sha256:ad083d260301b5add74b1669c75ab0df03bdf986decfcc092cb45eec2615b5f1", size = 23515258, upload-time = "2026-01-05T02:27:24.837Z" }, { url = "https://files.pythonhosted.org/packages/dd/44/bc0346e995d436d03fab682b7fbd2a9adcf0db6a05790b8f24853bf08170/netcdf4-1.7.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:7f22014092cc9da3f056b0368e2e38c42afd5725c87ad4843eb2f467e16dd4f6", size = 22910171, upload-time = "2026-01-05T02:27:27.166Z" }, { url = "https://files.pythonhosted.org/packages/30/6b/f9bc3f43c55e2dac72ee9f98d77860789bdd5d50c29adf164a6bdb303078/netcdf4-1.7.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:224a15434c165a5e0225e5831f591edf62533044b1ce62fdfee815195bbd077d", size = 10567579, upload-time = "2026-01-05T02:27:29.382Z" }, { url = "https://files.pythonhosted.org/packages/6d/d5/e7685c66b7f011c73cd746127f986358a26c642a4e4a1aa5ab51481b6586/netcdf4-1.7.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31a2318305de6831a18df25ad0df9f03b6d68666af0356d4f6057d66c02ffeb6", size = 10255032, upload-time = "2026-01-05T02:27:31.744Z" }, { url = "https://files.pythonhosted.org/packages/a6/14/7506738bb6c8bc373b01e5af8f3b727f83f4f496c6b108490ea2609dc2cf/netcdf4-1.7.4-cp314-cp314t-win_amd64.whl", hash = "sha256:6c4a0aa9446c3a616ef3be015b629dc6173643f8b09546de26a4e40e272cd1ed", size = 22289653, upload-time = "2026-01-05T02:27:34.294Z" }, { url = "https://files.pythonhosted.org/packages/af/2e/39d5e9179c543f2e6e149a65908f83afd9b6d64379a90789b323111761db/netcdf4-1.7.4-cp314-cp314t-win_arm64.whl", hash = "sha256:034220887d48da032cb2db5958f69759dbb04eb33e279ec6390571d4aea734fe", size = 2531682, upload-time = "2026-01-05T02:27:37.062Z" }, ] [[package]] name = "networkx" version = "3.6.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, ] [[package]] name = "nodeenv" version = "1.10.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] [[package]] name = "notebook" version = "7.5.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-server" }, { name = "jupyterlab" }, { name = "jupyterlab-server" }, { name = "notebook-shim" }, { name = "tornado" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b8/cb/cc7f4df5cee315dd126a47eb60890690a0438d5e0dd40c32d60ce16de377/notebook-7.5.3.tar.gz", hash = "sha256:393ceb269cf9fdb02a3be607a57d7bd5c2c14604f1818a17dbeb38e04f98cbfa", size = 14073140, upload-time = "2026-01-26T07:28:36.605Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/96/98/9286e7f35e5584ebb79f997f2fb0cb66745c86f6c5fccf15ba32aac5e908/notebook-7.5.3-py3-none-any.whl", hash = "sha256:c997bfa1a2a9eb58c9bbb7e77d50428befb1033dd6f02c482922e96851d67354", size = 14481744, upload-time = "2026-01-26T07:28:31.867Z" }, ] [[package]] name = "notebook-shim" version = "0.2.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-server" }, ] sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167, upload-time = "2024-02-14T23:35:18.353Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307, upload-time = "2024-02-14T23:35:16.286Z" }, ] [[package]] name = "numba" version = "0.63.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "llvmlite" }, { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dc/60/0145d479b2209bd8fdae5f44201eceb8ce5a23e0ed54c71f57db24618665/numba-0.63.1.tar.gz", hash = "sha256:b320aa675d0e3b17b40364935ea52a7b1c670c9037c39cf92c49502a75902f4b", size = 2761666, upload-time = "2025-12-10T02:57:39.002Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/70/90/5f8614c165d2e256fbc6c57028519db6f32e4982475a372bbe550ea0454c/numba-0.63.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b33db00f18ccc790ee9911ce03fcdfe9d5124637d1ecc266f5ae0df06e02fec3", size = 2680501, upload-time = "2025-12-10T02:57:09.797Z" }, { url = "https://files.pythonhosted.org/packages/dc/9d/d0afc4cf915edd8eadd9b2ab5b696242886ee4f97720d9322650d66a88c6/numba-0.63.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d31ea186a78a7c0f6b1b2a3fe68057fdb291b045c52d86232b5383b6cf4fc25", size = 3744945, upload-time = "2025-12-10T02:57:11.697Z" }, { url = "https://files.pythonhosted.org/packages/05/a9/d82f38f2ab73f3be6f838a826b545b80339762ee8969c16a8bf1d39395a8/numba-0.63.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed3bb2fbdb651d6aac394388130a7001aab6f4541837123a4b4ab8b02716530c", size = 3450827, upload-time = "2025-12-10T02:57:13.709Z" }, { url = "https://files.pythonhosted.org/packages/18/3f/a9b106e93c5bd7434e65f044bae0d204e20aa7f7f85d72ceb872c7c04216/numba-0.63.1-cp311-cp311-win_amd64.whl", hash = "sha256:1ecbff7688f044b1601be70113e2fb1835367ee0b28ffa8f3adf3a05418c5c87", size = 2747262, upload-time = "2025-12-10T02:57:15.664Z" }, { url = "https://files.pythonhosted.org/packages/14/9c/c0974cd3d00ff70d30e8ff90522ba5fbb2bcee168a867d2321d8d0457676/numba-0.63.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2819cd52afa5d8d04e057bdfd54367575105f8829350d8fb5e4066fb7591cc71", size = 2680981, upload-time = "2025-12-10T02:57:17.579Z" }, { url = "https://files.pythonhosted.org/packages/cb/70/ea2bc45205f206b7a24ee68a159f5097c9ca7e6466806e7c213587e0c2b1/numba-0.63.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5cfd45dbd3d409e713b1ccfdc2ee72ca82006860254429f4ef01867fdba5845f", size = 3801656, upload-time = "2025-12-10T02:57:19.106Z" }, { url = "https://files.pythonhosted.org/packages/0d/82/4f4ba4fd0f99825cbf3cdefd682ca3678be1702b63362011de6e5f71f831/numba-0.63.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69a599df6976c03b7ecf15d05302696f79f7e6d10d620367407517943355bcb0", size = 3501857, upload-time = "2025-12-10T02:57:20.721Z" }, { url = "https://files.pythonhosted.org/packages/af/fd/6540456efa90b5f6604a86ff50dabefb187e43557e9081adcad3be44f048/numba-0.63.1-cp312-cp312-win_amd64.whl", hash = "sha256:bbad8c63e4fc7eb3cdb2c2da52178e180419f7969f9a685f283b313a70b92af3", size = 2750282, upload-time = "2025-12-10T02:57:22.474Z" }, { url = "https://files.pythonhosted.org/packages/57/f7/e19e6eff445bec52dde5bed1ebb162925a8e6f988164f1ae4b3475a73680/numba-0.63.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:0bd4fd820ef7442dcc07da184c3f54bb41d2bdb7b35bacf3448e73d081f730dc", size = 2680954, upload-time = "2025-12-10T02:57:24.145Z" }, { url = "https://files.pythonhosted.org/packages/e9/6c/1e222edba1e20e6b113912caa9b1665b5809433cbcb042dfd133c6f1fd38/numba-0.63.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:53de693abe4be3bd4dee38e1c55f01c55ff644a6a3696a3670589e6e4c39cde2", size = 3809736, upload-time = "2025-12-10T02:57:25.836Z" }, { url = "https://files.pythonhosted.org/packages/76/0a/590bad11a8b3feeac30a24d01198d46bdb76ad15c70d3a530691ce3cae58/numba-0.63.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:81227821a72a763c3d4ac290abbb4371d855b59fdf85d5af22a47c0e86bf8c7e", size = 3508854, upload-time = "2025-12-10T02:57:27.438Z" }, { url = "https://files.pythonhosted.org/packages/4e/f5/3800384a24eed1e4d524669cdbc0b9b8a628800bb1e90d7bd676e5f22581/numba-0.63.1-cp313-cp313-win_amd64.whl", hash = "sha256:eb227b07c2ac37b09432a9bda5142047a2d1055646e089d4a240a2643e508102", size = 2750228, upload-time = "2025-12-10T02:57:30.36Z" }, { url = "https://files.pythonhosted.org/packages/36/2f/53be2aa8a55ee2608ebe1231789cbb217f6ece7f5e1c685d2f0752e95a5b/numba-0.63.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f180883e5508940cc83de8a8bea37fc6dd20fbe4e5558d4659b8b9bef5ff4731", size = 2681153, upload-time = "2025-12-10T02:57:32.016Z" }, { url = "https://files.pythonhosted.org/packages/13/91/53e59c86759a0648282368d42ba732c29524a745fd555ed1fb1df83febbe/numba-0.63.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0938764afa82a47c0e895637a6c55547a42c9e1d35cac42285b1fa60a8b02bb", size = 3778718, upload-time = "2025-12-10T02:57:33.764Z" }, { url = "https://files.pythonhosted.org/packages/6c/0c/2be19eba50b0b7636f6d1f69dfb2825530537708a234ba1ff34afc640138/numba-0.63.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f90a929fa5094e062d4e0368ede1f4497d5e40f800e80aa5222c4734236a2894", size = 3478712, upload-time = "2025-12-10T02:57:35.518Z" }, { url = "https://files.pythonhosted.org/packages/0d/5f/4d0c9e756732577a52211f31da13a3d943d185f7fb90723f56d79c696caa/numba-0.63.1-cp314-cp314-win_amd64.whl", hash = "sha256:8d6d5ce85f572ed4e1a135dbb8c0114538f9dd0e3657eeb0bb64ab204cbe2a8f", size = 2752161, upload-time = "2025-12-10T02:57:37.12Z" }, ] [[package]] name = "numbagg" version = "0.9.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numba" }, { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/31/5c/0b84cf3e2dc71fc9061bdf9db9c8d0e8ea0eb079b8c98f526831d0d2c1c1/numbagg-0.9.4.tar.gz", hash = "sha256:4aaddc7ca306c38e991f8b812d129a81f31cb77a066282d1e0b18cea5c126250", size = 141712, upload-time = "2025-12-15T06:07:48.575Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ae/d3/ff8f1b9968aa4dcd1da1880322ed492314cc920998182e549b586c895a17/numbagg-0.9.4-py3-none-any.whl", hash = "sha256:4f48276333fae856810873122d77aa75fb2d33f9ffe7888c1cfc86209117408f", size = 77532, upload-time = "2025-12-15T06:07:47.099Z" }, ] [[package]] name = "numcodecs" version = "0.16.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/44/bd/8a391e7c356366224734efd24da929cc4796fff468bfb179fe1af6548535/numcodecs-0.16.5.tar.gz", hash = "sha256:0d0fb60852f84c0bd9543cc4d2ab9eefd37fc8efcc410acd4777e62a1d300318", size = 6276387, upload-time = "2025-11-21T02:49:48.986Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/af/85/1ac101a40ead81eaa1c7dc49a8827a30e2e436211b43ebdc63c590eb1347/numcodecs-0.16.5-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:78382dcea50622f2ef1e6e7a71dbe7f861d8fe376b27b7c297c26907304fef1e", size = 1621795, upload-time = "2025-11-21T02:49:17.418Z" }, { url = "https://files.pythonhosted.org/packages/0e/cc/0d97ef55dda48cb0f93d7b92d761208e7a99bd2eea6b0e859426e6a99a21/numcodecs-0.16.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2d04a19cb57a3c519b4127ac377cca6471aee1990d7c18f5b1e3a4fe1306689", size = 1153030, upload-time = "2025-11-21T02:49:19.089Z" }, { url = "https://files.pythonhosted.org/packages/5e/41/e120ee1b390730ac5987cde2afd82e2b8442cec315ab40b94b0373e93e73/numcodecs-0.16.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c043af648eb280cd61785c99c22ff5c3c3460f906eb51a8511327c4f5111b283", size = 8510503, upload-time = "2025-11-21T02:49:20.324Z" }, { url = "https://files.pythonhosted.org/packages/54/4b/195ac84cc8f6077b4f0f421e8daee21b7f1bd88cb7716414234379fe68ec/numcodecs-0.16.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c398919ef2eb0e56b8e97456f622640bfd3deed06de3acc976989cbcb22628a3", size = 9123428, upload-time = "2025-11-21T02:49:22.328Z" }, { url = "https://files.pythonhosted.org/packages/0f/5b/af02c417954f46e5c7bd5163ac251f535877d909fce54861c99ae197f6f6/numcodecs-0.16.5-cp311-cp311-win_amd64.whl", hash = "sha256:3820860ed302d4d84a1c66e70981ff959d5eb712555be4e7d8ced49888594773", size = 801542, upload-time = "2025-11-21T02:49:24.265Z" }, { url = "https://files.pythonhosted.org/packages/75/cc/55420f3641a67f78392dc0bc5d02cb9eb0a9dcebf2848d1ac77253ca61fa/numcodecs-0.16.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:24e675dc8d1550cd976a99479b87d872cb142632c75cc402fea04c08c4898523", size = 1656287, upload-time = "2025-11-21T02:49:25.755Z" }, { url = "https://files.pythonhosted.org/packages/f5/6c/86644987505dcb90ba6d627d6989c27bafb0699f9fd00187e06d05ea8594/numcodecs-0.16.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:94ddfa4341d1a3ab99989d13b01b5134abb687d3dab2ead54b450aefe4ad5bd6", size = 1148899, upload-time = "2025-11-21T02:49:26.87Z" }, { url = "https://files.pythonhosted.org/packages/97/1e/98aaddf272552d9fef1f0296a9939d1487914a239e98678f6b20f8b0a5c8/numcodecs-0.16.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b554ab9ecf69de7ca2b6b5e8bc696bd9747559cb4dd5127bd08d7a28bec59c3a", size = 8534814, upload-time = "2025-11-21T02:49:28.547Z" }, { url = "https://files.pythonhosted.org/packages/fb/53/78c98ef5c8b2b784453487f3e4d6c017b20747c58b470393e230c78d18e8/numcodecs-0.16.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad1a379a45bd3491deab8ae6548313946744f868c21d5340116977ea3be5b1d6", size = 9173471, upload-time = "2025-11-21T02:49:30.444Z" }, { url = "https://files.pythonhosted.org/packages/1c/20/2fdec87fc7f8cec950d2b0bea603c12dc9f05b4966dc5924ba5a36a61bf6/numcodecs-0.16.5-cp312-cp312-win_amd64.whl", hash = "sha256:845a9857886ffe4a3172ba1c537ae5bcc01e65068c31cf1fce1a844bd1da050f", size = 801412, upload-time = "2025-11-21T02:49:32.123Z" }, { url = "https://files.pythonhosted.org/packages/38/38/071ced5a5fd1c85ba0e14ba721b66b053823e5176298c2f707e50bed11d9/numcodecs-0.16.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25be3a516ab677dad890760d357cfe081a371d9c0a2e9a204562318ac5969de3", size = 1654359, upload-time = "2025-11-21T02:49:33.673Z" }, { url = "https://files.pythonhosted.org/packages/d1/c0/5f84ba7525577c1b9909fc2d06ef11314825fc4ad4378f61d0e4c9883b4a/numcodecs-0.16.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0107e839ef75b854e969cb577e140b1aadb9847893937636582d23a2a4c6ce50", size = 1144237, upload-time = "2025-11-21T02:49:35.294Z" }, { url = "https://files.pythonhosted.org/packages/0b/00/787ea5f237b8ea7bc67140c99155f9c00b5baf11c49afc5f3bfefa298f95/numcodecs-0.16.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:015a7c859ecc2a06e2a548f64008c0ec3aaecabc26456c2c62f4278d8fc20597", size = 8483064, upload-time = "2025-11-21T02:49:36.454Z" }, { url = "https://files.pythonhosted.org/packages/c4/e6/d359fdd37498e74d26a167f7a51e54542e642ea47181eb4e643a69a066c3/numcodecs-0.16.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84230b4b9dad2392f2a84242bd6e3e659ac137b5a1ce3571d6965fca673e0903", size = 9126063, upload-time = "2025-11-21T02:49:38.018Z" }, { url = "https://files.pythonhosted.org/packages/27/72/6663cc0382ddbb866136c255c837bcb96cc7ce5e83562efec55e1b995941/numcodecs-0.16.5-cp313-cp313-win_amd64.whl", hash = "sha256:5088145502ad1ebf677ec47d00eb6f0fd600658217db3e0c070c321c85d6cf3d", size = 799275, upload-time = "2025-11-21T02:49:39.558Z" }, { url = "https://files.pythonhosted.org/packages/3c/9e/38e7ca8184c958b51f45d56a4aeceb1134ecde2d8bd157efadc98502cc42/numcodecs-0.16.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b05647b8b769e6bc8016e9fd4843c823ce5c9f2337c089fb5c9c4da05e5275de", size = 1654721, upload-time = "2025-11-21T02:49:40.602Z" }, { url = "https://files.pythonhosted.org/packages/a1/37/260fa42e7b2b08e6e00ad632f8dd620961a60a459426c26cea390f8c68d0/numcodecs-0.16.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3832bd1b5af8bb3e413076b7d93318c8e7d7b68935006b9fa36ca057d1725a8f", size = 1146887, upload-time = "2025-11-21T02:49:41.721Z" }, { url = "https://files.pythonhosted.org/packages/4e/15/e2e1151b5a8b14a15dfd4bb4abccce7fff7580f39bc34092780088835f3a/numcodecs-0.16.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49f7b7d24f103187f53135bed28bb9f0ed6b2e14c604664726487bb6d7c882e1", size = 8476987, upload-time = "2025-11-21T02:49:43.363Z" }, { url = "https://files.pythonhosted.org/packages/6d/30/16a57fc4d9fb0ba06c600408bd6634f2f1753c54a7a351c99c5e09b51ee2/numcodecs-0.16.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aec9736d81b70f337d89c4070ee3ffeff113f386fd789492fa152d26a15043e4", size = 9102377, upload-time = "2025-11-21T02:49:45.508Z" }, { url = "https://files.pythonhosted.org/packages/31/a5/a0425af36c20d55a3ea884db4b4efca25a43bea9214ba69ca7932dd997b4/numcodecs-0.16.5-cp314-cp314-win_amd64.whl", hash = "sha256:b16a14303800e9fb88abc39463ab4706c037647ac17e49e297faa5f7d7dbbf1d", size = 819022, upload-time = "2025-11-21T02:49:47.39Z" }, ] [[package]] name = "numpy" version = "2.3.5" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10", size = 17034641, upload-time = "2025-11-16T22:49:19.336Z" }, { url = "https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218", size = 12528324, upload-time = "2025-11-16T22:49:22.582Z" }, { url = "https://files.pythonhosted.org/packages/4d/1a/e85f0eea4cf03d6a0228f5c0256b53f2df4bc794706e7df019fc622e47f1/numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d", size = 5356872, upload-time = "2025-11-16T22:49:25.408Z" }, { url = "https://files.pythonhosted.org/packages/5c/bb/35ef04afd567f4c989c2060cde39211e4ac5357155c1833bcd1166055c61/numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5", size = 6893148, upload-time = "2025-11-16T22:49:27.549Z" }, { url = "https://files.pythonhosted.org/packages/f2/2b/05bbeb06e2dff5eab512dfc678b1cc5ee94d8ac5956a0885c64b6b26252b/numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7", size = 14557282, upload-time = "2025-11-16T22:49:30.964Z" }, { url = "https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4", size = 16897903, upload-time = "2025-11-16T22:49:34.191Z" }, { url = "https://files.pythonhosted.org/packages/ac/14/085f4cf05fc3f1e8aa95e85404e984ffca9b2275a5dc2b1aae18a67538b8/numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e", size = 16341672, upload-time = "2025-11-16T22:49:37.2Z" }, { url = "https://files.pythonhosted.org/packages/6f/3b/1f73994904142b2aa290449b3bb99772477b5fd94d787093e4f24f5af763/numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748", size = 18838896, upload-time = "2025-11-16T22:49:39.727Z" }, { url = "https://files.pythonhosted.org/packages/cd/b9/cf6649b2124f288309ffc353070792caf42ad69047dcc60da85ee85fea58/numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c", size = 6563608, upload-time = "2025-11-16T22:49:42.079Z" }, { url = "https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c", size = 13078442, upload-time = "2025-11-16T22:49:43.99Z" }, { url = "https://files.pythonhosted.org/packages/6d/a7/f99a41553d2da82a20a2f22e93c94f928e4490bb447c9ff3c4ff230581d3/numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa", size = 10458555, upload-time = "2025-11-16T22:49:47.092Z" }, { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" }, { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" }, { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" }, { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" }, { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" }, { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" }, { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" }, { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" }, { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" }, { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" }, { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" }, { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" }, { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" }, { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" }, { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" }, { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" }, { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" }, { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" }, { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" }, { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" }, { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" }, { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" }, { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" }, { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" }, { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" }, { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" }, { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" }, { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" }, { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" }, { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" }, { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" }, { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" }, { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" }, { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" }, { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" }, { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" }, { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" }, { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" }, { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" }, { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" }, { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" }, { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" }, { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" }, { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, { url = "https://files.pythonhosted.org/packages/c6/65/f9dea8e109371ade9c782b4e4756a82edf9d3366bca495d84d79859a0b79/numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310", size = 16910689, upload-time = "2025-11-16T22:52:23.247Z" }, { url = "https://files.pythonhosted.org/packages/00/4f/edb00032a8fb92ec0a679d3830368355da91a69cab6f3e9c21b64d0bb986/numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c", size = 12457053, upload-time = "2025-11-16T22:52:26.367Z" }, { url = "https://files.pythonhosted.org/packages/16/a4/e8a53b5abd500a63836a29ebe145fc1ab1f2eefe1cfe59276020373ae0aa/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18", size = 5285635, upload-time = "2025-11-16T22:52:29.266Z" }, { url = "https://files.pythonhosted.org/packages/a3/2f/37eeb9014d9c8b3e9c55bc599c68263ca44fdbc12a93e45a21d1d56df737/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff", size = 6801770, upload-time = "2025-11-16T22:52:31.421Z" }, { url = "https://files.pythonhosted.org/packages/7d/e4/68d2f474df2cb671b2b6c2986a02e520671295647dad82484cde80ca427b/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb", size = 14391768, upload-time = "2025-11-16T22:52:33.593Z" }, { url = "https://files.pythonhosted.org/packages/b8/50/94ccd8a2b141cb50651fddd4f6a48874acb3c91c8f0842b08a6afc4b0b21/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7", size = 16729263, upload-time = "2025-11-16T22:52:36.369Z" }, { url = "https://files.pythonhosted.org/packages/2d/ee/346fa473e666fe14c52fcdd19ec2424157290a032d4c41f98127bfb31ac7/numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425", size = 12967213, upload-time = "2025-11-16T22:52:39.38Z" }, ] [[package]] name = "numpy-groupies" version = "0.11.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7d/ff/0559b586423d9a59feac52c2261501106dcd61e45214862de5fbb03b78cb/numpy_groupies-0.11.3.tar.gz", hash = "sha256:aed4afdad55e856b9e737fe4b4673c77e47c2f887c3663a18baaa200407c23e0", size = 159159, upload-time = "2025-05-22T11:47:58.364Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b0/e0/760e73c111193db5ca37712a148e4807d1b0c60302ab31e4ada6528ca34d/numpy_groupies-0.11.3-py3-none-any.whl", hash = "sha256:d4065dd5d56fda941ad5a7c80a7f80b49f671ed148aaa3e243a0e4caa71adcb3", size = 40784, upload-time = "2025-05-22T11:47:56.997Z" }, ] [[package]] name = "numpydoc" version = "1.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e9/3c/dfccc9e7dee357fb2aa13c3890d952a370dd0ed071e0f7ed62ed0df567c1/numpydoc-1.10.0.tar.gz", hash = "sha256:3f7970f6eee30912260a6b31ac72bba2432830cd6722569ec17ee8d3ef5ffa01", size = 94027, upload-time = "2025-12-02T16:39:12.937Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/62/5e/3a6a3e90f35cea3853c45e5d5fb9b7192ce4384616f932cf7591298ab6e1/numpydoc-1.10.0-py3-none-any.whl", hash = "sha256:3149da9874af890bcc2a82ef7aae5484e5aa81cb2778f08e3c307ba6d963721b", size = 69255, upload-time = "2025-12-02T16:39:11.561Z" }, ] [[package]] name = "overrides" version = "7.7.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" }, ] [[package]] name = "packaging" version = "26.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] name = "pandas" version = "3.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "python-dateutil" }, { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/de/da/b1dc0481ab8d55d0f46e343cfe67d4551a0e14fcee52bd38ca1bd73258d8/pandas-3.0.0.tar.gz", hash = "sha256:0facf7e87d38f721f0af46fe70d97373a37701b1c09f7ed7aeeb292ade5c050f", size = 4633005, upload-time = "2026-01-21T15:52:04.726Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/46/1e/b184654a856e75e975a6ee95d6577b51c271cd92cb2b020c9378f53e0032/pandas-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d64ce01eb9cdca96a15266aa679ae50212ec52757c79204dbc7701a222401850", size = 10313247, upload-time = "2026-01-21T15:50:15.775Z" }, { url = "https://files.pythonhosted.org/packages/dd/5e/e04a547ad0f0183bf151fd7c7a477468e3b85ff2ad231c566389e6cc9587/pandas-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:613e13426069793aa1ec53bdcc3b86e8d32071daea138bbcf4fa959c9cdaa2e2", size = 9913131, upload-time = "2026-01-21T15:50:18.611Z" }, { url = "https://files.pythonhosted.org/packages/a2/93/bb77bfa9fc2aba9f7204db807d5d3fb69832ed2854c60ba91b4c65ba9219/pandas-3.0.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0192fee1f1a8e743b464a6607858ee4b071deb0b118eb143d71c2a1d170996d5", size = 10741925, upload-time = "2026-01-21T15:50:21.058Z" }, { url = "https://files.pythonhosted.org/packages/62/fb/89319812eb1d714bfc04b7f177895caeba8ab4a37ef6712db75ed786e2e0/pandas-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0b853319dec8d5e0c8b875374c078ef17f2269986a78168d9bd57e49bf650ae", size = 11245979, upload-time = "2026-01-21T15:50:23.413Z" }, { url = "https://files.pythonhosted.org/packages/a9/63/684120486f541fc88da3862ed31165b3b3e12b6a1c7b93be4597bc84e26c/pandas-3.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:707a9a877a876c326ae2cb640fbdc4ef63b0a7b9e2ef55c6df9942dcee8e2af9", size = 11756337, upload-time = "2026-01-21T15:50:25.932Z" }, { url = "https://files.pythonhosted.org/packages/39/92/7eb0ad232312b59aec61550c3c81ad0743898d10af5df7f80bc5e5065416/pandas-3.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:afd0aa3d0b5cda6e0b8ffc10dbcca3b09ef3cbcd3fe2b27364f85fdc04e1989d", size = 12325517, upload-time = "2026-01-21T15:50:27.952Z" }, { url = "https://files.pythonhosted.org/packages/51/27/bf9436dd0a4fc3130acec0828951c7ef96a0631969613a9a35744baf27f6/pandas-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:113b4cca2614ff7e5b9fee9b6f066618fe73c5a83e99d721ffc41217b2bf57dd", size = 9881576, upload-time = "2026-01-21T15:50:30.149Z" }, { url = "https://files.pythonhosted.org/packages/e7/2b/c618b871fce0159fd107516336e82891b404e3f340821853c2fc28c7830f/pandas-3.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c14837eba8e99a8da1527c0280bba29b0eb842f64aa94982c5e21227966e164b", size = 9140807, upload-time = "2026-01-21T15:50:32.308Z" }, { url = "https://files.pythonhosted.org/packages/0b/38/db33686f4b5fa64d7af40d96361f6a4615b8c6c8f1b3d334eee46ae6160e/pandas-3.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9803b31f5039b3c3b10cc858c5e40054adb4b29b4d81cb2fd789f4121c8efbcd", size = 10334013, upload-time = "2026-01-21T15:50:34.771Z" }, { url = "https://files.pythonhosted.org/packages/a5/7b/9254310594e9774906bacdd4e732415e1f86ab7dbb4b377ef9ede58cd8ec/pandas-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14c2a4099cd38a1d18ff108168ea417909b2dea3bd1ebff2ccf28ddb6a74d740", size = 9874154, upload-time = "2026-01-21T15:50:36.67Z" }, { url = "https://files.pythonhosted.org/packages/63/d4/726c5a67a13bc66643e66d2e9ff115cead482a44fc56991d0c4014f15aaf/pandas-3.0.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d257699b9a9960e6125686098d5714ac59d05222bef7a5e6af7a7fd87c650801", size = 10384433, upload-time = "2026-01-21T15:50:39.132Z" }, { url = "https://files.pythonhosted.org/packages/bf/2e/9211f09bedb04f9832122942de8b051804b31a39cfbad199a819bb88d9f3/pandas-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:69780c98f286076dcafca38d8b8eee1676adf220199c0a39f0ecbf976b68151a", size = 10864519, upload-time = "2026-01-21T15:50:41.043Z" }, { url = "https://files.pythonhosted.org/packages/00/8d/50858522cdc46ac88b9afdc3015e298959a70a08cd21e008a44e9520180c/pandas-3.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4a66384f017240f3858a4c8a7cf21b0591c3ac885cddb7758a589f0f71e87ebb", size = 11394124, upload-time = "2026-01-21T15:50:43.377Z" }, { url = "https://files.pythonhosted.org/packages/86/3f/83b2577db02503cd93d8e95b0f794ad9d4be0ba7cb6c8bcdcac964a34a42/pandas-3.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be8c515c9bc33989d97b89db66ea0cececb0f6e3c2a87fcc8b69443a6923e95f", size = 11920444, upload-time = "2026-01-21T15:50:45.932Z" }, { url = "https://files.pythonhosted.org/packages/64/2d/4f8a2f192ed12c90a0aab47f5557ece0e56b0370c49de9454a09de7381b2/pandas-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:a453aad8c4f4e9f166436994a33884442ea62aa8b27d007311e87521b97246e1", size = 9730970, upload-time = "2026-01-21T15:50:47.962Z" }, { url = "https://files.pythonhosted.org/packages/d4/64/ff571be435cf1e643ca98d0945d76732c0b4e9c37191a89c8550b105eed1/pandas-3.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:da768007b5a33057f6d9053563d6b74dd6d029c337d93c6d0d22a763a5c2ecc0", size = 9041950, upload-time = "2026-01-21T15:50:50.422Z" }, { url = "https://files.pythonhosted.org/packages/6f/fa/7f0ac4ca8877c57537aaff2a842f8760e630d8e824b730eb2e859ffe96ca/pandas-3.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b78d646249b9a2bc191040988c7bb524c92fa8534fb0898a0741d7e6f2ffafa6", size = 10307129, upload-time = "2026-01-21T15:50:52.877Z" }, { url = "https://files.pythonhosted.org/packages/6f/11/28a221815dcea4c0c9414dfc845e34a84a6a7dabc6da3194498ed5ba4361/pandas-3.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bc9cba7b355cb4162442a88ce495e01cb605f17ac1e27d6596ac963504e0305f", size = 9850201, upload-time = "2026-01-21T15:50:54.807Z" }, { url = "https://files.pythonhosted.org/packages/ba/da/53bbc8c5363b7e5bd10f9ae59ab250fc7a382ea6ba08e4d06d8694370354/pandas-3.0.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c9a1a149aed3b6c9bf246033ff91e1b02d529546c5d6fb6b74a28fea0cf4c70", size = 10354031, upload-time = "2026-01-21T15:50:57.463Z" }, { url = "https://files.pythonhosted.org/packages/f7/a3/51e02ebc2a14974170d51e2410dfdab58870ea9bcd37cda15bd553d24dc4/pandas-3.0.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95683af6175d884ee89471842acfca29172a85031fccdabc35e50c0984470a0e", size = 10861165, upload-time = "2026-01-21T15:50:59.32Z" }, { url = "https://files.pythonhosted.org/packages/a5/fe/05a51e3cac11d161472b8297bd41723ea98013384dd6d76d115ce3482f9b/pandas-3.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1fbbb5a7288719e36b76b4f18d46ede46e7f916b6c8d9915b756b0a6c3f792b3", size = 11359359, upload-time = "2026-01-21T15:51:02.014Z" }, { url = "https://files.pythonhosted.org/packages/ee/56/ba620583225f9b85a4d3e69c01df3e3870659cc525f67929b60e9f21dcd1/pandas-3.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e8b9808590fa364416b49b2a35c1f4cf2785a6c156935879e57f826df22038e", size = 11912907, upload-time = "2026-01-21T15:51:05.175Z" }, { url = "https://files.pythonhosted.org/packages/c9/8c/c6638d9f67e45e07656b3826405c5cc5f57f6fd07c8b2572ade328c86e22/pandas-3.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:98212a38a709feb90ae658cb6227ea3657c22ba8157d4b8f913cd4c950de5e7e", size = 9732138, upload-time = "2026-01-21T15:51:07.569Z" }, { url = "https://files.pythonhosted.org/packages/7b/bf/bd1335c3bf1770b6d8fed2799993b11c4971af93bb1b729b9ebbc02ca2ec/pandas-3.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:177d9df10b3f43b70307a149d7ec49a1229a653f907aa60a48f1877d0e6be3be", size = 9033568, upload-time = "2026-01-21T15:51:09.484Z" }, { url = "https://files.pythonhosted.org/packages/8e/c6/f5e2171914d5e29b9171d495344097d54e3ffe41d2d85d8115baba4dc483/pandas-3.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2713810ad3806767b89ad3b7b69ba153e1c6ff6d9c20f9c2140379b2a98b6c98", size = 10741936, upload-time = "2026-01-21T15:51:11.693Z" }, { url = "https://files.pythonhosted.org/packages/51/88/9a0164f99510a1acb9f548691f022c756c2314aad0d8330a24616c14c462/pandas-3.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:15d59f885ee5011daf8335dff47dcb8a912a27b4ad7826dc6cbe809fd145d327", size = 10393884, upload-time = "2026-01-21T15:51:14.197Z" }, { url = "https://files.pythonhosted.org/packages/e0/53/b34d78084d88d8ae2b848591229da8826d1e65aacf00b3abe34023467648/pandas-3.0.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24e6547fb64d2c92665dd2adbfa4e85fa4fd70a9c070e7cfb03b629a0bbab5eb", size = 10310740, upload-time = "2026-01-21T15:51:16.093Z" }, { url = "https://files.pythonhosted.org/packages/5b/d3/bee792e7c3d6930b74468d990604325701412e55d7aaf47460a22311d1a5/pandas-3.0.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48ee04b90e2505c693d3f8e8f524dab8cb8aaf7ddcab52c92afa535e717c4812", size = 10700014, upload-time = "2026-01-21T15:51:18.818Z" }, { url = "https://files.pythonhosted.org/packages/55/db/2570bc40fb13aaed1cbc3fbd725c3a60ee162477982123c3adc8971e7ac1/pandas-3.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66f72fb172959af42a459e27a8d8d2c7e311ff4c1f7db6deb3b643dbc382ae08", size = 11323737, upload-time = "2026-01-21T15:51:20.784Z" }, { url = "https://files.pythonhosted.org/packages/bc/2e/297ac7f21c8181b62a4cccebad0a70caf679adf3ae5e83cb676194c8acc3/pandas-3.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4a4a400ca18230976724a5066f20878af785f36c6756e498e94c2a5e5d57779c", size = 11771558, upload-time = "2026-01-21T15:51:22.977Z" }, { url = "https://files.pythonhosted.org/packages/0a/46/e1c6876d71c14332be70239acce9ad435975a80541086e5ffba2f249bcf6/pandas-3.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:940eebffe55528074341a5a36515f3e4c5e25e958ebbc764c9502cfc35ba3faa", size = 10473771, upload-time = "2026-01-21T15:51:25.285Z" }, { url = "https://files.pythonhosted.org/packages/c0/db/0270ad9d13c344b7a36fa77f5f8344a46501abf413803e885d22864d10bf/pandas-3.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:597c08fb9fef0edf1e4fa2f9828dd27f3d78f9b8c9b4a748d435ffc55732310b", size = 10312075, upload-time = "2026-01-21T15:51:28.5Z" }, { url = "https://files.pythonhosted.org/packages/09/9f/c176f5e9717f7c91becfe0f55a52ae445d3f7326b4a2cf355978c51b7913/pandas-3.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:447b2d68ac5edcbf94655fe909113a6dba6ef09ad7f9f60c80477825b6c489fe", size = 9900213, upload-time = "2026-01-21T15:51:30.955Z" }, { url = "https://files.pythonhosted.org/packages/d9/e7/63ad4cc10b257b143e0a5ebb04304ad806b4e1a61c5da25f55896d2ca0f4/pandas-3.0.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:debb95c77ff3ed3ba0d9aa20c3a2f19165cc7956362f9873fce1ba0a53819d70", size = 10428768, upload-time = "2026-01-21T15:51:33.018Z" }, { url = "https://files.pythonhosted.org/packages/9e/0e/4e4c2d8210f20149fd2248ef3fff26623604922bd564d915f935a06dd63d/pandas-3.0.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fedabf175e7cd82b69b74c30adbaa616de301291a5231138d7242596fc296a8d", size = 10882954, upload-time = "2026-01-21T15:51:35.287Z" }, { url = "https://files.pythonhosted.org/packages/c6/60/c9de8ac906ba1f4d2250f8a951abe5135b404227a55858a75ad26f84db47/pandas-3.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:412d1a89aab46889f3033a386912efcdfa0f1131c5705ff5b668dda88305e986", size = 11430293, upload-time = "2026-01-21T15:51:37.57Z" }, { url = "https://files.pythonhosted.org/packages/a1/69/806e6637c70920e5787a6d6896fd707f8134c2c55cd761e7249a97b7dc5a/pandas-3.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e979d22316f9350c516479dd3a92252be2937a9531ed3a26ec324198a99cdd49", size = 11952452, upload-time = "2026-01-21T15:51:39.618Z" }, { url = "https://files.pythonhosted.org/packages/cb/de/918621e46af55164c400ab0ef389c9d969ab85a43d59ad1207d4ddbe30a5/pandas-3.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:083b11415b9970b6e7888800c43c82e81a06cd6b06755d84804444f0007d6bb7", size = 9851081, upload-time = "2026-01-21T15:51:41.758Z" }, { url = "https://files.pythonhosted.org/packages/91/a1/3562a18dd0bd8c73344bfa26ff90c53c72f827df119d6d6b1dacc84d13e3/pandas-3.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:5db1e62cb99e739fa78a28047e861b256d17f88463c76b8dafc7c1338086dca8", size = 9174610, upload-time = "2026-01-21T15:51:44.312Z" }, { url = "https://files.pythonhosted.org/packages/ce/26/430d91257eaf366f1737d7a1c158677caaf6267f338ec74e3a1ec444111c/pandas-3.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:697b8f7d346c68274b1b93a170a70974cdc7d7354429894d5927c1effdcccd73", size = 10761999, upload-time = "2026-01-21T15:51:46.899Z" }, { url = "https://files.pythonhosted.org/packages/ec/1a/954eb47736c2b7f7fe6a9d56b0cb6987773c00faa3c6451a43db4beb3254/pandas-3.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8cb3120f0d9467ed95e77f67a75e030b67545bcfa08964e349252d674171def2", size = 10410279, upload-time = "2026-01-21T15:51:48.89Z" }, { url = "https://files.pythonhosted.org/packages/20/fc/b96f3a5a28b250cd1b366eb0108df2501c0f38314a00847242abab71bb3a/pandas-3.0.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33fd3e6baa72899746b820c31e4b9688c8e1b7864d7aec2de7ab5035c285277a", size = 10330198, upload-time = "2026-01-21T15:51:51.015Z" }, { url = "https://files.pythonhosted.org/packages/90/b3/d0e2952f103b4fbef1ef22d0c2e314e74fc9064b51cee30890b5e3286ee6/pandas-3.0.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8942e333dc67ceda1095227ad0febb05a3b36535e520154085db632c40ad084", size = 10728513, upload-time = "2026-01-21T15:51:53.387Z" }, { url = "https://files.pythonhosted.org/packages/76/81/832894f286df828993dc5fd61c63b231b0fb73377e99f6c6c369174cf97e/pandas-3.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:783ac35c4d0fe0effdb0d67161859078618b1b6587a1af15928137525217a721", size = 11345550, upload-time = "2026-01-21T15:51:55.329Z" }, { url = "https://files.pythonhosted.org/packages/34/a0/ed160a00fb4f37d806406bc0a79a8b62fe67f29d00950f8d16203ff3409b/pandas-3.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:125eb901e233f155b268bbef9abd9afb5819db74f0e677e89a61b246228c71ac", size = 11799386, upload-time = "2026-01-21T15:51:57.457Z" }, { url = "https://files.pythonhosted.org/packages/36/c8/2ac00d7255252c5e3cf61b35ca92ca25704b0188f7454ca4aec08a33cece/pandas-3.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b86d113b6c109df3ce0ad5abbc259fe86a1bd4adfd4a31a89da42f84f65509bb", size = 10873041, upload-time = "2026-01-21T15:52:00.034Z" }, { url = "https://files.pythonhosted.org/packages/e6/3f/a80ac00acbc6b35166b42850e98a4f466e2c0d9c64054161ba9620f95680/pandas-3.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:1c39eab3ad38f2d7a249095f0a3d8f8c22cc0f847e98ccf5bbe732b272e2d9fa", size = 9441003, upload-time = "2026-01-21T15:52:02.281Z" }, ] [[package]] name = "pandas-stubs" version = "3.0.0.260204" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/27/1d/297ff2c7ea50a768a2247621d6451abb2a07c0e9be7ca6d36ebe371658e5/pandas_stubs-3.0.0.260204.tar.gz", hash = "sha256:bf9294b76352effcffa9cb85edf0bed1339a7ec0c30b8e1ac3d66b4228f1fbc3", size = 109383, upload-time = "2026-02-04T15:17:17.247Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7c/2f/f91e4eee21585ff548e83358332d5632ee49f6b2dcd96cb5dca4e0468951/pandas_stubs-3.0.0.260204-py3-none-any.whl", hash = "sha256:5ab9e4d55a6e2752e9720828564af40d48c4f709e6a2c69b743014a6fcb6c241", size = 168540, upload-time = "2026-02-04T15:17:15.615Z" }, ] [[package]] name = "pandocfilters" version = "1.5.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" }, ] [[package]] name = "parso" version = "0.8.5" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, ] [[package]] name = "partd" version = "1.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "locket" }, { name = "toolz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b2/3a/3f06f34820a31257ddcabdfafc2672c5816be79c7e353b02c1f318daa7d4/partd-1.4.2.tar.gz", hash = "sha256:d022c33afbdc8405c226621b015e8067888173d85f7f5ecebb3cafed9a20f02c", size = 21029, upload-time = "2024-05-06T19:51:41.945Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl", hash = "sha256:978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f", size = 18905, upload-time = "2024-05-06T19:51:39.271Z" }, ] [[package]] name = "pathspec" version = "1.0.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] [[package]] name = "pdbpp" version = "0.12.0.post1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "fancycompleter" }, { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5b/23/0bd339679059fb289e0752671b5eac63a472742d1de0035e13a27429e500/pdbpp-0.12.0.post1.tar.gz", hash = "sha256:cace9951d7414fe651141d240ab20e4509c8f97d20f7b142386d1bd1bb182d18", size = 75765, upload-time = "2026-01-19T10:56:55.026Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/28/f2/20fa391af27da4ef421832fb7d554748786efcc3b0da69a51e2ca66043d0/pdbpp-0.12.0.post1-py3-none-any.whl", hash = "sha256:8c62acd6adaa02f620d7d9cff689208a28019b6c05e6b894318f313e61fb1dc7", size = 30661, upload-time = "2026-01-19T10:56:53.638Z" }, ] [[package]] name = "pexpect" version = "4.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ptyprocess", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, ] [[package]] name = "pillow" version = "12.1.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" }, { url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" }, { url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" }, { url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" }, { url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" }, { url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" }, { url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" }, { url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" }, { url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" }, { url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" }, { url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" }, { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, { url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" }, { url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" }, { url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" }, { url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" }, { url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" }, { url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" }, { url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" }, ] [[package]] name = "platformdirs" version = "4.5.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] name = "pooch" version = "1.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, { name = "platformdirs" }, { name = "requests" }, ] sdist = { url = "https://files.pythonhosted.org/packages/83/43/85ef45e8b36c6a48546af7b266592dc32d7f67837a6514d111bced6d7d75/pooch-1.9.0.tar.gz", hash = "sha256:de46729579b9857ffd3e741987a2f6d5e0e03219892c167c6578c0091fb511ed", size = 61788, upload-time = "2026-01-30T19:15:09.649Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl", hash = "sha256:f265597baa9f760d25ceb29d0beb8186c243d6607b0f60b83ecf14078dbc703b", size = 67175, upload-time = "2026-01-30T19:15:08.36Z" }, ] [[package]] name = "pre-commit" version = "4.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, { name = "identify" }, { name = "nodeenv" }, { name = "pyyaml" }, { name = "virtualenv" }, ] sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, ] [[package]] name = "prometheus-client" version = "0.24.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f0/58/a794d23feb6b00fc0c72787d7e87d872a6730dd9ed7c7b3e954637d8f280/prometheus_client-0.24.1.tar.gz", hash = "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9", size = 85616, upload-time = "2026-01-14T15:26:26.965Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", size = 64057, upload-time = "2026-01-14T15:26:24.42Z" }, ] [[package]] name = "prompt-toolkit" version = "3.0.52" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wcwidth" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, ] [[package]] name = "psutil" version = "7.2.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] [[package]] name = "ptyprocess" version = "0.7.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, ] [[package]] name = "pure-eval" version = "0.2.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, ] [[package]] name = "py-cpuinfo" version = "9.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716, upload-time = "2022-10-25T20:38:06.303Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335, upload-time = "2022-10-25T20:38:27.636Z" }, ] [[package]] name = "py-rattler" version = "0.22.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1e/da/8385df5a3bfb5f8904da27011074247a858d71b0a35e29688a0d4cbfe6b0/py_rattler-0.22.0.tar.gz", hash = "sha256:46e047ff23a8fe2f3b599f02ef24ff29a6e65c571202828863361d2e9d33fdd8", size = 2305836, upload-time = "2026-01-30T00:45:53.224Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/78/d0/cd76386cf9c13c5fa5526c5f26f11f8db9276b413155be9bc94b794fbf0a/py_rattler-0.22.0-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ee147995a22f62869ec241f461db3f29d3dfb92e585669953ebc11c0e0124282", size = 31460122, upload-time = "2026-01-30T00:45:17.969Z" }, { url = "https://files.pythonhosted.org/packages/53/d4/c2368a59e32bdcc5d230111c15a976364b2ff1830c7ba93e868c942033eb/py_rattler-0.22.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:60c6679744e4271bb76cb8dd5aff72427e18b94ad5ed025dc0ab913c5da75743", size = 16312533, upload-time = "2026-01-30T00:45:20.877Z" }, { url = "https://files.pythonhosted.org/packages/d0/5c/904613590fffad0d0e0182fe8ed8c9aa9aae0bdfb576cf53c80657c4c0fb/py_rattler-0.22.0-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3aa8b7c4dfe15accc4e42625492f9bbf4c120bf18ede1604af3104d2328f2d1", size = 20265369, upload-time = "2026-01-30T00:45:23.116Z" }, { url = "https://files.pythonhosted.org/packages/41/4e/45b38fa958fe3e64bce1c1c262f8f9b016117fae39a760603688196bd4c5/py_rattler-0.22.0-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:30c5f3373d74d4a9b6d276f1554e193f36158674b751a4d7b1f5aec02c25a538", size = 27016563, upload-time = "2026-01-30T00:45:25.697Z" }, { url = "https://files.pythonhosted.org/packages/3d/c2/70c9ee8ba764c7549b8b73d53c6f6e9bb16f9414161fd4e41cfc9b3372af/py_rattler-0.22.0-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:83fe479e28a3565015e8ef7bb797c14eab07d30ceafe4eeee1ecd128e6ed4d2b", size = 25840189, upload-time = "2026-01-30T00:45:28.28Z" }, { url = "https://files.pythonhosted.org/packages/63/7b/c9909e847b5c961fd8ce7f75ffc7080653f2e010dfcd0959fccb63bfd55c/py_rattler-0.22.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ddd8d78081d0f59f5394bd152223f6c0a616b7b800aec86131fa526f4539526", size = 19062843, upload-time = "2026-01-30T00:45:30.932Z" }, { url = "https://files.pythonhosted.org/packages/e6/b5/4b7e314fcf6b84d9d39963722922a5e67d664058f0bf55d71512c0d632ba/py_rattler-0.22.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e5794108c5ee00f31d106842b54564d5b847081e09ad0f143cb215ce20ac1385", size = 18764035, upload-time = "2026-01-30T00:45:34.641Z" }, { url = "https://files.pythonhosted.org/packages/bf/fb/edf3723aa679a93893120082e604091c7cf2b8459f8daa5ee45951eeaee8/py_rattler-0.22.0-cp38-abi3-manylinux_2_28_armv7l.whl", hash = "sha256:22bd84faedec35314efdd18ad71b1f790fed2ef0a15763ca38d6f77f0579814c", size = 18285126, upload-time = "2026-01-30T00:45:36.866Z" }, { url = "https://files.pythonhosted.org/packages/b9/74/71f8ec3711873386c2dd7d39bdb39f9524a0de5442ffc617191425945aca/py_rattler-0.22.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:338dcce0042db1e2274fd59974492dffbd63f257375bc8136084a73b84cd0dfb", size = 18761977, upload-time = "2026-01-30T00:45:39.152Z" }, { url = "https://files.pythonhosted.org/packages/f8/ae/9b25a848fb37dd151037475b84233d5a8888e57d39693f725815ccb2c5ae/py_rattler-0.22.0-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:8e665f1c57d034afe970bc696aeeba666136520f66e781671119d34df9d94b85", size = 18519897, upload-time = "2026-01-30T00:45:41.451Z" }, { url = "https://files.pythonhosted.org/packages/36/23/e0792f2fdd88cf965a4eea56397e8c70191f9ce97cf12e025028b13f9926/py_rattler-0.22.0-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:91b08dd879620d47142282b9621ec05cf9b02baa8a6db1e8f1b94c1f77b7abcf", size = 21651576, upload-time = "2026-01-30T00:45:44.168Z" }, { url = "https://files.pythonhosted.org/packages/f1/26/5ffdd07e5bfeded84cc561cc9066abe0afc7a40d52665c8423dc861cf967/py_rattler-0.22.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:899a3d41493998038d8841b088d32295b6e4372c5b3b59bb1bd2b731d357d217", size = 19278852, upload-time = "2026-01-30T00:45:46.284Z" }, { url = "https://files.pythonhosted.org/packages/0f/79/76b9f7b79c926b6ae5f768f5bcd68bb17dd794f998f7fbb7fbfd17934b55/py_rattler-0.22.0-cp38-abi3-win32.whl", hash = "sha256:65958cebea28b2c10be527289294984b763b6247fd76ef91dabf6cb9ea662a6d", size = 12787818, upload-time = "2026-01-30T00:45:48.332Z" }, { url = "https://files.pythonhosted.org/packages/50/44/38a450188c78858dc39635867f15e6964dc4d5aa8ad82bc4ae74a15293c4/py_rattler-0.22.0-cp38-abi3-win_amd64.whl", hash = "sha256:b806d57dae960274bd2730b56f603c53423f5af3e78b863e9b75a50b00740a69", size = 14066648, upload-time = "2026-01-30T00:45:50.865Z" }, ] [[package]] name = "pyarrow" version = "23.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/01/33/ffd9c3eb087fa41dd79c3cf20c4c0ae3cdb877c4f8e1107a446006344924/pyarrow-23.0.0.tar.gz", hash = "sha256:180e3150e7edfcd182d3d9afba72f7cf19839a497cc76555a8dce998a8f67615", size = 1167185, upload-time = "2026-01-18T16:19:42.218Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/aa/c0/57fe251102ca834fee0ef69a84ad33cc0ff9d5dfc50f50b466846356ecd7/pyarrow-23.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5574d541923efcbfdf1294a2746ae3b8c2498a2dc6cd477882f6f4e7b1ac08d3", size = 34276762, upload-time = "2026-01-18T16:14:34.128Z" }, { url = "https://files.pythonhosted.org/packages/f8/4e/24130286548a5bc250cbed0b6bbf289a2775378a6e0e6f086ae8c68fc098/pyarrow-23.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:2ef0075c2488932e9d3c2eb3482f9459c4be629aa673b725d5e3cf18f777f8e4", size = 35821420, upload-time = "2026-01-18T16:14:40.699Z" }, { url = "https://files.pythonhosted.org/packages/ee/55/a869e8529d487aa2e842d6c8865eb1e2c9ec33ce2786eb91104d2c3e3f10/pyarrow-23.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:65666fc269669af1ef1c14478c52222a2aa5c907f28b68fb50a203c777e4f60c", size = 44457412, upload-time = "2026-01-18T16:14:49.051Z" }, { url = "https://files.pythonhosted.org/packages/36/81/1de4f0edfa9a483bbdf0082a05790bd6a20ed2169ea12a65039753be3a01/pyarrow-23.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:4d85cb6177198f3812db4788e394b757223f60d9a9f5ad6634b3e32be1525803", size = 47534285, upload-time = "2026-01-18T16:14:56.748Z" }, { url = "https://files.pythonhosted.org/packages/f2/04/464a052d673b5ece074518f27377861662449f3c1fdb39ce740d646fd098/pyarrow-23.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1a9ff6fa4141c24a03a1a434c63c8fa97ce70f8f36bccabc18ebba905ddf0f17", size = 48157913, upload-time = "2026-01-18T16:15:05.114Z" }, { url = "https://files.pythonhosted.org/packages/f4/1b/32a4de9856ee6688c670ca2def588382e573cce45241a965af04c2f61687/pyarrow-23.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:84839d060a54ae734eb60a756aeacb62885244aaa282f3c968f5972ecc7b1ecc", size = 50582529, upload-time = "2026-01-18T16:15:12.846Z" }, { url = "https://files.pythonhosted.org/packages/db/c7/d6581f03e9b9e44ea60b52d1750ee1a7678c484c06f939f45365a45f7eef/pyarrow-23.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:a149a647dbfe928ce8830a713612aa0b16e22c64feac9d1761529778e4d4eaa5", size = 27542646, upload-time = "2026-01-18T16:15:18.89Z" }, { url = "https://files.pythonhosted.org/packages/3d/bd/c861d020831ee57609b73ea721a617985ece817684dc82415b0bc3e03ac3/pyarrow-23.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5961a9f646c232697c24f54d3419e69b4261ba8a8b66b0ac54a1851faffcbab8", size = 34189116, upload-time = "2026-01-18T16:15:28.054Z" }, { url = "https://files.pythonhosted.org/packages/8c/23/7725ad6cdcbaf6346221391e7b3eecd113684c805b0a95f32014e6fa0736/pyarrow-23.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:632b3e7c3d232f41d64e1a4a043fb82d44f8a349f339a1188c6a0dd9d2d47d8a", size = 35803831, upload-time = "2026-01-18T16:15:33.798Z" }, { url = "https://files.pythonhosted.org/packages/57/06/684a421543455cdc2944d6a0c2cc3425b028a4c6b90e34b35580c4899743/pyarrow-23.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:76242c846db1411f1d6c2cc3823be6b86b40567ee24493344f8226ba34a81333", size = 44436452, upload-time = "2026-01-18T16:15:41.598Z" }, { url = "https://files.pythonhosted.org/packages/c6/6f/8f9eb40c2328d66e8b097777ddcf38494115ff9f1b5bc9754ba46991191e/pyarrow-23.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b73519f8b52ae28127000986bf228fda781e81d3095cd2d3ece76eb5cf760e1b", size = 47557396, upload-time = "2026-01-18T16:15:51.252Z" }, { url = "https://files.pythonhosted.org/packages/10/6e/f08075f1472e5159553501fde2cc7bc6700944bdabe49a03f8a035ee6ccd/pyarrow-23.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:068701f6823449b1b6469120f399a1239766b117d211c5d2519d4ed5861f75de", size = 48147129, upload-time = "2026-01-18T16:16:00.299Z" }, { url = "https://files.pythonhosted.org/packages/7d/82/d5a680cd507deed62d141cc7f07f7944a6766fc51019f7f118e4d8ad0fb8/pyarrow-23.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1801ba947015d10e23bca9dd6ef5d0e9064a81569a89b6e9a63b59224fd060df", size = 50596642, upload-time = "2026-01-18T16:16:08.502Z" }, { url = "https://files.pythonhosted.org/packages/a9/26/4f29c61b3dce9fa7780303b86895ec6a0917c9af927101daaaf118fbe462/pyarrow-23.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:52265266201ec25b6839bf6bd4ea918ca6d50f31d13e1cf200b4261cd11dc25c", size = 27660628, upload-time = "2026-01-18T16:16:15.28Z" }, { url = "https://files.pythonhosted.org/packages/66/34/564db447d083ec7ff93e0a883a597d2f214e552823bfc178a2d0b1f2c257/pyarrow-23.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:ad96a597547af7827342ffb3c503c8316e5043bb09b47a84885ce39394c96e00", size = 34184630, upload-time = "2026-01-18T16:16:22.141Z" }, { url = "https://files.pythonhosted.org/packages/aa/3a/3999daebcb5e6119690c92a621c4d78eef2ffba7a0a1b56386d2875fcd77/pyarrow-23.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:b9edf990df77c2901e79608f08c13fbde60202334a4fcadb15c1f57bf7afee43", size = 35796820, upload-time = "2026-01-18T16:16:29.441Z" }, { url = "https://files.pythonhosted.org/packages/ec/ee/39195233056c6a8d0976d7d1ac1cd4fe21fb0ec534eca76bc23ef3f60e11/pyarrow-23.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:36d1b5bc6ddcaff0083ceec7e2561ed61a51f49cce8be079ee8ed406acb6fdef", size = 44438735, upload-time = "2026-01-18T16:16:38.79Z" }, { url = "https://files.pythonhosted.org/packages/2c/41/6a7328ee493527e7afc0c88d105ecca69a3580e29f2faaeac29308369fd7/pyarrow-23.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4292b889cd224f403304ddda8b63a36e60f92911f89927ec8d98021845ea21be", size = 47557263, upload-time = "2026-01-18T16:16:46.248Z" }, { url = "https://files.pythonhosted.org/packages/c6/ee/34e95b21ee84db494eae60083ddb4383477b31fb1fd19fd866d794881696/pyarrow-23.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dfd9e133e60eaa847fd80530a1b89a052f09f695d0b9c34c235ea6b2e0924cf7", size = 48153529, upload-time = "2026-01-18T16:16:53.412Z" }, { url = "https://files.pythonhosted.org/packages/52/88/8a8d83cea30f4563efa1b7bf51d241331ee5cd1b185a7e063f5634eca415/pyarrow-23.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832141cc09fac6aab1cd3719951d23301396968de87080c57c9a7634e0ecd068", size = 50598851, upload-time = "2026-01-18T16:17:01.133Z" }, { url = "https://files.pythonhosted.org/packages/c6/4c/2929c4be88723ba025e7b3453047dc67e491c9422965c141d24bab6b5962/pyarrow-23.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:7a7d067c9a88faca655c71bcc30ee2782038d59c802d57950826a07f60d83c4c", size = 27577747, upload-time = "2026-01-18T16:18:02.413Z" }, { url = "https://files.pythonhosted.org/packages/64/52/564a61b0b82d72bd68ec3aef1adda1e3eba776f89134b9ebcb5af4b13cb6/pyarrow-23.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:ce9486e0535a843cf85d990e2ec5820a47918235183a5c7b8b97ed7e92c2d47d", size = 34446038, upload-time = "2026-01-18T16:17:07.861Z" }, { url = "https://files.pythonhosted.org/packages/cc/c9/232d4f9855fd1de0067c8a7808a363230d223c83aeee75e0fe6eab851ba9/pyarrow-23.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:075c29aeaa685fd1182992a9ed2499c66f084ee54eea47da3eb76e125e06064c", size = 35921142, upload-time = "2026-01-18T16:17:15.401Z" }, { url = "https://files.pythonhosted.org/packages/96/f2/60af606a3748367b906bb82d41f0032e059f075444445d47e32a7ff1df62/pyarrow-23.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:799965a5379589510d888be3094c2296efd186a17ca1cef5b77703d4d5121f53", size = 44490374, upload-time = "2026-01-18T16:17:23.93Z" }, { url = "https://files.pythonhosted.org/packages/ff/2d/7731543050a678ea3a413955a2d5d80d2a642f270aa57a3cb7d5a86e3f46/pyarrow-23.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ef7cac8fe6fccd8b9e7617bfac785b0371a7fe26af59463074e4882747145d40", size = 47527896, upload-time = "2026-01-18T16:17:33.393Z" }, { url = "https://files.pythonhosted.org/packages/5a/90/f3342553b7ac9879413aed46500f1637296f3c8222107523a43a1c08b42a/pyarrow-23.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15a414f710dc927132dd67c361f78c194447479555af57317066ee5116b90e9e", size = 48210401, upload-time = "2026-01-18T16:17:42.012Z" }, { url = "https://files.pythonhosted.org/packages/f3/da/9862ade205ecc46c172b6ce5038a74b5151c7401e36255f15975a45878b2/pyarrow-23.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e0d2e6915eca7d786be6a77bf227fbc06d825a75b5b5fe9bcbef121dec32685", size = 50579677, upload-time = "2026-01-18T16:17:50.241Z" }, { url = "https://files.pythonhosted.org/packages/c2/4c/f11f371f5d4740a5dafc2e11c76bcf42d03dfdb2d68696da97de420b6963/pyarrow-23.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:4b317ea6e800b5704e5e5929acb6e2dc13e9276b708ea97a39eb8b345aa2658b", size = 27631889, upload-time = "2026-01-18T16:17:56.55Z" }, { url = "https://files.pythonhosted.org/packages/97/bb/15aec78bcf43a0c004067bd33eb5352836a29a49db8581fc56f2b6ca88b7/pyarrow-23.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:20b187ed9550d233a872074159f765f52f9d92973191cd4b93f293a19efbe377", size = 34213265, upload-time = "2026-01-18T16:18:07.904Z" }, { url = "https://files.pythonhosted.org/packages/f6/6c/deb2c594bbba41c37c5d9aa82f510376998352aa69dfcb886cb4b18ad80f/pyarrow-23.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:18ec84e839b493c3886b9b5e06861962ab4adfaeb79b81c76afbd8d84c7d5fda", size = 35819211, upload-time = "2026-01-18T16:18:13.94Z" }, { url = "https://files.pythonhosted.org/packages/e0/e5/ee82af693cb7b5b2b74f6524cdfede0e6ace779d7720ebca24d68b57c36b/pyarrow-23.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:e438dd3f33894e34fd02b26bd12a32d30d006f5852315f611aa4add6c7fab4bc", size = 44502313, upload-time = "2026-01-18T16:18:20.367Z" }, { url = "https://files.pythonhosted.org/packages/9c/86/95c61ad82236495f3c31987e85135926ba3ec7f3819296b70a68d8066b49/pyarrow-23.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:a244279f240c81f135631be91146d7fa0e9e840e1dfed2aba8483eba25cd98e6", size = 47585886, upload-time = "2026-01-18T16:18:27.544Z" }, { url = "https://files.pythonhosted.org/packages/bb/6e/a72d901f305201802f016d015de1e05def7706fff68a1dedefef5dc7eff7/pyarrow-23.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c4692e83e42438dba512a570c6eaa42be2f8b6c0f492aea27dec54bdc495103a", size = 48207055, upload-time = "2026-01-18T16:18:35.425Z" }, { url = "https://files.pythonhosted.org/packages/f9/e5/5de029c537630ca18828db45c30e2a78da03675a70ac6c3528203c416fe3/pyarrow-23.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae7f30f898dfe44ea69654a35c93e8da4cef6606dc4c72394068fd95f8e9f54a", size = 50619812, upload-time = "2026-01-18T16:18:43.553Z" }, { url = "https://files.pythonhosted.org/packages/59/8d/2af846cd2412e67a087f5bda4a8e23dfd4ebd570f777db2e8686615dafc1/pyarrow-23.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:5b86bb649e4112fb0614294b7d0a175c7513738876b89655605ebb87c804f861", size = 28263851, upload-time = "2026-01-18T16:19:38.567Z" }, { url = "https://files.pythonhosted.org/packages/7b/7f/caab863e587041156f6786c52e64151b7386742c8c27140f637176e9230e/pyarrow-23.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:ebc017d765d71d80a3f8584ca0566b53e40464586585ac64176115baa0ada7d3", size = 34463240, upload-time = "2026-01-18T16:18:49.755Z" }, { url = "https://files.pythonhosted.org/packages/c9/fa/3a5b8c86c958e83622b40865e11af0857c48ec763c11d472c87cd518283d/pyarrow-23.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:0800cc58a6d17d159df823f87ad66cefebf105b982493d4bad03ee7fab84b993", size = 35935712, upload-time = "2026-01-18T16:18:55.626Z" }, { url = "https://files.pythonhosted.org/packages/c5/08/17a62078fc1a53decb34a9aa79cf9009efc74d63d2422e5ade9fed2f99e3/pyarrow-23.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:3a7c68c722da9bb5b0f8c10e3eae71d9825a4b429b40b32709df5d1fa55beb3d", size = 44503523, upload-time = "2026-01-18T16:19:03.958Z" }, { url = "https://files.pythonhosted.org/packages/cc/70/84d45c74341e798aae0323d33b7c39194e23b1abc439ceaf60a68a7a969a/pyarrow-23.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:bd5556c24622df90551063ea41f559b714aa63ca953db884cfb958559087a14e", size = 47542490, upload-time = "2026-01-18T16:19:11.208Z" }, { url = "https://files.pythonhosted.org/packages/61/d9/d1274b0e6f19e235de17441e53224f4716574b2ca837022d55702f24d71d/pyarrow-23.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54810f6e6afc4ffee7c2e0051b61722fbea9a4961b46192dcfae8ea12fa09059", size = 48233605, upload-time = "2026-01-18T16:19:19.544Z" }, { url = "https://files.pythonhosted.org/packages/39/07/e4e2d568cb57543d84482f61e510732820cddb0f47c4bb7df629abfed852/pyarrow-23.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:14de7d48052cf4b0ed174533eafa3cfe0711b8076ad70bede32cf59f744f0d7c", size = 50603979, upload-time = "2026-01-18T16:19:26.717Z" }, { url = "https://files.pythonhosted.org/packages/72/9c/47693463894b610f8439b2e970b82ef81e9599c757bf2049365e40ff963c/pyarrow-23.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:427deac1f535830a744a4f04a6ac183a64fcac4341b3f618e693c41b7b98d2b0", size = 28338905, upload-time = "2026-01-18T16:19:32.93Z" }, ] [[package]] name = "pycparser" version = "3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] name = "pygments" version = "2.19.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] name = "pympler" version = "1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pywin32", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dd/37/c384631908029676d8e7213dd956bb686af303a80db7afbc9be36bc49495/pympler-1.1.tar.gz", hash = "sha256:1eaa867cb8992c218430f1708fdaccda53df064144d1c5656b1e6f1ee6000424", size = 179954, upload-time = "2024-06-28T19:56:06.563Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/79/4f/a6a2e2b202d7fd97eadfe90979845b8706676b41cbd3b42ba75adf329d1f/Pympler-1.1-py3-none-any.whl", hash = "sha256:5b223d6027d0619584116a0cbc28e8d2e378f7a79c1e5e024f9ff3b673c58506", size = 165766, upload-time = "2024-06-28T19:56:05.087Z" }, ] [[package]] name = "pyparsing" version = "3.3.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] [[package]] name = "pyproject-hooks" version = "1.2.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, ] [[package]] name = "pyreadline3" version = "3.5.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" }, ] [[package]] name = "pyrepl" version = "0.11.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/08/4f/7088417e5465c53a30b918d30542aad89352ea0d635a5d077717c69a7d2b/pyrepl-0.11.4.tar.gz", hash = "sha256:efe988b4a6e5eed587e9769dc2269aeec2b6feec2f5d77995ee85b9ad7cf7063", size = 51089, upload-time = "2025-07-17T22:56:25.42Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/bd/a5/ce97a778f096aaa27cfcb7ad09f1198cf73277dcab6c68a4b8f332d91e48/pyrepl-0.11.4-py3-none-any.whl", hash = "sha256:ac30d6340267a21c39e1b1934f92bca6b8735017d14b17e40f903b2d1563541d", size = 55596, upload-time = "2025-07-17T22:56:24.537Z" }, ] [[package]] name = "pytest" version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] name = "pytest-cov" version = "7.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pluggy" }, { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, ] [[package]] name = "pytest-pretty" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, { name = "rich" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ba/d7/c699e0be5401fe9ccad484562f0af9350b4e48c05acf39fb3dab1932128f/pytest_pretty-1.3.0.tar.gz", hash = "sha256:97e9921be40f003e40ae78db078d4a0c1ea42bf73418097b5077970c2cc43bf3", size = 219297, upload-time = "2025-06-04T12:54:37.322Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ab/85/2f97a1b65178b0f11c9c77c35417a4cc5b99a80db90dad4734a129844ea5/pytest_pretty-1.3.0-py3-none-any.whl", hash = "sha256:074b9d5783cef9571494543de07e768a4dda92a3e85118d6c7458c67297159b7", size = 5620, upload-time = "2025-06-04T12:54:36.229Z" }, ] [[package]] name = "pytest-reportlog" version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/93/d5/2f4a73822efaad1d6aea6660468d6290963e5f61d2f9d9ca707f7f0b1c13/pytest_reportlog-1.0.0.tar.gz", hash = "sha256:75aec3a92bb53456c3e028605a636579d26f31c6f1e035ad9f706c203cfcb74e", size = 5646, upload-time = "2025-11-11T16:05:15.98Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/05/70/807cbdac584629623c1c2f76b7a39a343e3989510cd52385f1f40581e963/pytest_reportlog-1.0.0-py3-none-any.whl", hash = "sha256:3fc837ef3be6e50f33b52aaf99f88bfbb2ec525febbc3990389d24bbfba28753", size = 6015, upload-time = "2025-11-11T16:05:14.585Z" }, ] [[package]] name = "pytest-xdist" version = "3.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "execnet" }, { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, ] [[package]] name = "python-dateutil" version = "2.9.0.post0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] [[package]] name = "python-json-logger" version = "4.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" }, ] [[package]] name = "pytz" version = "2025.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, ] [[package]] name = "pywin32" version = "311" source = { registry = "https://pypi.org/simple" } wheels = [ { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, ] [[package]] name = "pywinpty" version = "3.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f3/bb/a7cc2967c5c4eceb6cc49cfe39447d4bfc56e6c865e7c2249b6eb978935f/pywinpty-3.0.2.tar.gz", hash = "sha256:1505cc4cb248af42cb6285a65c9c2086ee9e7e574078ee60933d5d7fa86fb004", size = 30669, upload-time = "2025-10-03T21:16:29.205Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a6/a1/409c1651c9f874d598c10f51ff586c416625601df4bca315d08baec4c3e3/pywinpty-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:327790d70e4c841ebd9d0f295a780177149aeb405bca44c7115a3de5c2054b23", size = 2050304, upload-time = "2025-10-03T21:19:29.466Z" }, { url = "https://files.pythonhosted.org/packages/02/4e/1098484e042c9485f56f16eb2b69b43b874bd526044ee401512234cf9e04/pywinpty-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:99fdd9b455f0ad6419aba6731a7a0d2f88ced83c3c94a80ff9533d95fa8d8a9e", size = 2050391, upload-time = "2025-10-03T21:19:01.642Z" }, { url = "https://files.pythonhosted.org/packages/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:18f78b81e4cfee6aabe7ea8688441d30247b73e52cd9657138015c5f4ee13a51", size = 2050057, upload-time = "2025-10-03T21:19:26.732Z" }, { url = "https://files.pythonhosted.org/packages/cb/44/cbae12ecf6f4fa4129c36871fd09c6bef4f98d5f625ecefb5e2449765508/pywinpty-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:663383ecfab7fc382cc97ea5c4f7f0bb32c2f889259855df6ea34e5df42d305b", size = 2049874, upload-time = "2025-10-03T21:18:53.923Z" }, { url = "https://files.pythonhosted.org/packages/ca/15/f12c6055e2d7a617d4d5820e8ac4ceaff849da4cb124640ef5116a230771/pywinpty-3.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:28297cecc37bee9f24d8889e47231972d6e9e84f7b668909de54f36ca785029a", size = 2050386, upload-time = "2025-10-03T21:18:50.477Z" }, { url = "https://files.pythonhosted.org/packages/de/24/c6907c5bb06043df98ad6a0a0ff5db2e0affcecbc3b15c42404393a3f72a/pywinpty-3.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:34b55ae9a1b671fe3eae071d86618110538e8eaad18fcb1531c0830b91a82767", size = 2049834, upload-time = "2025-10-03T21:19:25.688Z" }, ] [[package]] name = "pyyaml" version = "6.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] [[package]] name = "pyzmq" version = "27.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "implementation_name == 'pypy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, ] [[package]] name = "referencing" version = "0.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "rpds-py" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, ] [[package]] name = "requests" version = "2.32.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "charset-normalizer" }, { name = "idna" }, { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] [[package]] name = "rfc3339-validator" version = "0.1.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, ] [[package]] name = "rfc3986-validator" version = "0.1.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760, upload-time = "2019-10-28T16:00:19.144Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242, upload-time = "2019-10-28T16:00:13.976Z" }, ] [[package]] name = "rfc3987-syntax" version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lark" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239, upload-time = "2025-07-18T01:05:05.015Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046, upload-time = "2025-07-18T01:05:03.843Z" }, ] [[package]] name = "rich" version = "14.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/74/99/a4cab2acbb884f80e558b0771e97e21e939c5dfb460f488d19df485e8298/rich-14.3.2.tar.gz", hash = "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8", size = 230143, upload-time = "2026-02-01T16:20:47.908Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69", size = 309963, upload-time = "2026-02-01T16:20:46.078Z" }, ] [[package]] name = "roman-numerals" version = "4.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, ] [[package]] name = "rpds-py" version = "0.30.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, ] [[package]] name = "scipy" version = "1.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/3e/9cca699f3486ce6bc12ff46dc2031f1ec8eb9ccc9a320fdaf925f1417426/scipy-1.17.0.tar.gz", hash = "sha256:2591060c8e648d8b96439e111ac41fd8342fdeff1876be2e19dea3fe8930454e", size = 30396830, upload-time = "2026-01-10T21:34:23.009Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/1e/4b/c89c131aa87cad2b77a54eb0fb94d633a842420fa7e919dc2f922037c3d8/scipy-1.17.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:2abd71643797bd8a106dff97894ff7869eeeb0af0f7a5ce02e4227c6a2e9d6fd", size = 31381316, upload-time = "2026-01-10T21:24:33.42Z" }, { url = "https://files.pythonhosted.org/packages/5e/5f/a6b38f79a07d74989224d5f11b55267714707582908a5f1ae854cf9a9b84/scipy-1.17.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:ef28d815f4d2686503e5f4f00edc387ae58dfd7a2f42e348bb53359538f01558", size = 27966760, upload-time = "2026-01-10T21:24:38.911Z" }, { url = "https://files.pythonhosted.org/packages/c1/20/095ad24e031ee8ed3c5975954d816b8e7e2abd731e04f8be573de8740885/scipy-1.17.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:272a9f16d6bb4667e8b50d25d71eddcc2158a214df1b566319298de0939d2ab7", size = 20138701, upload-time = "2026-01-10T21:24:43.249Z" }, { url = "https://files.pythonhosted.org/packages/89/11/4aad2b3858d0337756f3323f8960755704e530b27eb2a94386c970c32cbe/scipy-1.17.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:7204fddcbec2fe6598f1c5fdf027e9f259106d05202a959a9f1aecf036adc9f6", size = 22480574, upload-time = "2026-01-10T21:24:47.266Z" }, { url = "https://files.pythonhosted.org/packages/85/bd/f5af70c28c6da2227e510875cadf64879855193a687fb19951f0f44cfd6b/scipy-1.17.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc02c37a5639ee67d8fb646ffded6d793c06c5622d36b35cfa8fe5ececb8f042", size = 32862414, upload-time = "2026-01-10T21:24:52.566Z" }, { url = "https://files.pythonhosted.org/packages/ef/df/df1457c4df3826e908879fe3d76bc5b6e60aae45f4ee42539512438cfd5d/scipy-1.17.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dac97a27520d66c12a34fd90a4fe65f43766c18c0d6e1c0a80f114d2260080e4", size = 35112380, upload-time = "2026-01-10T21:24:58.433Z" }, { url = "https://files.pythonhosted.org/packages/5f/bb/88e2c16bd1dd4de19d80d7c5e238387182993c2fb13b4b8111e3927ad422/scipy-1.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ebb7446a39b3ae0fe8f416a9a3fdc6fba3f11c634f680f16a239c5187bc487c0", size = 34922676, upload-time = "2026-01-10T21:25:04.287Z" }, { url = "https://files.pythonhosted.org/packages/02/ba/5120242cc735f71fc002cff0303d536af4405eb265f7c60742851e7ccfe9/scipy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:474da16199f6af66601a01546144922ce402cb17362e07d82f5a6cf8f963e449", size = 37507599, upload-time = "2026-01-10T21:25:09.851Z" }, { url = "https://files.pythonhosted.org/packages/52/c8/08629657ac6c0da198487ce8cd3de78e02cfde42b7f34117d56a3fe249dc/scipy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:255c0da161bd7b32a6c898e7891509e8a9289f0b1c6c7d96142ee0d2b114c2ea", size = 36380284, upload-time = "2026-01-10T21:25:15.632Z" }, { url = "https://files.pythonhosted.org/packages/6c/4a/465f96d42c6f33ad324a40049dfd63269891db9324aa66c4a1c108c6f994/scipy-1.17.0-cp311-cp311-win_arm64.whl", hash = "sha256:85b0ac3ad17fa3be50abd7e69d583d98792d7edc08367e01445a1e2076005379", size = 24370427, upload-time = "2026-01-10T21:25:20.514Z" }, { url = "https://files.pythonhosted.org/packages/0b/11/7241a63e73ba5a516f1930ac8d5b44cbbfabd35ac73a2d08ca206df007c4/scipy-1.17.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:0d5018a57c24cb1dd828bcf51d7b10e65986d549f52ef5adb6b4d1ded3e32a57", size = 31364580, upload-time = "2026-01-10T21:25:25.717Z" }, { url = "https://files.pythonhosted.org/packages/ed/1d/5057f812d4f6adc91a20a2d6f2ebcdb517fdbc87ae3acc5633c9b97c8ba5/scipy-1.17.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:88c22af9e5d5a4f9e027e26772cc7b5922fab8bcc839edb3ae33de404feebd9e", size = 27969012, upload-time = "2026-01-10T21:25:30.921Z" }, { url = "https://files.pythonhosted.org/packages/e3/21/f6ec556c1e3b6ec4e088da667d9987bb77cc3ab3026511f427dc8451187d/scipy-1.17.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f3cd947f20fe17013d401b64e857c6b2da83cae567adbb75b9dcba865abc66d8", size = 20140691, upload-time = "2026-01-10T21:25:34.802Z" }, { url = "https://files.pythonhosted.org/packages/7a/fe/5e5ad04784964ba964a96f16c8d4676aa1b51357199014dce58ab7ec5670/scipy-1.17.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e8c0b331c2c1f531eb51f1b4fc9ba709521a712cce58f1aa627bc007421a5306", size = 22463015, upload-time = "2026-01-10T21:25:39.277Z" }, { url = "https://files.pythonhosted.org/packages/4a/69/7c347e857224fcaf32a34a05183b9d8a7aca25f8f2d10b8a698b8388561a/scipy-1.17.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5194c445d0a1c7a6c1a4a4681b6b7c71baad98ff66d96b949097e7513c9d6742", size = 32724197, upload-time = "2026-01-10T21:25:44.084Z" }, { url = "https://files.pythonhosted.org/packages/d1/fe/66d73b76d378ba8cc2fe605920c0c75092e3a65ae746e1e767d9d020a75a/scipy-1.17.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9eeb9b5f5997f75507814ed9d298ab23f62cf79f5a3ef90031b1ee2506abdb5b", size = 35009148, upload-time = "2026-01-10T21:25:50.591Z" }, { url = "https://files.pythonhosted.org/packages/af/07/07dec27d9dc41c18d8c43c69e9e413431d20c53a0339c388bcf72f353c4b/scipy-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:40052543f7bbe921df4408f46003d6f01c6af109b9e2c8a66dd1cf6cf57f7d5d", size = 34798766, upload-time = "2026-01-10T21:25:59.41Z" }, { url = "https://files.pythonhosted.org/packages/81/61/0470810c8a093cdacd4ba7504b8a218fd49ca070d79eca23a615f5d9a0b0/scipy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0cf46c8013fec9d3694dc572f0b54100c28405d55d3e2cb15e2895b25057996e", size = 37405953, upload-time = "2026-01-10T21:26:07.75Z" }, { url = "https://files.pythonhosted.org/packages/92/ce/672ed546f96d5d41ae78c4b9b02006cedd0b3d6f2bf5bb76ea455c320c28/scipy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:0937a0b0d8d593a198cededd4c439a0ea216a3f36653901ea1f3e4be949056f8", size = 36328121, upload-time = "2026-01-10T21:26:16.509Z" }, { url = "https://files.pythonhosted.org/packages/9d/21/38165845392cae67b61843a52c6455d47d0cc2a40dd495c89f4362944654/scipy-1.17.0-cp312-cp312-win_arm64.whl", hash = "sha256:f603d8a5518c7426414d1d8f82e253e454471de682ce5e39c29adb0df1efb86b", size = 24314368, upload-time = "2026-01-10T21:26:23.087Z" }, { url = "https://files.pythonhosted.org/packages/0c/51/3468fdfd49387ddefee1636f5cf6d03ce603b75205bf439bbf0e62069bfd/scipy-1.17.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:65ec32f3d32dfc48c72df4291345dae4f048749bc8d5203ee0a3f347f96c5ce6", size = 31344101, upload-time = "2026-01-10T21:26:30.25Z" }, { url = "https://files.pythonhosted.org/packages/b2/9a/9406aec58268d437636069419e6977af953d1e246df941d42d3720b7277b/scipy-1.17.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:1f9586a58039d7229ce77b52f8472c972448cded5736eaf102d5658bbac4c269", size = 27950385, upload-time = "2026-01-10T21:26:36.801Z" }, { url = "https://files.pythonhosted.org/packages/4f/98/e7342709e17afdfd1b26b56ae499ef4939b45a23a00e471dfb5375eea205/scipy-1.17.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9fad7d3578c877d606b1150135c2639e9de9cecd3705caa37b66862977cc3e72", size = 20122115, upload-time = "2026-01-10T21:26:42.107Z" }, { url = "https://files.pythonhosted.org/packages/fd/0e/9eeeb5357a64fd157cbe0302c213517c541cc16b8486d82de251f3c68ede/scipy-1.17.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:423ca1f6584fc03936972b5f7c06961670dbba9f234e71676a7c7ccf938a0d61", size = 22442402, upload-time = "2026-01-10T21:26:48.029Z" }, { url = "https://files.pythonhosted.org/packages/c9/10/be13397a0e434f98e0c79552b2b584ae5bb1c8b2be95db421533bbca5369/scipy-1.17.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe508b5690e9eaaa9467fc047f833af58f1152ae51a0d0aed67aa5801f4dd7d6", size = 32696338, upload-time = "2026-01-10T21:26:55.521Z" }, { url = "https://files.pythonhosted.org/packages/63/1e/12fbf2a3bb240161651c94bb5cdd0eae5d4e8cc6eaeceb74ab07b12a753d/scipy-1.17.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6680f2dfd4f6182e7d6db161344537da644d1cf85cf293f015c60a17ecf08752", size = 34977201, upload-time = "2026-01-10T21:27:03.501Z" }, { url = "https://files.pythonhosted.org/packages/19/5b/1a63923e23ccd20bd32156d7dd708af5bbde410daa993aa2500c847ab2d2/scipy-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eec3842ec9ac9de5917899b277428886042a93db0b227ebbe3a333b64ec7643d", size = 34777384, upload-time = "2026-01-10T21:27:11.423Z" }, { url = "https://files.pythonhosted.org/packages/39/22/b5da95d74edcf81e540e467202a988c50fef41bd2011f46e05f72ba07df6/scipy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d7425fcafbc09a03731e1bc05581f5fad988e48c6a861f441b7ab729a49a55ea", size = 37379586, upload-time = "2026-01-10T21:27:20.171Z" }, { url = "https://files.pythonhosted.org/packages/b9/b6/8ac583d6da79e7b9e520579f03007cb006f063642afd6b2eeb16b890bf93/scipy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:87b411e42b425b84777718cc41516b8a7e0795abfa8e8e1d573bf0ef014f0812", size = 36287211, upload-time = "2026-01-10T21:28:43.122Z" }, { url = "https://files.pythonhosted.org/packages/55/fb/7db19e0b3e52f882b420417644ec81dd57eeef1bd1705b6f689d8ff93541/scipy-1.17.0-cp313-cp313-win_arm64.whl", hash = "sha256:357ca001c6e37601066092e7c89cca2f1ce74e2a520ca78d063a6d2201101df2", size = 24312646, upload-time = "2026-01-10T21:28:49.893Z" }, { url = "https://files.pythonhosted.org/packages/20/b6/7feaa252c21cc7aff335c6c55e1b90ab3e3306da3f048109b8b639b94648/scipy-1.17.0-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:ec0827aa4d36cb79ff1b81de898e948a51ac0b9b1c43e4a372c0508c38c0f9a3", size = 31693194, upload-time = "2026-01-10T21:27:27.454Z" }, { url = "https://files.pythonhosted.org/packages/76/bb/bbb392005abce039fb7e672cb78ac7d158700e826b0515cab6b5b60c26fb/scipy-1.17.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:819fc26862b4b3c73a60d486dbb919202f3d6d98c87cf20c223511429f2d1a97", size = 28365415, upload-time = "2026-01-10T21:27:34.26Z" }, { url = "https://files.pythonhosted.org/packages/37/da/9d33196ecc99fba16a409c691ed464a3a283ac454a34a13a3a57c0d66f3a/scipy-1.17.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:363ad4ae2853d88ebcde3ae6ec46ccca903ea9835ee8ba543f12f575e7b07e4e", size = 20537232, upload-time = "2026-01-10T21:27:40.306Z" }, { url = "https://files.pythonhosted.org/packages/56/9d/f4b184f6ddb28e9a5caea36a6f98e8ecd2a524f9127354087ce780885d83/scipy-1.17.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:979c3a0ff8e5ba254d45d59ebd38cde48fce4f10b5125c680c7a4bfe177aab07", size = 22791051, upload-time = "2026-01-10T21:27:46.539Z" }, { url = "https://files.pythonhosted.org/packages/9b/9d/025cccdd738a72140efc582b1641d0dd4caf2e86c3fb127568dc80444e6e/scipy-1.17.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:130d12926ae34399d157de777472bf82e9061c60cc081372b3118edacafe1d00", size = 32815098, upload-time = "2026-01-10T21:27:54.389Z" }, { url = "https://files.pythonhosted.org/packages/48/5f/09b879619f8bca15ce392bfc1894bd9c54377e01d1b3f2f3b595a1b4d945/scipy-1.17.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e886000eb4919eae3a44f035e63f0fd8b651234117e8f6f29bad1cd26e7bc45", size = 35031342, upload-time = "2026-01-10T21:28:03.012Z" }, { url = "https://files.pythonhosted.org/packages/f2/9a/f0f0a9f0aa079d2f106555b984ff0fbb11a837df280f04f71f056ea9c6e4/scipy-1.17.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13c4096ac6bc31d706018f06a49abe0485f96499deb82066b94d19b02f664209", size = 34893199, upload-time = "2026-01-10T21:28:10.832Z" }, { url = "https://files.pythonhosted.org/packages/90/b8/4f0f5cf0c5ea4d7548424e6533e6b17d164f34a6e2fb2e43ffebb6697b06/scipy-1.17.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cacbaddd91fcffde703934897c5cd2c7cb0371fac195d383f4e1f1c5d3f3bd04", size = 37438061, upload-time = "2026-01-10T21:28:19.684Z" }, { url = "https://files.pythonhosted.org/packages/f9/cc/2bd59140ed3b2fa2882fb15da0a9cb1b5a6443d67cfd0d98d4cec83a57ec/scipy-1.17.0-cp313-cp313t-win_amd64.whl", hash = "sha256:edce1a1cf66298cccdc48a1bdf8fb10a3bf58e8b58d6c3883dd1530e103f87c0", size = 36328593, upload-time = "2026-01-10T21:28:28.007Z" }, { url = "https://files.pythonhosted.org/packages/13/1b/c87cc44a0d2c7aaf0f003aef2904c3d097b422a96c7e7c07f5efd9073c1b/scipy-1.17.0-cp313-cp313t-win_arm64.whl", hash = "sha256:30509da9dbec1c2ed8f168b8d8aa853bc6723fede1dbc23c7d43a56f5ab72a67", size = 24625083, upload-time = "2026-01-10T21:28:35.188Z" }, { url = "https://files.pythonhosted.org/packages/1a/2d/51006cd369b8e7879e1c630999a19d1fbf6f8b5ed3e33374f29dc87e53b3/scipy-1.17.0-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:c17514d11b78be8f7e6331b983a65a7f5ca1fd037b95e27b280921fe5606286a", size = 31346803, upload-time = "2026-01-10T21:28:57.24Z" }, { url = "https://files.pythonhosted.org/packages/d6/2e/2349458c3ce445f53a6c93d4386b1c4c5c0c540917304c01222ff95ff317/scipy-1.17.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:4e00562e519c09da34c31685f6acc3aa384d4d50604db0f245c14e1b4488bfa2", size = 27967182, upload-time = "2026-01-10T21:29:04.107Z" }, { url = "https://files.pythonhosted.org/packages/5e/7c/df525fbfa77b878d1cfe625249529514dc02f4fd5f45f0f6295676a76528/scipy-1.17.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f7df7941d71314e60a481e02d5ebcb3f0185b8d799c70d03d8258f6c80f3d467", size = 20139125, upload-time = "2026-01-10T21:29:10.179Z" }, { url = "https://files.pythonhosted.org/packages/33/11/fcf9d43a7ed1234d31765ec643b0515a85a30b58eddccc5d5a4d12b5f194/scipy-1.17.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:aabf057c632798832f071a8dde013c2e26284043934f53b00489f1773b33527e", size = 22443554, upload-time = "2026-01-10T21:29:15.888Z" }, { url = "https://files.pythonhosted.org/packages/80/5c/ea5d239cda2dd3d31399424967a24d556cf409fbea7b5b21412b0fd0a44f/scipy-1.17.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a38c3337e00be6fd8a95b4ed66b5d988bac4ec888fd922c2ea9fe5fb1603dd67", size = 32757834, upload-time = "2026-01-10T21:29:23.406Z" }, { url = "https://files.pythonhosted.org/packages/b8/7e/8c917cc573310e5dc91cbeead76f1b600d3fb17cf0969db02c9cf92e3cfa/scipy-1.17.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00fb5f8ec8398ad90215008d8b6009c9db9fa924fd4c7d6be307c6f945f9cd73", size = 34995775, upload-time = "2026-01-10T21:29:31.915Z" }, { url = "https://files.pythonhosted.org/packages/c5/43/176c0c3c07b3f7df324e7cdd933d3e2c4898ca202b090bd5ba122f9fe270/scipy-1.17.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f2a4942b0f5f7c23c7cd641a0ca1955e2ae83dedcff537e3a0259096635e186b", size = 34841240, upload-time = "2026-01-10T21:29:39.995Z" }, { url = "https://files.pythonhosted.org/packages/44/8c/d1f5f4b491160592e7f084d997de53a8e896a3ac01cd07e59f43ca222744/scipy-1.17.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf133ced83889583156566d2bdf7a07ff89228fe0c0cb727f777de92092ec6b", size = 37394463, upload-time = "2026-01-10T21:29:48.723Z" }, { url = "https://files.pythonhosted.org/packages/9f/ec/42a6657f8d2d087e750e9a5dde0b481fd135657f09eaf1cf5688bb23c338/scipy-1.17.0-cp314-cp314-win_amd64.whl", hash = "sha256:3625c631a7acd7cfd929e4e31d2582cf00f42fcf06011f59281271746d77e061", size = 37053015, upload-time = "2026-01-10T21:30:51.418Z" }, { url = "https://files.pythonhosted.org/packages/27/58/6b89a6afd132787d89a362d443a7bddd511b8f41336a1ae47f9e4f000dc4/scipy-1.17.0-cp314-cp314-win_arm64.whl", hash = "sha256:9244608d27eafe02b20558523ba57f15c689357c85bdcfe920b1828750aa26eb", size = 24951312, upload-time = "2026-01-10T21:30:56.771Z" }, { url = "https://files.pythonhosted.org/packages/e9/01/f58916b9d9ae0112b86d7c3b10b9e685625ce6e8248df139d0fcb17f7397/scipy-1.17.0-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:2b531f57e09c946f56ad0b4a3b2abee778789097871fc541e267d2eca081cff1", size = 31706502, upload-time = "2026-01-10T21:29:56.326Z" }, { url = "https://files.pythonhosted.org/packages/59/8e/2912a87f94a7d1f8b38aabc0faf74b82d3b6c9e22be991c49979f0eceed8/scipy-1.17.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:13e861634a2c480bd237deb69333ac79ea1941b94568d4b0efa5db5e263d4fd1", size = 28380854, upload-time = "2026-01-10T21:30:01.554Z" }, { url = "https://files.pythonhosted.org/packages/bd/1c/874137a52dddab7d5d595c1887089a2125d27d0601fce8c0026a24a92a0b/scipy-1.17.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:eb2651271135154aa24f6481cbae5cc8af1f0dd46e6533fb7b56aa9727b6a232", size = 20552752, upload-time = "2026-01-10T21:30:05.93Z" }, { url = "https://files.pythonhosted.org/packages/3f/f0/7518d171cb735f6400f4576cf70f756d5b419a07fe1867da34e2c2c9c11b/scipy-1.17.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:c5e8647f60679790c2f5c76be17e2e9247dc6b98ad0d3b065861e082c56e078d", size = 22803972, upload-time = "2026-01-10T21:30:10.651Z" }, { url = "https://files.pythonhosted.org/packages/7c/74/3498563a2c619e8a3ebb4d75457486c249b19b5b04a30600dfd9af06bea5/scipy-1.17.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fb10d17e649e1446410895639f3385fd2bf4c3c7dfc9bea937bddcbc3d7b9ba", size = 32829770, upload-time = "2026-01-10T21:30:16.359Z" }, { url = "https://files.pythonhosted.org/packages/48/d1/7b50cedd8c6c9d6f706b4b36fa8544d829c712a75e370f763b318e9638c1/scipy-1.17.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8547e7c57f932e7354a2319fab613981cde910631979f74c9b542bb167a8b9db", size = 35051093, upload-time = "2026-01-10T21:30:22.987Z" }, { url = "https://files.pythonhosted.org/packages/e2/82/a2d684dfddb87ba1b3ea325df7c3293496ee9accb3a19abe9429bce94755/scipy-1.17.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33af70d040e8af9d5e7a38b5ed3b772adddd281e3062ff23fec49e49681c38cf", size = 34909905, upload-time = "2026-01-10T21:30:28.704Z" }, { url = "https://files.pythonhosted.org/packages/ef/5e/e565bd73991d42023eb82bb99e51c5b3d9e2c588ca9d4b3e2cc1d3ca62a6/scipy-1.17.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb55bb97d00f8b7ab95cb64f873eb0bf54d9446264d9f3609130381233483f", size = 37457743, upload-time = "2026-01-10T21:30:34.819Z" }, { url = "https://files.pythonhosted.org/packages/58/a8/a66a75c3d8f1fb2b83f66007d6455a06a6f6cf5618c3dc35bc9b69dd096e/scipy-1.17.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1ff269abf702f6c7e67a4b7aad981d42871a11b9dd83c58d2d2ea624efbd1088", size = 37098574, upload-time = "2026-01-10T21:30:40.782Z" }, { url = "https://files.pythonhosted.org/packages/56/a5/df8f46ef7da168f1bc52cd86e09a9de5c6f19cc1da04454d51b7d4f43408/scipy-1.17.0-cp314-cp314t-win_arm64.whl", hash = "sha256:031121914e295d9791319a1875444d55079885bbae5bdc9c5e0f2ee5f09d34ff", size = 25246266, upload-time = "2026-01-10T21:30:45.923Z" }, ] [[package]] name = "send2trash" version = "2.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/c5/f0/184b4b5f8d00f2a92cf96eec8967a3d550b52cf94362dad1100df9e48d57/send2trash-2.1.0.tar.gz", hash = "sha256:1c72b39f09457db3c05ce1d19158c2cbef4c32b8bedd02c155e49282b7ea7459", size = 17255, upload-time = "2026-01-14T06:27:36.056Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl", hash = "sha256:0da2f112e6d6bb22de6aa6daa7e144831a4febf2a87261451c4ad849fe9a873c", size = 17610, upload-time = "2026-01-14T06:27:35.218Z" }, ] [[package]] name = "setuptools" version = "80.10.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/76/95/faf61eb8363f26aa7e1d762267a8d602a1b26d4f3a1e758e92cb3cb8b054/setuptools-80.10.2.tar.gz", hash = "sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70", size = 1200343, upload-time = "2026-01-25T22:38:17.252Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl", hash = "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173", size = 1064234, upload-time = "2026-01-25T22:38:15.216Z" }, ] [[package]] name = "six" version = "1.17.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] [[package]] name = "snakeviz" version = "2.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tornado" }, ] sdist = { url = "https://files.pythonhosted.org/packages/04/06/82f56563b16d33c2586ac2615a3034a83a4ff1969b84c8d79339e5d07d73/snakeviz-2.2.2.tar.gz", hash = "sha256:08028c6f8e34a032ff14757a38424770abb8662fb2818985aeea0d9bc13a7d83", size = 182039, upload-time = "2024-11-09T22:03:58.99Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cd/f7/83b00cdf4f114f10750a18b64c27dc34636d0ac990ccac98282f5c0fbb43/snakeviz-2.2.2-py3-none-any.whl", hash = "sha256:77e7b9c82f6152edc330040319b97612351cd9b48c706434c535c2df31d10ac5", size = 183477, upload-time = "2024-11-09T22:03:57.049Z" }, ] [[package]] name = "snowballstemmer" version = "3.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, ] [[package]] name = "sortedcontainers" version = "2.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, ] [[package]] name = "soupsieve" version = "2.8.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, ] [[package]] name = "sparse" version = "0.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numba" }, { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/63/74/5c674277fc3d61bd1863d233a8e1f7ddf35cb1adeeaf9973888629e7a9b1/sparse-0.17.0.tar.gz", hash = "sha256:6b1ad51a810c5be40b6f95e28513ec810fe1c785923bd83b2e4839a751df4bf7", size = 642387, upload-time = "2025-05-20T08:17:03.256Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/8d/fd/42a1720542199ae6ff0f9c37bbd55dd3033ddd7bbe00d68cde09d6824887/sparse-0.17.0-py2.py3-none-any.whl", hash = "sha256:1922d1d97f692b1061c4f03a1dd6ee21850aedc88e171aa845715f5069952f18", size = 259370, upload-time = "2025-05-20T08:17:00.874Z" }, ] [[package]] name = "sphinx" version = "9.0.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.12' and platform_machine == 'ARM64' and sys_platform == 'win32'", "python_full_version < '3.12' and platform_machine != 'ARM64' and sys_platform == 'win32'", "python_full_version < '3.12' and sys_platform == 'emscripten'", "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ { name = "alabaster", marker = "python_full_version < '3.12'" }, { name = "babel", marker = "python_full_version < '3.12'" }, { name = "colorama", marker = "python_full_version < '3.12' and sys_platform == 'win32'" }, { name = "docutils", marker = "python_full_version < '3.12'" }, { name = "imagesize", marker = "python_full_version < '3.12'" }, { name = "jinja2", marker = "python_full_version < '3.12'" }, { name = "packaging", marker = "python_full_version < '3.12'" }, { name = "pygments", marker = "python_full_version < '3.12'" }, { name = "requests", marker = "python_full_version < '3.12'" }, { name = "roman-numerals", marker = "python_full_version < '3.12'" }, { name = "snowballstemmer", marker = "python_full_version < '3.12'" }, { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.12'" }, { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.12'" }, { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.12'" }, { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.12'" }, { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.12'" }, { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c6/3f/4bbd76424c393caead2e1eb89777f575dee5c8653e2d4b6afd7a564f5974/sphinx-9.0.4-py3-none-any.whl", hash = "sha256:5bebc595a5e943ea248b99c13814c1c5e10b3ece718976824ffa7959ff95fffb", size = 3917713, upload-time = "2025-12-04T07:45:24.944Z" }, ] [[package]] name = "sphinx" version = "9.1.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and platform_machine == 'ARM64' and sys_platform == 'win32'", "python_full_version >= '3.14' and platform_machine != 'ARM64' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine == 'ARM64' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine != 'ARM64' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ { name = "alabaster", marker = "python_full_version >= '3.12'" }, { name = "babel", marker = "python_full_version >= '3.12'" }, { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, { name = "docutils", marker = "python_full_version >= '3.12'" }, { name = "imagesize", marker = "python_full_version >= '3.12'" }, { name = "jinja2", marker = "python_full_version >= '3.12'" }, { name = "packaging", marker = "python_full_version >= '3.12'" }, { name = "pygments", marker = "python_full_version >= '3.12'" }, { name = "requests", marker = "python_full_version >= '3.12'" }, { name = "roman-numerals", marker = "python_full_version >= '3.12'" }, { name = "snowballstemmer", marker = "python_full_version >= '3.12'" }, { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.12'" }, { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.12'" }, { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.12'" }, { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.12'" }, { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.12'" }, { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, ] [[package]] name = "sphinx-basic-ng" version = "1.0.0b2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/0b/a866924ded68efec7a1759587a4e478aec7559d8165fac8b2ad1c0e774d6/sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9", size = 20736, upload-time = "2023-07-08T18:40:54.166Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3c/dd/018ce05c532a22007ac58d4f45232514cd9d6dd0ee1dc374e309db830983/sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b", size = 22496, upload-time = "2023-07-08T18:40:52.659Z" }, ] [[package]] name = "sphinx-codeautolink" version = "0.17.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/47/cf/96065111f706555ca3e0b5a7f57f47bae48983d48223d09f8800ba4b9b00/sphinx_codeautolink-0.17.5.tar.gz", hash = "sha256:75098c20aa32de01481fa62409e6d2fbfdde760e6696ae9beb233a77bcb49283", size = 59686, upload-time = "2025-07-09T15:22:35.452Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7b/e2/a7232766639b8a447e5e1884f6f33bd704d213bc5d4eb376f81cd5a0cb1d/sphinx_codeautolink-0.17.5-py3-none-any.whl", hash = "sha256:a84595ad3a12da93f82796e638f27e239d07b388037f3d3f69d65fc7ea6e05bf", size = 35399, upload-time = "2025-07-09T15:22:33.85Z" }, ] [[package]] name = "sphinx-copybutton" version = "0.5.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fc/2b/a964715e7f5295f77509e59309959f4125122d648f86b4fe7d70ca1d882c/sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd", size = 23039, upload-time = "2023-04-14T08:10:22.998Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" }, ] [[package]] name = "sphinx-remove-toctrees" version = "1.0.0.post1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9a/50/e3153912d4382bb434b3679a42eb96966f8b741789004412d1ceecbeee30/sphinx_remove_toctrees-1.0.0.post1.tar.gz", hash = "sha256:4808d1edf151c06eff6d2c3922ec7ebc9fd3aa1762de1b2e1674a37f5ac9ce2d", size = 8473, upload-time = "2024-03-22T11:00:03.727Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/bc/19/59dadac9ee5e8fa32b0d735b5488a569de36892a54e0b9927bea2b22f605/sphinx_remove_toctrees-1.0.0.post1-py3-none-any.whl", hash = "sha256:22a96579d7899cc034de194195916717a54a122cc101f16ec79c996fe0baa2be", size = 5200, upload-time = "2024-03-22T11:00:01.507Z" }, ] [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, ] [[package]] name = "sphinxcontrib-devhelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, ] [[package]] name = "sphinxcontrib-htmlhelp" version = "2.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, ] [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, ] [[package]] name = "sphinxcontrib-qthelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, ] [[package]] name = "sphinxcontrib-serializinghtml" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, ] [[package]] name = "sqlalchemy" version = "2.0.46" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/aa/9ce0f3e7a9829ead5c8ce549392f33a12c4555a6c0609bb27d882e9c7ddf/sqlalchemy-2.0.46.tar.gz", hash = "sha256:cf36851ee7219c170bb0793dbc3da3e80c582e04a5437bc601bfe8c85c9216d7", size = 9865393, upload-time = "2026-01-21T18:03:45.119Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/69/ac/b42ad16800d0885105b59380ad69aad0cce5a65276e269ce2729a2343b6a/sqlalchemy-2.0.46-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:261c4b1f101b4a411154f1da2b76497d73abbfc42740029205d4d01fa1052684", size = 2154851, upload-time = "2026-01-21T18:27:30.54Z" }, { url = "https://files.pythonhosted.org/packages/a0/60/d8710068cb79f64d002ebed62a7263c00c8fd95f4ebd4b5be8f7ca93f2bc/sqlalchemy-2.0.46-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:181903fe8c1b9082995325f1b2e84ac078b1189e2819380c2303a5f90e114a62", size = 3311241, upload-time = "2026-01-21T18:32:33.45Z" }, { url = "https://files.pythonhosted.org/packages/2b/0f/20c71487c7219ab3aa7421c7c62d93824c97c1460f2e8bb72404b0192d13/sqlalchemy-2.0.46-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:590be24e20e2424a4c3c1b0835e9405fa3d0af5823a1a9fc02e5dff56471515f", size = 3310741, upload-time = "2026-01-21T18:44:57.887Z" }, { url = "https://files.pythonhosted.org/packages/65/80/d26d00b3b249ae000eee4db206fcfc564bf6ca5030e4747adf451f4b5108/sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7568fe771f974abadce52669ef3a03150ff03186d8eb82613bc8adc435a03f01", size = 3263116, upload-time = "2026-01-21T18:32:35.044Z" }, { url = "https://files.pythonhosted.org/packages/da/ee/74dda7506640923821340541e8e45bd3edd8df78664f1f2e0aae8077192b/sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf7e1e78af38047e08836d33502c7a278915698b7c2145d045f780201679999", size = 3285327, upload-time = "2026-01-21T18:44:59.254Z" }, { url = "https://files.pythonhosted.org/packages/9f/25/6dcf8abafff1389a21c7185364de145107b7394ecdcb05233815b236330d/sqlalchemy-2.0.46-cp311-cp311-win32.whl", hash = "sha256:9d80ea2ac519c364a7286e8d765d6cd08648f5b21ca855a8017d9871f075542d", size = 2114564, upload-time = "2026-01-21T18:33:15.85Z" }, { url = "https://files.pythonhosted.org/packages/93/5f/e081490f8523adc0088f777e4ebad3cac21e498ec8a3d4067074e21447a1/sqlalchemy-2.0.46-cp311-cp311-win_amd64.whl", hash = "sha256:585af6afe518732d9ccd3aea33af2edaae4a7aa881af5d8f6f4fe3a368699597", size = 2139233, upload-time = "2026-01-21T18:33:17.528Z" }, { url = "https://files.pythonhosted.org/packages/b6/35/d16bfa235c8b7caba3730bba43e20b1e376d2224f407c178fbf59559f23e/sqlalchemy-2.0.46-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a9a72b0da8387f15d5810f1facca8f879de9b85af8c645138cba61ea147968c", size = 2153405, upload-time = "2026-01-21T19:05:54.143Z" }, { url = "https://files.pythonhosted.org/packages/06/6c/3192e24486749862f495ddc6584ed730c0c994a67550ec395d872a2ad650/sqlalchemy-2.0.46-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2347c3f0efc4de367ba00218e0ae5c4ba2306e47216ef80d6e31761ac97cb0b9", size = 3334702, upload-time = "2026-01-21T18:46:45.384Z" }, { url = "https://files.pythonhosted.org/packages/ea/a2/b9f33c8d68a3747d972a0bb758c6b63691f8fb8a49014bc3379ba15d4274/sqlalchemy-2.0.46-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9094c8b3197db12aa6f05c51c05daaad0a92b8c9af5388569847b03b1007fb1b", size = 3347664, upload-time = "2026-01-21T18:40:09.979Z" }, { url = "https://files.pythonhosted.org/packages/aa/d2/3e59e2a91eaec9db7e8dc6b37b91489b5caeb054f670f32c95bcba98940f/sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37fee2164cf21417478b6a906adc1a91d69ae9aba8f9533e67ce882f4bb1de53", size = 3277372, upload-time = "2026-01-21T18:46:47.168Z" }, { url = "https://files.pythonhosted.org/packages/dd/dd/67bc2e368b524e2192c3927b423798deda72c003e73a1e94c21e74b20a85/sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b1e14b2f6965a685c7128bd315e27387205429c2e339eeec55cb75ca4ab0ea2e", size = 3312425, upload-time = "2026-01-21T18:40:11.548Z" }, { url = "https://files.pythonhosted.org/packages/43/82/0ecd68e172bfe62247e96cb47867c2d68752566811a4e8c9d8f6e7c38a65/sqlalchemy-2.0.46-cp312-cp312-win32.whl", hash = "sha256:412f26bb4ba942d52016edc8d12fb15d91d3cd46b0047ba46e424213ad407bcb", size = 2113155, upload-time = "2026-01-21T18:42:49.748Z" }, { url = "https://files.pythonhosted.org/packages/bc/2a/2821a45742073fc0331dc132552b30de68ba9563230853437cac54b2b53e/sqlalchemy-2.0.46-cp312-cp312-win_amd64.whl", hash = "sha256:ea3cd46b6713a10216323cda3333514944e510aa691c945334713fca6b5279ff", size = 2140078, upload-time = "2026-01-21T18:42:51.197Z" }, { url = "https://files.pythonhosted.org/packages/b3/4b/fa7838fe20bb752810feed60e45625a9a8b0102c0c09971e2d1d95362992/sqlalchemy-2.0.46-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93a12da97cca70cea10d4b4fc602589c4511f96c1f8f6c11817620c021d21d00", size = 2150268, upload-time = "2026-01-21T19:05:56.621Z" }, { url = "https://files.pythonhosted.org/packages/46/c1/b34dccd712e8ea846edf396e00973dda82d598cb93762e55e43e6835eba9/sqlalchemy-2.0.46-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af865c18752d416798dae13f83f38927c52f085c52e2f32b8ab0fef46fdd02c2", size = 3276511, upload-time = "2026-01-21T18:46:49.022Z" }, { url = "https://files.pythonhosted.org/packages/96/48/a04d9c94753e5d5d096c628c82a98c4793b9c08ca0e7155c3eb7d7db9f24/sqlalchemy-2.0.46-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d679b5f318423eacb61f933a9a0f75535bfca7056daeadbf6bd5bcee6183aee", size = 3292881, upload-time = "2026-01-21T18:40:13.089Z" }, { url = "https://files.pythonhosted.org/packages/be/f4/06eda6e91476f90a7d8058f74311cb65a2fb68d988171aced81707189131/sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64901e08c33462acc9ec3bad27fc7a5c2b6491665f2aa57564e57a4f5d7c52ad", size = 3224559, upload-time = "2026-01-21T18:46:50.974Z" }, { url = "https://files.pythonhosted.org/packages/ab/a2/d2af04095412ca6345ac22b33b89fe8d6f32a481e613ffcb2377d931d8d0/sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8ac45e8f4eaac0f9f8043ea0e224158855c6a4329fd4ee37c45c61e3beb518e", size = 3262728, upload-time = "2026-01-21T18:40:14.883Z" }, { url = "https://files.pythonhosted.org/packages/31/48/1980c7caa5978a3b8225b4d230e69a2a6538a3562b8b31cea679b6933c83/sqlalchemy-2.0.46-cp313-cp313-win32.whl", hash = "sha256:8d3b44b3d0ab2f1319d71d9863d76eeb46766f8cf9e921ac293511804d39813f", size = 2111295, upload-time = "2026-01-21T18:42:52.366Z" }, { url = "https://files.pythonhosted.org/packages/2d/54/f8d65bbde3d877617c4720f3c9f60e99bb7266df0d5d78b6e25e7c149f35/sqlalchemy-2.0.46-cp313-cp313-win_amd64.whl", hash = "sha256:77f8071d8fbcbb2dd11b7fd40dedd04e8ebe2eb80497916efedba844298065ef", size = 2137076, upload-time = "2026-01-21T18:42:53.924Z" }, { url = "https://files.pythonhosted.org/packages/56/ba/9be4f97c7eb2b9d5544f2624adfc2853e796ed51d2bb8aec90bc94b7137e/sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1e8cc6cc01da346dc92d9509a63033b9b1bda4fed7a7a7807ed385c7dccdc10", size = 3556533, upload-time = "2026-01-21T18:33:06.636Z" }, { url = "https://files.pythonhosted.org/packages/20/a6/b1fc6634564dbb4415b7ed6419cdfeaadefd2c39cdab1e3aa07a5f2474c2/sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96c7cca1a4babaaf3bfff3e4e606e38578856917e52f0384635a95b226c87764", size = 3523208, upload-time = "2026-01-21T18:45:08.436Z" }, { url = "https://files.pythonhosted.org/packages/a1/d8/41e0bdfc0f930ff236f86fccd12962d8fa03713f17ed57332d38af6a3782/sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2a9f9aee38039cf4755891a1e50e1effcc42ea6ba053743f452c372c3152b1b", size = 3464292, upload-time = "2026-01-21T18:33:08.208Z" }, { url = "https://files.pythonhosted.org/packages/f0/8b/9dcbec62d95bea85f5ecad9b8d65b78cc30fb0ffceeb3597961f3712549b/sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db23b1bf8cfe1f7fda19018e7207b20cdb5168f83c437ff7e95d19e39289c447", size = 3473497, upload-time = "2026-01-21T18:45:10.552Z" }, { url = "https://files.pythonhosted.org/packages/e9/f8/5ecdfc73383ec496de038ed1614de9e740a82db9ad67e6e4514ebc0708a3/sqlalchemy-2.0.46-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:56bdd261bfd0895452006d5316cbf35739c53b9bb71a170a331fa0ea560b2ada", size = 2152079, upload-time = "2026-01-21T19:05:58.477Z" }, { url = "https://files.pythonhosted.org/packages/e5/bf/eba3036be7663ce4d9c050bc3d63794dc29fbe01691f2bf5ccb64e048d20/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33e462154edb9493f6c3ad2125931e273bbd0be8ae53f3ecd1c161ea9a1dd366", size = 3272216, upload-time = "2026-01-21T18:46:52.634Z" }, { url = "https://files.pythonhosted.org/packages/05/45/1256fb597bb83b58a01ddb600c59fe6fdf0e5afe333f0456ed75c0f8d7bd/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bcdce05f056622a632f1d44bb47dbdb677f58cad393612280406ce37530eb6d", size = 3277208, upload-time = "2026-01-21T18:40:16.38Z" }, { url = "https://files.pythonhosted.org/packages/d9/a0/2053b39e4e63b5d7ceb3372cface0859a067c1ddbd575ea7e9985716f771/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e84b09a9b0f19accedcbeff5c2caf36e0dd537341a33aad8d680336152dc34e", size = 3221994, upload-time = "2026-01-21T18:46:54.622Z" }, { url = "https://files.pythonhosted.org/packages/1e/87/97713497d9502553c68f105a1cb62786ba1ee91dea3852ae4067ed956a50/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4f52f7291a92381e9b4de9050b0a65ce5d6a763333406861e33906b8aa4906bf", size = 3243990, upload-time = "2026-01-21T18:40:18.253Z" }, { url = "https://files.pythonhosted.org/packages/a8/87/5d1b23548f420ff823c236f8bea36b1a997250fd2f892e44a3838ca424f4/sqlalchemy-2.0.46-cp314-cp314-win32.whl", hash = "sha256:70ed2830b169a9960193f4d4322d22be5c0925357d82cbf485b3369893350908", size = 2114215, upload-time = "2026-01-21T18:42:55.232Z" }, { url = "https://files.pythonhosted.org/packages/3a/20/555f39cbcf0c10cf452988b6a93c2a12495035f68b3dbd1a408531049d31/sqlalchemy-2.0.46-cp314-cp314-win_amd64.whl", hash = "sha256:3c32e993bc57be6d177f7d5d31edb93f30726d798ad86ff9066d75d9bf2e0b6b", size = 2139867, upload-time = "2026-01-21T18:42:56.474Z" }, { url = "https://files.pythonhosted.org/packages/3e/f0/f96c8057c982d9d8a7a68f45d69c674bc6f78cad401099692fe16521640a/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4dafb537740eef640c4d6a7c254611dca2df87eaf6d14d6a5fca9d1f4c3fc0fa", size = 3561202, upload-time = "2026-01-21T18:33:10.337Z" }, { url = "https://files.pythonhosted.org/packages/d7/53/3b37dda0a5b137f21ef608d8dfc77b08477bab0fe2ac9d3e0a66eaeab6fc/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42a1643dc5427b69aca967dae540a90b0fbf57eaf248f13a90ea5930e0966863", size = 3526296, upload-time = "2026-01-21T18:45:12.657Z" }, { url = "https://files.pythonhosted.org/packages/33/75/f28622ba6dde79cd545055ea7bd4062dc934e0621f7b3be2891f8563f8de/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ff33c6e6ad006bbc0f34f5faf941cfc62c45841c64c0a058ac38c799f15b5ede", size = 3470008, upload-time = "2026-01-21T18:33:11.725Z" }, { url = "https://files.pythonhosted.org/packages/a9/42/4afecbbc38d5e99b18acef446453c76eec6fbd03db0a457a12a056836e22/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82ec52100ec1e6ec671563bbd02d7c7c8d0b9e71a0723c72f22ecf52d1755330", size = 3476137, upload-time = "2026-01-21T18:45:15.001Z" }, { url = "https://files.pythonhosted.org/packages/fc/a1/9c4efa03300926601c19c18582531b45aededfb961ab3c3585f1e24f120b/sqlalchemy-2.0.46-py3-none-any.whl", hash = "sha256:f9c11766e7e7c0a2767dda5acb006a118640c9fc0a4104214b96269bfb78399e", size = 1937882, upload-time = "2026-01-21T18:22:10.456Z" }, ] [[package]] name = "stack-data" version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asttokens" }, { name = "executing" }, { name = "pure-eval" }, ] sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, ] [[package]] name = "syrupy" version = "5.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2e/b0/24bca682d6a6337854be37f242d116cceeda9942571d5804c44bc1bdd427/syrupy-5.1.0.tar.gz", hash = "sha256:df543c7aa50d3cf1246e83d58fe490afe5f7dab7b41e74ecc0d8d23ae19bd4b8", size = 50495, upload-time = "2026-01-25T14:53:06.2Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/de/70/cf880c3b95a6034ef673e74b369941b42315c01f1554a5637a4f8b911009/syrupy-5.1.0-py3-none-any.whl", hash = "sha256:95162d2b05e61ed3e13f117b88dfab7c58bd6f90e66ebbf918e8a77114ad51c5", size = 51658, upload-time = "2026-01-25T14:53:05.105Z" }, ] [[package]] name = "tabulate" version = "0.9.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, ] [[package]] name = "tblib" version = "3.2.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f4/8a/14c15ae154895cc131174f858c707790d416c444fc69f93918adfd8c4c0b/tblib-3.2.2.tar.gz", hash = "sha256:e9a652692d91bf4f743d4a15bc174c0b76afc750fe8c7b6d195cc1c1d6d2ccec", size = 35046, upload-time = "2025-11-12T12:21:16.572Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl", hash = "sha256:26bdccf339bcce6a88b2b5432c988b266ebbe63a4e593f6b578b1d2e723d2b76", size = 12893, upload-time = "2025-11-12T12:21:14.407Z" }, ] [[package]] name = "tenacity" version = "9.1.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, ] [[package]] name = "terminado" version = "0.18.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ptyprocess", marker = "os_name != 'nt'" }, { name = "pywinpty", marker = "os_name == 'nt'" }, { name = "tornado" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701, upload-time = "2024-03-12T14:34:39.026Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154, upload-time = "2024-03-12T14:34:36.569Z" }, ] [[package]] name = "textual" version = "7.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py", extra = ["linkify"] }, { name = "mdit-py-plugins" }, { name = "platformdirs" }, { name = "pygments" }, { name = "rich" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/38/7d169a765993efde5095c70a668bf4f5831bb7ac099e932f2783e9b71abf/textual-7.5.0.tar.gz", hash = "sha256:c730cba1e3d704e8f1ca915b6a3af01451e3bca380114baacf6abf87e9dac8b6", size = 1592319, upload-time = "2026-01-30T13:46:39.881Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9c/78/96ddb99933e11d91bc6e05edae23d2687e44213066bcbaca338898c73c47/textual-7.5.0-py3-none-any.whl", hash = "sha256:849dfee9d705eab3b2d07b33152b7bd74fb1f5056e002873cc448bce500c6374", size = 718164, upload-time = "2026-01-30T13:46:37.635Z" }, ] [[package]] name = "tinycss2" version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "webencodings" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" }, ] [[package]] name = "tomli" version = "2.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, ] [[package]] name = "toolz" version = "1.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/11/d6/114b492226588d6ff54579d95847662fc69196bdeec318eb45393b24c192/toolz-1.1.0.tar.gz", hash = "sha256:27a5c770d068c110d9ed9323f24f1543e83b2f300a687b7891c1a6d56b697b5b", size = 52613, upload-time = "2025-10-17T04:03:21.661Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl", hash = "sha256:15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8", size = 58093, upload-time = "2025-10-17T04:03:20.435Z" }, ] [[package]] name = "tornado" version = "6.5.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" }, { url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" }, { url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" }, { url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" }, { url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" }, { url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" }, { url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" }, { url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" }, { url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" }, { url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" }, { url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" }, ] [[package]] name = "traitlets" version = "5.14.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, ] [[package]] name = "types-defusedxml" version = "0.7.0.20250822" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7d/4a/5b997ae87bf301d1796f72637baa4e0e10d7db17704a8a71878a9f77f0c0/types_defusedxml-0.7.0.20250822.tar.gz", hash = "sha256:ba6c395105f800c973bba8a25e41b215483e55ec79c8ca82b6fe90ba0bc3f8b2", size = 10590, upload-time = "2025-08-22T03:02:59.547Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/13/73/8a36998cee9d7c9702ed64a31f0866c7f192ecffc22771d44dbcc7878f18/types_defusedxml-0.7.0.20250822-py3-none-any.whl", hash = "sha256:5ee219f8a9a79c184773599ad216123aedc62a969533ec36737ec98601f20dcf", size = 13430, upload-time = "2025-08-22T03:02:58.466Z" }, ] [[package]] name = "types-docutils" version = "0.22.3.20251115" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/eb/d7/576ec24bf61a280f571e1f22284793adc321610b9bcfba1bf468cf7b334f/types_docutils-0.22.3.20251115.tar.gz", hash = "sha256:0f79ea6a7bd4d12d56c9f824a0090ffae0ea4204203eb0006392906850913e16", size = 56828, upload-time = "2025-11-15T02:59:57.371Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9c/01/61ac9eb38f1f978b47443dc6fd2e0a3b0f647c2da741ddad30771f1b2b6f/types_docutils-0.22.3.20251115-py3-none-any.whl", hash = "sha256:c6e53715b65395d00a75a3a8a74e352c669bc63959e65a207dffaa22f4a2ad6e", size = 91951, upload-time = "2025-11-15T02:59:56.413Z" }, ] [[package]] name = "types-pexpect" version = "4.9.0.20260127" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2e/32/7e03a07e16f79a404d6200ed6bdfcc320d0fb833436a5c6895a1403dedb7/types_pexpect-4.9.0.20260127.tar.gz", hash = "sha256:f8d43efc24251a8e533c71ea9be03d19bb5d08af096d561611697af9720cba7f", size = 13461, upload-time = "2026-01-27T03:28:30.923Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/8a/d9/7ac5c9aa5a89a1a64cd835ae348227f4939406d826e461b85b690a8ba1c2/types_pexpect-4.9.0.20260127-py3-none-any.whl", hash = "sha256:69216c0ebf0fe45ad2900823133959b027e9471e24fc3f2e4c7b00605555da5f", size = 17078, upload-time = "2026-01-27T03:28:29.848Z" }, ] [[package]] name = "types-psutil" version = "7.2.2.20260130" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/69/14/fc5fb0a6ddfadf68c27e254a02ececd4d5c7fdb0efcb7e7e917a183497fb/types_psutil-7.2.2.20260130.tar.gz", hash = "sha256:15b0ab69c52841cf9ce3c383e8480c620a4d13d6a8e22b16978ebddac5590950", size = 26535, upload-time = "2026-01-30T03:58:14.116Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/17/d7/60974b7e31545d3768d1770c5fe6e093182c3bfd819429b33133ba6b3e89/types_psutil-7.2.2.20260130-py3-none-any.whl", hash = "sha256:15523a3caa7b3ff03ac7f9b78a6470a59f88f48df1d74a39e70e06d2a99107da", size = 32876, upload-time = "2026-01-30T03:58:13.172Z" }, ] [[package]] name = "types-pygments" version = "2.19.0.20251121" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "types-docutils" }, ] sdist = { url = "https://files.pythonhosted.org/packages/90/3b/cd650700ce9e26b56bd1a6aa4af397bbbc1784e22a03971cb633cdb0b601/types_pygments-2.19.0.20251121.tar.gz", hash = "sha256:eef114fde2ef6265365522045eac0f8354978a566852f69e75c531f0553822b1", size = 18590, upload-time = "2025-11-21T03:03:46.623Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/99/8a/9244b21f1d60dcc62e261435d76b02f1853b4771663d7ec7d287e47a9ba9/types_pygments-2.19.0.20251121-py3-none-any.whl", hash = "sha256:cb3bfde34eb75b984c98fb733ce4f795213bd3378f855c32e75b49318371bb25", size = 25674, upload-time = "2025-11-21T03:03:45.72Z" }, ] [[package]] name = "types-pytz" version = "2025.2.0.20251108" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/40/ff/c047ddc68c803b46470a357454ef76f4acd8c1088f5cc4891cdd909bfcf6/types_pytz-2025.2.0.20251108.tar.gz", hash = "sha256:fca87917836ae843f07129567b74c1929f1870610681b4c92cb86a3df5817bdb", size = 10961, upload-time = "2025-11-08T02:55:57.001Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e7/c1/56ef16bf5dcd255155cc736d276efa6ae0a5c26fd685e28f0412a4013c01/types_pytz-2025.2.0.20251108-py3-none-any.whl", hash = "sha256:0f1c9792cab4eb0e46c52f8845c8f77cf1e313cb3d68bf826aa867fe4717d91c", size = 10116, upload-time = "2025-11-08T02:55:56.194Z" }, ] [[package]] name = "types-pyyaml" version = "6.0.12.20250915" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" }, ] [[package]] name = "types-requests" version = "2.32.4.20260107" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/f3/a0663907082280664d745929205a89d41dffb29e89a50f753af7d57d0a96/types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f", size = 23165, upload-time = "2026-01-07T03:20:54.091Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/1c/12/709ea261f2bf91ef0a26a9eed20f2623227a8ed85610c1e54c5805692ecb/types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d", size = 20676, upload-time = "2026-01-07T03:20:52.929Z" }, ] [[package]] name = "types-setuptools" version = "80.10.0.20260124" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/aa/7e/116539b9610585e34771611e33c88a4c706491fa3565500f5a63139f8731/types_setuptools-80.10.0.20260124.tar.gz", hash = "sha256:1b86d9f0368858663276a0cbe5fe5a9722caf94b5acde8aba0399a6e90680f20", size = 43299, upload-time = "2026-01-24T03:18:39.527Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2b/7f/016dc5cc718ec6ccaa84fb73ed409ef1c261793fd5e637cdfaa18beb40a9/types_setuptools-80.10.0.20260124-py3-none-any.whl", hash = "sha256:efed7e044f01adb9c2806c7a8e1b6aa3656b8e382379b53d5f26ee3db24d4c01", size = 64333, upload-time = "2026-01-24T03:18:38.344Z" }, ] [[package]] name = "typing-extensions" version = "4.15.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] [[package]] name = "tzdata" version = "2025.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, ] [[package]] name = "uc-micro-py" version = "1.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/91/7a/146a99696aee0609e3712f2b44c6274566bc368dfe8375191278045186b8/uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a", size = 6043, upload-time = "2024-02-09T16:52:01.654Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/37/87/1f677586e8ac487e29672e4b17455758fce261de06a0d086167bb760361a/uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5", size = 6229, upload-time = "2024-02-09T16:52:00.371Z" }, ] [[package]] name = "uri-template" version = "1.3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" }, ] [[package]] name = "urllib3" version = "2.6.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] [[package]] name = "virtualenv" version = "20.36.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, ] sdist = { url = "https://files.pythonhosted.org/packages/aa/a3/4d310fa5f00863544e1d0f4de93bddec248499ccf97d4791bc3122c9d4f3/virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba", size = 6032239, upload-time = "2026-01-09T18:21:01.296Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f", size = 6008258, upload-time = "2026-01-09T18:20:59.425Z" }, ] [[package]] name = "wcwidth" version = "0.5.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/c2/62/a7c072fbfefb2980a00f99ca994279cb9ecf310cb2e6b2a4d2a28fe192b3/wcwidth-0.5.3.tar.gz", hash = "sha256:53123b7af053c74e9fe2e92ac810301f6139e64379031f7124574212fb3b4091", size = 157587, upload-time = "2026-01-31T03:52:10.92Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3c/c1/d73f12f8cdb1891334a2ccf7389eed244d3941e74d80dd220badb937f3fb/wcwidth-0.5.3-py3-none-any.whl", hash = "sha256:d584eff31cd4753e1e5ff6c12e1edfdb324c995713f75d26c29807bb84bf649e", size = 92981, upload-time = "2026-01-31T03:52:09.14Z" }, ] [[package]] name = "webcolors" version = "25.10.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1d/7a/eb316761ec35664ea5174709a68bbd3389de60d4a1ebab8808bfc264ed67/webcolors-25.10.0.tar.gz", hash = "sha256:62abae86504f66d0f6364c2a8520de4a0c47b80c03fc3a5f1815fedbef7c19bf", size = 53491, upload-time = "2025-10-31T07:51:03.977Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d", size = 14905, upload-time = "2025-10-31T07:51:01.778Z" }, ] [[package]] name = "webencodings" version = "0.5.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, ] [[package]] name = "websocket-client" version = "1.9.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, ] [[package]] name = "wheel" version = "0.46.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] sdist = { url = "https://files.pythonhosted.org/packages/89/24/a2eb353a6edac9a0303977c4cb048134959dd2a51b48a269dfc9dde00c8a/wheel-0.46.3.tar.gz", hash = "sha256:e3e79874b07d776c40bd6033f8ddf76a7dad46a7b8aa1b2787a83083519a1803", size = 60605, upload-time = "2026-01-22T12:39:49.136Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/87/22/b76d483683216dde3d67cba61fb2444be8d5be289bf628c13fc0fd90e5f9/wheel-0.46.3-py3-none-any.whl", hash = "sha256:4b399d56c9d9338230118d705d9737a2a468ccca63d5e813e2a4fc7815d8bc4d", size = 30557, upload-time = "2026-01-22T12:39:48.099Z" }, ] [[package]] name = "widgetsnbextension" version = "4.0.15" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/bd/f4/c67440c7fb409a71b7404b7aefcd7569a9c0d6bd071299bf4198ae7a5d95/widgetsnbextension-4.0.15.tar.gz", hash = "sha256:de8610639996f1567952d763a5a41af8af37f2575a41f9852a38f947eb82a3b9", size = 1097402, upload-time = "2025-11-01T21:15:55.178Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl", hash = "sha256:8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366", size = 2196503, upload-time = "2025-11-01T21:15:53.565Z" }, ] [[package]] name = "xarray" version = "2026.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "packaging" }, { name = "pandas" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f5/85/113ff1e2cde9e8a5b13c2f0ef4e9f5cd6ca3a036b6452f4dd523419289b5/xarray-2026.1.0.tar.gz", hash = "sha256:0c9814761f9d9a9545df37292d3fda89f83201f3e02ae0f09f03313d9cfdd5e2", size = 3107024, upload-time = "2026-01-28T17:49:03.822Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7d/8e/952a351c10df395d9bab850f611f4368834ae9104d6449049f5a49e00925/xarray-2026.1.0-py3-none-any.whl", hash = "sha256:5fcc03d3ed8dfb662aa254efe6cd65efc70014182bbc2126e4b90d291d970d41", size = 1403009, upload-time = "2026-01-28T17:49:01.538Z" }, ] [[package]] name = "xyzservices" version = "2025.11.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ee/0f/022795fc1201e7c29e742a509913badb53ce0b38f64b6db859e2f6339da9/xyzservices-2025.11.0.tar.gz", hash = "sha256:2fc72b49502b25023fd71e8f532fb4beddbbf0aa124d90ea25dba44f545e17ce", size = 1135703, upload-time = "2025-11-22T11:31:51.82Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/5c/2c189d18d495dd0fa3f27ccc60762bbc787eed95b9b0147266e72bb76585/xyzservices-2025.11.0-py3-none-any.whl", hash = "sha256:de66a7599a8d6dad63980b77defd1d8f5a5a9cb5fc8774ea1c6e89ca7c2a3d2f", size = 93916, upload-time = "2025-11-22T11:31:50.525Z" }, ] [[package]] name = "zarr" version = "3.1.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "donfig" }, { name = "google-crc32c" }, { name = "numcodecs" }, { name = "numpy" }, { name = "packaging" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fc/76/7fa87f57c112c7b9c82f0a730f8b6f333e792574812872e2cd45ab604199/zarr-3.1.5.tar.gz", hash = "sha256:fbe0c79675a40c996de7ca08e80a1c0a20537bd4a9f43418b6d101395c0bba2b", size = 366825, upload-time = "2025-11-21T14:06:01.492Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/44/15/bb13b4913ef95ad5448490821eee4671d0e67673342e4d4070854e5fe081/zarr-3.1.5-py3-none-any.whl", hash = "sha256:29cd905afb6235b94c09decda4258c888fcb79bb6c862ef7c0b8fe009b5c8563", size = 284067, upload-time = "2025-11-21T14:05:59.235Z" }, ] [[package]] name = "zict" version = "3.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d1/ac/3c494dd7ec5122cff8252c1a209b282c0867af029f805ae9befd73ae37eb/zict-3.0.0.tar.gz", hash = "sha256:e321e263b6a97aafc0790c3cfb3c04656b7066e6738c37fffcca95d803c9fba5", size = 33238, upload-time = "2023-04-17T21:41:16.041Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/80/ab/11a76c1e2126084fde2639514f24e6111b789b0bfa4fc6264a8975c7e1f1/zict-3.0.0-py2.py3-none-any.whl", hash = "sha256:5796e36bd0e0cc8cf0fbc1ace6a68912611c1dbd74750a3f3026b9b9d6a327ae", size = 43332, upload-time = "2023-04-17T21:41:13.444Z" }, ] [[package]] name = "zipp" version = "3.23.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, ]